From f6f51235019deeee9c45cebf1402b5714d3ee051 Mon Sep 17 00:00:00 2001 From: sentientstardust Date: Tue, 26 May 2026 01:23:22 +0100 Subject: [PATCH] Preserve color data when subdividing mesh - Allow setting blend pattern length for top surface coloring - Reduce image bleed in image projection --- src/libslic3r/Fill/Fill.cpp | 27 +- src/libslic3r/LayerRegion.cpp | 64 ++- src/libslic3r/TextureMapping.cpp | 10 + src/libslic3r/TextureMapping.hpp | 7 +- src/slic3r/GUI/GUI_Factories.cpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 363 +++++++++++++++--- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 88 ++++- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 8 + src/slic3r/GUI/Plater.cpp | 69 +++- 9 files changed, 562 insertions(+), 76 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 37a4799ec80..8523e34d57b 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -426,6 +426,7 @@ struct TopSurfaceImageRegionPlan { bool color_lower_surfaces = true; int colored_top_layers = TextureMappingZone::DefaultTopSurfaceImageColoredTopLayers; int contoning_stack_layers = TextureMappingZone::DefaultTopSurfaceContoningStackLayers; + int contoning_pattern_filaments = TextureMappingZone::DefaultTopSurfaceContoningPatternFilaments; float contoning_min_feature_mm = 2.f; float contoning_external_width_mm = 0.4f; }; @@ -921,6 +922,19 @@ static ExPolygons top_surface_image_contoning_area_from_grid_label(const std::ve return top_surface_image_contoning_clean_area(std::move(cells), clip_area, blocked_area, min_feature_mm, throw_if_canceled); } +static int top_surface_image_contoning_pattern_filaments(int stack_layers, int configured_pattern_filaments) +{ + const int clamped_stack_layers = + std::clamp(stack_layers, + TextureMappingZone::MinTopSurfaceContoningStackLayers, + TextureMappingZone::MaxTopSurfaceContoningStackLayers); + const int clamped_pattern_filaments = + std::clamp(configured_pattern_filaments, + TextureMappingZone::MinTopSurfaceContoningPatternFilaments, + TextureMappingZone::MaxTopSurfaceContoningPatternFilaments); + return std::max(1, std::min(clamped_stack_layers, clamped_pattern_filaments)); +} + static std::vector top_surface_image_contoning_vector_regions( const TopSurfaceImageRegionPlan &plan, const Layer &source_layer, @@ -979,6 +993,8 @@ static std::vector top_surface_image_conto const int stack_layers = std::clamp(plan.contoning_stack_layers, TextureMappingZone::MinTopSurfaceContoningStackLayers, TextureMappingZone::MaxTopSurfaceContoningStackLayers); + const int pattern_filaments = + top_surface_image_contoning_pattern_filaments(stack_layers, plan.contoning_pattern_filaments); const std::vector source_stack_areas = top_surface_image_contoning_stack_areas(source_layer, plan.zone_id, stack_layers, source_surface, throw_if_canceled); @@ -999,7 +1015,7 @@ static std::vector top_surface_image_conto top_surface_image_contoning_local_stack_layers_at_point(sample_point, source_stack_areas, stack_layers); if (depth >= local_stack_layers) continue; - const int solve_layers = std::min({ local_stack_layers, stack_layers, 4 }); + const int solve_layers = std::min({ local_stack_layers, stack_layers, pattern_filaments }); if (solve_layers <= 0) continue; const float sample_x_mm = unscale(sample_point.x()); @@ -1117,6 +1133,8 @@ static void top_surface_image_append_contoning_slices(TopSurfaceImageRegionPlan if (area.empty() || !solver.valid()) return; check_canceled(throw_if_canceled); + const int pattern_filaments = + top_surface_image_contoning_pattern_filaments(plan.contoning_stack_layers, plan.contoning_pattern_filaments); std::vector by_component(print_config.filament_colour.values.size() + 1); const std::vector stack_regions = @@ -1156,8 +1174,8 @@ static void top_surface_image_append_contoning_slices(TopSurfaceImageRegionPlan TopSurfaceImageStackSlice slice; slice.component_id = component_id; slice.depth = depth; - slice.component_index = size_t(depth % std::max(1, std::min(plan.contoning_stack_layers, 4))); - slice.component_count = size_t(std::min(plan.contoning_stack_layers, 4)); + slice.component_index = size_t(depth % pattern_filaments); + slice.component_count = size_t(pattern_filaments); slice.contoning = true; slice.lower_surface = source_surface == TopSurfaceImageSourceSurface::Bottom; slice.angle_rad = (depth & 1) ? float(-PI / 4.0) : float(PI / 4.0); @@ -1239,6 +1257,9 @@ static std::vector top_surface_image_region_plans(con plan.contoning_stack_layers = std::clamp(zone->top_surface_contoning_stack_layers, TextureMappingZone::MinTopSurfaceContoningStackLayers, TextureMappingZone::MaxTopSurfaceContoningStackLayers); + plan.contoning_pattern_filaments = std::clamp(zone->top_surface_contoning_pattern_filaments, + TextureMappingZone::MinTopSurfaceContoningPatternFilaments, + TextureMappingZone::MaxTopSurfaceContoningPatternFilaments); plan.contoning_external_width_mm = float(layerm->flow(frExternalPerimeter).width()); plan.contoning_min_feature_mm = texture_mapping_contoning_min_feature_mm(*zone, print_config, components, plan.contoning_external_width_mm); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 15e6595997f..732236a2da5 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -459,11 +459,29 @@ struct PerimeterTextureRecolorSampler { std::vector> image_component_colors; TextureMappingContoningSolver contoning_solver; int contoning_stack_layers { TextureMappingZone::DefaultTopSurfaceContoningStackLayers }; + int contoning_pattern_filaments { TextureMappingZone::DefaultTopSurfaceContoningPatternFilaments }; float contoning_angle_threshold_deg { TextureMappingZone::DefaultTopSurfaceContoningAngleThresholdDeg }; std::vector component_ids; std::vector gradient_contexts; }; +static int perimeter_texture_available_contoning_shell_slots(const ExtrusionEntityCollection &perimeters, + int fallback_wall_loops, + int configured_stack_layers); + +static int perimeter_texture_contoning_pattern_layers(int stack_layers, int configured_pattern_filaments) +{ + const int clamped_stack_layers = + std::clamp(stack_layers, + TextureMappingZone::MinTopSurfaceContoningStackLayers, + TextureMappingZone::MaxTopSurfaceContoningStackLayers); + const int clamped_pattern_filaments = + std::clamp(configured_pattern_filaments, + TextureMappingZone::MinTopSurfaceContoningPatternFilaments, + TextureMappingZone::MaxTopSurfaceContoningPatternFilaments); + return std::max(1, std::min(clamped_stack_layers, clamped_pattern_filaments)); +} + static std::optional perimeter_texture_make_recolor_sampler( const LayerRegion &layer_region, const TextureMappingZone &zone, @@ -515,6 +533,10 @@ static std::optional perimeter_texture_make_reco std::clamp(zone.top_surface_contoning_stack_layers, TextureMappingZone::MinTopSurfaceContoningStackLayers, TextureMappingZone::MaxTopSurfaceContoningStackLayers); + sampler.contoning_pattern_filaments = + std::clamp(zone.top_surface_contoning_pattern_filaments, + TextureMappingZone::MinTopSurfaceContoningPatternFilaments, + TextureMappingZone::MaxTopSurfaceContoningPatternFilaments); sampler.contoning_angle_threshold_deg = std::clamp(zone.top_surface_contoning_angle_threshold_deg, TextureMappingZone::MinTopSurfaceContoningAngleThresholdDeg, @@ -547,7 +569,8 @@ static std::optional perimeter_texture_make_reco static std::optional perimeter_texture_choose_recolor_component_with_sampler( const ExPolygons &visible, const ExtrusionEntity *fallback_entity, - const PerimeterTextureRecolorSampler &sampler) + const PerimeterTextureRecolorSampler &sampler, + int contoning_stack_layers = 0) { if (sampler.image_texture) { if (!sampler.image_context) @@ -572,8 +595,15 @@ static std::optional perimeter_texture_choose_recolor_component_wi } if (!rgb) return std::nullopt; + const int stack_layers = perimeter_texture_contoning_pattern_layers( + contoning_stack_layers > 0 ? + std::clamp(contoning_stack_layers, + TextureMappingZone::MinTopSurfaceContoningStackLayers, + sampler.contoning_stack_layers) : + sampler.contoning_stack_layers, + sampler.contoning_pattern_filaments); const unsigned int component_id = - sampler.contoning_solver.component_for_depth(*rgb, sampler.contoning_stack_layers, 0); + sampler.contoning_solver.component_for_depth(*rgb, stack_layers, 0); if (component_id >= 1 && component_id <= sampler.num_physical) return component_id; return std::nullopt; @@ -621,7 +651,8 @@ static std::optional perimeter_texture_choose_recolor_component_fo const ExtrusionEntity *fallback_entity, const TextureMappingZone &zone, unsigned int texture_zone_id, - float base_outer_width_mm) + float base_outer_width_mm, + int contoning_stack_layers = 0) { const Layer *layer = layer_region.layer(); if (layer == nullptr || layer->object() == nullptr || layer->object()->print() == nullptr) @@ -632,7 +663,7 @@ static std::optional perimeter_texture_choose_recolor_component_fo if (!sampler) return std::nullopt; - return perimeter_texture_choose_recolor_component_with_sampler(visible, fallback_entity, *sampler); + return perimeter_texture_choose_recolor_component_with_sampler(visible, fallback_entity, *sampler, contoning_stack_layers); } static std::optional perimeter_texture_choose_recolor_component(const LayerRegion &layer_region, @@ -642,12 +673,30 @@ static std::optional perimeter_texture_choose_recolor_component(co float base_outer_width_mm) { const ExPolygons visible = perimeter_texture_external_visible_footprint(layer_region, entity); + int contoning_stack_layers = 0; + if (zone.top_surface_contoning_perimeters_active()) { + const int fallback_wall_loops = std::max(1, layer_region.region().config().wall_loops.value); + if (const ExtrusionEntityCollection *collection = dynamic_cast(&entity)) { + contoning_stack_layers = + perimeter_texture_available_contoning_shell_slots(*collection, + fallback_wall_loops, + zone.top_surface_contoning_stack_layers); + } else { + contoning_stack_layers = + std::clamp(fallback_wall_loops, + TextureMappingZone::MinTopSurfaceContoningStackLayers, + std::clamp(zone.top_surface_contoning_stack_layers, + TextureMappingZone::MinTopSurfaceContoningStackLayers, + TextureMappingZone::MaxTopSurfaceContoningStackLayers)); + } + } return perimeter_texture_choose_recolor_component_for_visible(layer_region, visible, &entity, zone, texture_zone_id, - base_outer_width_mm); + base_outer_width_mm, + contoning_stack_layers); } static bool perimeter_texture_apply_recolor_small_perimeter_loops(LayerRegion &layer_region, @@ -1014,8 +1063,11 @@ static std::optional perimeter_texture_choose_recolor_component_fr TextureMappingZone::MinTopSurfaceContoningStackLayers, sampler.contoning_stack_layers) : sampler.contoning_stack_layers; + const int pattern_layers = + perimeter_texture_contoning_pattern_layers(stack_layers, sampler.contoning_pattern_filaments); + const int pattern_depth = depth_from_top >= 0 ? depth_from_top % pattern_layers : 0; const unsigned int component_id = - sampler.contoning_solver.component_for_depth(rgb, stack_layers, depth_from_top); + sampler.contoning_solver.component_for_depth(rgb, pattern_layers, pattern_depth); if (component_id >= 1 && component_id <= sampler.num_physical) return component_id; return std::nullopt; diff --git a/src/libslic3r/TextureMapping.cpp b/src/libslic3r/TextureMapping.cpp index 27e7183142c..ff202ac3a52 100644 --- a/src/libslic3r/TextureMapping.cpp +++ b/src/libslic3r/TextureMapping.cpp @@ -1050,6 +1050,7 @@ bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const top_surface_image_fixed_coloring_filaments == rhs.top_surface_image_fixed_coloring_filaments && std::abs(top_surface_contoning_angle_threshold_deg - rhs.top_surface_contoning_angle_threshold_deg) <= eps && top_surface_contoning_stack_layers == rhs.top_surface_contoning_stack_layers && + top_surface_contoning_pattern_filaments == rhs.top_surface_contoning_pattern_filaments && std::abs(top_surface_contoning_min_feature_mm - rhs.top_surface_contoning_min_feature_mm) <= eps && top_surface_contoning_color_lower_surfaces == rhs.top_surface_contoning_color_lower_surfaces && top_surface_contoning_only_color_surface_infill == rhs.top_surface_contoning_only_color_surface_infill && @@ -1419,6 +1420,10 @@ std::string TextureMappingManager::serialize_entries() clamp_int(zone.top_surface_contoning_stack_layers, TextureMappingZone::MinTopSurfaceContoningStackLayers, TextureMappingZone::MaxTopSurfaceContoningStackLayers); + texture["top_surface_contoning_pattern_filaments"] = + clamp_int(zone.top_surface_contoning_pattern_filaments, + TextureMappingZone::MinTopSurfaceContoningPatternFilaments, + TextureMappingZone::MaxTopSurfaceContoningPatternFilaments); texture["top_surface_contoning_min_feature_mm"] = std::clamp(finite_or(zone.top_surface_contoning_min_feature_mm, TextureMappingZone::DefaultTopSurfaceContoningMinFeatureMm), @@ -1662,6 +1667,11 @@ void TextureMappingManager::load_entries(const std::string &serialized, TextureMappingZone::DefaultTopSurfaceContoningStackLayers), TextureMappingZone::MinTopSurfaceContoningStackLayers, TextureMappingZone::MaxTopSurfaceContoningStackLayers); + zone.top_surface_contoning_pattern_filaments = + clamp_int(texture.value("top_surface_contoning_pattern_filaments", + TextureMappingZone::DefaultTopSurfaceContoningPatternFilaments), + TextureMappingZone::MinTopSurfaceContoningPatternFilaments, + TextureMappingZone::MaxTopSurfaceContoningPatternFilaments); zone.top_surface_contoning_min_feature_mm = std::clamp(finite_or(texture.value("top_surface_contoning_min_feature_mm", TextureMappingZone::DefaultTopSurfaceContoningMinFeatureMm), diff --git a/src/libslic3r/TextureMapping.hpp b/src/libslic3r/TextureMapping.hpp index d4e23060c31..8591888d7ee 100644 --- a/src/libslic3r/TextureMapping.hpp +++ b/src/libslic3r/TextureMapping.hpp @@ -161,7 +161,10 @@ struct TextureMappingZone static constexpr float DefaultTopSurfaceContoningAngleThresholdDeg = 20.f; static constexpr int MinTopSurfaceContoningStackLayers = 1; static constexpr int MaxTopSurfaceContoningStackLayers = 20; - static constexpr int DefaultTopSurfaceContoningStackLayers = 6; + static constexpr int DefaultTopSurfaceContoningStackLayers = 15; + static constexpr int MinTopSurfaceContoningPatternFilaments = 1; + static constexpr int MaxTopSurfaceContoningPatternFilaments = 20; + static constexpr int DefaultTopSurfaceContoningPatternFilaments = 4; static constexpr float MinTopSurfaceContoningMinFeatureMm = 0.f; static constexpr float MaxTopSurfaceContoningMinFeatureMm = 20.f; static constexpr float DefaultTopSurfaceContoningMinFeatureMm = 0.f; @@ -263,6 +266,7 @@ struct TextureMappingZone bool top_surface_image_fixed_coloring_filaments = DefaultTopSurfaceImageFixedColoringFilaments; float top_surface_contoning_angle_threshold_deg = DefaultTopSurfaceContoningAngleThresholdDeg; int top_surface_contoning_stack_layers = DefaultTopSurfaceContoningStackLayers; + int top_surface_contoning_pattern_filaments = DefaultTopSurfaceContoningPatternFilaments; float top_surface_contoning_min_feature_mm = DefaultTopSurfaceContoningMinFeatureMm; bool top_surface_contoning_color_lower_surfaces = DefaultTopSurfaceContoningColorLowerSurfaces; bool top_surface_contoning_only_color_surface_infill = DefaultTopSurfaceContoningOnlyColorSurfaceInfill; @@ -375,6 +379,7 @@ struct TextureMappingZone top_surface_image_fixed_coloring_filaments = DefaultTopSurfaceImageFixedColoringFilaments; top_surface_contoning_angle_threshold_deg = DefaultTopSurfaceContoningAngleThresholdDeg; top_surface_contoning_stack_layers = DefaultTopSurfaceContoningStackLayers; + top_surface_contoning_pattern_filaments = DefaultTopSurfaceContoningPatternFilaments; top_surface_contoning_min_feature_mm = DefaultTopSurfaceContoningMinFeatureMm; top_surface_contoning_color_lower_surfaces = DefaultTopSurfaceContoningColorLowerSurfaces; top_surface_contoning_only_color_surface_infill = DefaultTopSurfaceContoningOnlyColorSurfaceInfill; diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 20e901ea724..6c10ec2704d 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -2165,7 +2165,7 @@ void MenuFactory::append_menu_item_simplify(wxMenu* menu) void MenuFactory::append_menu_item_smooth_mesh(wxMenu *menu) { wxMenuItem *menu_item = append_menu_item( - menu, wxID_ANY, _L("Subdivision mesh") + _L("(Lost color)"), "", [](wxCommandEvent &) { obj_list()->smooth_mesh(); }, "", menu, []() { return plater()->can_smooth_mesh(); }, + menu, wxID_ANY, _L("Subdivision mesh"), "", [](wxCommandEvent &) { obj_list()->smooth_mesh(); }, "", menu, []() { return plater()->can_smooth_mesh(); }, m_parent); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 64925669eb3..1d4566e8dac 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -29,6 +29,12 @@ #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -209,6 +215,216 @@ static bool mesh_boolean_texture_result_has_output(const SimplifyTextureDataResu return result.region_painting_valid; } +static bool smooth_mesh_color_snapshot_is_valid(const SimplifyTextureDataSnapshot &snapshot) +{ + return snapshot.source != SimplifyColorSource::None; +} + +static bool smooth_mesh_texture_result_preserved_color(const SimplifyTextureDataSnapshot &snapshot, const SimplifyTextureDataResult &result) +{ + switch (snapshot.source) { + case SimplifyColorSource::RgbaData: + return result.source == SimplifyColorSource::RgbaData && result.rgba_data != nullptr; + case SimplifyColorSource::ImageTexture: + return result.source == SimplifyColorSource::ImageTexture || + (result.source == SimplifyColorSource::RgbaData && result.rgba_data != nullptr); + case SimplifyColorSource::VertexColors: + return result.source == SimplifyColorSource::VertexColors && !result.vertex_colors_rgba.empty(); + case SimplifyColorSource::None: + break; + } + return true; +} + +struct SmoothMeshJob +{ + int obj_idx = -1; + int vol_idx = -1; + std::string name; + TriangleMesh mesh; + SimplifyTextureDataSnapshot texture_snapshot; +}; + +struct SmoothMeshResult +{ + int obj_idx = -1; + int vol_idx = -1; + std::string name; + bool ok = false; + TriangleMesh mesh; + SimplifyTextureDataResult texture_result; +}; + +struct SmoothMeshWorkerState +{ + std::atomic cancel_requested { false }; + std::atomic done { false }; + std::mutex mutex; + int progress = 0; + bool canceled = false; + bool failed = false; + std::string failure_message; + std::vector results; + ModelRepairColorRemapStats color_remap_stats; +}; + +class SmoothMeshCanceledException : public std::exception +{ +public: + const char *what() const noexcept override { return "Subdivision mesh has been canceled"; } +}; + +static void smooth_mesh_update_color_remap_stats(const SimplifyTextureDataSnapshot &snapshot, + const SimplifyTextureDataResult &result, + bool remap_region_painting, + ModelRepairColorRemapStats &stats) +{ + const bool has_color = smooth_mesh_color_snapshot_is_valid(snapshot); + stats.had_color_data |= has_color; + stats.remap_requested |= has_color; + stats.used_fallback_rgba |= result.used_fallback_rgba; + stats.remap_failed |= result.remap_failed && !result.used_fallback_rgba; + stats.had_region_painting |= snapshot.region_painting_present; + stats.region_remap_requested |= snapshot.region_painting_transfer_needed && remap_region_painting; + stats.region_remap_skipped |= snapshot.region_painting_transfer_needed && !remap_region_painting; + stats.region_remap_failed |= result.region_painting_remap_failed && remap_region_painting; + + if (has_color) { + if (smooth_mesh_texture_result_preserved_color(snapshot, result)) + ++stats.volumes_remapped; + else + ++stats.volumes_cleared; + } + if (snapshot.region_painting_present) { + if (result.region_painting_touched && result.region_painting_valid) + ++stats.region_volumes_remapped; + else + ++stats.region_volumes_cleared; + } +} + +static void smooth_mesh_apply_result(ModelVolume &volume, + TriangleMesh &&result_mesh, + SimplifyTextureDataResult &&texture_result) +{ + volume.set_mesh(std::move(result_mesh)); + volume.reset_extra_facets(); + apply_simplify_texture_data_result(volume, std::move(texture_result)); + volume.calculate_convex_hull(); + volume.invalidate_convex_hull_2d(); + volume.set_new_unique_id(); +} + +static void smooth_mesh_set_worker_progress(SmoothMeshWorkerState &state, int progress) +{ + std::lock_guard lk(state.mutex); + state.progress = std::clamp(progress, 0, 100); +} + +static void smooth_mesh_throw_on_cancel(const SmoothMeshWorkerState &state) +{ + if (state.cancel_requested.load()) + throw SmoothMeshCanceledException(); +} + +static void smooth_mesh_run_worker(std::shared_ptr state, + std::vector jobs, + bool remap_region_painting) +{ + try { + std::vector results; + results.reserve(jobs.size()); + ModelRepairColorRemapStats color_remap_stats; + + for (size_t job_idx = 0; job_idx < jobs.size(); ++job_idx) { + smooth_mesh_throw_on_cancel(*state); + const int base_progress = jobs.empty() ? 100 : int((uint64_t(job_idx) * 100u) / uint64_t(jobs.size())); + const int after_mesh_progress = jobs.empty() ? 100 : int((uint64_t(job_idx) * 100u + 70u) / uint64_t(jobs.size())); + smooth_mesh_set_worker_progress(*state, base_progress); + + SmoothMeshResult result; + result.obj_idx = jobs[job_idx].obj_idx; + result.vol_idx = jobs[job_idx].vol_idx; + result.name = jobs[job_idx].name; + + bool ok = false; + TriangleMesh result_mesh = TriangleMeshDeal::smooth_triangle_mesh(jobs[job_idx].mesh, ok); + smooth_mesh_throw_on_cancel(*state); + if (ok) { + result.ok = true; + smooth_mesh_set_worker_progress(*state, after_mesh_progress); + SimplifyTextureDataRemapOptions remap_options; + remap_options.remap_region_painting = remap_region_painting; + auto progress_fn = [state, job_idx, jobs_count = jobs.size()](int percent) { + const int clamped = std::clamp(percent, 0, 100); + const int progress = jobs_count == 0 ? + 100 : + int((uint64_t(job_idx) * 100u + 70u + uint64_t(clamped) * 30u / 100u) / uint64_t(jobs_count)); + smooth_mesh_set_worker_progress(*state, progress); + }; + result.texture_result = remap_simplify_texture_data( + jobs[job_idx].texture_snapshot, + result_mesh.its, + [state]() { smooth_mesh_throw_on_cancel(*state); }, + progress_fn, + remap_options); + smooth_mesh_update_color_remap_stats(jobs[job_idx].texture_snapshot, + result.texture_result, + remap_region_painting, + color_remap_stats); + result.mesh = std::move(result_mesh); + } + results.emplace_back(std::move(result)); + } + + { + std::lock_guard lk(state->mutex); + state->results = std::move(results); + state->color_remap_stats = color_remap_stats; + state->progress = 100; + } + } catch (const SmoothMeshCanceledException &) { + std::lock_guard lk(state->mutex); + state->canceled = true; + } catch (const std::exception &ex) { + std::lock_guard lk(state->mutex); + state->failed = true; + state->failure_message = ex.what(); + } catch (...) { + std::lock_guard lk(state->mutex); + state->failed = true; + } + state->done = true; +} + +static void smooth_mesh_notify_color_remap_result(const ModelRepairColorRemapStats &stats) +{ + Plater *plater = wxGetApp().plater(); + if (plater == nullptr) + return; + + if (stats.used_fallback_rgba) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Some image texture UVs could not be regenerated after subdivision; texture colors were preserved as RGBA data.")); + if (stats.remap_failed && stats.volumes_cleared > 0) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Some color data could not be remapped after subdivision and was cleared.")); + if (stats.region_remap_failed && 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 subdivision and was cleared.")); + if (stats.region_remap_skipped && 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 subdivided parts.")); +} + static void mesh_boolean_transform_snapshot(SimplifyTextureDataSnapshot &snapshot, const Transform3d &transform, bool fix_left_handed) { transform_simplify_texture_data_snapshot(snapshot, transform); @@ -6532,14 +6748,11 @@ void ObjectList::simplify() void GUI::ObjectList::smooth_mesh() { - wxBusyCursor cursor; auto plater = wxGetApp().plater(); if (!plater) { return; } - plater->take_snapshot("smooth_mesh"); std::vector obj_idxs, vol_idxs; get_selection_indexes(obj_idxs, vol_idxs); auto object_idx = obj_idxs.front(); - ModelObject *obj{nullptr}; auto show_warning_dlg = [this](int cur_face_count,std::string name,bool is_part) { int limit_face_count = 1000000; if (cur_face_count > limit_face_count) { @@ -6559,61 +6772,125 @@ void GUI::ObjectList::smooth_mesh() WarningDialog dlg(static_cast(wxGetApp().mainframe), content, wxEmptyString, wxOK); dlg.ShowModal(); }; - bool has_show_smooth_mesh_error_dlg = false; + std::vector jobs; + bool has_remappable_region_painting = false; + auto add_smooth_mesh_job = [&jobs, &has_remappable_region_painting](int obj_idx, int vol_idx, ModelVolume *mv) { + if (mv == nullptr) + return; + SmoothMeshJob job; + job.obj_idx = obj_idx; + job.vol_idx = vol_idx; + job.name = mv->name; + job.mesh = mv->mesh(); + job.texture_snapshot = snapshot_simplify_texture_data(*mv); + has_remappable_region_painting |= job.texture_snapshot.region_painting_transfer_needed; + jobs.emplace_back(std::move(job)); + }; if (vol_idxs.empty()) { - obj = object(object_idx); + ModelObject *obj = object(object_idx); auto future_face_count = static_cast(obj->facets_count()) * 4; if (show_warning_dlg(future_face_count, obj->name,false)) { return; } - for (auto mv : obj->volumes) { - bool ok; - auto result_mesh = TriangleMeshDeal::smooth_triangle_mesh(mv->mesh(), ok); - if (ok) { - mv->set_mesh(result_mesh); - mv->reset_extra_facets(); // reset paint color - mv->calculate_convex_hull(); - mv->invalidate_convex_hull_2d(); - mv->set_new_unique_id(); - } else { - if (!has_show_smooth_mesh_error_dlg) { - show_smooth_mesh_error_dlg(mv->name); - has_show_smooth_mesh_error_dlg = true; - } - } - } - obj->invalidate_bounding_box(); - obj->ensure_on_bed(); - plater->changed_mesh(object_idx); + for (size_t vol_idx = 0; vol_idx < obj->volumes.size(); ++vol_idx) + add_smooth_mesh_job(object_idx, int(vol_idx), obj->volumes[vol_idx]); } else { - obj = object(obj_idxs.front()); + ModelObject *obj = object(obj_idxs.front()); for (int vol_idx : vol_idxs) { auto mv = obj->volumes[vol_idx]; auto future_face_count = static_cast(mv->mesh().facets_count()) * 4; if (show_warning_dlg(future_face_count, mv->name,true)) { return; } - bool ok; - auto result_mesh = TriangleMeshDeal::smooth_triangle_mesh(mv->mesh(),ok); - if (ok) { - mv->set_mesh(result_mesh); - mv->reset_extra_facets(); // reset paint color - mv->calculate_convex_hull(); - mv->invalidate_convex_hull_2d(); - mv->set_new_unique_id(); - } else { - if (!has_show_smooth_mesh_error_dlg) { - show_smooth_mesh_error_dlg(mv->name); - has_show_smooth_mesh_error_dlg = true; - } - } + add_smooth_mesh_job(obj_idxs.front(), vol_idx, mv); } } - if (obj) { - obj->invalidate_bounding_box(); - obj->ensure_on_bed(); - plater->changed_mesh(object_idx); + + if (jobs.empty()) + return; + + bool remap_region_painting = true; + if (has_remappable_region_painting) { + RichMessageDialog dlg(static_cast(wxGetApp().mainframe), + _L("Choose options to use when subdividing mesh:"), + _L("Subdivision options"), + wxOK | wxCANCEL); + dlg.ShowCheckBox(_L("Remap filament region painting to subdivided mesh"), true); + if (dlg.ShowModal() != wxID_OK) + return; + remap_region_painting = dlg.IsCheckBoxChecked(); } + + auto worker_state = std::make_shared(); + ProgressDialog progress_dlg(_L("Subdivision mesh"), _L("Subdividing mesh"), 100, find_toplevel_parent(plater), + wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT, true); + progress_dlg.Update(0, _L("Subdividing mesh")); + std::thread worker(smooth_mesh_run_worker, worker_state, std::move(jobs), remap_region_painting); + while (!worker_state->done.load()) { + int progress = 0; + { + std::lock_guard lk(worker_state->mutex); + progress = worker_state->progress; + } + if (!progress_dlg.Update(progress, worker_state->cancel_requested.load() ? _L("Canceling subdivision") : _L("Subdividing mesh"))) + worker_state->cancel_requested = true; + wxMilliSleep(50); + } + if (worker.joinable()) + worker.join(); + + if (worker_state->canceled) + return; + progress_dlg.Update(100, _L("Subdivision finished")); + if (worker_state->failed) { + wxString msg = _L("Subdivision failed."); + if (!worker_state->failure_message.empty()) + msg += "\n" + from_u8(worker_state->failure_message); + WarningDialog dlg(static_cast(wxGetApp().mainframe), msg, wxEmptyString, wxOK); + dlg.ShowModal(); + return; + } + + bool applied = false; + bool has_show_smooth_mesh_error_dlg = false; + std::vector changed_objects; + for (SmoothMeshResult &result : worker_state->results) { + if (!result.ok) { + if (!has_show_smooth_mesh_error_dlg) { + show_smooth_mesh_error_dlg(result.name); + has_show_smooth_mesh_error_dlg = true; + } + continue; + } + + if (result.obj_idx < 0 || size_t(result.obj_idx) >= m_objects->size()) + continue; + ModelObject *target_obj = object(result.obj_idx); + if (target_obj == nullptr || result.vol_idx < 0 || size_t(result.vol_idx) >= target_obj->volumes.size()) + continue; + + if (!applied) { + plater->take_snapshot("smooth_mesh"); + applied = true; + } + + ModelVolume *mv = target_obj->volumes[size_t(result.vol_idx)]; + smooth_mesh_apply_result(*mv, std::move(result.mesh), std::move(result.texture_result)); + update_item_error_icon(result.obj_idx, result.vol_idx); + if (std::find(changed_objects.begin(), changed_objects.end(), result.obj_idx) == changed_objects.end()) + changed_objects.emplace_back(result.obj_idx); + } + + for (int changed_obj_idx : changed_objects) { + ModelObject *changed_obj = object(changed_obj_idx); + if (changed_obj == nullptr) + continue; + changed_obj->invalidate_bounding_box(); + changed_obj->ensure_on_bed(); + plater->changed_mesh(changed_obj_idx); + } + if (applied) + smooth_mesh_notify_color_remap_result(worker_state->color_remap_stats); } void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 1e476b93184..3c791eb400f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -4084,6 +4084,15 @@ static bool project_point_to_screen(const ProjectionContext &context, const Vec3 return true; } +static float projection_camera_depth(const ProjectionContext &context, const Vec3d &world_point) +{ + Vec3d forward = context.camera_forward; + if (forward.squaredNorm() <= EPSILON) + return 0.f; + forward.normalize(); + return float((world_point - context.camera_position).dot(forward)); +} + static bool project_point_to_depth_clipped_screen(const ProjectionContext &context, const Vec3d &world_point, Vec2f &screen, @@ -5616,6 +5625,8 @@ struct ProjectionVisibility float scale = 1.f; std::vector depth; std::vector local_depth_tolerance; + std::vector camera_depth; + float camera_depth_tolerance = 0.05f; std::vector triangle_keys; }; @@ -5623,6 +5634,9 @@ static constexpr float PROJECTION_VISIBILITY_DEPTH_TOLERANCE = 2e-4f; static constexpr float PROJECTION_VISIBILITY_SAME_TRIANGLE_DEPTH_TOLERANCE = 2e-3f; static constexpr float PROJECTION_VISIBILITY_PROJECTED_TRIANGLE_DEPTH_TOLERANCE = 5e-3f; static constexpr float PROJECTION_VISIBILITY_MAX_LOCAL_DEPTH_TOLERANCE = 2e-2f; +static constexpr float PROJECTION_VISIBILITY_CAMERA_DEPTH_TOLERANCE_MIN = 0.005f; +static constexpr float PROJECTION_VISIBILITY_CAMERA_DEPTH_TOLERANCE_MAX = 0.05f; +static constexpr float PROJECTION_VISIBILITY_CAMERA_DEPTH_TOLERANCE_RATIO = 0.0005f; static constexpr uint64_t PROJECTION_VISIBILITY_INVALID_TRIANGLE_KEY = std::numeric_limits::max(); static uint64_t projection_visibility_triangle_key(size_t volume_idx, size_t tri_idx) @@ -5636,9 +5650,27 @@ static bool projection_visibility_valid(const ProjectionVisibility &visibility) visibility.height > 0 && visibility.depth.size() == size_t(visibility.width) * size_t(visibility.height) && visibility.local_depth_tolerance.size() == visibility.depth.size() && + visibility.camera_depth.size() == visibility.depth.size() && visibility.triangle_keys.size() == visibility.depth.size(); } +static void projection_visibility_prepare_camera_depth_tolerance(ProjectionVisibility &visibility) +{ + float min_depth = std::numeric_limits::max(); + float max_depth = std::numeric_limits::lowest(); + for (float depth : visibility.camera_depth) { + if (!std::isfinite(depth)) + continue; + min_depth = std::min(min_depth, depth); + max_depth = std::max(max_depth, depth); + } + if (min_depth <= max_depth) { + visibility.camera_depth_tolerance = std::clamp((max_depth - min_depth) * PROJECTION_VISIBILITY_CAMERA_DEPTH_TOLERANCE_RATIO, + PROJECTION_VISIBILITY_CAMERA_DEPTH_TOLERANCE_MIN, + PROJECTION_VISIBILITY_CAMERA_DEPTH_TOLERANCE_MAX); + } +} + static void projection_visibility_prepare_local_depth_tolerances(ProjectionVisibility &visibility) { visibility.local_depth_tolerance.assign(visibility.depth.size(), PROJECTION_VISIBILITY_DEPTH_TOLERANCE); @@ -5681,10 +5713,22 @@ static float projection_visibility_local_depth_tolerance(const ProjectionVisibil return visibility.local_depth_tolerance[idx]; } +static bool projection_visibility_camera_depth_matches_sample(const ProjectionVisibility &visibility, + size_t idx, + float camera_depth) +{ + if (idx >= visibility.camera_depth.size() || !std::isfinite(camera_depth)) + return true; + const float nearest_camera_depth = visibility.camera_depth[idx]; + return !std::isfinite(nearest_camera_depth) || + camera_depth <= nearest_camera_depth + visibility.camera_depth_tolerance; +} + static bool projection_visibility_depth_matches_sample(const ProjectionVisibility &visibility, int x, int y, float depth, + float camera_depth, uint64_t triangle_key) { if (x < 0 || y < 0 || x >= visibility.width || y >= visibility.height) @@ -5703,7 +5747,9 @@ static bool projection_visibility_depth_matches_sample(const ProjectionVisibilit } else if (has_triangle_key) { center_tolerance = std::max(PROJECTION_VISIBILITY_PROJECTED_TRIANGLE_DEPTH_TOLERANCE, local_tolerance); } - if (std::isfinite(center_nearest) && depth <= center_nearest + center_tolerance) + if (std::isfinite(center_nearest) && + depth <= center_nearest + center_tolerance && + (center_same_triangle || !has_triangle_key || projection_visibility_camera_depth_matches_sample(visibility, center_idx, camera_depth))) return true; if (!has_triangle_key) @@ -5742,7 +5788,9 @@ static bool projection_visibility_depth_matches_sample(const ProjectionVisibilit const float nearby_tolerance = std::max(PROJECTION_VISIBILITY_PROJECTED_TRIANGLE_DEPTH_TOLERANCE, projection_visibility_local_depth_tolerance(visibility, sample_x, sample_y)); - if (std::isfinite(nearest) && depth <= nearest + nearby_tolerance) + if (std::isfinite(nearest) && + depth <= nearest + nearby_tolerance && + projection_visibility_camera_depth_matches_sample(visibility, idx, camera_depth)) return true; } } @@ -5772,10 +5820,14 @@ static ProjectionVisibility build_projection_visibility(const ProjectionContext visibility.height = std::max(1, int(std::ceil(bounds_height * visibility.scale))); visibility.left = bounds.left; visibility.top = bounds.top; - visibility.depth.assign(size_t(visibility.width) * size_t(visibility.height), std::numeric_limits::max()); + visibility.depth.assign(size_t(visibility.width) * size_t(visibility.height), std::numeric_limits::infinity()); + visibility.camera_depth.assign(visibility.depth.size(), std::numeric_limits::infinity()); visibility.triangle_keys.assign(visibility.depth.size(), PROJECTION_VISIBILITY_INVALID_TRIANGLE_KEY); - auto rasterize_triangle = [&visibility, &check_cancel](const std::array &screen, const std::array &depths, uint64_t triangle_key) { + auto rasterize_triangle = [&visibility, &check_cancel](const std::array &screen, + const std::array &depths, + const std::array &camera_depths, + uint64_t triangle_key) { const float min_screen_x = std::min({ screen[0].x(), screen[1].x(), screen[2].x() }); const float max_screen_x = std::max({ screen[0].x(), screen[1].x(), screen[2].x() }); const float min_screen_y = std::min({ screen[0].y(), screen[1].y(), screen[2].y() }); @@ -5804,6 +5856,8 @@ static ProjectionVisibility build_projection_visibility(const ProjectionContext const size_t idx = size_t(y) * size_t(visibility.width) + size_t(x); if (depth < visibility.depth[idx]) { visibility.depth[idx] = depth; + visibility.camera_depth[idx] = + camera_depths[0] * weights.x() + camera_depths[1] * weights.y() + camera_depths[2] * weights.z(); visibility.triangle_keys[idx] = triangle_key; } } @@ -5844,19 +5898,22 @@ static ProjectionVisibility build_projection_visibility(const ProjectionContext const std::array fan = { polygon[0], polygon[polygon_idx], polygon[polygon_idx + 1] }; std::array screen; std::array depths; + std::array camera_depths; bool projected = true; for (size_t idx = 0; idx < fan.size(); ++idx) { if (!project_point_to_depth_clipped_screen(context, fan[idx], screen[idx], &depths[idx])) { projected = false; break; } + camera_depths[idx] = projection_camera_depth(context, fan[idx]); } if (projected) - rasterize_triangle(screen, depths, projection_visibility_triangle_key(volume_idx, tri_idx)); + rasterize_triangle(screen, depths, camera_depths, projection_visibility_triangle_key(volume_idx, tri_idx)); } } } + projection_visibility_prepare_camera_depth_tolerance(visibility); projection_visibility_prepare_local_depth_tolerances(visibility); return visibility; } @@ -5878,18 +5935,20 @@ static bool projection_point_is_visible(const ProjectionVisibility &visibility, float depth = 0.f; if (!project_point_to_screen(context, world_point, screen, &depth)) return false; + const float camera_depth = projection_camera_depth(context, world_point); const int x = int(std::floor((screen.x() - visibility.left) * visibility.scale)); const int y = int(std::floor((screen.y() - visibility.top) * visibility.scale)); if (x < 0 || y < 0 || x >= visibility.width || y >= visibility.height) return false; - return projection_visibility_depth_matches_sample(visibility, x, y, depth, triangle_key); + return projection_visibility_depth_matches_sample(visibility, x, y, depth, camera_depth, triangle_key); } static bool projection_screen_triangle_has_visible_sample(const ProjectionVisibility &visibility, const std::array &screen, const std::array &depths, + const std::array &camera_depths, uint64_t triangle_key) { if (!projection_visibility_valid(visibility)) @@ -5918,7 +5977,9 @@ static bool projection_screen_triangle_has_visible_sample(const ProjectionVisibi continue; const float depth = depths[0] * weights.x() + depths[1] * weights.y() + depths[2] * weights.z(); - if (projection_visibility_depth_matches_sample(visibility, x, y, depth, triangle_key)) + const float camera_depth = + camera_depths[0] * weights.x() + camera_depths[1] * weights.y() + camera_depths[2] * weights.z(); + if (projection_visibility_depth_matches_sample(visibility, x, y, depth, camera_depth, triangle_key)) return true; } } @@ -6154,14 +6215,16 @@ static bool projection_triangle_has_visible_sample(const ProjectionVisibility &v const std::array fan = { polygon[0], polygon[polygon_idx], polygon[polygon_idx + 1] }; std::array screen; std::array depths; + std::array camera_depths; bool projected = true; for (size_t idx = 0; idx < fan.size(); ++idx) { if (!project_point_to_depth_clipped_screen(context, fan[idx], screen[idx], &depths[idx])) { projected = false; break; } + camera_depths[idx] = projection_camera_depth(context, fan[idx]); } - if (projected && projection_screen_triangle_has_visible_sample(visibility, screen, depths, triangle_key)) + if (projected && projection_screen_triangle_has_visible_sample(visibility, screen, depths, camera_depths, triangle_key)) return true; } @@ -6171,6 +6234,7 @@ static bool projection_triangle_has_visible_sample(const ProjectionVisibility &v static bool projection_screen_triangle_is_fully_visible(const ProjectionVisibility &visibility, const std::array &screen, const std::array &depths, + const std::array &camera_depths, uint64_t triangle_key) { if (!projection_visibility_valid(visibility)) @@ -6204,7 +6268,9 @@ static bool projection_screen_triangle_is_fully_visible(const ProjectionVisibili covered = true; const float depth = depths[0] * weights.x() + depths[1] * weights.y() + depths[2] * weights.z(); - if (!projection_visibility_depth_matches_sample(visibility, x, y, depth, triangle_key)) + const float camera_depth = + camera_depths[0] * weights.x() + camera_depths[1] * weights.y() + camera_depths[2] * weights.z(); + if (!projection_visibility_depth_matches_sample(visibility, x, y, depth, camera_depth, triangle_key)) return false; } } @@ -6227,12 +6293,14 @@ static bool projection_triangle_is_fully_visible(const ProjectionVisibility &vis std::array screen; std::array depths; + std::array camera_depths; for (size_t idx = 0; idx < polygon.size(); ++idx) { if (!project_point_to_depth_clipped_screen(context, polygon[idx], screen[idx], &depths[idx])) return false; + camera_depths[idx] = projection_camera_depth(context, polygon[idx]); } - return projection_screen_triangle_is_fully_visible(visibility, screen, depths, triangle_key); + return projection_screen_triangle_is_fully_visible(visibility, screen, depths, camera_depths, triangle_key); } static bool projection_triangle_should_project(const ProjectionContext &context, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index b378bb93cd8..7c03b003a4e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -677,6 +677,14 @@ void GLGizmoSimplify::on_set_state() m_parent.toggle_model_objects_visibility(true); stop_worker_thread_request(); + { + std::lock_guard lk(m_state_mutex); + m_state.result.reset(); + m_state.mv = nullptr; + m_state.skip_color_conversion_requested = false; + m_state.color_conversion_in_progress = false; + m_state.progress = 0.f; + } m_volume = nullptr; // invalidate selected model m_glmodel.reset(); } else if (GLGizmoBase::m_state == GLGizmoBase::On) { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 011b13b2f68..a7432e162a7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1901,6 +1901,7 @@ public: bool top_surface_image_fixed_coloring_filaments, float top_surface_contoning_angle_threshold_deg, int top_surface_contoning_stack_layers, + int top_surface_contoning_pattern_filaments, float top_surface_contoning_min_feature_mm, bool top_surface_contoning_color_lower_surfaces, bool top_surface_contoning_only_color_surface_infill, @@ -2462,7 +2463,7 @@ public: contoning_angle_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("deg")), 0, wxALIGN_CENTER_VERTICAL); top_surface_contoning_root->Add(contoning_angle_row, 0, wxEXPAND | wxBOTTOM, gap); auto *contoning_layers_row = new wxBoxSizer(wxHORIZONTAL); - contoning_layers_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("Stack layers")), + contoning_layers_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("Max infill/perimeter layer depth")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); @@ -2481,6 +2482,26 @@ public: contoning_layers_row->Add(m_top_surface_contoning_stack_layers_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2); contoning_layers_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("layers")), 0, wxALIGN_CENTER_VERTICAL); top_surface_contoning_root->Add(contoning_layers_row, 0, wxEXPAND | wxBOTTOM, gap); + auto *contoning_pattern_row = new wxBoxSizer(wxHORIZONTAL); + contoning_pattern_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("Surface infill blend layer count")), + 0, + wxALIGN_CENTER_VERTICAL | wxRIGHT, + gap); + m_top_surface_contoning_pattern_filaments_spin = + new wxSpinCtrl(m_top_surface_contoning_panel, + wxID_ANY, + wxEmptyString, + wxDefaultPosition, + wxSize(FromDIP(70), -1), + wxSP_ARROW_KEYS | wxALIGN_RIGHT, + TextureMappingZone::MinTopSurfaceContoningPatternFilaments, + TextureMappingZone::MaxTopSurfaceContoningPatternFilaments, + std::clamp(top_surface_contoning_pattern_filaments, + TextureMappingZone::MinTopSurfaceContoningPatternFilaments, + TextureMappingZone::MaxTopSurfaceContoningPatternFilaments)); + contoning_pattern_row->Add(m_top_surface_contoning_pattern_filaments_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2); + contoning_pattern_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("layers")), 0, wxALIGN_CENTER_VERTICAL); + top_surface_contoning_root->Add(contoning_pattern_row, 0, wxEXPAND | wxBOTTOM, gap); auto *contoning_feature_row = new wxBoxSizer(wxHORIZONTAL); contoning_feature_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("Minimum feature")), 0, @@ -2842,6 +2863,14 @@ public: TextureMappingZone::MaxTopSurfaceContoningStackLayers) : TextureMappingZone::DefaultTopSurfaceContoningStackLayers; } + int top_surface_contoning_pattern_filaments() const + { + return m_top_surface_contoning_pattern_filaments_spin ? + std::clamp(m_top_surface_contoning_pattern_filaments_spin->GetValue(), + TextureMappingZone::MinTopSurfaceContoningPatternFilaments, + TextureMappingZone::MaxTopSurfaceContoningPatternFilaments) : + TextureMappingZone::DefaultTopSurfaceContoningPatternFilaments; + } float top_surface_contoning_min_feature_mm() const { return float(std::clamp(m_top_surface_contoning_min_feature_spin != nullptr ? @@ -3302,6 +3331,8 @@ private: m_top_surface_contoning_angle_threshold_spin->Enable(contoning); if (m_top_surface_contoning_stack_layers_spin != nullptr) m_top_surface_contoning_stack_layers_spin->Enable(contoning); + if (m_top_surface_contoning_pattern_filaments_spin != nullptr) + m_top_surface_contoning_pattern_filaments_spin->Enable(contoning); if (m_top_surface_contoning_min_feature_spin != nullptr) m_top_surface_contoning_min_feature_spin->Enable(contoning); if (m_top_surface_contoning_color_lower_surfaces_checkbox != nullptr) { @@ -3369,6 +3400,7 @@ private: wxPanel *m_top_surface_contoning_panel {nullptr}; wxSpinCtrlDouble *m_top_surface_contoning_angle_threshold_spin {nullptr}; wxSpinCtrl *m_top_surface_contoning_stack_layers_spin {nullptr}; + wxSpinCtrl *m_top_surface_contoning_pattern_filaments_spin {nullptr}; wxSpinCtrlDouble *m_top_surface_contoning_min_feature_spin {nullptr}; wxCheckBox *m_top_surface_contoning_color_lower_surfaces_checkbox {nullptr}; wxCheckBox *m_top_surface_contoning_only_color_surface_infill_checkbox {nullptr}; @@ -8294,16 +8326,16 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) apply_zone(std::move(updated)); CallAfter([this]() { update_texture_mapping_panel(false); }); }); - advanced_btn->Bind(wxEVT_BUTTON, [this, - zone_index, - mgr_ptr, - palette, - physical_colors, - apply_zone, - bundle, - set_config_string, - notify_change, - refresh_texture_mapping_preview](wxCommandEvent &) { + auto open_advanced_options = [this, + zone_index, + mgr_ptr, + palette, + physical_colors, + apply_zone, + bundle, + set_config_string, + notify_change, + refresh_texture_mapping_preview]() { if (mgr_ptr == nullptr || zone_index >= mgr_ptr->zones().size()) return; TextureMappingZone updated = mgr_ptr->zones()[zone_index]; @@ -8369,6 +8401,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.top_surface_image_fixed_coloring_filaments, updated.top_surface_contoning_angle_threshold_deg, updated.top_surface_contoning_stack_layers, + updated.top_surface_contoning_pattern_filaments, updated.top_surface_contoning_min_feature_mm, updated.top_surface_contoning_color_lower_surfaces, updated.top_surface_contoning_only_color_surface_infill, @@ -8423,6 +8456,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.top_surface_image_fixed_coloring_filaments = dlg.top_surface_image_fixed_coloring_filaments(); updated.top_surface_contoning_angle_threshold_deg = dlg.top_surface_contoning_angle_threshold_deg(); updated.top_surface_contoning_stack_layers = dlg.top_surface_contoning_stack_layers(); + updated.top_surface_contoning_pattern_filaments = dlg.top_surface_contoning_pattern_filaments(); updated.top_surface_contoning_min_feature_mm = dlg.top_surface_contoning_min_feature_mm(); updated.top_surface_contoning_color_lower_surfaces = dlg.top_surface_contoning_color_lower_surfaces(); updated.top_surface_contoning_only_color_surface_infill = dlg.top_surface_contoning_only_color_surface_infill(); @@ -8485,6 +8519,9 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) if (affects_scene || prime_tower_preview_changed) refresh_texture_mapping_preview(); CallAfter([this]() { update_texture_mapping_panel(false); }); + }; + advanced_btn->Bind(wxEVT_BUTTON, [open_advanced_options](wxCommandEvent &) { + open_advanced_options(); }); auto toggle_editor = [this, zone_index, editor, row, update_texture_mapping_area_height]() { @@ -8521,12 +8558,14 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) evt.Skip(); }); menu_btn->Bind(wxEVT_BUTTON, [this, zone_index, num_physical, physical_colors, mgr_ptr, persist_rows, menu_btn, bundle, set_config_string, + open_advanced_options, texture_mapping_zone_affects_scene, selected_texture_mapping_object_idxs, refresh_texture_mapping_preview](wxCommandEvent &) { if (menu_btn == nullptr) return; wxMenu menu; const int assign_selected_objects_id = wxWindow::NewControlId(); const int assign_selected_objects_erase_id = wxWindow::NewControlId(); + const int advanced_options_id = wxWindow::NewControlId(); const int duplicate_id = wxWindow::NewControlId(); const int delete_id = wxWindow::NewControlId(); const int delete_all_id = wxWindow::NewControlId(); @@ -8537,11 +8576,13 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) wxMenuItem *assign_selected_objects_erase_item = menu.Append(assign_selected_objects_erase_id, _L("Assign to selected objects (erase region painting)")); if (assign_selected_objects_erase_item != nullptr) assign_selected_objects_erase_item->Enable(has_selected_objects); + menu.Append(advanced_options_id, _L("Advanced Options")); menu.Append(duplicate_id, _L("Duplicate")); menu.Append(delete_id, _L("Delete")); menu.Append(delete_all_id, _L("Delete All Texture Mapping Zones")); menu.Bind(wxEVT_COMMAND_MENU_SELECTED, [this, zone_index, num_physical, physical_colors, mgr_ptr, persist_rows, assign_selected_objects_id, - assign_selected_objects_erase_id, duplicate_id, delete_id, delete_all_id, bundle, set_config_string, + assign_selected_objects_erase_id, advanced_options_id, duplicate_id, delete_id, delete_all_id, + bundle, set_config_string, open_advanced_options, texture_mapping_zone_affects_scene, selected_texture_mapping_object_idxs, refresh_texture_mapping_preview, menu_btn](wxCommandEvent &evt) { if (mgr_ptr == nullptr) @@ -8564,6 +8605,10 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) } return; } + if (evt.GetId() == advanced_options_id) { + open_advanced_options(); + return; + } if (evt.GetId() == duplicate_id) { mgr_ptr->duplicate_zone(zone_index, num_physical, physical_colors); persist_rows(false);