From d37697d4929b958aa7a85c7a1409bcf656289cee Mon Sep 17 00:00:00 2001 From: sentientstardust Date: Mon, 18 May 2026 06:28:14 +0100 Subject: [PATCH] Add texture mapping halftone mode --- src/libslic3r/GCode.cpp | 261 +++++++++++++++++--- src/libslic3r/TextureMapping.cpp | 16 +- src/libslic3r/TextureMapping.hpp | 8 +- src/slic3r/GUI/MMUPaintedTexturePreview.cpp | 25 +- src/slic3r/GUI/Plater.cpp | 46 ++-- 5 files changed, 297 insertions(+), 59 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f13f441a5f9..cd57974eff0 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -7059,7 +7059,9 @@ static float ordered_bayer_threshold_for_gcode(int x, int y) static float halftone_threshold_for_gcode(float x_mm, float y_mm, float dot_size_mm) { - const float period = std::clamp(dot_size_mm, 0.08f, 2.f); + const float period = std::clamp(dot_size_mm, + TextureMappingZone::MinHalftoneDotSizeMm, + TextureMappingZone::MaxHalftoneDotSizeMm); const float u = x_mm / period - std::floor(x_mm / period); const float v = y_mm / period - std::floor(y_mm / period); const float dx = u - 0.5f; @@ -7068,6 +7070,66 @@ static float halftone_threshold_for_gcode(float x_mm, float y_mm, float dot_size return std::clamp(radius_area, 0.f, 1.f) - 0.5f; } +static float halftone_screen_angle_deg_for_gcode(int filament_color_mode, size_t component_idx) +{ + const int clamped_mode = + std::clamp(filament_color_mode, int(TextureMappingZone::FilamentColorAny), int(TextureMappingZone::FilamentColorRGBKW)); + if (component_idx < 3) { + static constexpr float primary_angles[3] = {15.f, 75.f, 0.f}; + return primary_angles[component_idx]; + } + + switch (clamped_mode) { + case int(TextureMappingZone::FilamentColorCMYK): + case int(TextureMappingZone::FilamentColorRGBK): + case int(TextureMappingZone::FilamentColorCMYKW): + case int(TextureMappingZone::FilamentColorRGBKW): + if (component_idx == 3) + return 45.f; + if (component_idx == 4) + return 90.f; + break; + case int(TextureMappingZone::FilamentColorCMYW): + case int(TextureMappingZone::FilamentColorRGBW): + if (component_idx == 3) + return 90.f; + break; + case int(TextureMappingZone::FilamentColorBW): + if (component_idx == 0) + return 45.f; + if (component_idx == 1) + return 90.f; + break; + default: + break; + } + + static constexpr float fallback_angles[] = {15.f, 75.f, 0.f, 45.f, 90.f, 30.f, 60.f, 105.f, 120.f, 150.f}; + return fallback_angles[component_idx % (sizeof(fallback_angles) / sizeof(fallback_angles[0]))]; +} + +static bool halftone_screen_on_for_gcode(float coverage, + float surface_u_mm, + float surface_v_mm, + float dot_size_mm, + float angle_deg) +{ + const float safe_coverage = clamp01f_for_gcode(coverage); + if (safe_coverage <= EPSILON) + return false; + if (safe_coverage >= 1.f - EPSILON) + return true; + if (!std::isfinite(surface_u_mm) || !std::isfinite(surface_v_mm)) + return safe_coverage >= 0.5f; + + const float radians = angle_deg * float(PI) / 180.f; + const float c = std::cos(radians); + const float s = std::sin(radians); + const float x = c * surface_u_mm + s * surface_v_mm; + const float y = -s * surface_u_mm + c * surface_v_mm; + return halftone_threshold_for_gcode(x, y, dot_size_mm) + 0.5f < safe_coverage; +} + static float dither_pitch_for_gcode(float base_outer_width_mm, int dithering_method, float dithering_resolution_mm, @@ -7078,10 +7140,26 @@ static float dither_pitch_for_gcode(float base_outer_width_mm, int(TextureMappingZone::DitheringClosest), int(TextureMappingZone::DitheringHalftone)); if (clamped_method == int(TextureMappingZone::DitheringHalftone)) { - const float dot_sample_step_mm = std::clamp(std::clamp(halftone_dot_size_mm, 0.08f, 2.f) * 0.25f, 0.04f, 0.08f); + const float dot_sample_step_mm = + std::clamp(std::clamp(halftone_dot_size_mm, + TextureMappingZone::MinHalftoneDotSizeMm, + TextureMappingZone::MaxHalftoneDotSizeMm) * + 0.25f, + 0.04f, + 0.08f); return std::min(high_res_step_mm, dot_sample_step_mm); } - return std::min(high_res_step_mm, std::clamp(dithering_resolution_mm, 0.04f, 0.25f)); + return std::min(high_res_step_mm, + std::clamp(dithering_resolution_mm, + TextureMappingZone::MinDitheringResolutionMm, + TextureMappingZone::MaxDitheringResolutionMm)); +} + +static float dither_cell_size_for_gcode(float dithering_resolution_mm) +{ + return std::clamp(dithering_resolution_mm, + TextureMappingZone::MinDitheringResolutionMm, + TextureMappingZone::MaxDitheringResolutionMm); } static float wrap_repeat01_for_gcode(float uv) @@ -8210,6 +8288,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( bool dithering_enabled, int dithering_method, float dither_pitch_mm, + float dither_cell_size_mm, float halftone_dot_size_mm, const std::vector &component_strength_factors, const std::vector &component_minimum_offset_factors, @@ -8224,6 +8303,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( bool high_speed_image_texture_sampling) { VertexColorOverhangWeightField weight_field; + (void) halftone_dot_size_mm; if (component_colors.empty()) return weight_field; const size_t component_count = component_colors.size(); @@ -8734,13 +8814,16 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( if (samples.empty()) return VertexColorOverhangWeightField{}; + const int clamped_binary_dither_method = + std::clamp(dithering_method, int(TextureMappingZone::DitheringClosest), int(TextureMappingZone::DitheringHalftone)); std::vector binary_dither_masks; const bool can_binary_dither = dithering_enabled && !raw_values_mode && + clamped_binary_dither_method != int(TextureMappingZone::DitheringHalftone) && component_count > 0 && - std::isfinite(dither_pitch_mm) && - dither_pitch_mm > EPSILON; + std::isfinite(dither_cell_size_mm) && + dither_cell_size_mm > EPSILON; if (can_binary_dither) { bool has_raw_samples = false; for (const WeightedTextureSample &sample : samples) { @@ -8788,8 +8871,8 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( if (sample.weight <= EPSILON) continue; - const int cell_x = int(std::floor((sample.x_mm - min_x_mm) / dither_pitch_mm)); - const int cell_y = int(std::floor((sample.y_mm - min_y_mm) / dither_pitch_mm)); + const int cell_x = int(std::floor((sample.x_mm - min_x_mm) / dither_cell_size_mm)); + const int cell_y = int(std::floor((sample.y_mm - min_y_mm) / dither_cell_size_mm)); const std::pair key(cell_x, cell_y); auto cell_it = cell_index_by_coord.find(key); if (cell_it == cell_index_by_coord.end()) { @@ -8824,15 +8907,11 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( }); binary_dither_masks.assign(samples.size(), 0); - const int clamped_dither_method = - std::clamp(dithering_method, - int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftone)); std::map, std::array> floyd_error; for (const size_t cell_idx : order) { BinaryDitherCell &cell = cells[cell_idx]; std::array target_oklab = cell.target_oklab; - if (clamped_dither_method == int(TextureMappingZone::DitheringFloydSteinberg)) { + if (clamped_binary_dither_method == int(TextureMappingZone::DitheringFloydSteinberg)) { const auto error_it = floyd_error.find({cell.x, cell.y}); if (error_it != floyd_error.end()) for (size_t axis = 0; axis < 3; ++axis) @@ -8841,14 +8920,9 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( bool thresholded_dither = false; float threshold = 0.f; - if (clamped_dither_method == int(TextureMappingZone::DitheringOrderedBayer)) { + if (clamped_binary_dither_method == int(TextureMappingZone::DitheringOrderedBayer)) { thresholded_dither = true; threshold = ordered_bayer_threshold_for_gcode(cell.x, cell.y) + 0.5f; - } else if (clamped_dither_method == int(TextureMappingZone::DitheringHalftone)) { - const float x_mm = min_x_mm + (float(cell.x) + 0.5f) * dither_pitch_mm; - const float y_mm = min_y_mm + (float(cell.y) + 0.5f) * dither_pitch_mm; - thresholded_dither = true; - threshold = halftone_threshold_for_gcode(x_mm, y_mm, halftone_dot_size_mm) + 0.5f; } const size_t candidate_idx = @@ -8863,7 +8937,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( if (sample_idx < binary_dither_masks.size()) binary_dither_masks[sample_idx] = candidate.mask; - if (clamped_dither_method == int(TextureMappingZone::DitheringFloydSteinberg)) { + if (clamped_binary_dither_method == int(TextureMappingZone::DitheringFloydSteinberg)) { std::array error = { target_oklab[0] - candidate.oklab[0], target_oklab[1] - candidate.oklab[1], @@ -9613,6 +9687,8 @@ std::optional GCode::texture_mapping_seam_hiding_hint(const const int dithering_method = std::clamp(zone->dithering_method, int(TextureMappingZone::DitheringClosest), int(TextureMappingZone::DitheringHalftone)); + const bool halftone_dithering_enabled = + dithering_enabled && dithering_method == int(TextureMappingZone::DitheringHalftone); const float seam_texture_base_width_mm = std::max(0.05f, float(m_config.texture_mapping_outer_wall_gradient_max_line_width.value)); const float dither_pitch_mm = @@ -9620,8 +9696,11 @@ std::optional GCode::texture_mapping_seam_hiding_hint(const dithering_method, zone->dithering_resolution_mm, zone->halftone_dot_size_mm); - const float halftone_dot_size_mm = std::clamp(zone->halftone_dot_size_mm, 0.08f, 2.f); - const bool compact_offset_mode = zone->compact_offset_mode || dithering_enabled; + const float dither_cell_size_mm = dither_cell_size_for_gcode(zone->dithering_resolution_mm); + const float halftone_dot_size_mm = std::clamp(zone->halftone_dot_size_mm, + TextureMappingZone::MinHalftoneDotSizeMm, + TextureMappingZone::MaxHalftoneDotSizeMm); + const bool compact_offset_mode = halftone_dithering_enabled ? false : zone->compact_offset_mode || dithering_enabled; const bool nonlinear_offset_adjustment = zone->nonlinear_offset_adjustment; const bool use_legacy_fixed_color_mode = zone->use_legacy_fixed_color_mode; const float texture_contrast_pct = std::clamp(zone->contrast_pct, 25.f, 300.f); @@ -9684,7 +9763,7 @@ std::optional GCode::texture_mapping_seam_hiding_hint(const if (dithering_method == int(TextureMappingZone::DitheringHalftone)) component_key_stream << "|hdt" << int(std::lround(halftone_dot_size_mm * 1000.f)); else - component_key_stream << "|hrz" << int(std::lround(std::clamp(zone->dithering_resolution_mm, 0.04f, 0.25f) * 1000.f)); + component_key_stream << "|hrz" << int(std::lround(dither_cell_size_mm * 1000.f)); for (const float strength_factor : component_strength_factors) component_key_stream << "|st" << int(std::lround(std::clamp(strength_factor, 0.f, 1.f) * 1000.f)); for (const float minimum_offset_factor : component_minimum_offset_factors) @@ -9760,6 +9839,7 @@ std::optional GCode::texture_mapping_seam_hiding_hint(const dithering_enabled, dithering_method, dither_pitch_mm, + dither_cell_size_mm, halftone_dot_size_mm, component_strength_factors, component_minimum_offset_factors, @@ -10916,11 +10996,17 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, float balance_weight { 0.f }; }; + struct OuterWallGradientSurfaceCoord { + float u_mm { std::numeric_limits::quiet_NaN() }; + float v_mm { std::numeric_limits::quiet_NaN() }; + }; + struct OuterWallGradientDynamicContext { bool enabled { false }; bool vertex_color_match_mode { false }; bool high_resolution_texture_sampling { false }; bool dithering_enabled { false }; + bool halftone_dithering_enabled { false }; bool nonlinear_offset_adjustment { false }; bool compact_offset_mode { false }; bool object_center_mode { false }; @@ -10940,6 +11026,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, float active_component_td_width_factor { 1.f }; float base_outer_width_mm { 0.4f }; float dither_pitch_mm { 0.08f }; + float halftone_dot_size_mm { TextureMappingZone::DefaultHalftoneDotSizeMm }; + float active_halftone_angle_deg { 0.f }; float flow_reference_width_mm { 0.4f }; float base_centerline_shift_mm { 0.f }; float centerline_shift_balance_mm { 0.f }; @@ -10967,6 +11055,53 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, return dest.allFinite() && std::isfinite(dE); }; + const bool halftone_path_closed = path.polyline.points.size() > 2 && path.polyline.is_closed(); + std::vector halftone_source_cumulative_mm(path.polyline.points.size(), 0.0); + for (size_t point_idx = 1; point_idx < path.polyline.points.size(); ++point_idx) + halftone_source_cumulative_mm[point_idx] = + halftone_source_cumulative_mm[point_idx - 1] + + Line(path.polyline.points[point_idx - 1], path.polyline.points[point_idx]).length() * SCALING_FACTOR; + const double halftone_total_path_mm = halftone_source_cumulative_mm.empty() ? 0.0 : halftone_source_cumulative_mm.back(); + double halftone_anchor_mm = 0.0; + if (halftone_path_closed && path.polyline.points.size() > 1 && halftone_total_path_mm > EPSILON) { + const size_t end_idx = path.polyline.points.size() - 1; + size_t best_idx = 0; + for (size_t point_idx = 1; point_idx < end_idx; ++point_idx) { + const Point &candidate = path.polyline.points[point_idx]; + const Point &best = path.polyline.points[best_idx]; + if (candidate.x() < best.x() || (candidate.x() == best.x() && candidate.y() < best.y())) + best_idx = point_idx; + } + halftone_anchor_mm = halftone_source_cumulative_mm[best_idx]; + } + float halftone_surface_v_mm = m_layer != nullptr ? float(m_layer->print_z) : float(m_nominal_z); + if (!std::isfinite(halftone_surface_v_mm)) + halftone_surface_v_mm = 0.f; + auto halftone_surface_u_for_source_distance = [&](double source_distance_mm) { + if (!std::isfinite(source_distance_mm)) + source_distance_mm = 0.0; + if (halftone_path_closed && halftone_total_path_mm > EPSILON) { + double u = std::fmod(source_distance_mm - halftone_anchor_mm, halftone_total_path_mm); + if (u < 0.0) + u += halftone_total_path_mm; + return float(u); + } + return float(std::max(0.0, source_distance_mm)); + }; + auto halftone_surface_coord_for_source_distance = [&](double source_distance_mm) { + OuterWallGradientSurfaceCoord coord; + coord.u_mm = halftone_surface_u_for_source_distance(source_distance_mm); + coord.v_mm = halftone_surface_v_mm; + return coord; + }; + auto halftone_source_distance_for_segment_fraction = [&](size_t segment_idx, double fraction) { + if (segment_idx + 1 >= halftone_source_cumulative_mm.size()) + return 0.0; + const double start_mm = halftone_source_cumulative_mm[segment_idx]; + const double end_mm = halftone_source_cumulative_mm[segment_idx + 1]; + return start_mm + (end_mm - start_mm) * std::clamp(fraction, 0.0, 1.0); + }; + std::vector outer_wall_gradient_segment_mods; OuterWallGradientDynamicContext outer_wall_gradient_dynamic_ctx; bool outer_wall_gradient_modulated_path = false; @@ -11131,16 +11266,22 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, const int dithering_method = std::clamp(zone->dithering_method, int(TextureMappingZone::DitheringClosest), int(TextureMappingZone::DitheringHalftone)); + const bool halftone_dithering_enabled = + dithering_enabled && dithering_method == int(TextureMappingZone::DitheringHalftone); const float dither_pitch_mm = dither_pitch_for_gcode(base_outer_width_mm, dithering_method, zone->dithering_resolution_mm, zone->halftone_dot_size_mm); - const float halftone_dot_size_mm = std::clamp(zone->halftone_dot_size_mm, 0.08f, 2.f); + const float dither_cell_size_mm = dither_cell_size_for_gcode(zone->dithering_resolution_mm); + const float halftone_dot_size_mm = std::clamp(zone->halftone_dot_size_mm, + TextureMappingZone::MinHalftoneDotSizeMm, + TextureMappingZone::MaxHalftoneDotSizeMm); const bool high_resolution_texture_sampling = zone->high_resolution_sampling || dithering_enabled; const bool high_speed_image_texture_sampling = zone->high_speed_image_texture_sampling; const bool nonlinear_offset_adjustment = zone->nonlinear_offset_adjustment; - const bool compact_offset_mode = zone->compact_offset_mode || dithering_enabled; + const bool compact_offset_mode = + halftone_dithering_enabled ? false : zone->compact_offset_mode || dithering_enabled; const float layer_sample_z_mm = use_layer_aware_weighting ? float(m_layer->print_z) : 0.f; const float layer_sample_falloff_mm = high_resolution_texture_sampling ? std::max(0.03f, layer_height_mm * 0.5f) : @@ -11232,7 +11373,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (dithering_method == int(TextureMappingZone::DitheringHalftone)) component_key_stream << "|hdt" << int(std::lround(halftone_dot_size_mm * 1000.f)); else - component_key_stream << "|hrz" << int(std::lround(std::clamp(zone->dithering_resolution_mm, 0.04f, 0.25f) * 1000.f)); + component_key_stream << "|hrz" << int(std::lround(dither_cell_size_mm * 1000.f)); for (const float strength_factor : component_strength_factors) component_key_stream << "|st" << int(std::lround(std::clamp(strength_factor, 0.f, 1.f) * 1000.f)); for (const float minimum_offset_factor : component_minimum_offset_factors) @@ -11261,6 +11402,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, dithering_enabled, dithering_method, dither_pitch_mm, + dither_cell_size_mm, halftone_dot_size_mm, component_strength_factors, component_minimum_offset_factors, @@ -11293,6 +11435,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, outer_wall_gradient_dynamic_ctx.vertex_color_match_mode = vertex_color_match_mode; outer_wall_gradient_dynamic_ctx.high_resolution_texture_sampling = high_resolution_texture_sampling; outer_wall_gradient_dynamic_ctx.dithering_enabled = dithering_enabled; + outer_wall_gradient_dynamic_ctx.halftone_dithering_enabled = halftone_dithering_enabled; outer_wall_gradient_dynamic_ctx.nonlinear_offset_adjustment = nonlinear_offset_adjustment; outer_wall_gradient_dynamic_ctx.compact_offset_mode = compact_offset_mode; outer_wall_gradient_dynamic_ctx.object_center_mode = object_center_mode; @@ -11312,6 +11455,9 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, 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.dither_pitch_mm = dither_pitch_mm; + outer_wall_gradient_dynamic_ctx.halftone_dot_size_mm = halftone_dot_size_mm; + outer_wall_gradient_dynamic_ctx.active_halftone_angle_deg = + halftone_screen_angle_deg_for_gcode(texture_filament_color_mode, active_component_idx); 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; outer_wall_gradient_dynamic_ctx.layer_height_mm = layer_height_mm; @@ -11340,6 +11486,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, clamped_shift_coord_for_gcode(shift_y, shift_scaled, max_shift_coord, mod.shift_dy); }; + size_t texture_path_segment_idx = 0; for (const Line &line : path.polyline.lines()) { if (!outer_wall_gradient_dynamic_ctx.enabled) break; @@ -11353,6 +11500,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, const double dy = by - ay; const double len = std::hypot(dx, dy); mod.length_mm = unscale(len); + const OuterWallGradientSurfaceCoord surface_coord = + halftone_surface_coord_for_source_distance( + halftone_source_distance_for_segment_fraction(texture_path_segment_idx, 0.5)); + ++texture_path_segment_idx; if (len <= EPSILON) { outer_wall_gradient_segment_mods.emplace_back(mod); continue; @@ -11386,13 +11537,23 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, !vertex_color_weight_field->empty()) { const float mid_x_mm = 0.5f * (unscale(line.a.x()) + unscale(line.b.x())); const float mid_y_mm = 0.5f * (unscale(line.a.y()) + unscale(line.b.y())); - const float desired_strength = sample_vertex_color_weight_field_for_gcode( + float desired_strength = sample_vertex_color_weight_field_for_gcode( *vertex_color_weight_field, mid_x_mm, mid_y_mm, active_component_idx, high_resolution_texture_sampling, - compact_offset_mode); + compact_offset_mode && !halftone_dithering_enabled); + if (halftone_dithering_enabled) + desired_strength = halftone_screen_on_for_gcode(desired_strength, + surface_coord.u_mm, + surface_coord.v_mm, + halftone_dot_size_mm, + halftone_screen_angle_deg_for_gcode( + texture_filament_color_mode, + active_component_idx)) ? + 1.f : + 0.f; inset_strength = std::clamp(1.f - desired_strength, 0.f, 1.f); } } else { @@ -12071,7 +12232,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, const Layer *surface_layer = m_layer; - auto dynamic_modulation_for_line = [&outer_wall_gradient_dynamic_ctx, surface_layer](const Line &line) { + auto dynamic_modulation_for_line = + [&outer_wall_gradient_dynamic_ctx, surface_layer](const Line &line, const OuterWallGradientSurfaceCoord &surface_coord) { OuterWallGradientSegmentMod mod; if (!outer_wall_gradient_dynamic_ctx.enabled) return mod; @@ -12123,13 +12285,22 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, outer_wall_gradient_dynamic_ctx.vertex_color_weight_field->component_count) { const float mid_x_mm = 0.5f * (unscale(line.a.x()) + unscale(line.b.x())); const float mid_y_mm = 0.5f * (unscale(line.a.y()) + unscale(line.b.y())); - const float desired_strength = sample_vertex_color_weight_field_for_gcode( + float desired_strength = sample_vertex_color_weight_field_for_gcode( *outer_wall_gradient_dynamic_ctx.vertex_color_weight_field, mid_x_mm, mid_y_mm, outer_wall_gradient_dynamic_ctx.active_component_idx, outer_wall_gradient_dynamic_ctx.high_resolution_texture_sampling, - outer_wall_gradient_dynamic_ctx.compact_offset_mode); + outer_wall_gradient_dynamic_ctx.compact_offset_mode && + !outer_wall_gradient_dynamic_ctx.halftone_dithering_enabled); + if (outer_wall_gradient_dynamic_ctx.halftone_dithering_enabled) + desired_strength = halftone_screen_on_for_gcode(desired_strength, + surface_coord.u_mm, + surface_coord.v_mm, + outer_wall_gradient_dynamic_ctx.halftone_dot_size_mm, + outer_wall_gradient_dynamic_ctx.active_halftone_angle_deg) ? + 1.f : + 0.f; inset_strength = std::clamp(1.f - desired_strength, 0.f, 1.f); } } else { @@ -12366,7 +12537,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, continue; } - const OuterWallGradientSegmentMod sub_mod = dynamic_modulation_for_line(sub_line); + const OuterWallGradientSurfaceCoord surface_coord = + halftone_surface_coord_for_source_distance( + halftone_source_distance_for_segment_fraction( + segment_idx, + (double(sub_idx) - 0.5) / double(subsegment_count))); + const OuterWallGradientSegmentMod sub_mod = dynamic_modulation_for_line(sub_line, surface_coord); const Point target_point = make_shifted_point(sub_line.b, sub_mod.shift_dx, sub_mod.shift_dy); const Vec2d target_xy = this->point_to_gcode(target_point); if (!is_reasonable_quantized_gcode_point_for_gcode(target_xy)) { @@ -12546,11 +12722,14 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, gcode += m_writer.set_speed(last_set_speed, "", comment); Point prev_point_model = new_points[0].p; if (outer_wall_gradient_modulated_path && new_points.size() > 1) { + const Line first_line(new_points[0].p, new_points[1].p); + const OuterWallGradientSurfaceCoord first_surface_coord = + halftone_surface_coord_for_source_distance(first_line.length() * SCALING_FACTOR * 0.5); const OuterWallGradientSegmentMod first_mod = (outer_wall_gradient_dynamic_ctx.enabled && (outer_wall_gradient_dynamic_ctx.object_center_mode || outer_wall_gradient_dynamic_ctx.vertex_color_match_mode)) ? - dynamic_modulation_for_line(Line(new_points[0].p, new_points[1].p)) : + dynamic_modulation_for_line(first_line, first_surface_coord) : segment_modulation_at(0); prev_point_model = make_shifted_point(prev_point_model, first_mod.shift_dx, first_mod.shift_dy); } @@ -12566,17 +12745,27 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, pre_fan_enabled = true; double path_length = 0.; + double source_path_length_mm = 0.; for (size_t i = 1; i < new_points.size(); i++) { std::string tempDescription = description; const ProcessedPoint &processed_point = new_points[i]; const ProcessedPoint &pre_processed_point = new_points[i-1]; + const Line parent_source_line(pre_processed_point.p, processed_point.p); + const double parent_source_length_mm = parent_source_line.length() * SCALING_FACTOR; + const double safe_parent_source_length_mm = + std::isfinite(parent_source_length_mm) ? std::max(0.0, parent_source_length_mm) : 0.0; + const double segment_source_start_mm = source_path_length_mm; + if (std::isfinite(parent_source_length_mm)) + source_path_length_mm += parent_source_length_mm; const bool dynamic_line_modulation = outer_wall_gradient_dynamic_ctx.enabled && (outer_wall_gradient_dynamic_ctx.object_center_mode || outer_wall_gradient_dynamic_ctx.vertex_color_match_mode); + const OuterWallGradientSurfaceCoord segment_surface_coord = + halftone_surface_coord_for_source_distance(segment_source_start_mm + safe_parent_source_length_mm * 0.5); const OuterWallGradientSegmentMod segment_mod = dynamic_line_modulation ? - dynamic_modulation_for_line(Line(pre_processed_point.p, processed_point.p)) : + dynamic_modulation_for_line(parent_source_line, segment_surface_coord) : segment_modulation_at(i - 1); const Point processed_target_point = make_shifted_point(processed_point.p, segment_mod.shift_dx, segment_mod.shift_dy); Vec2d p = this->point_to_gcode_quantized(processed_target_point); @@ -12687,7 +12876,11 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, Point(coord_t(std::llround(double(parent_line.a.x()) + (double(parent_line.b.x()) - double(parent_line.a.x())) * t)), coord_t(std::llround(double(parent_line.a.y()) + (double(parent_line.b.y()) - double(parent_line.a.y())) * t))); const Line sub_line(sub_a, sub_b); - const OuterWallGradientSegmentMod sub_mod = dynamic_modulation_for_line(sub_line); + const OuterWallGradientSurfaceCoord sub_surface_coord = + halftone_surface_coord_for_source_distance( + segment_source_start_mm + safe_parent_source_length_mm * + ((double(sub_idx) - 0.5) / double(subsegment_count))); + const OuterWallGradientSegmentMod sub_mod = dynamic_modulation_for_line(sub_line, sub_surface_coord); const Point sub_target_point = make_shifted_point(sub_line.b, sub_mod.shift_dx, sub_mod.shift_dy); Vec2d sub_p = this->point_to_gcode_quantized(sub_target_point); if (!is_reasonable_quantized_gcode_point_for_gcode(sub_p)) { diff --git a/src/libslic3r/TextureMapping.cpp b/src/libslic3r/TextureMapping.cpp index 89ea81bef68..5724b9471a8 100644 --- a/src/libslic3r/TextureMapping.cpp +++ b/src/libslic3r/TextureMapping.cpp @@ -1083,9 +1083,13 @@ std::string TextureMappingManager::serialize_entries() texture["dithering_enabled"] = zone.dithering_enabled; texture["dithering_method"] = dithering_method_name(zone.dithering_method); texture["dithering_resolution_mm"] = - std::clamp(finite_or(zone.dithering_resolution_mm, TextureMappingZone::DefaultDitheringResolutionMm), 0.04f, 0.25f); + std::clamp(finite_or(zone.dithering_resolution_mm, TextureMappingZone::DefaultDitheringResolutionMm), + TextureMappingZone::MinDitheringResolutionMm, + TextureMappingZone::MaxDitheringResolutionMm); texture["halftone_dot_size_mm"] = - std::clamp(finite_or(zone.halftone_dot_size_mm, TextureMappingZone::DefaultHalftoneDotSizeMm), 0.08f, 2.f); + std::clamp(finite_or(zone.halftone_dot_size_mm, TextureMappingZone::DefaultHalftoneDotSizeMm), + TextureMappingZone::MinHalftoneDotSizeMm, + TextureMappingZone::MaxHalftoneDotSizeMm); texture["contrast_pct"] = std::clamp(finite_or(zone.contrast_pct, 100.f), 25.f, 300.f); texture["high_resolution_sampling"] = zone.high_resolution_sampling; texture["tone_gamma"] = normalize_tone_gamma(zone.tone_gamma); @@ -1235,9 +1239,13 @@ void TextureMappingManager::load_entries(const std::string &serialized, zone.dithering_method = dithering_method_from_name( texture.value("dithering_method", dithering_method_name(TextureMappingZone::DefaultDitheringMethod))); zone.dithering_resolution_mm = - std::clamp(texture.value("dithering_resolution_mm", TextureMappingZone::DefaultDitheringResolutionMm), 0.04f, 0.25f); + std::clamp(texture.value("dithering_resolution_mm", TextureMappingZone::DefaultDitheringResolutionMm), + TextureMappingZone::MinDitheringResolutionMm, + TextureMappingZone::MaxDitheringResolutionMm); zone.halftone_dot_size_mm = - std::clamp(texture.value("halftone_dot_size_mm", TextureMappingZone::DefaultHalftoneDotSizeMm), 0.08f, 2.f); + std::clamp(texture.value("halftone_dot_size_mm", TextureMappingZone::DefaultHalftoneDotSizeMm), + TextureMappingZone::MinHalftoneDotSizeMm, + TextureMappingZone::MaxHalftoneDotSizeMm); if (zone.dithering_enabled) zone.compact_offset_mode = true; zone.contrast_pct = std::clamp(texture.value("contrast_pct", 100.f), 25.f, 300.f); diff --git a/src/libslic3r/TextureMapping.hpp b/src/libslic3r/TextureMapping.hpp index f93e4929eec..6d631a513dc 100644 --- a/src/libslic3r/TextureMapping.hpp +++ b/src/libslic3r/TextureMapping.hpp @@ -110,9 +110,13 @@ struct TextureMappingZone static constexpr int DefaultGenericSolverMode = int(GenericSolverV2); static constexpr int DefaultGenericSolverMixModel = int(GenericSolverPigmentPainter); static constexpr bool DefaultDitheringEnabled = false; - static constexpr int DefaultDitheringMethod = int(DitheringFloydSteinberg); + static constexpr int DefaultDitheringMethod = int(DitheringHalftone); + static constexpr float MinDitheringResolutionMm = 0.04f; + static constexpr float MaxDitheringResolutionMm = 10.f; static constexpr float DefaultDitheringResolutionMm = 0.08f; - static constexpr float DefaultHalftoneDotSizeMm = 0.32f; + static constexpr float MinHalftoneDotSizeMm = 0.08f; + static constexpr float MaxHalftoneDotSizeMm = 50.f; + static constexpr float DefaultHalftoneDotSizeMm = 0.8f; static constexpr float DefaultContrastPct = 100.f; static constexpr bool DefaultHighResolutionSampling = true; static constexpr float DefaultToneGamma = 1.f; diff --git a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp index b2d4312f05a..053489e46d4 100644 --- a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp +++ b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp @@ -1623,7 +1623,12 @@ float ordered_bayer_threshold_for_texture_preview(int x, int y) float halftone_threshold_for_texture_preview(int x, int y, float dot_size_mm) { const float period_px = - std::clamp(std::clamp(dot_size_mm, 0.08f, 2.f) / TextureMappingZone::DefaultDitheringResolutionMm, 2.f, 64.f); + std::clamp(std::clamp(dot_size_mm, + TextureMappingZone::MinHalftoneDotSizeMm, + TextureMappingZone::MaxHalftoneDotSizeMm) / + TextureMappingZone::DefaultDitheringResolutionMm, + 2.f, + TextureMappingZone::MaxHalftoneDotSizeMm / TextureMappingZone::DefaultDitheringResolutionMm); const float u = float(x) / period_px - std::floor(float(x) / period_px); const float v = float(y) / period_px - std::floor(float(y) / period_px); const float dx = u - 0.5f; @@ -1785,7 +1790,9 @@ std::vector component_weights_for_texture_preview(const TexturePreviewSim if (component_count == 0) return {}; - if (settings.dithering_enabled && settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues)) { + if (settings.dithering_enabled && + settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) && + settings.dithering_method != int(TextureMappingZone::DitheringHalftone)) { const std::vector candidates = binary_dither_candidates_for_texture_preview(settings); const std::array target_oklab = texture_preview_target_oklab(settings, sample_rgba); const size_t candidate_idx = nearest_binary_dither_candidate_for_texture_preview(candidates, target_oklab); @@ -1961,9 +1968,16 @@ std::optional texture_preview_simulation_setti settings.dithering_method = std::clamp(zone->dithering_method, int(TextureMappingZone::DitheringClosest), int(TextureMappingZone::DitheringHalftone)); - settings.dithering_resolution_mm = std::clamp(zone->dithering_resolution_mm, 0.04f, 0.25f); - settings.halftone_dot_size_mm = std::clamp(zone->halftone_dot_size_mm, 0.08f, 2.f); - settings.compact_offset_mode = zone->compact_offset_mode || settings.dithering_enabled; + settings.dithering_resolution_mm = std::clamp(zone->dithering_resolution_mm, + TextureMappingZone::MinDitheringResolutionMm, + TextureMappingZone::MaxDitheringResolutionMm); + settings.halftone_dot_size_mm = std::clamp(zone->halftone_dot_size_mm, + TextureMappingZone::MinHalftoneDotSizeMm, + TextureMappingZone::MaxHalftoneDotSizeMm); + const bool halftone_dithering_enabled = + settings.dithering_enabled && settings.dithering_method == int(TextureMappingZone::DitheringHalftone); + settings.compact_offset_mode = + halftone_dithering_enabled ? false : zone->compact_offset_mode || settings.dithering_enabled; settings.use_legacy_fixed_color_mode = zone->use_legacy_fixed_color_mode; settings.contrast_pct = std::clamp(zone->contrast_pct, 25.f, 300.f); settings.tone_gamma = (!std::isfinite(zone->tone_gamma) || zone->tone_gamma <= 0.f) ? @@ -2098,6 +2112,7 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig const bool use_binary_dithering = settings.dithering_enabled && settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) && + settings.dithering_method != int(TextureMappingZone::DitheringHalftone) && !use_raw_offsets; const std::vector binary_dither_candidates = use_binary_dithering ? binary_dither_candidates_for_texture_preview(settings) : diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3046c92b840..a78fb8c188c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1346,10 +1346,12 @@ public: wxEmptyString, wxDefaultPosition, wxSize(FromDIP(84), -1), - wxSP_ARROW_KEYS | wxALIGN_RIGHT, - 0.04, - 0.25, - std::clamp(double(dithering_resolution_mm), 0.04, 0.25), + wxSP_ARROW_KEYS | wxALIGN_RIGHT | wxTE_PROCESS_ENTER, + double(TextureMappingZone::MinDitheringResolutionMm), + double(TextureMappingZone::MaxDitheringResolutionMm), + std::clamp(double(dithering_resolution_mm), + double(TextureMappingZone::MinDitheringResolutionMm), + double(TextureMappingZone::MaxDitheringResolutionMm)), 0.01); m_dithering_resolution_spin->SetDigits(2); dithering_resolution_row->Add(m_dithering_resolution_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2); @@ -1367,12 +1369,14 @@ public: wxID_ANY, wxEmptyString, wxDefaultPosition, - wxSize(FromDIP(84), -1), - wxSP_ARROW_KEYS | wxALIGN_RIGHT, - 0.08, - 2.0, - std::clamp(double(halftone_dot_size_mm), 0.08, 2.0), - 0.01); + wxSize(FromDIP(96), -1), + wxSP_ARROW_KEYS | wxALIGN_RIGHT | wxTE_PROCESS_ENTER, + double(TextureMappingZone::MinHalftoneDotSizeMm), + double(TextureMappingZone::MaxHalftoneDotSizeMm), + std::clamp(double(halftone_dot_size_mm), + double(TextureMappingZone::MinHalftoneDotSizeMm), + double(TextureMappingZone::MaxHalftoneDotSizeMm)), + 0.1); m_halftone_dot_size_spin->SetDigits(2); halftone_dot_size_row->Add(m_halftone_dot_size_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2); halftone_dot_size_row->Add(new wxStaticText(m_halftone_dot_size_panel, wxID_ANY, _L("mm")), 0, wxALIGN_CENTER_VERTICAL); @@ -1638,16 +1642,16 @@ public: return float(std::clamp(m_dithering_resolution_spin != nullptr ? m_dithering_resolution_spin->GetValue() : double(TextureMappingZone::DefaultDitheringResolutionMm), - 0.04, - 0.25)); + double(TextureMappingZone::MinDitheringResolutionMm), + double(TextureMappingZone::MaxDitheringResolutionMm))); } float halftone_dot_size_mm() const { return float(std::clamp(m_halftone_dot_size_spin != nullptr ? m_halftone_dot_size_spin->GetValue() : double(TextureMappingZone::DefaultHalftoneDotSizeMm), - 0.08, - 2.0)); + double(TextureMappingZone::MinHalftoneDotSizeMm), + double(TextureMappingZone::MaxHalftoneDotSizeMm))); } bool minimum_visibility_offset_enabled() const { @@ -1904,6 +1908,19 @@ private: m_options_book->SetMinSize(wxSize(FromDIP(420), max_height)); } + void layout_current_options_page() + { + if (m_options_book == nullptr) + return; + const int selection = m_options_book->GetSelection(); + if (selection >= 0 && selection < int(m_options_book->GetPageCount())) { + wxWindow *page = m_options_book->GetPage(size_t(selection)); + if (page != nullptr) + page->Layout(); + } + m_options_book->Layout(); + } + void update_strength_offsets_visibility(bool fit_dialog) { if (m_strength_offsets_toggle_button != nullptr) @@ -1957,6 +1974,7 @@ private: m_compact_offset_mode_checkbox->SetValue(true); m_compact_offset_mode_checkbox->Enable(!enabled); } + layout_current_options_page(); if (!fit_dialog) return; update_options_book_min_size();