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:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user