More work on Seam Hiding

This commit is contained in:
sentientstardust
2026-05-09 22:19:07 +01:00
parent 6ab2e96c0a
commit 57cd99b950
2 changed files with 173 additions and 202 deletions

View File

@@ -571,7 +571,7 @@ endif()
# boost::process was introduced first in version 1.64.0,
# boost::beast::detail::base64 was introduced first in version 1.66.0
if(POLICY CMP0167)
cmake_policy(SET CMP0167 NEW)
cmake_policy(SET CMP0167 OLD)
endif()
set(Boost_NO_SYSTEM_PATHS TRUE)
find_package(Boost 1.83.0 REQUIRED COMPONENTS system filesystem thread log log_setup locale regex chrono atomic date_time iostreams program_options nowide)

View File

@@ -8685,7 +8685,6 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
{
if (m_curr_print == nullptr ||
m_layer == nullptr ||
m_layer->upper_layer == nullptr ||
m_writer.filament() == nullptr ||
loop.paths.empty() ||
!is_external_perimeter(loop.role()))
@@ -8708,8 +8707,9 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
const PrintObject *layer_object = m_layer->object();
const Layer *upper_layer = m_layer->upper_layer;
const Layer *lower_layer = m_layer->lower_layer;
const int object_layer_count = layer_object ? int(layer_object->layer_count()) : 0;
if (layer_object == nullptr || upper_layer == nullptr || object_layer_count <= 0)
if (layer_object == nullptr || object_layer_count <= 0 || (upper_layer == nullptr && lower_layer == nullptr))
return std::nullopt;
std::vector<unsigned int> component_ids = decode_offset_component_ids_for_gcode(*zone, num_physical);
@@ -8896,9 +8896,16 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
const unsigned int current_component_id = unsigned(m_writer.filament()->id() + 1);
const std::optional<SeamLayerTextureState> current_state = texture_state_for_layer(m_layer, current_component_id);
const std::optional<SeamLayerTextureState> upper_state = texture_state_for_layer(upper_layer, 0);
if (!current_state || !upper_state)
const std::optional<SeamLayerTextureState> upper_state =
upper_layer != nullptr ? texture_state_for_layer(upper_layer, 0) : std::optional<SeamLayerTextureState>();
const std::optional<SeamLayerTextureState> lower_state =
lower_layer != nullptr ? texture_state_for_layer(lower_layer, 0) : std::optional<SeamLayerTextureState>();
if (!current_state ||
(upper_layer != nullptr && !upper_state) ||
(lower_layer != nullptr && !lower_state) ||
(!upper_state && !lower_state))
return std::nullopt;
const bool require_bidirectional_cover = upper_state.has_value() && lower_state.has_value();
std::vector<float> reference_nozzles;
reference_nozzles.reserve(component_ids.size() + 2);
@@ -8955,6 +8962,11 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
float width_delta_mm { 0.f };
};
struct SeamAdjacentOcclusion {
float cover_mm { 0.f };
float score_mm { 0.f };
};
struct SeamHidingCandidate {
Point point;
float cover_mm { 0.f };
@@ -9114,6 +9126,147 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
if (is_external_perimeter(path.role()) && path.polyline.points.size() >= 2)
external_paths.push_back(&path);
auto evaluate_sample_for_path = [&](const ExtrusionPath &path,
size_t path_index,
size_t segment_index,
double segment_t,
double arc_mm,
double span_mm) -> std::optional<SeamHidingCandidate> {
const Points &points = path.polyline.points;
if (segment_index == 0 || segment_index >= points.size())
return std::nullopt;
const float path_outer_width_mm = std::max(
0.01f,
path.width > EPSILON ? path.width : float(m_config.outer_wall_line_width.get_abs_value(reference_nozzle)));
const float texture_mapping_max_outer_width_mm = std::max(
0.05f,
float(m_config.texture_mapping_outer_wall_gradient_max_line_width.value));
const float base_outer_width_mm = vertex_color_match_mode ? texture_mapping_max_outer_width_mm : path_outer_width_mm;
const float flow_reference_width_mm = path_outer_width_mm;
const double half_flow_reference_scaled = scale_(0.5 * double(flow_reference_width_mm));
const float max_local_edge_tangent_delta_mm = std::max(0.75f, base_outer_width_mm * 1.5f);
const float max_local_edge_normal_delta_mm =
std::max(1.25f, base_outer_width_mm * 3.f + 2.f * max_allowed_distance_mm);
const Point &a = points[segment_index - 1];
const Point &b = points[segment_index];
const double ax = double(a.x());
const double ay = double(a.y());
const double bx = double(b.x());
const double by = double(b.y());
const double dx_scaled = bx - ax;
const double dy_scaled = by - ay;
const double len_scaled = std::hypot(dx_scaled, dy_scaled);
if (len_scaled <= EPSILON)
return std::nullopt;
const Point sample_point(coord_t(std::llround(ax + segment_t * dx_scaled)),
coord_t(std::llround(ay + segment_t * dy_scaled)));
double outward_x = 0.0;
double outward_y = 0.0;
resolve_segment_shift_outward_normal_for_gcode(m_layer,
sample_point,
dx_scaled,
dy_scaled,
len_scaled,
double(sample_point.x()) - double(object_center.x()),
double(sample_point.y()) - double(object_center.y()),
outward_x,
outward_y);
const double tangent_x = dx_scaled / len_scaled;
const double tangent_y = dy_scaled / len_scaled;
const std::optional<SeamTextureEnvelope> current_envelope =
texture_envelope_for_state(*current_state, path, sample_point, outward_x, outward_y, reference_nozzle);
if (!current_envelope)
return std::nullopt;
const Point current_outer_edge = scaled_offset_point(sample_point,
outward_x,
outward_y,
current_envelope->outer_offset_mm);
auto evaluate_adjacent_occlusion =
[&](const std::optional<SeamLayerTextureState> &adjacent_state) -> std::optional<SeamAdjacentOcclusion> {
if (!adjacent_state)
return std::nullopt;
const std::optional<NormalAwareLayerSliceBoundaryPointForGCode> adjacent_boundary =
find_normal_aware_layer_slice_boundary_point_for_gcode(adjacent_state->layer,
current_outer_edge,
outward_x,
outward_y,
tangent_x,
tangent_y,
max_local_edge_normal_delta_mm,
max_local_edge_tangent_delta_mm);
if (!adjacent_boundary)
return std::nullopt;
const Point adjacent_centerline(
coord_t(std::llround(double(adjacent_boundary->point.x()) - adjacent_boundary->outward_x * half_flow_reference_scaled)),
coord_t(std::llround(double(adjacent_boundary->point.y()) - adjacent_boundary->outward_y * half_flow_reference_scaled)));
const std::optional<SeamTextureEnvelope> adjacent_envelope =
texture_envelope_for_state(*adjacent_state,
path,
adjacent_centerline,
adjacent_boundary->outward_x,
adjacent_boundary->outward_y,
reference_nozzle);
if (!adjacent_envelope)
return std::nullopt;
const Point adjacent_outer_edge = scaled_offset_point(adjacent_centerline,
adjacent_boundary->outward_x,
adjacent_boundary->outward_y,
adjacent_envelope->outer_offset_mm);
const double cover_scaled =
(double(adjacent_outer_edge.x()) - double(current_outer_edge.x())) * outward_x +
(double(adjacent_outer_edge.y()) - double(current_outer_edge.y())) * outward_y;
const float cover_mm = unscale<float>(cover_scaled);
if (!std::isfinite(cover_mm))
return std::nullopt;
const float texture_cover_bonus_mm = std::max(0.f, current_envelope->width_delta_mm - adjacent_envelope->width_delta_mm);
const float score_mm = cover_mm > 0.f ?
std::max(0.f, cover_mm + 0.25f * texture_cover_bonus_mm - 0.2f * adjacent_boundary->tangent_delta_mm) :
0.f;
return SeamAdjacentOcclusion{ std::max(0.f, cover_mm), score_mm };
};
const std::optional<SeamAdjacentOcclusion> upper_occlusion = evaluate_adjacent_occlusion(upper_state);
const std::optional<SeamAdjacentOcclusion> lower_occlusion = evaluate_adjacent_occlusion(lower_state);
float cover_mm = 0.f;
float score_mm = 0.f;
if (require_bidirectional_cover) {
if (!upper_occlusion || !lower_occlusion)
return std::nullopt;
cover_mm = std::min(upper_occlusion->cover_mm, lower_occlusion->cover_mm);
const float weaker_score_mm = std::min(upper_occlusion->score_mm, lower_occlusion->score_mm);
const float stronger_score_mm = std::max(upper_occlusion->score_mm, lower_occlusion->score_mm);
if (cover_mm > 0.f && weaker_score_mm > 0.f)
score_mm = weaker_score_mm + 0.35f * stronger_score_mm;
} else {
const std::optional<SeamAdjacentOcclusion> &occlusion = upper_occlusion ? upper_occlusion : lower_occlusion;
if (!occlusion)
return std::nullopt;
cover_mm = occlusion->cover_mm;
score_mm = occlusion->score_mm;
}
return SeamHidingCandidate{
sample_point,
cover_mm,
score_mm,
arc_mm,
span_mm,
segment_t,
path_index,
segment_index
};
};
for (size_t external_path_idx = 0; external_path_idx < external_paths.size(); ++external_path_idx) {
const ExtrusionPath *path_ptr = external_paths[external_path_idx];
const ExtrusionPath &path = *path_ptr;
@@ -9127,12 +9280,7 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
0.05f,
float(m_config.texture_mapping_outer_wall_gradient_max_line_width.value));
const float base_outer_width_mm = vertex_color_match_mode ? texture_mapping_max_outer_width_mm : path_outer_width_mm;
const float flow_reference_width_mm = path_outer_width_mm;
const double half_flow_reference_scaled = scale_(0.5 * double(flow_reference_width_mm));
const float sample_step_mm = std::clamp(0.5f * base_outer_width_mm, 0.15f, 0.5f);
const float max_local_edge_tangent_delta_mm = std::max(0.75f, base_outer_width_mm * 1.5f);
const float max_local_edge_normal_delta_mm =
std::max(1.25f, base_outer_width_mm * 3.f + 2.f * max_allowed_distance_mm);
const Points &points = path.polyline.points;
const size_t path_index = external_path_idx;
double path_arc_start_mm = total_length_mm;
@@ -9142,102 +9290,19 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
double arc_mm,
double span_mm,
bool add_candidate) -> std::optional<SeamHidingCandidate> {
const Point &a = points[segment_index - 1];
const Point &b = points[segment_index];
const double ax = double(a.x());
const double ay = double(a.y());
const double bx = double(b.x());
const double by = double(b.y());
const double dx_scaled = bx - ax;
const double dy_scaled = by - ay;
const double len_scaled = std::hypot(dx_scaled, dy_scaled);
if (len_scaled <= EPSILON)
std::optional<SeamHidingCandidate> candidate =
evaluate_sample_for_path(path, path_index, segment_index, segment_t, arc_mm, span_mm);
if (!candidate)
return std::nullopt;
const Point sample_point(coord_t(std::llround(ax + segment_t * dx_scaled)),
coord_t(std::llround(ay + segment_t * dy_scaled)));
double outward_x = 0.0;
double outward_y = 0.0;
resolve_segment_shift_outward_normal_for_gcode(m_layer,
sample_point,
dx_scaled,
dy_scaled,
len_scaled,
double(sample_point.x()) - double(object_center.x()),
double(sample_point.y()) - double(object_center.y()),
outward_x,
outward_y);
const double tangent_x = dx_scaled / len_scaled;
const double tangent_y = dy_scaled / len_scaled;
const std::optional<SeamTextureEnvelope> current_envelope =
texture_envelope_for_state(*current_state, path, sample_point, outward_x, outward_y, reference_nozzle);
if (!current_envelope)
return std::nullopt;
const Point current_outer_edge = scaled_offset_point(sample_point,
outward_x,
outward_y,
current_envelope->outer_offset_mm);
const std::optional<NormalAwareLayerSliceBoundaryPointForGCode> upper_boundary =
find_normal_aware_layer_slice_boundary_point_for_gcode(upper_layer,
current_outer_edge,
outward_x,
outward_y,
tangent_x,
tangent_y,
max_local_edge_normal_delta_mm,
max_local_edge_tangent_delta_mm);
if (!upper_boundary)
return std::nullopt;
const Point upper_centerline(
coord_t(std::llround(double(upper_boundary->point.x()) - upper_boundary->outward_x * half_flow_reference_scaled)),
coord_t(std::llround(double(upper_boundary->point.y()) - upper_boundary->outward_y * half_flow_reference_scaled)));
const std::optional<SeamTextureEnvelope> upper_envelope =
texture_envelope_for_state(*upper_state,
path,
upper_centerline,
upper_boundary->outward_x,
upper_boundary->outward_y,
reference_nozzle);
if (!upper_envelope)
return std::nullopt;
const Point upper_outer_edge = scaled_offset_point(upper_centerline,
upper_boundary->outward_x,
upper_boundary->outward_y,
upper_envelope->outer_offset_mm);
const double cover_scaled =
(double(upper_outer_edge.x()) - double(current_outer_edge.x())) * outward_x +
(double(upper_outer_edge.y()) - double(current_outer_edge.y())) * outward_y;
const float cover_mm = unscale<float>(cover_scaled);
if (!std::isfinite(cover_mm))
return std::nullopt;
const float texture_cover_bonus_mm = std::max(0.f, current_envelope->width_delta_mm - upper_envelope->width_delta_mm);
const float score_mm = cover_mm > 0.f ?
std::max(0.f, cover_mm + 0.25f * texture_cover_bonus_mm - 0.2f * upper_boundary->tangent_delta_mm) :
0.f;
SeamHidingCandidate candidate{
sample_point,
std::max(0.f, cover_mm),
score_mm,
arc_mm,
span_mm,
segment_t,
path_index,
segment_index
};
if (add_candidate) {
candidates.push_back(candidate);
min_score_mm = std::min(min_score_mm, score_mm);
weighted_score_mm += double(score_mm) * span_mm;
candidates.push_back(*candidate);
min_score_mm = std::min(min_score_mm, candidate->score_mm);
weighted_score_mm += double(candidate->score_mm) * span_mm;
weighted_length_mm += span_mm;
if (score_mm > best_score_mm) {
best_score_mm = score_mm;
best_candidate = candidate;
if (candidate->score_mm > best_score_mm) {
best_score_mm = candidate->score_mm;
best_candidate = *candidate;
}
}
@@ -9309,106 +9374,12 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
if (std::abs(t - best_candidate.segment_t) <= 1e-5)
continue;
const std::optional<SeamHidingCandidate> refined =
[&]() -> std::optional<SeamHidingCandidate> {
const Points &points = best_path->polyline.points;
const float path_outer_width_mm = std::max(
0.01f,
best_path->width > EPSILON ?
best_path->width :
float(m_config.outer_wall_line_width.get_abs_value(reference_nozzle)));
const float texture_mapping_max_outer_width_mm = std::max(
0.05f,
float(m_config.texture_mapping_outer_wall_gradient_max_line_width.value));
const float base_outer_width_mm =
vertex_color_match_mode ? texture_mapping_max_outer_width_mm : path_outer_width_mm;
const float flow_reference_width_mm = path_outer_width_mm;
const double half_flow_reference_scaled = scale_(0.5 * double(flow_reference_width_mm));
const float max_local_edge_tangent_delta_mm = std::max(0.75f, base_outer_width_mm * 1.5f);
const float max_local_edge_normal_delta_mm =
std::max(1.25f, base_outer_width_mm * 3.f + 2.f * max_allowed_distance_mm);
const Point &a = points[best_candidate.segment_index - 1];
const Point &b = points[best_candidate.segment_index];
const double ax = double(a.x());
const double ay = double(a.y());
const double bx = double(b.x());
const double by = double(b.y());
const double dx_scaled = bx - ax;
const double dy_scaled = by - ay;
const double len_scaled = std::hypot(dx_scaled, dy_scaled);
if (len_scaled <= EPSILON)
return std::nullopt;
const Point sample_point(coord_t(std::llround(ax + t * dx_scaled)),
coord_t(std::llround(ay + t * dy_scaled)));
double outward_x = 0.0;
double outward_y = 0.0;
resolve_segment_shift_outward_normal_for_gcode(m_layer,
sample_point,
dx_scaled,
dy_scaled,
len_scaled,
double(sample_point.x()) - double(object_center.x()),
double(sample_point.y()) - double(object_center.y()),
outward_x,
outward_y);
const double tangent_x = dx_scaled / len_scaled;
const double tangent_y = dy_scaled / len_scaled;
const std::optional<SeamTextureEnvelope> current_envelope =
texture_envelope_for_state(*current_state, *best_path, sample_point, outward_x, outward_y, reference_nozzle);
if (!current_envelope)
return std::nullopt;
const Point current_outer_edge = scaled_offset_point(sample_point,
outward_x,
outward_y,
current_envelope->outer_offset_mm);
const std::optional<NormalAwareLayerSliceBoundaryPointForGCode> upper_boundary =
find_normal_aware_layer_slice_boundary_point_for_gcode(upper_layer,
current_outer_edge,
outward_x,
outward_y,
tangent_x,
tangent_y,
max_local_edge_normal_delta_mm,
max_local_edge_tangent_delta_mm);
if (!upper_boundary)
return std::nullopt;
const Point upper_centerline(
coord_t(std::llround(double(upper_boundary->point.x()) -
upper_boundary->outward_x * half_flow_reference_scaled)),
coord_t(std::llround(double(upper_boundary->point.y()) -
upper_boundary->outward_y * half_flow_reference_scaled)));
const std::optional<SeamTextureEnvelope> upper_envelope =
texture_envelope_for_state(*upper_state,
*best_path,
upper_centerline,
upper_boundary->outward_x,
upper_boundary->outward_y,
reference_nozzle);
if (!upper_envelope)
return std::nullopt;
const Point upper_outer_edge = scaled_offset_point(upper_centerline,
upper_boundary->outward_x,
upper_boundary->outward_y,
upper_envelope->outer_offset_mm);
const double cover_scaled =
(double(upper_outer_edge.x()) - double(current_outer_edge.x())) * outward_x +
(double(upper_outer_edge.y()) - double(current_outer_edge.y())) * outward_y;
const float cover_mm = unscale<float>(cover_scaled);
const float texture_cover_bonus_mm =
std::max(0.f, current_envelope->width_delta_mm - upper_envelope->width_delta_mm);
const float score_mm = cover_mm > 0.f ?
std::max(0.f, cover_mm + 0.25f * texture_cover_bonus_mm - 0.2f * upper_boundary->tangent_delta_mm) :
0.f;
return SeamHidingCandidate{
sample_point,
std::max(0.f, cover_mm),
score_mm,
best_candidate.arc_mm + (t - best_candidate.segment_t) * len_mm,
0.25 * len_mm,
t,
best_candidate.path_index,
best_candidate.segment_index
};
}();
evaluate_sample_for_path(*best_path,
best_candidate.path_index,
best_candidate.segment_index,
t,
best_candidate.arc_mm + (t - best_candidate.segment_t) * len_mm,
0.25 * len_mm);
if (refined && refined->score_mm > best_score_mm) {
best_score_mm = refined->score_mm;
best_candidate = *refined;