Warn if loaded raw offset atlas uses different min/max line widths than the slicer is using

This commit is contained in:
sentientstardust
2026-05-15 22:56:35 +01:00
parent e8c305e4cd
commit 0bad0d4fd4
3 changed files with 160 additions and 0 deletions

View File

@@ -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<double>();
expected.max_mm = max_it->get<double>();
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<bool>() : 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<ImageMapRawFilament> 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<std::string> image_map_raw_filament_channel_keys(const std::vector<ImageMapRawFilament> &filaments)
{
std::vector<std::string> keys;
@@ -320,6 +369,7 @@ bool decode_image_map_raw_filament_offset_atlas(const std::vector<uint8_t> &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()) {

View File

@@ -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<uint8_t> offsets;
std::vector<uint8_t> mask;
std::string metadata_json;
ImageMapRawExpectedLineWidth expected_line_width_mm;
bool valid() const;
};
@@ -50,6 +59,8 @@ std::vector<ImageMapRawFilament> image_map_raw_filaments_for_channels(const std:
std::vector<ImageMapRawFilament> 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<std::string> image_map_raw_filament_channel_keys(const std::vector<ImageMapRawFilament> &filaments);
std::vector<uint8_t> image_map_raw_filament_offset_preview_rgba(const ImageMapRawFilamentOffsetAtlas &atlas);

View File

@@ -1,14 +1,18 @@
#include <boost/log/trivial.hpp>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <limits>
#include <memory>
#include <set>
#include <sstream>
#include <tbb/parallel_for.h>
#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<std::string> 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<std::string> 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<std::string> warnings;
std::set<std::string> 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<int> 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<std::string> 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))