From 0bad0d4fd478f58398d5952a754e0a6ac17fa506 Mon Sep 17 00:00:00 2001 From: sentientstardust Date: Fri, 15 May 2026 22:56:35 +0100 Subject: [PATCH] Warn if loaded raw offset atlas uses different min/max line widths than the slicer is using --- .../ImageMapRawFilamentOffsetAtlas.cpp | 50 ++++++++++ .../ImageMapRawFilamentOffsetAtlas.hpp | 11 +++ src/libslic3r/PrintObjectSlice.cpp | 99 +++++++++++++++++++ 3 files changed, 160 insertions(+) diff --git a/src/libslic3r/ImageMapRawFilamentOffsetAtlas.cpp b/src/libslic3r/ImageMapRawFilamentOffsetAtlas.cpp index 5538ea8f050..098791a1404 100644 --- a/src/libslic3r/ImageMapRawFilamentOffsetAtlas.cpp +++ b/src/libslic3r/ImageMapRawFilamentOffsetAtlas.cpp @@ -102,6 +102,41 @@ static std::string standard_hex_for_color_code(const std::string &color) return "#FFFFFF"; } +static ImageMapRawExpectedLineWidth expected_line_width_from_json(const nlohmann::json &root) +{ + ImageMapRawExpectedLineWidth expected; + const auto it = root.find("expected_line_width_mm"); + if (it == root.end() || !it->is_object()) + return expected; + const nlohmann::json &entry = *it; + const auto min_it = entry.find("min"); + const auto max_it = entry.find("max"); + if (min_it == entry.end() || max_it == entry.end() || !min_it->is_number() || !max_it->is_number()) + return expected; + expected.min_mm = min_it->get(); + expected.max_mm = max_it->get(); + if (!std::isfinite(expected.min_mm) || !std::isfinite(expected.max_mm) || expected.min_mm <= 0.0 || expected.max_mm <= 0.0 || + expected.max_mm < expected.min_mm) { + expected = {}; + return expected; + } + const auto warn_it = entry.find("warn_if_differs"); + expected.warn_if_differs = warn_it != entry.end() && warn_it->is_boolean() ? warn_it->get() : false; + expected.valid = true; + return expected; +} + +static ImageMapRawExpectedLineWidth expected_line_width_from_metadata_json(const std::string &metadata_json) +{ + try { + const nlohmann::json root = nlohmann::json::parse(metadata_json); + if (root.is_object()) + return expected_line_width_from_json(root); + } catch (...) { + } + return {}; +} + static nlohmann::json atlas_metadata_json(const ImageMapRawFilamentOffsetAtlas &atlas, uint32_t header_rows) { const uint32_t region_count = (atlas.channels + 2u) / 3u; @@ -121,6 +156,15 @@ static nlohmann::json atlas_metadata_json(const ImageMapRawFilamentOffsetAtlas & entry["hex"] = filament.hex; root["filaments"].push_back(std::move(entry)); } + const ImageMapRawExpectedLineWidth expected = + atlas.expected_line_width_mm.valid ? atlas.expected_line_width_mm : expected_line_width_from_metadata_json(atlas.metadata_json); + if (expected.valid) { + root["expected_line_width_mm"] = { + { "min", expected.min_mm }, + { "max", expected.max_mm }, + { "warn_if_differs", expected.warn_if_differs } + }; + } root["regions"] = nlohmann::json::array(); for (uint32_t region_idx = 0; region_idx < region_count; ++region_idx) { nlohmann::json channels = nlohmann::json::object(); @@ -237,6 +281,11 @@ std::vector image_map_raw_filaments_from_metadata_json(cons return image_map_raw_filaments_for_channels(filaments, channels); } +ImageMapRawExpectedLineWidth image_map_raw_expected_line_width_from_metadata_json(const std::string &metadata_json) +{ + return expected_line_width_from_metadata_json(metadata_json); +} + std::vector image_map_raw_filament_channel_keys(const std::vector &filaments) { std::vector keys; @@ -320,6 +369,7 @@ bool decode_image_map_raw_filament_offset_atlas(const std::vector &rgba decoded.offsets.assign(size_t(decoded.width) * size_t(decoded.height) * size_t(decoded.channels), 0); decoded.mask.assign(size_t(decoded.width) * size_t(decoded.height), 255); decoded.metadata_json = metadata; + decoded.expected_line_width_mm = expected_line_width_from_json(root); const nlohmann::json filaments = root.value("filaments", nlohmann::json::array()); if (filaments.is_array()) { diff --git a/src/libslic3r/ImageMapRawFilamentOffsetAtlas.hpp b/src/libslic3r/ImageMapRawFilamentOffsetAtlas.hpp index dad30a8eb8e..9fe71656f30 100644 --- a/src/libslic3r/ImageMapRawFilamentOffsetAtlas.hpp +++ b/src/libslic3r/ImageMapRawFilamentOffsetAtlas.hpp @@ -15,6 +15,14 @@ struct ImageMapRawFilament std::string hex; }; +struct ImageMapRawExpectedLineWidth +{ + double min_mm { 0.0 }; + double max_mm { 0.0 }; + bool warn_if_differs { false }; + bool valid { false }; +}; + struct ImageMapRawFilamentOffsetAtlas { uint32_t width { 0 }; @@ -24,6 +32,7 @@ struct ImageMapRawFilamentOffsetAtlas std::vector offsets; std::vector mask; std::string metadata_json; + ImageMapRawExpectedLineWidth expected_line_width_mm; bool valid() const; }; @@ -50,6 +59,8 @@ std::vector image_map_raw_filaments_for_channels(const std: std::vector image_map_raw_filaments_from_metadata_json(const std::string &metadata_json, uint32_t channels); +ImageMapRawExpectedLineWidth image_map_raw_expected_line_width_from_metadata_json(const std::string &metadata_json); + std::vector image_map_raw_filament_channel_keys(const std::vector &filaments); std::vector image_map_raw_filament_offset_preview_rgba(const ImageMapRawFilamentOffsetAtlas &atlas); diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 1e9242c94ac..a77da464917 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -1,14 +1,18 @@ #include #include +#include +#include #include #include #include +#include #include #include "ClipperUtils.hpp" #include "ElephantFootCompensation.hpp" +#include "ImageMapRawFilamentOffsetAtlas.hpp" #include "I18N.hpp" #include "Layer.hpp" #include "MultiMaterialSegmentation.hpp" @@ -169,6 +173,99 @@ static std::vector collect_texture_mapping_outer_wall_gradient_line return warnings; } +static bool model_volume_has_raw_offset_texture_data(const ModelVolume *volume) +{ + return volume != nullptr && + volume->imported_texture_width > 0 && + volume->imported_texture_height > 0 && + volume->imported_texture_raw_channels > 0 && + volume->imported_texture_raw_filament_offsets.size() >= + size_t(volume->imported_texture_width) * + size_t(volume->imported_texture_height) * + size_t(volume->imported_texture_raw_channels); +} + +static std::string format_texture_mapping_line_width_mm(double value) +{ + std::ostringstream stream; + stream << std::fixed << std::setprecision(3) << value; + std::string formatted = stream.str(); + while (formatted.size() > 1 && formatted.back() == '0') + formatted.pop_back(); + if (!formatted.empty() && formatted.back() == '.') + formatted.pop_back(); + return formatted + " mm"; +} + +static bool texture_mapping_line_width_differs(double lhs, double rhs) +{ + return std::abs(lhs - rhs) > 0.001; +} + +static std::vector collect_texture_mapping_raw_atlas_line_width_warnings(const PrintObject &print_object) +{ + const Print *print = print_object.print(); + if (print == nullptr) + return {}; + + const ModelObject *model_object = print_object.model_object(); + if (model_object == nullptr) + return {}; + + const double active_max_line_width_mm = + std::max(0.05, print->config().texture_mapping_outer_wall_gradient_max_line_width.value); + const double active_min_line_width_mm = + std::clamp(print->config().texture_mapping_outer_wall_gradient_min_line_width.value, 0.05, active_max_line_width_mm); + + std::vector warnings; + std::set seen; + for (const ModelVolume *volume : model_object->volumes) { + if (!model_volume_has_raw_offset_texture_data(volume)) + continue; + + const ImageMapRawExpectedLineWidth expected = + image_map_raw_expected_line_width_from_metadata_json(volume->imported_texture_raw_metadata_json); + if (!expected.valid || !expected.warn_if_differs) + continue; + if (!texture_mapping_line_width_differs(active_min_line_width_mm, expected.min_mm) && + !texture_mapping_line_width_differs(active_max_line_width_mm, expected.max_mm)) + continue; + + const std::vector used_extruders = volume->get_extruders(); + for (const int filament_id : used_extruders) { + if (filament_id <= 0) + continue; + + const unsigned int filament_id_u = unsigned(filament_id); + const TextureMappingZone *zone = print->texture_mapping_manager().zone_from_id(filament_id_u); + if (zone == nullptr || + !zone->enabled || + zone->deleted || + !zone->is_image_texture() || + zone->texture_mapping_mode != int(TextureMappingZone::TextureMappingRawValues)) + continue; + + std::ostringstream key_stream; + key_stream << filament_id_u << "|" << std::fixed << std::setprecision(3) << expected.min_mm << "|" << expected.max_mm; + const std::string key = key_stream.str(); + if (!seen.insert(key).second) + continue; + + warnings.emplace_back( + L("Texture mapping zone ") + std::to_string(filament_id_u) + + L(" uses a raw filament offset atlas authored for line widths ") + + format_texture_mapping_line_width_mm(expected.min_mm) + " - " + + format_texture_mapping_line_width_mm(expected.max_mm) + + L(", but current texture mapping settings use ") + + format_texture_mapping_line_width_mm(active_min_line_width_mm) + " - " + + format_texture_mapping_line_width_mm(active_max_line_width_mm) + + L(". Update the texture mapping minimum/maximum outer wall line width or regenerate the raw offset atlas.")); + } + } + + return warnings; +} + static std::vector collect_texture_mapping_vertex_color_match_warnings(const PrintObject &print_object) { const Print *print = print_object.print(); @@ -1467,6 +1564,8 @@ void PrintObject::slice_volumes() this->apply_conical_overhang(); for (const std::string &warning_msg : collect_texture_mapping_outer_wall_gradient_line_width_warnings(*this)) this->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, warning_msg); + for (const std::string &warning_msg : collect_texture_mapping_raw_atlas_line_width_warnings(*this)) + this->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, warning_msg); for (const std::string &warning_msg : collect_texture_mapping_vertex_color_match_warnings(*this)) this->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, warning_msg); for (const std::string &warning_msg : collect_texture_mapping_filament_color_match_warnings(*this))