From 7667cd8c96a5e0c4b2f0cceaecc0fc892168a130 Mon Sep 17 00:00:00 2001 From: sentientstardust Date: Sun, 10 May 2026 17:21:42 +0100 Subject: [PATCH] Allow manually generating a new UV map for image data --- src/libslic3r/Format/bbs_3mf.cpp | 12 +- src/libslic3r/Model.cpp | 5 +- src/libslic3r/Model.hpp | 7 +- src/libslic3r/PresetBundle.cpp | 43 ++- src/libslic3r/Print.cpp | 3 +- src/libslic3r/PrintApply.cpp | 1 + src/slic3r/GUI/GUI_Factories.cpp | 20 +- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 313 +++++++++++++++++- version.inc | 2 +- 9 files changed, 374 insertions(+), 32 deletions(-) diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp index da5eaded0e8..8b8b88c35dd 100644 --- a/src/libslic3r/Format/bbs_3mf.cpp +++ b/src/libslic3r/Format/bbs_3mf.cpp @@ -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 &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 &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 &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 &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); diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 7dcbd27eae3..16bf05a4b4d 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -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) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index dac008986b7..0e2aed13769 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -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); diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index b1b07da28f3..1aa6e931296 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -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 old_matrix = this->project_config.option("flush_volumes_matrix")->values; - size_t old_nozzle_nums = this->project_config.option("flush_multiplier")->values.size(); - size_t old_number_of_filaments = size_t(sqrt(old_matrix.size() / old_nozzle_nums) + EPSILON); + std::vector& f_multiplier = this->project_config.option("flush_multiplier")->values; + size_t old_nozzle_nums = std::max(f_multiplier.size(), 1); size_t nozzle_nums = get_printer_extruder_count(); - if (old_nozzle_nums != nozzle_nums) { - std::vector& f_multiplier = this->project_config.option("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& filaments = this->project_config.option("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 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("flush_volumes_matrix")->values = new_matrix; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 89b3a1916b3..700ada2568e 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -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; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index e062d729944..a5ea6c623e1 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -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? diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index c862459874c..4903427d924 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -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 = []() { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 864e4eae4a2..dc8867b438f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -61,6 +61,25 @@ static std::vector get_extruders_colors() return wxGetApp().plater() != nullptr ? wxGetApp().plater()->get_extruders_colors() : std::vector{}; } +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 &rgba, a); } +static ColorRGBA sample_rgba_bilinear_wrapped(const std::vector &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 sample_raw_offsets_bilinear_clamped(const ImageMapRawFilamentOffsetAtlas &atlas, float u, float v) { std::vector values; @@ -2258,6 +2282,43 @@ static std::vector sample_raw_offsets_bilinear_clamped(const ImageMapRa return values; } +static std::vector sample_raw_offsets_bilinear_wrapped(const std::vector &offsets, + uint32_t width, + uint32_t height, + uint32_t channels, + float u, + float v) +{ + std::vector 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(std::floor(x)), size_t(width - 1)); + const size_t y0 = std::min(size_t(std::floor(y)), size_t(height - 1)); + const size_t x1 = std::min(x0 + 1, size_t(width - 1)); + const size_t y1 = std::min(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 source_rgba(volume.imported_texture_rgba.begin(), volume.imported_texture_rgba.end()); + const std::vector source_uvs(volume.imported_texture_uvs_per_face.begin(), volume.imported_texture_uvs_per_face.end()); + const std::vector 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 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 source_face_uvs = unwrap_projection_uvs(std::array{ + 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 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 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 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(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 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; } diff --git a/version.inc b/version.inc index ac59b9e2734..bf4d2d38197 100644 --- a/version.inc +++ b/version.inc @@ -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})