diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index ba1643724f4..8ed68ca72fa 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -48,7 +48,8 @@ static void apply_tolerance(ModelVolume* vol) static void apply_cut_texture_data(ModelVolume *volume, const SimplifyTextureDataSnapshot *source_snapshot, const Transform3d &cut_matrix) { - if (volume == nullptr || source_snapshot == nullptr || source_snapshot->source == SimplifyColorSource::None || + if (volume == nullptr || source_snapshot == nullptr || + (source_snapshot->source == SimplifyColorSource::None && !source_snapshot->region_painting_present) || volume->mesh().empty()) return; @@ -66,6 +67,9 @@ static bool volume_has_remappable_color_data(const ModelVolume &volume) if (its.vertices.empty() || its.indices.empty()) return false; + if (model_volume_region_painting_needs_remap(volume)) + return true; + if (!volume.texture_mapping_color_facets.empty()) return true; @@ -481,9 +485,12 @@ static void merge_solid_parts_inside_object(ModelObjectPtrs& objects) } TriangleMesh mesh; + const ModelVolume *merged_volume_source = nullptr; // Merge all SolidPart but not Connectors for (const ModelVolume* mv : mo->volumes) { if (mv->is_model_part() && !mv->is_cut_connector()) { + if (merged_volume_source == nullptr) + merged_volume_source = mv; TriangleMesh m = mv->mesh(); m.transform(mv->get_matrix()); mesh.merge(m); @@ -492,6 +499,14 @@ static void merge_solid_parts_inside_object(ModelObjectPtrs& objects) if (!mesh.empty()) { ModelVolume* new_volume = mo->add_volume(mesh); new_volume->name = mo->name; + if (merged_volume_source != nullptr) { + new_volume->config.assign_config(merged_volume_source->config); + if (ModelMaterial *material = merged_volume_source->material()) + new_volume->set_material(merged_volume_source->material_id(), *material); + else + new_volume->set_material_id(merged_volume_source->material_id()); + new_volume->cut_info = merged_volume_source->cut_info; + } // Delete all merged SolidPart but not Connectors for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { const ModelVolume* mv = mo->volumes[i]; diff --git a/src/libslic3r/ModelTextureDataRemap.cpp b/src/libslic3r/ModelTextureDataRemap.cpp index c7ca766057a..2c246470403 100644 --- a/src/libslic3r/ModelTextureDataRemap.cpp +++ b/src/libslic3r/ModelTextureDataRemap.cpp @@ -47,6 +47,11 @@ struct RgbaFacetLookup std::unordered_map> by_triangle; }; +struct RegionFacetLookup +{ + std::vector>> by_state_and_triangle; +}; + struct FastTriangleProjection { bool enabled = false; @@ -86,6 +91,18 @@ static ColorRGBA unpack_rgba(uint32_t packed) float(packed & 0xFFu) / 255.f); } +static unsigned int effective_region_state(unsigned int state, unsigned int base_filament_id) +{ + if (state == 0 || state == base_filament_id || state > unsigned(EnforcerBlockerType::ExtruderMax)) + return 0; + return state; +} + +static unsigned int volume_base_region_filament_id(const ModelVolume &volume) +{ + return unsigned(std::max(volume.extruder_id(), 1)); +} + static bool valid_triangle_vertices(const indexed_triangle_set &its, size_t tri_idx, std::array &vertices) { if (tri_idx >= its.indices.size()) @@ -407,6 +424,19 @@ static bool volume_has_valid_image_texture(const ModelVolume &volume) return valid_rgba || volume_has_valid_raw_atlas(volume); } +static bool region_painting_data_needs_remap(const ModelVolume &volume) +{ + if (volume.mmu_segmentation_facets.empty()) + return false; + + const unsigned int base_filament_id = volume_base_region_filament_id(volume); + const auto &used_states = volume.mmu_segmentation_facets.get_data().used_states; + for (size_t state_idx = 0; state_idx < used_states.size(); ++state_idx) + if (used_states[state_idx] && effective_region_state(unsigned(state_idx), base_filament_id) != 0) + return true; + return false; +} + static std::array source_triangle_uvs(const SimplifyTextureDataSnapshot &snapshot, size_t tri_idx, bool *valid = nullptr) { std::array uvs = { Vec2f::Zero(), Vec2f::Zero(), Vec2f::Zero() }; @@ -779,6 +809,20 @@ static RgbaFacetLookup make_rgba_facet_lookup(const SimplifyTextureDataSnapshot return lookup; } +static RegionFacetLookup make_region_facet_lookup(const SimplifyTextureDataSnapshot &snapshot) +{ + RegionFacetLookup lookup; + lookup.by_state_and_triangle.resize(snapshot.region_facets_per_type.size()); + for (size_t state_idx = 0; state_idx < snapshot.region_facets_per_type.size(); ++state_idx) { + const auto &facets = snapshot.region_facets_per_type[state_idx]; + auto &by_triangle = lookup.by_state_and_triangle[state_idx]; + by_triangle.reserve(facets.size()); + for (size_t facet_idx = 0; facet_idx < facets.size(); ++facet_idx) + by_triangle[facets[facet_idx].source_triangle].emplace_back(facet_idx); + } + return lookup; +} + static ColorRGBA rgb_metadata_background_color(const std::string &metadata) { const std::string key = "\"background_color\":\"#"; @@ -843,6 +887,45 @@ static ColorRGBA sample_rgba_facets_at_source(const SimplifyTextureDataSnapshot return unpack_rgba(best_rgba); } +static unsigned int sample_region_state_at_source(const SimplifyTextureDataSnapshot &snapshot, + const RegionFacetLookup &lookup, + size_t source_triangle, + const Vec3f &point) +{ + float best_distance = std::numeric_limits::max(); + unsigned int best_state = 0; + bool found_candidate = false; + for (size_t state_idx = 0; state_idx < snapshot.region_facets_per_type.size(); ++state_idx) { + if (state_idx >= lookup.by_state_and_triangle.size()) + continue; + const auto range_it = lookup.by_state_and_triangle[state_idx].find(int(source_triangle)); + if (range_it == lookup.by_state_and_triangle[state_idx].end()) + continue; + + const auto &facets = snapshot.region_facets_per_type[state_idx]; + for (const size_t facet_idx : range_it->second) { + if (facet_idx >= facets.size()) + continue; + const TriangleSelector::FacetStateTriangle &facet = facets[facet_idx]; + const Vec3f bary = barycentric_weights_3d(point, facet.vertices); + const float min_weight = std::min({ bary.x(), bary.y(), bary.z() }); + const unsigned int state = effective_region_state(unsigned(state_idx), unsigned(snapshot.region_base_filament_id)); + if (min_weight >= -1e-4f) + return state; + + const Vec3f safe = normalized_nonnegative_barycentric(bary); + const Vec3f closest = facet.vertices[0] * safe.x() + facet.vertices[1] * safe.y() + facet.vertices[2] * safe.z(); + const float distance = (closest - point).squaredNorm(); + if (distance < best_distance) { + best_distance = distance; + best_state = state; + found_candidate = true; + } + } + } + return found_candidate ? best_state : 0; +} + static ColorRGBA sample_vertex_color_at_source(const SimplifyTextureDataSnapshot &snapshot, size_t source_triangle, const Vec3f &barycentric) { if (source_triangle >= snapshot.source_mesh.indices.size()) @@ -914,6 +997,83 @@ static int texture_mapping_depth_from_span(float span, float target_span, int ma return std::clamp(int(std::ceil(std::log2(span / target_span))), 0, max_depth); } +static void region_append_nibble(std::vector &bits, unsigned int code) +{ + for (int bit = 0; bit < 4; ++bit) + bits.emplace_back((code & (1u << bit)) != 0u); +} + +static void region_append_leaf(TriangleSelector::TriangleSplittingData &data, unsigned int state) +{ + state = std::min(state, unsigned(EnforcerBlockerType::ExtruderMax)); + if (state < data.used_states.size()) + data.used_states[state] = true; + + if (state >= 3u) { + region_append_nibble(data.bitstream, 0b1100u); + state -= 3u; + while (state >= 15u) { + region_append_nibble(data.bitstream, 0b1111u); + state -= 15u; + } + region_append_nibble(data.bitstream, state); + } else { + region_append_nibble(data.bitstream, state << 2); + } +} + +static std::array, 4> region_split_triangle(const std::array &vertices) +{ + const Vec3f ab = 0.5f * (vertices[0] + vertices[1]); + const Vec3f bc = 0.5f * (vertices[1] + vertices[2]); + const Vec3f ca = 0.5f * (vertices[2] + vertices[0]); + return { std::array{ vertices[0], ab, ca }, + std::array{ ab, vertices[1], bc }, + std::array{ bc, vertices[2], ca }, + std::array{ ab, bc, ca } }; +} + +using RegionPaintingSampler = std::function; + +static bool region_append_sampled_triangle(TriangleSelector::TriangleSplittingData &data, + const RegionPaintingSampler &sampler, + size_t target_triangle, + const std::array &vertices, + const std::array &barycentrics, + int depth, + int min_depth, + int max_depth) +{ + const Vec3f centroid = (vertices[0] + vertices[1] + vertices[2]) / 3.f; + const Vec3f centroid_bary = (barycentrics[0] + barycentrics[1] + barycentrics[2]) / 3.f; + const unsigned int s0 = sampler(target_triangle, vertices[0], barycentrics[0]); + const unsigned int s1 = sampler(target_triangle, vertices[1], barycentrics[1]); + const unsigned int s2 = sampler(target_triangle, vertices[2], barycentrics[2]); + const unsigned int sc = sampler(target_triangle, centroid, centroid_bary); + const bool states_differ = s0 != s1 || s1 != s2 || s2 != sc; + + if (depth < max_depth && (depth < min_depth || states_differ)) { + region_append_nibble(data.bitstream, 3u); + const std::array, 4> child_vertices = region_split_triangle(vertices); + const std::array, 4> child_barycentrics = region_split_triangle(barycentrics); + bool has_non_base = false; + for (int child_idx = 3; child_idx >= 0; --child_idx) { + has_non_base |= region_append_sampled_triangle(data, + sampler, + target_triangle, + child_vertices[size_t(child_idx)], + child_barycentrics[size_t(child_idx)], + depth + 1, + min_depth, + max_depth); + } + return has_non_base; + } + + region_append_leaf(data, sc); + return sc != 0; +} + static SimplifyTextureDataResult remap_rgba_from_snapshot(const SimplifyTextureDataSnapshot &snapshot, const indexed_triangle_set &simplified_mesh, const SimplifyTextureCancelFn &throw_on_cancel, @@ -996,6 +1156,110 @@ static SimplifyTextureDataResult remap_rgba_from_snapshot(const SimplifyTextureD return result; } +static void remap_region_painting_from_snapshot(const SimplifyTextureDataSnapshot &snapshot, + const indexed_triangle_set &simplified_mesh, + const SimplifyTextureCancelFn &throw_on_cancel, + const SimplifyTextureProgressFn &status_fn, + SimplifyTextureDataResult &result) +{ + result.region_painting_touched = snapshot.region_painting_present; + if (!snapshot.region_painting_present) { + set_progress(status_fn, 100); + return; + } + + if (!snapshot.region_painting_transfer_needed) { + set_progress(status_fn, 100); + return; + } + + if (simplified_mesh.indices.empty() || snapshot.source_mesh.indices.empty() || snapshot.region_facets_per_type.empty()) { + result.region_painting_remap_failed = true; + set_progress(status_fn, 100); + return; + } + + set_progress(status_fn, 0); + AABBMesh source_aabb(snapshot.source_mesh); + RegionFacetLookup region_lookup = make_region_facet_lookup(snapshot); + std::vector projection_cache(simplified_mesh.indices.size()); + std::vector projection_cache_valid(simplified_mesh.indices.size(), 0); + + const int max_depth = 4; + const float target_span = std::max(mesh_max_axis_span(simplified_mesh) / 120.f, 0.3f); + const std::array root_barycentrics = { + Vec3f(1.f, 0.f, 0.f), + Vec3f(0.f, 1.f, 0.f), + Vec3f(0.f, 0.f, 1.f) + }; + + RegionPaintingSampler sampler = [&](size_t target_triangle, const Vec3f &point, const Vec3f &target_barycentric) { + size_t source_triangle = 0; + Vec3f source_barycentric = Vec3f(1.f / 3.f, 1.f / 3.f, 1.f / 3.f); + bool source_projected = false; + if (target_triangle < projection_cache.size()) { + if (projection_cache_valid[target_triangle] == 0) { + std::array target_vertices; + if (valid_triangle_vertices(simplified_mesh, target_triangle, target_vertices)) + projection_cache[target_triangle] = make_fast_triangle_projection(snapshot, source_aabb, target_vertices); + projection_cache_valid[target_triangle] = 1; + } + source_projected = projected_source_from_fast_triangle(projection_cache[target_triangle], + target_barycentric, + source_triangle, + source_barycentric); + } + if (!source_projected && !project_to_source(snapshot, source_aabb, point, source_triangle, source_barycentric)) + return 0u; + + std::array source_vertices; + if (!valid_triangle_vertices(snapshot.source_mesh, source_triangle, source_vertices)) + return 0u; + const Vec3f source_point = source_vertices[0] * source_barycentric.x() + + source_vertices[1] * source_barycentric.y() + + source_vertices[2] * source_barycentric.z(); + return sample_region_state_at_source(snapshot, region_lookup, source_triangle, source_point); + }; + + TriangleSelector::TriangleSplittingData data; + data.triangles_to_split.reserve(simplified_mesh.indices.size()); + size_t sample_counter = 0; + for (size_t tri_idx = 0; tri_idx < simplified_mesh.indices.size(); ++tri_idx) { + if ((++sample_counter & 255u) == 0) { + check_cancel(throw_on_cancel); + set_progress(status_fn, int((uint64_t(tri_idx) * 100u) / std::max(simplified_mesh.indices.size(), 1))); + } + + std::array vertices; + if (!valid_triangle_vertices(simplified_mesh, tri_idx, vertices)) + continue; + + const size_t bitstream_start = data.bitstream.size(); + const int min_depth = texture_mapping_depth_from_span(triangle_max_edge_length(vertices), target_span, max_depth); + const bool has_non_base = region_append_sampled_triangle(data, + sampler, + tri_idx, + vertices, + root_barycentrics, + 0, + min_depth, + max_depth); + if (has_non_base) + data.triangles_to_split.emplace_back(int(tri_idx), int(bitstream_start)); + else + data.bitstream.resize(bitstream_start); + } + + check_cancel(throw_on_cancel); + set_progress(status_fn, 100); + data.triangles_to_split.shrink_to_fit(); + data.bitstream.shrink_to_fit(); + if (!data.triangles_to_split.empty()) { + result.region_painting_data = std::move(data); + result.region_painting_valid = true; + } +} + static bool refresh_result_preview_from_raw(SimplifyTextureDataResult &result) { ImageMapRawFilamentOffsetAtlas atlas; @@ -1204,6 +1468,11 @@ static void touch_imported_texture(ModelVolume &volume) } // namespace +bool model_volume_region_painting_needs_remap(const ModelVolume &volume) +{ + return region_painting_data_needs_remap(volume); +} + SimplifyTextureDataSnapshot snapshot_simplify_texture_data(const ModelVolume &volume) { SimplifyTextureDataSnapshot snapshot; @@ -1211,17 +1480,22 @@ SimplifyTextureDataSnapshot snapshot_simplify_texture_data(const ModelVolume &vo if (its.vertices.empty() || its.indices.empty()) return snapshot; + bool found_color_source = false; if (!volume.texture_mapping_color_facets.empty()) { snapshot.source = SimplifyColorSource::RgbaData; snapshot.source_mesh = its; snapshot.rgba_metadata_json = volume.texture_mapping_color_facets.metadata_json(); volume.texture_mapping_color_facets.get_facet_triangles(volume, snapshot.rgba_facets); if (!snapshot.rgba_facets.empty()) - return snapshot; - snapshot = SimplifyTextureDataSnapshot(); + found_color_source = true; + else { + snapshot.source = SimplifyColorSource::None; + snapshot.source_mesh = indexed_triangle_set(); + snapshot.rgba_metadata_json.clear(); + } } - if (volume_has_valid_image_texture(volume)) { + if (!found_color_source && volume_has_valid_image_texture(volume)) { snapshot.source = SimplifyColorSource::ImageTexture; snapshot.source_mesh = its; snapshot.texture_rgba.assign(volume.imported_texture_rgba.begin(), volume.imported_texture_rgba.end()); @@ -1235,21 +1509,45 @@ SimplifyTextureDataSnapshot snapshot_simplify_texture_data(const ModelVolume &vo snapshot.texture_raw_metadata_json = volume.imported_texture_raw_metadata_json; snapshot.uv_map_generator_version = volume.uv_map_generator_version; if (snapshot_has_valid_rgba_texture(snapshot) || snapshot_has_valid_raw_atlas(snapshot)) - return snapshot; - snapshot = SimplifyTextureDataSnapshot(); + found_color_source = true; + else { + snapshot.source = SimplifyColorSource::None; + snapshot.source_mesh = indexed_triangle_set(); + snapshot.texture_rgba.clear(); + snapshot.texture_uvs_per_face.clear(); + snapshot.texture_uv_valid.clear(); + snapshot.texture_raw_filament_offsets.clear(); + snapshot.texture_width = 0; + snapshot.texture_height = 0; + snapshot.texture_raw_channels = 0; + snapshot.texture_raw_metadata_json.clear(); + snapshot.uv_map_generator_version = 0; + } } - if (!volume.imported_vertex_colors_rgba.empty() && volume.imported_vertex_colors_rgba.size() == its.vertices.size()) { + if (!found_color_source && + !volume.imported_vertex_colors_rgba.empty() && + volume.imported_vertex_colors_rgba.size() == its.vertices.size()) { snapshot.source = SimplifyColorSource::VertexColors; snapshot.source_mesh = its; snapshot.vertex_colors_rgba.assign(volume.imported_vertex_colors_rgba.begin(), volume.imported_vertex_colors_rgba.end()); + found_color_source = true; + } + + snapshot.region_painting_present = !volume.mmu_segmentation_facets.empty(); + snapshot.region_base_filament_id = int(volume_base_region_filament_id(volume)); + snapshot.region_painting_transfer_needed = region_painting_data_needs_remap(volume); + if (snapshot.region_painting_transfer_needed) { + if (!found_color_source) + snapshot.source_mesh = its; + volume.mmu_segmentation_facets.get_facet_triangles(volume, snapshot.region_facets_per_type); } return snapshot; } void transform_simplify_texture_data_snapshot(SimplifyTextureDataSnapshot &snapshot, const Transform3d &transform) { - if (snapshot.source == SimplifyColorSource::None) + if (snapshot.source == SimplifyColorSource::None && !snapshot.region_painting_transfer_needed) return; if (!snapshot.source_mesh.vertices.empty() && !snapshot.source_mesh.indices.empty()) { @@ -1263,50 +1561,93 @@ void transform_simplify_texture_data_snapshot(SimplifyTextureDataSnapshot &snaps for (Vec3f &vertex : facet.vertices) vertex = (transform * vertex.cast()).cast(); } + + for (auto &facets : snapshot.region_facets_per_type) + for (TriangleSelector::FacetStateTriangle &facet : facets) + for (Vec3f &vertex : facet.vertices) + vertex = (transform * vertex.cast()).cast(); } SimplifyTextureDataResult remap_simplify_texture_data(const SimplifyTextureDataSnapshot &snapshot, const indexed_triangle_set &simplified_mesh, const SimplifyTextureCancelFn &throw_on_cancel, - const SimplifyTextureProgressFn &status_fn) + const SimplifyTextureProgressFn &status_fn, + const SimplifyTextureDataRemapOptions &options) { - if (snapshot.source == SimplifyColorSource::None || simplified_mesh.indices.empty()) { + const bool has_color_data = snapshot.source != SimplifyColorSource::None; + const bool remap_color = has_color_data && options.remap_color_data && !simplified_mesh.indices.empty(); + const bool remap_region = snapshot.region_painting_present && + options.remap_region_painting && + snapshot.region_painting_transfer_needed && + !simplified_mesh.indices.empty(); + + if (!remap_color && !remap_region) { set_progress(status_fn, 100); SimplifyTextureDataResult result; - result.source = snapshot.source; + result.source = options.remap_color_data ? snapshot.source : SimplifyColorSource::None; + result.region_painting_touched = snapshot.region_painting_present; return result; } - switch (snapshot.source) { - case SimplifyColorSource::RgbaData: - return remap_rgba_from_snapshot(snapshot, simplified_mesh, throw_on_cancel, status_fn); - case SimplifyColorSource::ImageTexture: { - SimplifyTextureDataResult result = remap_image_texture_from_snapshot(snapshot, simplified_mesh, throw_on_cancel, status_fn); - if (result.source == SimplifyColorSource::ImageTexture) - return result; - SimplifyTextureDataSnapshot fallback_snapshot = snapshot; - limit_snapshot_texture_resolution(fallback_snapshot); - if (!snapshot_has_valid_rgba_texture(fallback_snapshot) && snapshot_has_valid_raw_atlas(fallback_snapshot)) { - ImageMapRawFilamentOffsetAtlas atlas; - atlas.width = fallback_snapshot.texture_width; - atlas.height = fallback_snapshot.texture_height; - atlas.channels = fallback_snapshot.texture_raw_channels; - atlas.offsets = fallback_snapshot.texture_raw_filament_offsets; - atlas.metadata_json = fallback_snapshot.texture_raw_metadata_json; - atlas.filaments = image_map_raw_filaments_from_metadata_json(atlas.metadata_json, atlas.channels); - fallback_snapshot.texture_rgba = image_map_raw_filament_offset_preview_rgba(atlas); + auto scaled_progress = [&status_fn](int offset, int span) { + return [status_fn, offset, span](int percent) { + set_progress(status_fn, offset + int(std::round(float(std::clamp(percent, 0, 100)) * float(span) / 100.f))); + }; + }; + + const bool split_progress = remap_color && snapshot.region_painting_present; + SimplifyTextureDataResult result; + if (remap_color) { + const SimplifyTextureProgressFn color_progress = split_progress ? scaled_progress(0, 80) : status_fn; + switch (snapshot.source) { + case SimplifyColorSource::RgbaData: + result = remap_rgba_from_snapshot(snapshot, simplified_mesh, throw_on_cancel, color_progress); + break; + case SimplifyColorSource::ImageTexture: { + result = remap_image_texture_from_snapshot(snapshot, simplified_mesh, throw_on_cancel, color_progress); + if (result.source == SimplifyColorSource::ImageTexture) + break; + SimplifyTextureDataSnapshot fallback_snapshot = snapshot; + limit_snapshot_texture_resolution(fallback_snapshot); + if (!snapshot_has_valid_rgba_texture(fallback_snapshot) && snapshot_has_valid_raw_atlas(fallback_snapshot)) { + ImageMapRawFilamentOffsetAtlas atlas; + atlas.width = fallback_snapshot.texture_width; + atlas.height = fallback_snapshot.texture_height; + atlas.channels = fallback_snapshot.texture_raw_channels; + atlas.offsets = fallback_snapshot.texture_raw_filament_offsets; + atlas.metadata_json = fallback_snapshot.texture_raw_metadata_json; + atlas.filaments = image_map_raw_filaments_from_metadata_json(atlas.metadata_json, atlas.channels); + fallback_snapshot.texture_rgba = image_map_raw_filament_offset_preview_rgba(atlas); + } + result = remap_rgba_from_snapshot(fallback_snapshot, simplified_mesh, throw_on_cancel, color_progress); + result.remap_failed = true; + result.used_fallback_rgba = result.source == SimplifyColorSource::RgbaData && result.rgba_data != nullptr; + break; } - result = remap_rgba_from_snapshot(fallback_snapshot, simplified_mesh, throw_on_cancel, status_fn); - result.remap_failed = true; - result.used_fallback_rgba = result.source == SimplifyColorSource::RgbaData && result.rgba_data != nullptr; - return result; + case SimplifyColorSource::VertexColors: + result = remap_vertex_colors_from_snapshot(snapshot, simplified_mesh, throw_on_cancel, color_progress); + break; + case SimplifyColorSource::None: + break; + } + } else { + result.source = SimplifyColorSource::None; } - case SimplifyColorSource::VertexColors: - return remap_vertex_colors_from_snapshot(snapshot, simplified_mesh, throw_on_cancel, status_fn); - case SimplifyColorSource::None: - break; + + if (snapshot.region_painting_present) { + SimplifyTextureProgressFn region_progress = status_fn; + if (split_progress) + region_progress = scaled_progress(80, 20); + if (options.remap_region_painting) + remap_region_painting_from_snapshot(snapshot, simplified_mesh, throw_on_cancel, region_progress, result); + else { + result.region_painting_touched = true; + set_progress(region_progress, 100); + } } - return SimplifyTextureDataResult(); + + set_progress(status_fn, 100); + return result; } void apply_simplify_texture_data_result(ModelVolume &volume, SimplifyTextureDataResult &&result) @@ -1352,6 +1693,16 @@ void apply_simplify_texture_data_result(ModelVolume &volume, SimplifyTextureData touch_imported_texture(volume); break; } + + if (result.region_painting_touched) { + if (result.region_painting_valid && !result.region_painting_data.triangles_to_split.empty()) { + TriangleSelector selector(volume.mesh()); + selector.deserialize(result.region_painting_data); + volume.mmu_segmentation_facets.set(selector); + } else { + volume.mmu_segmentation_facets.reset(); + } + } } } // namespace Slic3r diff --git a/src/libslic3r/ModelTextureDataRemap.hpp b/src/libslic3r/ModelTextureDataRemap.hpp index c489af82114..16ba2aac4fc 100644 --- a/src/libslic3r/ModelTextureDataRemap.hpp +++ b/src/libslic3r/ModelTextureDataRemap.hpp @@ -38,6 +38,10 @@ struct SimplifyTextureDataSnapshot std::string texture_raw_metadata_json; int uv_map_generator_version { 0 }; std::vector vertex_colors_rgba; + bool region_painting_present { false }; + bool region_painting_transfer_needed { false }; + int region_base_filament_id { 1 }; + std::vector> region_facets_per_type; }; struct SimplifyTextureDataResult @@ -56,11 +60,23 @@ struct SimplifyTextureDataResult std::string texture_raw_metadata_json; int uv_map_generator_version { 0 }; std::vector vertex_colors_rgba; + bool region_painting_touched { false }; + bool region_painting_valid { false }; + bool region_painting_remap_failed { false }; + TriangleSelector::TriangleSplittingData region_painting_data; +}; + +struct SimplifyTextureDataRemapOptions +{ + bool remap_color_data { true }; + bool remap_region_painting { true }; }; using SimplifyTextureCancelFn = std::function; using SimplifyTextureProgressFn = std::function; +bool model_volume_region_painting_needs_remap(const ModelVolume &volume); + SimplifyTextureDataSnapshot snapshot_simplify_texture_data(const ModelVolume &volume); void transform_simplify_texture_data_snapshot(SimplifyTextureDataSnapshot &snapshot, const Transform3d &transform); @@ -68,7 +84,8 @@ void transform_simplify_texture_data_snapshot(SimplifyTextureDataSnapshot &snaps SimplifyTextureDataResult remap_simplify_texture_data(const SimplifyTextureDataSnapshot &snapshot, const indexed_triangle_set &simplified_mesh, const SimplifyTextureCancelFn &throw_on_cancel = {}, - const SimplifyTextureProgressFn &status_fn = {}); + const SimplifyTextureProgressFn &status_fn = {}, + const SimplifyTextureDataRemapOptions &options = SimplifyTextureDataRemapOptions()); void apply_simplify_texture_data_result(ModelVolume &volume, SimplifyTextureDataResult &&result); diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index c3c40f0ab4a..7b55361254f 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -240,14 +240,12 @@ static std::vector collect_texture_mapping_vertex_color_match_warni if (has_uv_texture_reference_but_no_image) return { L("Image Texture Mapping is used on this object and OBJ UVs were found, but the texture image could not be loaded. " - "Texture color matching will be skipped for this object. " - "(This importer path currently expects a PNG image texture.)") + "Texture color matching will be skipped for this object.") }; return { L("Image Texture Mapping is used on this object, but no imported vertex colors or OBJ UV texture data were found. " - "Texture color matching will be skipped for this object. " - "(This importer path currently expects a PNG image texture.)") + "Texture color matching will be skipped for this object.") }; } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index af55391c427..6b7104a7cf1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -6028,8 +6028,14 @@ void ObjectList::fix_through_cgal() color_remap_stats.remap_canceled |= stats.remap_canceled; color_remap_stats.remap_failed |= stats.remap_failed; color_remap_stats.used_fallback_rgba |= stats.used_fallback_rgba; + color_remap_stats.had_region_painting |= stats.had_region_painting; + color_remap_stats.region_remap_requested |= stats.region_remap_requested; + color_remap_stats.region_remap_skipped |= stats.region_remap_skipped; + color_remap_stats.region_remap_failed |= stats.region_remap_failed; color_remap_stats.volumes_remapped += stats.volumes_remapped; color_remap_stats.volumes_cleared += stats.volumes_cleared; + color_remap_stats.region_volumes_remapped += stats.region_volumes_remapped; + color_remap_stats.region_volumes_cleared += stats.region_volumes_cleared; }; std::vector obj_idxs, vol_idxs; @@ -6165,6 +6171,12 @@ void ObjectList::fix_through_cgal() msg += "\n\n" + _L("Color remapping was skipped; stale color data was cleared from repaired parts."); if (color_remap_stats.remap_canceled && color_remap_stats.volumes_cleared > 0) msg += "\n\n" + _L("Color remapping was canceled; stale color data was cleared from repaired parts."); + if (color_remap_stats.region_remap_failed && color_remap_stats.region_volumes_cleared > 0) + msg += "\n\n" + _L("Some region painting could not be remapped after repair and was cleared."); + if (color_remap_stats.region_remap_skipped && color_remap_stats.region_volumes_cleared > 0) + msg += "\n\n" + _L("Region painting remapping was skipped; stale region painting was cleared from repaired parts."); + if (color_remap_stats.region_remap_requested && color_remap_stats.remap_canceled && color_remap_stats.region_volumes_cleared > 0) + msg += "\n\n" + _L("Region painting remapping was canceled; stale region painting was cleared from repaired parts."); if (msg.IsEmpty()) msg = _L("Repairing was canceled"); plater->get_notification_manager()->push_notification(NotificationType::CgalFinished, NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel, into_u8(msg)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp index 5243dd4fe8a..f2406fd9700 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp @@ -715,6 +715,21 @@ void GLGizmoAdvancedCut::perform_cut(const Selection& selection) NotificationType::CustomNotification, NotificationManager::NotificationLevel::PrintInfoNotificationLevel, _u8L("Color remapping was canceled; stale color data was cleared from repaired parts.")); + if (remap_stats.region_remap_failed && remap_stats.region_volumes_cleared > 0) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Some region painting could not be remapped after repair and was cleared.")); + if (remap_stats.region_remap_skipped && remap_stats.region_volumes_cleared > 0) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Region painting remapping was skipped; stale region painting was cleared from repaired parts.")); + if (remap_stats.region_remap_requested && remap_stats.remap_canceled && remap_stats.region_volumes_cleared > 0) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Region painting remapping was canceled; stale region painting was cleared from repaired parts.")); return !remap_stats.remap_canceled; }; ProgressDialog progress_dlg(_L("Repairing model object"), "", 100, find_toplevel_parent(plater), wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT, true); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 964316b250d..11574393157 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -3395,6 +3395,21 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) NotificationType::CustomNotification, NotificationManager::NotificationLevel::PrintInfoNotificationLevel, _u8L("Color remapping was canceled; stale color data was cleared from repaired parts.")); + if (remap_stats.region_remap_failed && remap_stats.region_volumes_cleared > 0) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Some region painting could not be remapped after repair and was cleared.")); + if (remap_stats.region_remap_skipped && remap_stats.region_volumes_cleared > 0) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Region painting remapping was skipped; stale region painting was cleared from repaired parts.")); + if (remap_stats.region_remap_requested && remap_stats.remap_canceled && remap_stats.region_volumes_cleared > 0) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Region painting remapping was canceled; stale region painting was cleared from repaired parts.")); return !remap_stats.remap_canceled; }; ProgressDialog progress_dlg(_L("Repairing model object"), "", 100, find_toplevel_parent(plater), wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT, true); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index fe8d1b09895..b378bb93cd8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -195,6 +195,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi m_volume = act_volume; m_configuration.decimate_ratio = 50.; // default value + m_configuration.remap_region_painting = true; m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size()); init_model(m_volume->mesh().its); @@ -343,6 +344,10 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi m_imgui->disabled_end(); // use_count m_imgui->bbl_checkbox(_L("Show wireframe").c_str(), m_show_wireframe); + if (model_volume_region_painting_needs_remap(*m_volume)) { + if (m_imgui->bbl_checkbox(_L("Remap region painting to simplified mesh").c_str(), m_configuration.remap_region_painting)) + start_process = true; + } const wxString skip_color_conversion_label = color_conversion_in_progress ? _L("Skip color conversion (in progress)") : _L("Skip color conversion"); @@ -551,12 +556,14 @@ void GLGizmoSimplify::process() // Initialize. uint32_t triangle_count = 0; float max_error = std::numeric_limits::max(); + bool remap_region_painting = true; { std::lock_guard lk(m_state_mutex); if (m_state.config.use_count) triangle_count = m_state.config.wanted_count; if (! m_state.config.use_count) max_error = m_state.config.max_error; + remap_region_painting = m_state.config.remap_region_painting; m_state.progress = 0.f; m_state.result.reset(); m_state.color_conversion_in_progress = false; @@ -574,12 +581,15 @@ void GLGizmoSimplify::process() set_color_conversion_in_progress(true); try { throw_on_color_conversion_stop(); + SimplifyTextureDataRemapOptions remap_options; + remap_options.remap_region_painting = remap_region_painting; texture_result = remap_simplify_texture_data(texture_snapshot, *its, throw_on_color_conversion_stop, [&statusfn](int percent) { statusfn(90.f + float(std::clamp(percent, 0, 100)) * 0.1f); - }); + }, remap_options); } catch (SimplifyColorConversionSkippedException &) { texture_result = SimplifyTextureDataResult(); + texture_result.region_painting_touched = texture_snapshot.region_painting_present; } catch (...) { set_color_conversion_in_progress(false); throw; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index 2cdc84dc66d..ced14c9ff16 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -62,11 +62,13 @@ private: float decimate_ratio = 50.f; // in percent uint32_t wanted_count = 0; // initialize by percents float max_error = 1.; // maximal quadric error + bool remap_region_painting = true; void fix_count_by_ratio(size_t triangle_count); bool operator==(const Configuration& rhs) { return (use_count == rhs.use_count && decimate_ratio == rhs.decimate_ratio - && wanted_count == rhs.wanted_count && max_error == rhs.max_error); + && wanted_count == rhs.wanted_count && max_error == rhs.max_error + && remap_region_painting == rhs.remap_region_painting); } bool operator!=(const Configuration& rhs) { return ! (*this == rhs); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 9303eaf7216..d2064802189 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -19331,14 +19331,13 @@ void Plater::clear_before_change_mesh(int obj_idx) { ModelObject* mo = model().objects[obj_idx]; - // If there are custom supports/seams/mmu/fuzzy skin segmentation, remove them. Fixed mesh + // If there are custom supports/seams/fuzzy skin segmentation, remove them. Fixed mesh // may be different and they would make no sense. bool paint_removed = false; for (ModelVolume* mv : mo->volumes) { - paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty() || !mv->fuzzy_skin_facets.empty(); + paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || !mv->fuzzy_skin_facets.empty(); mv->supported_facets.reset(); mv->seam_facets.reset(); - mv->mmu_segmentation_facets.reset(); mv->fuzzy_skin_facets.reset(); } if (paint_removed) { @@ -19346,7 +19345,7 @@ void Plater::clear_before_change_mesh(int obj_idx) get_notification_manager()->push_notification( NotificationType::CustomSupportsAndSeamRemovedAfterRepair, NotificationManager::NotificationLevel::PrintInfoNotificationLevel, - _u8L("Custom supports and color region painting were removed before repairing.")); + _u8L("Custom supports and seam painting were removed before repairing.")); } } diff --git a/src/slic3r/Utils/FixModelByCgal.cpp b/src/slic3r/Utils/FixModelByCgal.cpp index effa22bf956..59de42c2faf 100644 --- a/src/slic3r/Utils/FixModelByCgal.cpp +++ b/src/slic3r/Utils/FixModelByCgal.cpp @@ -95,6 +95,11 @@ bool color_snapshot_is_valid(const SimplifyTextureDataSnapshot &snapshot) return snapshot.source != SimplifyColorSource::None; } +bool snapshot_has_mesh_bound_data(const SimplifyTextureDataSnapshot &snapshot) +{ + return snapshot.source != SimplifyColorSource::None || snapshot.region_painting_present; +} + bool volume_may_change_during_repair(const ModelVolume &volume, const ModelRepairOptions &options) { return options.weld_same_position_vertices || @@ -130,6 +135,12 @@ bool volume_has_remappable_color_data(const ModelVolume &volume) return volume.imported_vertex_colors_rgba.size() == its.vertices.size(); } +bool volume_has_transferable_region_painting(const ModelVolume &volume) +{ + const indexed_triangle_set &its = volume.mesh().its; + return !its.vertices.empty() && !its.indices.empty() && model_volume_region_painting_needs_remap(volume); +} + bool target_has_remappable_color_data(const ModelObject &model_object, int volume_idx) { const size_t start_volume = volume_idx == -1 ? 0 : size_t(volume_idx); @@ -143,6 +154,19 @@ bool target_has_remappable_color_data(const ModelObject &model_object, int volum return false; } +bool target_has_transferable_region_painting(const ModelObject &model_object, int volume_idx) +{ + const size_t start_volume = volume_idx == -1 ? 0 : size_t(volume_idx); + const size_t end_volume = + volume_idx == -1 ? model_object.volumes.size() : std::min(model_object.volumes.size(), size_t(volume_idx) + 1); + for (size_t idx = start_volume; idx < end_volume; ++idx) { + const ModelVolume *volume = model_object.volumes[idx]; + if (volume != nullptr && volume_has_transferable_region_painting(*volume)) + return true; + } + return false; +} + ColorSnapshotByVolume collect_color_snapshots(ModelObject &model_object, int volume_idx, const ModelRepairOptions &options) { ColorSnapshotByVolume snapshots; @@ -154,7 +178,7 @@ ColorSnapshotByVolume collect_color_snapshots(ModelObject &model_object, int vol if (volume == nullptr || !volume_may_change_during_repair(*volume, options)) continue; SimplifyTextureDataSnapshot snapshot = snapshot_simplify_texture_data(*volume); - if (color_snapshot_is_valid(snapshot)) + if (snapshot_has_mesh_bound_data(snapshot)) snapshots.emplace(volume, std::move(snapshot)); } return snapshots; @@ -163,7 +187,9 @@ ColorSnapshotByVolume collect_color_snapshots(ModelObject &model_object, int vol bool ask_repair_options(GUI::ProgressDialog &progress_dialog, ModelRepairOptions &options, ModelRepairColorRemapChoice &color_remap_choice, - bool show_color_remap_option) + bool ®ion_painting_remap, + bool show_color_remap_option, + bool show_region_painting_remap_option) { wxDialog dialog(&progress_dialog, wxID_ANY, _L("Repair options"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); auto *top_sizer = new wxBoxSizer(wxVERTICAL); @@ -186,6 +212,13 @@ bool ask_repair_options(GUI::ProgressDialog &progress_dialog, top_sizer->Add(remap_colors_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 12); } + wxCheckBox *remap_region_painting_checkbox = nullptr; + if (show_region_painting_remap_option) { + remap_region_painting_checkbox = new wxCheckBox(&dialog, wxID_ANY, _L("Remap region painting to repaired mesh")); + remap_region_painting_checkbox->SetValue(region_painting_remap); + top_sizer->Add(remap_region_painting_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 12); + } + wxSizer *buttons = dialog.CreateSeparatedButtonSizer(wxOK | wxCANCEL); if (buttons != nullptr) top_sizer->Add(buttons, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 12); @@ -200,6 +233,8 @@ bool ask_repair_options(GUI::ProgressDialog &progress_dialog, options.split_before_repair = split_checkbox->GetValue(); if (remap_colors_checkbox != nullptr) color_remap_choice = remap_colors_checkbox->GetValue() ? ModelRepairColorRemapChoice::Remap : ModelRepairColorRemapChoice::Skip; + if (remap_region_painting_checkbox != nullptr) + region_painting_remap = remap_region_painting_checkbox->GetValue(); return true; } @@ -248,14 +283,25 @@ void merge_color_remap_stats(ModelRepairColorRemapStats &dst, const ModelRepairC dst.remap_canceled |= src.remap_canceled; dst.remap_failed |= src.remap_failed; dst.used_fallback_rgba |= src.used_fallback_rgba; + dst.had_region_painting |= src.had_region_painting; + dst.region_remap_requested |= src.region_remap_requested; + dst.region_remap_skipped |= src.region_remap_skipped; + dst.region_remap_failed |= src.region_remap_failed; dst.volumes_remapped += src.volumes_remapped; dst.volumes_cleared += src.volumes_cleared; + dst.region_volumes_remapped += src.region_volumes_remapped; + dst.region_volumes_cleared += src.region_volumes_cleared; } -void clear_remappable_color_data(ModelVolume &volume, ModelRepairColorRemapStats &stats) +void clear_remappable_data(ModelVolume &volume, const SimplifyTextureDataSnapshot &snapshot, ModelRepairColorRemapStats &stats) { - apply_simplify_texture_data_result(volume, SimplifyTextureDataResult()); - ++stats.volumes_cleared; + SimplifyTextureDataResult result; + result.region_painting_touched = snapshot.region_painting_present; + apply_simplify_texture_data_result(volume, std::move(result)); + if (color_snapshot_is_valid(snapshot)) + ++stats.volumes_cleared; + if (snapshot.region_painting_present) + ++stats.region_volumes_cleared; } bool result_preserved_color(const SimplifyTextureDataSnapshot &snapshot, const SimplifyTextureDataResult &result) @@ -276,21 +322,24 @@ bool result_preserved_color(const SimplifyTextureDataSnapshot &snapshot, const S bool remap_or_clear_color_data(ModelVolume &volume, const SimplifyTextureDataSnapshot &snapshot, - bool remap_requested, + bool remap_color_requested, + bool remap_region_painting_requested, std::atomic &cancel_requested, const std::function &status_fn, ModelRepairColorRemapStats &stats) { - if (!color_snapshot_is_valid(snapshot)) + if (!snapshot_has_mesh_bound_data(snapshot)) return false; - if (!remap_requested) { + if (color_snapshot_is_valid(snapshot) && !remap_color_requested) stats.remap_skipped = true; - clear_remappable_color_data(volume, stats); - return false; - } + if (snapshot.region_painting_transfer_needed && !remap_region_painting_requested) + stats.region_remap_skipped = true; try { + SimplifyTextureDataRemapOptions options; + options.remap_color_data = remap_color_requested; + options.remap_region_painting = remap_region_painting_requested; SimplifyTextureDataResult result = remap_simplify_texture_data( snapshot, volume.mesh().its, @@ -298,25 +347,42 @@ bool remap_or_clear_color_data(ModelVolume &volume, if (cancel_requested) throw ColorRemapCanceledException(); }, - status_fn); + status_fn, + options); stats.used_fallback_rgba |= result.used_fallback_rgba; stats.remap_failed |= result.remap_failed && !result.used_fallback_rgba; + stats.region_remap_failed |= result.region_painting_remap_failed; const bool preserved = result_preserved_color(snapshot, result); + const bool region_preserved = result.region_painting_touched && result.region_painting_valid; apply_simplify_texture_data_result(volume, std::move(result)); - if (preserved) { - ++stats.volumes_remapped; + if (color_snapshot_is_valid(snapshot)) { + if (preserved) + ++stats.volumes_remapped; + else if (remap_color_requested) { + stats.remap_failed = true; + ++stats.volumes_cleared; + } else { + ++stats.volumes_cleared; + } + } + if (snapshot.region_painting_present) { + if (region_preserved) + ++stats.region_volumes_remapped; + else + ++stats.region_volumes_cleared; + } + if (preserved || region_preserved) { return true; - } else { - stats.remap_failed = true; - ++stats.volumes_cleared; } } catch (ColorRemapCanceledException &) { stats.remap_canceled = true; throw; } catch (std::exception &) { stats.remap_failed = true; - clear_remappable_color_data(volume, stats); + if (snapshot.region_painting_transfer_needed) + stats.region_remap_failed = true; + clear_remappable_data(volume, snapshot, stats); } return false; } @@ -365,10 +431,13 @@ bool fix_model_with_cgal_gui(ModelObject &model_object, if (!active_prompt_state.repair_options_selected) { const bool show_color_remap_option = target_has_remappable_color_data(model_object, volume_idx); + const bool show_region_painting_remap_option = target_has_transferable_region_painting(model_object, volume_idx); if (!ask_repair_options(progress_dialog, active_prompt_state.repair_options, active_prompt_state.color_remap_choice, - show_color_remap_option)) { + active_prompt_state.region_painting_remap, + show_color_remap_option, + show_region_painting_remap_option)) { active_prompt_state.repair_options_canceled = true; return false; } @@ -379,15 +448,23 @@ bool fix_model_with_cgal_gui(ModelObject &model_object, ColorSnapshotByVolume color_snapshots = collect_color_snapshots(model_object, volume_idx, repair_options); ModelRepairColorRemapChoice color_remap_choice = active_prompt_state.color_remap_choice; + const bool region_painting_remap_requested = active_prompt_state.region_painting_remap; if (!color_snapshots.empty()) { - local_color_stats.had_color_data = true; + bool had_transferable_region_painting = false; + for (const auto &snapshot_pair : color_snapshots) { + local_color_stats.had_color_data |= color_snapshot_is_valid(snapshot_pair.second); + local_color_stats.had_region_painting |= snapshot_pair.second.region_painting_present; + had_transferable_region_painting |= snapshot_pair.second.region_painting_transfer_needed; + } if (color_remap_choice == ModelRepairColorRemapChoice::Cancel) { if (color_remap_stats) merge_color_remap_stats(*color_remap_stats, local_color_stats); return false; } - local_color_stats.remap_requested = color_remap_choice == ModelRepairColorRemapChoice::Remap; - local_color_stats.remap_skipped = color_remap_choice == ModelRepairColorRemapChoice::Skip; + local_color_stats.remap_requested = local_color_stats.had_color_data && color_remap_choice == ModelRepairColorRemapChoice::Remap; + local_color_stats.remap_skipped = local_color_stats.had_color_data && color_remap_choice == ModelRepairColorRemapChoice::Skip; + local_color_stats.region_remap_requested = had_transferable_region_painting && region_painting_remap_requested; + local_color_stats.region_remap_skipped = had_transferable_region_painting && !region_painting_remap_requested; } // Orca: Synchronization primitives for progress updates between worker thread and GUI. @@ -407,6 +484,7 @@ bool fix_model_with_cgal_gui(ModelObject &model_object, bool success = false; size_t ivolume = 0; const bool remap_requested = color_remap_choice == ModelRepairColorRemapChoice::Remap; + const bool remap_region_painting_requested = region_painting_remap_requested; // Orca: Lambda for updating progress from worker thread. auto on_progress = [&mtx, &condition, &ivolume, &model_object, &progress](RepairProgressStage stage, const char *msg, unsigned prcnt) { @@ -445,6 +523,7 @@ bool fix_model_with_cgal_gui(ModelObject &model_object, &fix_result, &local_color_stats, remap_requested, + remap_region_painting_requested, repair_options, color_snapshots = std::move(color_snapshots)]() { try { @@ -518,12 +597,18 @@ bool fix_model_with_cgal_gui(ModelObject &model_object, else if (pre_split_weld_changed && color_snapshot != nullptr && !color_part_states.empty()) color_part_states.front() = ColorRemapPartState::Pending; - auto clear_pending_color_parts = [&model_object, &local_color_stats, ivolume, part_end, &color_part_states]() { + auto clear_pending_color_parts = [&model_object, + &local_color_stats, + ivolume, + part_end, + &color_part_states, + color_snapshot]() { for (size_t part_idx = ivolume; part_idx <= part_end && part_idx < model_object.volumes.size(); ++part_idx) { ColorRemapPartState &state = color_part_states[part_idx - ivolume]; if (state != ColorRemapPartState::Pending) continue; - clear_remappable_color_data(*model_object.volumes[part_idx], local_color_stats); + if (color_snapshot != nullptr) + clear_remappable_data(*model_object.volumes[part_idx], *color_snapshot, local_color_stats); state = ColorRemapPartState::Cleared; } }; @@ -557,6 +642,7 @@ bool fix_model_with_cgal_gui(ModelObject &model_object, *part_volume, *color_snapshot, remap_requested, + remap_region_painting_requested, cancel_requested, [on_progress](int percent) { const int clamped_percent = std::clamp(percent, 0, 100); diff --git a/src/slic3r/Utils/FixModelByCgal.hpp b/src/slic3r/Utils/FixModelByCgal.hpp index 87264f94606..44205fff2b2 100644 --- a/src/slic3r/Utils/FixModelByCgal.hpp +++ b/src/slic3r/Utils/FixModelByCgal.hpp @@ -19,8 +19,14 @@ struct ModelRepairColorRemapStats bool remap_canceled = false; bool remap_failed = false; bool used_fallback_rgba = false; + bool had_region_painting = false; + bool region_remap_requested = false; + bool region_remap_skipped = false; + bool region_remap_failed = false; size_t volumes_remapped = 0; size_t volumes_cleared = 0; + size_t region_volumes_remapped = 0; + size_t region_volumes_cleared = 0; }; struct ModelRepairOptions @@ -42,6 +48,7 @@ struct ModelRepairPromptState bool repair_options_canceled = false; ModelRepairOptions repair_options; ModelRepairColorRemapChoice color_remap_choice = ModelRepairColorRemapChoice::Remap; + bool region_painting_remap = true; }; // Return false if fixing was canceled. fix_result is empty on success.