Merge ImageMap raw offset atlas format
This commit is contained in:
@@ -277,6 +277,8 @@ set(lisbslic3r_sources
|
||||
Geometry/VoronoiUtils.cpp
|
||||
Geometry/VoronoiUtils.hpp
|
||||
Geometry/VoronoiVisualUtils.hpp
|
||||
ImageMapRawFilamentOffsetAtlas.cpp
|
||||
ImageMapRawFilamentOffsetAtlas.hpp
|
||||
Int128.hpp
|
||||
KDTreeIndirect.hpp
|
||||
Layer.cpp
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "../GCode.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../GCode/ThumbnailData.hpp"
|
||||
#include "../ImageMapRawFilamentOffsetAtlas.hpp"
|
||||
#include "../PNGReadWrite.hpp"
|
||||
#include "../Semver.hpp"
|
||||
#include "../TextureMapping.hpp"
|
||||
@@ -785,6 +786,64 @@ static bool has_imported_obj_texture_payload(const ModelVolume &volume)
|
||||
volume.imported_texture_uvs_per_face.size() >= triangle_count * 6;
|
||||
}
|
||||
|
||||
static bool has_imported_raw_atlas_texture_payload(const ModelVolume &volume)
|
||||
{
|
||||
return 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::vector<ImageMapRawFilament> raw_atlas_filaments_from_metadata(const std::string &metadata_json, uint32_t channels)
|
||||
{
|
||||
std::vector<ImageMapRawFilament> filaments;
|
||||
try {
|
||||
const nlohmann::json root = nlohmann::json::parse(metadata_json);
|
||||
const nlohmann::json entries = root.value("filaments", nlohmann::json::array());
|
||||
if (entries.is_array()) {
|
||||
for (const nlohmann::json &entry : entries) {
|
||||
if (!entry.is_object())
|
||||
continue;
|
||||
ImageMapRawFilament filament;
|
||||
filament.slot = unsigned(std::max(0, entry.value("slot", 0)));
|
||||
filament.color = entry.value("color", std::string());
|
||||
filament.hex = entry.value("hex", std::string());
|
||||
filaments.emplace_back(std::move(filament));
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
filaments.clear();
|
||||
}
|
||||
|
||||
if (filaments.empty()) {
|
||||
filaments.reserve(channels);
|
||||
for (uint32_t channel = 0; channel < channels; ++channel) {
|
||||
ImageMapRawFilament filament;
|
||||
filament.slot = channel + 1;
|
||||
filament.color = "custom";
|
||||
filament.hex = "#FFFFFF";
|
||||
filaments.emplace_back(std::move(filament));
|
||||
}
|
||||
}
|
||||
return filaments;
|
||||
}
|
||||
|
||||
static ImageMapRawFilamentOffsetAtlas raw_atlas_from_model_volume(const ModelVolume &volume)
|
||||
{
|
||||
ImageMapRawFilamentOffsetAtlas atlas;
|
||||
atlas.width = volume.imported_texture_width;
|
||||
atlas.height = volume.imported_texture_height;
|
||||
atlas.channels = volume.imported_texture_raw_channels;
|
||||
atlas.offsets.assign(volume.imported_texture_raw_filament_offsets.begin(), volume.imported_texture_raw_filament_offsets.end());
|
||||
atlas.mask.assign(size_t(atlas.width) * size_t(atlas.height), 255);
|
||||
atlas.metadata_json = volume.imported_texture_raw_metadata_json;
|
||||
atlas.filaments = raw_atlas_filaments_from_metadata(volume.imported_texture_raw_metadata_json, atlas.channels);
|
||||
return atlas;
|
||||
}
|
||||
|
||||
static bool has_imported_obj_material_payload(const ModelVolume &volume)
|
||||
{
|
||||
return has_imported_vertex_color_payload(volume) || has_imported_obj_texture_payload(volume);
|
||||
@@ -5901,9 +5960,22 @@ static void append_triangle_material_data(std::vector<uint8_t> &uv_valid,
|
||||
volume->imported_texture_uv_valid = source.uv_valid;
|
||||
volume->imported_texture_uvs_per_face.assign(source.uvs_per_face.begin(),
|
||||
source.uvs_per_face.begin() + triangle_count * 6);
|
||||
volume->imported_texture_rgba = std::move(imported_rgba);
|
||||
volume->imported_texture_width = imported_width;
|
||||
volume->imported_texture_height = imported_height;
|
||||
ImageMapRawFilamentOffsetAtlas raw_atlas;
|
||||
if (decode_image_map_raw_filament_offset_atlas(imported_rgba, imported_width, imported_height, raw_atlas, nullptr)) {
|
||||
volume->imported_texture_rgba = image_map_raw_filament_offset_preview_rgba(raw_atlas);
|
||||
volume->imported_texture_width = raw_atlas.width;
|
||||
volume->imported_texture_height = raw_atlas.height;
|
||||
volume->imported_texture_raw_channels = raw_atlas.channels;
|
||||
volume->imported_texture_raw_filament_offsets = std::move(raw_atlas.offsets);
|
||||
volume->imported_texture_raw_metadata_json = std::move(raw_atlas.metadata_json);
|
||||
} else {
|
||||
volume->imported_texture_rgba = std::move(imported_rgba);
|
||||
volume->imported_texture_width = imported_width;
|
||||
volume->imported_texture_height = imported_height;
|
||||
volume->imported_texture_raw_filament_offsets.clear();
|
||||
volume->imported_texture_raw_channels = 0;
|
||||
volume->imported_texture_raw_metadata_json.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
@@ -7898,8 +7970,11 @@ static void append_triangle_material_data(std::vector<uint8_t> &uv_valid,
|
||||
&& (shared_volume->imported_texture_uvs_per_face == volume->imported_texture_uvs_per_face)
|
||||
&& (shared_volume->imported_texture_uv_valid == volume->imported_texture_uv_valid)
|
||||
&& (shared_volume->imported_texture_rgba == volume->imported_texture_rgba)
|
||||
&& (shared_volume->imported_texture_raw_filament_offsets == volume->imported_texture_raw_filament_offsets)
|
||||
&& (shared_volume->imported_texture_width == volume->imported_texture_width)
|
||||
&& (shared_volume->imported_texture_height == volume->imported_texture_height))
|
||||
&& (shared_volume->imported_texture_height == volume->imported_texture_height)
|
||||
&& (shared_volume->imported_texture_raw_channels == volume->imported_texture_raw_channels)
|
||||
&& (shared_volume->imported_texture_raw_metadata_json == volume->imported_texture_raw_metadata_json))
|
||||
{
|
||||
auto data = iter->second.first;
|
||||
const_cast<_BBS_3MF_Exporter *>(this)->m_volume_paths.insert({volume, {data->sub_path, data->volumes_objectID.find(iter->second.second)->second}});
|
||||
@@ -8130,11 +8205,25 @@ static void append_triangle_material_data(std::vector<uint8_t> &uv_valid,
|
||||
if (texture_targets.find(texture_resource.texture_part_path) != texture_targets.end())
|
||||
continue;
|
||||
|
||||
std::vector<uint8_t> png_source_rgba;
|
||||
uint32_t png_width = volume->imported_texture_width;
|
||||
uint32_t png_height = volume->imported_texture_height;
|
||||
if (has_imported_raw_atlas_texture_payload(*volume)) {
|
||||
const ImageMapRawFilamentOffsetAtlas raw_atlas = raw_atlas_from_model_volume(*volume);
|
||||
std::string encode_error;
|
||||
if (!encode_image_map_raw_filament_offset_atlas(raw_atlas, png_source_rgba, png_width, png_height, &encode_error)) {
|
||||
add_error(encode_error.empty() ? "Unable to encode ImageMap raw filament offset atlas texture image" : encode_error);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
png_source_rgba.assign(volume->imported_texture_rgba.begin(), volume->imported_texture_rgba.end());
|
||||
}
|
||||
|
||||
size_t png_size = 0;
|
||||
void *png_data = tdefl_write_image_to_png_file_in_memory_ex(
|
||||
static_cast<const void *>(volume->imported_texture_rgba.data()),
|
||||
int(volume->imported_texture_width),
|
||||
int(volume->imported_texture_height),
|
||||
static_cast<const void *>(png_source_rgba.data()),
|
||||
int(png_width),
|
||||
int(png_height),
|
||||
4,
|
||||
&png_size,
|
||||
MZ_DEFAULT_COMPRESSION,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "ShortestPath.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "TextureMapping.hpp"
|
||||
#include "ImageMapRawFilamentOffsetAtlas.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "libslic3r.h"
|
||||
@@ -32,6 +33,7 @@
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
@@ -2000,6 +2002,9 @@ namespace DoExport {
|
||||
}
|
||||
} // namespace DoExport
|
||||
|
||||
static bool print_has_raw_offset_texture_data_without_raw_zone_for_gcode(const Print &print);
|
||||
static bool print_has_raw_offset_texture_zone_without_raw_data_for_gcode(const Print &print);
|
||||
|
||||
bool GCode::is_BBL_Printer()
|
||||
{
|
||||
if (m_curr_print)
|
||||
@@ -2038,6 +2043,18 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
GCodeProcessor::s_IsBBLPrinter = print->is_BBL_printer();
|
||||
m_writer.set_is_bbl_machine(print->is_BBL_printer());
|
||||
print->set_started(psGCodeExport);
|
||||
if (print_has_raw_offset_texture_data_without_raw_zone_for_gcode(*print)) {
|
||||
print->active_step_add_warning(
|
||||
PrintStateBase::WarningLevel::NON_CRITICAL,
|
||||
_(L("An object contains raw filament offset texture data, but it is not assigned to a texture mapping zone "
|
||||
"set to Raw filament offset mode. Slicing will use the preview texture instead of the raw offsets for that object.")));
|
||||
}
|
||||
if (print_has_raw_offset_texture_zone_without_raw_data_for_gcode(*print)) {
|
||||
print->active_step_add_warning(
|
||||
PrintStateBase::WarningLevel::NON_CRITICAL,
|
||||
_(L("An object is assigned to a Raw filament offset texture mapping zone, but it does not contain raw filament "
|
||||
"offset atlas data. Slicing will interpret its regular RGB texture channels as raw offsets.")));
|
||||
}
|
||||
|
||||
// check if any custom gcode contains keywords used by the gcode processor to
|
||||
// produce time estimation and gcode toolpaths
|
||||
@@ -6101,6 +6118,107 @@ static std::array<float, 4> unpack_rgba_u32(uint32_t packed_rgba)
|
||||
return { clamp01f_for_gcode(r), clamp01f_for_gcode(g), clamp01f_for_gcode(b), clamp01f_for_gcode(a) };
|
||||
}
|
||||
|
||||
static constexpr const char *TEXTURE_MAPPING_BACKGROUND_COLOR_CONFIG_KEY = "texture_mapping_background_color";
|
||||
|
||||
static int texture_mapping_color_hex_digit_for_gcode(char ch)
|
||||
{
|
||||
return ch >= '0' && ch <= '9' ? ch - '0' :
|
||||
ch >= 'a' && ch <= 'f' ? ch - 'a' + 10 :
|
||||
ch >= 'A' && ch <= 'F' ? ch - 'A' + 10 : -1;
|
||||
}
|
||||
|
||||
static std::optional<std::array<float, 4>> parse_texture_mapping_color_hex_for_gcode(const std::string &text)
|
||||
{
|
||||
if (text.empty())
|
||||
return std::nullopt;
|
||||
|
||||
const size_t hash_pos = text.find('#');
|
||||
const size_t start = hash_pos == std::string::npos ? 0 : hash_pos + 1;
|
||||
if (start + 6 > text.size())
|
||||
return std::nullopt;
|
||||
|
||||
uint32_t packed = 0;
|
||||
for (size_t idx = 0; idx < 6; ++idx) {
|
||||
const int value = texture_mapping_color_hex_digit_for_gcode(text[start + idx]);
|
||||
if (value < 0)
|
||||
return std::nullopt;
|
||||
packed = (packed << 4) | uint32_t(value);
|
||||
}
|
||||
|
||||
uint32_t alpha = 255;
|
||||
if (start + 8 <= text.size()) {
|
||||
alpha = 0;
|
||||
for (size_t idx = 6; idx < 8; ++idx) {
|
||||
const int value = texture_mapping_color_hex_digit_for_gcode(text[start + idx]);
|
||||
if (value < 0)
|
||||
return std::nullopt;
|
||||
alpha = (alpha << 4) | uint32_t(value);
|
||||
}
|
||||
}
|
||||
|
||||
return std::array<float, 4> {
|
||||
clamp01f_for_gcode(float((packed >> 16) & 0xFFu) / 255.f),
|
||||
clamp01f_for_gcode(float((packed >> 8) & 0xFFu) / 255.f),
|
||||
clamp01f_for_gcode(float(packed & 0xFFu) / 255.f),
|
||||
clamp01f_for_gcode(float(alpha & 0xFFu) / 255.f)
|
||||
};
|
||||
}
|
||||
|
||||
static std::optional<std::array<float, 4>> texture_mapping_background_color_from_config_for_gcode(const ModelConfigObject &config)
|
||||
{
|
||||
if (!config.has(TEXTURE_MAPPING_BACKGROUND_COLOR_CONFIG_KEY))
|
||||
return std::nullopt;
|
||||
|
||||
const ConfigOptionString *opt = dynamic_cast<const ConfigOptionString *>(config.option(TEXTURE_MAPPING_BACKGROUND_COLOR_CONFIG_KEY));
|
||||
if (opt == nullptr)
|
||||
return std::nullopt;
|
||||
|
||||
std::optional<std::array<float, 4>> color = parse_texture_mapping_color_hex_for_gcode(opt->value);
|
||||
if (color)
|
||||
(*color)[3] = 1.f;
|
||||
return color;
|
||||
}
|
||||
|
||||
static std::optional<std::array<float, 4>> texture_mapping_background_color_from_metadata_for_gcode(const ColorFacetsAnnotation &annotation)
|
||||
{
|
||||
const std::string &metadata = annotation.metadata_json();
|
||||
const std::string key = "\"background_color\":\"#";
|
||||
const size_t start = metadata.find(key);
|
||||
if (start == std::string::npos || start + key.size() + 8 > metadata.size())
|
||||
return std::nullopt;
|
||||
|
||||
std::optional<std::array<float, 4>> color =
|
||||
parse_texture_mapping_color_hex_for_gcode(metadata.substr(start + key.size() - 1, 9));
|
||||
if (color)
|
||||
(*color)[3] = 1.f;
|
||||
return color;
|
||||
}
|
||||
|
||||
static std::array<float, 4> texture_mapping_background_color_for_gcode(const ModelVolume &volume)
|
||||
{
|
||||
if (std::optional<std::array<float, 4>> color = texture_mapping_background_color_from_config_for_gcode(volume.config))
|
||||
return *color;
|
||||
if (volume.get_object() != nullptr) {
|
||||
if (std::optional<std::array<float, 4>> color = texture_mapping_background_color_from_config_for_gcode(volume.get_object()->config))
|
||||
return *color;
|
||||
}
|
||||
if (std::optional<std::array<float, 4>> color = texture_mapping_background_color_from_metadata_for_gcode(volume.texture_mapping_color_facets))
|
||||
return *color;
|
||||
return { 1.f, 1.f, 1.f, 1.f };
|
||||
}
|
||||
|
||||
static std::array<float, 4> composite_rgba_over_background_for_gcode(const std::array<float, 4> &rgba,
|
||||
const std::array<float, 4> &background)
|
||||
{
|
||||
const float alpha = clamp01f_for_gcode(rgba[3]);
|
||||
return {
|
||||
clamp01f_for_gcode(rgba[0] * alpha + background[0] * (1.f - alpha)),
|
||||
clamp01f_for_gcode(rgba[1] * alpha + background[1] * (1.f - alpha)),
|
||||
clamp01f_for_gcode(rgba[2] * alpha + background[2] * (1.f - alpha)),
|
||||
1.f
|
||||
};
|
||||
}
|
||||
|
||||
static std::array<float, 3> mix_component_colors_with_filament_mixer_for_gcode(const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<int> &weights)
|
||||
{
|
||||
@@ -6639,6 +6757,225 @@ static std::array<float, 4> sample_texture_rgba_bilinear_for_gcode(const std::ve
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::vector<float> sample_texture_raw_offsets_bilinear_for_gcode(const std::vector<uint8_t> &offsets,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
uint32_t channels,
|
||||
float u,
|
||||
float v)
|
||||
{
|
||||
std::vector<float> out;
|
||||
if (width == 0 || height == 0 || channels == 0 ||
|
||||
offsets.size() < size_t(width) * size_t(height) * size_t(channels))
|
||||
return out;
|
||||
|
||||
const float uu = wrap_repeat01_for_gcode(u);
|
||||
const float vv = wrap_repeat01_for_gcode(v);
|
||||
|
||||
const float x = uu * float(width > 1 ? width - 1 : 0);
|
||||
const float y = vv * float(height > 1 ? height - 1 : 0);
|
||||
const size_t x0 = std::min<size_t>(size_t(std::floor(x)), size_t(width - 1));
|
||||
const size_t y0 = std::min<size_t>(size_t(std::floor(y)), size_t(height - 1));
|
||||
const size_t x1 = std::min<size_t>(x0 + 1, size_t(width - 1));
|
||||
const size_t y1 = std::min<size_t>(y0 + 1, size_t(height - 1));
|
||||
const float tx = x - float(x0);
|
||||
const float ty = y - float(y0);
|
||||
|
||||
auto sample_channel = [&offsets, width, channels](size_t sx, size_t sy, size_t channel) {
|
||||
const size_t idx = (sy * size_t(width) + sx) * size_t(channels) + channel;
|
||||
return float(offsets[idx]) / 255.f;
|
||||
};
|
||||
|
||||
out.assign(size_t(channels), 0.f);
|
||||
for (size_t c = 0; c < out.size(); ++c) {
|
||||
const float c00 = sample_channel(x0, y0, c);
|
||||
const float c10 = sample_channel(x1, y0, c);
|
||||
const float c01 = sample_channel(x0, y1, c);
|
||||
const float c11 = sample_channel(x1, y1, c);
|
||||
const float cx0 = c00 + (c10 - c00) * tx;
|
||||
const float cx1 = c01 + (c11 - c01) * tx;
|
||||
out[c] = clamp01f_for_gcode(cx0 + (cx1 - cx0) * ty);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::array<float, 4> raw_offset_preview_rgba_for_gcode(const std::vector<float> &offsets)
|
||||
{
|
||||
if (offsets.empty())
|
||||
return { 0.f, 0.f, 0.f, 1.f };
|
||||
if (offsets.size() == 1)
|
||||
return { offsets[0], offsets[0], offsets[0], 1.f };
|
||||
return {
|
||||
offsets.size() > 0 ? offsets[0] : 0.f,
|
||||
offsets.size() > 1 ? offsets[1] : 0.f,
|
||||
offsets.size() > 2 ? offsets[2] : 0.f,
|
||||
1.f
|
||||
};
|
||||
}
|
||||
|
||||
static std::vector<std::string> raw_filament_color_mode_channel_keys_for_gcode(int filament_color_mode, size_t component_count)
|
||||
{
|
||||
std::vector<std::string> keys;
|
||||
switch (std::clamp(filament_color_mode, int(TextureMappingZone::FilamentColorAny), int(TextureMappingZone::FilamentColorBW))) {
|
||||
case int(TextureMappingZone::FilamentColorRGB):
|
||||
keys = { "R", "G", "B" };
|
||||
break;
|
||||
case int(TextureMappingZone::FilamentColorCMY):
|
||||
keys = { "C", "M", "Y" };
|
||||
break;
|
||||
case int(TextureMappingZone::FilamentColorCMYK):
|
||||
keys = { "C", "M", "Y", "K" };
|
||||
break;
|
||||
case int(TextureMappingZone::FilamentColorCMYW):
|
||||
keys = { "C", "M", "Y", "W" };
|
||||
break;
|
||||
case int(TextureMappingZone::FilamentColorRGBK):
|
||||
keys = { "R", "G", "B", "K" };
|
||||
break;
|
||||
case int(TextureMappingZone::FilamentColorRGBW):
|
||||
keys = { "R", "G", "B", "W" };
|
||||
break;
|
||||
case int(TextureMappingZone::FilamentColorBW):
|
||||
keys = { "K", "W" };
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (keys.size() > component_count)
|
||||
keys.resize(component_count);
|
||||
return keys;
|
||||
}
|
||||
|
||||
static std::vector<size_t> raw_component_source_channels_for_gcode(const std::string &metadata_json,
|
||||
uint32_t source_channels,
|
||||
int filament_color_mode,
|
||||
size_t component_count)
|
||||
{
|
||||
if (source_channels == 0 || component_count == 0)
|
||||
return {};
|
||||
|
||||
const size_t sentinel = std::numeric_limits<size_t>::max();
|
||||
std::vector<size_t> mapping(component_count, sentinel);
|
||||
const std::vector<ImageMapRawFilament> filaments =
|
||||
image_map_raw_filaments_from_metadata_json(metadata_json, source_channels);
|
||||
if (filaments.size() != size_t(source_channels))
|
||||
return {};
|
||||
|
||||
std::vector<std::string> source_keys(static_cast<size_t>(source_channels));
|
||||
std::vector<uint8_t> used(static_cast<size_t>(source_channels), 0);
|
||||
for (size_t channel = 0; channel < filaments.size(); ++channel) {
|
||||
const std::string key = image_map_raw_filament_channel_key(filaments[channel], channel);
|
||||
if (key.size() == 1 && image_map_raw_filament_is_standard_color(key))
|
||||
source_keys[channel] = key;
|
||||
}
|
||||
|
||||
const std::vector<std::string> target_keys =
|
||||
raw_filament_color_mode_channel_keys_for_gcode(filament_color_mode, component_count);
|
||||
if (!target_keys.empty()) {
|
||||
for (size_t component_idx = 0; component_idx < target_keys.size(); ++component_idx) {
|
||||
for (size_t channel = 0; channel < source_keys.size(); ++channel) {
|
||||
if (used[channel] == 0 && source_keys[channel] == target_keys[component_idx]) {
|
||||
mapping[component_idx] = channel;
|
||||
used[channel] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t next_source = 0;
|
||||
for (size_t component_idx = 0; component_idx < mapping.size(); ++component_idx) {
|
||||
if (mapping[component_idx] != sentinel)
|
||||
continue;
|
||||
while (next_source < source_keys.size() &&
|
||||
(used[next_source] != 0 || (!target_keys.empty() && !source_keys[next_source].empty())))
|
||||
++next_source;
|
||||
if (next_source >= source_keys.size())
|
||||
continue;
|
||||
mapping[component_idx] = next_source;
|
||||
used[next_source] = 1;
|
||||
++next_source;
|
||||
}
|
||||
|
||||
const bool has_mapping = std::any_of(mapping.begin(), mapping.end(), [sentinel](size_t value) { return value != sentinel; });
|
||||
return has_mapping ? mapping : std::vector<size_t>{};
|
||||
}
|
||||
|
||||
static std::vector<float> map_raw_sample_to_components_for_gcode(const std::vector<float> &raw_sample,
|
||||
const std::vector<size_t> &component_source_channels)
|
||||
{
|
||||
if (component_source_channels.empty())
|
||||
return {};
|
||||
const size_t sentinel = std::numeric_limits<size_t>::max();
|
||||
std::vector<float> mapped(component_source_channels.size(), 0.f);
|
||||
for (size_t component_idx = 0; component_idx < component_source_channels.size(); ++component_idx) {
|
||||
const size_t source_channel = component_source_channels[component_idx];
|
||||
if (source_channel != sentinel && source_channel < raw_sample.size())
|
||||
mapped[component_idx] = raw_sample[source_channel];
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
|
||||
static bool model_volume_has_raw_offset_texture_data_for_gcode(const ModelVolume *volume)
|
||||
{
|
||||
if (volume == nullptr ||
|
||||
volume->imported_texture_width == 0 ||
|
||||
volume->imported_texture_height == 0 ||
|
||||
volume->imported_texture_raw_channels == 0 ||
|
||||
volume->imported_texture_raw_filament_offsets.empty())
|
||||
return false;
|
||||
|
||||
return 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 bool print_has_raw_offset_texture_data_without_raw_zone_for_gcode(const Print &print)
|
||||
{
|
||||
const TextureMappingManager &texture_mgr = print.texture_mapping_manager();
|
||||
for (const PrintObject *print_object : print.objects()) {
|
||||
if (print_object == nullptr || print_object->model_object() == nullptr)
|
||||
continue;
|
||||
|
||||
for (const ModelVolume *volume : print_object->model_object()->volumes) {
|
||||
if (volume == nullptr ||
|
||||
!volume->is_model_part() ||
|
||||
!model_volume_has_raw_offset_texture_data_for_gcode(volume))
|
||||
continue;
|
||||
|
||||
const unsigned int filament_id = unsigned(std::max(0, volume->extruder_id()));
|
||||
const TextureMappingZone *zone = texture_mgr.zone_from_id(filament_id);
|
||||
if (zone == nullptr ||
|
||||
zone->texture_mapping_mode != int(TextureMappingZone::TextureMappingRawValues))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool print_has_raw_offset_texture_zone_without_raw_data_for_gcode(const Print &print)
|
||||
{
|
||||
const TextureMappingManager &texture_mgr = print.texture_mapping_manager();
|
||||
for (const PrintObject *print_object : print.objects()) {
|
||||
if (print_object == nullptr || print_object->model_object() == nullptr)
|
||||
continue;
|
||||
|
||||
for (const ModelVolume *volume : print_object->model_object()->volumes) {
|
||||
if (volume == nullptr || !volume->is_model_part())
|
||||
continue;
|
||||
|
||||
const unsigned int filament_id = unsigned(std::max(0, volume->extruder_id()));
|
||||
const TextureMappingZone *zone = texture_mgr.zone_from_id(filament_id);
|
||||
if (zone != nullptr &&
|
||||
zone->texture_mapping_mode == int(TextureMappingZone::TextureMappingRawValues) &&
|
||||
!model_volume_has_raw_offset_texture_data_for_gcode(volume))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::array<Vec2f, 3> unwrap_triangle_uvs_for_sampling_for_gcode(const Vec2f &uv0,
|
||||
const Vec2f &uv1,
|
||||
const Vec2f &uv2)
|
||||
@@ -6934,6 +7271,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
VertexColorOverhangWeightField weight_field;
|
||||
if (component_colors.empty())
|
||||
return weight_field;
|
||||
const size_t component_count = component_colors.size();
|
||||
|
||||
const ModelObject *model_object = print_object.model_object();
|
||||
if (model_object == nullptr)
|
||||
@@ -6957,16 +7295,26 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
const float tone_gamma =
|
||||
(!std::isfinite(texture_tone_gamma) || texture_tone_gamma <= 0.f) ? 1.f : std::clamp(texture_tone_gamma, 0.5f, 3.f);
|
||||
|
||||
struct TextureSampleData {
|
||||
std::array<float, 4> rgba { { 0.f, 0.f, 0.f, 1.f } };
|
||||
std::vector<float> raw_component_weights;
|
||||
};
|
||||
|
||||
struct WeightedTextureSample {
|
||||
float x_mm { 0.f };
|
||||
float y_mm { 0.f };
|
||||
std::array<float, 4> rgba { { 0.f, 0.f, 0.f, 1.f } };
|
||||
std::vector<float> raw_component_weights;
|
||||
float weight { 0.f };
|
||||
};
|
||||
std::vector<WeightedTextureSample> samples;
|
||||
samples.reserve(8192);
|
||||
|
||||
auto accumulate_sample = [&samples](float x_mm, float y_mm, const std::array<float, 4> &rgba, float sample_weight) {
|
||||
auto accumulate_sample = [&samples](float x_mm,
|
||||
float y_mm,
|
||||
const std::array<float, 4> &rgba,
|
||||
float sample_weight,
|
||||
std::vector<float> raw_component_weights = {}) {
|
||||
if (!std::isfinite(x_mm) || !std::isfinite(y_mm) || sample_weight <= EPSILON)
|
||||
return;
|
||||
if (!std::isfinite(sample_weight) ||
|
||||
@@ -6976,7 +7324,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
!std::isfinite(rgba[3]))
|
||||
return;
|
||||
|
||||
samples.push_back({ x_mm, y_mm, rgba, sample_weight });
|
||||
samples.push_back({ x_mm, y_mm, rgba, std::move(raw_component_weights), sample_weight });
|
||||
};
|
||||
|
||||
struct LayerPlaneSamplePoint {
|
||||
@@ -6987,7 +7335,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
auto accumulate_layer_plane_triangle_samples = [&](const Vec3d &p0,
|
||||
const Vec3d &p1,
|
||||
const Vec3d &p2,
|
||||
const auto &sample_rgba_for_barycentric) {
|
||||
const auto &sample_data_for_barycentric) {
|
||||
if (!use_layer_weighting)
|
||||
return false;
|
||||
|
||||
@@ -7087,7 +7435,12 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
barycentric /= barycentric_sum;
|
||||
|
||||
const Vec3d world_pos = p0 * double(barycentric.x()) + p1 * double(barycentric.y()) + p2 * double(barycentric.z());
|
||||
accumulate_sample(float(world_pos.x()), float(world_pos.y()), sample_rgba_for_barycentric(barycentric), sample_weight);
|
||||
TextureSampleData sample_data = sample_data_for_barycentric(barycentric);
|
||||
accumulate_sample(float(world_pos.x()),
|
||||
float(world_pos.y()),
|
||||
sample_data.rgba,
|
||||
sample_weight,
|
||||
std::move(sample_data.raw_component_weights));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -7097,7 +7450,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
const Vec3d &p1,
|
||||
const Vec3d &p2,
|
||||
const std::array<float, 4> &rgba) {
|
||||
if (accumulate_layer_plane_triangle_samples(p0, p1, p2, [&rgba](const Vec3f &) { return rgba; }))
|
||||
if (accumulate_layer_plane_triangle_samples(p0, p1, p2, [&rgba](const Vec3f &) { return TextureSampleData{ rgba, {} }; }))
|
||||
return;
|
||||
|
||||
const float max_world_edge_mm = std::max({
|
||||
@@ -7161,6 +7514,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
|
||||
const indexed_triangle_set &its = mesh_ptr->its;
|
||||
const Transform3d volume_trafo = object_trafo * volume->get_matrix();
|
||||
const std::array<float, 4> background_color = texture_mapping_background_color_for_gcode(*volume);
|
||||
|
||||
if (!volume->texture_mapping_color_facets.empty()) {
|
||||
std::vector<ColorFacetTriangle> color_facets;
|
||||
@@ -7172,8 +7526,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
if (!p0.allFinite() || !p1.allFinite() || !p2.allFinite())
|
||||
continue;
|
||||
|
||||
std::array<float, 4> rgba = unpack_rgba_u32(facet.rgba);
|
||||
rgba[3] = 1.f;
|
||||
std::array<float, 4> rgba = composite_rgba_over_background_for_gcode(unpack_rgba_u32(facet.rgba), background_color);
|
||||
|
||||
accumulate_constant_surface_triangle_samples(p0, p1, p2, rgba);
|
||||
}
|
||||
@@ -7190,6 +7543,18 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
volume->imported_texture_rgba.size() >= size_t(volume->imported_texture_width) * size_t(volume->imported_texture_height) * 4;
|
||||
|
||||
if (has_uv_texture) {
|
||||
const std::vector<size_t> raw_component_source_channels =
|
||||
raw_component_source_channels_for_gcode(volume->imported_texture_raw_metadata_json,
|
||||
volume->imported_texture_raw_channels,
|
||||
filament_color_mode,
|
||||
component_count);
|
||||
const bool use_raw_uv_texture =
|
||||
raw_values_mode &&
|
||||
raw_component_source_channels.size() == component_count &&
|
||||
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);
|
||||
const auto uv_edge_texel_length = [volume](const Vec2f &a, const Vec2f &b) {
|
||||
const float du = (a.x() - b.x()) * float(volume->imported_texture_width);
|
||||
const float dv = (a.y() - b.y()) * float(volume->imported_texture_height);
|
||||
@@ -7222,17 +7587,35 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
continue;
|
||||
const std::array<Vec2f, 3> tri_uv = unwrap_triangle_uvs_for_sampling_for_gcode(uv0, uv1, uv2);
|
||||
|
||||
auto sample_rgba_for_barycentric = [&](const Vec3f &barycentric) {
|
||||
const Vec2f uv = tri_uv[0] * barycentric.x() + tri_uv[1] * barycentric.y() + tri_uv[2] * barycentric.z();
|
||||
auto sample_data_for_uv = [&](const Vec2f &uv) {
|
||||
std::array<float, 4> rgba = sample_texture_rgba_bilinear_for_gcode(volume->imported_texture_rgba,
|
||||
volume->imported_texture_width,
|
||||
volume->imported_texture_height,
|
||||
uv.x(),
|
||||
uv.y());
|
||||
rgba[3] = 1.f;
|
||||
return rgba;
|
||||
std::vector<float> raw_component_weights;
|
||||
if (use_raw_uv_texture) {
|
||||
const std::vector<float> raw_sample =
|
||||
sample_texture_raw_offsets_bilinear_for_gcode(volume->imported_texture_raw_filament_offsets,
|
||||
volume->imported_texture_width,
|
||||
volume->imported_texture_height,
|
||||
volume->imported_texture_raw_channels,
|
||||
uv.x(),
|
||||
uv.y());
|
||||
raw_component_weights = map_raw_sample_to_components_for_gcode(raw_sample, raw_component_source_channels);
|
||||
if (raw_component_weights.size() == component_count)
|
||||
rgba = raw_offset_preview_rgba_for_gcode(raw_component_weights);
|
||||
}
|
||||
if (raw_component_weights.size() != component_count)
|
||||
rgba = composite_rgba_over_background_for_gcode(rgba, background_color);
|
||||
return TextureSampleData{ rgba, std::move(raw_component_weights) };
|
||||
};
|
||||
if (accumulate_layer_plane_triangle_samples(p0, p1, p2, sample_rgba_for_barycentric)) {
|
||||
|
||||
auto sample_data_for_barycentric = [&](const Vec3f &barycentric) {
|
||||
const Vec2f uv = tri_uv[0] * barycentric.x() + tri_uv[1] * barycentric.y() + tri_uv[2] * barycentric.z();
|
||||
return sample_data_for_uv(uv);
|
||||
};
|
||||
if (accumulate_layer_plane_triangle_samples(p0, p1, p2, sample_data_for_barycentric)) {
|
||||
sampled_from_uv_texture = true;
|
||||
continue;
|
||||
}
|
||||
@@ -7278,12 +7661,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
|
||||
const Vec3d world_pos = p0 * double(b0) + p1 * double(b1) + p2 * double(b2);
|
||||
const Vec2f uv = tri_uv[0] * b0 + tri_uv[1] * b1 + tri_uv[2] * b2;
|
||||
std::array<float, 4> rgba = sample_texture_rgba_bilinear_for_gcode(volume->imported_texture_rgba,
|
||||
volume->imported_texture_width,
|
||||
volume->imported_texture_height,
|
||||
uv.x(),
|
||||
uv.y());
|
||||
rgba[3] = 1.f;
|
||||
TextureSampleData sample_data = sample_data_for_uv(uv);
|
||||
|
||||
float sample_weight = area_weight;
|
||||
if (use_layer_weighting) {
|
||||
@@ -7297,7 +7675,11 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
if (sample_weight <= EPSILON)
|
||||
continue;
|
||||
|
||||
accumulate_sample(float(world_pos.x()), float(world_pos.y()), rgba, sample_weight);
|
||||
accumulate_sample(float(world_pos.x()),
|
||||
float(world_pos.y()),
|
||||
sample_data.rgba,
|
||||
sample_weight,
|
||||
std::move(sample_data.raw_component_weights));
|
||||
sampled_from_uv_texture = true;
|
||||
}
|
||||
}
|
||||
@@ -7314,8 +7696,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
|
||||
for (size_t i = 0; i < its.vertices.size(); ++i) {
|
||||
const Vec3d world_pos = volume_trafo * its.vertices[i].cast<double>();
|
||||
std::array<float, 4> rgba = unpack_rgba_u32(volume->imported_vertex_colors_rgba[i]);
|
||||
rgba[3] = 1.f;
|
||||
std::array<float, 4> rgba = composite_rgba_over_background_for_gcode(unpack_rgba_u32(volume->imported_vertex_colors_rgba[i]), background_color);
|
||||
float sample_weight = 1.f;
|
||||
if (use_layer_weighting) {
|
||||
const float dz = std::abs(float(world_pos.z()) - layer_z_mm);
|
||||
@@ -7335,7 +7716,6 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
if (samples.empty())
|
||||
return VertexColorOverhangWeightField{};
|
||||
|
||||
const size_t component_count = component_colors.size();
|
||||
const std::vector<std::array<float, 3>> fixed_color_solver_component_colors =
|
||||
fixed_color_generic_solver_component_colors_for_gcode(filament_color_mode);
|
||||
const bool use_fixed_color_generic_solver =
|
||||
@@ -7385,48 +7765,54 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
weight_field.sample_y_mm[sample_idx] = sample.y_mm;
|
||||
weight_field.sample_weight[sample_idx] = sample.weight;
|
||||
|
||||
std::array<float, 3> target = {
|
||||
clamp01f_for_gcode(sample.rgba[0]),
|
||||
clamp01f_for_gcode(sample.rgba[1]),
|
||||
clamp01f_for_gcode(sample.rgba[2])
|
||||
};
|
||||
if (std::abs(tone_gamma - 1.f) > 1e-5f) {
|
||||
target[0] = apply_texture_tone_gamma_for_gcode(target[0], tone_gamma);
|
||||
target[1] = apply_texture_tone_gamma_for_gcode(target[1], tone_gamma);
|
||||
target[2] = apply_texture_tone_gamma_for_gcode(target[2], tone_gamma);
|
||||
}
|
||||
|
||||
std::vector<float> desired(component_count, 0.f);
|
||||
size_t mapped_component_count = component_count;
|
||||
if (raw_values_mode) {
|
||||
const float channels[3] = { target[0], target[1], target[2] };
|
||||
const size_t channel_count = std::min(component_count, size_t(3));
|
||||
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;
|
||||
const bool has_raw_component_weights = raw_values_mode && sample.raw_component_weights.size() == component_count;
|
||||
if (has_raw_component_weights) {
|
||||
for (size_t component_idx = 0; component_idx < component_count; ++component_idx)
|
||||
desired[component_idx] = clamp01f_for_gcode(sample.raw_component_weights[component_idx]);
|
||||
} else {
|
||||
std::vector<float> optimized;
|
||||
if (!use_fixed_color_generic_solver)
|
||||
optimized = optimized_primary_component_weights_for_target_for_gcode(target,
|
||||
component_count,
|
||||
filament_color_mode,
|
||||
component_colors,
|
||||
force_sequential_filaments);
|
||||
if (optimized.size() == component_count)
|
||||
desired = std::move(optimized);
|
||||
else {
|
||||
std::vector<float> best = generic_mix_candidates != nullptr ?
|
||||
best_component_mix_weights_for_target_for_gcode(*generic_mix_candidates,
|
||||
target,
|
||||
generic_solver_lookup_mode,
|
||||
generic_solver_mode) :
|
||||
std::vector<float>{};
|
||||
if (best.size() == component_count)
|
||||
desired = std::move(best);
|
||||
std::array<float, 3> target = {
|
||||
clamp01f_for_gcode(sample.rgba[0]),
|
||||
clamp01f_for_gcode(sample.rgba[1]),
|
||||
clamp01f_for_gcode(sample.rgba[2])
|
||||
};
|
||||
if (std::abs(tone_gamma - 1.f) > 1e-5f) {
|
||||
target[0] = apply_texture_tone_gamma_for_gcode(target[0], tone_gamma);
|
||||
target[1] = apply_texture_tone_gamma_for_gcode(target[1], tone_gamma);
|
||||
target[2] = apply_texture_tone_gamma_for_gcode(target[2], tone_gamma);
|
||||
}
|
||||
|
||||
if (raw_values_mode) {
|
||||
const float channels[3] = { target[0], target[1], target[2] };
|
||||
const size_t channel_count = std::min(component_count, size_t(3));
|
||||
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 {
|
||||
std::vector<float> optimized;
|
||||
if (!use_fixed_color_generic_solver)
|
||||
optimized = optimized_primary_component_weights_for_target_for_gcode(target,
|
||||
component_count,
|
||||
filament_color_mode,
|
||||
component_colors,
|
||||
force_sequential_filaments);
|
||||
if (optimized.size() == component_count)
|
||||
desired = std::move(optimized);
|
||||
else {
|
||||
std::vector<float> best = generic_mix_candidates != nullptr ?
|
||||
best_component_mix_weights_for_target_for_gcode(*generic_mix_candidates,
|
||||
target,
|
||||
generic_solver_lookup_mode,
|
||||
generic_solver_mode) :
|
||||
std::vector<float>{};
|
||||
if (best.size() == component_count)
|
||||
desired = std::move(best);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (std::abs(contrast_factor - 1.f) > 1e-5f)
|
||||
if (!has_raw_component_weights && 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) {
|
||||
|
||||
511
src/libslic3r/ImageMapRawFilamentOffsetAtlas.cpp
Normal file
511
src/libslic3r/ImageMapRawFilamentOffsetAtlas.cpp
Normal file
@@ -0,0 +1,511 @@
|
||||
#include "ImageMapRawFilamentOffsetAtlas.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <utility>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char *Magic = "imagemap_raw_filament_offset";
|
||||
constexpr size_t MagicSize = 28;
|
||||
constexpr size_t FixedHeaderSize = 38;
|
||||
|
||||
static void set_error(std::string *error, const std::string &message)
|
||||
{
|
||||
if (error != nullptr)
|
||||
*error = message;
|
||||
}
|
||||
|
||||
static uint32_t read_be_u32(const std::vector<uint8_t> &bytes, size_t offset)
|
||||
{
|
||||
return (uint32_t(bytes[offset]) << 24) |
|
||||
(uint32_t(bytes[offset + 1]) << 16) |
|
||||
(uint32_t(bytes[offset + 2]) << 8) |
|
||||
uint32_t(bytes[offset + 3]);
|
||||
}
|
||||
|
||||
static void append_be_u32(std::vector<uint8_t> &bytes, uint32_t value)
|
||||
{
|
||||
bytes.emplace_back(uint8_t((value >> 24) & 0xFFu));
|
||||
bytes.emplace_back(uint8_t((value >> 16) & 0xFFu));
|
||||
bytes.emplace_back(uint8_t((value >> 8) & 0xFFu));
|
||||
bytes.emplace_back(uint8_t(value & 0xFFu));
|
||||
}
|
||||
|
||||
static bool read_header_bytes(const std::vector<uint8_t> &rgba,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
size_t byte_count,
|
||||
std::vector<uint8_t> &bytes)
|
||||
{
|
||||
if (byte_count > std::numeric_limits<size_t>::max() / 8)
|
||||
return false;
|
||||
if (size_t(width) * size_t(height) < byte_count * 8)
|
||||
return false;
|
||||
|
||||
bytes.assign(byte_count, 0);
|
||||
for (size_t byte_idx = 0; byte_idx < byte_count; ++byte_idx) {
|
||||
uint8_t value = 0;
|
||||
for (size_t bit_idx = 0; bit_idx < 8; ++bit_idx) {
|
||||
const size_t pixel_idx = byte_idx * 8 + bit_idx;
|
||||
const size_t rgba_idx = pixel_idx * 4;
|
||||
if (rgba_idx + 2 >= rgba.size())
|
||||
return false;
|
||||
const unsigned int average = (unsigned(rgba[rgba_idx + 0]) + unsigned(rgba[rgba_idx + 1]) + unsigned(rgba[rgba_idx + 2])) / 3u;
|
||||
value = uint8_t((value << 1) | (average >= 128u ? 1u : 0u));
|
||||
}
|
||||
bytes[byte_idx] = value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void write_header_bytes(std::vector<uint8_t> &rgba, const std::vector<uint8_t> &bytes)
|
||||
{
|
||||
for (size_t byte_idx = 0; byte_idx < bytes.size(); ++byte_idx) {
|
||||
const uint8_t value = bytes[byte_idx];
|
||||
for (size_t bit_idx = 0; bit_idx < 8; ++bit_idx) {
|
||||
const bool bit = (value & (uint8_t(1) << (7 - bit_idx))) != 0;
|
||||
const size_t rgba_idx = (byte_idx * 8 + bit_idx) * 4;
|
||||
const uint8_t channel = bit ? 255 : 0;
|
||||
rgba[rgba_idx + 0] = channel;
|
||||
rgba[rgba_idx + 1] = channel;
|
||||
rgba[rgba_idx + 2] = channel;
|
||||
rgba[rgba_idx + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string uppercase_ascii(std::string value)
|
||||
{
|
||||
for (char &ch : value)
|
||||
if (ch >= 'a' && ch <= 'z')
|
||||
ch = char(ch - 'a' + 'A');
|
||||
return value;
|
||||
}
|
||||
|
||||
static std::string standard_hex_for_color_code(const std::string &color)
|
||||
{
|
||||
const std::string key = uppercase_ascii(color);
|
||||
if (key == "C") return "#00FFFF";
|
||||
if (key == "M") return "#FF00FF";
|
||||
if (key == "Y") return "#FFFF00";
|
||||
if (key == "K") return "#000000";
|
||||
if (key == "W") return "#FFFFFF";
|
||||
if (key == "R") return "#FF0000";
|
||||
if (key == "G") return "#00FF00";
|
||||
if (key == "B") return "#0000FF";
|
||||
return "#FFFFFF";
|
||||
}
|
||||
|
||||
static nlohmann::json atlas_metadata_json(const ImageMapRawFilamentOffsetAtlas &atlas, uint32_t header_rows)
|
||||
{
|
||||
const uint32_t region_count = (atlas.channels + 2u) / 3u;
|
||||
nlohmann::json root;
|
||||
root["format"] = "raw_filament_offset_atlas";
|
||||
root["image"] = {
|
||||
{ "width", atlas.width },
|
||||
{ "height", atlas.height },
|
||||
{ "channels", atlas.channels }
|
||||
};
|
||||
root["filaments"] = nlohmann::json::array();
|
||||
for (const ImageMapRawFilament &filament : atlas.filaments) {
|
||||
nlohmann::json entry;
|
||||
entry["slot"] = filament.slot;
|
||||
entry["color"] = filament.color.empty() ? "custom" : filament.color;
|
||||
if (!filament.hex.empty())
|
||||
entry["hex"] = filament.hex;
|
||||
root["filaments"].push_back(std::move(entry));
|
||||
}
|
||||
root["regions"] = nlohmann::json::array();
|
||||
for (uint32_t region_idx = 0; region_idx < region_count; ++region_idx) {
|
||||
nlohmann::json channels = nlohmann::json::object();
|
||||
const uint32_t first_channel = region_idx * 3u + 1u;
|
||||
if (first_channel <= atlas.channels)
|
||||
channels["r"] = first_channel;
|
||||
if (first_channel + 1u <= atlas.channels)
|
||||
channels["g"] = first_channel + 1u;
|
||||
if (first_channel + 2u <= atlas.channels)
|
||||
channels["b"] = first_channel + 2u;
|
||||
|
||||
nlohmann::json region;
|
||||
region["x"] = region_idx * atlas.width;
|
||||
region["y"] = header_rows;
|
||||
region["width"] = atlas.width;
|
||||
region["height"] = atlas.height;
|
||||
region["channels"] = std::move(channels);
|
||||
if (region_idx == 0)
|
||||
region["alpha"] = "projection_mask";
|
||||
root["regions"].push_back(std::move(region));
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ImageMapRawFilamentOffsetAtlas::valid() const
|
||||
{
|
||||
return width > 0 &&
|
||||
height > 0 &&
|
||||
channels > 0 &&
|
||||
offsets.size() >= size_t(width) * size_t(height) * size_t(channels);
|
||||
}
|
||||
|
||||
bool image_map_raw_filament_is_standard_color(const std::string &color)
|
||||
{
|
||||
const std::string key = uppercase_ascii(color);
|
||||
return key == "C" || key == "M" || key == "Y" || key == "K" ||
|
||||
key == "W" || key == "R" || key == "G" || key == "B";
|
||||
}
|
||||
|
||||
std::string image_map_raw_filament_channel_key(const ImageMapRawFilament &filament, size_t channel_idx)
|
||||
{
|
||||
const std::string color = uppercase_ascii(filament.color);
|
||||
if (image_map_raw_filament_is_standard_color(color))
|
||||
return color;
|
||||
|
||||
std::string hex = uppercase_ascii(filament.hex);
|
||||
const unsigned int slot = filament.slot != 0 ? filament.slot : unsigned(channel_idx + 1);
|
||||
if (!hex.empty())
|
||||
return "CUSTOM:" + std::to_string(slot) + ":" + hex;
|
||||
if (!color.empty())
|
||||
return color + ":" + std::to_string(slot);
|
||||
return "SLOT:" + std::to_string(slot);
|
||||
}
|
||||
|
||||
std::vector<ImageMapRawFilament> image_map_raw_filaments_for_channels(const std::vector<ImageMapRawFilament> &filaments,
|
||||
uint32_t channels)
|
||||
{
|
||||
std::vector<ImageMapRawFilament> normalized(static_cast<size_t>(channels));
|
||||
std::vector<uint8_t> filled(static_cast<size_t>(channels), 0);
|
||||
for (uint32_t channel = 0; channel < channels; ++channel) {
|
||||
normalized[size_t(channel)].slot = channel + 1;
|
||||
normalized[size_t(channel)].color = "custom";
|
||||
normalized[size_t(channel)].hex = "#FFFFFF";
|
||||
}
|
||||
|
||||
size_t next_empty = 0;
|
||||
for (ImageMapRawFilament filament : filaments) {
|
||||
size_t target = size_t(channels);
|
||||
if (filament.slot >= 1 && filament.slot <= channels)
|
||||
target = size_t(filament.slot - 1);
|
||||
else {
|
||||
while (next_empty < filled.size() && filled[next_empty] != 0)
|
||||
++next_empty;
|
||||
if (next_empty < filled.size())
|
||||
target = next_empty;
|
||||
}
|
||||
if (target >= normalized.size() || filled[target] != 0)
|
||||
continue;
|
||||
if (filament.slot == 0)
|
||||
filament.slot = unsigned(target + 1);
|
||||
if (filament.color.empty())
|
||||
filament.color = "custom";
|
||||
if (filament.hex.empty() && !image_map_raw_filament_is_standard_color(filament.color))
|
||||
filament.hex = "#FFFFFF";
|
||||
normalized[target] = std::move(filament);
|
||||
filled[target] = 1;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
std::vector<ImageMapRawFilament> image_map_raw_filaments_from_metadata_json(const std::string &metadata_json,
|
||||
uint32_t channels)
|
||||
{
|
||||
std::vector<ImageMapRawFilament> filaments;
|
||||
try {
|
||||
const nlohmann::json root = nlohmann::json::parse(metadata_json);
|
||||
const nlohmann::json entries = root.value("filaments", nlohmann::json::array());
|
||||
if (entries.is_array()) {
|
||||
for (const nlohmann::json &entry : entries) {
|
||||
if (!entry.is_object())
|
||||
continue;
|
||||
ImageMapRawFilament filament;
|
||||
filament.slot = unsigned(std::max(0, entry.value("slot", 0)));
|
||||
filament.color = entry.value("color", std::string());
|
||||
filament.hex = entry.value("hex", std::string());
|
||||
filaments.emplace_back(std::move(filament));
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
filaments.clear();
|
||||
}
|
||||
return image_map_raw_filaments_for_channels(filaments, channels);
|
||||
}
|
||||
|
||||
std::vector<std::string> image_map_raw_filament_channel_keys(const std::vector<ImageMapRawFilament> &filaments)
|
||||
{
|
||||
std::vector<std::string> keys;
|
||||
keys.reserve(filaments.size());
|
||||
for (size_t idx = 0; idx < filaments.size(); ++idx)
|
||||
keys.emplace_back(image_map_raw_filament_channel_key(filaments[idx], idx));
|
||||
return keys;
|
||||
}
|
||||
|
||||
bool decode_image_map_raw_filament_offset_atlas(const std::vector<uint8_t> &rgba,
|
||||
uint32_t atlas_width,
|
||||
uint32_t atlas_height,
|
||||
ImageMapRawFilamentOffsetAtlas &out,
|
||||
std::string *error)
|
||||
{
|
||||
out = {};
|
||||
if (atlas_width == 0 || atlas_height == 0 || rgba.size() < size_t(atlas_width) * size_t(atlas_height) * 4) {
|
||||
set_error(error, "Invalid image dimensions.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> fixed;
|
||||
if (!read_header_bytes(rgba, atlas_width, atlas_height, FixedHeaderSize, fixed)) {
|
||||
set_error(error, "The image is too small for an ImageMap raw filament offset header.");
|
||||
return false;
|
||||
}
|
||||
if (!std::equal(fixed.begin(), fixed.begin() + MagicSize, Magic)) {
|
||||
set_error(error, "The image is not an ImageMap raw filament offset atlas.");
|
||||
return false;
|
||||
}
|
||||
if (fixed[MagicSize] != 1u || (fixed[MagicSize + 1] & 1u) == 0u) {
|
||||
set_error(error, "Unsupported ImageMap raw filament offset atlas header.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t header_rows = read_be_u32(fixed, MagicSize + 2);
|
||||
const uint32_t metadata_length = read_be_u32(fixed, MagicSize + 6);
|
||||
if (header_rows == 0 || header_rows > atlas_height) {
|
||||
set_error(error, "Invalid ImageMap raw filament offset header row count.");
|
||||
return false;
|
||||
}
|
||||
const size_t total_header_bytes = FixedHeaderSize + size_t(metadata_length);
|
||||
if (total_header_bytes > std::numeric_limits<size_t>::max() / 8 ||
|
||||
total_header_bytes * 8 > size_t(header_rows) * size_t(atlas_width)) {
|
||||
set_error(error, "ImageMap raw filament offset metadata exceeds the declared header rows.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> header;
|
||||
if (!read_header_bytes(rgba, atlas_width, atlas_height, total_header_bytes, header)) {
|
||||
set_error(error, "Could not read ImageMap raw filament offset metadata.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string metadata(reinterpret_cast<const char *>(header.data() + FixedHeaderSize), metadata_length);
|
||||
nlohmann::json root;
|
||||
try {
|
||||
root = nlohmann::json::parse(metadata);
|
||||
} catch (...) {
|
||||
set_error(error, "ImageMap raw filament offset metadata is not valid JSON.");
|
||||
return false;
|
||||
}
|
||||
if (!root.is_object() || root.value("format", std::string()) != "raw_filament_offset_atlas") {
|
||||
set_error(error, "ImageMap raw filament offset metadata has an unsupported format.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const nlohmann::json image = root.value("image", nlohmann::json::object());
|
||||
const int logical_width = image.value("width", 0);
|
||||
const int logical_height = image.value("height", 0);
|
||||
const int logical_channels = image.value("channels", 0);
|
||||
if (logical_width <= 0 || logical_height <= 0 || logical_channels <= 0) {
|
||||
set_error(error, "ImageMap raw filament offset metadata has invalid image dimensions.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageMapRawFilamentOffsetAtlas decoded;
|
||||
decoded.width = uint32_t(logical_width);
|
||||
decoded.height = uint32_t(logical_height);
|
||||
decoded.channels = uint32_t(logical_channels);
|
||||
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;
|
||||
|
||||
const nlohmann::json filaments = root.value("filaments", nlohmann::json::array());
|
||||
if (filaments.is_array()) {
|
||||
for (const nlohmann::json &entry : filaments) {
|
||||
if (!entry.is_object())
|
||||
continue;
|
||||
ImageMapRawFilament filament;
|
||||
filament.slot = unsigned(std::max(0, entry.value("slot", 0)));
|
||||
filament.color = entry.value("color", std::string());
|
||||
filament.hex = entry.value("hex", std::string());
|
||||
if (filament.hex.empty())
|
||||
filament.hex = standard_hex_for_color_code(filament.color);
|
||||
decoded.filaments.emplace_back(std::move(filament));
|
||||
}
|
||||
}
|
||||
|
||||
const nlohmann::json regions = root.value("regions", nlohmann::json::array());
|
||||
if (!regions.is_array() || regions.empty()) {
|
||||
set_error(error, "ImageMap raw filament offset metadata does not contain regions.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const nlohmann::json ®ion : regions) {
|
||||
if (!region.is_object())
|
||||
continue;
|
||||
const int rx = region.value("x", -1);
|
||||
const int ry = region.value("y", -1);
|
||||
const int rw = region.value("width", 0);
|
||||
const int rh = region.value("height", 0);
|
||||
if (rx < 0 || ry < 0 || rw <= 0 || rh <= 0 ||
|
||||
uint64_t(rx) + uint64_t(rw) > atlas_width ||
|
||||
uint64_t(ry) + uint64_t(rh) > atlas_height) {
|
||||
set_error(error, "ImageMap raw filament offset region exceeds the atlas image.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const nlohmann::json channels = region.value("channels", nlohmann::json::object());
|
||||
if (channels.is_object()) {
|
||||
const std::array<std::pair<const char *, size_t>, 3> rgb_channels = {
|
||||
std::make_pair("r", size_t(0)),
|
||||
std::make_pair("g", size_t(1)),
|
||||
std::make_pair("b", size_t(2))
|
||||
};
|
||||
for (const auto &rgb_channel : rgb_channels) {
|
||||
const int logical_channel = channels.value(rgb_channel.first, 0);
|
||||
if (logical_channel <= 0 || logical_channel > logical_channels)
|
||||
continue;
|
||||
const size_t dst_channel = size_t(logical_channel - 1);
|
||||
const uint32_t copy_width = std::min<uint32_t>(decoded.width, uint32_t(rw));
|
||||
const uint32_t copy_height = std::min<uint32_t>(decoded.height, uint32_t(rh));
|
||||
for (uint32_t y = 0; y < copy_height; ++y) {
|
||||
for (uint32_t x = 0; x < copy_width; ++x) {
|
||||
const size_t src_idx = (size_t(ry + int(y)) * size_t(atlas_width) + size_t(rx + int(x))) * 4 + rgb_channel.second;
|
||||
const size_t dst_idx = (size_t(y) * size_t(decoded.width) + size_t(x)) * size_t(decoded.channels) + dst_channel;
|
||||
decoded.offsets[dst_idx] = rgba[src_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (region.value("alpha", std::string()) == "projection_mask") {
|
||||
const uint32_t copy_width = std::min<uint32_t>(decoded.width, uint32_t(rw));
|
||||
const uint32_t copy_height = std::min<uint32_t>(decoded.height, uint32_t(rh));
|
||||
for (uint32_t y = 0; y < copy_height; ++y) {
|
||||
for (uint32_t x = 0; x < copy_width; ++x) {
|
||||
const size_t src_idx = (size_t(ry + int(y)) * size_t(atlas_width) + size_t(rx + int(x))) * 4 + 3;
|
||||
decoded.mask[size_t(y) * size_t(decoded.width) + size_t(x)] = rgba[src_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out = std::move(decoded);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool encode_image_map_raw_filament_offset_atlas(const ImageMapRawFilamentOffsetAtlas &atlas,
|
||||
std::vector<uint8_t> &rgba,
|
||||
uint32_t &atlas_width,
|
||||
uint32_t &atlas_height,
|
||||
std::string *error)
|
||||
{
|
||||
rgba.clear();
|
||||
atlas_width = 0;
|
||||
atlas_height = 0;
|
||||
if (!atlas.valid()) {
|
||||
set_error(error, "Invalid ImageMap raw filament offset atlas data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t region_count = (atlas.channels + 2u) / 3u;
|
||||
if (region_count == 0 || atlas.width > std::numeric_limits<uint32_t>::max() / region_count) {
|
||||
set_error(error, "ImageMap raw filament offset atlas is too wide.");
|
||||
return false;
|
||||
}
|
||||
|
||||
atlas_width = atlas.width * region_count;
|
||||
uint32_t header_rows = 1;
|
||||
std::string metadata;
|
||||
for (int iter = 0; iter < 8; ++iter) {
|
||||
metadata = atlas_metadata_json(atlas, header_rows).dump();
|
||||
const size_t bits = (FixedHeaderSize + metadata.size()) * 8;
|
||||
const uint32_t needed_rows = uint32_t((bits + size_t(atlas_width) - 1) / size_t(atlas_width));
|
||||
if (needed_rows == header_rows)
|
||||
break;
|
||||
header_rows = std::max<uint32_t>(needed_rows, 1);
|
||||
}
|
||||
metadata = atlas_metadata_json(atlas, header_rows).dump();
|
||||
if ((FixedHeaderSize + metadata.size()) * 8 > size_t(header_rows) * size_t(atlas_width)) {
|
||||
set_error(error, "Could not fit ImageMap raw filament offset metadata header.");
|
||||
return false;
|
||||
}
|
||||
if (header_rows > std::numeric_limits<uint32_t>::max() - atlas.height) {
|
||||
set_error(error, "ImageMap raw filament offset atlas is too tall.");
|
||||
return false;
|
||||
}
|
||||
|
||||
atlas_height = header_rows + atlas.height;
|
||||
rgba.assign(size_t(atlas_width) * size_t(atlas_height) * 4, 255);
|
||||
for (uint32_t y = 0; y < header_rows; ++y) {
|
||||
for (uint32_t x = 0; x < atlas_width; ++x) {
|
||||
const size_t idx = (size_t(y) * size_t(atlas_width) + size_t(x)) * 4;
|
||||
rgba[idx + 0] = 0;
|
||||
rgba[idx + 1] = 0;
|
||||
rgba[idx + 2] = 0;
|
||||
rgba[idx + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t region_idx = 0; region_idx < region_count; ++region_idx) {
|
||||
const uint32_t region_x = region_idx * atlas.width;
|
||||
const uint32_t first_channel = region_idx * 3u;
|
||||
for (uint32_t y = 0; y < atlas.height; ++y) {
|
||||
for (uint32_t x = 0; x < atlas.width; ++x) {
|
||||
const size_t dst = (size_t(header_rows + y) * size_t(atlas_width) + size_t(region_x + x)) * 4;
|
||||
for (uint32_t c = 0; c < 3; ++c) {
|
||||
const uint32_t channel = first_channel + c;
|
||||
rgba[dst + c] = channel < atlas.channels ?
|
||||
atlas.offsets[(size_t(y) * size_t(atlas.width) + size_t(x)) * size_t(atlas.channels) + size_t(channel)] :
|
||||
0;
|
||||
}
|
||||
rgba[dst + 3] = region_idx == 0 && atlas.mask.size() >= size_t(atlas.width) * size_t(atlas.height) ?
|
||||
atlas.mask[size_t(y) * size_t(atlas.width) + size_t(x)] :
|
||||
255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> header;
|
||||
header.reserve(FixedHeaderSize + metadata.size());
|
||||
header.insert(header.end(), Magic, Magic + MagicSize);
|
||||
header.emplace_back(1);
|
||||
header.emplace_back(1);
|
||||
append_be_u32(header, header_rows);
|
||||
append_be_u32(header, uint32_t(metadata.size()));
|
||||
header.insert(header.end(), metadata.begin(), metadata.end());
|
||||
write_header_bytes(rgba, header);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> image_map_raw_filament_offset_preview_rgba(const ImageMapRawFilamentOffsetAtlas &atlas)
|
||||
{
|
||||
std::vector<uint8_t> preview;
|
||||
if (!atlas.valid())
|
||||
return preview;
|
||||
|
||||
preview.assign(size_t(atlas.width) * size_t(atlas.height) * 4, 255);
|
||||
const bool grayscale = atlas.channels == 1;
|
||||
for (uint32_t y = 0; y < atlas.height; ++y) {
|
||||
for (uint32_t x = 0; x < atlas.width; ++x) {
|
||||
const size_t pixel = size_t(y) * size_t(atlas.width) + size_t(x);
|
||||
const size_t src = pixel * size_t(atlas.channels);
|
||||
const size_t dst = pixel * 4;
|
||||
if (grayscale) {
|
||||
preview[dst + 0] = atlas.offsets[src];
|
||||
preview[dst + 1] = atlas.offsets[src];
|
||||
preview[dst + 2] = atlas.offsets[src];
|
||||
} else {
|
||||
preview[dst + 0] = atlas.channels > 0 ? atlas.offsets[src + 0] : 0;
|
||||
preview[dst + 1] = atlas.channels > 1 ? atlas.offsets[src + 1] : 0;
|
||||
preview[dst + 2] = atlas.channels > 2 ? atlas.offsets[src + 2] : 0;
|
||||
}
|
||||
preview[dst + 3] = atlas.mask.size() > pixel ? atlas.mask[pixel] : 255;
|
||||
}
|
||||
}
|
||||
return preview;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
59
src/libslic3r/ImageMapRawFilamentOffsetAtlas.hpp
Normal file
59
src/libslic3r/ImageMapRawFilamentOffsetAtlas.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef slic3r_ImageMapRawFilamentOffsetAtlas_hpp_
|
||||
#define slic3r_ImageMapRawFilamentOffsetAtlas_hpp_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct ImageMapRawFilament
|
||||
{
|
||||
unsigned int slot { 0 };
|
||||
std::string color;
|
||||
std::string hex;
|
||||
};
|
||||
|
||||
struct ImageMapRawFilamentOffsetAtlas
|
||||
{
|
||||
uint32_t width { 0 };
|
||||
uint32_t height { 0 };
|
||||
uint32_t channels { 0 };
|
||||
std::vector<ImageMapRawFilament> filaments;
|
||||
std::vector<uint8_t> offsets;
|
||||
std::vector<uint8_t> mask;
|
||||
std::string metadata_json;
|
||||
|
||||
bool valid() const;
|
||||
};
|
||||
|
||||
bool decode_image_map_raw_filament_offset_atlas(const std::vector<uint8_t> &rgba,
|
||||
uint32_t atlas_width,
|
||||
uint32_t atlas_height,
|
||||
ImageMapRawFilamentOffsetAtlas &out,
|
||||
std::string *error = nullptr);
|
||||
|
||||
bool encode_image_map_raw_filament_offset_atlas(const ImageMapRawFilamentOffsetAtlas &atlas,
|
||||
std::vector<uint8_t> &rgba,
|
||||
uint32_t &atlas_width,
|
||||
uint32_t &atlas_height,
|
||||
std::string *error = nullptr);
|
||||
|
||||
bool image_map_raw_filament_is_standard_color(const std::string &color);
|
||||
|
||||
std::string image_map_raw_filament_channel_key(const ImageMapRawFilament &filament, size_t channel_idx);
|
||||
|
||||
std::vector<ImageMapRawFilament> image_map_raw_filaments_for_channels(const std::vector<ImageMapRawFilament> &filaments,
|
||||
uint32_t channels);
|
||||
|
||||
std::vector<ImageMapRawFilament> image_map_raw_filaments_from_metadata_json(const std::string &metadata_json,
|
||||
uint32_t channels);
|
||||
|
||||
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);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
@@ -795,8 +795,11 @@ Model Model::read_from_file(const std::string&
|
||||
volume->imported_texture_uvs_per_face.clear();
|
||||
volume->imported_texture_uv_valid.clear();
|
||||
volume->imported_texture_rgba.clear();
|
||||
volume->imported_texture_raw_filament_offsets.clear();
|
||||
volume->imported_texture_width = 0;
|
||||
volume->imported_texture_height = 0;
|
||||
volume->imported_texture_raw_channels = 0;
|
||||
volume->imported_texture_raw_metadata_json.clear();
|
||||
bool has_imported_usable_uv_texture_data = false;
|
||||
|
||||
const size_t triangle_count = volume->mesh().its.indices.size();
|
||||
@@ -830,6 +833,9 @@ Model Model::read_from_file(const std::string&
|
||||
volume->imported_texture_width = atlas_width;
|
||||
volume->imported_texture_height = atlas_height;
|
||||
volume->imported_texture_rgba = std::move(atlas_rgba);
|
||||
volume->imported_texture_raw_filament_offsets.clear();
|
||||
volume->imported_texture_raw_channels = 0;
|
||||
volume->imported_texture_raw_metadata_json.clear();
|
||||
has_imported_usable_uv_texture_data = has_any_valid_uv_face;
|
||||
}
|
||||
}
|
||||
@@ -3357,6 +3363,7 @@ void ModelVolume::assign_new_unique_ids_recursive()
|
||||
imported_texture_uvs_per_face.set_new_unique_id();
|
||||
imported_texture_uv_valid.set_new_unique_id();
|
||||
imported_texture_rgba.set_new_unique_id();
|
||||
imported_texture_raw_filament_offsets.set_new_unique_id();
|
||||
fuzzy_skin_facets.set_new_unique_id();
|
||||
}
|
||||
|
||||
@@ -4857,8 +4864,11 @@ static bool model_volume_texture_mapping_data_matches(const ModelVolume &mv_old,
|
||||
model_volume_imported_vector_matches(mv_old.imported_texture_uvs_per_face, mv_new.imported_texture_uvs_per_face) &&
|
||||
model_volume_imported_vector_matches(mv_old.imported_texture_uv_valid, mv_new.imported_texture_uv_valid) &&
|
||||
model_volume_imported_vector_matches(mv_old.imported_texture_rgba, mv_new.imported_texture_rgba) &&
|
||||
model_volume_imported_vector_matches(mv_old.imported_texture_raw_filament_offsets, mv_new.imported_texture_raw_filament_offsets) &&
|
||||
mv_old.imported_texture_width == mv_new.imported_texture_width &&
|
||||
mv_old.imported_texture_height == mv_new.imported_texture_height;
|
||||
mv_old.imported_texture_height == mv_new.imported_texture_height &&
|
||||
mv_old.imported_texture_raw_channels == mv_new.imported_texture_raw_channels &&
|
||||
mv_old.imported_texture_raw_metadata_json == mv_new.imported_texture_raw_metadata_json;
|
||||
}
|
||||
|
||||
bool model_texture_mapping_color_data_changed(const ModelObject& mo, const ModelObject& mo_new)
|
||||
|
||||
@@ -1059,8 +1059,11 @@ public:
|
||||
ModelVolumeImportedVector<float> imported_texture_uvs_per_face;
|
||||
ModelVolumeImportedVector<uint8_t> imported_texture_uv_valid;
|
||||
ModelVolumeImportedVector<uint8_t> imported_texture_rgba;
|
||||
ModelVolumeImportedVector<uint8_t> imported_texture_raw_filament_offsets;
|
||||
uint32_t imported_texture_width{0};
|
||||
uint32_t imported_texture_height{0};
|
||||
uint32_t imported_texture_raw_channels{0};
|
||||
std::string imported_texture_raw_metadata_json;
|
||||
|
||||
// List of mesh facets painted for fuzzy skin.
|
||||
FacetsAnnotation fuzzy_skin_facets;
|
||||
@@ -1193,6 +1196,7 @@ public:
|
||||
this->imported_texture_uvs_per_face.set_new_unique_id();
|
||||
this->imported_texture_uv_valid.set_new_unique_id();
|
||||
this->imported_texture_rgba.set_new_unique_id();
|
||||
this->imported_texture_raw_filament_offsets.set_new_unique_id();
|
||||
this->fuzzy_skin_facets.set_new_unique_id();
|
||||
}
|
||||
|
||||
@@ -1307,8 +1311,11 @@ private:
|
||||
imported_texture_uvs_per_face(other.imported_texture_uvs_per_face),
|
||||
imported_texture_uv_valid(other.imported_texture_uv_valid),
|
||||
imported_texture_rgba(other.imported_texture_rgba),
|
||||
imported_texture_raw_filament_offsets(other.imported_texture_raw_filament_offsets),
|
||||
imported_texture_width(other.imported_texture_width),
|
||||
imported_texture_height(other.imported_texture_height),
|
||||
imported_texture_raw_channels(other.imported_texture_raw_channels),
|
||||
imported_texture_raw_metadata_json(other.imported_texture_raw_metadata_json),
|
||||
fuzzy_skin_facets(other.fuzzy_skin_facets), cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
|
||||
{
|
||||
assert(this->id().valid());
|
||||
@@ -1376,7 +1383,7 @@ private:
|
||||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
// Used for deserialization, therefore no IDs are allocated.
|
||||
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), texture_mapping_color_facets(-1), imported_vertex_colors_rgba(-1), imported_texture_uvs_per_face(-1), imported_texture_uv_valid(-1), imported_texture_rgba(-1), fuzzy_skin_facets(-1), object(nullptr) {
|
||||
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), texture_mapping_color_facets(-1), imported_vertex_colors_rgba(-1), imported_texture_uvs_per_face(-1), imported_texture_uv_valid(-1), imported_texture_rgba(-1), imported_texture_raw_filament_offsets(-1), fuzzy_skin_facets(-1), object(nullptr) {
|
||||
assert(this->id().invalid());
|
||||
assert(this->config.id().invalid());
|
||||
assert(this->supported_facets.id().invalid());
|
||||
@@ -1408,7 +1415,8 @@ private:
|
||||
cereal::load_by_value(ar, imported_texture_uvs_per_face);
|
||||
cereal::load_by_value(ar, imported_texture_uv_valid);
|
||||
cereal::load_by_value(ar, imported_texture_rgba);
|
||||
ar(imported_texture_width, imported_texture_height);
|
||||
cereal::load_by_value(ar, imported_texture_raw_filament_offsets);
|
||||
ar(imported_texture_width, imported_texture_height, imported_texture_raw_channels, imported_texture_raw_metadata_json);
|
||||
cereal::load_by_value(ar, fuzzy_skin_facets);
|
||||
mesh_changed |= t != fuzzy_skin_facets.timestamp();
|
||||
cereal::load_by_value(ar, config);
|
||||
@@ -1420,8 +1428,11 @@ private:
|
||||
imported_texture_uvs_per_face.clear();
|
||||
imported_texture_uv_valid.clear();
|
||||
imported_texture_rgba.clear();
|
||||
imported_texture_raw_filament_offsets.clear();
|
||||
imported_texture_width = 0;
|
||||
imported_texture_height = 0;
|
||||
imported_texture_raw_channels = 0;
|
||||
imported_texture_raw_metadata_json.clear();
|
||||
texture_mapping_color_facets.reset();
|
||||
}
|
||||
assert(m_mesh);
|
||||
@@ -1446,7 +1457,8 @@ private:
|
||||
cereal::save_by_value(ar, imported_texture_uvs_per_face);
|
||||
cereal::save_by_value(ar, imported_texture_uv_valid);
|
||||
cereal::save_by_value(ar, imported_texture_rgba);
|
||||
ar(imported_texture_width, imported_texture_height);
|
||||
cereal::save_by_value(ar, imported_texture_raw_filament_offsets);
|
||||
ar(imported_texture_width, imported_texture_height, imported_texture_raw_channels, imported_texture_raw_metadata_json);
|
||||
cereal::save_by_value(ar, fuzzy_skin_facets);
|
||||
cereal::save_by_value(ar, config);
|
||||
cereal::save(ar, text_configuration);
|
||||
|
||||
@@ -261,6 +261,20 @@ static int prime_tower_texture_mapping_color_mode_for_print(int prime_tower_colo
|
||||
}
|
||||
}
|
||||
|
||||
static bool model_volume_texture_mapping_data_equal_for_print_sharing(const ModelVolume &lhs, const ModelVolume &rhs)
|
||||
{
|
||||
return lhs.texture_mapping_color_facets.equals(rhs.texture_mapping_color_facets) &&
|
||||
lhs.imported_vertex_colors_rgba == rhs.imported_vertex_colors_rgba &&
|
||||
lhs.imported_texture_uvs_per_face == rhs.imported_texture_uvs_per_face &&
|
||||
lhs.imported_texture_uv_valid == rhs.imported_texture_uv_valid &&
|
||||
lhs.imported_texture_rgba == rhs.imported_texture_rgba &&
|
||||
lhs.imported_texture_raw_filament_offsets == rhs.imported_texture_raw_filament_offsets &&
|
||||
lhs.imported_texture_width == rhs.imported_texture_width &&
|
||||
lhs.imported_texture_height == rhs.imported_texture_height &&
|
||||
lhs.imported_texture_raw_channels == rhs.imported_texture_raw_channels &&
|
||||
lhs.imported_texture_raw_metadata_json == rhs.imported_texture_raw_metadata_json;
|
||||
}
|
||||
|
||||
template class PrintState<PrintStep, psCount>;
|
||||
template class PrintState<PrintObjectStep, posCount>;
|
||||
|
||||
@@ -2330,6 +2344,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
return false;
|
||||
if (!model_volume1.mmu_segmentation_facets.equals(model_volume2.mmu_segmentation_facets))
|
||||
return false;
|
||||
if (!model_volume_texture_mapping_data_equal_for_print_sharing(model_volume1, model_volume2))
|
||||
return false;
|
||||
if (!model_volume1.fuzzy_skin_facets.equals(model_volume2.fuzzy_skin_facets))
|
||||
return false;
|
||||
if (model_volume1.config.get() != model_volume2.config.get())
|
||||
|
||||
@@ -87,8 +87,11 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst,
|
||||
mv_dst.imported_texture_uvs_per_face = mv_src.imported_texture_uvs_per_face;
|
||||
mv_dst.imported_texture_uv_valid = mv_src.imported_texture_uv_valid;
|
||||
mv_dst.imported_texture_rgba = mv_src.imported_texture_rgba;
|
||||
mv_dst.imported_texture_raw_filament_offsets = mv_src.imported_texture_raw_filament_offsets;
|
||||
mv_dst.imported_texture_width = mv_src.imported_texture_width;
|
||||
mv_dst.imported_texture_height = mv_src.imported_texture_height;
|
||||
mv_dst.imported_texture_raw_channels = mv_src.imported_texture_raw_channels;
|
||||
mv_dst.imported_texture_raw_metadata_json = mv_src.imported_texture_raw_metadata_json;
|
||||
assert(mv_dst.fuzzy_skin_facets.id() == mv_src.fuzzy_skin_facets.id());
|
||||
mv_dst.fuzzy_skin_facets.assign(mv_src.fuzzy_skin_facets);
|
||||
//FIXME what to do with the materials?
|
||||
|
||||
@@ -2338,6 +2338,13 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("texture_mapping_background_color", coString);
|
||||
def->label = L("Texture mapping background color");
|
||||
def->tooltip = L("Background color used when texture mapping samples translucent color data.");
|
||||
def->gui_flags = "serialized";
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionString("#FFFFFFFF"));
|
||||
|
||||
def = this->add("texture_mapping_global_settings", coString);
|
||||
def->label = L("Texture mapping global settings");
|
||||
def->tooltip = L("Serialized global texture mapping settings.");
|
||||
|
||||
@@ -901,6 +901,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionPercent, elefant_foot_layers_density))
|
||||
((ConfigOptionFloat, max_bridge_length))
|
||||
((ConfigOptionFloatOrPercent, line_width))
|
||||
((ConfigOptionString, texture_mapping_background_color))
|
||||
// Force the generation of solid shells between adjacent materials/volumes.
|
||||
((ConfigOptionBool, interface_shells))
|
||||
((ConfigOptionFloat, layer_height))
|
||||
|
||||
@@ -840,18 +840,41 @@ bool TextureMappingManager::duplicate_zone(size_t zone_index,
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned int TextureMappingManager::find_image_texture_zone_id(size_t) const
|
||||
unsigned int TextureMappingManager::find_image_texture_zone_id(size_t,
|
||||
bool allow_raw_values,
|
||||
bool prefer_raw_values) const
|
||||
{
|
||||
auto selectable = [allow_raw_values](const TextureMappingZone &zone) {
|
||||
const bool raw_values = zone.texture_mapping_mode == int(TextureMappingZone::TextureMappingRawValues);
|
||||
return zone.enabled && !zone.deleted && zone.is_image_texture() && zone.zone_id != 0 && (allow_raw_values || !raw_values);
|
||||
};
|
||||
auto find_raw = [this, selectable](bool raw_values) {
|
||||
for (const TextureMappingZone &zone : m_zones)
|
||||
if (selectable(zone) &&
|
||||
(zone.texture_mapping_mode == int(TextureMappingZone::TextureMappingRawValues)) == raw_values)
|
||||
return zone.zone_id;
|
||||
return 0u;
|
||||
};
|
||||
|
||||
if (allow_raw_values && prefer_raw_values) {
|
||||
const unsigned int raw_id = find_raw(true);
|
||||
if (raw_id != 0)
|
||||
return raw_id;
|
||||
}
|
||||
|
||||
for (const TextureMappingZone &zone : m_zones)
|
||||
if (zone.enabled && !zone.deleted && zone.is_image_texture() && zone.zone_id != 0)
|
||||
if (selectable(zone))
|
||||
return zone.zone_id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int TextureMappingManager::ensure_image_texture_zone(size_t num_physical,
|
||||
const std::vector<std::string> &filament_colours)
|
||||
const std::vector<std::string> &filament_colours,
|
||||
bool allow_raw_values,
|
||||
bool prefer_raw_values)
|
||||
{
|
||||
if (unsigned int existing = find_image_texture_zone_id(num_physical); existing != 0)
|
||||
if (unsigned int existing = find_image_texture_zone_id(num_physical, allow_raw_values, prefer_raw_values);
|
||||
existing != 0)
|
||||
return existing;
|
||||
TextureMappingZone *zone = add_zone(num_physical, filament_colours, int(TextureMappingZone::ImageTexture));
|
||||
return zone != nullptr ? zone->zone_id : 0;
|
||||
|
||||
@@ -265,8 +265,13 @@ public:
|
||||
size_t num_physical,
|
||||
const std::vector<std::string> &filament_colours);
|
||||
|
||||
unsigned int find_image_texture_zone_id(size_t num_physical) const;
|
||||
unsigned int ensure_image_texture_zone(size_t num_physical, const std::vector<std::string> &filament_colours);
|
||||
unsigned int find_image_texture_zone_id(size_t num_physical,
|
||||
bool allow_raw_values = false,
|
||||
bool prefer_raw_values = false) const;
|
||||
unsigned int ensure_image_texture_zone(size_t num_physical,
|
||||
const std::vector<std::string> &filament_colours,
|
||||
bool allow_raw_values = false,
|
||||
bool prefer_raw_values = false);
|
||||
|
||||
std::string serialize_entries();
|
||||
void load_entries(const std::string &serialized, const std::vector<std::string> &filament_colours);
|
||||
|
||||
@@ -57,11 +57,14 @@ static wxString texture_mapping_menu_label(const TextureMappingZone &zone)
|
||||
if (zone.is_2d_gradient())
|
||||
return _L("Texture Mapping 2D Gradient");
|
||||
const std::string color_model = TextureMappingManager::filament_color_mode_name(zone.filament_color_mode);
|
||||
const bool raw_offset = zone.texture_mapping_mode == int(TextureMappingZone::TextureMappingRawValues);
|
||||
if (color_model == "any")
|
||||
return _L("Texture Mapping");
|
||||
return raw_offset ? _L("Texture Mapping Raw Offset") : _L("Texture Mapping");
|
||||
wxString color_model_text = from_u8(color_model);
|
||||
color_model_text.MakeUpper();
|
||||
return _L("Texture Mapping ") + color_model_text;
|
||||
return raw_offset ?
|
||||
_L("Texture Mapping Raw Offset ") + color_model_text :
|
||||
_L("Texture Mapping ") + color_model_text;
|
||||
}
|
||||
|
||||
static wxString filament_menu_item_name(int filament_id_1based)
|
||||
|
||||
@@ -35,11 +35,14 @@ static wxString texture_mapping_table_label(const TextureMappingZone &zone)
|
||||
if (zone.is_2d_gradient())
|
||||
return _L("Texture Mapping 2D Gradient");
|
||||
const std::string color_model = TextureMappingManager::filament_color_mode_name(zone.filament_color_mode);
|
||||
const bool raw_offset = zone.texture_mapping_mode == int(TextureMappingZone::TextureMappingRawValues);
|
||||
if (color_model == "any")
|
||||
return _L("Texture Mapping");
|
||||
return raw_offset ? _L("Raw Offset Texture Mapping Zone") : _L("Texture Mapping");
|
||||
wxString color_model_text = from_u8(color_model);
|
||||
color_model_text.MakeUpper();
|
||||
return _L("Texture Mapping ") + color_model_text;
|
||||
return raw_offset ?
|
||||
_L("Raw Offset Texture Mapping ") + color_model_text :
|
||||
_L("Texture Mapping ") + color_model_text;
|
||||
}
|
||||
|
||||
//min row count
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/TextureMapping.hpp"
|
||||
#include "libslic3r/filament_mixer.h"
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
#include "GLGizmoUtils.hpp"
|
||||
|
||||
@@ -31,7 +32,9 @@
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <wx/button.h>
|
||||
#include <wx/clrpicker.h>
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/image.h>
|
||||
@@ -165,7 +168,7 @@ static size_t display_filament_index_for_requested_id(const std::vector<unsigned
|
||||
return selected_it != display_filament_ids.end() ? size_t(std::distance(display_filament_ids.begin(), selected_it)) : 0;
|
||||
}
|
||||
|
||||
static unsigned int ensure_texture_mapping_zone()
|
||||
static unsigned int ensure_texture_mapping_zone(bool allow_raw_values = false, bool prefer_raw_values = false)
|
||||
{
|
||||
if (wxGetApp().preset_bundle == nullptr || wxGetApp().plater() == nullptr)
|
||||
return 0;
|
||||
@@ -175,7 +178,7 @@ static unsigned int ensure_texture_mapping_zone()
|
||||
std::vector<std::string> physical_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, false);
|
||||
physical_colors.resize(num_physical, "#26A69A");
|
||||
|
||||
if (unsigned int existing_id = mgr.find_image_texture_zone_id(num_physical); existing_id != 0) {
|
||||
if (unsigned int existing_id = mgr.find_image_texture_zone_id(num_physical, allow_raw_values, prefer_raw_values); existing_id != 0) {
|
||||
if (TextureMappingZone *zone = mgr.zone_from_id(existing_id);
|
||||
zone == nullptr || !TextureMappingManager::auto_adjust_texture_component_ids(*zone, num_physical, physical_colors)) {
|
||||
return existing_id;
|
||||
@@ -183,7 +186,7 @@ static unsigned int ensure_texture_mapping_zone()
|
||||
} else if (num_physical < 2) {
|
||||
return 0;
|
||||
} else {
|
||||
mgr.ensure_image_texture_zone(num_physical, physical_colors);
|
||||
mgr.ensure_image_texture_zone(num_physical, physical_colors, allow_raw_values, prefer_raw_values);
|
||||
}
|
||||
|
||||
const std::string texture_serialized = mgr.serialize_entries();
|
||||
@@ -205,7 +208,202 @@ static unsigned int ensure_texture_mapping_zone()
|
||||
if (wxGetApp().mainframe != nullptr)
|
||||
wxGetApp().mainframe->on_config_changed(print_cfg);
|
||||
|
||||
return mgr.find_image_texture_zone_id(num_physical);
|
||||
return mgr.find_image_texture_zone_id(num_physical, allow_raw_values, prefer_raw_values);
|
||||
}
|
||||
|
||||
static void persist_texture_mapping_zone_updates()
|
||||
{
|
||||
if (wxGetApp().preset_bundle == nullptr)
|
||||
return;
|
||||
|
||||
TextureMappingManager &mgr = wxGetApp().preset_bundle->texture_mapping_zones;
|
||||
const std::string texture_serialized = mgr.serialize_entries();
|
||||
DynamicPrintConfig *print_cfg = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
if (ConfigOptionString *opt = print_cfg->option<ConfigOptionString>("texture_mapping_definitions"))
|
||||
opt->value = texture_serialized;
|
||||
else
|
||||
print_cfg->set_key_value("texture_mapping_definitions", new ConfigOptionString(texture_serialized));
|
||||
|
||||
if (ConfigOptionString *opt = wxGetApp().preset_bundle->project_config.option<ConfigOptionString>("texture_mapping_definitions"))
|
||||
opt->value = texture_serialized;
|
||||
else
|
||||
wxGetApp().preset_bundle->project_config.set_key_value("texture_mapping_definitions", new ConfigOptionString(texture_serialized));
|
||||
|
||||
wxGetApp().sidebar().update_texture_mapping_panel(false);
|
||||
wxGetApp().sidebar().update_dynamic_filament_list();
|
||||
if (auto *print_tab = wxGetApp().get_tab(Preset::TYPE_PRINT))
|
||||
print_tab->update_dirty();
|
||||
if (wxGetApp().mainframe != nullptr)
|
||||
wxGetApp().mainframe->on_config_changed(print_cfg);
|
||||
}
|
||||
|
||||
static constexpr size_t MaxImageProjectionRawOffsetChannels = 4;
|
||||
|
||||
struct RawAtlasProjectionLayout
|
||||
{
|
||||
std::vector<ImageMapRawFilament> filaments;
|
||||
std::vector<std::string> channel_keys;
|
||||
std::vector<size_t> atlas_to_target_channel;
|
||||
};
|
||||
|
||||
static bool model_volume_has_raw_atlas_texture_data(const ModelVolume *volume)
|
||||
{
|
||||
if (volume == nullptr ||
|
||||
volume->imported_texture_width == 0 ||
|
||||
volume->imported_texture_height == 0 ||
|
||||
volume->imported_texture_raw_channels == 0 ||
|
||||
volume->imported_texture_raw_filament_offsets.empty())
|
||||
return false;
|
||||
return 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 bool add_raw_layout_channel(RawAtlasProjectionLayout &layout,
|
||||
const std::string &key,
|
||||
const ImageMapRawFilament &filament,
|
||||
std::string *error)
|
||||
{
|
||||
if (std::find(layout.channel_keys.begin(), layout.channel_keys.end(), key) != layout.channel_keys.end())
|
||||
return true;
|
||||
if (layout.channel_keys.size() >= MaxImageProjectionRawOffsetChannels) {
|
||||
if (error != nullptr) {
|
||||
*error = GUI::format("This raw filament offset atlas would require more than %1% raw offset channels on the selected object.",
|
||||
MaxImageProjectionRawOffsetChannels);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
layout.channel_keys.emplace_back(key);
|
||||
layout.filaments.emplace_back(filament);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool raw_channel_keys_are_unique(const std::vector<std::string> &keys)
|
||||
{
|
||||
for (size_t idx = 0; idx < keys.size(); ++idx)
|
||||
for (size_t other = idx + 1; other < keys.size(); ++other)
|
||||
if (keys[idx] == keys[other])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool raw_atlas_projection_layout_for_object(const ModelObject &object,
|
||||
const ImageMapRawFilamentOffsetAtlas &atlas,
|
||||
RawAtlasProjectionLayout &layout,
|
||||
std::string *error)
|
||||
{
|
||||
layout = {};
|
||||
if (!atlas.valid()) {
|
||||
if (error != nullptr)
|
||||
*error = "The selected raw filament offset atlas is invalid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const ModelVolume *volume : object.volumes) {
|
||||
if (volume == nullptr || !volume->is_model_part() || !model_volume_has_raw_atlas_texture_data(volume))
|
||||
continue;
|
||||
const std::vector<ImageMapRawFilament> volume_filaments =
|
||||
image_map_raw_filaments_from_metadata_json(volume->imported_texture_raw_metadata_json, volume->imported_texture_raw_channels);
|
||||
const std::vector<std::string> volume_keys = image_map_raw_filament_channel_keys(volume_filaments);
|
||||
if (!raw_channel_keys_are_unique(volume_keys)) {
|
||||
if (error != nullptr)
|
||||
*error = "The selected object's existing raw filament offset metadata has duplicate channels.";
|
||||
return false;
|
||||
}
|
||||
for (size_t channel = 0; channel < volume_keys.size(); ++channel)
|
||||
if (!add_raw_layout_channel(layout, volume_keys[channel], volume_filaments[channel], error))
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<ImageMapRawFilament> atlas_filaments =
|
||||
image_map_raw_filaments_for_channels(atlas.filaments, atlas.channels);
|
||||
const std::vector<std::string> atlas_keys = image_map_raw_filament_channel_keys(atlas_filaments);
|
||||
if (!raw_channel_keys_are_unique(atlas_keys)) {
|
||||
if (error != nullptr)
|
||||
*error = "The selected raw filament offset atlas has duplicate channels.";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t channel = 0; channel < atlas_keys.size(); ++channel)
|
||||
if (!add_raw_layout_channel(layout, atlas_keys[channel], atlas_filaments[channel], error))
|
||||
return false;
|
||||
|
||||
layout.atlas_to_target_channel.assign(atlas_keys.size(), size_t(-1));
|
||||
for (size_t atlas_channel = 0; atlas_channel < atlas_keys.size(); ++atlas_channel) {
|
||||
const auto it = std::find(layout.channel_keys.begin(), layout.channel_keys.end(), atlas_keys[atlas_channel]);
|
||||
if (it == layout.channel_keys.end()) {
|
||||
if (error != nullptr)
|
||||
*error = "The selected raw filament offset atlas is not compatible with the selected object.";
|
||||
return false;
|
||||
}
|
||||
layout.atlas_to_target_channel[atlas_channel] = size_t(std::distance(layout.channel_keys.begin(), it));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string raw_layout_metadata_json(uint32_t width, uint32_t height, const RawAtlasProjectionLayout &layout)
|
||||
{
|
||||
nlohmann::json root;
|
||||
root["format"] = "raw_filament_offset_atlas";
|
||||
root["image"] = {
|
||||
{ "width", width },
|
||||
{ "height", height },
|
||||
{ "channels", layout.filaments.size() }
|
||||
};
|
||||
root["filaments"] = nlohmann::json::array();
|
||||
for (size_t idx = 0; idx < layout.filaments.size(); ++idx) {
|
||||
const ImageMapRawFilament &filament = layout.filaments[idx];
|
||||
nlohmann::json entry;
|
||||
entry["slot"] = filament.slot != 0 ? filament.slot : unsigned(idx + 1);
|
||||
entry["color"] = filament.color.empty() ? "custom" : filament.color;
|
||||
if (!filament.hex.empty())
|
||||
entry["hex"] = filament.hex;
|
||||
root["filaments"].push_back(std::move(entry));
|
||||
}
|
||||
return root.dump();
|
||||
}
|
||||
|
||||
static void configure_texture_mapping_zone_for_raw_atlas(unsigned int texture_mapping_filament_id,
|
||||
const RawAtlasProjectionLayout &layout)
|
||||
{
|
||||
if (texture_mapping_filament_id == 0 || wxGetApp().preset_bundle == nullptr || layout.filaments.empty())
|
||||
return;
|
||||
|
||||
TextureMappingZone *zone = wxGetApp().preset_bundle->texture_mapping_zones.zone_from_id(texture_mapping_filament_id);
|
||||
if (zone == nullptr)
|
||||
return;
|
||||
|
||||
const unsigned int max_physical = unsigned(std::min<size_t>(size_t(std::max(wxGetApp().filaments_cnt(), 0)), 9));
|
||||
std::vector<unsigned int> ids;
|
||||
ids.reserve(layout.filaments.size());
|
||||
auto add_id = [&ids, max_physical, &layout](unsigned int id) {
|
||||
if (id == 0 ||
|
||||
id > max_physical ||
|
||||
ids.size() >= layout.filaments.size() ||
|
||||
std::find(ids.begin(), ids.end(), id) != ids.end())
|
||||
return;
|
||||
ids.emplace_back(id);
|
||||
};
|
||||
for (const ImageMapRawFilament &filament : layout.filaments)
|
||||
add_id(filament.slot);
|
||||
for (unsigned int id = 1; id <= max_physical && ids.size() < layout.filaments.size(); ++id)
|
||||
add_id(id);
|
||||
|
||||
std::string encoded;
|
||||
encoded.reserve(ids.size());
|
||||
for (const unsigned int id : ids)
|
||||
encoded.push_back(char('0' + id));
|
||||
|
||||
zone->texture_mapping_mode = int(TextureMappingZone::TextureMappingRawValues);
|
||||
zone->preview_simulate_colors = true;
|
||||
zone->auto_adjust_filament_selection = false;
|
||||
if (!encoded.empty()) {
|
||||
zone->component_ids = std::move(encoded);
|
||||
zone->component_a = ids.front();
|
||||
zone->component_b = ids.size() > 1 ? ids[1] : ids.front();
|
||||
}
|
||||
persist_texture_mapping_zone_updates();
|
||||
}
|
||||
|
||||
static bool model_volume_has_imported_image_texture_data(const ModelVolume *volume)
|
||||
@@ -594,12 +792,317 @@ static ColorRGBA rgb_metadata_background_color(const ColorFacetsAnnotation &anno
|
||||
return unpack_vertex_color_rgba_for_conversion(packed);
|
||||
}
|
||||
|
||||
static bool set_texture_mapping_background_config(ModelConfigObject &config, const ColorRGBA &color)
|
||||
{
|
||||
const uint32_t packed = pack_vertex_color_rgba(color);
|
||||
char buffer[16];
|
||||
std::snprintf(buffer,
|
||||
sizeof(buffer),
|
||||
"#%02X%02X%02X%02X",
|
||||
unsigned((packed >> 24) & 0xFFu),
|
||||
unsigned((packed >> 16) & 0xFFu),
|
||||
unsigned((packed >> 8) & 0xFFu),
|
||||
unsigned(packed & 0xFFu));
|
||||
const std::string value(buffer);
|
||||
if (const ConfigOptionString *opt = dynamic_cast<const ConfigOptionString *>(config.option("texture_mapping_background_color"));
|
||||
opt != nullptr && opt->value == value)
|
||||
return false;
|
||||
config.set("texture_mapping_background_color", value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool set_managed_color_data_background_color(ModelObject &object, const ColorRGBA &color)
|
||||
{
|
||||
ColorRGBA background = color;
|
||||
background.a(1.f);
|
||||
bool changed = set_texture_mapping_background_config(object.config, background);
|
||||
const std::string metadata = rgb_metadata_json(background);
|
||||
for (ModelVolume *volume : object.volumes) {
|
||||
if (volume == nullptr || !volume->is_model_part())
|
||||
continue;
|
||||
changed |= set_texture_mapping_background_config(volume->config, background);
|
||||
if (!volume->texture_mapping_color_facets.empty() && volume->texture_mapping_color_facets.metadata_json() != metadata) {
|
||||
volume->texture_mapping_color_facets.set_metadata_json(metadata);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
static wxColour wx_colour_from_color_rgba(const ColorRGBA &color)
|
||||
{
|
||||
auto to_u8 = [](float value) {
|
||||
return static_cast<unsigned char>(std::clamp(value, 0.f, 1.f) * 255.f + 0.5f);
|
||||
};
|
||||
return wxColour(to_u8(color.r()), to_u8(color.g()), to_u8(color.b()));
|
||||
}
|
||||
|
||||
static ColorRGBA color_rgba_from_wx_colour(const wxColour &color)
|
||||
{
|
||||
return ColorRGBA(float(color.Red()) / 255.f,
|
||||
float(color.Green()) / 255.f,
|
||||
float(color.Blue()) / 255.f,
|
||||
1.f);
|
||||
}
|
||||
|
||||
static ColorRGBA managed_color_data_background_color(const ModelObject *object)
|
||||
{
|
||||
if (object == nullptr)
|
||||
return ColorRGBA(1.f, 1.f, 1.f, 1.f);
|
||||
|
||||
auto read_config_color = [](const ModelConfigObject &config) -> std::optional<ColorRGBA> {
|
||||
if (!config.has("texture_mapping_background_color"))
|
||||
return std::nullopt;
|
||||
const ConfigOptionString *opt = dynamic_cast<const ConfigOptionString *>(config.option("texture_mapping_background_color"));
|
||||
if (opt == nullptr)
|
||||
return std::nullopt;
|
||||
const std::string &text = opt->value;
|
||||
const size_t hash_pos = text.find('#');
|
||||
const size_t start = hash_pos == std::string::npos ? 0 : hash_pos + 1;
|
||||
if (start + 6 > text.size())
|
||||
return std::nullopt;
|
||||
unsigned int values[3] = { 255, 255, 255 };
|
||||
for (size_t channel = 0; channel < 3; ++channel) {
|
||||
int value = 0;
|
||||
for (size_t digit = 0; digit < 2; ++digit) {
|
||||
const char ch = text[start + channel * 2 + digit];
|
||||
const int hex = ch >= '0' && ch <= '9' ? ch - '0' :
|
||||
ch >= 'a' && ch <= 'f' ? ch - 'a' + 10 :
|
||||
ch >= 'A' && ch <= 'F' ? ch - 'A' + 10 : -1;
|
||||
if (hex < 0)
|
||||
return std::nullopt;
|
||||
value = (value << 4) | hex;
|
||||
}
|
||||
values[channel] = unsigned(value);
|
||||
}
|
||||
return ColorRGBA(float(values[0]) / 255.f, float(values[1]) / 255.f, float(values[2]) / 255.f, 1.f);
|
||||
};
|
||||
|
||||
if (std::optional<ColorRGBA> color = read_config_color(object->config))
|
||||
return *color;
|
||||
for (const ModelVolume *volume : object->volumes)
|
||||
if (volume != nullptr && volume->is_model_part() && !volume->texture_mapping_color_facets.empty())
|
||||
return rgb_metadata_background_color(volume->texture_mapping_color_facets);
|
||||
return ColorRGBA(1.f, 1.f, 1.f, 1.f);
|
||||
}
|
||||
|
||||
static void refresh_imported_texture_storage(ModelVolume &volume)
|
||||
{
|
||||
std::vector<uint8_t> refreshed(volume.imported_texture_rgba.begin(), volume.imported_texture_rgba.end());
|
||||
volume.imported_texture_rgba.swap(refreshed);
|
||||
}
|
||||
|
||||
static void refresh_imported_texture_raw_storage(ModelVolume &volume)
|
||||
{
|
||||
std::vector<uint8_t> refreshed(volume.imported_texture_raw_filament_offsets.begin(),
|
||||
volume.imported_texture_raw_filament_offsets.end());
|
||||
volume.imported_texture_raw_filament_offsets.swap(refreshed);
|
||||
}
|
||||
|
||||
static void clear_imported_texture_raw_atlas(ModelVolume &volume)
|
||||
{
|
||||
volume.imported_texture_raw_filament_offsets.clear();
|
||||
volume.imported_texture_raw_channels = 0;
|
||||
volume.imported_texture_raw_metadata_json.clear();
|
||||
}
|
||||
|
||||
static ColorRGBA raw_filament_color_for_projection_preview(const ImageMapRawFilament &filament)
|
||||
{
|
||||
const std::string key = image_map_raw_filament_channel_key(filament, 0);
|
||||
if (key == "C")
|
||||
return ColorRGBA(0.f, 0.75f, 0.75f, 1.f);
|
||||
if (key == "M")
|
||||
return ColorRGBA(0.9f, 0.f, 0.75f, 1.f);
|
||||
if (key == "Y")
|
||||
return ColorRGBA(0.95f, 0.85f, 0.f, 1.f);
|
||||
if (key == "K")
|
||||
return ColorRGBA(0.05f, 0.05f, 0.05f, 1.f);
|
||||
if (key == "W")
|
||||
return ColorRGBA(1.f, 1.f, 1.f, 1.f);
|
||||
if (key == "R")
|
||||
return ColorRGBA(1.f, 0.f, 0.f, 1.f);
|
||||
if (key == "G")
|
||||
return ColorRGBA(0.f, 0.75f, 0.f, 1.f);
|
||||
if (key == "B")
|
||||
return ColorRGBA(0.f, 0.25f, 1.f, 1.f);
|
||||
|
||||
unsigned char rgba[4] = { 255, 255, 255, 255 };
|
||||
if (!filament.hex.empty())
|
||||
GUI::BitmapCache::parse_color4(filament.hex, rgba);
|
||||
return ColorRGBA(float(rgba[0]) / 255.f, float(rgba[1]) / 255.f, float(rgba[2]) / 255.f, 1.f);
|
||||
}
|
||||
|
||||
static ColorRGBA simulated_preview_color_from_raw_offsets(const std::vector<ColorRGBA> &filament_colors,
|
||||
const uint8_t *values,
|
||||
size_t value_count,
|
||||
uint8_t alpha)
|
||||
{
|
||||
if (values == nullptr || value_count == 0 || filament_colors.empty())
|
||||
return ColorRGBA(0.f, 0.f, 0.f, float(alpha) / 255.f);
|
||||
|
||||
bool has_base = false;
|
||||
float out_r = 0.f;
|
||||
float out_g = 0.f;
|
||||
float out_b = 0.f;
|
||||
float accumulated = 0.f;
|
||||
for (size_t idx = 0; idx < filament_colors.size() && idx < value_count; ++idx) {
|
||||
const float weight = std::clamp(float(values[idx]) / 255.f, 0.f, 1.f);
|
||||
if (weight <= EPSILON)
|
||||
continue;
|
||||
|
||||
if (!has_base) {
|
||||
out_r = filament_colors[idx].r();
|
||||
out_g = filament_colors[idx].g();
|
||||
out_b = filament_colors[idx].b();
|
||||
accumulated = weight;
|
||||
has_base = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const float t = weight / std::max(float(EPSILON), accumulated + weight);
|
||||
float mixed_r = out_r;
|
||||
float mixed_g = out_g;
|
||||
float mixed_b = out_b;
|
||||
Slic3r::filament_mixer_lerp_float(out_r,
|
||||
out_g,
|
||||
out_b,
|
||||
filament_colors[idx].r(),
|
||||
filament_colors[idx].g(),
|
||||
filament_colors[idx].b(),
|
||||
t,
|
||||
&mixed_r,
|
||||
&mixed_g,
|
||||
&mixed_b);
|
||||
out_r = std::clamp(mixed_r, 0.f, 1.f);
|
||||
out_g = std::clamp(mixed_g, 0.f, 1.f);
|
||||
out_b = std::clamp(mixed_b, 0.f, 1.f);
|
||||
accumulated += weight;
|
||||
}
|
||||
|
||||
if (!has_base)
|
||||
return ColorRGBA(0.f, 0.f, 0.f, float(alpha) / 255.f);
|
||||
return ColorRGBA(out_r, out_g, out_b, float(alpha) / 255.f);
|
||||
}
|
||||
|
||||
static ColorRGBA preview_color_from_raw_offsets(const std::vector<uint8_t> &values, uint8_t alpha)
|
||||
{
|
||||
if (values.empty())
|
||||
return ColorRGBA(0.f, 0.f, 0.f, float(alpha) / 255.f);
|
||||
if (values.size() == 1) {
|
||||
const float gray = float(values.front()) / 255.f;
|
||||
return ColorRGBA(gray, gray, gray, float(alpha) / 255.f);
|
||||
}
|
||||
return ColorRGBA(float(values[0]) / 255.f,
|
||||
float(values.size() > 1 ? values[1] : 0) / 255.f,
|
||||
float(values.size() > 2 ? values[2] : 0) / 255.f,
|
||||
float(alpha) / 255.f);
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> raw_offset_pixel_values(const ModelVolume &volume, uint32_t x, uint32_t y)
|
||||
{
|
||||
std::vector<uint8_t> values(size_t(volume.imported_texture_raw_channels), 0);
|
||||
if (volume.imported_texture_width == 0 || volume.imported_texture_raw_channels == 0)
|
||||
return values;
|
||||
const size_t idx =
|
||||
(size_t(y) * size_t(volume.imported_texture_width) + size_t(x)) *
|
||||
size_t(volume.imported_texture_raw_channels);
|
||||
if (idx + values.size() > volume.imported_texture_raw_filament_offsets.size())
|
||||
return values;
|
||||
std::copy(volume.imported_texture_raw_filament_offsets.begin() + idx,
|
||||
volume.imported_texture_raw_filament_offsets.begin() + idx + values.size(),
|
||||
values.begin());
|
||||
return values;
|
||||
}
|
||||
|
||||
static bool refresh_imported_texture_preview_from_raw_offsets(ModelVolume &volume)
|
||||
{
|
||||
if (!model_volume_has_raw_atlas_texture_data(&volume))
|
||||
return false;
|
||||
const size_t pixel_count = size_t(volume.imported_texture_width) * size_t(volume.imported_texture_height);
|
||||
bool changed = false;
|
||||
if (volume.imported_texture_rgba.size() != pixel_count * 4) {
|
||||
volume.imported_texture_rgba.assign(pixel_count * 4, 255);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> values(size_t(volume.imported_texture_raw_channels), 0);
|
||||
for (size_t pixel_idx = 0; pixel_idx < pixel_count; ++pixel_idx) {
|
||||
const size_t raw_idx = pixel_idx * size_t(volume.imported_texture_raw_channels);
|
||||
if (raw_idx + values.size() > volume.imported_texture_raw_filament_offsets.size())
|
||||
break;
|
||||
std::copy(volume.imported_texture_raw_filament_offsets.begin() + raw_idx,
|
||||
volume.imported_texture_raw_filament_offsets.begin() + raw_idx + values.size(),
|
||||
values.begin());
|
||||
const ColorRGBA preview = preview_color_from_raw_offsets(values, 255);
|
||||
const uint8_t r = uint8_t(std::clamp(preview.r(), 0.f, 1.f) * 255.f + 0.5f);
|
||||
const uint8_t g = uint8_t(std::clamp(preview.g(), 0.f, 1.f) * 255.f + 0.5f);
|
||||
const uint8_t b = uint8_t(std::clamp(preview.b(), 0.f, 1.f) * 255.f + 0.5f);
|
||||
const size_t rgba_idx = pixel_idx * 4;
|
||||
if (volume.imported_texture_rgba[rgba_idx + 0] != r ||
|
||||
volume.imported_texture_rgba[rgba_idx + 1] != g ||
|
||||
volume.imported_texture_rgba[rgba_idx + 2] != b ||
|
||||
volume.imported_texture_rgba[rgba_idx + 3] != 255) {
|
||||
volume.imported_texture_rgba[rgba_idx + 0] = r;
|
||||
volume.imported_texture_rgba[rgba_idx + 1] = g;
|
||||
volume.imported_texture_rgba[rgba_idx + 2] = b;
|
||||
volume.imported_texture_rgba[rgba_idx + 3] = 255;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
static bool merge_imported_texture_raw_atlas(ModelVolume &volume, const RawAtlasProjectionLayout &layout)
|
||||
{
|
||||
if (layout.filaments.empty() || volume.imported_texture_width == 0 || volume.imported_texture_height == 0)
|
||||
return false;
|
||||
const size_t expected_size =
|
||||
size_t(volume.imported_texture_width) *
|
||||
size_t(volume.imported_texture_height) *
|
||||
layout.filaments.size();
|
||||
std::vector<uint8_t> merged(expected_size, 0);
|
||||
if (model_volume_has_raw_atlas_texture_data(&volume)) {
|
||||
const std::vector<ImageMapRawFilament> old_filaments =
|
||||
image_map_raw_filaments_from_metadata_json(volume.imported_texture_raw_metadata_json, volume.imported_texture_raw_channels);
|
||||
const std::vector<std::string> old_keys = image_map_raw_filament_channel_keys(old_filaments);
|
||||
std::vector<size_t> old_to_new(size_t(volume.imported_texture_raw_channels), size_t(-1));
|
||||
for (size_t old_channel = 0; old_channel < old_keys.size() && old_channel < old_to_new.size(); ++old_channel) {
|
||||
const auto found = std::find(layout.channel_keys.begin(), layout.channel_keys.end(), old_keys[old_channel]);
|
||||
if (found != layout.channel_keys.end())
|
||||
old_to_new[old_channel] = size_t(std::distance(layout.channel_keys.begin(), found));
|
||||
}
|
||||
|
||||
const size_t pixel_count = size_t(volume.imported_texture_width) * size_t(volume.imported_texture_height);
|
||||
for (size_t pixel_idx = 0; pixel_idx < pixel_count; ++pixel_idx) {
|
||||
const size_t old_base = pixel_idx * size_t(volume.imported_texture_raw_channels);
|
||||
const size_t new_base = pixel_idx * layout.filaments.size();
|
||||
for (size_t old_channel = 0; old_channel < old_to_new.size(); ++old_channel) {
|
||||
const size_t new_channel = old_to_new[old_channel];
|
||||
if (new_channel != size_t(-1) &&
|
||||
old_base + old_channel < volume.imported_texture_raw_filament_offsets.size() &&
|
||||
new_base + new_channel < merged.size())
|
||||
merged[new_base + new_channel] = volume.imported_texture_raw_filament_offsets[old_base + old_channel];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::string metadata =
|
||||
raw_layout_metadata_json(volume.imported_texture_width, volume.imported_texture_height, layout);
|
||||
const uint32_t merged_channels = uint32_t(layout.filaments.size());
|
||||
const bool changed =
|
||||
volume.imported_texture_raw_channels != merged_channels ||
|
||||
volume.imported_texture_raw_filament_offsets.size() != expected_size ||
|
||||
volume.imported_texture_raw_metadata_json != metadata ||
|
||||
!std::equal(volume.imported_texture_raw_filament_offsets.begin(),
|
||||
volume.imported_texture_raw_filament_offsets.end(),
|
||||
merged.begin(),
|
||||
merged.end());
|
||||
volume.imported_texture_raw_channels = merged_channels;
|
||||
volume.imported_texture_raw_metadata_json = metadata;
|
||||
volume.imported_texture_raw_filament_offsets = std::move(merged);
|
||||
return changed;
|
||||
}
|
||||
|
||||
static std::optional<ColorRGBA> sample_rgb_color_facets(const std::vector<ColorFacetTriangle> &facets,
|
||||
const std::unordered_map<int, std::vector<size_t>> &facets_by_source_triangle,
|
||||
int source_triangle,
|
||||
@@ -1364,6 +1867,38 @@ static ColorRGBA sample_rgba_bilinear_clamped(const std::vector<uint8_t> &rgba,
|
||||
a);
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> sample_raw_offsets_bilinear_clamped(const ImageMapRawFilamentOffsetAtlas &atlas, float u, float v)
|
||||
{
|
||||
std::vector<uint8_t> values;
|
||||
if (!atlas.valid())
|
||||
return values;
|
||||
|
||||
values.assign(atlas.channels, 0);
|
||||
u = std::clamp(u, 0.f, 1.f);
|
||||
v = std::clamp(v, 0.f, 1.f);
|
||||
const float x = u * float(atlas.width > 1 ? atlas.width - 1 : 0);
|
||||
const float y = v * float(atlas.height > 1 ? atlas.height - 1 : 0);
|
||||
const size_t x0 = std::min<size_t>(size_t(std::floor(x)), size_t(atlas.width - 1));
|
||||
const size_t y0 = std::min<size_t>(size_t(std::floor(y)), size_t(atlas.height - 1));
|
||||
const size_t x1 = std::min<size_t>(x0 + 1, size_t(atlas.width - 1));
|
||||
const size_t y1 = std::min<size_t>(y0 + 1, size_t(atlas.height - 1));
|
||||
const float tx = x - float(x0);
|
||||
const float ty = y - float(y0);
|
||||
|
||||
auto channel = [&atlas](size_t sx, size_t sy, size_t ch) {
|
||||
return float(atlas.offsets[(sy * size_t(atlas.width) + sx) * size_t(atlas.channels) + ch]);
|
||||
};
|
||||
for (size_t ch = 0; ch < values.size(); ++ch) {
|
||||
const float c00 = channel(x0, y0, ch);
|
||||
const float c10 = channel(x1, y0, ch);
|
||||
const float c01 = channel(x0, y1, ch);
|
||||
const float c11 = channel(x1, y1, ch);
|
||||
const float value = (c00 + (c10 - c00) * tx) + ((c01 + (c11 - c01) * tx) - (c00 + (c10 - c00) * tx)) * ty;
|
||||
values[ch] = uint8_t(std::clamp(int(std::lround(value)), 0, 255));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
static ColorRGBA blend_projection_color(const ColorRGBA &base, const ColorRGBA &overlay, float opacity)
|
||||
{
|
||||
const float alpha = std::clamp(overlay.a(), 0.f, 1.f) * std::clamp(opacity, 0.f, 1.f);
|
||||
@@ -1491,6 +2026,28 @@ static std::optional<ColorRGBA> projected_image_color_at_point(const ProjectionC
|
||||
return sample_rgba_bilinear_clamped(*context.image_rgba, context.image_width, context.image_height, u, v);
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> projected_raw_offsets_at_point(const ProjectionContext &context,
|
||||
const ImageMapRawFilamentOffsetAtlas &atlas,
|
||||
const Transform3d &world_matrix,
|
||||
const Vec3f &point)
|
||||
{
|
||||
if (!atlas.valid() || context.overlay_width <= 0.f || context.overlay_height <= 0.f)
|
||||
return {};
|
||||
|
||||
Vec2f screen = Vec2f::Zero();
|
||||
if (!project_point_to_screen(context, world_matrix * point.cast<double>(), screen))
|
||||
return {};
|
||||
if (screen.x() < context.overlay_left ||
|
||||
screen.y() < context.overlay_top ||
|
||||
screen.x() > context.overlay_left + context.overlay_width ||
|
||||
screen.y() > context.overlay_top + context.overlay_height)
|
||||
return {};
|
||||
|
||||
const float u = (screen.x() - context.overlay_left) / context.overlay_width;
|
||||
const float v = (screen.y() - context.overlay_top) / context.overlay_height;
|
||||
return sample_raw_offsets_bilinear_clamped(atlas, u, v);
|
||||
}
|
||||
|
||||
static bool projection_triangle_intersects_overlay(const ProjectionContext &context,
|
||||
const Transform3d &world_matrix,
|
||||
const std::array<Vec3f, 3> &vertices)
|
||||
@@ -2170,6 +2727,7 @@ static bool initialize_generated_image_texture(ModelVolume &volum
|
||||
volume.imported_texture_width = GENERATED_IMAGE_TEXTURE_SIZE;
|
||||
volume.imported_texture_height = GENERATED_IMAGE_TEXTURE_SIZE;
|
||||
volume.imported_texture_rgba.assign(size_t(GENERATED_IMAGE_TEXTURE_SIZE) * size_t(GENERATED_IMAGE_TEXTURE_SIZE) * 4, 0);
|
||||
clear_imported_texture_raw_atlas(volume);
|
||||
for (size_t idx = 0; idx < size_t(GENERATED_IMAGE_TEXTURE_SIZE) * size_t(GENERATED_IMAGE_TEXTURE_SIZE); ++idx) {
|
||||
volume.imported_texture_rgba[idx * 4 + 0] = r;
|
||||
volume.imported_texture_rgba[idx * 4 + 1] = g;
|
||||
@@ -2218,6 +2776,28 @@ static bool write_rgba_pixel(std::vector<uint8_t> &rgba, uint32_t width, uint32_
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool write_raw_offset_pixel(std::vector<uint8_t> &offsets,
|
||||
uint32_t width,
|
||||
uint32_t channels,
|
||||
uint32_t x,
|
||||
uint32_t y,
|
||||
const std::vector<uint8_t> &values)
|
||||
{
|
||||
if (width == 0 || channels == 0 || values.empty())
|
||||
return false;
|
||||
const size_t idx = (size_t(y) * size_t(width) + size_t(x)) * size_t(channels);
|
||||
if (idx + size_t(channels) > offsets.size())
|
||||
return false;
|
||||
|
||||
bool changed = false;
|
||||
for (size_t channel = 0; channel < size_t(channels); ++channel) {
|
||||
const uint8_t value = channel < values.size() ? values[channel] : 0;
|
||||
changed = changed || offsets[idx + channel] != value;
|
||||
offsets[idx + channel] = value;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
static ColorRGBA read_rgba_pixel(const std::vector<uint8_t> &rgba, uint32_t width, uint32_t x, uint32_t y)
|
||||
{
|
||||
if (width == 0)
|
||||
@@ -3323,6 +3903,7 @@ static bool clear_object_managed_color_data(ModelObject &object, ManagedColorDat
|
||||
volume->imported_texture_uvs_per_face.clear();
|
||||
volume->imported_texture_uv_valid.clear();
|
||||
volume->imported_texture_rgba.clear();
|
||||
clear_imported_texture_raw_atlas(*volume);
|
||||
volume->imported_texture_width = 0;
|
||||
volume->imported_texture_height = 0;
|
||||
changed = true;
|
||||
@@ -4218,6 +4799,14 @@ public:
|
||||
, m_on_object_changed(std::move(on_object_changed))
|
||||
{
|
||||
wxBoxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
wxBoxSizer *background_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
background_sizer->Add(new wxStaticText(this, wxID_ANY, _L("Background color")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(8));
|
||||
m_background_picker = new wxColourPickerCtrl(this,
|
||||
wxID_ANY,
|
||||
wx_colour_from_color_rgba(managed_color_data_background_color(m_object)));
|
||||
background_sizer->Add(m_background_picker, 0, wxALIGN_CENTER_VERTICAL);
|
||||
main_sizer->Add(background_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, FromDIP(16));
|
||||
|
||||
wxFlexGridSizer *grid = new wxFlexGridSizer(5, 8, 14);
|
||||
grid->AddGrowableCol(2, 1);
|
||||
|
||||
@@ -4248,6 +4837,9 @@ public:
|
||||
CenterOnParent();
|
||||
|
||||
Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { EndModal(wxID_CLOSE); }, wxID_CLOSE);
|
||||
m_background_picker->Bind(wxEVT_COLOURPICKER_CHANGED, [this](wxColourPickerEvent &event) {
|
||||
set_background_color(event.GetColour());
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -4370,6 +4962,22 @@ private:
|
||||
refresh_rows();
|
||||
}
|
||||
|
||||
void set_background_color(const wxColour &color)
|
||||
{
|
||||
if (m_object == nullptr || !color.IsOk())
|
||||
return;
|
||||
|
||||
const ColorRGBA background = color_rgba_from_wx_colour(color);
|
||||
if (managed_color_data_background_color(m_object) == background)
|
||||
return;
|
||||
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Set color data background", UndoRedo::SnapshotType::GizmoAction);
|
||||
if (!set_managed_color_data_background_color(*m_object, background))
|
||||
return;
|
||||
|
||||
refresh_object_after_change();
|
||||
}
|
||||
|
||||
void notify_object_changed()
|
||||
{
|
||||
if (m_on_object_changed)
|
||||
@@ -4418,6 +5026,7 @@ private:
|
||||
GLCanvas3D &m_canvas;
|
||||
ModelObject *m_object = nullptr;
|
||||
std::function<void()> m_on_object_changed;
|
||||
wxColourPickerCtrl *m_background_picker = nullptr;
|
||||
std::vector<Row> m_rows;
|
||||
};
|
||||
|
||||
@@ -5542,6 +6151,7 @@ void GLGizmoMmuSegmentation::bake_selected_object_image_texture_to_vertex_colors
|
||||
volume->imported_texture_uvs_per_face.clear();
|
||||
volume->imported_texture_uv_valid.clear();
|
||||
volume->imported_texture_rgba.clear();
|
||||
clear_imported_texture_raw_atlas(*volume);
|
||||
volume->imported_texture_width = 0;
|
||||
volume->imported_texture_height = 0;
|
||||
baked = true;
|
||||
@@ -5914,6 +6524,7 @@ void GLGizmoMmuSegmentation::clear_selected_object_image_texture_data()
|
||||
volume->imported_texture_uvs_per_face.clear();
|
||||
volume->imported_texture_uv_valid.clear();
|
||||
volume->imported_texture_rgba.clear();
|
||||
clear_imported_texture_raw_atlas(*volume);
|
||||
volume->imported_texture_width = 0;
|
||||
volume->imported_texture_height = 0;
|
||||
cleared = true;
|
||||
@@ -7295,6 +7906,7 @@ CommonGizmosDataID GLGizmoImageProjection::on_get_requirements() const
|
||||
bool GLGizmoImageProjection::load_projection_image()
|
||||
{
|
||||
m_image_error.clear();
|
||||
m_raw_atlas = {};
|
||||
wxFileDialog dialog(wxGetApp().mainframe,
|
||||
_L("Load projection image"),
|
||||
"",
|
||||
@@ -7313,6 +7925,42 @@ bool GLGizmoImageProjection::load_projection_image()
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageMapRawFilamentOffsetAtlas raw_atlas;
|
||||
std::string raw_atlas_error;
|
||||
const bool loaded_raw_atlas =
|
||||
decode_image_map_raw_filament_offset_atlas(rgba, width, height, raw_atlas, &raw_atlas_error);
|
||||
if (loaded_raw_atlas) {
|
||||
ModelObject *object = selected_model_object();
|
||||
RawAtlasProjectionLayout raw_layout;
|
||||
if (object == nullptr || !raw_atlas_projection_layout_for_object(*object, raw_atlas, raw_layout, &raw_atlas_error)) {
|
||||
m_image_path.clear();
|
||||
m_image_rgba.clear();
|
||||
m_image_width = 0;
|
||||
m_image_height = 0;
|
||||
m_raw_atlas = {};
|
||||
m_overlay_texture.reset();
|
||||
m_overlay_texture_dirty = false;
|
||||
m_show_overlay = false;
|
||||
m_image_error = raw_atlas_error.empty() ?
|
||||
_u8L("The selected raw filament offset atlas is not compatible with the selected object.") :
|
||||
raw_atlas_error;
|
||||
m_parent.set_as_dirty();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> preview = image_map_raw_filament_offset_preview_rgba(raw_atlas);
|
||||
if (preview.empty()) {
|
||||
m_image_error = _u8L("Unable to preview the selected raw filament offset atlas.");
|
||||
return false;
|
||||
}
|
||||
rgba = std::move(preview);
|
||||
width = raw_atlas.width;
|
||||
height = raw_atlas.height;
|
||||
m_raw_atlas = std::move(raw_atlas);
|
||||
if (!projection_mode_allowed(m_projection_mode))
|
||||
m_projection_mode_initialized = false;
|
||||
}
|
||||
|
||||
m_image_path = into_u8(dialog.GetPath());
|
||||
m_image_rgba = std::move(rgba);
|
||||
m_image_width = width;
|
||||
@@ -7330,6 +7978,7 @@ void GLGizmoImageProjection::clear_projection_image()
|
||||
m_image_rgba.clear();
|
||||
m_image_width = 0;
|
||||
m_image_height = 0;
|
||||
m_raw_atlas = {};
|
||||
m_overlay_texture.reset();
|
||||
m_overlay_texture_dirty = false;
|
||||
m_show_overlay = false;
|
||||
@@ -7426,6 +8075,8 @@ void GLGizmoImageProjection::update_default_projection_mode()
|
||||
|
||||
GLGizmoImageProjection::ProjectionMode GLGizmoImageProjection::default_projection_mode() const
|
||||
{
|
||||
if (m_raw_atlas.valid())
|
||||
return ProjectionMode::ImageTexture;
|
||||
if (selected_object_has_rgb_data())
|
||||
return ProjectionMode::RGBData;
|
||||
return ProjectionMode::ImageTexture;
|
||||
@@ -7433,6 +8084,8 @@ GLGizmoImageProjection::ProjectionMode GLGizmoImageProjection::default_projectio
|
||||
|
||||
bool GLGizmoImageProjection::projection_mode_allowed(ProjectionMode mode) const
|
||||
{
|
||||
if (m_raw_atlas.valid())
|
||||
return mode == ProjectionMode::ImageTexture;
|
||||
if (mode == ProjectionMode::ImageTexture)
|
||||
return true;
|
||||
if (selected_object_has_rgb_data())
|
||||
@@ -7475,6 +8128,17 @@ bool GLGizmoImageProjection::selected_object_has_rgb_data() const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GLGizmoImageProjection::selected_object_has_raw_atlas_texture_data() const
|
||||
{
|
||||
const ModelObject *object = selected_model_object();
|
||||
if (object == nullptr)
|
||||
return false;
|
||||
for (const ModelVolume *volume : object->volumes)
|
||||
if (volume != nullptr && volume->is_model_part() && model_volume_has_raw_atlas_texture_data(volume))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void GLGizmoImageProjection::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
update_default_projection_mode();
|
||||
@@ -7540,10 +8204,10 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott
|
||||
if (ImGui::BeginCombo("##projection_mode", mode_labels[mode])) {
|
||||
for (int idx = 0; idx < 3; ++idx) {
|
||||
const ProjectionMode candidate = ProjectionMode(idx);
|
||||
if (!projection_mode_allowed(candidate))
|
||||
continue;
|
||||
const bool allowed = projection_mode_allowed(candidate);
|
||||
const bool selected = m_projection_mode == candidate;
|
||||
if (ImGui::Selectable(mode_labels[idx], selected)) {
|
||||
const ImGuiSelectableFlags flags = allowed ? ImGuiSelectableFlags_None : ImGuiSelectableFlags_Disabled;
|
||||
if (ImGui::Selectable(mode_labels[idx], selected, flags) && allowed) {
|
||||
mode = idx;
|
||||
m_projection_mode = candidate;
|
||||
}
|
||||
@@ -7586,6 +8250,26 @@ bool GLGizmoImageProjection::project_image_to_selected_object()
|
||||
if (object == nullptr || m_image_rgba.empty())
|
||||
return false;
|
||||
|
||||
RawAtlasProjectionLayout raw_layout;
|
||||
if (m_raw_atlas.valid()) {
|
||||
std::string raw_atlas_error;
|
||||
if (!raw_atlas_projection_layout_for_object(*object, m_raw_atlas, raw_layout, &raw_atlas_error)) {
|
||||
m_image_error = raw_atlas_error.empty() ?
|
||||
_u8L("The selected raw filament offset atlas is not compatible with the selected object.") :
|
||||
raw_atlas_error;
|
||||
m_image_path.clear();
|
||||
m_image_rgba.clear();
|
||||
m_image_width = 0;
|
||||
m_image_height = 0;
|
||||
m_raw_atlas = {};
|
||||
m_overlay_texture.reset();
|
||||
m_overlay_texture_dirty = false;
|
||||
m_show_overlay = false;
|
||||
m_parent.set_as_dirty();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
update_default_projection_mode();
|
||||
if (!projection_mode_allowed(m_projection_mode))
|
||||
return false;
|
||||
@@ -7607,8 +8291,21 @@ bool GLGizmoImageProjection::project_image_to_selected_object()
|
||||
if (!changed)
|
||||
return false;
|
||||
|
||||
unsigned int raw_texture_mapping_filament_id = 0;
|
||||
if (m_raw_atlas.valid()) {
|
||||
raw_texture_mapping_filament_id = ensure_texture_mapping_zone(true, true);
|
||||
if (raw_texture_mapping_filament_id != 0) {
|
||||
object->config.set("extruder", int(raw_texture_mapping_filament_id));
|
||||
for (ModelVolume *volume : object->volumes)
|
||||
if (volume != nullptr && volume->is_model_part())
|
||||
volume->config.set("extruder", int(raw_texture_mapping_filament_id));
|
||||
configure_texture_mapping_zone_for_raw_atlas(raw_texture_mapping_filament_id, raw_layout);
|
||||
}
|
||||
}
|
||||
|
||||
if (!object_is_whole_image_texture_mapped_without_regions(*object)) {
|
||||
const unsigned int texture_mapping_filament_id = ensure_texture_mapping_zone();
|
||||
const unsigned int texture_mapping_filament_id =
|
||||
raw_texture_mapping_filament_id != 0 ? raw_texture_mapping_filament_id : ensure_texture_mapping_zone();
|
||||
if (texture_mapping_filament_id != 0) {
|
||||
const Selection &selection = m_parent.get_selection();
|
||||
const int instance_idx = selection.get_instance_idx();
|
||||
@@ -7803,6 +8500,17 @@ bool GLGizmoImageProjection::project_to_image_texture(ModelObject *object)
|
||||
const ProjectionVisibility visibility = m_pass_through_model ?
|
||||
ProjectionVisibility() :
|
||||
build_projection_visibility(context, m_parent, object, instance_idx);
|
||||
const bool raw_atlas_projection = m_raw_atlas.valid();
|
||||
RawAtlasProjectionLayout raw_layout;
|
||||
if (raw_atlas_projection) {
|
||||
std::string raw_atlas_error;
|
||||
if (!raw_atlas_projection_layout_for_object(*object, m_raw_atlas, raw_layout, &raw_atlas_error)) {
|
||||
m_image_error = raw_atlas_error.empty() ?
|
||||
_u8L("The selected raw filament offset atlas is not compatible with the selected object.") :
|
||||
raw_atlas_error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
for (size_t volume_idx = 0; volume_idx < object->volumes.size(); ++volume_idx) {
|
||||
@@ -7823,10 +8531,17 @@ bool GLGizmoImageProjection::project_to_image_texture(ModelObject *object)
|
||||
continue;
|
||||
}
|
||||
|
||||
bool volume_changed = generated_texture;
|
||||
if (raw_atlas_projection) {
|
||||
volume_changed |= merge_imported_texture_raw_atlas(*volume, raw_layout);
|
||||
volume_changed |= refresh_imported_texture_preview_from_raw_offsets(*volume);
|
||||
} else if (model_volume_has_raw_atlas_texture_data(volume)) {
|
||||
clear_imported_texture_raw_atlas(*volume);
|
||||
volume_changed = true;
|
||||
}
|
||||
const VolumeColorSource source = build_volume_color_source(*volume);
|
||||
const bool rewrite_texture_base = generated_texture || !volume->texture_mapping_color_facets.empty();
|
||||
const std::vector<uint8_t> source_texture_rgba(volume->imported_texture_rgba.begin(), volume->imported_texture_rgba.end());
|
||||
bool volume_changed = generated_texture;
|
||||
|
||||
for (size_t tri_idx = 0; tri_idx < its.indices.size(); ++tri_idx) {
|
||||
const stl_triangle_vertex_indices &tri = its.indices[tri_idx];
|
||||
@@ -7930,7 +8645,39 @@ bool GLGizmoImageProjection::project_to_image_texture(ModelObject *object)
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
color = apply_projection_color(color, *projected, context, true);
|
||||
if (raw_atlas_projection) {
|
||||
std::vector<uint8_t> atlas_raw_values =
|
||||
projected_raw_offsets_at_point(context, m_raw_atlas, world_matrix, point);
|
||||
if (atlas_raw_values.empty())
|
||||
continue;
|
||||
const uint32_t wrapped_x = wrapped_texture_pixel(x_px, volume->imported_texture_width);
|
||||
const uint32_t wrapped_y = wrapped_texture_pixel(y_px, volume->imported_texture_height);
|
||||
std::vector<uint8_t> raw_values = raw_offset_pixel_values(*volume, wrapped_x, wrapped_y);
|
||||
if (raw_values.size() != size_t(volume->imported_texture_raw_channels))
|
||||
raw_values.assign(size_t(volume->imported_texture_raw_channels), 0);
|
||||
const float alpha = projection_overlay_alpha(*projected, context);
|
||||
for (size_t atlas_channel = 0;
|
||||
atlas_channel < atlas_raw_values.size() &&
|
||||
atlas_channel < raw_layout.atlas_to_target_channel.size();
|
||||
++atlas_channel) {
|
||||
const size_t target_channel = raw_layout.atlas_to_target_channel[atlas_channel];
|
||||
if (target_channel >= raw_values.size())
|
||||
continue;
|
||||
const float base = float(raw_values[target_channel]);
|
||||
const float projected_value = float(atlas_raw_values[atlas_channel]);
|
||||
raw_values[target_channel] = uint8_t(std::clamp(
|
||||
int(std::lround(base * (1.f - alpha) + projected_value * alpha)), 0, 255));
|
||||
}
|
||||
volume_changed |= write_raw_offset_pixel(volume->imported_texture_raw_filament_offsets,
|
||||
volume->imported_texture_width,
|
||||
volume->imported_texture_raw_channels,
|
||||
wrapped_x,
|
||||
wrapped_y,
|
||||
raw_values);
|
||||
color = preview_color_from_raw_offsets(raw_values, 255);
|
||||
} else {
|
||||
color = apply_projection_color(color, *projected, context, true);
|
||||
}
|
||||
}
|
||||
} else if (!rewrite_texture_base) {
|
||||
continue;
|
||||
@@ -7947,6 +8694,8 @@ bool GLGizmoImageProjection::project_to_image_texture(ModelObject *object)
|
||||
}
|
||||
if (volume_changed) {
|
||||
refresh_imported_texture_storage(*volume);
|
||||
if (raw_atlas_projection)
|
||||
refresh_imported_texture_raw_storage(*volume);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "GLGizmoPainterBase.hpp"
|
||||
|
||||
#include "libslic3r/ImageMapRawFilamentOffsetAtlas.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -336,6 +338,7 @@ private:
|
||||
bool selected_object_has_image_texture_data() const;
|
||||
bool selected_object_has_vertex_color_data() const;
|
||||
bool selected_object_has_rgb_data() const;
|
||||
bool selected_object_has_raw_atlas_texture_data() const;
|
||||
bool project_image_to_selected_object();
|
||||
bool project_to_vertex_colors(ModelObject *object);
|
||||
bool project_to_image_texture(ModelObject *object);
|
||||
@@ -350,6 +353,7 @@ private:
|
||||
std::vector<uint8_t> m_image_rgba;
|
||||
uint32_t m_image_width = 0;
|
||||
uint32_t m_image_height = 0;
|
||||
ImageMapRawFilamentOffsetAtlas m_raw_atlas;
|
||||
GLTexture m_overlay_texture;
|
||||
bool m_overlay_texture_dirty = false;
|
||||
bool m_show_overlay = true;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/ImageMapRawFilamentOffsetAtlas.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/TextureMapping.hpp"
|
||||
#include "libslic3r/filament_mixer.h"
|
||||
@@ -36,6 +37,7 @@ constexpr float k_polygon_offset_units = -1.f;
|
||||
constexpr float k_epsilon = 1e-6f;
|
||||
constexpr unsigned int k_simulated_texture_preview_max_edge = 1024;
|
||||
constexpr size_t k_simulated_texture_preview_max_pixels = 1024ull * 1024ull;
|
||||
constexpr const char *TEXTURE_MAPPING_BACKGROUND_COLOR_CONFIG_KEY = "texture_mapping_background_color";
|
||||
|
||||
struct TexturePreviewMixCandidate
|
||||
{
|
||||
@@ -322,10 +324,119 @@ unsigned char to_u8(float value)
|
||||
return static_cast<unsigned char>(clamp01(value) * 255.f + 0.5f);
|
||||
}
|
||||
|
||||
void make_texture_preview_rgba_opaque(std::vector<unsigned char> &rgba)
|
||||
int texture_mapping_color_hex_digit_for_preview(char ch)
|
||||
{
|
||||
for (size_t idx = 3; idx < rgba.size(); idx += 4)
|
||||
rgba[idx] = 255;
|
||||
return ch >= '0' && ch <= '9' ? ch - '0' :
|
||||
ch >= 'a' && ch <= 'f' ? ch - 'a' + 10 :
|
||||
ch >= 'A' && ch <= 'F' ? ch - 'A' + 10 : -1;
|
||||
}
|
||||
|
||||
std::optional<ColorRGBA> parse_texture_mapping_color_hex_for_preview(const std::string &text)
|
||||
{
|
||||
if (text.empty())
|
||||
return std::nullopt;
|
||||
|
||||
const size_t hash_pos = text.find('#');
|
||||
const size_t start = hash_pos == std::string::npos ? 0 : hash_pos + 1;
|
||||
if (start + 6 > text.size())
|
||||
return std::nullopt;
|
||||
|
||||
uint32_t packed = 0;
|
||||
for (size_t idx = 0; idx < 6; ++idx) {
|
||||
const int value = texture_mapping_color_hex_digit_for_preview(text[start + idx]);
|
||||
if (value < 0)
|
||||
return std::nullopt;
|
||||
packed = (packed << 4) | uint32_t(value);
|
||||
}
|
||||
|
||||
uint32_t alpha = 255;
|
||||
if (start + 8 <= text.size()) {
|
||||
alpha = 0;
|
||||
for (size_t idx = 6; idx < 8; ++idx) {
|
||||
const int value = texture_mapping_color_hex_digit_for_preview(text[start + idx]);
|
||||
if (value < 0)
|
||||
return std::nullopt;
|
||||
alpha = (alpha << 4) | uint32_t(value);
|
||||
}
|
||||
}
|
||||
|
||||
return ColorRGBA(float((packed >> 16) & 0xFFu) / 255.f,
|
||||
float((packed >> 8) & 0xFFu) / 255.f,
|
||||
float(packed & 0xFFu) / 255.f,
|
||||
float(alpha & 0xFFu) / 255.f);
|
||||
}
|
||||
|
||||
ColorRGBA opaque_texture_mapping_background_color_for_preview(ColorRGBA color)
|
||||
{
|
||||
color.a(1.f);
|
||||
return color;
|
||||
}
|
||||
|
||||
std::optional<ColorRGBA> texture_mapping_background_color_from_config_for_preview(const ModelConfigObject &config)
|
||||
{
|
||||
if (!config.has(TEXTURE_MAPPING_BACKGROUND_COLOR_CONFIG_KEY))
|
||||
return std::nullopt;
|
||||
|
||||
const ConfigOptionString *opt = dynamic_cast<const ConfigOptionString *>(config.option(TEXTURE_MAPPING_BACKGROUND_COLOR_CONFIG_KEY));
|
||||
if (opt == nullptr)
|
||||
return std::nullopt;
|
||||
|
||||
const std::optional<ColorRGBA> color = parse_texture_mapping_color_hex_for_preview(opt->value);
|
||||
return color ? std::optional<ColorRGBA>(opaque_texture_mapping_background_color_for_preview(*color)) : std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<ColorRGBA> texture_mapping_background_color_from_metadata_for_preview(const ColorFacetsAnnotation &annotation)
|
||||
{
|
||||
const std::string &metadata = annotation.metadata_json();
|
||||
const std::string key = "\"background_color\":\"#";
|
||||
const size_t start = metadata.find(key);
|
||||
if (start == std::string::npos || start + key.size() + 8 > metadata.size())
|
||||
return std::nullopt;
|
||||
|
||||
const std::optional<ColorRGBA> color = parse_texture_mapping_color_hex_for_preview(metadata.substr(start + key.size() - 1, 9));
|
||||
return color ? std::optional<ColorRGBA>(opaque_texture_mapping_background_color_for_preview(*color)) : std::nullopt;
|
||||
}
|
||||
|
||||
ColorRGBA texture_mapping_background_color_for_preview(const ModelVolume &model_volume,
|
||||
const ColorFacetsAnnotation *color_source = nullptr)
|
||||
{
|
||||
if (std::optional<ColorRGBA> color = texture_mapping_background_color_from_config_for_preview(model_volume.config))
|
||||
return *color;
|
||||
if (model_volume.get_object() != nullptr) {
|
||||
if (std::optional<ColorRGBA> color = texture_mapping_background_color_from_config_for_preview(model_volume.get_object()->config))
|
||||
return *color;
|
||||
}
|
||||
if (color_source != nullptr) {
|
||||
if (std::optional<ColorRGBA> color = texture_mapping_background_color_from_metadata_for_preview(*color_source))
|
||||
return *color;
|
||||
}
|
||||
if (std::optional<ColorRGBA> color = texture_mapping_background_color_from_metadata_for_preview(model_volume.texture_mapping_color_facets))
|
||||
return *color;
|
||||
return ColorRGBA(1.f, 1.f, 1.f, 1.f);
|
||||
}
|
||||
|
||||
ColorRGBA composite_texture_mapping_color_over_background_for_preview(const ColorRGBA &color, const ColorRGBA &background)
|
||||
{
|
||||
const float alpha = clamp01(color.a());
|
||||
return ColorRGBA(clamp01(color.r() * alpha + background.r() * (1.f - alpha)),
|
||||
clamp01(color.g() * alpha + background.g() * (1.f - alpha)),
|
||||
clamp01(color.b() * alpha + background.b() * (1.f - alpha)),
|
||||
1.f);
|
||||
}
|
||||
|
||||
void composite_texture_preview_rgba_over_background(std::vector<unsigned char> &rgba, const ColorRGBA &background)
|
||||
{
|
||||
for (size_t idx = 0; idx + 3 < rgba.size(); idx += 4) {
|
||||
const ColorRGBA color(float(rgba[idx + 0]) / 255.f,
|
||||
float(rgba[idx + 1]) / 255.f,
|
||||
float(rgba[idx + 2]) / 255.f,
|
||||
float(rgba[idx + 3]) / 255.f);
|
||||
const ColorRGBA blended = composite_texture_mapping_color_over_background_for_preview(color, background);
|
||||
rgba[idx + 0] = to_u8(blended.r());
|
||||
rgba[idx + 1] = to_u8(blended.g());
|
||||
rgba[idx + 2] = to_u8(blended.b());
|
||||
rgba[idx + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
void configure_texture_preview_sampler(const GUI::GLTexture &texture)
|
||||
@@ -374,13 +485,13 @@ std::array<unsigned int, 2> limited_simulated_texture_preview_size(unsigned int
|
||||
return { limited_width, limited_height };
|
||||
}
|
||||
|
||||
std::array<unsigned char, 3> sample_texture_preview_rgb_bilinear(const std::vector<unsigned char> &rgba,
|
||||
unsigned int width,
|
||||
unsigned int height,
|
||||
unsigned int preview_x,
|
||||
unsigned int preview_y,
|
||||
unsigned int preview_width,
|
||||
unsigned int preview_height)
|
||||
std::array<unsigned char, 4> sample_texture_preview_rgba_bilinear(const std::vector<unsigned char> &rgba,
|
||||
unsigned int width,
|
||||
unsigned int height,
|
||||
unsigned int preview_x,
|
||||
unsigned int preview_y,
|
||||
unsigned int preview_width,
|
||||
unsigned int preview_height)
|
||||
{
|
||||
const double src_x = std::clamp((double(preview_x) + 0.5) * double(width) / double(std::max(1u, preview_width)) - 0.5,
|
||||
0.0,
|
||||
@@ -404,7 +515,150 @@ std::array<unsigned char, 3> sample_texture_preview_rgb_bilinear(const std::vect
|
||||
return static_cast<unsigned char>(std::clamp(int(std::lround(top * (1.0 - ty) + bottom * ty)), 0, 255));
|
||||
};
|
||||
|
||||
return { sample_channel(0), sample_channel(1), sample_channel(2) };
|
||||
return { sample_channel(0), sample_channel(1), sample_channel(2), sample_channel(3) };
|
||||
}
|
||||
|
||||
std::vector<float> sample_texture_preview_raw_offsets_bilinear(const std::vector<unsigned char> &offsets,
|
||||
unsigned int width,
|
||||
unsigned int height,
|
||||
unsigned int channels,
|
||||
unsigned int preview_x,
|
||||
unsigned int preview_y,
|
||||
unsigned int preview_width,
|
||||
unsigned int preview_height)
|
||||
{
|
||||
std::vector<float> values(channels, 0.f);
|
||||
if (width == 0 || height == 0 || channels == 0 ||
|
||||
offsets.size() < size_t(width) * size_t(height) * size_t(channels))
|
||||
return values;
|
||||
|
||||
const double src_x = std::clamp((double(preview_x) + 0.5) * double(width) / double(std::max(1u, preview_width)) - 0.5,
|
||||
0.0,
|
||||
double(width - 1));
|
||||
const double src_y = std::clamp((double(preview_y) + 0.5) * double(height) / double(std::max(1u, preview_height)) - 0.5,
|
||||
0.0,
|
||||
double(height - 1));
|
||||
const unsigned int x0 = std::min(width - 1, unsigned(std::floor(src_x)));
|
||||
const unsigned int y0 = std::min(height - 1, unsigned(std::floor(src_y)));
|
||||
const unsigned int x1 = std::min(width - 1, x0 + 1);
|
||||
const unsigned int y1 = std::min(height - 1, y0 + 1);
|
||||
const double tx = src_x - double(x0);
|
||||
const double ty = src_y - double(y0);
|
||||
|
||||
auto channel_at = [&offsets, width, channels](unsigned int x, unsigned int y, unsigned int channel) {
|
||||
return double(offsets[(size_t(y) * size_t(width) + size_t(x)) * size_t(channels) + size_t(channel)]) / 255.0;
|
||||
};
|
||||
for (unsigned int channel = 0; channel < channels; ++channel) {
|
||||
const double top = channel_at(x0, y0, channel) * (1.0 - tx) + channel_at(x1, y0, channel) * tx;
|
||||
const double bottom = channel_at(x0, y1, channel) * (1.0 - tx) + channel_at(x1, y1, channel) * tx;
|
||||
values[size_t(channel)] = clamp01(float(top * (1.0 - ty) + bottom * ty));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::vector<std::string> raw_filament_color_mode_channel_keys_for_texture_preview(int filament_color_mode, size_t component_count)
|
||||
{
|
||||
std::vector<std::string> keys;
|
||||
switch (std::clamp(filament_color_mode,
|
||||
int(TextureMappingZone::FilamentColorAny),
|
||||
int(TextureMappingZone::FilamentColorBW))) {
|
||||
case int(TextureMappingZone::FilamentColorRGB):
|
||||
keys = { "R", "G", "B" };
|
||||
break;
|
||||
case int(TextureMappingZone::FilamentColorCMY):
|
||||
keys = { "C", "M", "Y" };
|
||||
break;
|
||||
case int(TextureMappingZone::FilamentColorCMYK):
|
||||
keys = { "C", "M", "Y", "K" };
|
||||
break;
|
||||
case int(TextureMappingZone::FilamentColorCMYW):
|
||||
keys = { "C", "M", "Y", "W" };
|
||||
break;
|
||||
case int(TextureMappingZone::FilamentColorRGBK):
|
||||
keys = { "R", "G", "B", "K" };
|
||||
break;
|
||||
case int(TextureMappingZone::FilamentColorRGBW):
|
||||
keys = { "R", "G", "B", "W" };
|
||||
break;
|
||||
case int(TextureMappingZone::FilamentColorBW):
|
||||
keys = { "K", "W" };
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (keys.size() > component_count)
|
||||
keys.resize(component_count);
|
||||
return keys;
|
||||
}
|
||||
|
||||
std::vector<size_t> raw_component_source_channels_for_texture_preview(const std::string &metadata_json,
|
||||
unsigned int source_channels,
|
||||
int filament_color_mode,
|
||||
size_t component_count)
|
||||
{
|
||||
if (source_channels == 0 || component_count == 0)
|
||||
return {};
|
||||
|
||||
const size_t sentinel = std::numeric_limits<size_t>::max();
|
||||
std::vector<size_t> mapping(component_count, sentinel);
|
||||
const std::vector<ImageMapRawFilament> filaments =
|
||||
image_map_raw_filaments_from_metadata_json(metadata_json, source_channels);
|
||||
if (filaments.size() != size_t(source_channels))
|
||||
return {};
|
||||
|
||||
std::vector<std::string> source_keys(static_cast<size_t>(source_channels));
|
||||
std::vector<uint8_t> used(static_cast<size_t>(source_channels), 0);
|
||||
for (size_t channel = 0; channel < filaments.size(); ++channel) {
|
||||
const std::string key = image_map_raw_filament_channel_key(filaments[channel], channel);
|
||||
if (key.size() == 1 && image_map_raw_filament_is_standard_color(key))
|
||||
source_keys[channel] = key;
|
||||
}
|
||||
|
||||
const std::vector<std::string> target_keys =
|
||||
raw_filament_color_mode_channel_keys_for_texture_preview(filament_color_mode, component_count);
|
||||
if (!target_keys.empty()) {
|
||||
for (size_t component_idx = 0; component_idx < target_keys.size(); ++component_idx) {
|
||||
for (size_t channel = 0; channel < source_keys.size(); ++channel) {
|
||||
if (used[channel] == 0 && source_keys[channel] == target_keys[component_idx]) {
|
||||
mapping[component_idx] = channel;
|
||||
used[channel] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t next_source = 0;
|
||||
for (size_t component_idx = 0; component_idx < mapping.size(); ++component_idx) {
|
||||
if (mapping[component_idx] != sentinel)
|
||||
continue;
|
||||
while (next_source < source_keys.size() &&
|
||||
(used[next_source] != 0 || (!target_keys.empty() && !source_keys[next_source].empty())))
|
||||
++next_source;
|
||||
if (next_source >= source_keys.size())
|
||||
continue;
|
||||
mapping[component_idx] = next_source;
|
||||
used[next_source] = 1;
|
||||
++next_source;
|
||||
}
|
||||
|
||||
const bool has_mapping = std::any_of(mapping.begin(), mapping.end(), [sentinel](size_t value) { return value != sentinel; });
|
||||
return has_mapping ? mapping : std::vector<size_t>{};
|
||||
}
|
||||
|
||||
std::vector<float> map_raw_sample_to_components_for_texture_preview(const std::vector<float> &raw_sample,
|
||||
const std::vector<size_t> &component_source_channels)
|
||||
{
|
||||
if (component_source_channels.empty())
|
||||
return {};
|
||||
const size_t sentinel = std::numeric_limits<size_t>::max();
|
||||
std::vector<float> mapped(component_source_channels.size(), 0.f);
|
||||
for (size_t component_idx = 0; component_idx < component_source_channels.size(); ++component_idx) {
|
||||
const size_t source_channel = component_source_channels[component_idx];
|
||||
if (source_channel != sentinel && source_channel < raw_sample.size())
|
||||
mapped[component_idx] = raw_sample[source_channel];
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
|
||||
unsigned int texture_preview_rgb_cache_key(const std::array<unsigned char, 3> &rgb, bool quantize)
|
||||
@@ -1302,6 +1556,10 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig
|
||||
unsigned int width,
|
||||
unsigned int height,
|
||||
std::vector<unsigned char> source_rgba,
|
||||
std::vector<unsigned char> source_raw_offsets,
|
||||
unsigned int source_raw_channels,
|
||||
std::vector<size_t> source_raw_component_channels,
|
||||
ColorRGBA background_color,
|
||||
TexturePreviewSimulationSettings settings)
|
||||
{
|
||||
TexturePreviewSimulationResult result;
|
||||
@@ -1320,6 +1578,10 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig
|
||||
|
||||
prepare_texture_preview_simulation_settings(settings);
|
||||
const bool use_generic_solver = !settings.generic_mix_candidates.empty();
|
||||
const bool use_raw_offsets =
|
||||
settings.mapping_mode == int(TextureMappingZone::TextureMappingRawValues) &&
|
||||
source_raw_component_channels.size() == settings.component_colors.size() &&
|
||||
source_raw_offsets.size() >= size_t(width) * size_t(height) * size_t(source_raw_channels);
|
||||
|
||||
std::unordered_map<unsigned int, std::array<unsigned char, 4>> simulated_color_cache;
|
||||
simulated_color_cache.reserve(std::min(size_t(result.width) * size_t(result.height),
|
||||
@@ -1327,13 +1589,26 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig
|
||||
|
||||
for (unsigned int y = 0; y < result.height; ++y) {
|
||||
for (unsigned int x = 0; x < result.width; ++x) {
|
||||
const std::array<unsigned char, 3> source_rgb =
|
||||
sample_texture_preview_rgb_bilinear(source_rgba, width, height, x, y, result.width, result.height);
|
||||
const unsigned int cache_key = texture_preview_rgb_cache_key(source_rgb, use_generic_solver);
|
||||
const std::array<unsigned char, 4> source_rgba_sample =
|
||||
sample_texture_preview_rgba_bilinear(source_rgba, width, height, x, y, result.width, result.height);
|
||||
const ColorRGBA blended_source_color =
|
||||
composite_texture_mapping_color_over_background_for_preview(ColorRGBA(float(source_rgba_sample[0]) / 255.f,
|
||||
float(source_rgba_sample[1]) / 255.f,
|
||||
float(source_rgba_sample[2]) / 255.f,
|
||||
float(source_rgba_sample[3]) / 255.f),
|
||||
background_color);
|
||||
const std::array<unsigned char, 3> source_rgb = {
|
||||
to_u8(blended_source_color.r()),
|
||||
to_u8(blended_source_color.g()),
|
||||
to_u8(blended_source_color.b())
|
||||
};
|
||||
const unsigned int cache_key = use_raw_offsets ?
|
||||
unsigned(std::numeric_limits<unsigned int>::max()) :
|
||||
texture_preview_rgb_cache_key(source_rgb, use_generic_solver);
|
||||
const size_t idx = (size_t(y) * size_t(result.width) + size_t(x)) * 4;
|
||||
|
||||
auto cached_color = simulated_color_cache.find(cache_key);
|
||||
if (cached_color != simulated_color_cache.end()) {
|
||||
auto cached_color = !use_raw_offsets ? simulated_color_cache.find(cache_key) : simulated_color_cache.end();
|
||||
if (!use_raw_offsets && cached_color != simulated_color_cache.end()) {
|
||||
result.rgba[idx + 0] = cached_color->second[0];
|
||||
result.rgba[idx + 1] = cached_color->second[1];
|
||||
result.rgba[idx + 2] = cached_color->second[2];
|
||||
@@ -1342,12 +1617,26 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig
|
||||
}
|
||||
|
||||
const std::array<float, 4> sample_rgba = {
|
||||
float(source_rgb[0]) / 255.f,
|
||||
float(source_rgb[1]) / 255.f,
|
||||
float(source_rgb[2]) / 255.f,
|
||||
blended_source_color.r(),
|
||||
blended_source_color.g(),
|
||||
blended_source_color.b(),
|
||||
1.f
|
||||
};
|
||||
const std::vector<float> component_weights = component_weights_for_texture_preview(settings, sample_rgba);
|
||||
std::vector<float> component_weights;
|
||||
if (use_raw_offsets) {
|
||||
const std::vector<float> raw_sample =
|
||||
sample_texture_preview_raw_offsets_bilinear(source_raw_offsets,
|
||||
width,
|
||||
height,
|
||||
source_raw_channels,
|
||||
x,
|
||||
y,
|
||||
result.width,
|
||||
result.height);
|
||||
component_weights = map_raw_sample_to_components_for_texture_preview(raw_sample, source_raw_component_channels);
|
||||
} else {
|
||||
component_weights = component_weights_for_texture_preview(settings, sample_rgba);
|
||||
}
|
||||
float activity = 0.f;
|
||||
for (const float weight : component_weights)
|
||||
activity = std::max(activity, clamp01(weight));
|
||||
@@ -1362,7 +1651,8 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig
|
||||
to_u8(simulated_rgb[2]),
|
||||
255
|
||||
};
|
||||
simulated_color_cache.emplace(cache_key, out_rgba);
|
||||
if (!use_raw_offsets)
|
||||
simulated_color_cache.emplace(cache_key, out_rgba);
|
||||
result.rgba[idx + 0] = out_rgba[0];
|
||||
result.rgba[idx + 1] = out_rgba[1];
|
||||
result.rgba[idx + 2] = out_rgba[2];
|
||||
@@ -1506,6 +1796,7 @@ const GUI::GLTexture *simulated_texture_preview_texture_for_filament(const Model
|
||||
if (!settings.has_value())
|
||||
return &fallback_texture;
|
||||
|
||||
const ColorRGBA background_color = texture_mapping_background_color_for_preview(model_volume);
|
||||
const size_t simulation_signature = texture_preview_simulation_signature(model_volume, source_texture_signature, *settings);
|
||||
auto &cache = texture_preview_simulation_cache();
|
||||
const size_t cache_key = texture_preview_simulation_cache_key(model_volume, filament_id);
|
||||
@@ -1540,17 +1831,33 @@ const GUI::GLTexture *simulated_texture_preview_texture_for_filament(const Model
|
||||
const unsigned int width = model_volume.imported_texture_width;
|
||||
const unsigned int height = model_volume.imported_texture_height;
|
||||
std::vector<unsigned char> source_rgba(model_volume.imported_texture_rgba.begin(), model_volume.imported_texture_rgba.end());
|
||||
std::vector<unsigned char> source_raw_offsets(model_volume.imported_texture_raw_filament_offsets.begin(),
|
||||
model_volume.imported_texture_raw_filament_offsets.end());
|
||||
const unsigned int source_raw_channels = model_volume.imported_texture_raw_channels;
|
||||
TexturePreviewSimulationSettings simulation_settings = *settings;
|
||||
std::vector<size_t> source_raw_component_channels =
|
||||
raw_component_source_channels_for_texture_preview(model_volume.imported_texture_raw_metadata_json,
|
||||
source_raw_channels,
|
||||
simulation_settings.filament_color_mode,
|
||||
simulation_settings.component_colors.size());
|
||||
entry.pending_future = std::async(std::launch::async,
|
||||
[simulation_signature,
|
||||
width,
|
||||
height,
|
||||
source_rgba = std::move(source_rgba),
|
||||
source_raw_offsets = std::move(source_raw_offsets),
|
||||
source_raw_channels,
|
||||
source_raw_component_channels = std::move(source_raw_component_channels),
|
||||
background_color,
|
||||
simulation_settings = std::move(simulation_settings)]() mutable {
|
||||
return build_simulated_texture_preview_result(simulation_signature,
|
||||
width,
|
||||
height,
|
||||
std::move(source_rgba),
|
||||
std::move(source_raw_offsets),
|
||||
source_raw_channels,
|
||||
std::move(source_raw_component_channels),
|
||||
background_color,
|
||||
std::move(simulation_settings));
|
||||
});
|
||||
}
|
||||
@@ -1652,19 +1959,28 @@ bool build_vertex_color_preview_model_for_state(const ModelVolume
|
||||
std::unordered_map<uint32_t, ColorRGBA> simulated_color_cache;
|
||||
if (simulation_settings != nullptr)
|
||||
simulated_color_cache.reserve(std::min(model_volume.imported_vertex_colors_rgba.size(), size_t(65536)));
|
||||
auto source_vertex_color = [simulation_settings, &simulated_color_cache](uint32_t packed) {
|
||||
const ColorRGBA source_color = unpack_vertex_color(packed);
|
||||
const ColorRGBA background_color = texture_mapping_background_color_for_preview(model_volume);
|
||||
auto preview_color = [simulation_settings, &simulated_color_cache, background_color](const ColorRGBA &source_color) {
|
||||
const ColorRGBA blended_source =
|
||||
composite_texture_mapping_color_over_background_for_preview(source_color, background_color);
|
||||
if (simulation_settings == nullptr)
|
||||
return source_color;
|
||||
return blended_source;
|
||||
|
||||
auto cached = simulated_color_cache.find(packed);
|
||||
const uint32_t key = (uint32_t(std::clamp(blended_source.r(), 0.f, 1.f) * 255.f + 0.5f) << 24) |
|
||||
(uint32_t(std::clamp(blended_source.g(), 0.f, 1.f) * 255.f + 0.5f) << 16) |
|
||||
(uint32_t(std::clamp(blended_source.b(), 0.f, 1.f) * 255.f + 0.5f) << 8) |
|
||||
uint32_t(std::clamp(blended_source.a(), 0.f, 1.f) * 255.f + 0.5f);
|
||||
auto cached = simulated_color_cache.find(key);
|
||||
if (cached != simulated_color_cache.end())
|
||||
return cached->second;
|
||||
|
||||
const ColorRGBA simulated_color = simulated_texture_preview_color_for_vertex_color(&source_color, simulation_settings);
|
||||
simulated_color_cache.emplace(packed, simulated_color);
|
||||
const ColorRGBA simulated_color = simulated_texture_preview_color_for_vertex_color(&blended_source, simulation_settings);
|
||||
simulated_color_cache.emplace(key, simulated_color);
|
||||
return simulated_color;
|
||||
};
|
||||
auto source_vertex_color = [](uint32_t packed) {
|
||||
return unpack_vertex_color(packed);
|
||||
};
|
||||
|
||||
unsigned int vertex_index = 0;
|
||||
for (const TriangleSelector::FacetStateTriangle &triangle : state_triangles) {
|
||||
@@ -1710,7 +2026,7 @@ bool build_vertex_color_preview_model_for_state(const ModelVolume
|
||||
valid_leaf = false;
|
||||
break;
|
||||
}
|
||||
leaf_colors[vertex_idx] = interpolate_color(source_colors, barycentric);
|
||||
leaf_colors[vertex_idx] = preview_color(interpolate_color(source_colors, barycentric));
|
||||
}
|
||||
if (!valid_leaf)
|
||||
continue;
|
||||
@@ -1842,19 +2158,22 @@ bool build_texture_mapping_color_preview_model_for_state(
|
||||
std::unordered_map<uint32_t, ColorRGBA> simulated_color_cache;
|
||||
if (simulation_settings != nullptr)
|
||||
simulated_color_cache.reserve(std::min(color_facets.size(), size_t(65536)));
|
||||
auto preview_color = [simulation_settings, &simulated_color_cache](const ColorRGBA &source_color) {
|
||||
const ColorRGBA background_color = texture_mapping_background_color_for_preview(model_volume, color_source);
|
||||
auto preview_color = [simulation_settings, &simulated_color_cache, background_color](const ColorRGBA &source_color) {
|
||||
const ColorRGBA blended_source =
|
||||
composite_texture_mapping_color_over_background_for_preview(source_color, background_color);
|
||||
if (simulation_settings == nullptr)
|
||||
return source_color;
|
||||
return blended_source;
|
||||
|
||||
const uint32_t key = (uint32_t(std::clamp(source_color.r(), 0.f, 1.f) * 255.f + 0.5f) << 24) |
|
||||
(uint32_t(std::clamp(source_color.g(), 0.f, 1.f) * 255.f + 0.5f) << 16) |
|
||||
(uint32_t(std::clamp(source_color.b(), 0.f, 1.f) * 255.f + 0.5f) << 8) |
|
||||
uint32_t(std::clamp(source_color.a(), 0.f, 1.f) * 255.f + 0.5f);
|
||||
const uint32_t key = (uint32_t(std::clamp(blended_source.r(), 0.f, 1.f) * 255.f + 0.5f) << 24) |
|
||||
(uint32_t(std::clamp(blended_source.g(), 0.f, 1.f) * 255.f + 0.5f) << 16) |
|
||||
(uint32_t(std::clamp(blended_source.b(), 0.f, 1.f) * 255.f + 0.5f) << 8) |
|
||||
uint32_t(std::clamp(blended_source.a(), 0.f, 1.f) * 255.f + 0.5f);
|
||||
auto cached = simulated_color_cache.find(key);
|
||||
if (cached != simulated_color_cache.end())
|
||||
return cached->second;
|
||||
|
||||
const ColorRGBA simulated_color = simulated_texture_preview_color_for_vertex_color(&source_color, simulation_settings);
|
||||
const ColorRGBA simulated_color = simulated_texture_preview_color_for_vertex_color(&blended_source, simulation_settings);
|
||||
simulated_color_cache.emplace(key, simulated_color);
|
||||
return simulated_color;
|
||||
};
|
||||
@@ -2621,10 +2940,21 @@ size_t model_volume_texture_preview_signature(const ModelVolume &model_volume)
|
||||
mix(size_t(model_volume.imported_texture_height));
|
||||
mix(model_volume.imported_texture_rgba.size());
|
||||
mix(reinterpret_cast<size_t>(model_volume.imported_texture_rgba.data()));
|
||||
mix(size_t(model_volume.imported_texture_raw_channels));
|
||||
mix(std::hash<std::string>{}(model_volume.imported_texture_raw_metadata_json));
|
||||
mix(model_volume.imported_texture_raw_filament_offsets.size());
|
||||
mix(reinterpret_cast<size_t>(model_volume.imported_texture_raw_filament_offsets.data()));
|
||||
mix(model_volume.imported_texture_uvs_per_face.size());
|
||||
mix(reinterpret_cast<size_t>(model_volume.imported_texture_uvs_per_face.data()));
|
||||
mix(model_volume.imported_texture_uv_valid.size());
|
||||
mix(reinterpret_cast<size_t>(model_volume.imported_texture_uv_valid.data()));
|
||||
const ColorRGBA background = texture_mapping_background_color_for_preview(model_volume);
|
||||
auto background_signature_component = [](float value) {
|
||||
return size_t(std::clamp(value, 0.f, 1.f) * 255.f + 0.5f);
|
||||
};
|
||||
mix(background_signature_component(background.r()));
|
||||
mix(background_signature_component(background.g()));
|
||||
mix(background_signature_component(background.b()));
|
||||
return signature;
|
||||
}
|
||||
|
||||
@@ -2650,6 +2980,13 @@ size_t model_volume_texture_mapping_color_preview_signature(const ModelVolume &m
|
||||
mix(size_t(color));
|
||||
for (const char ch : data.metadata_json)
|
||||
mix(size_t(static_cast<unsigned char>(ch)));
|
||||
const ColorRGBA background = texture_mapping_background_color_for_preview(model_volume);
|
||||
auto background_signature_component = [](float value) {
|
||||
return size_t(std::clamp(value, 0.f, 1.f) * 255.f + 0.5f);
|
||||
};
|
||||
mix(background_signature_component(background.r()));
|
||||
mix(background_signature_component(background.g()));
|
||||
mix(background_signature_component(background.b()));
|
||||
return signature;
|
||||
}
|
||||
|
||||
@@ -2666,7 +3003,7 @@ bool ensure_model_volume_texture_preview(const ModelVolume &model_volume,
|
||||
|
||||
texture.reset();
|
||||
std::vector<unsigned char> texture_data(model_volume.imported_texture_rgba.begin(), model_volume.imported_texture_rgba.end());
|
||||
make_texture_preview_rgba_opaque(texture_data);
|
||||
composite_texture_preview_rgba_over_background(texture_data, texture_mapping_background_color_for_preview(model_volume));
|
||||
if (!texture.load_from_raw_data(std::move(texture_data), model_volume.imported_texture_width, model_volume.imported_texture_height)) {
|
||||
texture_signature = 0;
|
||||
return false;
|
||||
|
||||
@@ -639,11 +639,15 @@ static std::vector<wxString> texture_mapping_channel_labels(int filament_color_m
|
||||
|
||||
static wxString texture_mapping_summary(const TextureMappingZone &zone, size_t num_physical)
|
||||
{
|
||||
const bool raw_offset = zone.is_image_texture() && zone.texture_mapping_mode == int(TextureMappingZone::TextureMappingRawValues);
|
||||
wxString summary = zone.is_2d_gradient() ? _L("2D Gradient") : from_u8(TextureMappingManager::filament_color_mode_name(zone.filament_color_mode));
|
||||
if (!zone.is_2d_gradient() && summary == "any")
|
||||
summary = _L("Texture");
|
||||
else
|
||||
summary = raw_offset ? _L("Raw Offset Texture") : _L("Texture");
|
||||
else {
|
||||
summary.MakeUpper();
|
||||
if (raw_offset)
|
||||
summary = _L("Raw Offset ") + summary;
|
||||
}
|
||||
|
||||
const std::vector<unsigned int> ids = texture_mapping_selected_ids(zone, num_physical);
|
||||
summary += " ";
|
||||
|
||||
Reference in New Issue
Block a user