Allow manually generating a new UV map for image data

This commit is contained in:
sentientstardust
2026-05-10 17:21:42 +01:00
parent 5823c83691
commit 7667cd8c96
9 changed files with 374 additions and 32 deletions

View File

@@ -431,6 +431,7 @@ static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y";
static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z";
static constexpr const char* SOURCE_IN_INCHES = "source_in_inches";
static constexpr const char* SOURCE_IN_METERS = "source_in_meters";
static constexpr const char* UV_MAP_GENERATOR_VERSION_KEY = "uv_map_generator_version";
static constexpr const char *MATERIALS_NAMESPACE = "http://schemas.microsoft.com/3dmanufacturing/material/2015/02";
static constexpr const char *MODEL_TEXTURE_REL_TYPE = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dtexture";
@@ -5910,6 +5911,8 @@ static void append_triangle_material_data(std::vector<uint8_t> &uv_valid,
volume->source.is_converted_from_inches = metadata.value == "1";
else if (metadata.key == SOURCE_IN_METERS)
volume->source.is_converted_from_meters = metadata.value == "1";
else if (metadata.key == UV_MAP_GENERATOR_VERSION_KEY)
volume->uv_map_generator_version = std::max(0, ::atoi(metadata.value.c_str()));
else if ((metadata.key == MATRIX_KEY) || (metadata.key == MESH_SHARED_KEY))
continue;
else
@@ -6126,6 +6129,8 @@ static void append_triangle_material_data(std::vector<uint8_t> &uv_valid,
volume->source.is_converted_from_inches = metadata.value == "1";
else if (metadata.key == SOURCE_IN_METERS)
volume->source.is_converted_from_meters = metadata.value == "1";
else if (metadata.key == UV_MAP_GENERATOR_VERSION_KEY)
volume->uv_map_generator_version = std::max(0, ::atoi(metadata.value.c_str()));
else
volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
}
@@ -7984,7 +7989,8 @@ static void append_triangle_material_data(std::vector<uint8_t> &uv_valid,
&& (shared_volume->imported_texture_width == volume->imported_texture_width)
&& (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))
&& (shared_volume->imported_texture_raw_metadata_json == volume->imported_texture_raw_metadata_json)
&& (shared_volume->uv_map_generator_version == volume->uv_map_generator_version))
{
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}});
@@ -9286,6 +9292,10 @@ static void append_triangle_material_data(std::vector<uint8_t> &uv_valid,
}
stream << "\"/>\n";
if (volume->uv_map_generator_version > 0)
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << UV_MAP_GENERATOR_VERSION_KEY << "\" "
<< VALUE_ATTR << "=\"" << volume->uv_map_generator_version << "\"/>\n";
// stores volume's source data
{
auto file_path =get_dealed_platform_path(volume->source.input_file);

View File

@@ -845,6 +845,7 @@ Model Model::read_from_file(const std::string&
volume->imported_texture_height = 0;
volume->imported_texture_raw_channels = 0;
volume->imported_texture_raw_metadata_json.clear();
volume->uv_map_generator_version = 0;
bool has_imported_usable_uv_texture_data = false;
const size_t triangle_count = volume->mesh().its.indices.size();
@@ -882,6 +883,7 @@ Model Model::read_from_file(const std::string&
volume->imported_texture_raw_filament_offsets.clear();
volume->imported_texture_raw_channels = 0;
volume->imported_texture_raw_metadata_json.clear();
volume->uv_map_generator_version = 0;
has_imported_usable_uv_texture_data = has_any_valid_uv_face;
}
}
@@ -4918,7 +4920,8 @@ static bool model_volume_texture_mapping_data_matches(const ModelVolume &mv_old,
mv_old.imported_texture_width == mv_new.imported_texture_width &&
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;
mv_old.imported_texture_raw_metadata_json == mv_new.imported_texture_raw_metadata_json &&
mv_old.uv_map_generator_version == mv_new.uv_map_generator_version;
}
bool model_texture_mapping_color_data_changed(const ModelObject& mo, const ModelObject& mo_new)

View File

@@ -1065,6 +1065,7 @@ public:
uint32_t imported_texture_height{0};
uint32_t imported_texture_raw_channels{0};
std::string imported_texture_raw_metadata_json;
int uv_map_generator_version{0};
// List of mesh facets painted for fuzzy skin.
FacetsAnnotation fuzzy_skin_facets;
@@ -1317,6 +1318,7 @@ private:
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),
uv_map_generator_version(other.uv_map_generator_version),
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());
@@ -1417,7 +1419,7 @@ private:
cereal::load_by_value(ar, imported_texture_uv_valid);
cereal::load_by_value(ar, imported_texture_rgba);
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);
ar(imported_texture_width, imported_texture_height, imported_texture_raw_channels, imported_texture_raw_metadata_json, uv_map_generator_version);
cereal::load_by_value(ar, fuzzy_skin_facets);
mesh_changed |= t != fuzzy_skin_facets.timestamp();
cereal::load_by_value(ar, config);
@@ -1434,6 +1436,7 @@ private:
imported_texture_height = 0;
imported_texture_raw_channels = 0;
imported_texture_raw_metadata_json.clear();
uv_map_generator_version = 0;
texture_mapping_color_facets.reset();
}
assert(m_mesh);
@@ -1459,7 +1462,7 @@ private:
cereal::save_by_value(ar, imported_texture_uv_valid);
cereal::save_by_value(ar, imported_texture_rgba);
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);
ar(imported_texture_width, imported_texture_height, imported_texture_raw_channels, imported_texture_raw_metadata_json, uv_map_generator_version);
cereal::save_by_value(ar, fuzzy_skin_facets);
cereal::save_by_value(ar, config);
cereal::save(ar, text_configuration);

View File

@@ -4354,20 +4354,26 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
#else
size_t num_filaments = this->filament_presets.size();
#endif
if (to_delete_filament_id == -1)
if (to_delete_filament_id == size_t(-1))
to_delete_filament_id = num_filaments;
// Now verify if flush_volumes_matrix has proper size (it is used to deduce number of extruders in wipe tower generator):
std::vector<double> old_matrix = this->project_config.option<ConfigOptionFloats>("flush_volumes_matrix")->values;
size_t old_nozzle_nums = this->project_config.option<ConfigOptionFloats>("flush_multiplier")->values.size();
size_t old_number_of_filaments = size_t(sqrt(old_matrix.size() / old_nozzle_nums) + EPSILON);
std::vector<double>& f_multiplier = this->project_config.option<ConfigOptionFloats>("flush_multiplier")->values;
size_t old_nozzle_nums = std::max<size_t>(f_multiplier.size(), 1);
size_t nozzle_nums = get_printer_extruder_count();
if (old_nozzle_nums != nozzle_nums) {
std::vector<double>& f_multiplier = this->project_config.option<ConfigOptionFloats>("flush_multiplier")->values;
f_multiplier.resize(nozzle_nums, 1.f);
if (f_multiplier.size() != nozzle_nums) {
const double multiplier = f_multiplier.empty() ? 1. : f_multiplier.back();
f_multiplier.resize(nozzle_nums, multiplier);
}
if ( (num_filaments * num_filaments) != size_t(old_matrix.size() / old_nozzle_nums) ) {
const size_t old_matrix_values_per_nozzle = old_matrix.size() / old_nozzle_nums;
const size_t old_number_of_filaments = size_t(sqrt(old_matrix_values_per_nozzle) + EPSILON);
const size_t old_matrix_size = old_number_of_filaments * old_number_of_filaments;
const size_t new_matrix_size = num_filaments * num_filaments;
const size_t expected_matrix_size = new_matrix_size * nozzle_nums;
if (old_matrix.size() != expected_matrix_size) {
// First verify if purging volumes presets for each extruder matches number of extruders
std::vector<double>& filaments = this->project_config.option<ConfigOptionFloats>("flush_volumes_vector")->values;
while (filaments.size() < 2* num_filaments) {
@@ -4379,21 +4385,22 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
filaments.pop_back();
}
size_t old_matrix_size = old_number_of_filaments * old_number_of_filaments;
size_t new_matrix_size = num_filaments * num_filaments;
std::vector<double> new_matrix(new_matrix_size * nozzle_nums, 0);
for (unsigned int i = 0; i < num_filaments; ++i)
for (unsigned int j = 0; j < num_filaments; ++j) {
if (i < old_number_of_filaments && j < old_number_of_filaments) {
unsigned int old_i = i >= to_delete_filament_id ? i + 1 : i;
unsigned int old_j = j >= to_delete_filament_id ? j + 1 : j;
for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) {
new_matrix[i * num_filaments + j + new_matrix_size * nozzle_id] = old_matrix[old_i * old_number_of_filaments + old_j + old_matrix_size * nozzle_id];
}
} else {
for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) {
new_matrix[i * num_filaments + j + new_matrix_size * nozzle_id] = (i == j ? 0. : filaments[2 * i] + filaments[2 * j + 1]);
unsigned int old_i = i >= to_delete_filament_id ? i + 1 : i;
unsigned int old_j = j >= to_delete_filament_id ? j + 1 : j;
for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) {
double value = i == j ? 0. : filaments[2 * i] + filaments[2 * j + 1];
if (old_matrix_size == old_matrix_values_per_nozzle &&
old_i < old_number_of_filaments &&
old_j < old_number_of_filaments) {
const size_t source_nozzle_id = std::min(nozzle_id, old_nozzle_nums - 1);
const size_t old_idx = old_i * old_number_of_filaments + old_j + old_matrix_size * source_nozzle_id;
if (old_idx < old_matrix.size())
value = old_matrix[old_idx];
}
new_matrix[i * num_filaments + j + new_matrix_size * nozzle_id] = value;
}
}
this->project_config.option<ConfigOptionFloats>("flush_volumes_matrix")->values = new_matrix;

View File

@@ -283,7 +283,8 @@ static bool model_volume_texture_mapping_data_equal_for_print_sharing(const Mode
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;
lhs.imported_texture_raw_metadata_json == rhs.imported_texture_raw_metadata_json &&
lhs.uv_map_generator_version == rhs.uv_map_generator_version;
}
template class PrintState<PrintStep, psCount>;

View File

@@ -92,6 +92,7 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst,
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;
mv_dst.uv_map_generator_version = mv_src.uv_map_generator_version;
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?

View File

@@ -110,6 +110,17 @@ static int menu_item_position(wxMenu *menu, const wxString &name)
return wxNOT_FOUND;
}
static int first_separator_position(wxMenu *menu)
{
int position = 0;
for (auto node = menu->GetMenuItems().GetFirst(); node; node = node->GetNext(), ++position) {
wxMenuItem *item = node->GetData();
if (item != nullptr && item->IsSeparator())
return position;
}
return wxNOT_FOUND;
}
static ModelObject *selected_managed_color_data_object()
{
Plater *app_plater = wxGetApp().plater();
@@ -1210,13 +1221,18 @@ void MenuFactory::append_menu_item_manage_color_data(wxMenu *menu)
if (selected_managed_color_data_object() == nullptr)
return;
int insert_pos = menu_item_position(menu, _L("Flush Options"));
int insert_pos = first_separator_position(menu);
if (insert_pos != wxNOT_FOUND)
++insert_pos;
else {
insert_pos = menu_item_position(menu, _L("Edit in Parameter Table"));
insert_pos = menu_item_position(menu, _L("Flush Options"));
if (insert_pos != wxNOT_FOUND)
++insert_pos;
else {
insert_pos = menu_item_position(menu, _L("Edit in Parameter Table"));
if (insert_pos != wxNOT_FOUND)
++insert_pos;
}
}
auto can_manage_color_data = []() {

View File

@@ -61,6 +61,25 @@ static std::vector<ColorRGBA> get_extruders_colors()
return wxGetApp().plater() != nullptr ? wxGetApp().plater()->get_extruders_colors() : std::vector<ColorRGBA>{};
}
static size_t extruder_color_index_for_filament_id(unsigned int filament_id, size_t color_count)
{
if (color_count == 0)
return 0;
if (filament_id >= 1 && filament_id <= color_count)
return size_t(filament_id - 1);
if (wxGetApp().preset_bundle != nullptr) {
const size_t physical_count = size_t(std::max(wxGetApp().filaments_cnt(), 0));
const unsigned int resolved_id =
wxGetApp().preset_bundle->texture_mapping_zones.resolve_zone_component(filament_id, physical_count, 0);
if (resolved_id >= 1 && resolved_id <= color_count)
return size_t(resolved_id - 1);
}
return 0;
}
void GLGizmoMmuSegmentation::on_opening()
{
if (get_extruders_colors().size() > GLGizmoMmuSegmentation::EXTRUDERS_LIMIT)
@@ -2226,6 +2245,11 @@ static ColorRGBA sample_rgba_bilinear_clamped(const std::vector<uint8_t> &rgba,
a);
}
static ColorRGBA sample_rgba_bilinear_wrapped(const std::vector<uint8_t> &rgba, uint32_t width, uint32_t height, float u, float v)
{
return sample_rgba_bilinear_clamped(rgba, width, height, wrap_texture_uv_for_vertex_bake(u), wrap_texture_uv_for_vertex_bake(v));
}
static std::vector<uint8_t> sample_raw_offsets_bilinear_clamped(const ImageMapRawFilamentOffsetAtlas &atlas, float u, float v)
{
std::vector<uint8_t> values;
@@ -2258,6 +2282,43 @@ static std::vector<uint8_t> sample_raw_offsets_bilinear_clamped(const ImageMapRa
return values;
}
static std::vector<uint8_t> sample_raw_offsets_bilinear_wrapped(const std::vector<uint8_t> &offsets,
uint32_t width,
uint32_t height,
uint32_t channels,
float u,
float v)
{
std::vector<uint8_t> values(size_t(channels), 0);
if (width == 0 || height == 0 || channels == 0 ||
offsets.size() < size_t(width) * size_t(height) * size_t(channels))
return values;
u = wrap_texture_uv_for_vertex_bake(u);
v = wrap_texture_uv_for_vertex_bake(v);
const float x = u * float(width > 1 ? width - 1 : 0);
const float y = v * 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 channel = [&offsets, width, channels](size_t sx, size_t sy, size_t ch) {
return float(offsets[(sy * size_t(width) + sx) * size_t(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);
@@ -2875,6 +2936,7 @@ static ColorRGBA projection_base_color_for_volume(const ModelVolume &volume)
}
static constexpr uint32_t GENERATED_IMAGE_TEXTURE_SIZE = 4096;
static constexpr int GENERATED_IMAGE_TEXTURE_UV_MAP_VERSION = 1;
struct GeneratedImageTextureIsland
{
@@ -3113,6 +3175,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);
volume.uv_map_generator_version = GENERATED_IMAGE_TEXTURE_UV_MAP_VERSION;
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;
@@ -4593,6 +4656,7 @@ static bool clear_object_managed_color_data(ModelObject &object, ManagedColorDat
clear_imported_texture_raw_atlas(*volume);
volume->imported_texture_width = 0;
volume->imported_texture_height = 0;
volume->uv_map_generator_version = 0;
changed = true;
}
break;
@@ -5370,6 +5434,190 @@ static bool convert_object_to_image_texture(ModelObject &object, const ManagedCo
return changed;
}
static bool object_has_regenerable_image_texture_uvs(const ModelObject &object)
{
for (const ModelVolume *volume : object.volumes)
if (volume != nullptr && volume->is_model_part() && model_volume_has_bakeable_image_texture_data(volume))
return true;
return false;
}
static wxString managed_image_texture_uv_map_info_text(const ModelObject *object)
{
if (object == nullptr)
return _L("UV map: none");
bool has_texture = false;
bool has_imported = false;
bool has_generated = false;
int generated_version = -1;
bool mixed_generated_versions = false;
for (const ModelVolume *volume : object->volumes) {
if (volume == nullptr || !volume->is_model_part() || !model_volume_has_imported_image_texture_data(volume))
continue;
has_texture = true;
if (volume->uv_map_generator_version > 0) {
has_generated = true;
if (generated_version < 0)
generated_version = volume->uv_map_generator_version;
else if (generated_version != volume->uv_map_generator_version)
mixed_generated_versions = true;
} else {
has_imported = true;
}
}
if (!has_texture)
return _L("UV map: none");
if (has_imported && has_generated)
return _L("UV map: mixed");
if (has_generated) {
if (mixed_generated_versions)
return _L("UV map: generated (mixed)");
return wxString::Format(_L("UV map: generated (v%d)"), generated_version);
}
return _L("UV map: imported");
}
static bool regenerate_volume_image_texture_uv_map(ModelObject &object, ModelVolume &volume)
{
if (!model_volume_has_bakeable_image_texture_data(&volume))
return false;
const indexed_triangle_set &its = volume.mesh().its;
const uint32_t source_width = volume.imported_texture_width;
const uint32_t source_height = volume.imported_texture_height;
const std::vector<uint8_t> source_rgba(volume.imported_texture_rgba.begin(), volume.imported_texture_rgba.end());
const std::vector<float> source_uvs(volume.imported_texture_uvs_per_face.begin(), volume.imported_texture_uvs_per_face.end());
const std::vector<uint8_t> source_uv_valid(volume.imported_texture_uv_valid.begin(), volume.imported_texture_uv_valid.end());
const bool source_has_raw_atlas = model_volume_has_raw_atlas_texture_data(&volume);
const uint32_t source_raw_channels = volume.imported_texture_raw_channels;
const std::vector<uint8_t> source_raw_offsets(volume.imported_texture_raw_filament_offsets.begin(),
volume.imported_texture_raw_filament_offsets.end());
const std::string source_raw_metadata = volume.imported_texture_raw_metadata_json;
GeneratedImageTextureAtlas generated_atlas;
Transform3d metric_matrix = volume.get_matrix();
if (!object.instances.empty() && object.instances.front() != nullptr)
metric_matrix = object.instances.front()->get_transformation().get_matrix() * metric_matrix;
if (!initialize_generated_image_texture(volume,
blank_color_for_managed_target(ManagedColorDataType::ImageTexture),
&generated_atlas,
&metric_matrix))
return false;
if (source_has_raw_atlas) {
volume.imported_texture_raw_channels = source_raw_channels;
volume.imported_texture_raw_metadata_json = source_raw_metadata;
volume.imported_texture_raw_filament_offsets.assign(size_t(volume.imported_texture_width) *
size_t(volume.imported_texture_height) *
size_t(source_raw_channels),
0);
}
bool changed = true;
const float target_width = float(volume.imported_texture_width);
const float target_height = float(volume.imported_texture_height);
for (const GeneratedImageTextureIsland &island : generated_atlas.islands) {
const size_t tri_idx = island.tri_idx;
if (tri_idx >= its.indices.size() ||
tri_idx >= source_uv_valid.size() ||
source_uv_valid[tri_idx] == 0)
continue;
const size_t source_uv_offset = tri_idx * 6;
const size_t target_uv_offset = tri_idx * 6;
if (source_uv_offset + 5 >= source_uvs.size() ||
target_uv_offset + 5 >= volume.imported_texture_uvs_per_face.size())
continue;
const std::array<Vec2f, 3> source_face_uvs = unwrap_projection_uvs(std::array<Vec2f, 3>{
Vec2f(source_uvs[source_uv_offset + 0], source_uvs[source_uv_offset + 1]),
Vec2f(source_uvs[source_uv_offset + 2], source_uvs[source_uv_offset + 3]),
Vec2f(source_uvs[source_uv_offset + 4], source_uvs[source_uv_offset + 5])
});
const std::array<Vec2f, 3> target_pixel_uvs = {
Vec2f(volume.imported_texture_uvs_per_face[target_uv_offset + 0] * target_width,
volume.imported_texture_uvs_per_face[target_uv_offset + 1] * target_height),
Vec2f(volume.imported_texture_uvs_per_face[target_uv_offset + 2] * target_width,
volume.imported_texture_uvs_per_face[target_uv_offset + 3] * target_height),
Vec2f(volume.imported_texture_uvs_per_face[target_uv_offset + 4] * target_width,
volume.imported_texture_uvs_per_face[target_uv_offset + 5] * target_height)
};
const int min_x = std::clamp(island.x, 0, int(volume.imported_texture_width) - 1);
const int max_x = std::clamp(island.x + island.rect_width - 1, 0, int(volume.imported_texture_width) - 1);
const int min_y = std::clamp(island.y, 0, int(volume.imported_texture_height) - 1);
const int max_y = std::clamp(island.y + island.rect_height - 1, 0, int(volume.imported_texture_height) - 1);
for (int y_px = min_y; y_px <= max_y; ++y_px) {
for (int x_px = min_x; x_px <= max_x; ++x_px) {
Vec3f barycentric = Vec3f::Zero();
const Vec2f pixel(float(x_px) + 0.5f, float(y_px) + 0.5f);
if (!barycentric_weights_2d(pixel, target_pixel_uvs[0], target_pixel_uvs[1], target_pixel_uvs[2], barycentric))
continue;
if (barycentric.x() < -1e-4f || barycentric.y() < -1e-4f || barycentric.z() < -1e-4f)
barycentric = normalized_nonnegative_barycentric(barycentric);
const Vec3f safe_barycentric =
texture_barycentric_for_bleed_safe_sampling(barycentric,
source_face_uvs[0],
source_face_uvs[1],
source_face_uvs[2],
source_width,
source_height);
const Vec2f source_uv = source_face_uvs[0] * safe_barycentric.x() +
source_face_uvs[1] * safe_barycentric.y() +
source_face_uvs[2] * safe_barycentric.z();
if (source_has_raw_atlas) {
const std::vector<uint8_t> values =
sample_raw_offsets_bilinear_wrapped(source_raw_offsets,
source_width,
source_height,
source_raw_channels,
source_uv.x(),
source_uv.y());
write_raw_offset_pixel(volume.imported_texture_raw_filament_offsets,
volume.imported_texture_width,
source_raw_channels,
uint32_t(x_px),
uint32_t(y_px),
values);
} else {
const ColorRGBA color = sample_rgba_bilinear_wrapped(source_rgba,
source_width,
source_height,
source_uv.x(),
source_uv.y());
write_rgba_pixel(volume.imported_texture_rgba,
volume.imported_texture_width,
uint32_t(x_px),
uint32_t(y_px),
color);
}
}
}
}
if (source_has_raw_atlas) {
refresh_imported_texture_preview_from_raw_offsets(volume);
refresh_imported_texture_raw_storage(volume);
}
refresh_imported_texture_storage(volume);
return changed;
}
static bool regenerate_object_image_texture_uv_maps(ModelObject &object)
{
bool changed = false;
for (ModelVolume *volume : object.volumes) {
if (volume == nullptr || !volume->is_model_part())
continue;
changed |= regenerate_volume_image_texture_uv_map(object, *volume);
}
return changed;
}
static bool convert_object_to_rgba_data(ModelObject &object, const ManagedColorDataCreateSource &source)
{
if (object_has_rgba_data(object))
@@ -5823,6 +6071,7 @@ private:
wxStaticText *size = nullptr;
wxButton *clear = nullptr;
wxButton *create = nullptr;
wxButton *actions = nullptr;
};
void add_raw_image_texture_info_row(wxFlexGridSizer *grid)
@@ -5852,17 +6101,27 @@ private:
wxStaticText *size = new wxStaticText(this, wxID_ANY, wxString());
wxButton *clear = new wxButton(this, wxID_ANY, _L("Clear"));
wxButton *create = new wxButton(this, wxID_ANY, _L("Create From..."));
wxButton *actions = nullptr;
wxBoxSizer *size_sizer = new wxBoxSizer(wxHORIZONTAL);
size_sizer->Add(size, 1, wxALIGN_CENTER_VERTICAL);
if (type == ManagedColorDataType::ImageTexture) {
actions = new wxButton(this, wxID_ANY, wxString::FromUTF8("\xE2\x96\xBE"), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
actions->SetToolTip(_L("Image texture actions"));
size_sizer->Add(actions, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
}
grid->Add(new wxStaticText(this, wxID_ANY, label), 0, wxALIGN_CENTER_VERTICAL);
grid->Add(status, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(preview, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(size, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
grid->Add(size_sizer, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
grid->Add(clear, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(create, 0, wxALIGN_CENTER_VERTICAL);
clear->Bind(wxEVT_BUTTON, [this, type](wxCommandEvent &) { clear_data(type); });
create->Bind(wxEVT_BUTTON, [this, type, create](wxCommandEvent &) { show_create_menu(type, create); });
m_rows.push_back({ type, status, preview, size, clear, create });
if (actions != nullptr)
actions->Bind(wxEVT_BUTTON, [this, actions](wxCommandEvent &) { show_image_texture_menu(actions); });
m_rows.push_back({ type, status, preview, size, clear, create, actions });
}
void refresh_rows()
@@ -5877,6 +6136,8 @@ private:
row.size->SetLabel(managed_color_data_size_text(summary, row.type));
row.clear->Enable(has_data);
row.create->Enable(m_object != nullptr && !has_data);
if (row.actions != nullptr)
row.actions->Enable(m_object != nullptr);
}
const bool show_raw_info = summary.has_raw_offset_image_texture;
if (m_raw_image_texture_status != nullptr)
@@ -5959,6 +6220,39 @@ private:
button->PopupMenu(&menu, wxPoint(0, button->GetSize().GetHeight()));
}
void show_image_texture_menu(wxButton *button)
{
if (button == nullptr)
return;
wxMenu menu;
wxMenuItem *info = menu.Append(wxWindow::NewControlId(), managed_image_texture_uv_map_info_text(m_object));
info->Enable(false);
menu.AppendSeparator();
const int generate_id = wxWindow::NewControlId();
wxMenuItem *generate = menu.Append(generate_id, _L("Generate new UV Map"));
generate->Enable(m_object != nullptr && object_has_regenerable_image_texture_uvs(*m_object));
menu.Bind(wxEVT_COMMAND_MENU_SELECTED, [this, generate_id](wxCommandEvent &event) {
if (event.GetId() == generate_id)
generate_new_uv_map();
});
button->PopupMenu(&menu, wxPoint(0, button->GetSize().GetHeight()));
}
void generate_new_uv_map()
{
if (m_object == nullptr || !object_has_regenerable_image_texture_uvs(*m_object))
return;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Generate new image texture UV map", UndoRedo::SnapshotType::GizmoAction);
if (!regenerate_object_image_texture_uv_maps(*m_object))
return;
refresh_object_after_change();
refresh_rows();
}
void create_data(ManagedColorDataType type, const ManagedColorDataCreateSource &source)
{
if (m_object == nullptr || object_has_managed_color_data(*m_object, type) || (source.type && *source.type == type))
@@ -6891,9 +7185,10 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors()
if (!mv->is_model_part())
continue;
int extruder_idx = (mv->extruder_id() > 0) ? mv->extruder_id() - 1 : 0;
const unsigned int extruder_id = mv->extruder_id() > 0 ? unsigned(mv->extruder_id()) : 1u;
const size_t extruder_color_id = extruder_color_index_for_filament_id(extruder_id, m_extruders_colors.size());
std::vector<ColorRGBA> ebt_colors;
ebt_colors.push_back(m_extruders_colors[size_t(extruder_idx)]);
ebt_colors.push_back(m_extruders_colors[extruder_color_id]);
ebt_colors.insert(ebt_colors.end(), m_extruders_colors.begin(), m_extruders_colors.end());
// This mesh does not account for the possible Z up SLA offset.
@@ -6910,10 +7205,14 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors()
void GLGizmoMmuSegmentation::update_triangle_selectors_colors()
{
if (m_extruders_colors.empty())
return;
for (int i = 0; i < m_triangle_selectors.size(); i++) {
TriangleSelectorPatch* selector = dynamic_cast<TriangleSelectorPatch*>(m_triangle_selectors[i].get());
int extruder_idx = m_volumes_extruder_idxs[i];
int extruder_color_idx = std::max(0, extruder_idx - 1);
int extruder_idx = i < int(m_volumes_extruder_idxs.size()) ? m_volumes_extruder_idxs[i] : 1;
const unsigned int extruder_id = extruder_idx > 0 ? unsigned(extruder_idx) : 1u;
const size_t extruder_color_idx = extruder_color_index_for_filament_id(extruder_id, m_extruders_colors.size());
std::vector<ColorRGBA> ebt_colors;
ebt_colors.push_back(m_extruders_colors[extruder_color_idx]);
ebt_colors.insert(ebt_colors.end(), m_extruders_colors.begin(), m_extruders_colors.end());
@@ -7201,6 +7500,7 @@ void GLGizmoMmuSegmentation::bake_selected_object_image_texture_to_vertex_colors
clear_imported_texture_raw_atlas(*volume);
volume->imported_texture_width = 0;
volume->imported_texture_height = 0;
volume->uv_map_generator_version = 0;
baked = true;
}
@@ -7564,6 +7864,7 @@ void GLGizmoMmuSegmentation::clear_selected_object_image_texture_data()
clear_imported_texture_raw_atlas(*volume);
volume->imported_texture_width = 0;
volume->imported_texture_height = 0;
volume->uv_map_generator_version = 0;
cleared = true;
}

View File

@@ -7,7 +7,7 @@ set(SLIC3R_APP_KEY "OrcaSlicer")
if(NOT DEFINED BBL_INTERNAL_TESTING)
set(BBL_INTERNAL_TESTING "0")
endif()
set(SoftFever_VERSION "1.0.0")
set(SoftFever_VERSION "1.0.5")
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)"
SoftFever_VERSION_MATCH ${SoftFever_VERSION})
set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})