Show (non-exact) halftone preview in 3d object view

This commit is contained in:
sentientstardust
2026-05-18 10:46:39 +01:00
parent 591e7b940f
commit fa68f7653e
7 changed files with 993 additions and 256 deletions

View File

@@ -35,6 +35,7 @@
#include <iostream>
#include <iomanip>
#include <limits>
#include <map>
#include <optional>
#include <set>
#include <sstream>
@@ -43,6 +44,7 @@
#include <string>
#include <utility>
#include <string_view>
#include <tuple>
#include <regex>
#include <boost/algorithm/string.hpp>
@@ -7070,6 +7072,15 @@ 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 bool is_halftone_dithering_method_for_gcode(int method)
{
const int clamped_method = std::clamp(method,
int(TextureMappingZone::DitheringClosest),
int(TextureMappingZone::DitheringHalftoneIncreasedDetail));
return clamped_method == int(TextureMappingZone::DitheringHalftone) ||
clamped_method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail);
}
static float halftone_screen_angle_deg_for_gcode(int filament_color_mode, size_t component_idx)
{
const int clamped_mode =
@@ -7138,8 +7149,8 @@ static float dither_pitch_for_gcode(float base_outer_width_mm,
const float high_res_step_mm = std::clamp(base_outer_width_mm * 0.20f, 0.04f, 0.12f);
const int clamped_method = std::clamp(dithering_method,
int(TextureMappingZone::DitheringClosest),
int(TextureMappingZone::DitheringHalftone));
if (clamped_method == int(TextureMappingZone::DitheringHalftone)) {
int(TextureMappingZone::DitheringHalftoneIncreasedDetail));
if (is_halftone_dithering_method_for_gcode(clamped_method)) {
const float dot_sample_step_mm =
std::clamp(std::clamp(halftone_dot_size_mm,
TextureMappingZone::MinHalftoneDotSizeMm,
@@ -8815,12 +8826,14 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
return VertexColorOverhangWeightField{};
const int clamped_binary_dither_method =
std::clamp(dithering_method, int(TextureMappingZone::DitheringClosest), int(TextureMappingZone::DitheringHalftone));
std::clamp(dithering_method,
int(TextureMappingZone::DitheringClosest),
int(TextureMappingZone::DitheringHalftoneIncreasedDetail));
std::vector<uint32_t> binary_dither_masks;
const bool can_binary_dither =
dithering_enabled &&
!raw_values_mode &&
clamped_binary_dither_method != int(TextureMappingZone::DitheringHalftone) &&
!is_halftone_dithering_method_for_gcode(clamped_binary_dither_method) &&
component_count > 0 &&
std::isfinite(dither_cell_size_mm) &&
dither_cell_size_mm > EPSILON;
@@ -9128,7 +9141,8 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
static std::vector<float> sample_vertex_color_weight_field_components_for_gcode(const VertexColorOverhangWeightField &weight_field,
float x_mm,
float y_mm,
bool high_resolution_texture_sampling)
bool high_resolution_texture_sampling,
float smoothing_radius_mm = 0.f)
{
std::vector<float> fallback = weight_field.fallback_weights;
if (fallback.size() < weight_field.component_count)
@@ -9207,14 +9221,20 @@ static std::vector<float> sample_vertex_color_weight_field_components_for_gcode(
const float sigma_scale = high_resolution_texture_sampling ? 0.45f : 0.7f;
const float min_sigma_mm = high_resolution_texture_sampling ? 0.04f : 0.06f;
const float sigma_x_mm = std::max(min_sigma_mm, weight_field.bucket_width_mm * sigma_scale);
const float sigma_y_mm = std::max(min_sigma_mm, weight_field.bucket_height_mm * sigma_scale);
const float safe_smoothing_radius_mm =
std::isfinite(smoothing_radius_mm) ? std::max(0.f, smoothing_radius_mm) : 0.f;
const float sigma_floor_mm = safe_smoothing_radius_mm > EPSILON ?
std::max(min_sigma_mm, safe_smoothing_radius_mm * 0.5f) :
min_sigma_mm;
const float sigma_x_mm = std::max(sigma_floor_mm, weight_field.bucket_width_mm * sigma_scale);
const float sigma_y_mm = std::max(sigma_floor_mm, weight_field.bucket_height_mm * sigma_scale);
const float inv_two_sigma_x2 = 1.f / std::max(2.f * sigma_x_mm * sigma_x_mm, 1e-8f);
const float inv_two_sigma_y2 = 1.f / std::max(2.f * sigma_y_mm * sigma_y_mm, 1e-8f);
const float min_radius_mm = high_resolution_texture_sampling ? 0.16f : 0.30f;
const float radius_scale = high_resolution_texture_sampling ? 1.75f : 3.f;
const float max_radius_mm = std::max(min_radius_mm, std::max(weight_field.bucket_width_mm, weight_field.bucket_height_mm) * radius_scale);
const float max_radius_mm = std::max(std::max(min_radius_mm, safe_smoothing_radius_mm),
std::max(weight_field.bucket_width_mm, weight_field.bucket_height_mm) * radius_scale);
const float max_radius2 = max_radius_mm * max_radius_mm;
const float min_bucket_span_mm = std::max(1e-3f, std::min(weight_field.bucket_width_mm, weight_field.bucket_height_mm));
const int max_ring = std::max(1, int(std::ceil(max_radius_mm / min_bucket_span_mm)));
@@ -9373,14 +9393,18 @@ static float sample_vertex_color_weight_field_for_gcode(const VertexColorOverhan
float y_mm,
size_t component_idx,
bool high_resolution_texture_sampling,
bool compact_offset_mode = false)
bool compact_offset_mode = false,
float smoothing_radius_mm = 0.f)
{
const float fallback = component_idx < weight_field.fallback_weights.size() ?
weight_field.fallback_weights[component_idx] : 0.f;
if (compact_offset_mode && !weight_field.raw_component_weights_from_texture && !weight_field.empty() &&
component_idx < weight_field.component_count) {
std::vector<float> values = sample_vertex_color_weight_field_components_for_gcode(weight_field,
x_mm,
y_mm,
high_resolution_texture_sampling);
high_resolution_texture_sampling,
smoothing_radius_mm);
float max_value = 0.f;
for (size_t idx = 0; idx < weight_field.component_count && idx < values.size(); ++idx)
max_value = std::max(max_value, clamp01f_for_gcode(values[idx]));
@@ -9388,170 +9412,12 @@ static float sample_vertex_color_weight_field_for_gcode(const VertexColorOverhan
return clamp01f_for_gcode(values[component_idx] / max_value);
}
const float fallback = component_idx < weight_field.fallback_weights.size() ?
weight_field.fallback_weights[component_idx] : 0.f;
if (weight_field.empty() || component_idx >= weight_field.component_count)
return fallback;
if (!std::isfinite(x_mm) || !std::isfinite(y_mm))
return fallback;
const float gx_unclamped = (x_mm - weight_field.min_x_mm) / std::max(weight_field.bucket_width_mm, 1e-6f);
const float gy_unclamped = (y_mm - weight_field.min_y_mm) / std::max(weight_field.bucket_height_mm, 1e-6f);
const int cx = std::clamp(int(std::floor(gx_unclamped)), 0, weight_field.bucket_width - 1);
const int cy = std::clamp(int(std::floor(gy_unclamped)), 0, weight_field.bucket_height - 1);
const float sigma_scale = high_resolution_texture_sampling ? 0.45f : 0.7f;
const float min_sigma_mm = high_resolution_texture_sampling ? 0.04f : 0.06f;
const float sigma_x_mm = std::max(min_sigma_mm, weight_field.bucket_width_mm * sigma_scale);
const float sigma_y_mm = std::max(min_sigma_mm, weight_field.bucket_height_mm * sigma_scale);
const float inv_two_sigma_x2 = 1.f / std::max(2.f * sigma_x_mm * sigma_x_mm, 1e-8f);
const float inv_two_sigma_y2 = 1.f / std::max(2.f * sigma_y_mm * sigma_y_mm, 1e-8f);
const float min_radius_mm = high_resolution_texture_sampling ? 0.16f : 0.30f;
const float radius_scale = high_resolution_texture_sampling ? 1.75f : 3.f;
const float max_radius_mm = std::max(min_radius_mm, std::max(weight_field.bucket_width_mm, weight_field.bucket_height_mm) * radius_scale);
const float max_radius2 = max_radius_mm * max_radius_mm;
const float min_bucket_span_mm = std::max(1e-3f, std::min(weight_field.bucket_width_mm, weight_field.bucket_height_mm));
const int max_ring = std::max(1, int(std::ceil(max_radius_mm / min_bucket_span_mm)));
float weighted_sum = 0.f;
float total_weight = 0.f;
size_t contributing_samples = 0;
auto process_bucket = [&weight_field,
component_idx,
x_mm,
y_mm,
max_radius2,
inv_two_sigma_x2,
inv_two_sigma_y2,
&weighted_sum,
&total_weight,
&contributing_samples](int bx, int by) {
if (bx < 0 || by < 0 || bx >= weight_field.bucket_width || by >= weight_field.bucket_height)
return;
const size_t bucket_idx = size_t(by) * size_t(weight_field.bucket_width) + size_t(bx);
if (bucket_idx >= weight_field.buckets.size())
return;
for (const uint32_t sample_idx_u32 : weight_field.buckets[bucket_idx]) {
const size_t sample_idx = size_t(sample_idx_u32);
if (sample_idx >= weight_field.sample_x_mm.size() ||
sample_idx >= weight_field.sample_y_mm.size() ||
sample_idx >= weight_field.sample_weight.size())
continue;
const float dx = x_mm - weight_field.sample_x_mm[sample_idx];
const float dy = y_mm - weight_field.sample_y_mm[sample_idx];
const float d2 = dx * dx + dy * dy;
if (d2 > max_radius2)
continue;
const float kernel = std::exp(-(dx * dx) * inv_two_sigma_x2 - (dy * dy) * inv_two_sigma_y2);
const float sample_w = weight_field.sample_weight[sample_idx] * kernel;
if (!std::isfinite(sample_w) || sample_w <= EPSILON)
continue;
const size_t value_idx = sample_idx * weight_field.component_count + component_idx;
if (value_idx >= weight_field.sample_component_weights.size())
continue;
weighted_sum += weight_field.sample_component_weights[value_idx] * sample_w;
total_weight += sample_w;
++contributing_samples;
}
};
for (int ring = 0; ring <= max_ring; ++ring) {
const int min_x = std::max(0, cx - ring);
const int max_x = std::min(weight_field.bucket_width - 1, cx + ring);
const int min_y = std::max(0, cy - ring);
const int max_y = std::min(weight_field.bucket_height - 1, cy + ring);
if (ring == 0) {
process_bucket(cx, cy);
} else {
for (int x = min_x; x <= max_x; ++x) {
process_bucket(x, min_y);
if (max_y != min_y)
process_bucket(x, max_y);
}
for (int y = min_y + 1; y <= max_y - 1; ++y) {
process_bucket(min_x, y);
if (max_x != min_x)
process_bucket(max_x, y);
}
}
if (total_weight > EPSILON && contributing_samples >= 12)
break;
}
if (total_weight > EPSILON)
return clamp01f_for_gcode(weighted_sum / total_weight);
float nearest_d2 = std::numeric_limits<float>::max();
float nearest_value = fallback;
const int nearest_ring_limit = std::min(std::max(max_ring + 2, 4), std::max(weight_field.bucket_width, weight_field.bucket_height));
for (int ring = 0; ring <= nearest_ring_limit; ++ring) {
const int min_x = std::max(0, cx - ring);
const int max_x = std::min(weight_field.bucket_width - 1, cx + ring);
const int min_y = std::max(0, cy - ring);
const int max_y = std::min(weight_field.bucket_height - 1, cy + ring);
auto visit_bucket = [&weight_field, component_idx, x_mm, y_mm, &nearest_d2, &nearest_value](int bx, int by) {
if (bx < 0 || by < 0 || bx >= weight_field.bucket_width || by >= weight_field.bucket_height)
return;
const size_t bucket_idx = size_t(by) * size_t(weight_field.bucket_width) + size_t(bx);
if (bucket_idx >= weight_field.buckets.size())
return;
for (const uint32_t sample_idx_u32 : weight_field.buckets[bucket_idx]) {
const size_t sample_idx = size_t(sample_idx_u32);
if (sample_idx >= weight_field.sample_x_mm.size() || sample_idx >= weight_field.sample_y_mm.size())
continue;
const float dx = x_mm - weight_field.sample_x_mm[sample_idx];
const float dy = y_mm - weight_field.sample_y_mm[sample_idx];
const float d2 = dx * dx + dy * dy;
if (d2 >= nearest_d2)
continue;
const size_t value_idx = sample_idx * weight_field.component_count + component_idx;
if (value_idx >= weight_field.sample_component_weights.size())
continue;
nearest_d2 = d2;
nearest_value = weight_field.sample_component_weights[value_idx];
}
};
if (ring == 0) {
visit_bucket(cx, cy);
} else {
for (int x = min_x; x <= max_x; ++x) {
visit_bucket(x, min_y);
if (max_y != min_y)
visit_bucket(x, max_y);
}
for (int y = min_y + 1; y <= max_y - 1; ++y) {
visit_bucket(min_x, y);
if (max_x != min_x)
visit_bucket(max_x, y);
}
}
if (nearest_d2 < std::numeric_limits<float>::max() && ring >= 2)
break;
}
if (nearest_d2 < std::numeric_limits<float>::max())
return clamp01f_for_gcode(nearest_value);
return fallback;
const std::vector<float> values = sample_vertex_color_weight_field_components_for_gcode(weight_field,
x_mm,
y_mm,
high_resolution_texture_sampling,
smoothing_radius_mm);
return component_idx < values.size() ? clamp01f_for_gcode(values[component_idx]) : fallback;
}
static float component_angular_influence_for_gcode(unsigned int active_component_id,
@@ -9686,9 +9552,9 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
zone->texture_mapping_mode != int(TextureMappingZone::TextureMappingRawValues);
const int dithering_method = std::clamp(zone->dithering_method,
int(TextureMappingZone::DitheringClosest),
int(TextureMappingZone::DitheringHalftone));
int(TextureMappingZone::DitheringHalftoneIncreasedDetail));
const bool halftone_dithering_enabled =
dithering_enabled && dithering_method == int(TextureMappingZone::DitheringHalftone);
dithering_enabled && is_halftone_dithering_method_for_gcode(dithering_method);
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 =
@@ -9760,7 +9626,7 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
component_key_stream << "|de" << (dithering_enabled ? 1 : 0);
component_key_stream << "|dm" << dithering_method;
component_key_stream << "|dp" << int(std::lround(dither_pitch_mm * 1000.f));
if (dithering_method == int(TextureMappingZone::DitheringHalftone))
if (is_halftone_dithering_method_for_gcode(dithering_method))
component_key_stream << "|hdt" << int(std::lround(halftone_dot_size_mm * 1000.f));
else
component_key_stream << "|hrz" << int(std::lround(dither_cell_size_mm * 1000.f));
@@ -11007,6 +10873,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
bool high_resolution_texture_sampling { false };
bool dithering_enabled { false };
bool halftone_dithering_enabled { false };
bool halftone_increased_detail_enabled { false };
bool nonlinear_offset_adjustment { false };
bool compact_offset_mode { false };
bool object_center_mode { false };
@@ -11101,6 +10968,94 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
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);
};
auto halftone_normalized_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 d = std::fmod(source_distance_mm, halftone_total_path_mm);
if (d < 0.0)
d += halftone_total_path_mm;
return d;
}
return std::clamp(source_distance_mm, 0.0, halftone_total_path_mm);
};
auto halftone_source_distance_for_surface_u = [&](float surface_u_mm) {
if (!std::isfinite(surface_u_mm))
surface_u_mm = 0.f;
if (halftone_path_closed && halftone_total_path_mm > EPSILON)
return halftone_normalized_source_distance(double(surface_u_mm) + halftone_anchor_mm);
return halftone_normalized_source_distance(double(surface_u_mm));
};
auto halftone_point_for_source_distance = [&](double source_distance_mm) -> std::optional<Point> {
if (path.polyline.points.empty())
return std::nullopt;
if (path.polyline.points.size() == 1 || halftone_total_path_mm <= EPSILON)
return path.polyline.points.front();
const double d = halftone_normalized_source_distance(source_distance_mm);
auto upper = std::lower_bound(halftone_source_cumulative_mm.begin(), halftone_source_cumulative_mm.end(), d);
size_t idx = size_t(std::distance(halftone_source_cumulative_mm.begin(), upper));
if (idx == 0)
idx = 1;
if (idx >= path.polyline.points.size())
idx = path.polyline.points.size() - 1;
const double start_mm = halftone_source_cumulative_mm[idx - 1];
const double end_mm = halftone_source_cumulative_mm[idx];
const double t = end_mm > start_mm + EPSILON ? std::clamp((d - start_mm) / (end_mm - start_mm), 0.0, 1.0) : 0.0;
const Point &a = path.polyline.points[idx - 1];
const Point &b = path.polyline.points[idx];
return Point(coord_t(std::llround(double(a.x()) + (double(b.x()) - double(a.x())) * t)),
coord_t(std::llround(double(a.y()) + (double(b.y()) - double(a.y())) * t)));
};
std::map<std::tuple<size_t, int, int>, float> halftone_cell_tone_cache;
auto halftone_screen_cell_tone_for_gcode = [&](const VertexColorOverhangWeightField &weight_field,
const OuterWallGradientSurfaceCoord &surface_coord,
size_t component_idx,
bool high_resolution_texture_sampling,
float dot_size_mm,
float angle_deg,
float fallback_x_mm,
float fallback_y_mm) {
const float period = std::clamp(dot_size_mm,
TextureMappingZone::MinHalftoneDotSizeMm,
TextureMappingZone::MaxHalftoneDotSizeMm);
if (!std::isfinite(surface_coord.u_mm) || !std::isfinite(surface_coord.v_mm) || period <= EPSILON)
return sample_vertex_color_weight_field_for_gcode(weight_field,
fallback_x_mm,
fallback_y_mm,
component_idx,
high_resolution_texture_sampling,
false,
period * 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 screen_x = c * surface_coord.u_mm + s * surface_coord.v_mm;
const float screen_y = -s * surface_coord.u_mm + c * surface_coord.v_mm;
const int cell_x = int(std::floor(screen_x / period));
const int cell_y = int(std::floor(screen_y / period));
const std::tuple<size_t, int, int> key(component_idx, cell_x, cell_y);
const auto cached = halftone_cell_tone_cache.find(key);
if (cached != halftone_cell_tone_cache.end())
return cached->second;
const float center_screen_x = (float(cell_x) + 0.5f) * period;
const float center_screen_y = (float(cell_y) + 0.5f) * period;
const float delta_screen_x = center_screen_x - screen_x;
const float delta_screen_y = center_screen_y - screen_y;
const float center_u_mm = surface_coord.u_mm + c * delta_screen_x - s * delta_screen_y;
const double center_source_distance_mm = halftone_source_distance_for_surface_u(center_u_mm);
const std::optional<Point> center_point = halftone_point_for_source_distance(center_source_distance_mm);
const float sample_x_mm = center_point ? unscale<float>(center_point->x()) : fallback_x_mm;
const float sample_y_mm = center_point ? unscale<float>(center_point->y()) : fallback_y_mm;
const float tone = sample_vertex_color_weight_field_for_gcode(weight_field,
sample_x_mm,
sample_y_mm,
component_idx,
high_resolution_texture_sampling,
false,
period * 0.5f);
halftone_cell_tone_cache.emplace(key, tone);
return tone;
};
std::vector<OuterWallGradientSegmentMod> outer_wall_gradient_segment_mods;
OuterWallGradientDynamicContext outer_wall_gradient_dynamic_ctx;
@@ -11265,9 +11220,11 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
const bool dithering_enabled = zone->dithering_enabled && !raw_texture_mapping_mode;
const int dithering_method = std::clamp(zone->dithering_method,
int(TextureMappingZone::DitheringClosest),
int(TextureMappingZone::DitheringHalftone));
int(TextureMappingZone::DitheringHalftoneIncreasedDetail));
const bool halftone_dithering_enabled =
dithering_enabled && dithering_method == int(TextureMappingZone::DitheringHalftone);
dithering_enabled && is_halftone_dithering_method_for_gcode(dithering_method);
const bool halftone_increased_detail_enabled =
dithering_enabled && dithering_method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail);
const float dither_pitch_mm =
dither_pitch_for_gcode(base_outer_width_mm,
dithering_method,
@@ -11370,7 +11327,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
component_key_stream << "|de" << (dithering_enabled ? 1 : 0);
component_key_stream << "|dm" << dithering_method;
component_key_stream << "|dp" << int(std::lround(dither_pitch_mm * 1000.f));
if (dithering_method == int(TextureMappingZone::DitheringHalftone))
if (is_halftone_dithering_method_for_gcode(dithering_method))
component_key_stream << "|hdt" << int(std::lround(halftone_dot_size_mm * 1000.f));
else
component_key_stream << "|hrz" << int(std::lround(dither_cell_size_mm * 1000.f));
@@ -11436,6 +11393,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
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.halftone_increased_detail_enabled =
halftone_increased_detail_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;
@@ -11537,21 +11496,31 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
!vertex_color_weight_field->empty()) {
const float mid_x_mm = 0.5f * (unscale<float>(line.a.x()) + unscale<float>(line.b.x()));
const float mid_y_mm = 0.5f * (unscale<float>(line.a.y()) + unscale<float>(line.b.y()));
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 && !halftone_dithering_enabled);
const float active_halftone_angle_deg =
halftone_screen_angle_deg_for_gcode(texture_filament_color_mode, active_component_idx);
float desired_strength =
halftone_dithering_enabled && !halftone_increased_detail_enabled ?
halftone_screen_cell_tone_for_gcode(*vertex_color_weight_field,
surface_coord,
active_component_idx,
high_resolution_texture_sampling,
halftone_dot_size_mm,
active_halftone_angle_deg,
mid_x_mm,
mid_y_mm) :
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 && !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)) ?
active_halftone_angle_deg) ?
1.f :
0.f;
inset_strength = std::clamp(1.f - desired_strength, 0.f, 1.f);
@@ -12233,7 +12202,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, const OuterWallGradientSurfaceCoord &surface_coord) {
[&outer_wall_gradient_dynamic_ctx, surface_layer, &halftone_screen_cell_tone_for_gcode](
const Line &line, const OuterWallGradientSurfaceCoord &surface_coord) {
OuterWallGradientSegmentMod mod;
if (!outer_wall_gradient_dynamic_ctx.enabled)
return mod;
@@ -12285,14 +12255,25 @@ 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<float>(line.a.x()) + unscale<float>(line.b.x()));
const float mid_y_mm = 0.5f * (unscale<float>(line.a.y()) + unscale<float>(line.b.y()));
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.halftone_dithering_enabled);
float desired_strength =
outer_wall_gradient_dynamic_ctx.halftone_dithering_enabled &&
!outer_wall_gradient_dynamic_ctx.halftone_increased_detail_enabled ?
halftone_screen_cell_tone_for_gcode(*outer_wall_gradient_dynamic_ctx.vertex_color_weight_field,
surface_coord,
outer_wall_gradient_dynamic_ctx.active_component_idx,
outer_wall_gradient_dynamic_ctx.high_resolution_texture_sampling,
outer_wall_gradient_dynamic_ctx.halftone_dot_size_mm,
outer_wall_gradient_dynamic_ctx.active_halftone_angle_deg,
mid_x_mm,
mid_y_mm) :
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.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,

View File

@@ -603,11 +603,12 @@ static std::string dithering_method_name(int mode)
{
switch (clamp_int(mode,
int(TextureMappingZone::DitheringClosest),
int(TextureMappingZone::DitheringHalftone))) {
case int(TextureMappingZone::DitheringClosest): return "closest";
case int(TextureMappingZone::DitheringOrderedBayer): return "ordered_bayer";
case int(TextureMappingZone::DitheringHalftone): return "halftone";
default: return "floyd_steinberg";
int(TextureMappingZone::DitheringHalftoneIncreasedDetail))) {
case int(TextureMappingZone::DitheringClosest): return "closest";
case int(TextureMappingZone::DitheringOrderedBayer): return "ordered_bayer";
case int(TextureMappingZone::DitheringHalftone): return "halftone";
case int(TextureMappingZone::DitheringHalftoneIncreasedDetail): return "halftone_increased_detail";
default: return "floyd_steinberg";
}
}
@@ -620,6 +621,8 @@ static int dithering_method_from_name(std::string name)
return int(TextureMappingZone::DitheringOrderedBayer);
if (name == "halftone")
return int(TextureMappingZone::DitheringHalftone);
if (name == "halftone_increased_detail" || name == "halftone_detail" || name == "halftone_high_detail")
return int(TextureMappingZone::DitheringHalftoneIncreasedDetail);
return int(TextureMappingZone::DitheringFloydSteinberg);
}
@@ -1254,7 +1257,7 @@ void TextureMappingManager::load_entries(const std::string &serialized,
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_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())));

View File

@@ -77,7 +77,8 @@ struct TextureMappingZone
DitheringClosest = 0,
DitheringFloydSteinberg = 1,
DitheringOrderedBayer = 2,
DitheringHalftone = 3
DitheringHalftone = 3,
DitheringHalftoneIncreasedDetail = 4
};
enum TransmissionDistanceCalibrationMode : uint8_t {
@@ -121,7 +122,7 @@ struct TextureMappingZone
static constexpr bool DefaultHighResolutionSampling = true;
static constexpr float DefaultToneGamma = 1.f;
static constexpr int DefaultTransmissionDistanceCalibrationMode = int(TDCalibrationAbsolute);
static constexpr bool DefaultPreviewSimulateColors = false;
static constexpr bool DefaultPreviewSimulateColors = true;
static constexpr bool DefaultPreviewLimitResolution = true;
static constexpr bool DefaultAutoAdjustFilamentSelection = true;

View File

@@ -667,6 +667,8 @@ void GLVolume::simple_render(GLShaderProgram* shader,
const bool base_uses_surface_gradient_preview =
filament_state_uses_surface_gradient_preview(base_filament_id, num_physical, texture_mgr);
const bool base_uses_image_texture_preview = base_uses_texture_preview && !base_uses_surface_gradient_preview;
const bool base_uses_halftone_texture_preview =
texture_preview_halftone_simulation_enabled_for_filament(base_filament_id, num_physical, texture_mgr);
const bool has_surface_gradient_preview_state =
base_uses_surface_gradient_preview ||
(has_mmu_segmentation &&
@@ -681,6 +683,7 @@ void GLVolume::simple_render(GLShaderProgram* shader,
!has_mmu_segmentation &&
!has_texture_mapping_color_preview_data &&
base_uses_image_texture_preview &&
!base_uses_halftone_texture_preview &&
model_volume_has_complete_texture_preview_data(*model_volume) &&
GUI::GLModel::Geometry::has_tex_coord(model.get_geometry().format);
if (!has_mmu_segmentation && !base_uses_texture_preview)
@@ -713,7 +716,7 @@ void GLVolume::simple_render(GLShaderProgram* shader,
(preview_visual_signature << 6) + (preview_visual_signature >> 2);
preview_visual_signature ^= model_volume_texture_mapping_color_preview_signature(*model_volume) + 0x9e3779b97f4a7c15ull +
(preview_visual_signature << 6) + (preview_visual_signature >> 2);
if (has_surface_gradient_preview_state) {
if (has_surface_gradient_preview_state || has_texture_preview_data) {
for (int row = 0; row < 4; ++row) {
for (int col = 0; col < 4; ++col) {
preview_visual_signature ^= std::hash<int>{}(int(std::lround(preview_world_matrix(row, col) * 1000000.0))) +
@@ -771,10 +774,7 @@ void GLVolume::simple_render(GLShaderProgram* shader,
mmuseg_texture_preview_colors,
mmuseg_texture_preview_filament_ids);
}
if (has_texture_preview_state &&
(has_active_texture_mapping_color_preview_data ||
!has_texture_preview_data ||
has_surface_gradient_preview_state)) {
if (has_texture_preview_state) {
build_mmu_vertex_color_preview_models(*model_volume,
triangles_per_type,
state_colors,
@@ -893,6 +893,8 @@ void GLVolume::render_mmu_texture_preview(const Transform3d &view_matrix,
const bool base_uses_surface_gradient_preview =
filament_state_uses_surface_gradient_preview(base_filament_id, num_physical, texture_mgr);
const bool base_uses_image_texture_preview = base_uses_texture_preview && !base_uses_surface_gradient_preview;
const bool base_uses_halftone_texture_preview =
texture_preview_halftone_simulation_enabled_for_filament(base_filament_id, num_physical, texture_mgr);
const bool has_mmu_segmentation = !model_volume->mmu_segmentation_facets.empty();
const bool has_texture_mapping_color_preview_data = model_volume_has_texture_mapping_color_preview_data(*model_volume);
@@ -900,6 +902,7 @@ void GLVolume::render_mmu_texture_preview(const Transform3d &view_matrix,
!has_mmu_segmentation &&
!has_texture_mapping_color_preview_data &&
base_uses_image_texture_preview &&
!base_uses_halftone_texture_preview &&
model_volume_has_complete_texture_preview_data(*model_volume) &&
GUI::GLModel::Geometry::has_tex_coord(model.get_geometry().format);

File diff suppressed because it is too large Load Diff

View File

@@ -71,6 +71,9 @@ void clear_texture_preview_simulation_cache();
bool texture_preview_simulation_enabled_for_filament(unsigned int filament_id,
size_t num_physical,
const TextureMappingManager *texture_mgr);
bool texture_preview_halftone_simulation_enabled_for_filament(unsigned int filament_id,
size_t num_physical,
const TextureMappingManager *texture_mgr);
bool texture_preview_simulation_enabled_for_all_filaments(const std::vector<unsigned int> &filament_ids,
size_t num_physical,
const TextureMappingManager *texture_mgr);

View File

@@ -1321,10 +1321,11 @@ public:
dithering_choices.Add(_L("Floyd-Steinberg"));
dithering_choices.Add(_L("Ordered Bayer"));
dithering_choices.Add(_L("Halftone"));
dithering_choices.Add(_L("Halftone (increased detail)"));
m_dithering_method_choice = new wxChoice(experimental_page, wxID_ANY, wxDefaultPosition, wxDefaultSize, dithering_choices);
m_dithering_method_choice->SetSelection(std::clamp(dithering_method,
int(TextureMappingZone::DitheringClosest),
int(TextureMappingZone::DitheringHalftone)));
int(TextureMappingZone::DitheringHalftoneIncreasedDetail)));
dithering_row->Add(m_dithering_method_choice, 1, wxALIGN_CENTER_VERTICAL);
m_dithering_enabled_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) {
update_dithering_options_visibility(true);
@@ -1634,7 +1635,7 @@ public:
return m_dithering_method_choice ?
std::clamp(m_dithering_method_choice->GetSelection(),
int(TextureMappingZone::DitheringClosest),
int(TextureMappingZone::DitheringHalftone)) :
int(TextureMappingZone::DitheringHalftoneIncreasedDetail)) :
TextureMappingZone::DefaultDitheringMethod;
}
float dithering_resolution_mm() const
@@ -1960,9 +1961,11 @@ private:
const int method = m_dithering_method_choice != nullptr ?
std::clamp(m_dithering_method_choice->GetSelection(),
int(TextureMappingZone::DitheringClosest),
int(TextureMappingZone::DitheringHalftone)) :
int(TextureMappingZone::DitheringHalftoneIncreasedDetail)) :
TextureMappingZone::DefaultDitheringMethod;
const bool halftone = enabled && method == int(TextureMappingZone::DitheringHalftone);
const bool halftone = enabled &&
(method == int(TextureMappingZone::DitheringHalftone) ||
method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail));
if (m_dithering_method_choice != nullptr)
m_dithering_method_choice->Enable(enabled);
if (m_dithering_resolution_panel != nullptr)