Add v2 perimeter path modulation mode. Add option for support generation to use modulated overhang geometry

This commit is contained in:
sentientstardust
2026-05-22 14:53:43 +01:00
parent 63f5828425
commit 679ed9b8d1
8 changed files with 517 additions and 103 deletions

View File

@@ -75,14 +75,47 @@ static inline bool layer_needs_raw_backup(const Layer *layer)
void Layer::backup_untyped_slices()
{
if (layer_needs_raw_backup(this)) {
for (LayerRegion *layerm : m_regions)
for (LayerRegion *layerm : m_regions) {
layerm->raw_slices = to_expolygons(layerm->slices.surfaces);
layerm->unmodulated_raw_slices = layerm->raw_slices;
layerm->perimeter_path_modulation_v2_applied = false;
layerm->perimeter_path_modulation_v2_fallback_slices.clear();
layerm->perimeter_path_modulation_v2_has_fallback_slices = false;
layerm->perimeter_path_modulation_v2_fallback_is_modulated = false;
}
} else {
assert(m_regions.size() == 1);
m_regions.front()->raw_slices.clear();
m_regions.front()->unmodulated_raw_slices.clear();
m_regions.front()->perimeter_path_modulation_v2_applied = false;
m_regions.front()->perimeter_path_modulation_v2_fallback_slices.clear();
m_regions.front()->perimeter_path_modulation_v2_has_fallback_slices = false;
m_regions.front()->perimeter_path_modulation_v2_fallback_is_modulated = false;
}
}
void Layer::commit_perimeter_path_modulation_v2_fallbacks()
{
bool changed = false;
for (LayerRegion *layerm : m_regions) {
if (layerm == nullptr || !layerm->perimeter_path_modulation_v2_has_fallback_slices)
continue;
layerm->slices = std::move(layerm->perimeter_path_modulation_v2_fallback_slices);
layerm->raw_slices = to_expolygons(layerm->slices.surfaces);
layerm->perimeter_path_modulation_v2_applied = layerm->perimeter_path_modulation_v2_fallback_is_modulated;
layerm->perimeter_path_modulation_v2_has_fallback_slices = false;
layerm->perimeter_path_modulation_v2_fallback_is_modulated = false;
changed = true;
}
if (!changed)
return;
this->make_slices();
this->lslices_bboxes.clear();
this->lslices_bboxes.reserve(this->lslices.size());
for (const ExPolygon &expoly : this->lslices)
this->lslices_bboxes.emplace_back(get_extents(expoly));
}
void Layer::restore_untyped_slices()
{
if (layer_needs_raw_backup(this)) {
@@ -182,8 +215,14 @@ void Layer::make_perimeters()
// keep track of regions whose perimeters we have already generated
std::vector<unsigned char> done(m_regions.size(), false);
auto has_perimeter_input = [](const LayerRegion *layerm) {
return layerm != nullptr &&
(!layerm->slices.empty() ||
(layerm->perimeter_path_modulation_v2_applied && !layerm->unmodulated_raw_slices.empty()));
};
for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm)
if ((*layerm)->slices.empty()) {
if (!has_perimeter_input(*layerm)) {
(*layerm)->perimeters.clear();
(*layerm)->fills.clear();
(*layerm)->thin_fills.clear();
@@ -199,7 +238,7 @@ void Layer::make_perimeters()
LayerRegionPtrs layerms;
layerms.push_back(*layerm);
for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it)
if (! (*it)->slices.empty()) {
if (has_perimeter_input(*it)) {
LayerRegion* other_layerm = *it;
const PrintRegion &other_region = other_layerm->region();
if (is_perimeter_compatible(this_region, other_region))

View File

@@ -42,6 +42,11 @@ public:
// Only backed up for multi-region layers or layers with elephant foot compensation.
//FIXME Review whether not to simplify the code by keeping the raw_slices all the time.
ExPolygons raw_slices;
ExPolygons unmodulated_raw_slices;
bool perimeter_path_modulation_v2_applied = false;
SurfaceCollection perimeter_path_modulation_v2_fallback_slices;
bool perimeter_path_modulation_v2_has_fallback_slices = false;
bool perimeter_path_modulation_v2_fallback_is_modulated = false;
// collection of extrusion paths/loops filling gaps
// These fills are generated by the perimeter generator.
@@ -170,6 +175,8 @@ public:
void restore_untyped_slices();
// To improve robustness of detect_surfaces_type() when reslicing (working with typed slices), see GH issue #7442.
void restore_untyped_slices_no_extra_perimeters();
void apply_perimeter_path_modulation_v2();
void commit_perimeter_path_modulation_v2_fallbacks();
// Slices merged into islands, to be used by the elephant foot compensation to trim the individual surfaces with the shrunk merged slices.
ExPolygons merged(float offset) const;
template <class T> bool any_internal_region_slice_contains(const T &item) const {

View File

@@ -411,21 +411,33 @@ static std::optional<PerimeterTextureRecolorSampler> perimeter_texture_make_reco
float base_outer_width_mm)
{
const Layer *layer = layer_region.layer();
if (layer == nullptr || layer->object() == nullptr || layer->object()->print() == nullptr)
if (layer == nullptr)
return std::nullopt;
const PrintObject *print_object = layer->object();
if (print_object == nullptr)
return std::nullopt;
const Print *print = print_object->print();
if (print == nullptr || print->canceled())
return std::nullopt;
const PrintConfig &print_config = print->config();
const size_t num_physical = print_config.filament_colour.values.size();
if (num_physical == 0)
return std::nullopt;
std::optional<TextureMappingOffsetContext> context =
build_texture_mapping_offset_context_for_layer(*layer->object(), *layer, zone, texture_zone_id, 0, base_outer_width_mm);
if (!context)
build_texture_mapping_offset_context_for_layer(*print_object, *layer, zone, texture_zone_id, 0, base_outer_width_mm);
if (!context || print->canceled() || context->component_ids.empty())
return std::nullopt;
PerimeterTextureRecolorSampler sampler;
sampler.image_texture = zone.is_image_texture();
sampler.num_physical = layer->object()->print()->config().filament_colour.values.size();
sampler.num_physical = num_physical;
if (sampler.image_texture) {
sampler.image_context = std::move(*context);
sampler.image_component_colors.reserve(sampler.image_context->component_ids.size());
const PrintConfig &print_config = layer->object()->print()->config();
for (const unsigned int id : sampler.image_context->component_ids) {
ColorRGB decoded;
if (id >= 1 && id <= print_config.filament_colour.values.size() &&
@@ -441,15 +453,19 @@ static std::optional<PerimeterTextureRecolorSampler> perimeter_texture_make_reco
sampler.component_ids.reserve(context->component_ids.size());
sampler.gradient_contexts.reserve(context->component_ids.size());
for (const unsigned int component_id : context->component_ids) {
if (print->canceled())
return std::nullopt;
if (component_id < 1 || component_id > sampler.num_physical)
continue;
std::optional<TextureMappingOffsetContext> component_context =
build_texture_mapping_offset_context_for_layer(*layer->object(), *layer, zone, texture_zone_id, component_id, base_outer_width_mm);
build_texture_mapping_offset_context_for_layer(*print_object, *layer, zone, texture_zone_id, component_id, base_outer_width_mm);
if (!component_context)
continue;
sampler.component_ids.emplace_back(component_id);
sampler.gradient_contexts.emplace_back(std::move(*component_context));
}
if (sampler.component_ids.empty() || sampler.component_ids.size() != sampler.gradient_contexts.size())
return std::nullopt;
return sampler;
}
@@ -971,6 +987,52 @@ static ExPolygons perimeter_texture_recolor_masks_union(const std::vector<ExPoly
return out.empty() ? ExPolygons() : union_ex(out);
}
static void perimeter_texture_top_visible_recolor_data(const LayerRegion &layer_region,
const SurfaceCollection &slices,
const TextureMappingZone &zone,
unsigned int texture_zone_id,
float texture_external_width_mm,
std::vector<ExPolygons> &top_visible_recolor_masks,
ExPolygons &top_visible_recolor_protection_mask,
PerimeterTextureTopVisibleRecolorThresholds &top_visible_recolor_thresholds)
{
top_visible_recolor_masks.clear();
top_visible_recolor_protection_mask.clear();
top_visible_recolor_thresholds =
perimeter_texture_top_visible_recolor_thresholds(zone.top_visible_perimeter_recolor_aggressiveness);
const PrintRegionConfig &region_config = layer_region.region().config();
const int wall_loops = std::max(1, region_config.wall_loops.value);
const Flow perimeter_flow = layer_region.flow(frPerimeter);
const float wall_depth_mm =
0.5f * texture_external_width_mm +
float(std::max(0, wall_loops - 1)) * float(perimeter_flow.spacing()) +
0.5f * float(perimeter_flow.width()) + 0.05f;
ExPolygons top_visible_recolor_wall_band;
ExPolygons top_visible_recolor_mask =
perimeter_texture_top_visible_wall_band_mask(layer_region, slices, wall_depth_mm, &top_visible_recolor_wall_band);
if (top_visible_recolor_mask.empty())
return;
top_visible_recolor_masks =
perimeter_texture_top_visible_region_recolor_masks(layer_region,
slices,
top_visible_recolor_wall_band,
top_visible_recolor_mask,
zone,
texture_zone_id,
texture_external_width_mm,
top_visible_recolor_thresholds);
if (!perimeter_texture_recolor_masks_have_color(top_visible_recolor_masks)) {
top_visible_recolor_masks.clear();
return;
}
ExPolygons recolor_union = perimeter_texture_recolor_masks_union(top_visible_recolor_masks);
top_visible_recolor_protection_mask = intersection_ex(recolor_union, top_visible_recolor_mask);
if (top_visible_recolor_protection_mask.empty())
top_visible_recolor_masks.clear();
}
static bool perimeter_texture_split_surfaces_by_recolor_masks(const SurfaceCollection &input,
const SurfaceCollection &colored_input,
const std::vector<ExPolygons> &masks,
@@ -1365,6 +1427,106 @@ static const TextureMappingZone *perimeter_path_modulation_zone_for_region(const
return zone;
}
static const TextureMappingZone *perimeter_path_modulation_v2_zone_for_region(const Print &print,
const PrintRegionConfig &region_config,
unsigned int &texture_zone_id)
{
const TextureMappingZone *zone = perimeter_path_modulation_zone_for_region(print, region_config, texture_zone_id);
return zone != nullptr && zone->uses_perimeter_path_modulation_v2() ? zone : nullptr;
}
void Layer::apply_perimeter_path_modulation_v2()
{
PrintObject *print_object = this->object();
if (print_object == nullptr || print_object->print() == nullptr)
return;
bool needs_geometry_update = false;
for (LayerRegion *layerm : m_regions) {
if (layerm == nullptr)
continue;
unsigned int texture_zone_id = 0;
if (layerm->perimeter_path_modulation_v2_applied ||
perimeter_path_modulation_v2_zone_for_region(*print_object->print(),
layerm->region().config(),
texture_zone_id) != nullptr) {
needs_geometry_update = true;
break;
}
}
if (!needs_geometry_update)
return;
for (LayerRegion *layerm : m_regions) {
if (layerm == nullptr)
continue;
if (layerm->unmodulated_raw_slices.empty() && !layerm->raw_slices.empty())
layerm->unmodulated_raw_slices = layerm->raw_slices;
if (layerm->unmodulated_raw_slices.empty() && !layerm->slices.empty())
layerm->unmodulated_raw_slices = to_expolygons(layerm->slices.surfaces);
if (!layerm->unmodulated_raw_slices.empty() || !layerm->slices.empty()) {
layerm->slices.set(layerm->unmodulated_raw_slices, stInternal);
layerm->raw_slices = layerm->unmodulated_raw_slices;
} else {
layerm->raw_slices.clear();
}
layerm->perimeter_path_modulation_v2_applied = false;
layerm->perimeter_path_modulation_v2_fallback_slices.clear();
layerm->perimeter_path_modulation_v2_has_fallback_slices = false;
layerm->perimeter_path_modulation_v2_fallback_is_modulated = false;
}
for (LayerRegion *layerm : m_regions) {
if (layerm == nullptr || layerm->slices.empty())
continue;
unsigned int texture_zone_id = 0;
const TextureMappingZone *zone =
perimeter_path_modulation_v2_zone_for_region(*print_object->print(),
layerm->region().config(),
texture_zone_id);
if (zone == nullptr)
continue;
ExPolygons top_visible_recolor_protection_mask;
std::vector<ExPolygons> top_visible_recolor_masks;
PerimeterTextureTopVisibleRecolorThresholds top_visible_recolor_thresholds;
const ExPolygons *top_visible_recolor_mask_ptr = nullptr;
const PerimeterTextureTopVisibleRecolorThresholds *top_visible_recolor_thresholds_ptr = nullptr;
if (zone->recolor_top_visible_perimeter_sections) {
const float texture_external_width_mm =
std::max(0.05f, float(print_object->print()->config().texture_mapping_outer_wall_gradient_max_line_width.value));
perimeter_texture_top_visible_recolor_data(*layerm,
layerm->slices,
*zone,
texture_zone_id,
texture_external_width_mm,
top_visible_recolor_masks,
top_visible_recolor_protection_mask,
top_visible_recolor_thresholds);
if (!top_visible_recolor_protection_mask.empty()) {
top_visible_recolor_mask_ptr = &top_visible_recolor_protection_mask;
top_visible_recolor_thresholds_ptr = &top_visible_recolor_thresholds;
}
}
SurfaceCollection modulated_slices =
perimeter_path_modulated_surfaces(*layerm,
layerm->slices,
*zone,
texture_zone_id,
std::nullopt,
top_visible_recolor_mask_ptr,
top_visible_recolor_thresholds_ptr);
layerm->slices = std::move(modulated_slices);
layerm->raw_slices = to_expolygons(layerm->slices.surfaces);
layerm->perimeter_path_modulation_v2_applied = true;
}
this->make_slices();
this->lslices_bboxes.clear();
this->lslices_bboxes.reserve(this->lslices.size());
for (const ExPolygon &expoly : this->lslices)
this->lslices_bboxes.emplace_back(get_extents(expoly));
}
Flow LayerRegion::flow(FlowRole role) const
{
return this->flow(role, m_layer->height);
@@ -1435,8 +1597,6 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
SurfaceCollection modulated_slices;
const SurfaceCollection *perimeter_slices = &slices;
ExPolygons top_visible_recolor_mask;
ExPolygons top_visible_recolor_wall_band;
ExPolygons top_visible_recolor_protection_mask;
std::vector<ExPolygons> top_visible_recolor_masks;
const ExPolygons *top_visible_recolor_mask_ptr = nullptr;
@@ -1444,55 +1604,57 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
const PerimeterTextureTopVisibleRecolorThresholds *top_visible_recolor_thresholds_ptr = nullptr;
unsigned int perimeter_texture_zone_id = 0;
bool use_perimeter_path_modulation = false;
bool use_legacy_perimeter_path_modulation = false;
bool use_perimeter_path_modulation_v2 = false;
const TextureMappingZone *perimeter_path_zone =
perimeter_path_modulation_zone_for_region(*this->layer()->object()->print(), region_config, perimeter_texture_zone_id);
if (perimeter_path_zone != nullptr) {
if (perimeter_path_zone->recolor_top_visible_perimeter_sections) {
top_visible_recolor_thresholds =
perimeter_texture_top_visible_recolor_thresholds(perimeter_path_zone->top_visible_perimeter_recolor_aggressiveness);
const int wall_loops = std::max(1, region_config.wall_loops.value);
const Flow perimeter_flow = this->flow(frPerimeter);
const float wall_depth_mm =
0.5f * texture_external_width_mm +
float(std::max(0, wall_loops - 1)) * float(perimeter_flow.spacing()) +
0.5f * float(perimeter_flow.width()) + 0.05f;
top_visible_recolor_mask =
perimeter_texture_top_visible_wall_band_mask(*this, slices, wall_depth_mm, &top_visible_recolor_wall_band);
if (!top_visible_recolor_mask.empty()) {
top_visible_recolor_masks =
perimeter_texture_top_visible_region_recolor_masks(*this,
slices,
top_visible_recolor_wall_band,
top_visible_recolor_mask,
*perimeter_path_zone,
perimeter_texture_zone_id,
texture_external_width_mm,
top_visible_recolor_thresholds);
if (perimeter_texture_recolor_masks_have_color(top_visible_recolor_masks)) {
ExPolygons recolor_union = perimeter_texture_recolor_masks_union(top_visible_recolor_masks);
top_visible_recolor_protection_mask = intersection_ex(recolor_union, top_visible_recolor_mask);
if (!top_visible_recolor_protection_mask.empty()) {
top_visible_recolor_mask_ptr = &top_visible_recolor_protection_mask;
top_visible_recolor_thresholds_ptr = &top_visible_recolor_thresholds;
} else {
top_visible_recolor_masks.clear();
}
} else {
top_visible_recolor_masks.clear();
}
perimeter_texture_top_visible_recolor_data(*this,
slices,
*perimeter_path_zone,
perimeter_texture_zone_id,
texture_external_width_mm,
top_visible_recolor_masks,
top_visible_recolor_protection_mask,
top_visible_recolor_thresholds);
if (!top_visible_recolor_protection_mask.empty()) {
top_visible_recolor_mask_ptr = &top_visible_recolor_protection_mask;
top_visible_recolor_thresholds_ptr = &top_visible_recolor_thresholds;
}
}
modulated_slices = perimeter_path_modulated_surfaces(*this,
slices,
*perimeter_path_zone,
perimeter_texture_zone_id,
std::nullopt,
top_visible_recolor_mask_ptr,
top_visible_recolor_thresholds_ptr);
perimeter_slices = &modulated_slices;
if (perimeter_path_zone->uses_legacy_perimeter_path_modulation()) {
modulated_slices = perimeter_path_modulated_surfaces(*this,
slices,
*perimeter_path_zone,
perimeter_texture_zone_id,
std::nullopt,
top_visible_recolor_mask_ptr,
top_visible_recolor_thresholds_ptr);
perimeter_slices = &modulated_slices;
use_legacy_perimeter_path_modulation = true;
} else if (perimeter_path_zone->uses_perimeter_path_modulation_v2()) {
use_perimeter_path_modulation_v2 = true;
}
use_perimeter_path_modulation = true;
}
SurfaceCollection v2_original_slices;
const SurfaceCollection *fallback_original_slices = &slices;
if (use_perimeter_path_modulation_v2 && !this->unmodulated_raw_slices.empty()) {
v2_original_slices.set(this->unmodulated_raw_slices, stInternal);
fallback_original_slices = &v2_original_slices;
}
auto set_perimeter_path_modulation_v2_fallback_slices =
[this, use_perimeter_path_modulation_v2](const SurfaceCollection &fallback_slices, bool is_modulated) {
if (!use_perimeter_path_modulation_v2)
return;
this->perimeter_path_modulation_v2_fallback_slices = fallback_slices;
this->perimeter_path_modulation_v2_has_fallback_slices = true;
this->perimeter_path_modulation_v2_fallback_is_modulated = is_modulated;
};
const bool force_classic_wall_generator = region_uses_overhang_texture_mapping(*this->layer()->object()->print(), region_config);
auto texture_external_flow = [&](float external_width_mm) {
Flow out = this->flow(frExternalPerimeter);
@@ -1592,7 +1754,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
SurfaceCollection fill_surfaces_before;
ExPolygons fill_no_overlap_before;
if (use_perimeter_path_modulation) {
if (use_legacy_perimeter_path_modulation || use_perimeter_path_modulation_v2) {
fill_surfaces_before = *fill_surfaces;
fill_no_overlap_before = *fill_no_overlap;
}
@@ -1601,14 +1763,16 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
use_perimeter_path_modulation ? std::optional<float>(texture_external_width_mm) : std::optional<float>();
process_slices_with_top_visible_recolor(perimeter_slices, &slices, initial_texture_external_width_mm);
if (use_perimeter_path_modulation && this->perimeters.entities.empty() && this->thin_fills.entities.empty()) {
if ((use_legacy_perimeter_path_modulation || use_perimeter_path_modulation_v2) &&
this->perimeters.entities.empty() &&
this->thin_fills.entities.empty()) {
this->perimeters.clear();
this->thin_fills.clear();
*fill_surfaces = fill_surfaces_before;
*fill_no_overlap = fill_no_overlap_before;
fill_surfaces_before = *fill_surfaces;
fill_no_overlap_before = *fill_no_overlap;
process_slices(&slices, std::optional<float>(texture_external_width_mm));
process_slices(fallback_original_slices, std::optional<float>(texture_external_width_mm));
const bool original_texture_has_extrusions =
!this->perimeters.entities.empty() || !this->thin_fills.entities.empty();
const std::optional<float> reduced_external_width_mm = perimeter_texture_min_external_width(this->perimeters);
@@ -1619,8 +1783,10 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
if (perimeter_texture_apply_recolor_small_perimeter_loops(*this,
*perimeter_path_zone,
perimeter_texture_zone_id,
texture_external_width_mm))
texture_external_width_mm)) {
set_perimeter_path_modulation_v2_fallback_slices(*fallback_original_slices, false);
return;
}
}
if (has_reduced_external_width) {
this->perimeters.clear();
@@ -1629,14 +1795,18 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
*fill_no_overlap = fill_no_overlap_before;
SurfaceCollection reduced_modulated_slices =
perimeter_path_modulated_surfaces(*this,
slices,
*fallback_original_slices,
*perimeter_path_zone,
perimeter_texture_zone_id,
reduced_external_width_mm,
top_visible_recolor_mask_ptr,
top_visible_recolor_thresholds_ptr);
process_slices_with_top_visible_recolor(&reduced_modulated_slices, &slices, reduced_external_width_mm);
process_slices_with_top_visible_recolor(&reduced_modulated_slices, fallback_original_slices, reduced_external_width_mm);
if (!this->perimeters.entities.empty() || !this->thin_fills.entities.empty())
set_perimeter_path_modulation_v2_fallback_slices(reduced_modulated_slices, true);
}
if (!has_reduced_external_width && (!this->perimeters.entities.empty() || !this->thin_fills.entities.empty()))
set_perimeter_path_modulation_v2_fallback_slices(*fallback_original_slices, false);
if (this->perimeters.entities.empty() && this->thin_fills.entities.empty()) {
this->perimeters.clear();
this->thin_fills.clear();
@@ -1644,7 +1814,8 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
*fill_no_overlap = fill_no_overlap_before;
const std::optional<float> fallback_texture_external_width_mm =
original_texture_has_extrusions ? std::optional<float>(texture_external_width_mm) : std::optional<float>();
process_slices(&slices, fallback_texture_external_width_mm);
process_slices(fallback_original_slices, fallback_texture_external_width_mm);
set_perimeter_path_modulation_v2_fallback_slices(*fallback_original_slices, false);
}
}
}

View File

@@ -514,6 +514,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "texture_mapping_definitions"
|| opt_key == "texture_mapping_global_settings") {
osteps.emplace_back(posPerimeters);
osteps.emplace_back(posSupportMaterial);
steps.emplace_back(psWipeTower);
steps.emplace_back(psSkirtBrim);
} else if (
@@ -4116,6 +4117,8 @@ const std::string PrintStatistics::TotalFilamentUsedWipeTowerValueMask = "; tota
#define JSON_LAYER_REGION_CONFIG_HASH "config_hash"
#define JSON_LAYER_REGION_SLICES "slices"
#define JSON_LAYER_REGION_RAW_SLICES "raw_slices"
#define JSON_LAYER_REGION_UNMODULATED_RAW_SLICES "unmodulated_raw_slices"
#define JSON_LAYER_REGION_PERIMETER_PATH_MODULATION_V2_APPLIED "perimeter_path_modulation_v2_applied"
//#define JSON_LAYER_REGION_ENTITIES "entities"
#define JSON_LAYER_REGION_THIN_FILLS "thin_fills"
#define JSON_LAYER_REGION_FILL_EXPOLYGONS "fill_expolygons"
@@ -4326,7 +4329,7 @@ static bool convert_extrusion_to_json(json& entity_json, json& entity_paths_json
}
static void to_json(json& j, const LayerRegion& layer_region) {
json unsupported_bridge_edges_json = json::array(), slices_surfaces_json = json::array(), raw_slices_json = json::array(), thin_fills_json, thin_fill_entities_json = json::array();
json unsupported_bridge_edges_json = json::array(), slices_surfaces_json = json::array(), raw_slices_json = json::array(), unmodulated_raw_slices_json = json::array(), thin_fills_json, thin_fill_entities_json = json::array();
json fill_expolygons_json = json::array(), fill_no_overlap_expolygons_json = json::array(), fill_surfaces_json = json::array(), perimeters_json, perimeter_entities_json = json::array(), fills_json, fill_entities_json = json::array();
j[JSON_LAYER_REGION_CONFIG_HASH] = layer_region.region().config_hash();
@@ -4344,6 +4347,13 @@ static void to_json(json& j, const LayerRegion& layer_region) {
raw_slices_json.push_back(std::move(raw_polygon_json));
}
j.push_back({JSON_LAYER_REGION_RAW_SLICES, std::move(raw_slices_json)});
for (const ExPolygon& raw_slice_explogyon : layer_region.unmodulated_raw_slices) {
json raw_polygon_json = raw_slice_explogyon;
unmodulated_raw_slices_json.push_back(std::move(raw_polygon_json));
}
j.push_back({JSON_LAYER_REGION_UNMODULATED_RAW_SLICES, std::move(unmodulated_raw_slices_json)});
j.push_back({JSON_LAYER_REGION_PERIMETER_PATH_MODULATION_V2_APPLIED, layer_region.perimeter_path_modulation_v2_applied});
//thin fills
thin_fills_json[JSON_EXTRUSION_NO_SORT] = layer_region.thin_fills.no_sort;
@@ -4631,6 +4641,20 @@ static void convert_layer_region_from_json(const json& j, LayerRegion& layer_reg
polygon = j[JSON_LAYER_REGION_RAW_SLICES][raw_slices_index];
layer_region.raw_slices.push_back(std::move(polygon));
}
if (j.contains(JSON_LAYER_REGION_UNMODULATED_RAW_SLICES)) {
int unmodulated_raw_slices_count = j[JSON_LAYER_REGION_UNMODULATED_RAW_SLICES].size();
for (int raw_slices_index = 0; raw_slices_index < unmodulated_raw_slices_count; raw_slices_index++)
{
ExPolygon polygon;
polygon = j[JSON_LAYER_REGION_UNMODULATED_RAW_SLICES][raw_slices_index];
layer_region.unmodulated_raw_slices.push_back(std::move(polygon));
}
} else {
layer_region.unmodulated_raw_slices = layer_region.raw_slices;
}
layer_region.perimeter_path_modulation_v2_applied =
j.value(JSON_LAYER_REGION_PERIMETER_PATH_MODULATION_V2_APPLIED, false);
//thin fills
layer_region.thin_fills.no_sort = j[JSON_LAYER_REGION_THIN_FILLS][JSON_EXTRUSION_NO_SORT];

View File

@@ -509,6 +509,11 @@ void PrintObject::make_perimeters()
m_typed_slices = false;
}
for (auto layer_it = m_layers.rbegin(); layer_it != m_layers.rend(); ++layer_it) {
m_print->throw_if_canceled();
(*layer_it)->apply_perimeter_path_modulation_v2();
}
// compare each layer to the one below, and mark those slices needing
// one additional inner perimeter, like the top of domed objects-
@@ -593,6 +598,11 @@ void PrintObject::make_perimeters()
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end";
for (Layer *layer : m_layers) {
m_print->throw_if_canceled();
layer->commit_perimeter_path_modulation_v2_fallbacks();
}
this->set_done(posPerimeters);
}
@@ -832,45 +842,169 @@ void PrintObject::generate_support_material()
if (this->set_started(posSupportMaterial)) {
this->clear_support_layers();
if(!has_support() && !m_print->get_no_check_flag()) {
// BBS: pop a warning if objects have significant amount of overhangs but support material is not enabled
// Note: we also need to pop warning if support is disabled and only raft is enabled
m_print->set_status(50, L("Checking support necessity"));
typedef std::chrono::high_resolution_clock clock_;
typedef std::chrono::duration<double, std::ratio<1> > second_;
std::chrono::time_point<clock_> t0{ clock_::now() };
struct LayerSupportGeometryBackup {
Layer *layer { nullptr };
ExPolygons lslices;
std::vector<BoundingBox> lslices_bboxes;
};
struct LayerRegionSupportGeometryBackup {
LayerRegion *layerm { nullptr };
SurfaceCollection slices;
ExPolygons raw_slices;
SurfaceCollection fill_surfaces;
ExPolygons fill_expolygons;
ExPolygons fill_no_overlap_expolygons;
bool perimeter_path_modulation_v2_applied { false };
};
SupportNecessaryType sntype = this->is_support_necessary();
std::vector<LayerSupportGeometryBackup> layer_backups;
std::vector<LayerRegionSupportGeometryBackup> region_backups;
const bool typed_slices_backup = m_typed_slices;
bool support_geometry_swapped = false;
double duration{ std::chrono::duration_cast<second_>(clock_::now() - t0).count() };
BOOST_LOG_TRIVIAL(info) << std::fixed << std::setprecision(0) << "is_support_necessary takes " << duration << " secs.";
auto rebuild_layer_geometry = [](Layer *layer) {
layer->make_slices();
layer->lslices_bboxes.clear();
layer->lslices_bboxes.reserve(layer->lslices.size());
for (const ExPolygon &expoly : layer->lslices)
layer->lslices_bboxes.emplace_back(get_extents(expoly));
};
if (sntype != NoNeedSupp) {
std::map<SupportNecessaryType, std::string> reasons = {
{SharpTail,L("floating regions")},
{Cantilever,L("floating cantilever")},
{LargeOverhang,L("large overhangs")} };
std::string warning_message = Slic3r::format(L("It seems object %s has %s. Please re-orient the object or enable support generation."),
this->model_object()->name, reasons[sntype]);
this->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, warning_message, PrintStateBase::SlicingNeedSupportOn);
auto region_uses_unmodulated_support_geometry = [this](const LayerRegion *layerm) {
if (layerm == nullptr)
return false;
const int filament_id = layerm->region().config().wall_filament.value;
if (filament_id <= 0)
return false;
const TextureMappingZone *zone = this->print()->texture_mapping_manager().zone_from_id(unsigned(filament_id));
return zone != nullptr &&
zone->enabled &&
!zone->deleted &&
zone->uses_perimeter_path_modulation_v2() &&
!zone->use_modulated_overhang_geometry_for_support &&
(zone->is_2d_gradient() || zone->is_image_texture());
};
auto restore_support_geometry = [&]() {
if (!support_geometry_swapped)
return;
for (LayerRegionSupportGeometryBackup &backup : region_backups) {
backup.layerm->slices = std::move(backup.slices);
backup.layerm->raw_slices = std::move(backup.raw_slices);
backup.layerm->fill_surfaces = std::move(backup.fill_surfaces);
backup.layerm->fill_expolygons = std::move(backup.fill_expolygons);
backup.layerm->fill_no_overlap_expolygons = std::move(backup.fill_no_overlap_expolygons);
backup.layerm->perimeter_path_modulation_v2_applied = backup.perimeter_path_modulation_v2_applied;
}
for (LayerSupportGeometryBackup &backup : layer_backups) {
backup.layer->lslices = std::move(backup.lslices);
backup.layer->lslices_bboxes = std::move(backup.lslices_bboxes);
}
m_typed_slices = typed_slices_backup;
support_geometry_swapped = false;
};
auto use_unmodulated_support_geometry = [&]() {
bool needed = false;
for (Layer *layer : m_layers) {
for (LayerRegion *layerm : layer->m_regions) {
if (region_uses_unmodulated_support_geometry(layerm)) {
needed = true;
break;
}
}
if (needed)
break;
}
if (!needed)
return;
layer_backups.reserve(m_layers.size());
for (Layer *layer : m_layers) {
layer_backups.push_back({ layer, layer->lslices, layer->lslices_bboxes });
for (LayerRegion *layerm : layer->m_regions) {
region_backups.push_back({
layerm,
layerm->slices,
layerm->raw_slices,
layerm->fill_surfaces,
layerm->fill_expolygons,
layerm->fill_no_overlap_expolygons,
layerm->perimeter_path_modulation_v2_applied
});
}
}
support_geometry_swapped = true;
for (Layer *layer : m_layers) {
bool layer_changed = false;
for (LayerRegion *layerm : layer->m_regions) {
if (!region_uses_unmodulated_support_geometry(layerm))
continue;
ExPolygons support_slices =
!layerm->unmodulated_raw_slices.empty() ?
layerm->unmodulated_raw_slices :
(!layerm->raw_slices.empty() ?
layerm->raw_slices :
to_expolygons(layerm->slices.surfaces));
layerm->slices.set(support_slices, stInternal);
layerm->raw_slices = std::move(support_slices);
layerm->perimeter_path_modulation_v2_applied = false;
layer_changed = true;
}
if (layer_changed)
rebuild_layer_geometry(layer);
}
this->detect_surfaces_type();
};
try {
use_unmodulated_support_geometry();
if(!has_support() && !m_print->get_no_check_flag()) {
// BBS: pop a warning if objects have significant amount of overhangs but support material is not enabled
// Note: we also need to pop warning if support is disabled and only raft is enabled
m_print->set_status(50, L("Checking support necessity"));
typedef std::chrono::high_resolution_clock clock_;
typedef std::chrono::duration<double, std::ratio<1> > second_;
std::chrono::time_point<clock_> t0{ clock_::now() };
SupportNecessaryType sntype = this->is_support_necessary();
double duration{ std::chrono::duration_cast<second_>(clock_::now() - t0).count() };
BOOST_LOG_TRIVIAL(info) << std::fixed << std::setprecision(0) << "is_support_necessary takes " << duration << " secs.";
if (sntype != NoNeedSupp) {
std::map<SupportNecessaryType, std::string> reasons = {
{SharpTail,L("floating regions")},
{Cantilever,L("floating cantilever")},
{LargeOverhang,L("large overhangs")} };
std::string warning_message = Slic3r::format(L("It seems object %s has %s. Please re-orient the object or enable support generation."),
this->model_object()->name, reasons[sntype]);
this->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, warning_message, PrintStateBase::SlicingNeedSupportOn);
}
#if 0
// Printing without supports. Empty layer means some objects or object parts are levitating,
// therefore they cannot be printed without supports.
for (const Layer *layer : m_layers)
if (layer->empty())
throw Slic3r::SlicingError("Levitating objects cannot be printed without supports.");
// Printing without supports. Empty layer means some objects or object parts are levitating,
// therefore they cannot be printed without supports.
for (const Layer *layer : m_layers)
if (layer->empty())
throw Slic3r::SlicingError("Levitating objects cannot be printed without supports.");
#endif
}
}
if ((this->has_support() && m_layers.size() > 1) || (this->has_raft() && !m_layers.empty())) {
m_print->set_status(50, L("Generating support"));
if ((this->has_support() && m_layers.size() > 1) || (this->has_raft() && !m_layers.empty())) {
m_print->set_status(50, L("Generating support"));
this->_generate_support_material();
m_print->throw_if_canceled();
this->_generate_support_material();
m_print->throw_if_canceled();
}
restore_support_geometry();
this->set_done(posSupportMaterial);
} catch (...) {
restore_support_geometry();
throw;
}
this->set_done(posSupportMaterial);
}
}

View File

@@ -526,19 +526,25 @@ static int mapping_mode_from_name(const std::string &name)
static std::string modulation_mode_name(int mode)
{
return clamp_int(mode,
int(TextureMappingZone::ModulationLineWidth),
int(TextureMappingZone::ModulationPerimeterPath)) ==
int(TextureMappingZone::ModulationPerimeterPath) ?
std::string("perimeter_path") :
std::string("line_width");
switch (clamp_int(mode,
int(TextureMappingZone::ModulationLineWidth),
int(TextureMappingZone::ModulationPerimeterPathV2))) {
case int(TextureMappingZone::ModulationPerimeterPath):
return std::string("perimeter_path");
case int(TextureMappingZone::ModulationPerimeterPathV2):
return std::string("perimeter_path_v2");
default:
return std::string("line_width");
}
}
static int modulation_mode_from_name(const std::string &name)
{
return name == "perimeter_path" ?
int(TextureMappingZone::ModulationPerimeterPath) :
int(TextureMappingZone::ModulationLineWidth);
if (name == "perimeter_path")
return int(TextureMappingZone::ModulationPerimeterPath);
if (name == "perimeter_path_v2")
return int(TextureMappingZone::ModulationPerimeterPathV2);
return int(TextureMappingZone::ModulationLineWidth);
}
static std::string top_visible_recolor_aggressiveness_name(int mode)
@@ -859,6 +865,7 @@ bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const
seam_hiding == rhs.seam_hiding &&
nonlinear_offset_adjustment == rhs.nonlinear_offset_adjustment &&
modulation_mode == rhs.modulation_mode &&
use_modulated_overhang_geometry_for_support == rhs.use_modulated_overhang_geometry_for_support &&
recolor_small_perimeter_loops == rhs.recolor_small_perimeter_loops &&
recolor_top_visible_perimeter_sections == rhs.recolor_top_visible_perimeter_sections &&
top_visible_perimeter_recolor_aggressiveness == rhs.top_visible_perimeter_recolor_aggressiveness &&
@@ -1116,6 +1123,7 @@ std::string TextureMappingManager::serialize_entries()
texture["hide_seams"] = zone.seam_hiding;
texture["nonlinear_offset_adjustment"] = zone.nonlinear_offset_adjustment;
texture["modulation_mode"] = modulation_mode_name(zone.modulation_mode);
texture["use_modulated_overhang_geometry_for_support"] = zone.use_modulated_overhang_geometry_for_support;
texture["recolor_small_perimeter_loops"] = zone.recolor_small_perimeter_loops || zone.recolor_top_visible_perimeter_sections;
texture["recolor_top_visible_perimeter_sections"] = zone.recolor_top_visible_perimeter_sections;
texture["top_visible_perimeter_recolor_aggressiveness"] =
@@ -1264,6 +1272,9 @@ void TextureMappingManager::load_entries(const std::string &serialized,
zone.modulation_mode =
modulation_mode_from_name(texture.value("modulation_mode",
modulation_mode_name(TextureMappingZone::DefaultModulationMode)));
zone.use_modulated_overhang_geometry_for_support =
texture.value("use_modulated_overhang_geometry_for_support",
TextureMappingZone::DefaultUseModulatedOverhangGeometryForSupport);
zone.recolor_small_perimeter_loops =
texture.value("recolor_small_perimeter_loops", TextureMappingZone::DefaultRecolorSmallPerimeterLoops);
zone.recolor_top_visible_perimeter_sections =

View File

@@ -48,7 +48,8 @@ struct TextureMappingZone
enum ModulationMode : uint8_t {
ModulationLineWidth = 0,
ModulationPerimeterPath = 1
ModulationPerimeterPath = 1,
ModulationPerimeterPathV2 = 2
};
enum TopVisiblePerimeterRecolorAggressiveness : uint8_t {
@@ -114,6 +115,7 @@ struct TextureMappingZone
static constexpr bool DefaultSeamHiding = false;
static constexpr bool DefaultNonlinearOffsetAdjustment = false;
static constexpr int DefaultModulationMode = int(ModulationLineWidth);
static constexpr bool DefaultUseModulatedOverhangGeometryForSupport = false;
static constexpr bool DefaultRecolorSmallPerimeterLoops = false;
static constexpr bool DefaultRecolorTopVisiblePerimeterSections = false;
static constexpr int DefaultTopVisiblePerimeterRecolorAggressiveness = int(TopVisibleRecolorAggressive);
@@ -170,6 +172,7 @@ struct TextureMappingZone
bool seam_hiding = DefaultSeamHiding;
bool nonlinear_offset_adjustment = DefaultNonlinearOffsetAdjustment;
int modulation_mode = DefaultModulationMode;
bool use_modulated_overhang_geometry_for_support = DefaultUseModulatedOverhangGeometryForSupport;
bool recolor_small_perimeter_loops = DefaultRecolorSmallPerimeterLoops;
bool recolor_top_visible_perimeter_sections = DefaultRecolorTopVisiblePerimeterSections;
int top_visible_perimeter_recolor_aggressiveness = DefaultTopVisiblePerimeterRecolorAggressiveness;
@@ -199,7 +202,13 @@ struct TextureMappingZone
bool is_image_texture() const { return surface_pattern == int(ImageTexture); }
bool is_2d_gradient() const { return surface_pattern == int(Gradient2D); }
bool uses_perimeter_path_modulation() const { return modulation_mode == int(ModulationPerimeterPath); }
bool uses_perimeter_path_modulation() const
{
return modulation_mode == int(ModulationPerimeterPath) ||
modulation_mode == int(ModulationPerimeterPathV2);
}
bool uses_legacy_perimeter_path_modulation() const { return modulation_mode == int(ModulationPerimeterPath); }
bool uses_perimeter_path_modulation_v2() const { return modulation_mode == int(ModulationPerimeterPathV2); }
void reset_offset_settings()
{
@@ -224,6 +233,7 @@ struct TextureMappingZone
seam_hiding = DefaultSeamHiding;
nonlinear_offset_adjustment = DefaultNonlinearOffsetAdjustment;
modulation_mode = DefaultModulationMode;
use_modulated_overhang_geometry_for_support = DefaultUseModulatedOverhangGeometryForSupport;
recolor_small_perimeter_loops = DefaultRecolorSmallPerimeterLoops;
recolor_top_visible_perimeter_sections = DefaultRecolorTopVisiblePerimeterSections;
top_visible_perimeter_recolor_aggressiveness = DefaultTopVisiblePerimeterRecolorAggressiveness;

View File

@@ -1426,6 +1426,7 @@ public:
bool seam_hiding,
bool nonlinear_offset_adjustment,
int modulation_mode,
bool use_modulated_overhang_geometry_for_support,
bool recolor_small_perimeter_loops,
bool recolor_top_visible_perimeter_sections,
int top_visible_perimeter_recolor_aggressiveness,
@@ -1699,11 +1700,12 @@ public:
modulation_mode_row->Add(new wxStaticText(print_settings_page, wxID_ANY, _L("Modulation mode:")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap);
wxArrayString modulation_mode_choices;
modulation_mode_choices.Add(_L("Line width modulation"));
modulation_mode_choices.Add(_L("Perimeter path modulation"));
modulation_mode_choices.Add(_L("Perimeter path modulation (legacy v1)"));
modulation_mode_choices.Add(_L("Perimeter path modulation (v2)"));
m_modulation_mode_choice = new wxChoice(print_settings_page, wxID_ANY, wxDefaultPosition, wxDefaultSize, modulation_mode_choices);
m_modulation_mode_choice->SetSelection(std::clamp(modulation_mode,
int(TextureMappingZone::ModulationLineWidth),
int(TextureMappingZone::ModulationPerimeterPath)));
int(TextureMappingZone::ModulationPerimeterPathV2)));
modulation_mode_row->Add(m_modulation_mode_choice, 1, wxALIGN_CENTER_VERTICAL);
print_settings_root->Add(modulation_mode_row, 0, wxEXPAND | wxALL, gap);
m_modulation_mode_choice->Bind(wxEVT_CHOICE, [this](wxCommandEvent &) { update_modulation_mode_options_visibility(false); });
@@ -1757,6 +1759,10 @@ public:
m_compact_offset_mode_checkbox->SetToolTip(
_L("Normalizes sampled filament offsets so the strongest active color uses the full maximum line width."));
experimental_box->Add(m_compact_offset_mode_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap);
m_use_modulated_overhang_geometry_for_support_checkbox =
new wxCheckBox(experimental_page, wxID_ANY, _L("Use modulated overhang geometry in support generation"));
m_use_modulated_overhang_geometry_for_support_checkbox->SetValue(use_modulated_overhang_geometry_for_support);
experimental_box->Add(m_use_modulated_overhang_geometry_for_support_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap);
m_dithering_resolution_panel = new wxPanel(experimental_page, wxID_ANY);
auto *dithering_resolution_row = new wxBoxSizer(wxHORIZONTAL);
m_dithering_resolution_panel->SetSizer(dithering_resolution_row);
@@ -2028,12 +2034,17 @@ public:
bool reduce_outer_surface_texture() const { return false; }
bool seam_hiding() const { return m_seam_hiding_checkbox && m_seam_hiding_checkbox->GetValue(); }
bool nonlinear_offset_adjustment() const { return m_nonlinear_offset_adjustment_checkbox && m_nonlinear_offset_adjustment_checkbox->GetValue(); }
bool use_modulated_overhang_geometry_for_support() const
{
return m_use_modulated_overhang_geometry_for_support_checkbox != nullptr &&
m_use_modulated_overhang_geometry_for_support_checkbox->GetValue();
}
int modulation_mode() const
{
return m_modulation_mode_choice ?
std::clamp(m_modulation_mode_choice->GetSelection(),
int(TextureMappingZone::ModulationLineWidth),
int(TextureMappingZone::ModulationPerimeterPath)) :
int(TextureMappingZone::ModulationPerimeterPathV2)) :
TextureMappingZone::DefaultModulationMode;
}
bool compact_offset_mode() const { return dithering_enabled() || (m_compact_offset_mode_checkbox && m_compact_offset_mode_checkbox->GetValue()); }
@@ -2383,7 +2394,9 @@ private:
void update_modulation_mode_options_visibility(bool fit_dialog)
{
const bool perimeter_path_mode = modulation_mode() == int(TextureMappingZone::ModulationPerimeterPath);
const bool perimeter_path_mode = modulation_mode() == int(TextureMappingZone::ModulationPerimeterPath) ||
modulation_mode() == int(TextureMappingZone::ModulationPerimeterPathV2);
const bool perimeter_path_v2_mode = modulation_mode() == int(TextureMappingZone::ModulationPerimeterPathV2);
const bool top_visible_checked =
m_recolor_top_visible_perimeter_sections_checkbox != nullptr &&
m_recolor_top_visible_perimeter_sections_checkbox->GetValue();
@@ -2393,6 +2406,8 @@ private:
m_recolor_small_perimeter_loops_checkbox->Enable(perimeter_path_mode && !top_visible_checked);
if (m_recolor_top_visible_perimeter_sections_checkbox != nullptr)
m_recolor_top_visible_perimeter_sections_checkbox->Enable(perimeter_path_mode);
if (m_use_modulated_overhang_geometry_for_support_checkbox != nullptr)
m_use_modulated_overhang_geometry_for_support_checkbox->Enable(perimeter_path_v2_mode);
const bool top_visible_enabled =
perimeter_path_mode &&
top_visible_checked;
@@ -2457,6 +2472,7 @@ private:
wxCheckBox *m_reduce_outer_surface_texture_checkbox {nullptr};
wxCheckBox *m_seam_hiding_checkbox {nullptr};
wxCheckBox *m_nonlinear_offset_adjustment_checkbox {nullptr};
wxCheckBox *m_use_modulated_overhang_geometry_for_support_checkbox {nullptr};
wxChoice *m_modulation_mode_choice {nullptr};
wxCheckBox *m_recolor_small_perimeter_loops_checkbox {nullptr};
wxCheckBox *m_recolor_top_visible_perimeter_sections_checkbox {nullptr};
@@ -6642,6 +6658,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
updated.seam_hiding,
updated.nonlinear_offset_adjustment,
updated.modulation_mode,
updated.use_modulated_overhang_geometry_for_support,
updated.recolor_small_perimeter_loops,
updated.recolor_top_visible_perimeter_sections,
updated.top_visible_perimeter_recolor_aggressiveness,
@@ -6683,6 +6700,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
updated.seam_hiding = dlg.seam_hiding();
updated.nonlinear_offset_adjustment = dlg.nonlinear_offset_adjustment();
updated.modulation_mode = dlg.modulation_mode();
updated.use_modulated_overhang_geometry_for_support = dlg.use_modulated_overhang_geometry_for_support();
updated.recolor_small_perimeter_loops = dlg.recolor_small_perimeter_loops();
updated.recolor_top_visible_perimeter_sections = dlg.recolor_top_visible_perimeter_sections();
updated.top_visible_perimeter_recolor_aggressiveness = dlg.top_visible_perimeter_recolor_aggressiveness();