Support loading side-surface calibration JSON

- Move ColorSolver library to deps_src
This commit is contained in:
sentientstardust
2026-06-06 08:47:01 +01:00
parent 3249a2ccee
commit 3a7f52398c
17 changed files with 718 additions and 68 deletions

View File

@@ -31,6 +31,7 @@ add_subdirectory(miniz)
add_subdirectory(minilzo)
add_subdirectory(pigment-painter)
add_subdirectory(prusa-fdm-mixer)
add_subdirectory(colorsolver)
add_subdirectory(qhull)
add_subdirectory(qoi)
add_subdirectory(semver) # Semver static library

View File

@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.13)
project(colorsolver)
add_library(colorsolver STATIC
ColorSolver.cpp
ColorSolver.hpp
)
target_include_directories(colorsolver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_features(colorsolver PUBLIC cxx_std_17)
target_link_libraries(colorsolver PUBLIC pigment_painter prusa_fdm_mixer)

View File

@@ -1637,6 +1637,11 @@ ColorSolverCandidateSet build_color_solver_candidates(const std::vector<std::arr
return candidates;
}
void build_color_solver_candidate_kd_trees(ColorSolverCandidateSet &candidates)
{
build_color_solver_kd_trees(candidates);
}
const ColorSolverCandidateSet &color_solver_candidates(ColorSolverCandidateCache &cache,
const std::vector<std::array<float, 3>> &component_colors,
ColorSolverMixModel mix_model,

View File

@@ -169,6 +169,7 @@ std::string color_solver_candidate_cache_key(const std::vector<std::array<float,
ColorSolverCandidateSet build_color_solver_candidates(const std::vector<std::array<float, 3>> &component_colors,
ColorSolverMixModel mix_model,
int total_units = 0);
void build_color_solver_candidate_kd_trees(ColorSolverCandidateSet &candidates);
const ColorSolverCandidateSet &color_solver_candidates(ColorSolverCandidateCache &cache,
const std::vector<std::array<float, 3>> &component_colors,
ColorSolverMixModel mix_model,

View File

@@ -148,8 +148,6 @@ set(lisbslic3r_sources
Feature/Interlocking/VoxelUtils.cpp
Feature/Interlocking/VoxelUtils.hpp
FileParserError.hpp
ColorSolver.cpp
ColorSolver.hpp
Fill/Fill3DHoneycomb.cpp
Fill/Fill3DHoneycomb.hpp
Fill/FillAdaptive.cpp
@@ -608,8 +606,7 @@ target_link_libraries(libslic3r
libnest2d
miniz
opencv_world
pigment_painter
prusa_fdm_mixer
colorsolver
PRIVATE
${CMAKE_DL_LIBS}
${EXPAT_LIBRARIES}

View File

@@ -16,7 +16,7 @@
#endif
#include "../ClipperUtils.hpp"
#include "../Color.hpp"
#include "../ColorSolver.hpp"
#include "ColorSolver.hpp"
#include "../Geometry.hpp"
#include "../Layer.hpp"
#include "../MarchingSquares.hpp"
@@ -12418,8 +12418,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree,
FillParams fallback_params = params;
fallback_params.flow = fallback_flow;
fallback_params.pattern = ipRectilinear;
fallback_params.no_edge_overlap =
!surface_fill.params.texture_mapping_top_surface_contoning_partition_color_regions;
fallback_params.no_edge_overlap = true;
fallback_params.edge_overlap_width_factor = 0.5f;
ExtrusionEntitiesPtr hybrid_interior_entities;
if (!hybrid_boundary_skin && collection && !collection->empty()) {
apply_top_surface_image_collection_metadata(*collection, surface_fill.params, std::nullopt, throw_if_canceled_ptr);

View File

@@ -98,6 +98,7 @@ struct FillParams
bool dont_sort{ false }; // do not sort the lines, just simply connect them
bool can_reverse{true};
bool no_edge_overlap{false};
float edge_overlap_width_factor{1.0f};
float horiz_move{0.0}; //move infill to get cross zag pattern
bool symmetric_infill_y_axis{false};

View File

@@ -2768,7 +2768,9 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa
assert(params.density > 0.0001f && params.density <= 1.f);
coord_t line_spacing = coord_t(scale_(this->spacing) / params.density);
const coordf_t edge_width = std::max<coordf_t>(this->spacing, coordf_t(params.flow.width()));
const coordf_t edge_overlap_width = std::min<coordf_t>(0.075 * edge_width, coordf_t(0.05));
const coordf_t edge_overlap_width =
std::clamp<coordf_t>(params.edge_overlap_width_factor, coordf_t(0.0), coordf_t(1.0)) *
std::min<coordf_t>(0.075 * edge_width, coordf_t(0.05));
const coordf_t outer_clearance = params.no_edge_overlap ?
std::max<coordf_t>(0.5 * edge_width - edge_overlap_width, coordf_t(0.0)) :
(0.5f - INFILL_OVERLAP_OVER_SPACING) * this->spacing;

View File

@@ -6122,8 +6122,10 @@ static TransmissionDistanceCalibrationContextForGCode transmission_distance_cali
TransmissionDistanceCalibrationContextForGCode context;
context.mode = std::clamp(zone.transmission_distance_calibration_mode,
int(TextureMappingZone::TDCalibrationNone),
int(TextureMappingZone::TDCalibrationNeighbor));
if (context.mode == int(TextureMappingZone::TDCalibrationNone) || component_ids.empty())
int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample));
if (context.mode == int(TextureMappingZone::TDCalibrationNone) ||
context.mode == int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample) ||
component_ids.empty())
return context;
std::vector<float> explicit_tds(component_ids.size(), 0.f);
@@ -8272,6 +8274,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
float halftone_dot_size_mm,
const std::vector<float> &component_strength_factors,
const std::vector<float> &component_minimum_offset_factors,
const GCodeGenericMixCandidateSet *calibrated_side_candidates,
std::map<std::string, GCodeGenericMixCandidateSet> *generic_mix_candidate_cache,
std::map<const PrintObject*, GCodeUVTextureTriangleCache> *uv_texture_triangle_cache,
float texture_contrast_pct,
@@ -8800,8 +8803,11 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
int(TextureMappingZone::DitheringClosest),
int(TextureMappingZone::DitheringHalftoneV2));
std::vector<uint32_t> binary_dither_masks;
const bool has_calibrated_side_candidates =
calibrated_side_candidates != nullptr && !calibrated_side_candidates->empty();
const bool can_binary_dither =
dithering_enabled &&
!has_calibrated_side_candidates &&
!raw_values_mode &&
!is_halftone_dithering_method_for_gcode(clamped_binary_dither_method) &&
component_count > 0 &&
@@ -8958,7 +8964,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
const size_t sample_count = samples.size();
GCodeGenericMixCandidateSet local_generic_mix_candidates;
const GCodeGenericMixCandidateSet *generic_mix_candidates = nullptr;
if (!raw_values_mode) {
if (!raw_values_mode && !has_calibrated_side_candidates) {
std::vector<float> optimized_probe;
if (!use_fixed_color_generic_solver) {
const std::array<float, 3> probe_target { 0.f, 0.f, 0.f };
@@ -9038,6 +9044,14 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
for (size_t channel_idx = 0; channel_idx < channel_count; ++channel_idx)
desired[channel_idx] = clamp01f_for_gcode(channels[channel_idx]);
mapped_component_count = channel_count;
} else if (has_calibrated_side_candidates) {
std::vector<float> calibrated =
best_component_mix_weights_for_target_for_gcode(*calibrated_side_candidates,
target,
int(TextureMappingZone::GenericSolverClosestMix),
effective_solver_mode);
if (calibrated.size() == component_count)
desired = std::move(calibrated);
} else {
std::vector<float> optimized;
if (!use_fixed_color_generic_solver)
@@ -9061,7 +9075,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
}
}
if (!has_binary_dither && !has_raw_component_weights && std::abs(contrast_factor - 1.f) > 1e-5f)
if (!has_binary_dither && !has_raw_component_weights && !has_calibrated_side_candidates && std::abs(contrast_factor - 1.f) > 1e-5f)
apply_texture_contrast_to_mapped_components_for_gcode(desired, contrast_factor, mapped_component_count);
for (size_t component_idx = 0; component_idx < component_count; ++component_idx) {
@@ -9550,9 +9564,18 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
std::clamp(zone->tone_gamma, 0.5f, 3.f);
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 std::vector<float> component_strength_factors = overhang_component_strength_factors_for_gcode(*zone, component_ids);
const std::vector<float> component_minimum_offset_factors =
overhang_component_minimum_offset_factors_for_gcode(*zone, component_ids);
const bool calibrated_side_mode =
vertex_color_match_mode &&
!raw_texture_mapping_mode &&
zone->transmission_distance_calibration_mode == int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample);
std::vector<float> component_strength_factors;
component_strength_factors.reserve(component_ids.size());
std::vector<float> component_minimum_offset_factors;
component_minimum_offset_factors.reserve(component_ids.size());
for (const unsigned int id : component_ids) {
component_strength_factors.emplace_back(calibrated_side_mode ? 1.f : overhang_filament_strength_factor_for_gcode(*zone, id));
component_minimum_offset_factors.emplace_back(calibrated_side_mode ? 0.f : overhang_filament_minimum_offset_factor_for_gcode(*zone, id));
}
std::vector<std::array<float, 3>> component_colors;
component_colors.reserve(component_ids.size());
@@ -9579,6 +9602,26 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
(missing_component_color || component_colors.size() != component_ids.size() || component_colors.empty()))
return std::nullopt;
std::vector<float> component_transmission_distances_mm;
component_transmission_distances_mm.reserve(component_ids.size());
for (const unsigned int id : component_ids) {
const size_t idx = id > 0 ? size_t(id - 1) : size_t(-1);
const float value = idx < zone->filament_transmission_distances_mm.size() ?
zone->filament_transmission_distances_mm[idx] :
0.f;
component_transmission_distances_mm.emplace_back(std::isfinite(value) && value > 0.f ? std::clamp(value, 0.01f, 50.f) : 0.f);
}
std::optional<GCodeGenericMixCandidateSet> calibrated_side_candidates;
const GCodeGenericMixCandidateSet *calibrated_side_candidates_ptr = nullptr;
if (calibrated_side_mode) {
calibrated_side_candidates =
texture_mapping_side_surface_color_calibrated_candidates(*zone,
component_colors,
component_transmission_distances_mm);
if (calibrated_side_candidates && !calibrated_side_candidates->empty())
calibrated_side_candidates_ptr = &*calibrated_side_candidates;
}
const TransmissionDistanceCalibrationContextForGCode td_calibration_context =
transmission_distance_calibration_context_for_gcode(*zone,
component_ids,
@@ -9613,6 +9656,8 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
component_key_stream << "|tg" << int(std::lround(texture_tone_gamma * 100.f));
component_key_stream << "|hr" << (high_resolution_texture_sampling ? 1 : 0);
component_key_stream << "|hs" << (high_speed_image_texture_sampling ? 1 : 0);
if (calibrated_side_mode)
component_key_stream << "|sc" << std::hash<std::string>{}(zone->side_surface_color_calibration_json);
const std::string component_key_prefix = component_key_stream.str();
struct SeamLayerTextureState {
@@ -9683,6 +9728,7 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
halftone_dot_size_mm,
component_strength_factors,
component_minimum_offset_factors,
calibrated_side_candidates_ptr,
&m_generic_solver_mix_candidate_cache,
&m_uv_texture_triangle_cache,
texture_contrast_pct,
@@ -9712,9 +9758,9 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
active_component_id,
active_component_idx,
weight_field,
texture_mapping_offset_filament_strength_factor(*zone, active_component_id),
texture_mapping_offset_filament_minimum_offset_factor(*zone, active_component_id),
transmission_distance_width_factor_for_gcode(td_calibration_context, active_component_idx, previous_component_idx),
calibrated_side_mode ? 1.f : texture_mapping_offset_filament_strength_factor(*zone, active_component_id),
calibrated_side_mode ? 0.f : texture_mapping_offset_filament_minimum_offset_factor(*zone, active_component_id),
calibrated_side_mode ? 1.f : transmission_distance_width_factor_for_gcode(td_calibration_context, active_component_idx, previous_component_idx),
signed_fade_factor,
fade_factor
};

View File

@@ -15,7 +15,7 @@
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/ColorSolver.hpp"
#include "ColorSolver.hpp"
#include "libslic3r/TextureMapping.hpp"
#include "libslic3r/Polyline.hpp"
#include "libslic3r/TriangleMesh.hpp"

View File

@@ -1125,20 +1125,31 @@ static std::string transmission_distance_calibration_mode_name(int mode)
{
switch (clamp_int(mode,
int(TextureMappingZone::TDCalibrationNone),
int(TextureMappingZone::TDCalibrationNeighbor))) {
int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample))) {
case int(TextureMappingZone::TDCalibrationNone): return "none";
case int(TextureMappingZone::TDCalibrationNeighbor): return "neighbor";
case int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample):
return "calibrated_nearest_measured_sample";
default: return "absolute";
}
}
static int transmission_distance_calibration_mode_from_name(std::string name)
{
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return char(std::tolower(c)); });
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) {
const char lowered = char(std::tolower(c));
return lowered == '-' || lowered == ' ' ? '_' : lowered;
});
if (name == "none" || name == "off" || name == "disabled")
return int(TextureMappingZone::TDCalibrationNone);
if (name == "neighbor" || name == "neighbour")
if (name == "neighbor" || name == "neighbour" || name == "neighbor_td" || name == "neighbour_td")
return int(TextureMappingZone::TDCalibrationNeighbor);
if (name == "calibrated_nearest_measured_sample" ||
name == "nearest_measured_sample" ||
name == "nearest_measured_sample_global" ||
name == "measured_sample" ||
name == "measured_samples")
return int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample);
return int(TextureMappingZone::TDCalibrationAbsolute);
}
@@ -1151,7 +1162,7 @@ static int transmission_distance_calibration_mode_from_json(const nlohmann::json
if (mode_it->is_number_integer() || mode_it->is_number_unsigned())
return clamp_int(mode_it->get<int>(),
int(TextureMappingZone::TDCalibrationNone),
int(TextureMappingZone::TDCalibrationNeighbor));
int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample));
if (mode_it->is_boolean())
return mode_it->get<bool>() ?
int(TextureMappingZone::TDCalibrationAbsolute) :
@@ -1334,6 +1345,11 @@ bool TextureMappingColorCalibration::has_mode(int mode) const
}
}
bool TextureMappingSideSurfaceColorCalibration::has_nearest_measured_sample() const
{
return !nearest_measured_sample.empty();
}
namespace {
static const nlohmann::json *calibration_model_json(const nlohmann::json &models, const char *base_key)
@@ -1348,6 +1364,37 @@ static const nlohmann::json *calibration_model_json(const nlohmann::json &models
return it != models.end() ? &*it : nullptr;
}
static const nlohmann::json *surface_calibration_json(const nlohmann::json &root, const char *surface_key)
{
if (!root.is_object())
return nullptr;
auto surfaces_it = root.find("surface_calibrations");
if (surfaces_it != root.end() && surfaces_it->is_object()) {
auto surface_it = surfaces_it->find(surface_key);
if (surface_it != surfaces_it->end() && surface_it->is_object())
return &*surface_it;
}
auto root_surface_it = root.find(surface_key);
if (root_surface_it != root.end() && root_surface_it->is_object())
return &*root_surface_it;
return nullptr;
}
static nlohmann::json calibration_models_from_root_or_surface(const nlohmann::json &root, const nlohmann::json *surface)
{
if (root.contains("weights") && root["weights"].is_object() && !root["weights"].empty())
return root["weights"];
if (root.contains("models") && root["models"].is_object() && !root["models"].empty())
return root["models"];
if (surface != nullptr) {
if (surface->contains("weights") && (*surface)["weights"].is_object())
return (*surface)["weights"];
if (surface->contains("models") && (*surface)["models"].is_object())
return (*surface)["models"];
}
return nlohmann::json::object();
}
static ColorSolverCalibratedStackModel calibration_current_linear_affine_model(const nlohmann::json &raw)
{
ColorSolverCalibratedStackModel model;
@@ -1617,6 +1664,78 @@ static std::vector<TextureMappingColorCalibrationFilament> calibration_filaments
return filaments;
}
static std::vector<float> side_surface_swatch_weights_from_json(const nlohmann::json &raw, size_t component_count)
{
std::vector<float> weights = json_float_vector_or_empty(raw, { "raw_offsets", "raw_filament_offsets" });
if (!weights.empty()) {
if (weights.size() < component_count)
return {};
weights.resize(component_count);
for (float &value : weights)
value = std::clamp((std::isfinite(value) ? value : 0.f) / 255.f, 0.f, 1.f);
return weights;
}
weights = json_float_vector_or_empty(raw, { "normalized_weights", "weights", "component_weights" });
if (weights.size() < component_count)
return {};
weights.resize(component_count);
for (float &value : weights)
value = std::clamp(std::isfinite(value) ? value : 0.f, 0.f, 1.f);
return weights;
}
static ColorSolverCandidateSet side_surface_nearest_measured_sample_candidates(const nlohmann::json &surface,
size_t fallback_component_count)
{
ColorSolverCandidateSet candidates;
if (!surface.is_object())
return candidates;
auto swatches_it = surface.find("swatches");
if (swatches_it == surface.end() || !swatches_it->is_array())
return candidates;
size_t component_count = fallback_component_count;
if (component_count == 0) {
for (const nlohmann::json &swatch : *swatches_it) {
const std::vector<float> raw_offsets =
json_float_vector_or_empty(swatch, { "raw_offsets", "raw_filament_offsets", "normalized_weights", "weights", "component_weights" });
if (!raw_offsets.empty()) {
component_count = raw_offsets.size();
break;
}
}
}
if (component_count == 0)
return candidates;
candidates.component_count = component_count;
candidates.rgbs.reserve(swatches_it->size() * 3);
candidates.perceptual_coords.reserve(swatches_it->size() * 3);
candidates.weights.reserve(swatches_it->size() * component_count);
for (const nlohmann::json &swatch : *swatches_it) {
std::optional<std::array<float, 3>> rgb = calibration_measured_sample_rgb_from_json(swatch);
if (!rgb)
continue;
std::vector<float> weights = side_surface_swatch_weights_from_json(swatch, component_count);
if (weights.size() != component_count)
continue;
const std::array<float, 3> perceptual = color_solver_oklab_from_srgb(*rgb);
candidates.rgbs.emplace_back((*rgb)[0]);
candidates.rgbs.emplace_back((*rgb)[1]);
candidates.rgbs.emplace_back((*rgb)[2]);
candidates.perceptual_coords.emplace_back(perceptual[0]);
candidates.perceptual_coords.emplace_back(perceptual[1]);
candidates.perceptual_coords.emplace_back(perceptual[2]);
candidates.weights.insert(candidates.weights.end(), weights.begin(), weights.end());
}
if (candidates.empty())
return {};
build_color_solver_candidate_kd_trees(candidates);
return candidates;
}
static const ColorSolverCalibratedStackModel &calibration_model_for_mode(const TextureMappingColorCalibration &calibration,
int mode)
{
@@ -1654,7 +1773,8 @@ static float calibration_color_distance(const std::array<float, 3> &lhs, const s
static TextureMappingCalibrationMatch match_calibration_filaments(const TextureMappingColorCalibration &calibration,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm,
int filament_color_mode)
int filament_color_mode,
const char *warning_prefix = "Top-surface color calibration")
{
TextureMappingCalibrationMatch match;
match.calibration_index_by_component.assign(component_colors.size(), size_t(-1));
@@ -1742,7 +1862,7 @@ static TextureMappingCalibrationMatch match_calibration_filaments(const TextureM
if (!match.complete)
notes.emplace_back("the calibration file does not contain enough matching filaments");
if (!notes.empty()) {
match.warning = "Top-surface color calibration mismatch: ";
match.warning = std::string(warning_prefix != nullptr ? warning_prefix : "Calibration") + " mismatch: ";
for (size_t idx = 0; idx < notes.size(); ++idx) {
if (idx > 0)
match.warning += "; ";
@@ -1874,6 +1994,41 @@ static std::optional<ColorSolverCalibratedStackModel> remap_calibration_model(
return std::nullopt;
}
static std::optional<ColorSolverCandidateSet> remap_side_surface_candidate_set(
const ColorSolverCandidateSet &source,
const TextureMappingCalibrationMatch &match,
size_t target_component_count)
{
if (source.empty() || !match.complete || target_component_count == 0)
return std::nullopt;
ColorSolverCandidateSet out;
out.component_count = target_component_count;
const size_t source_component_count = source.component_count;
const size_t candidate_count = source.rgbs.size() / 3;
if (source_component_count == 0 ||
source.weights.size() != candidate_count * source_component_count ||
source.perceptual_coords.size() != source.rgbs.size())
return std::nullopt;
out.rgbs = source.rgbs;
out.perceptual_coords = source.perceptual_coords;
out.weights.assign(candidate_count * target_component_count, 0.f);
for (size_t candidate_idx = 0; candidate_idx < candidate_count; ++candidate_idx) {
const size_t src_begin = candidate_idx * source_component_count;
const size_t dst_begin = candidate_idx * target_component_count;
for (size_t component_idx = 0; component_idx < target_component_count; ++component_idx) {
const size_t source_idx = match.calibration_index_by_component[component_idx];
if (source_idx >= source_component_count)
return std::nullopt;
out.weights[dst_begin + component_idx] = source.weights[src_begin + source_idx];
}
}
build_color_solver_candidate_kd_trees(out);
return out.empty() ? std::nullopt : std::optional<ColorSolverCandidateSet>(out);
}
} // namespace
std::optional<TextureMappingColorCalibration> texture_mapping_parse_top_surface_color_calibration(
@@ -1893,11 +2048,12 @@ std::optional<TextureMappingColorCalibration> texture_mapping_parse_top_surface_
}
TextureMappingColorCalibration calibration;
const nlohmann::json *top_surface = surface_calibration_json(root, "top_surface");
calibration.display_name = json_string_or_empty(root, { "display_name", "name", "profile_id" });
if (calibration.display_name.empty() && top_surface != nullptr)
calibration.display_name = json_string_or_empty(*top_surface, { "display_name", "name", "profile_id" });
calibration.filaments = calibration_filaments_from_json(root);
const nlohmann::json models = root.contains("weights") && root["weights"].is_object() ?
root["weights"] :
root.value("models", nlohmann::json::object());
const nlohmann::json models = calibration_models_from_root_or_surface(root, top_surface);
if (const nlohmann::json *raw = calibration_model_json(models, "current_linear_affine"))
calibration.current_linear_affine = calibration_current_linear_affine_model(*raw);
@@ -1927,6 +2083,46 @@ std::optional<TextureMappingColorCalibration> texture_mapping_parse_top_surface_
return calibration;
}
std::optional<TextureMappingSideSurfaceColorCalibration> texture_mapping_parse_side_surface_color_calibration(
const std::string &json_text,
std::string *error)
{
if (error != nullptr)
error->clear();
if (json_text.empty())
return std::nullopt;
nlohmann::json root = nlohmann::json::parse(json_text, nullptr, false);
if (root.is_discarded() || !root.is_object()) {
if (error != nullptr)
*error = "The calibration file is not valid JSON.";
return std::nullopt;
}
const nlohmann::json *side_surface = surface_calibration_json(root, "side_surface");
if (side_surface == nullptr) {
if (error != nullptr)
*error = "The calibration file does not contain side-surface calibration data.";
return std::nullopt;
}
TextureMappingSideSurfaceColorCalibration calibration;
calibration.display_name = json_string_or_empty(root, { "display_name", "name", "profile_id" });
if (calibration.display_name.empty())
calibration.display_name = json_string_or_empty(*side_surface, { "display_name", "name", "profile_id" });
calibration.filaments = calibration_filaments_from_json(root);
calibration.nearest_measured_sample =
side_surface_nearest_measured_sample_candidates(*side_surface, calibration.filaments.size());
if (!calibration.has_nearest_measured_sample()) {
if (error != nullptr)
*error = "The side-surface calibration does not contain supported measured swatches.";
return std::nullopt;
}
return calibration;
}
std::vector<int> texture_mapping_top_surface_color_calibration_supported_modes(const std::string &json_text)
{
std::vector<int> out;
@@ -1983,6 +2179,13 @@ std::string texture_mapping_top_surface_color_calibration_display_name(const std
return calibration ? calibration->display_name : std::string();
}
std::string texture_mapping_side_surface_color_calibration_display_name(const std::string &json_text)
{
std::optional<TextureMappingSideSurfaceColorCalibration> calibration =
texture_mapping_parse_side_surface_color_calibration(json_text);
return calibration ? calibration->display_name : std::string();
}
std::string texture_mapping_top_surface_color_calibration_warning(const TextureMappingZone &zone,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm)
@@ -2007,6 +2210,30 @@ std::string texture_mapping_top_surface_color_calibration_warning(const TextureM
return std::string();
}
std::string texture_mapping_side_surface_color_calibration_warning(const TextureMappingZone &zone,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm)
{
if (zone.side_surface_color_calibration_json.empty())
return std::string();
std::string error;
std::optional<TextureMappingSideSurfaceColorCalibration> side_calibration =
texture_mapping_parse_side_surface_color_calibration(zone.side_surface_color_calibration_json, &error);
if (!side_calibration)
return error.empty() ? "Side-surface color calibration could not be loaded." : error;
TextureMappingColorCalibration calibration;
calibration.display_name = side_calibration->display_name;
calibration.filaments = side_calibration->filaments;
const TextureMappingCalibrationMatch match =
match_calibration_filaments(calibration,
component_colors,
component_tds_mm,
zone.filament_color_mode,
"Side-surface color calibration");
return match.warning;
}
std::optional<ColorSolverCalibratedStackModel> texture_mapping_top_surface_color_calibrated_model(
const TextureMappingZone &zone,
int mode,
@@ -2035,6 +2262,62 @@ std::optional<ColorSolverCalibratedStackModel> texture_mapping_top_surface_color
return remap_calibration_model(source, mode, match, component_colors.size());
}
std::optional<ColorSolverCandidateSet> texture_mapping_side_surface_color_calibrated_candidates(
const TextureMappingZone &zone,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm,
std::string *warning)
{
if (warning != nullptr)
warning->clear();
std::string error;
std::optional<TextureMappingSideSurfaceColorCalibration> side_calibration =
texture_mapping_parse_side_surface_color_calibration(zone.side_surface_color_calibration_json, &error);
if (!side_calibration) {
if (warning != nullptr)
*warning = error;
return std::nullopt;
}
TextureMappingColorCalibration calibration;
calibration.display_name = side_calibration->display_name;
calibration.filaments = side_calibration->filaments;
const TextureMappingCalibrationMatch match =
match_calibration_filaments(calibration,
component_colors,
component_tds_mm,
zone.filament_color_mode,
"Side-surface color calibration");
if (warning != nullptr)
*warning = match.warning;
return remap_side_surface_candidate_set(side_calibration->nearest_measured_sample,
match,
component_colors.size());
}
std::vector<float> texture_mapping_calibration_component_transmission_distances(
const std::vector<TextureMappingColorCalibrationFilament> &filaments,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm,
int filament_color_mode)
{
TextureMappingColorCalibration calibration;
calibration.filaments = filaments;
const TextureMappingCalibrationMatch match =
match_calibration_filaments(calibration, component_colors, component_tds_mm, filament_color_mode);
std::vector<float> out(component_colors.size(), 0.f);
if (!match.complete)
return out;
for (size_t component_idx = 0; component_idx < component_colors.size(); ++component_idx) {
const size_t calibration_idx = match.calibration_index_by_component[component_idx];
if (calibration_idx < filaments.size() &&
std::isfinite(filaments[calibration_idx].td_mm) &&
filaments[calibration_idx].td_mm > 0.f)
out[component_idx] = std::clamp(filaments[calibration_idx].td_mm, 0.01f, 50.f);
}
return out;
}
bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const
{
constexpr float eps = 1e-6f;
@@ -2154,6 +2437,8 @@ bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const
effective_top_surface_contoning_beam_search_stack_expansion_enabled() == rhs.effective_top_surface_contoning_beam_search_stack_expansion_enabled() &&
top_surface_color_calibration_name == rhs.top_surface_color_calibration_name &&
top_surface_color_calibration_json == rhs.top_surface_color_calibration_json &&
side_surface_color_calibration_name == rhs.side_surface_color_calibration_name &&
side_surface_color_calibration_json == rhs.side_surface_color_calibration_json &&
compact_offset_mode == rhs.compact_offset_mode &&
use_legacy_fixed_color_mode == rhs.use_legacy_fixed_color_mode &&
high_speed_image_texture_sampling == rhs.high_speed_image_texture_sampling &&
@@ -2584,6 +2869,12 @@ std::string TextureMappingManager::serialize_entries()
texture["top_surface_color_calibration"] =
calibration.is_discarded() ? nlohmann::json(zone.top_surface_color_calibration_json) : std::move(calibration);
}
if (!zone.side_surface_color_calibration_json.empty()) {
texture["side_surface_color_calibration_name"] = zone.side_surface_color_calibration_name;
nlohmann::json calibration = nlohmann::json::parse(zone.side_surface_color_calibration_json, nullptr, false);
texture["side_surface_color_calibration"] =
calibration.is_discarded() ? nlohmann::json(zone.side_surface_color_calibration_json) : std::move(calibration);
}
texture["compact_offset_mode"] = zone.compact_offset_mode;
texture["use_legacy_fixed_color_mode"] = zone.use_legacy_fixed_color_mode;
texture["high_speed_image_texture_sampling"] = true;
@@ -2949,6 +3240,19 @@ void TextureMappingManager::load_entries(const std::string &serialized,
zone.top_surface_contoning_color_prediction_mode =
texture_mapping_top_surface_color_calibration_first_supported_mode(zone.top_surface_color_calibration_json);
}
zone.side_surface_color_calibration_name =
texture.value("side_surface_color_calibration_name", std::string());
zone.side_surface_color_calibration_json.clear();
auto side_color_calibration_it = texture.find("side_surface_color_calibration");
if (side_color_calibration_it != texture.end()) {
zone.side_surface_color_calibration_json =
side_color_calibration_it->is_string() ?
side_color_calibration_it->get<std::string>() :
side_color_calibration_it->dump();
if (zone.side_surface_color_calibration_name.empty())
zone.side_surface_color_calibration_name =
texture_mapping_side_surface_color_calibration_display_name(zone.side_surface_color_calibration_json);
}
zone.apply_top_surface_contoning_experimental_defaults();
zone.compact_offset_mode = texture.value("compact_offset_mode", TextureMappingZone::DefaultCompactOffsetMode);
zone.use_legacy_fixed_color_mode =
@@ -2984,6 +3288,11 @@ void TextureMappingManager::load_entries(const std::string &serialized,
zone.high_resolution_sampling = true;
zone.tone_gamma = normalize_tone_gamma(texture.value("tone_gamma", 1.f));
zone.transmission_distance_calibration_mode = transmission_distance_calibration_mode_from_json(texture);
if (zone.transmission_distance_calibration_mode == int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample) &&
!texture_mapping_parse_side_surface_color_calibration(zone.side_surface_color_calibration_json)) {
zone.transmission_distance_calibration_mode =
TextureMappingZone::DefaultTransmissionDistanceCalibrationMode;
}
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())));
zone.filament_minimum_offsets_pct =

View File

@@ -154,7 +154,8 @@ struct TextureMappingZone
enum TransmissionDistanceCalibrationMode : uint8_t {
TDCalibrationNone = 0,
TDCalibrationAbsolute = 1,
TDCalibrationNeighbor = 2
TDCalibrationNeighbor = 2,
TDCalibrationCalibratedNearestMeasuredSample = 3
};
static constexpr int DefaultSurfacePattern = int(ImageTexture);
@@ -518,6 +519,8 @@ struct TextureMappingZone
bool top_surface_contoning_beam_search_stack_expansion_enabled = DefaultTopSurfaceContoningBeamSearchStackExpansionEnabled;
std::string top_surface_color_calibration_name;
std::string top_surface_color_calibration_json;
std::string side_surface_color_calibration_name;
std::string side_surface_color_calibration_json;
bool compact_offset_mode = DefaultCompactOffsetMode;
bool use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode;
bool high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling;
@@ -823,6 +826,8 @@ struct TextureMappingZone
top_surface_contoning_beam_search_stack_expansion_enabled = DefaultTopSurfaceContoningBeamSearchStackExpansionEnabled;
top_surface_color_calibration_name.clear();
top_surface_color_calibration_json.clear();
side_surface_color_calibration_name.clear();
side_surface_color_calibration_json.clear();
compact_offset_mode = DefaultCompactOffsetMode;
use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode;
high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling;
@@ -893,21 +898,46 @@ struct TextureMappingColorCalibration {
bool has_mode(int mode) const;
};
struct TextureMappingSideSurfaceColorCalibration {
std::string display_name;
std::vector<TextureMappingColorCalibrationFilament> filaments;
ColorSolverCandidateSet nearest_measured_sample;
bool has_nearest_measured_sample() const;
};
std::optional<TextureMappingColorCalibration> texture_mapping_parse_top_surface_color_calibration(
const std::string &json_text,
std::string *error = nullptr);
std::optional<TextureMappingSideSurfaceColorCalibration> texture_mapping_parse_side_surface_color_calibration(
const std::string &json_text,
std::string *error = nullptr);
std::vector<int> texture_mapping_top_surface_color_calibration_supported_modes(const std::string &json_text);
int texture_mapping_top_surface_color_calibration_first_supported_mode(const std::string &json_text);
std::string texture_mapping_top_surface_color_calibration_display_name(const std::string &json_text);
std::string texture_mapping_side_surface_color_calibration_display_name(const std::string &json_text);
std::string texture_mapping_top_surface_color_calibration_warning(const TextureMappingZone &zone,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm);
std::string texture_mapping_side_surface_color_calibration_warning(const TextureMappingZone &zone,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm);
std::optional<ColorSolverCalibratedStackModel> texture_mapping_top_surface_color_calibrated_model(
const TextureMappingZone &zone,
int mode,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm,
std::string *warning = nullptr);
std::optional<ColorSolverCandidateSet> texture_mapping_side_surface_color_calibrated_candidates(
const TextureMappingZone &zone,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm,
std::string *warning = nullptr);
std::vector<float> texture_mapping_calibration_component_transmission_distances(
const std::vector<TextureMappingColorCalibrationFilament> &filaments,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm,
int filament_color_mode);
struct TextureMappingPrimeTowerImage
{

View File

@@ -1630,11 +1630,14 @@ std::vector<float> component_weights_for_sample(const WeightedTextureSample &sam
float contrast_factor,
float tone_gamma,
const std::vector<std::array<float, 3>> &component_colors,
const ColorSolverCandidateSet *generic_mix_candidates)
const ColorSolverCandidateSet *generic_mix_candidates,
const ColorSolverCandidateSet *calibrated_side_candidates)
{
std::vector<float> desired(component_count, 0.f);
size_t mapped_component_count = component_count;
const bool has_raw_component_weights = sample.raw_component_weights.size() == component_count;
const bool has_calibrated_side_candidates =
calibrated_side_candidates != nullptr && !calibrated_side_candidates->empty();
const int effective_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
if (has_raw_component_weights) {
float raw_activity = 0.f;
@@ -1662,6 +1665,14 @@ std::vector<float> component_weights_for_sample(const WeightedTextureSample &sam
for (size_t channel_idx = 0; channel_idx < channel_count; ++channel_idx)
desired[channel_idx] = clamp01f(channels[channel_idx]);
mapped_component_count = channel_count;
} else if (has_calibrated_side_candidates) {
std::vector<float> calibrated =
solve_color_solver_weights_for_target(*calibrated_side_candidates,
target,
ColorSolverLookupMode::ClosestMix,
color_solver_mode_from_index(effective_solver_mode));
if (calibrated.size() == component_count)
desired = std::move(calibrated);
} else {
std::vector<float> optimized;
if (!use_fixed_color_generic_solver)
@@ -1684,7 +1695,7 @@ std::vector<float> component_weights_for_sample(const WeightedTextureSample &sam
}
}
if (!has_raw_component_weights && std::abs(contrast_factor - 1.f) > 1e-5f)
if (!has_raw_component_weights && !has_calibrated_side_candidates && std::abs(contrast_factor - 1.f) > 1e-5f)
apply_texture_contrast_to_components(desired, contrast_factor, mapped_component_count);
for (float &v : desired)
v = clamp01f(v);
@@ -1717,6 +1728,7 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(const
bool high_speed_image_texture_sampling,
std::optional<float> texture_sample_pitch_mm_override,
std::optional<std::array<float, 4>> image_background_rgba_override,
const ColorSolverCandidateSet *calibrated_side_candidates,
std::optional<int> raw_top_surface_depth_override)
{
TextureMappingOffsetWeightField weight_field;
@@ -2099,8 +2111,11 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(const
int(TextureMappingZone::DitheringClosest),
int(TextureMappingZone::DitheringHalftoneV2));
std::vector<uint32_t> binary_dither_masks;
const bool has_calibrated_side_candidates =
calibrated_side_candidates != nullptr && !calibrated_side_candidates->empty();
const bool can_binary_dither =
dithering_enabled &&
!has_calibrated_side_candidates &&
!raw_values_mode &&
!is_halftone_dithering_method(clamped_binary_dither_method) &&
component_count > 0 &&
@@ -2245,7 +2260,7 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(const
use_fixed_color_generic_solver ? fixed_colors : component_colors;
ColorSolverCandidateSet candidates;
const ColorSolverCandidateSet *candidate_ptr = nullptr;
if (!raw_values_mode) {
if (!raw_values_mode && !has_calibrated_side_candidates) {
candidates = build_color_solver_candidates(solver_colors, color_solver_mix_model_from_index(generic_solver_mix_model));
candidate_ptr = candidates.empty() ? nullptr : &candidates;
}
@@ -2301,7 +2316,8 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(const
contrast_factor,
tone_gamma,
component_colors,
candidate_ptr);
candidate_ptr,
calibrated_side_candidates);
}
for (size_t component_idx = 0; component_idx < component_count; ++component_idx) {
const float v = component_idx < desired.size() ? clamp01f(desired[component_idx]) : 0.f;
@@ -2669,8 +2685,10 @@ TransmissionDistanceCalibrationContext transmission_distance_calibration_context
TransmissionDistanceCalibrationContext context;
context.mode = std::clamp(zone.transmission_distance_calibration_mode,
int(TextureMappingZone::TDCalibrationNone),
int(TextureMappingZone::TDCalibrationNeighbor));
if (context.mode == int(TextureMappingZone::TDCalibrationNone) || component_ids.empty())
int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample));
if (context.mode == int(TextureMappingZone::TDCalibrationNone) ||
context.mode == int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample) ||
component_ids.empty())
return context;
std::vector<float> explicit_tds(component_ids.size(), 0.f);
@@ -3609,13 +3627,36 @@ std::optional<TextureMappingOffsetContext> build_texture_mapping_offset_context_
TextureMappingZone::MaxHalftoneDotSizeMm);
const bool high_resolution_texture_sampling = zone.high_resolution_sampling || dithering_enabled;
const bool compact_offset_mode = halftone_dithering_enabled ? false : zone.compact_offset_mode || dithering_enabled;
const bool calibrated_side_mode =
vertex_color_match_mode &&
!raw_texture_mapping_mode &&
zone.transmission_distance_calibration_mode == int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample);
std::vector<float> component_strength_factors;
component_strength_factors.reserve(component_ids.size());
std::vector<float> component_minimum_offset_factors;
component_minimum_offset_factors.reserve(component_ids.size());
for (const unsigned int id : component_ids) {
component_strength_factors.emplace_back(texture_mapping_offset_filament_strength_factor(zone, id));
component_minimum_offset_factors.emplace_back(texture_mapping_offset_filament_minimum_offset_factor(zone, id));
component_strength_factors.emplace_back(calibrated_side_mode ? 1.f : texture_mapping_offset_filament_strength_factor(zone, id));
component_minimum_offset_factors.emplace_back(calibrated_side_mode ? 0.f : texture_mapping_offset_filament_minimum_offset_factor(zone, id));
}
std::vector<float> component_transmission_distances_mm;
component_transmission_distances_mm.reserve(component_ids.size());
for (const unsigned int id : component_ids) {
const size_t idx = id > 0 ? size_t(id - 1) : size_t(-1);
const float value = idx < zone.filament_transmission_distances_mm.size() ?
zone.filament_transmission_distances_mm[idx] :
0.f;
component_transmission_distances_mm.emplace_back(std::isfinite(value) && value > 0.f ? std::clamp(value, 0.01f, 50.f) : 0.f);
}
std::optional<ColorSolverCandidateSet> calibrated_side_candidates;
const ColorSolverCandidateSet *calibrated_side_candidates_ptr = nullptr;
if (calibrated_side_mode) {
calibrated_side_candidates =
texture_mapping_side_surface_color_calibrated_candidates(zone,
component_colors,
component_transmission_distances_mm);
if (calibrated_side_candidates && !calibrated_side_candidates->empty())
calibrated_side_candidates_ptr = &*calibrated_side_candidates;
}
TextureMappingOffsetWeightField weight_field;
@@ -3632,7 +3673,8 @@ std::optional<TextureMappingOffsetContext> build_texture_mapping_offset_context_
component_minimum_offset_factors, texture_contrast_pct, texture_tone_gamma,
sample_z_mm, layer_sample_falloff_mm, high_resolution_texture_sampling,
zone.high_speed_image_texture_sampling, texture_sample_pitch_mm_override,
image_background_rgba_override, raw_top_surface_depth_override);
image_background_rgba_override, calibrated_side_candidates_ptr,
raw_top_surface_depth_override);
if (weight_field.empty())
return std::nullopt;
}
@@ -3734,11 +3776,11 @@ std::optional<TextureMappingOffsetContext> build_texture_mapping_offset_context_
context.active_halftone_angle_deg = halftone_screen_angle_deg(filament_color_mode, active_component_idx);
context.active_component_strength_factor = linear_gradient_mode ?
1.f :
texture_mapping_offset_filament_strength_factor(zone, active_component_id);
(calibrated_side_mode ? 1.f : texture_mapping_offset_filament_strength_factor(zone, active_component_id));
context.active_component_minimum_offset_factor = linear_gradient_mode ?
0.f :
texture_mapping_offset_filament_minimum_offset_factor(zone, active_component_id);
context.active_component_td_width_factor = linear_gradient_mode ?
(calibrated_side_mode ? 0.f : texture_mapping_offset_filament_minimum_offset_factor(zone, active_component_id));
context.active_component_td_width_factor = linear_gradient_mode || calibrated_side_mode ?
1.f :
transmission_distance_width_factor(td_calibration_context, active_component_idx, previous_component_idx);
context.base_outer_width_mm = base_outer_width_mm;

View File

@@ -11,7 +11,7 @@
#include "libslic3r/BuildVolume.hpp"
#include "libslic3r/Color.hpp"
#include "libslic3r/ColorSolver.hpp"
#include "ColorSolver.hpp"
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/ExtrusionEntityCollection.hpp"
#include "libslic3r/Geometry.hpp"

View File

@@ -21,7 +21,7 @@
#include "libslic3r/Format/bbs_3mf.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/TextureMapping.hpp"
#include "libslic3r/ColorSolver.hpp"
#include "ColorSolver.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/AABBMesh.hpp"
#include "slic3r/Utils/UndoRedo.hpp"

View File

@@ -14,7 +14,7 @@
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/TextureMapping.hpp"
#include "libslic3r/TextureMappingContoning.hpp"
#include "libslic3r/ColorSolver.hpp"
#include "ColorSolver.hpp"
#include <algorithm>
#include <cmath>

View File

@@ -73,7 +73,7 @@
#include "libslic3r/Format/STL.hpp"
#include "libslic3r/Format/DRC.hpp"
#include "libslic3r/Format/STEP.hpp"
#include "libslic3r/ColorSolver.hpp"
#include "ColorSolver.hpp"
#include "libslic3r/Format/AMF.hpp"
//#include "libslic3r/Format/3mf.hpp"
#include "libslic3r/Format/bbs_3mf.hpp"
@@ -2550,6 +2550,8 @@ public:
bool top_surface_contoning_variable_layer_height_compensation_enabled,
const std::string &top_surface_color_calibration_name,
const std::string &top_surface_color_calibration_json,
const std::string &side_surface_color_calibration_name,
const std::string &side_surface_color_calibration_json,
std::function<bool(const std::vector<std::string>&)> apply_filament_colors,
const TextureMappingZoneShellUsageSummary &top_surface_contoning_shell_usage,
const TextureMappingManager &texture_mapping_zones,
@@ -2569,6 +2571,8 @@ public:
, m_apply_filament_colors(std::move(apply_filament_colors))
, m_top_surface_color_calibration_name(top_surface_color_calibration_name)
, m_top_surface_color_calibration_json(top_surface_color_calibration_json)
, m_side_surface_color_calibration_name(side_surface_color_calibration_name)
, m_side_surface_color_calibration_json(side_surface_color_calibration_json)
{
(void) reduce_outer_surface_texture;
(void) top_surface_contoning_min_feature_mm;
@@ -2671,20 +2675,24 @@ public:
auto *td_box = new wxStaticBoxSizer(wxVERTICAL, filament_page, _L("Transmission distance"));
auto *td_mode_row = new wxBoxSizer(wxHORIZONTAL);
td_mode_row->Add(new wxStaticText(filament_page, wxID_ANY, _L("TD calibration mode")),
td_mode_row->Add(new wxStaticText(filament_page, wxID_ANY, _L("Side calibration mode")),
0,
wxALIGN_CENTER_VERTICAL | wxRIGHT,
gap);
wxArrayString td_mode_choices;
td_mode_choices.Add(_L("None"));
td_mode_choices.Add(_L("Absolute"));
td_mode_choices.Add(_L("Neighbor"));
td_mode_choices.Add(_L("Absolute TD"));
td_mode_choices.Add(_L("Neighbour TD"));
td_mode_choices.Add(_L("Calibrated: nearest measured sample"));
m_transmission_distance_calibration_mode_choice =
new wxChoice(filament_page, wxID_ANY, wxDefaultPosition, wxDefaultSize, td_mode_choices);
m_transmission_distance_calibration_mode_choice->SetSelection(
std::clamp(transmission_distance_calibration_mode,
int(TextureMappingZone::TDCalibrationNone),
int(TextureMappingZone::TDCalibrationNeighbor)));
int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)));
m_transmission_distance_calibration_mode_choice->Bind(wxEVT_CHOICE, [this](wxCommandEvent &) {
update_strength_offsets_visibility(true);
});
td_mode_row->Add(m_transmission_distance_calibration_mode_choice, 1, wxALIGN_CENTER_VERTICAL);
td_box->Add(td_mode_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap);
for (size_t i = 0; i < component_ids.size(); ++i) {
@@ -2711,6 +2719,36 @@ public:
}
filament_root->Add(td_box, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap);
auto add_filament_calibration_row = [this, filament_page, filament_root, gap](const wxString &title,
wxStaticText **label_out,
std::function<void()> load_fn,
std::function<void()> clear_fn) {
auto *row = new wxBoxSizer(wxHORIZONTAL);
row->Add(new wxStaticText(filament_page, wxID_ANY, title),
0,
wxALIGN_CENTER_VERTICAL | wxRIGHT,
gap);
auto *label = new wxStaticText(filament_page, wxID_ANY, wxEmptyString);
row->Add(label, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap);
auto *load_button = new wxButton(filament_page, wxID_ANY, _L("Load..."));
auto *clear_button = new wxButton(filament_page, wxID_ANY, _L("Clear"));
row->Add(load_button, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, std::max(FromDIP(2), gap / 2));
row->Add(clear_button, 0, wxALIGN_CENTER_VERTICAL);
load_button->Bind(wxEVT_BUTTON, [load_fn](wxCommandEvent &) { load_fn(); });
clear_button->Bind(wxEVT_BUTTON, [clear_fn](wxCommandEvent &) { clear_fn(); });
if (label_out != nullptr)
*label_out = label;
filament_root->Add(row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap);
};
add_filament_calibration_row(_L("Top Calibration"),
&m_filament_top_surface_color_calibration_label,
[this]() { load_top_surface_color_calibration(); },
[this]() { clear_top_surface_color_calibration(); });
add_filament_calibration_row(_L("Side Calibration"),
&m_side_surface_color_calibration_label,
[this]() { load_side_surface_color_calibration(); },
[this]() { clear_side_surface_color_calibration(); });
m_strength_offsets_toggle_button = new wxButton(filament_page, wxID_ANY, wxEmptyString);
m_strength_offsets_toggle_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
m_strength_offsets_expanded = !m_strength_offsets_expanded;
@@ -2763,9 +2801,9 @@ public:
}
strength_offsets_root->Add(minimum_offsets_box, 0, wxEXPAND | wxBOTTOM, gap);
strength_offsets_root->Add(strengths_box, 0, wxEXPAND | wxBOTTOM, gap);
auto *reset_btn = new wxButton(m_strength_offsets_panel, wxID_ANY, _L("Reset strengths and offsets"));
reset_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { reset_strengths_and_offsets(); });
strength_offsets_root->Add(reset_btn, 0, wxALIGN_RIGHT | wxBOTTOM, gap);
m_strength_offsets_reset_button = new wxButton(m_strength_offsets_panel, wxID_ANY, _L("Reset strengths and offsets"));
m_strength_offsets_reset_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { reset_strengths_and_offsets(); });
strength_offsets_root->Add(m_strength_offsets_reset_button, 0, wxALIGN_RIGHT | wxBOTTOM, gap);
filament_root->Add(m_strength_offsets_panel, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap);
update_strength_offsets_visibility(false);
@@ -3324,6 +3362,7 @@ public:
new wxChoice(m_top_surface_contoning_checkboxes_panel, wxID_ANY);
rebuild_top_surface_contoning_color_prediction_choices(top_surface_contoning_color_prediction_mode);
update_top_surface_color_calibration_label();
update_side_surface_color_calibration_label();
contoning_td_correction_row->Add(m_top_surface_contoning_td_correction_choice, 1, wxALIGN_CENTER_VERTICAL);
contoning_checkboxes_root->Add(contoning_td_correction_row, 0, wxEXPAND | wxTOP | wxBOTTOM, gap / 2);
m_top_surface_contoning_variable_layer_height_compensation_checkbox =
@@ -3809,7 +3848,7 @@ public:
return m_transmission_distance_calibration_mode_choice ?
std::clamp(m_transmission_distance_calibration_mode_choice->GetSelection(),
int(TextureMappingZone::TDCalibrationNone),
int(TextureMappingZone::TDCalibrationNeighbor)) :
int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)) :
TextureMappingZone::DefaultTransmissionDistanceCalibrationMode;
}
@@ -4156,6 +4195,14 @@ public:
{
return m_top_surface_color_calibration_json;
}
std::string side_surface_color_calibration_name() const
{
return m_side_surface_color_calibration_name;
}
std::string side_surface_color_calibration_json() const
{
return m_side_surface_color_calibration_json;
}
bool top_surface_contoning_td_effective_alpha_correction_selected() const
{
const int effective_mode = TextureMappingZone::effective_top_surface_contoning_color_prediction_mode(
@@ -4305,6 +4352,11 @@ public:
}
private:
enum class CalibrationSurface {
Top,
Side
};
static std::string prime_tower_color_mode_name(int selection)
{
switch (selection) {
@@ -4482,7 +4534,9 @@ private:
void update_top_surface_color_calibration_label()
{
if (m_top_surface_color_calibration_label != nullptr) {
auto update_label = [this](wxStaticText *label_widget) {
if (label_widget == nullptr)
return;
wxString label = _L("No calibration loaded");
wxString tooltip;
if (!m_top_surface_color_calibration_json.empty()) {
@@ -4497,9 +4551,11 @@ private:
display = display.Left(19) + "..." + display.Right(20);
label = display;
}
m_top_surface_color_calibration_label->SetLabel(label);
m_top_surface_color_calibration_label->SetToolTip(tooltip);
}
label_widget->SetLabel(label);
label_widget->SetToolTip(tooltip);
};
update_label(m_top_surface_color_calibration_label);
update_label(m_filament_top_surface_color_calibration_label);
if (m_top_surface_color_calibration_warning_text != nullptr) {
TextureMappingZone tmp;
@@ -4520,6 +4576,59 @@ private:
Layout();
}
void update_side_surface_color_calibration_label()
{
if (m_side_surface_color_calibration_label != nullptr) {
wxString label = _L("No calibration loaded");
wxString tooltip;
if (!m_side_surface_color_calibration_json.empty()) {
std::string name = m_side_surface_color_calibration_name;
if (name.empty())
name = texture_mapping_side_surface_color_calibration_display_name(m_side_surface_color_calibration_json);
if (name.empty())
name = "Loaded calibration";
tooltip = from_u8(name);
wxString display = from_u8(name);
if (display.length() > 42)
display = display.Left(19) + "..." + display.Right(20);
label = display;
}
m_side_surface_color_calibration_label->SetLabel(label);
m_side_surface_color_calibration_label->SetToolTip(tooltip);
}
Layout();
}
int preferred_top_surface_color_calibration_mode() const
{
const std::vector<int> modes =
texture_mapping_top_surface_color_calibration_supported_modes(m_top_surface_color_calibration_json);
const int nearest_mode = int(TextureMappingZone::ContoningColorPredictionCalibratedNearestMeasuredSample);
if (std::find(modes.begin(), modes.end(), nearest_mode) != modes.end())
return nearest_mode;
return texture_mapping_top_surface_color_calibration_first_supported_mode(m_top_surface_color_calibration_json);
}
bool apply_transmission_distances_from_calibration_filaments(
const std::vector<TextureMappingColorCalibrationFilament> &filaments)
{
if (filaments.empty())
return false;
const std::vector<float> tds =
texture_mapping_calibration_component_transmission_distances(filaments,
top_surface_color_calibration_component_colors(),
component_transmission_distances_mm(),
m_filament_color_mode);
bool applied = false;
for (size_t idx = 0; idx < tds.size() && idx < m_transmission_distance_spins.size(); ++idx) {
if (tds[idx] > 0.f && m_transmission_distance_spins[idx] != nullptr) {
m_transmission_distance_spins[idx]->SetValue(double(tds[idx]));
applied = true;
}
}
return applied;
}
bool filament_color_lists_differ(const std::vector<std::string> &lhs,
const std::vector<std::string> &rhs) const
{
@@ -4677,10 +4786,11 @@ private:
return true;
}
void load_top_surface_color_calibration()
void load_surface_color_calibration(CalibrationSurface target_surface)
{
const bool target_top = target_surface == CalibrationSurface::Top;
wxFileDialog dlg(this,
_L("Load top-surface color calibration"),
target_top ? _L("Load top-surface color calibration") : _L("Load side-surface color calibration"),
wxEmptyString,
wxEmptyString,
_L("JSON files (*.json)|*.json"),
@@ -4697,23 +4807,78 @@ private:
std::ostringstream ss;
ss << file.rdbuf();
const std::string json_text = ss.str();
std::string error;
if (!texture_mapping_parse_top_surface_color_calibration(json_text, &error)) {
std::string top_error;
std::string side_error;
std::optional<TextureMappingColorCalibration> top_calibration =
texture_mapping_parse_top_surface_color_calibration(json_text, &top_error);
std::optional<TextureMappingSideSurfaceColorCalibration> side_calibration =
texture_mapping_parse_side_surface_color_calibration(json_text, &side_error);
if ((target_top && !top_calibration) || (!target_top && !side_calibration)) {
const std::string &error = target_top ? top_error : side_error;
MessageDialog msg(this,
error.empty() ? _L("The selected file is not a supported top-surface color calibration.") : from_u8(error),
error.empty() ?
(target_top ?
_L("The selected file is not a supported top-surface color calibration.") :
_L("The selected file is not a supported side-surface color calibration.")) :
from_u8(error),
_L("Invalid calibration"),
wxOK | wxICON_WARNING);
msg.ShowModal();
return;
}
m_top_surface_color_calibration_json = json_text;
m_top_surface_color_calibration_name =
boost::filesystem::path(into_u8(dlg.GetPath())).filename().string();
const int mode = texture_mapping_top_surface_color_calibration_first_supported_mode(m_top_surface_color_calibration_json);
rebuild_top_surface_contoning_color_prediction_choices(mode);
const std::string filename = boost::filesystem::path(into_u8(dlg.GetPath())).filename().string();
bool top_changed = false;
bool side_changed = false;
if (target_top) {
m_top_surface_color_calibration_json = json_text;
m_top_surface_color_calibration_name = filename;
rebuild_top_surface_contoning_color_prediction_choices(preferred_top_surface_color_calibration_mode());
bool td_applied = apply_transmission_distances_from_calibration_filaments(top_calibration->filaments);
top_changed = true;
if (side_calibration && m_side_surface_color_calibration_json.empty()) {
m_side_surface_color_calibration_json = json_text;
m_side_surface_color_calibration_name = filename;
if (m_transmission_distance_calibration_mode_choice != nullptr)
m_transmission_distance_calibration_mode_choice->SetSelection(
int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample));
if (!td_applied)
td_applied = apply_transmission_distances_from_calibration_filaments(side_calibration->filaments);
side_changed = true;
}
} else {
m_side_surface_color_calibration_json = json_text;
m_side_surface_color_calibration_name = filename;
if (m_transmission_distance_calibration_mode_choice != nullptr)
m_transmission_distance_calibration_mode_choice->SetSelection(
int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample));
bool td_applied = apply_transmission_distances_from_calibration_filaments(side_calibration->filaments);
side_changed = true;
if (top_calibration && m_top_surface_color_calibration_json.empty()) {
m_top_surface_color_calibration_json = json_text;
m_top_surface_color_calibration_name = filename;
rebuild_top_surface_contoning_color_prediction_choices(preferred_top_surface_color_calibration_mode());
if (!td_applied)
td_applied = apply_transmission_distances_from_calibration_filaments(top_calibration->filaments);
top_changed = true;
}
}
update_top_surface_color_calibration_label();
update_top_surface_image_options_visibility(true);
update_side_surface_color_calibration_label();
if (top_changed)
update_top_surface_image_options_visibility(true);
if (side_changed)
update_strength_offsets_visibility(true);
}
void load_top_surface_color_calibration()
{
load_surface_color_calibration(CalibrationSurface::Top);
}
void load_side_surface_color_calibration()
{
load_surface_color_calibration(CalibrationSurface::Side);
}
void clear_top_surface_color_calibration()
@@ -4725,6 +4890,19 @@ private:
update_top_surface_image_options_visibility(true);
}
void clear_side_surface_color_calibration()
{
m_side_surface_color_calibration_name.clear();
m_side_surface_color_calibration_json.clear();
if (m_transmission_distance_calibration_mode_choice != nullptr &&
transmission_distance_calibration_mode() == int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)) {
m_transmission_distance_calibration_mode_choice->SetSelection(
TextureMappingZone::DefaultTransmissionDistanceCalibrationMode);
}
update_side_surface_color_calibration_label();
update_strength_offsets_visibility(true);
}
void reset_strengths_and_offsets()
{
for (wxSlider *slider : m_minimum_offset_sliders)
@@ -4773,6 +4951,22 @@ private:
m_strength_offsets_toggle_button->SetLabel(m_strength_offsets_expanded ?
_L("Hide Filament Strengths and Offsets") :
_L("Show Filament Strengths and Offsets"));
const bool manual_controls_enabled =
transmission_distance_calibration_mode() != int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample);
for (wxSlider *slider : m_minimum_offset_sliders)
if (slider)
slider->Enable(manual_controls_enabled);
for (wxSpinCtrl *spin : m_minimum_offset_spins)
if (spin)
spin->Enable(manual_controls_enabled);
for (wxSlider *slider : m_strength_sliders)
if (slider)
slider->Enable(manual_controls_enabled);
for (wxSpinCtrl *spin : m_strength_spins)
if (spin)
spin->Enable(manual_controls_enabled);
if (m_strength_offsets_reset_button != nullptr)
m_strength_offsets_reset_button->Enable(manual_controls_enabled);
if (m_strength_offsets_panel != nullptr)
m_strength_offsets_panel->Show(m_strength_offsets_expanded);
if (!fit_dialog)
@@ -5531,6 +5725,8 @@ private:
wxCheckBox *m_top_surface_contoning_surface_scatter_checkbox {nullptr};
wxChoice *m_top_surface_contoning_td_correction_choice {nullptr};
std::vector<int> m_top_surface_contoning_color_prediction_modes;
wxStaticText *m_filament_top_surface_color_calibration_label {nullptr};
wxStaticText *m_side_surface_color_calibration_label {nullptr};
wxStaticText *m_top_surface_color_calibration_label {nullptr};
wxStaticText *m_top_surface_color_calibration_warning_text {nullptr};
wxButton *m_set_filament_colors_from_calibration_button {nullptr};
@@ -5560,11 +5756,14 @@ private:
std::function<bool(const std::vector<std::string>&)> m_apply_filament_colors;
std::string m_top_surface_color_calibration_name;
std::string m_top_surface_color_calibration_json;
std::string m_side_surface_color_calibration_name;
std::string m_side_surface_color_calibration_json;
std::vector<wxSlider*> m_minimum_offset_sliders;
std::vector<wxSpinCtrl*> m_minimum_offset_spins;
std::vector<wxSlider*> m_strength_sliders;
std::vector<wxSpinCtrl*> m_strength_spins;
std::vector<wxSpinCtrlDouble*> m_transmission_distance_spins;
wxButton *m_strength_offsets_reset_button {nullptr};
bool m_strength_offsets_expanded {false};
};
@@ -10650,6 +10849,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
updated.top_surface_contoning_variable_layer_height_compensation_enabled,
updated.top_surface_color_calibration_name,
updated.top_surface_color_calibration_json,
updated.side_surface_color_calibration_name,
updated.side_surface_color_calibration_json,
apply_filament_colors_from_calibration,
shell_usage,
bundle->texture_mapping_zones,
@@ -10762,6 +10963,10 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
dlg.top_surface_color_calibration_name();
updated.top_surface_color_calibration_json =
dlg.top_surface_color_calibration_json();
updated.side_surface_color_calibration_name =
dlg.side_surface_color_calibration_name();
updated.side_surface_color_calibration_json =
dlg.side_surface_color_calibration_json();
if (updated.top_surface_image_printing_enabled &&
updated.top_surface_image_printing_method == int(TextureMappingZone::TopSurfaceImageContoning)) {
updated.modulation_mode = int(TextureMappingZone::ModulationPerimeterPathV2);