Preserve color data when subdividing mesh

- Allow setting blend pattern length for top surface coloring
- Reduce image bleed in image projection
This commit is contained in:
sentientstardust
2026-05-26 01:23:22 +01:00
parent 1ef400bbf1
commit f6f5123501
9 changed files with 562 additions and 76 deletions

View File

@@ -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<TopSurfaceImageContoningVectorRegion> top_surface_image_contoning_vector_regions(
const TopSurfaceImageRegionPlan &plan,
const Layer &source_layer,
@@ -979,6 +993,8 @@ static std::vector<TopSurfaceImageContoningVectorRegion> 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<ExPolygons> 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<TopSurfaceImageContoningVectorRegion> 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<float>(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<ExPolygons> by_component(print_config.filament_colour.values.size() + 1);
const std::vector<TopSurfaceImageContoningVectorRegion> 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<TopSurfaceImageRegionPlan> 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);

View File

@@ -459,11 +459,29 @@ struct PerimeterTextureRecolorSampler {
std::vector<std::array<float, 3>> 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<unsigned int> component_ids;
std::vector<TextureMappingOffsetContext> 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<PerimeterTextureRecolorSampler> perimeter_texture_make_recolor_sampler(
const LayerRegion &layer_region,
const TextureMappingZone &zone,
@@ -515,6 +533,10 @@ static std::optional<PerimeterTextureRecolorSampler> 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<PerimeterTextureRecolorSampler> perimeter_texture_make_reco
static std::optional<unsigned int> 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<unsigned int> 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<unsigned int> 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<unsigned int> 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<unsigned int> perimeter_texture_choose_recolor_component(const LayerRegion &layer_region,
@@ -642,12 +673,30 @@ static std::optional<unsigned int> 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<const ExtrusionEntityCollection *>(&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<unsigned int> 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;

View File

@@ -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),

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -29,6 +29,12 @@
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <exception>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
#include <unordered_map>
#include <functional>
@@ -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<bool> cancel_requested { false };
std::atomic<bool> done { false };
std::mutex mutex;
int progress = 0;
bool canceled = false;
bool failed = false;
std::string failure_message;
std::vector<SmoothMeshResult> 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<SmoothMeshWorkerState> state,
std::vector<SmoothMeshJob> jobs,
bool remap_region_painting)
{
try {
std::vector<SmoothMeshResult> 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<int> 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<wxWindow *>(wxGetApp().mainframe), content, wxEmptyString, wxOK);
dlg.ShowModal();
};
bool has_show_smooth_mesh_error_dlg = false;
std::vector<SmoothMeshJob> 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<int>(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<int>(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<wxWindow *>(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<SmoothMeshWorkerState>();
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<wxWindow *>(wxGetApp().mainframe), msg, wxEmptyString, wxOK);
dlg.ShowModal();
return;
}
bool applied = false;
bool has_show_smooth_mesh_error_dlg = false;
std::vector<int> 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

View File

@@ -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<float> depth;
std::vector<float> local_depth_tolerance;
std::vector<float> camera_depth;
float camera_depth_tolerance = 0.05f;
std::vector<uint64_t> 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<uint64_t>::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<float>::max();
float max_depth = std::numeric_limits<float>::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<float>::max());
visibility.depth.assign(size_t(visibility.width) * size_t(visibility.height), std::numeric_limits<float>::infinity());
visibility.camera_depth.assign(visibility.depth.size(), std::numeric_limits<float>::infinity());
visibility.triangle_keys.assign(visibility.depth.size(), PROJECTION_VISIBILITY_INVALID_TRIANGLE_KEY);
auto rasterize_triangle = [&visibility, &check_cancel](const std::array<Vec2f, 3> &screen, const std::array<float, 3> &depths, uint64_t triangle_key) {
auto rasterize_triangle = [&visibility, &check_cancel](const std::array<Vec2f, 3> &screen,
const std::array<float, 3> &depths,
const std::array<float, 3> &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<Vec3d, 3> fan = { polygon[0], polygon[polygon_idx], polygon[polygon_idx + 1] };
std::array<Vec2f, 3> screen;
std::array<float, 3> depths;
std::array<float, 3> 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<Vec2f, 3> &screen,
const std::array<float, 3> &depths,
const std::array<float, 3> &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<Vec3d, 3> fan = { polygon[0], polygon[polygon_idx], polygon[polygon_idx + 1] };
std::array<Vec2f, 3> screen;
std::array<float, 3> depths;
std::array<float, 3> 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<Vec2f, 3> &screen,
const std::array<float, 3> &depths,
const std::array<float, 3> &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<Vec2f, 3> screen;
std::array<float, 3> depths;
std::array<float, 3> 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,

View File

@@ -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) {

View File

@@ -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);