mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-05-14 09:02:06 +00:00
Slicing
- align merge_segmented_layers shape with FS apply_mm_segmentation
(size = num_facets_states, loop from 0, no -1 shift); painted mixed
regions were previously attributed to filament_id-1 of intent.
apply_fuzzy_skin_segmentation reads channel 1;
apply_mixed_surface_indentation uses segmentation_channel_filament_id
- port apply_mixed_surface_indentation, apply_mixed_component_surface_offsets,
apply_mixed_region_surface_offsets, apply_surface_emboss_mixed_region_override,
plus surface_emboss_mixed_* debug subsystem
- refactor apply_mm_segmentation (by-value MM, bias_mode, surface-type-
preserving intersection, region normalization, post-MM dump); hoist MM
segmentation into slice_volumes so mixed apply_* flow can mutate it
- restore clear_local_z_plan() invalidation hooks
(PrintObject.cpp:805/1264/1286)
GCode
- add LayerTools::preserve_extruder_order, honored by collect_extruders,
both reorder_extruders overloads, and
reorder_filaments_for_minimum_flush_volume; helpers
append_unique_preserve_order / remove_duplicates_preserve_order
- wire MixedFilamentManager::ordered_perimeter_extruders for grouped
manual-pattern walls; set preserve_extruder_order when >= 2
- mixed-aware support: layer_height set for support-only layers,
ExtrusionRole-based has_support/has_interface with erMixed short-
circuit, support_filament / support_interface_filament routed through
resolve_mixed
Print
- materialize mixed_filament_pointillism_{pixel_size,line_gap} in
PrintApply's option-tracking block so in-session edits diff correctly
GUI
- Tab::on_value_change: dithering_local_z_mode cascading clears, 17-key
project_config sync, update_mixed_filament_panel(false) on
mixed_filament_component_bias_enabled change
- GUI_Factories: physical_filaments_count, ui_ordered_filament_ids,
filament_menu_item_name; filaments_count includes enabled mixed
virtuals; right-click 'Change filament' submenus iterate UI-ordered IDs
Tests
- sentinel asserting MultiMaterialSegmentation uses FS-aligned shape
(39 -> 40)
4949 lines
249 KiB
C++
4949 lines
249 KiB
C++
#include <algorithm>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <numeric>
|
|
#include <sstream>
|
|
|
|
#include <boost/log/trivial.hpp>
|
|
|
|
#include <tbb/parallel_for.h>
|
|
|
|
#include "ClipperUtils.hpp"
|
|
#include "ElephantFootCompensation.hpp"
|
|
#include "Exception.hpp"
|
|
#include "I18N.hpp"
|
|
#include "Layer.hpp"
|
|
#include "MultiMaterialSegmentation.hpp"
|
|
#include "Print.hpp"
|
|
#include "SVG.hpp"
|
|
//BBS
|
|
#include "ShortestPath.hpp"
|
|
#include "libslic3r/Feature/Interlocking/InterlockingGenerator.hpp"
|
|
|
|
//! macro used to mark string used at localization, return same string
|
|
#define L(s) Slic3r::I18N::translate(s)
|
|
|
|
namespace Slic3r {
|
|
|
|
bool PrintObject::clip_multipart_objects = true;
|
|
bool PrintObject::infill_only_where_needed = false;
|
|
|
|
// Forward declarations for the surface_emboss_mixed debug subsystem (defined further below).
|
|
static bool has_surface_emboss_mixed_volume(const PrintObject &print_object);
|
|
static std::string surface_emboss_mixed_debug_file_path(const PrintObject &print_object);
|
|
static void reset_surface_emboss_mixed_debug_file(const PrintObject &print_object);
|
|
static void dump_surface_emboss_mixed_layer_state(
|
|
const char *stage,
|
|
const PrintObject &print_object,
|
|
size_t layer_id,
|
|
const Layer &layer,
|
|
const PrintObjectRegions::LayerRangeRegions &layer_range,
|
|
const std::vector<ExPolygons> *segmentation_layer = nullptr);
|
|
|
|
LayerPtrs new_layers(
|
|
PrintObject *print_object,
|
|
// Object layers (pairs of bottom/top Z coordinate), without the raft.
|
|
const std::vector<coordf_t> &object_layers)
|
|
{
|
|
LayerPtrs out;
|
|
out.reserve(object_layers.size());
|
|
auto id = int(print_object->slicing_parameters().raft_layers());
|
|
coordf_t zmin = print_object->slicing_parameters().object_print_z_min;
|
|
Layer *prev = nullptr;
|
|
for (size_t i_layer = 0; i_layer < object_layers.size(); i_layer += 2) {
|
|
coordf_t lo = object_layers[i_layer];
|
|
coordf_t hi = object_layers[i_layer + 1];
|
|
coordf_t slice_z = 0.5 * (lo + hi);
|
|
bool zaa_active = false;
|
|
coordf_t z_offset = 0.0;
|
|
size_t num_regions = print_object->num_printing_regions();
|
|
for (size_t rid = 0; rid < num_regions; ++rid) {
|
|
const auto &rcfg = print_object->printing_region(rid).config();
|
|
if (rcfg.zaa_enabled) {
|
|
if (!zaa_active || rcfg.zaa_min_z < z_offset)
|
|
z_offset = rcfg.zaa_min_z;
|
|
zaa_active = true;
|
|
}
|
|
}
|
|
if (zaa_active) {
|
|
slice_z = lo + z_offset;
|
|
if (slice_z < lo || slice_z > hi) {
|
|
throw RuntimeError("Bad min Z value");
|
|
}
|
|
}
|
|
Layer *layer = new Layer(id ++, print_object, hi - lo, hi + zmin, slice_z);
|
|
out.emplace_back(layer);
|
|
if (prev != nullptr) {
|
|
prev->upper_layer = layer;
|
|
layer->lower_layer = prev;
|
|
}
|
|
prev = layer;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Slice single triangle mesh.
|
|
static std::vector<ExPolygons> slice_volume(
|
|
const ModelVolume &volume,
|
|
const std::vector<float> &zs,
|
|
const MeshSlicingParamsEx ¶ms,
|
|
const std::function<void()> &throw_on_cancel_callback)
|
|
{
|
|
std::vector<ExPolygons> layers;
|
|
if (! zs.empty()) {
|
|
indexed_triangle_set its = volume.mesh().its;
|
|
if (its.indices.size() > 0) {
|
|
MeshSlicingParamsEx params2 { params };
|
|
params2.trafo = params2.trafo * volume.get_matrix();
|
|
if (params2.trafo.rotation().determinant() < 0.)
|
|
its_flip_triangles(its);
|
|
layers = slice_mesh_ex(its, zs, params2, throw_on_cancel_callback);
|
|
throw_on_cancel_callback();
|
|
}
|
|
}
|
|
return layers;
|
|
}
|
|
|
|
// Slice single triangle mesh.
|
|
// Filter the zs not inside the ranges. The ranges are closed at the bottom and open at the top, they are sorted lexicographically and non overlapping.
|
|
static std::vector<ExPolygons> slice_volume(
|
|
const ModelVolume &volume,
|
|
const std::vector<float> &z,
|
|
const std::vector<t_layer_height_range> &ranges,
|
|
const MeshSlicingParamsEx ¶ms,
|
|
const std::function<void()> &throw_on_cancel_callback)
|
|
{
|
|
std::vector<ExPolygons> out;
|
|
if (! z.empty() && ! ranges.empty()) {
|
|
if (ranges.size() == 1 && z.front() >= ranges.front().first && z.back() < ranges.front().second) {
|
|
// All layers fit into a single range.
|
|
out = slice_volume(volume, z, params, throw_on_cancel_callback);
|
|
} else {
|
|
std::vector<float> z_filtered;
|
|
std::vector<std::pair<size_t, size_t>> n_filtered;
|
|
z_filtered.reserve(z.size());
|
|
n_filtered.reserve(2 * ranges.size());
|
|
size_t i = 0;
|
|
for (const t_layer_height_range &range : ranges) {
|
|
for (; i < z.size() && z[i] < range.first; ++ i) ;
|
|
size_t first = i;
|
|
for (; i < z.size() && z[i] < range.second; ++ i)
|
|
z_filtered.emplace_back(z[i]);
|
|
if (i > first)
|
|
n_filtered.emplace_back(std::make_pair(first, i));
|
|
}
|
|
if (! n_filtered.empty()) {
|
|
std::vector<ExPolygons> layers = slice_volume(volume, z_filtered, params, throw_on_cancel_callback);
|
|
out.assign(z.size(), ExPolygons());
|
|
i = 0;
|
|
for (const std::pair<size_t, size_t> &span : n_filtered)
|
|
for (size_t j = span.first; j < span.second; ++ j)
|
|
out[j] = std::move(layers[i ++]);
|
|
}
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
static inline bool model_volume_needs_slicing(const ModelVolume &mv)
|
|
{
|
|
ModelVolumeType type = mv.type();
|
|
return type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER;
|
|
}
|
|
|
|
// Slice printable volumes, negative volumes and modifier volumes, sorted by ModelVolume::id().
|
|
// Apply closing radius.
|
|
// Apply positive XY compensation to ModelVolumeType::MODEL_PART and ModelVolumeType::PARAMETER_MODIFIER, not to ModelVolumeType::NEGATIVE_VOLUME.
|
|
// Apply contour simplification.
|
|
static std::vector<VolumeSlices> slice_volumes_inner(
|
|
const PrintConfig &print_config,
|
|
const PrintObjectConfig &print_object_config,
|
|
const Transform3d &object_trafo,
|
|
ModelVolumePtrs model_volumes,
|
|
const std::vector<PrintObjectRegions::LayerRangeRegions> &layer_ranges,
|
|
const std::vector<float> &zs,
|
|
const std::function<void()> &throw_on_cancel_callback)
|
|
{
|
|
model_volumes_sort_by_id(model_volumes);
|
|
|
|
std::vector<VolumeSlices> out;
|
|
out.reserve(model_volumes.size());
|
|
|
|
std::vector<t_layer_height_range> slicing_ranges;
|
|
if (layer_ranges.size() > 1)
|
|
slicing_ranges.reserve(layer_ranges.size());
|
|
|
|
MeshSlicingParamsEx params_base;
|
|
params_base.closing_radius = print_object_config.slice_closing_radius.value;
|
|
params_base.extra_offset = 0;
|
|
params_base.trafo = object_trafo;
|
|
//BBS: 0.0025mm is safe enough to simplify the data to speed slicing up for high-resolution model.
|
|
//Also has on influence on arc fitting which has default resolution 0.0125mm.
|
|
params_base.resolution = print_config.resolution <= 0.001 ? 0.0f : 0.0025;
|
|
switch (print_object_config.slicing_mode.value) {
|
|
case SlicingMode::Regular: params_base.mode = MeshSlicingParams::SlicingMode::Regular; break;
|
|
case SlicingMode::EvenOdd: params_base.mode = MeshSlicingParams::SlicingMode::EvenOdd; break;
|
|
case SlicingMode::CloseHoles: params_base.mode = MeshSlicingParams::SlicingMode::Positive; break;
|
|
}
|
|
|
|
params_base.mode_below = params_base.mode;
|
|
|
|
// BBS
|
|
const size_t num_extruders = print_config.filament_diameter.size();
|
|
const bool is_mm_painted = num_extruders > 1 && std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
|
|
// BBS: don't do size compensation when slice volume.
|
|
// Will handle contour and hole size compensation seperately later.
|
|
//const auto extra_offset = is_mm_painted ? 0.f : std::max(0.f, float(print_object_config.xy_contour_compensation.value));
|
|
const auto extra_offset = 0.f;
|
|
|
|
for (const ModelVolume *model_volume : model_volumes)
|
|
if (model_volume_needs_slicing(*model_volume)) {
|
|
MeshSlicingParamsEx params { params_base };
|
|
if (! model_volume->is_negative_volume())
|
|
params.extra_offset = extra_offset;
|
|
if (layer_ranges.size() == 1) {
|
|
if (const PrintObjectRegions::LayerRangeRegions &layer_range = layer_ranges.front(); layer_range.has_volume(model_volume->id())) {
|
|
if (model_volume->is_model_part() && print_config.spiral_mode) {
|
|
auto it = std::find_if(layer_range.volume_regions.begin(), layer_range.volume_regions.end(),
|
|
[model_volume](const auto &slice){ return model_volume == slice.model_volume; });
|
|
params.mode = MeshSlicingParams::SlicingMode::PositiveLargestContour;
|
|
// Slice the bottom layers with SlicingMode::Regular.
|
|
// This needs to be in sync with LayerRegion::make_perimeters() spiral_mode!
|
|
const PrintRegionConfig ®ion_config = it->region->config();
|
|
params.slicing_mode_normal_below_layer = size_t(region_config.bottom_shell_layers.value);
|
|
for (; params.slicing_mode_normal_below_layer < zs.size() && zs[params.slicing_mode_normal_below_layer] < region_config.bottom_shell_thickness - EPSILON;
|
|
++ params.slicing_mode_normal_below_layer);
|
|
}
|
|
out.push_back({
|
|
model_volume->id(),
|
|
slice_volume(*model_volume, zs, params, throw_on_cancel_callback)
|
|
});
|
|
}
|
|
} else {
|
|
assert(! print_config.spiral_mode);
|
|
slicing_ranges.clear();
|
|
for (const PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges)
|
|
if (layer_range.has_volume(model_volume->id()))
|
|
slicing_ranges.emplace_back(layer_range.layer_height_range);
|
|
if (! slicing_ranges.empty())
|
|
out.push_back({
|
|
model_volume->id(),
|
|
slice_volume(*model_volume, zs, slicing_ranges, params, throw_on_cancel_callback)
|
|
});
|
|
}
|
|
if (! out.empty() && out.back().slices.empty())
|
|
out.pop_back();
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
static inline VolumeSlices& volume_slices_find_by_id(std::vector<VolumeSlices> &volume_slices, const ObjectID id)
|
|
{
|
|
auto it = lower_bound_by_predicate(volume_slices.begin(), volume_slices.end(), [id](const VolumeSlices &vs) { return vs.volume_id < id; });
|
|
assert(it != volume_slices.end() && it->volume_id == id);
|
|
return *it;
|
|
}
|
|
|
|
static inline bool overlap_in_xy(const PrintObjectRegions::BoundingBox &l, const PrintObjectRegions::BoundingBox &r)
|
|
{
|
|
return ! (l.max().x() < r.min().x() || l.min().x() > r.max().x() ||
|
|
l.max().y() < r.min().y() || l.min().y() > r.max().y());
|
|
}
|
|
|
|
static std::vector<PrintObjectRegions::LayerRangeRegions>::const_iterator layer_range_first(const std::vector<PrintObjectRegions::LayerRangeRegions> &layer_ranges, double z)
|
|
{
|
|
auto it = lower_bound_by_predicate(layer_ranges.begin(), layer_ranges.end(),
|
|
[z](const PrintObjectRegions::LayerRangeRegions &lr) {
|
|
return lr.layer_height_range.second < z && abs(lr.layer_height_range.second - z) > EPSILON;
|
|
});
|
|
assert(it != layer_ranges.end() && it->layer_height_range.first <= z && z <= it->layer_height_range.second);
|
|
if (z == it->layer_height_range.second)
|
|
if (auto it_next = it; ++ it_next != layer_ranges.end() && it_next->layer_height_range.first == z)
|
|
it = it_next;
|
|
assert(it != layer_ranges.end() && it->layer_height_range.first <= z && z <= it->layer_height_range.second);
|
|
return it;
|
|
}
|
|
|
|
static std::vector<PrintObjectRegions::LayerRangeRegions>::const_iterator layer_range_next(
|
|
const std::vector<PrintObjectRegions::LayerRangeRegions> &layer_ranges,
|
|
std::vector<PrintObjectRegions::LayerRangeRegions>::const_iterator it,
|
|
double z)
|
|
{
|
|
for (; it->layer_height_range.second <= z + EPSILON; ++ it)
|
|
assert(it != layer_ranges.end());
|
|
assert(it != layer_ranges.end() && it->layer_height_range.first <= z && z < it->layer_height_range.second);
|
|
return it;
|
|
}
|
|
|
|
static std::vector<std::vector<ExPolygons>> slices_to_regions(
|
|
const PrintConfig &print_config,
|
|
const PrintObject &print_object,
|
|
ModelVolumePtrs model_volumes,
|
|
const PrintObjectRegions &print_object_regions,
|
|
const std::vector<float> &zs,
|
|
std::vector<VolumeSlices> &&volume_slices,
|
|
// If clipping is disabled, then ExPolygons produced by different volumes will never be merged, thus they will be allowed to overlap.
|
|
// It is up to the model designer to handle these overlaps.
|
|
const bool clip_multipart_objects,
|
|
const std::function<void()> &throw_on_cancel_callback)
|
|
{
|
|
model_volumes_sort_by_id(model_volumes);
|
|
|
|
std::vector<std::vector<ExPolygons>> slices_by_region(print_object_regions.all_regions.size(), std::vector<ExPolygons>(zs.size(), ExPolygons()));
|
|
|
|
// First shuffle slices into regions if there is no overlap with another region possible, collect zs of the complex cases.
|
|
std::vector<std::pair<size_t, float>> zs_complex;
|
|
{
|
|
size_t z_idx = 0;
|
|
for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) {
|
|
for (; z_idx < zs.size() && zs[z_idx] < layer_range.layer_height_range.first; ++ z_idx) ;
|
|
if (layer_range.volume_regions.empty()) {
|
|
} else if (layer_range.volume_regions.size() == 1) {
|
|
const ModelVolume *model_volume = layer_range.volume_regions.front().model_volume;
|
|
assert(model_volume != nullptr);
|
|
if (model_volume->is_model_part()) {
|
|
VolumeSlices &slices_src = volume_slices_find_by_id(volume_slices, model_volume->id());
|
|
auto &slices_dst = slices_by_region[layer_range.volume_regions.front().region->print_object_region_id()];
|
|
for (; z_idx < zs.size() && zs[z_idx] < layer_range.layer_height_range.second; ++ z_idx)
|
|
slices_dst[z_idx] = std::move(slices_src.slices[z_idx]);
|
|
}
|
|
} else {
|
|
zs_complex.reserve(zs.size());
|
|
for (; z_idx < zs.size() && zs[z_idx] < layer_range.layer_height_range.second; ++ z_idx) {
|
|
float z = zs[z_idx];
|
|
int idx_first_printable_region = -1;
|
|
bool complex = false;
|
|
for (int idx_region = 0; idx_region < int(layer_range.volume_regions.size()); ++ idx_region) {
|
|
const PrintObjectRegions::VolumeRegion ®ion = layer_range.volume_regions[idx_region];
|
|
if (region.bbox->min().z() <= z && region.bbox->max().z() >= z) {
|
|
if (idx_first_printable_region == -1 && region.model_volume->is_model_part())
|
|
idx_first_printable_region = idx_region;
|
|
else if (idx_first_printable_region != -1) {
|
|
// Test for overlap with some other region.
|
|
for (int idx_region2 = idx_first_printable_region; idx_region2 < idx_region; ++ idx_region2) {
|
|
const PrintObjectRegions::VolumeRegion ®ion2 = layer_range.volume_regions[idx_region2];
|
|
if (region2.bbox->min().z() <= z && region2.bbox->max().z() >= z && overlap_in_xy(*region.bbox, *region2.bbox)) {
|
|
complex = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (complex)
|
|
zs_complex.push_back({ z_idx, z });
|
|
else if (idx_first_printable_region >= 0) {
|
|
const PrintObjectRegions::VolumeRegion ®ion = layer_range.volume_regions[idx_first_printable_region];
|
|
slices_by_region[region.region->print_object_region_id()][z_idx] = std::move(volume_slices_find_by_id(volume_slices, region.model_volume->id()).slices[z_idx]);
|
|
}
|
|
}
|
|
}
|
|
throw_on_cancel_callback();
|
|
}
|
|
}
|
|
|
|
// Second perform region clipping and assignment in parallel.
|
|
if (! zs_complex.empty()) {
|
|
std::vector<std::vector<VolumeSlices*>> layer_ranges_regions_to_slices(print_object_regions.layer_ranges.size(), std::vector<VolumeSlices*>());
|
|
for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) {
|
|
std::vector<VolumeSlices*> &layer_range_regions_to_slices = layer_ranges_regions_to_slices[&layer_range - print_object_regions.layer_ranges.data()];
|
|
layer_range_regions_to_slices.reserve(layer_range.volume_regions.size());
|
|
for (const PrintObjectRegions::VolumeRegion ®ion : layer_range.volume_regions)
|
|
layer_range_regions_to_slices.push_back(&volume_slices_find_by_id(volume_slices, region.model_volume->id()));
|
|
}
|
|
tbb::parallel_for(
|
|
tbb::blocked_range<size_t>(0, zs_complex.size()),
|
|
[&slices_by_region, &print_object_regions, &zs_complex, &layer_ranges_regions_to_slices, clip_multipart_objects, &throw_on_cancel_callback]
|
|
(const tbb::blocked_range<size_t> &range) {
|
|
float z = zs_complex[range.begin()].second;
|
|
auto it_layer_range = layer_range_first(print_object_regions.layer_ranges, z);
|
|
// Per volume_regions slices at this Z height.
|
|
struct RegionSlice {
|
|
ExPolygons expolygons;
|
|
// Identifier of this region in PrintObjectRegions::all_regions
|
|
int region_id;
|
|
ObjectID volume_id;
|
|
bool operator<(const RegionSlice &rhs) const {
|
|
bool this_empty = this->region_id < 0 || this->expolygons.empty();
|
|
bool rhs_empty = rhs.region_id < 0 || rhs.expolygons.empty();
|
|
// Sort the empty items to the end of the list.
|
|
// Sort by region_id & volume_id lexicographically.
|
|
return ! this_empty && (rhs_empty || (this->region_id < rhs.region_id || (this->region_id == rhs.region_id && volume_id < volume_id)));
|
|
}
|
|
};
|
|
|
|
// BBS
|
|
auto trim_overlap = [](ExPolygons& expolys_a, ExPolygons& expolys_b) {
|
|
ExPolygons trimming_a;
|
|
ExPolygons trimming_b;
|
|
|
|
for (ExPolygon& expoly_a : expolys_a) {
|
|
BoundingBox bbox_a = get_extents(expoly_a);
|
|
ExPolygons expolys_new;
|
|
for (ExPolygon& expoly_b : expolys_b) {
|
|
BoundingBox bbox_b = get_extents(expoly_b);
|
|
if (!bbox_a.overlap(bbox_b))
|
|
continue;
|
|
|
|
ExPolygons temp = intersection_ex(expoly_b, expoly_a, ApplySafetyOffset::Yes);
|
|
if (temp.empty())
|
|
continue;
|
|
|
|
if (expoly_a.contour.length() > expoly_b.contour.length())
|
|
trimming_a.insert(trimming_a.end(), temp.begin(), temp.end());
|
|
else
|
|
trimming_b.insert(trimming_b.end(), temp.begin(), temp.end());
|
|
}
|
|
}
|
|
|
|
expolys_a = diff_ex(expolys_a, trimming_a);
|
|
expolys_b = diff_ex(expolys_b, trimming_b);
|
|
};
|
|
|
|
std::vector<RegionSlice> temp_slices;
|
|
for (size_t zs_complex_idx = range.begin(); zs_complex_idx < range.end(); ++ zs_complex_idx) {
|
|
auto [z_idx, z] = zs_complex[zs_complex_idx];
|
|
it_layer_range = layer_range_next(print_object_regions.layer_ranges, it_layer_range, z);
|
|
const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range;
|
|
{
|
|
std::vector<VolumeSlices*> &layer_range_regions_to_slices = layer_ranges_regions_to_slices[it_layer_range - print_object_regions.layer_ranges.begin()];
|
|
// Per volume_regions slices at thiz Z height.
|
|
temp_slices.clear();
|
|
temp_slices.reserve(layer_range.volume_regions.size());
|
|
for (VolumeSlices* &slices : layer_range_regions_to_slices) {
|
|
const PrintObjectRegions::VolumeRegion &volume_region = layer_range.volume_regions[&slices - layer_range_regions_to_slices.data()];
|
|
temp_slices.push_back({ std::move(slices->slices[z_idx]), volume_region.region ? volume_region.region->print_object_region_id() : -1, volume_region.model_volume->id() });
|
|
}
|
|
}
|
|
for (int idx_region = 0; idx_region < int(layer_range.volume_regions.size()); ++ idx_region)
|
|
if (! temp_slices[idx_region].expolygons.empty()) {
|
|
const PrintObjectRegions::VolumeRegion ®ion = layer_range.volume_regions[idx_region];
|
|
if (region.model_volume->is_modifier()) {
|
|
assert(region.parent > -1);
|
|
bool next_region_same_modifier = idx_region + 1 < int(temp_slices.size()) && layer_range.volume_regions[idx_region + 1].model_volume == region.model_volume;
|
|
RegionSlice &parent_slice = temp_slices[region.parent];
|
|
RegionSlice &this_slice = temp_slices[idx_region];
|
|
ExPolygons source = std::move(this_slice.expolygons);
|
|
if (parent_slice.expolygons.empty()) {
|
|
this_slice .expolygons.clear();
|
|
} else {
|
|
this_slice .expolygons = intersection_ex(parent_slice.expolygons, source);
|
|
parent_slice.expolygons = diff_ex (parent_slice.expolygons, source);
|
|
}
|
|
if (next_region_same_modifier)
|
|
// To be used in the following iteration.
|
|
temp_slices[idx_region + 1].expolygons = std::move(source);
|
|
} else if ((region.model_volume->is_model_part() && clip_multipart_objects) || region.model_volume->is_negative_volume()) {
|
|
// Clip every non-zero region preceding it.
|
|
for (int idx_region2 = 0; idx_region2 < idx_region; ++ idx_region2)
|
|
if (! temp_slices[idx_region2].expolygons.empty()) {
|
|
// Skip trim_overlap for now, because it slow down the performace so much for some special cases
|
|
#if 1
|
|
if (const PrintObjectRegions::VolumeRegion& region2 = layer_range.volume_regions[idx_region2];
|
|
!region2.model_volume->is_negative_volume() && overlap_in_xy(*region.bbox, *region2.bbox))
|
|
temp_slices[idx_region2].expolygons = diff_ex(temp_slices[idx_region2].expolygons, temp_slices[idx_region].expolygons);
|
|
#else
|
|
const PrintObjectRegions::VolumeRegion& region2 = layer_range.volume_regions[idx_region2];
|
|
if (!region2.model_volume->is_negative_volume() && overlap_in_xy(*region.bbox, *region2.bbox))
|
|
//BBS: handle negative_volume seperately, always minus the negative volume and don't need to trim overlap
|
|
if (!region.model_volume->is_negative_volume())
|
|
trim_overlap(temp_slices[idx_region2].expolygons, temp_slices[idx_region].expolygons);
|
|
else
|
|
temp_slices[idx_region2].expolygons = diff_ex(temp_slices[idx_region2].expolygons, temp_slices[idx_region].expolygons);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
// Sort by region_id, push empty slices to the end.
|
|
std::sort(temp_slices.begin(), temp_slices.end());
|
|
// Remove the empty slices.
|
|
temp_slices.erase(std::find_if(temp_slices.begin(), temp_slices.end(), [](const auto &slice) { return slice.region_id == -1 || slice.expolygons.empty(); }), temp_slices.end());
|
|
// Merge slices and store them to the output.
|
|
for (int i = 0; i < int(temp_slices.size());) {
|
|
// Find a range of temp_slices with the same region_id.
|
|
int j = i;
|
|
bool merged = false;
|
|
ExPolygons &expolygons = temp_slices[i].expolygons;
|
|
for (++ j; j < int(temp_slices.size()) && temp_slices[i].region_id == temp_slices[j].region_id; ++ j)
|
|
if (ExPolygons &expolygons2 = temp_slices[j].expolygons; ! expolygons2.empty()) {
|
|
if (expolygons.empty()) {
|
|
expolygons = std::move(expolygons2);
|
|
} else {
|
|
append(expolygons, std::move(expolygons2));
|
|
merged = true;
|
|
}
|
|
}
|
|
// Don't unite the regions if ! clip_multipart_objects. In that case it is user's responsibility
|
|
// to handle region overlaps. Indeed, one may intentionally let the regions overlap to produce crossing perimeters
|
|
// for example.
|
|
if (merged && clip_multipart_objects)
|
|
expolygons = closing_ex(expolygons, float(scale_(EPSILON)));
|
|
slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons);
|
|
i = j;
|
|
}
|
|
throw_on_cancel_callback();
|
|
}
|
|
});
|
|
}
|
|
|
|
return slices_by_region;
|
|
}
|
|
|
|
//BBS: justify whether a volume is connected to another one
|
|
bool doesVolumeIntersect(VolumeSlices& vs1, VolumeSlices& vs2)
|
|
{
|
|
if (vs1.volume_id == vs2.volume_id) return true;
|
|
// two volumes in the same object should have same number of layers, otherwise the slicing is incorrect.
|
|
if (vs1.slices.size() != vs2.slices.size()) return false;
|
|
|
|
auto& vs1s = vs1.slices;
|
|
auto& vs2s = vs2.slices;
|
|
bool is_intersect = false;
|
|
|
|
tbb::parallel_for(tbb::blocked_range<int>(0, vs1s.size()),
|
|
[&vs1s, &vs2s, &is_intersect](const tbb::blocked_range<int>& range) {
|
|
for (auto i = range.begin(); i != range.end(); ++i) {
|
|
if (vs1s[i].empty()) continue;
|
|
|
|
if (overlaps(vs1s[i], vs2s[i])) {
|
|
is_intersect = true;
|
|
break;
|
|
}
|
|
if (i + 1 != vs2s.size() && overlaps(vs1s[i], vs2s[i + 1])) {
|
|
is_intersect = true;
|
|
break;
|
|
}
|
|
if (i - 1 >= 0 && overlaps(vs1s[i], vs2s[i - 1])) {
|
|
is_intersect = true;
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
return is_intersect;
|
|
}
|
|
|
|
//BBS: grouping the volumes of an object according to their connection relationship
|
|
bool groupingVolumes(std::vector<VolumeSlices> objSliceByVolume, std::vector<groupedVolumeSlices>& groups, double resolution, int firstLayerReplacedBy)
|
|
{
|
|
std::vector<int> groupIndex(objSliceByVolume.size(), -1);
|
|
double offsetValue = 0.05 / SCALING_FACTOR;
|
|
|
|
std::vector<std::vector<int>> osvIndex;
|
|
for (int i = 0; i != objSliceByVolume.size(); ++i) {
|
|
for (int j = 0; j != objSliceByVolume[i].slices.size(); ++j) {
|
|
osvIndex.push_back({ i,j });
|
|
}
|
|
}
|
|
|
|
tbb::parallel_for(tbb::blocked_range<int>(0, osvIndex.size()),
|
|
[&osvIndex, &objSliceByVolume, &offsetValue, &resolution](const tbb::blocked_range<int>& range) {
|
|
for (auto k = range.begin(); k != range.end(); ++k) {
|
|
for (ExPolygon& poly_ex : objSliceByVolume[osvIndex[k][0]].slices[osvIndex[k][1]])
|
|
poly_ex.douglas_peucker(resolution);
|
|
}
|
|
});
|
|
|
|
tbb::parallel_for(tbb::blocked_range<int>(0, osvIndex.size()),
|
|
[&osvIndex, &objSliceByVolume,&offsetValue, &resolution](const tbb::blocked_range<int>& range) {
|
|
for (auto k = range.begin(); k != range.end(); ++k) {
|
|
objSliceByVolume[osvIndex[k][0]].slices[osvIndex[k][1]] = offset_ex(objSliceByVolume[osvIndex[k][0]].slices[osvIndex[k][1]], offsetValue);
|
|
}
|
|
});
|
|
|
|
for (int i = 0; i != objSliceByVolume.size(); ++i) {
|
|
if (groupIndex[i] < 0) {
|
|
groupIndex[i] = i;
|
|
}
|
|
for (int j = i + 1; j != objSliceByVolume.size(); ++j) {
|
|
if (doesVolumeIntersect(objSliceByVolume[i], objSliceByVolume[j])) {
|
|
if (groupIndex[j] < 0) groupIndex[j] = groupIndex[i];
|
|
if (groupIndex[j] != groupIndex[i]) {
|
|
int retain = std::min(groupIndex[i], groupIndex[j]);
|
|
int cover = std::max(groupIndex[i], groupIndex[j]);
|
|
for (int k = 0; k != objSliceByVolume.size(); ++k) {
|
|
if (groupIndex[k] == cover) groupIndex[k] = retain;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
std::vector<int> groupVector{};
|
|
for (int gi : groupIndex) {
|
|
bool exist = false;
|
|
for (int gv : groupVector) {
|
|
if (gv == gi) {
|
|
exist = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!exist) groupVector.push_back(gi);
|
|
}
|
|
|
|
// group volumes and their slices according to the grouping Vector
|
|
groups.clear();
|
|
|
|
for (int gv : groupVector) {
|
|
groupedVolumeSlices gvs;
|
|
gvs.groupId = gv;
|
|
for (int i = 0; i != objSliceByVolume.size(); ++i) {
|
|
if (groupIndex[i] == gv) {
|
|
gvs.volume_ids.push_back(objSliceByVolume[i].volume_id);
|
|
append(gvs.slices, objSliceByVolume[i].slices[firstLayerReplacedBy]);
|
|
}
|
|
}
|
|
|
|
// the slices of a group should be unioned
|
|
gvs.slices = offset_ex(union_ex(gvs.slices), -offsetValue);
|
|
for (ExPolygon& poly_ex : gvs.slices)
|
|
poly_ex.douglas_peucker(resolution);
|
|
|
|
groups.push_back(gvs);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//BBS: filter the members of "objSliceByVolume" such that only "model_part" are included
|
|
std::vector<VolumeSlices> findPartVolumes(const std::vector<VolumeSlices>& objSliceByVolume, ModelVolumePtrs model_volumes) {
|
|
std::vector<VolumeSlices> outPut;
|
|
for (const auto& vs : objSliceByVolume) {
|
|
for (const auto& mv : model_volumes) {
|
|
if (vs.volume_id == mv->id() && mv->is_model_part()) outPut.push_back(vs);
|
|
}
|
|
}
|
|
return outPut;
|
|
}
|
|
|
|
void applyNegtiveVolumes(ModelVolumePtrs model_volumes, const std::vector<VolumeSlices>& objSliceByVolume, std::vector<groupedVolumeSlices>& groups, double resolution) {
|
|
ExPolygons negTotal;
|
|
for (const auto& vs : objSliceByVolume) {
|
|
for (const auto& mv : model_volumes) {
|
|
if (vs.volume_id == mv->id() && mv->is_negative_volume()) {
|
|
if (vs.slices.size() > 0) {
|
|
append(negTotal, vs.slices.front());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto& g : groups) {
|
|
g.slices = diff_ex(g.slices, negTotal);
|
|
for (ExPolygon& poly_ex : g.slices)
|
|
poly_ex.douglas_peucker(resolution);
|
|
}
|
|
}
|
|
|
|
void reGroupingLayerPolygons(std::vector<groupedVolumeSlices>& gvss, ExPolygons &eps, double resolution)
|
|
{
|
|
std::vector<int> epsIndex;
|
|
epsIndex.resize(eps.size(), -1);
|
|
|
|
auto gvssc = gvss;
|
|
auto epsc = eps;
|
|
|
|
for (ExPolygon& poly_ex : epsc)
|
|
poly_ex.douglas_peucker(resolution);
|
|
|
|
for (int i = 0; i != gvssc.size(); ++i) {
|
|
for (ExPolygon& poly_ex : gvssc[i].slices)
|
|
poly_ex.douglas_peucker(resolution);
|
|
}
|
|
|
|
tbb::parallel_for(tbb::blocked_range<int>(0, epsc.size()),
|
|
[&epsc, &gvssc, &epsIndex](const tbb::blocked_range<int>& range) {
|
|
for (auto ie = range.begin(); ie != range.end(); ++ie) {
|
|
if (epsc[ie].area() <= 0)
|
|
continue;
|
|
|
|
double minArea = epsc[ie].area();
|
|
for (int iv = 0; iv != gvssc.size(); iv++) {
|
|
auto clipedExPolys = diff_ex(epsc[ie], gvssc[iv].slices);
|
|
double area = 0;
|
|
for (const auto& ce : clipedExPolys) {
|
|
area += ce.area();
|
|
}
|
|
if (area < minArea) {
|
|
minArea = area;
|
|
epsIndex[ie] = iv;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
for (int iv = 0; iv != gvss.size(); iv++)
|
|
gvss[iv].slices.clear();
|
|
|
|
for (int ie = 0; ie != eps.size(); ie++) {
|
|
if (epsIndex[ie] >= 0)
|
|
gvss[epsIndex[ie]].slices.push_back(eps[ie]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
std::string fix_slicing_errors(PrintObject* object, LayerPtrs &layers, const std::function<void()> &throw_if_canceled, int &firstLayerReplacedBy)
|
|
{
|
|
std::string error_msg;//BBS
|
|
|
|
if (layers.size() == 0) return error_msg;
|
|
|
|
// Collect layers with slicing errors.
|
|
// These layers will be fixed in parallel.
|
|
std::vector<size_t> buggy_layers;
|
|
buggy_layers.reserve(layers.size());
|
|
// BBS: get largest external perimenter width of all layers
|
|
auto get_ext_peri_width = [](Layer* layer) {return layer->m_regions.empty() ? 0 : layer->m_regions[0]->flow(frExternalPerimeter).scaled_width(); };
|
|
auto it = std::max_element(layers.begin(), layers.end(), [get_ext_peri_width](auto& a, auto& b) {return get_ext_peri_width(a) < get_ext_peri_width(b); });
|
|
coord_t thresh = get_ext_peri_width(*it) * 0.5;// half of external perimeter width // 0.5 * scale_(this->config().line_width);
|
|
for (size_t idx_layer = 0; idx_layer < layers.size(); ++idx_layer) {
|
|
// BBS: detect empty layers (layers with very small regions) and mark them as problematic, then these layers will copy the nearest good layer
|
|
auto layer = layers[idx_layer];
|
|
ExPolygons lslices;
|
|
for (size_t region_id = 0; region_id < layer->m_regions.size(); ++region_id) {
|
|
LayerRegion* layerm = layer->m_regions[region_id];
|
|
for (auto& surface : layerm->slices.surfaces) {
|
|
auto expoly = offset_ex(surface.expolygon, -thresh);
|
|
lslices.insert(lslices.begin(), expoly.begin(), expoly.end());
|
|
}
|
|
}
|
|
if (lslices.empty()) {
|
|
layer->slicing_errors = true;
|
|
}
|
|
|
|
if (layers[idx_layer]->slicing_errors) {
|
|
buggy_layers.push_back(idx_layer);
|
|
}
|
|
else
|
|
break; // only detect empty layers near bed
|
|
}
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - begin";
|
|
std::atomic<bool> is_replaced = false;
|
|
tbb::parallel_for(
|
|
tbb::blocked_range<size_t>(0, buggy_layers.size()),
|
|
[&layers, &throw_if_canceled, &buggy_layers, &is_replaced](const tbb::blocked_range<size_t>& range) {
|
|
for (size_t buggy_layer_idx = range.begin(); buggy_layer_idx < range.end(); ++ buggy_layer_idx) {
|
|
throw_if_canceled();
|
|
size_t idx_layer = buggy_layers[buggy_layer_idx];
|
|
// BBS: only replace empty layers lower than 1mm
|
|
const coordf_t thresh_empty_layer_height = 1;
|
|
Layer* layer = layers[idx_layer];
|
|
if (layer->print_z>= thresh_empty_layer_height)
|
|
continue;
|
|
assert(layer->slicing_errors);
|
|
// Try to repair the layer surfaces by merging all contours and all holes from neighbor layers.
|
|
// BOOST_LOG_TRIVIAL(trace) << "Attempting to repair layer" << idx_layer;
|
|
for (size_t region_id = 0; region_id < layer->region_count(); ++ region_id) {
|
|
LayerRegion *layerm = layer->get_region(region_id);
|
|
// Find the first valid layer below / above the current layer.
|
|
const Surfaces *upper_surfaces = nullptr;
|
|
const Surfaces *lower_surfaces = nullptr;
|
|
//BBS: only repair empty layers lowers than 1mm
|
|
for (size_t j = idx_layer + 1; j < layers.size(); ++j) {
|
|
if (!layers[j]->slicing_errors) {
|
|
upper_surfaces = &layers[j]->regions()[region_id]->slices.surfaces;
|
|
break;
|
|
}
|
|
if (layers[j]->print_z >= thresh_empty_layer_height) break;
|
|
}
|
|
for (int j = int(idx_layer) - 1; j >= 0; --j) {
|
|
if (layers[j]->print_z >= thresh_empty_layer_height) continue;
|
|
if (!layers[j]->slicing_errors) {
|
|
lower_surfaces = &layers[j]->regions()[region_id]->slices.surfaces;
|
|
break;
|
|
}
|
|
}
|
|
// Collect outer contours and holes from the valid layers above & below.
|
|
ExPolygons expolys;
|
|
expolys.reserve(
|
|
((upper_surfaces == nullptr) ? 0 : upper_surfaces->size()) +
|
|
((lower_surfaces == nullptr) ? 0 : lower_surfaces->size()));
|
|
if (upper_surfaces)
|
|
for (const auto &surface : *upper_surfaces) {
|
|
expolys.emplace_back(surface.expolygon);
|
|
}
|
|
if (lower_surfaces)
|
|
for (const auto &surface : *lower_surfaces) {
|
|
expolys.emplace_back(surface.expolygon);
|
|
}
|
|
if (!expolys.empty()) {
|
|
//BBS
|
|
is_replaced = true;
|
|
layerm->slices.set(union_ex(expolys), stInternal);
|
|
}
|
|
}
|
|
// Update layer slices after repairing the single regions.
|
|
layer->make_slices();
|
|
}
|
|
});
|
|
throw_if_canceled();
|
|
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - end";
|
|
|
|
if(is_replaced)
|
|
error_msg = L("Empty layers around bottom are replaced by nearest normal layers.");
|
|
|
|
// remove empty layers from bottom
|
|
while (! layers.empty() && (layers.front()->lslices.empty() || layers.front()->empty())) {
|
|
delete layers.front();
|
|
layers.erase(layers.begin());
|
|
layers.front()->lower_layer = nullptr;
|
|
for (size_t i = 0; i < layers.size(); ++ i)
|
|
layers[i]->set_id(layers[i]->id() - 1);
|
|
}
|
|
|
|
//BBS
|
|
if(error_msg.empty() && !buggy_layers.empty())
|
|
error_msg = L("The model has too many empty layers.");
|
|
|
|
// BBS: first layer slices are sorted by volume group, if the first layer is empty and replaced by the 2nd layer
|
|
// the later will be stored in "object->firstLayerObjGroupsMod()"
|
|
if (!buggy_layers.empty() && buggy_layers.front() == 0 && layers.size() > 1)
|
|
firstLayerReplacedBy = 1;
|
|
|
|
return error_msg;
|
|
}
|
|
*/
|
|
|
|
void groupingVolumesForBrim(PrintObject* object, LayerPtrs& layers, int firstLayerReplacedBy)
|
|
{
|
|
const auto scaled_resolution = scaled<double>(object->print()->config().resolution.value);
|
|
auto partsObjSliceByVolume = findPartVolumes(object->firstLayerObjSliceMod(), object->model_object()->volumes);
|
|
groupingVolumes(partsObjSliceByVolume, object->firstLayerObjGroupsMod(), scaled_resolution, firstLayerReplacedBy);
|
|
applyNegtiveVolumes(object->model_object()->volumes, object->firstLayerObjSliceMod(), object->firstLayerObjGroupsMod(), scaled_resolution);
|
|
|
|
// BBS: the actual first layer slices stored in layers are re-sorted by volume group and will be used to generate brim
|
|
reGroupingLayerPolygons(object->firstLayerObjGroupsMod(), layers.front()->lslices, scaled_resolution);
|
|
}
|
|
|
|
// Called by make_perimeters()
|
|
// 1) Decides Z positions of the layers,
|
|
// 2) Initializes layers and their regions
|
|
// 3) Slices the object meshes
|
|
// 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes
|
|
// 5) Applies size compensation (offsets the slices in XY plane)
|
|
// 6) Replaces bad slices by the slices reconstructed from the upper/lower layer
|
|
// Resulting expolygons of layer regions are marked as Internal.
|
|
void PrintObject::slice()
|
|
{
|
|
if (! this->set_started(posSlice))
|
|
return;
|
|
//BBS: add flag to reload scene for shell rendering
|
|
m_print->set_status(5, L("Slicing mesh"), PrintBase::SlicingStatus::RELOAD_SCENE);
|
|
std::vector<coordf_t> layer_height_profile;
|
|
this->update_layer_height_profile(*this->model_object(), m_slicing_params, layer_height_profile, this);
|
|
m_print->throw_if_canceled();
|
|
m_typed_slices = false;
|
|
this->clear_layers();
|
|
m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile, m_config.precise_z_height.value));
|
|
if (has_surface_emboss_mixed_volume(*this)) {
|
|
reset_surface_emboss_mixed_debug_file(*this);
|
|
BOOST_LOG_TRIVIAL(warning) << "Surface emboss mixed debug enabled"
|
|
<< " object=" << (this->model_object() ? this->model_object()->name : std::string("<unknown>"))
|
|
<< " debug_file=" << surface_emboss_mixed_debug_file_path(*this);
|
|
}
|
|
this->slice_volumes();
|
|
m_print->throw_if_canceled();
|
|
int firstLayerReplacedBy = 0;
|
|
|
|
#if 0
|
|
// Fix the model.
|
|
//FIXME is this the right place to do? It is done repeateadly at the UI and now here at the backend.
|
|
std::string warning = fix_slicing_errors(this, m_layers, [this](){ m_print->throw_if_canceled(); }, firstLayerReplacedBy);
|
|
m_print->throw_if_canceled();
|
|
//BBS: send warning message to slicing callback
|
|
// This warning is inaccurate, because the empty layers may have been replaced, or the model has supports.
|
|
//if (!warning.empty()) {
|
|
// BOOST_LOG_TRIVIAL(info) << warning;
|
|
// this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning, PrintStateBase::SlicingReplaceInitEmptyLayers);
|
|
//}
|
|
#endif
|
|
|
|
// Detect and process holes that should be converted to polyholes
|
|
this->_transform_hole_to_polyholes();
|
|
|
|
// BBS: the actual first layer slices stored in layers are re-sorted by volume group and will be used to generate brim
|
|
groupingVolumesForBrim(this, m_layers, firstLayerReplacedBy);
|
|
|
|
// Update bounding boxes, back up raw slices of complex models.
|
|
tbb::parallel_for(
|
|
tbb::blocked_range<size_t>(0, m_layers.size()),
|
|
[this](const tbb::blocked_range<size_t>& range) {
|
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
|
m_print->throw_if_canceled();
|
|
Layer &layer = *m_layers[layer_idx];
|
|
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));
|
|
layer.backup_untyped_slices();
|
|
}
|
|
});
|
|
if (m_layers.empty())
|
|
throw Slic3r::SlicingError(L("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"));
|
|
|
|
// BBS
|
|
this->set_done(posSlice);
|
|
}
|
|
|
|
static bool bool_from_full_config(const DynamicPrintConfig &full_cfg, const char *key, bool fallback)
|
|
{
|
|
if (!full_cfg.has(key))
|
|
return fallback;
|
|
if (const ConfigOptionBool *opt = full_cfg.option<ConfigOptionBool>(key))
|
|
return opt->value;
|
|
if (const ConfigOptionInt *opt = full_cfg.option<ConfigOptionInt>(key))
|
|
return opt->value != 0;
|
|
return fallback;
|
|
}
|
|
|
|
static coordf_t float_from_full_config(const DynamicPrintConfig &full_cfg, const char *key, coordf_t fallback)
|
|
{
|
|
if (!full_cfg.has(key))
|
|
return fallback;
|
|
if (const ConfigOptionFloat *opt = full_cfg.option<ConfigOptionFloat>(key))
|
|
return coordf_t(opt->value);
|
|
return coordf_t(full_cfg.opt_float(key));
|
|
}
|
|
|
|
// Forward declarations — defined after the Task 30/31 helpers further below.
|
|
template<typename ThrowOnCancel>
|
|
static void build_local_z_plan(PrintObject &print_object, const std::vector<std::vector<ExPolygons>> &segmentation, ThrowOnCancel throw_on_cancel);
|
|
// Task 30 helpers (also defined below).
|
|
static std::vector<std::vector<ExPolygons>> whole_object_local_z_segmentation_by_mixed_wall(const PrintObject &print_object);
|
|
static std::vector<std::vector<ExPolygons>> local_z_planner_segmentation_with_whole_object_mixed_wall(
|
|
const PrintObject &print_object, const std::vector<std::vector<ExPolygons>> &paint_segmentation);
|
|
// Surface-emboss override is defined alongside the surface_emboss_mixed debug subsystem far
|
|
// below; declared here so slice_volumes() can call it.
|
|
template<typename ThrowOnCancel>
|
|
static bool apply_surface_emboss_mixed_region_override(PrintObject &print_object, ThrowOnCancel throw_on_cancel);
|
|
|
|
// Forward declarations for FS mixed-filament surface helpers, defined further below alongside
|
|
// segmentation_channel_filament_id / collect_layer_region_slices.
|
|
static inline unsigned int segmentation_channel_filament_id(size_t channel_idx);
|
|
static coordf_t clamped_mixed_component_surface_offset(const MixedFilamentManager &mixed_mgr,
|
|
const PrintConfig &print_cfg,
|
|
unsigned int filament_id,
|
|
size_t num_physical,
|
|
int layer_index,
|
|
float layer_print_z,
|
|
float layer_height,
|
|
bool force_height_weighted = false);
|
|
|
|
template<typename ThrowOnCancel>
|
|
static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<std::vector<ExPolygons>> segmentation, ThrowOnCancel throw_on_cancel)
|
|
{
|
|
assert(segmentation.size() == print_object.layer_count());
|
|
const PrintConfig &print_cfg = print_object.print()->config();
|
|
const DynamicPrintConfig &full_cfg = print_object.print()->full_print_config();
|
|
const size_t num_physical = print_cfg.filament_diameter.size();
|
|
const coordf_t preferred_a = float_from_full_config(full_cfg, "mixed_color_layer_height_a",
|
|
coordf_t(print_cfg.mixed_color_layer_height_a.value));
|
|
const coordf_t preferred_b = float_from_full_config(full_cfg, "mixed_color_layer_height_b",
|
|
coordf_t(print_cfg.mixed_color_layer_height_b.value));
|
|
const coordf_t base_height = std::max<coordf_t>(0.01f, coordf_t(print_object.config().layer_height.value));
|
|
const bool collapse_mixed_regions =
|
|
bool_from_full_config(full_cfg, "mixed_filament_region_collapse", print_cfg.mixed_filament_region_collapse.value);
|
|
const bool bias_mode_enabled =
|
|
bool_from_full_config(full_cfg, "mixed_filament_component_bias_enabled", print_cfg.mixed_filament_component_bias_enabled.value);
|
|
const MixedFilamentManager &mixed_mgr = print_object.print()->mixed_filament_manager();
|
|
|
|
tbb::parallel_for(
|
|
tbb::blocked_range<size_t>(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))),
|
|
[&print_object, &segmentation, &mixed_mgr, num_physical, preferred_a, preferred_b, base_height, collapse_mixed_regions, bias_mode_enabled, throw_on_cancel](const tbb::blocked_range<size_t> &range) {
|
|
const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
|
|
double z = print_object.get_layer(int(range.begin()))->slice_z;
|
|
auto it_layer_range = layer_range_first(layer_ranges, z);
|
|
// MM segmentation channel 0 is the underlying / default color of the parent
|
|
// region. Remaining channels correspond to filament IDs (1-based), which
|
|
// now include enabled mixed / virtual filaments.
|
|
const size_t num_channels = segmentation.empty() ? 0 : segmentation.front().size();
|
|
const size_t num_extruders = num_channels > 0 ? num_channels - 1 : 0;
|
|
|
|
struct ByExtruder {
|
|
ExPolygons expolygons;
|
|
BoundingBox bbox;
|
|
};
|
|
|
|
auto intersect_surfaces_preserve_types = [](const SurfaceCollection &src, const ExPolygons &mask) {
|
|
SurfaceCollection out;
|
|
if (src.empty() || mask.empty())
|
|
return out;
|
|
|
|
std::array<SurfacesPtr, size_t(stCount)> by_surface;
|
|
for (const Surface &surface : src.surfaces)
|
|
by_surface[size_t(surface.surface_type)].emplace_back(&surface);
|
|
|
|
for (size_t surface_type = 0; surface_type < size_t(stCount); ++surface_type) {
|
|
const SurfacesPtr &typed_surfaces = by_surface[surface_type];
|
|
if (typed_surfaces.empty())
|
|
continue;
|
|
ExPolygons clipped = intersection_ex(typed_surfaces, mask);
|
|
if (!clipped.empty())
|
|
out.append(std::move(clipped), SurfaceType(surface_type));
|
|
}
|
|
return out;
|
|
};
|
|
|
|
struct ByRegion {
|
|
SurfaceCollection surfaces;
|
|
bool needs_merge { false };
|
|
};
|
|
|
|
auto normalize_region_surfaces = [](SurfaceCollection &src) {
|
|
if (src.surfaces.empty())
|
|
return;
|
|
|
|
std::array<ExPolygons, size_t(stCount)> by_surface;
|
|
for (Surface &surface : src.surfaces)
|
|
by_surface[size_t(surface.surface_type)].emplace_back(std::move(surface.expolygon));
|
|
|
|
src.surfaces.clear();
|
|
for (size_t surface_type = 0; surface_type < size_t(stCount); ++surface_type) {
|
|
ExPolygons &typed = by_surface[surface_type];
|
|
if (typed.empty())
|
|
continue;
|
|
if (typed.size() > 1)
|
|
typed = closing_ex(std::move(typed), scaled<float>(10. * EPSILON));
|
|
src.append(std::move(typed), SurfaceType(surface_type));
|
|
}
|
|
};
|
|
|
|
std::vector<ByExtruder> by_extruder;
|
|
std::vector<ByRegion> by_region;
|
|
for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) {
|
|
throw_on_cancel();
|
|
Layer &layer = *print_object.get_layer(int(layer_id));
|
|
it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z);
|
|
const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range;
|
|
// Gather per extruder expolygons.
|
|
assert(segmentation[layer_id].size() == num_channels);
|
|
by_extruder.assign(num_extruders, ByExtruder());
|
|
by_region.assign(layer.region_count(), ByRegion());
|
|
bool layer_split = false;
|
|
size_t missing_target_regions = 0;
|
|
std::vector<int> missing_target_extruders;
|
|
ExPolygons default_segmentation = num_channels > 0 ? std::move(segmentation[layer_id][0]) : ExPolygons();
|
|
BoundingBox default_bbox;
|
|
bool layer_has_component_bias = false;
|
|
if (!default_segmentation.empty()) {
|
|
default_bbox = get_extents(default_segmentation);
|
|
layer_split = true;
|
|
}
|
|
for (size_t channel_idx = 1; channel_idx < num_channels; ++ channel_idx) {
|
|
const unsigned int channel_id = unsigned(channel_idx);
|
|
const unsigned int effective_filament_id = collapse_mixed_regions ?
|
|
mixed_mgr.effective_painted_region_filament_id(channel_id,
|
|
num_physical,
|
|
int(layer_id),
|
|
float(layer.print_z),
|
|
float(layer.height),
|
|
float(preferred_a),
|
|
float(preferred_b),
|
|
float(base_height)) :
|
|
channel_id;
|
|
const size_t effective_idx =
|
|
effective_filament_id >= 1 && effective_filament_id <= num_extruders ? size_t(effective_filament_id - 1) : size_t(channel_idx - 1);
|
|
ByExtruder ®ion = by_extruder[effective_idx];
|
|
append(region.expolygons, std::move(segmentation[layer_id][channel_idx]));
|
|
if (! region.expolygons.empty()) {
|
|
region.bbox = get_extents(region.expolygons);
|
|
layer_split = true;
|
|
}
|
|
|
|
if (!region.expolygons.empty() &&
|
|
bias_mode_enabled &&
|
|
mixed_mgr.is_mixed(channel_id, num_physical) &&
|
|
std::abs(mixed_mgr.component_surface_offset(channel_id,
|
|
num_physical,
|
|
int(layer_id),
|
|
float(layer.print_z),
|
|
float(layer.height))) > EPSILON)
|
|
layer_has_component_bias = true;
|
|
}
|
|
|
|
if (!layer_split)
|
|
continue;
|
|
|
|
ExPolygons layer_geometry_mask;
|
|
BoundingBox layer_geometry_bbox;
|
|
if (layer_has_component_bias) {
|
|
if (!default_segmentation.empty())
|
|
append(layer_geometry_mask, default_segmentation);
|
|
for (const ByExtruder &segmented : by_extruder) {
|
|
if (!segmented.expolygons.empty())
|
|
append(layer_geometry_mask, segmented.expolygons);
|
|
}
|
|
if (!layer_geometry_mask.empty()) {
|
|
if (layer_geometry_mask.size() > 1)
|
|
layer_geometry_mask = closing_ex(union_ex(std::move(layer_geometry_mask)), scaled<float>(5. * EPSILON));
|
|
layer_geometry_bbox = get_extents(layer_geometry_mask);
|
|
}
|
|
}
|
|
|
|
// Split LayerRegions by by_extruder regions.
|
|
// layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID.
|
|
auto it_painted_region_begin = layer_range.painted_regions.cbegin();
|
|
for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) {
|
|
const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx);
|
|
const PrintRegion &parent_print_region = parent_layer_region.region();
|
|
assert(parent_print_region.print_object_region_id() == parent_layer_region_idx);
|
|
if (parent_layer_region.slices.empty())
|
|
continue;
|
|
|
|
auto preserve_parent_region = [&by_region, &parent_layer_region, &parent_print_region]() {
|
|
if (!parent_layer_region.slices.empty())
|
|
by_region[parent_print_region.print_object_region_id()].surfaces = parent_layer_region.slices;
|
|
};
|
|
|
|
if (it_painted_region_begin == layer_range.painted_regions.cend()) {
|
|
preserve_parent_region();
|
|
continue;
|
|
}
|
|
|
|
// Find the first PaintedRegion, which overrides the parent PrintRegion.
|
|
auto it_first_painted_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [&layer_range, &parent_print_region](const auto &painted_region) {
|
|
return layer_range.volume_regions[painted_region.parent].region->print_object_region_id() == parent_print_region.print_object_region_id();
|
|
});
|
|
|
|
if (it_first_painted_region == layer_range.painted_regions.cend()) {
|
|
preserve_parent_region();
|
|
continue; // This LayerRegion isn't overrides by any PaintedRegion.
|
|
}
|
|
|
|
assert(&parent_print_region == layer_range.volume_regions[it_first_painted_region->parent].region);
|
|
|
|
// Update the beginning PaintedRegion iterator for the next iteration.
|
|
it_painted_region_begin = it_first_painted_region;
|
|
|
|
const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices.surfaces);
|
|
const bool clamp_parent_to_geometry =
|
|
layer_has_component_bias &&
|
|
layer_geometry_bbox.defined &&
|
|
parent_layer_region_bbox.overlap(layer_geometry_bbox);
|
|
ExPolygons clamped_parent_expolygons;
|
|
if (clamp_parent_to_geometry)
|
|
clamped_parent_expolygons = intersection_ex(parent_layer_region.slices.surfaces, layer_geometry_mask);
|
|
|
|
int self_extruder_id = -1; // 1-based extruder ID
|
|
ExPolygons explicit_self_expolygons;
|
|
ExPolygons default_self_expolygons;
|
|
if (const int cfg_wall = parent_print_region.config().wall_filament.value;
|
|
cfg_wall >= 1 && cfg_wall <= int(by_extruder.size()))
|
|
self_extruder_id = cfg_wall;
|
|
if (default_bbox.defined && parent_layer_region_bbox.overlap(default_bbox))
|
|
default_self_expolygons = intersection_ex(parent_layer_region.slices.surfaces, default_segmentation);
|
|
std::vector<bool> assigned_extruder(by_extruder.size(), false);
|
|
std::vector<int> alias_to_self_extruders;
|
|
for (int extruder_id = 1; extruder_id <= int(by_extruder.size()); ++extruder_id) {
|
|
const ByExtruder &segmented = by_extruder[extruder_id - 1];
|
|
if (!segmented.bbox.defined || !parent_layer_region_bbox.overlap(segmented.bbox))
|
|
continue;
|
|
|
|
// Find the matching target region for this parent and extruder ID.
|
|
auto it_target_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [&layer_range, &parent_print_region, extruder_id](const auto &painted_region) {
|
|
return layer_range.volume_regions[painted_region.parent].region == &parent_print_region &&
|
|
int(painted_region.extruder_id) == extruder_id;
|
|
});
|
|
|
|
if (it_target_region == layer_range.painted_regions.cend()) {
|
|
++missing_target_regions;
|
|
missing_target_extruders.emplace_back(extruder_id);
|
|
continue;
|
|
}
|
|
|
|
// Update the beginning PaintedRegion iterator for the next iteration.
|
|
it_painted_region_begin = it_target_region;
|
|
|
|
// FIXME: Don't trim by self, it is not reliable.
|
|
if (it_target_region->region == &parent_print_region) {
|
|
if (self_extruder_id < 0)
|
|
self_extruder_id = extruder_id;
|
|
if (extruder_id != self_extruder_id)
|
|
alias_to_self_extruders.emplace_back(extruder_id);
|
|
ExPolygons self_segmented = intersection_ex(parent_layer_region.slices.surfaces, segmented.expolygons);
|
|
if (!self_segmented.empty()) {
|
|
if (explicit_self_expolygons.empty())
|
|
explicit_self_expolygons = std::move(self_segmented);
|
|
else
|
|
append(explicit_self_expolygons, std::move(self_segmented));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
assigned_extruder[size_t(extruder_id - 1)] = true;
|
|
|
|
// Steal from this region.
|
|
int target_region_id = it_target_region->region->print_object_region_id();
|
|
ExPolygons stolen = intersection_ex(parent_layer_region.slices.surfaces, segmented.expolygons);
|
|
if (!stolen.empty()) {
|
|
ByRegion &dst = by_region[target_region_id];
|
|
SurfaceCollection stolen_surfaces = intersect_surfaces_preserve_types(parent_layer_region.slices, stolen);
|
|
if (stolen_surfaces.empty())
|
|
continue;
|
|
if (dst.surfaces.empty()) {
|
|
dst.surfaces = std::move(stolen_surfaces);
|
|
} else {
|
|
dst.surfaces.append(std::move(stolen_surfaces));
|
|
dst.needs_merge = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Trim slices of this LayerRegion with all the MM regions.
|
|
// Mixed bias can intentionally shrink a painted layer's true silhouette.
|
|
// Clamp the parent region to the post-bias segmentation union so the
|
|
// vacated area stays empty instead of falling back to the parent tool.
|
|
Polygons mine = clamp_parent_to_geometry ? to_polygons(clamped_parent_expolygons) :
|
|
to_polygons(parent_layer_region.slices.surfaces);
|
|
for (size_t extruder_idx = 0; extruder_idx < by_extruder.size(); ++extruder_idx) {
|
|
const ByExtruder &segmented = by_extruder[extruder_idx];
|
|
if (!assigned_extruder[extruder_idx])
|
|
continue;
|
|
if (int(extruder_idx + 1) != self_extruder_id && segmented.bbox.defined && parent_layer_region_bbox.overlap(segmented.bbox)) {
|
|
mine = diff(mine, segmented.expolygons);
|
|
if (mine.empty())
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!explicit_self_expolygons.empty())
|
|
explicit_self_expolygons = union_ex(explicit_self_expolygons);
|
|
if (!default_self_expolygons.empty())
|
|
default_self_expolygons = union_ex(default_self_expolygons);
|
|
|
|
ExPolygons preserved_self_expolygons;
|
|
if (!explicit_self_expolygons.empty())
|
|
append(preserved_self_expolygons, explicit_self_expolygons);
|
|
if (!default_self_expolygons.empty())
|
|
append(preserved_self_expolygons, default_self_expolygons);
|
|
if (!preserved_self_expolygons.empty())
|
|
preserved_self_expolygons = union_ex(preserved_self_expolygons);
|
|
|
|
ExPolygons mine_expolygons;
|
|
if (!mine.empty()) {
|
|
if (!preserved_self_expolygons.empty())
|
|
mine = diff(mine, preserved_self_expolygons);
|
|
|
|
// Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region().
|
|
// ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region()
|
|
// (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from
|
|
// layerm.region() could produce a huge number of small unprintable regions for the model's base extruder.
|
|
// This could, on some models, produce bulges with the model's base color (#7109).
|
|
if (!mine.empty())
|
|
mine = opening(union_ex(mine), scaled<float>(5. * EPSILON), scaled<float>(5. * EPSILON));
|
|
|
|
if (!mine.empty())
|
|
mine_expolygons = union_ex(mine);
|
|
}
|
|
|
|
if (!preserved_self_expolygons.empty()) {
|
|
append(mine_expolygons, preserved_self_expolygons);
|
|
mine_expolygons = union_ex(mine_expolygons);
|
|
}
|
|
|
|
if (!mine_expolygons.empty()) {
|
|
SurfaceCollection mine_surfaces = intersect_surfaces_preserve_types(parent_layer_region.slices, mine_expolygons);
|
|
if (!mine_surfaces.empty()) {
|
|
ByRegion &dst = by_region[parent_print_region.print_object_region_id()];
|
|
if (dst.surfaces.empty()) {
|
|
dst.surfaces = std::move(mine_surfaces);
|
|
} else {
|
|
dst.surfaces.append(std::move(mine_surfaces));
|
|
dst.needs_merge = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!alias_to_self_extruders.empty()) {
|
|
std::sort(alias_to_self_extruders.begin(), alias_to_self_extruders.end());
|
|
alias_to_self_extruders.erase(std::unique(alias_to_self_extruders.begin(), alias_to_self_extruders.end()), alias_to_self_extruders.end());
|
|
std::string alias_ids;
|
|
for (size_t i = 0; i < alias_to_self_extruders.size(); ++i) {
|
|
if (i > 0)
|
|
alias_ids += ",";
|
|
alias_ids += std::to_string(alias_to_self_extruders[i]);
|
|
}
|
|
BOOST_LOG_TRIVIAL(warning) << "MM segmentation alias-to-parent channels ignored"
|
|
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
|
|
<< " layer_id=" << layer_id
|
|
<< " parent_region_id=" << parent_print_region.print_object_region_id()
|
|
<< " self_extruder_id=" << self_extruder_id
|
|
<< " alias_extruders=[" << alias_ids << "]";
|
|
}
|
|
}
|
|
|
|
if (missing_target_regions > 0) {
|
|
std::sort(missing_target_extruders.begin(), missing_target_extruders.end());
|
|
missing_target_extruders.erase(std::unique(missing_target_extruders.begin(), missing_target_extruders.end()), missing_target_extruders.end());
|
|
std::string missing_ids;
|
|
for (size_t i = 0; i < missing_target_extruders.size(); ++i) {
|
|
if (i > 0)
|
|
missing_ids += ",";
|
|
missing_ids += std::to_string(missing_target_extruders[i]);
|
|
}
|
|
BOOST_LOG_TRIVIAL(warning) << "MM segmentation missing painted target regions"
|
|
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
|
|
<< " layer_id=" << layer_id
|
|
<< " missing_targets=" << missing_target_regions
|
|
<< " missing_extruders=[" << missing_ids << "]"
|
|
<< " segmentation_channels=" << num_extruders
|
|
<< " painted_regions=" << layer_range.painted_regions.size();
|
|
}
|
|
|
|
// Re-create Surfaces of LayerRegions.
|
|
for (int region_id = 0; region_id < layer.region_count(); ++region_id) {
|
|
ByRegion &src = by_region[region_id];
|
|
if (src.needs_merge) {
|
|
// Multiple regions were merged into one.
|
|
normalize_region_surfaces(src.surfaces);
|
|
}
|
|
|
|
layer.get_region(region_id)->slices.set(std::move(src.surfaces));
|
|
}
|
|
|
|
dump_surface_emboss_mixed_layer_state("post-mm-segmentation",
|
|
print_object,
|
|
layer_id,
|
|
layer,
|
|
layer_range,
|
|
&segmentation[layer_id]);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Adjust virtual mixed-state masks by `mixed_filament_surface_indentation` mm. Positive values
|
|
// shrink the mixed footprint (revealing the parent masks beneath); negative values expand the
|
|
// mixed footprint outward, clipping against already-occupied physical-channel masks.
|
|
static bool apply_mixed_surface_indentation(PrintObject &print_object, std::vector<std::vector<ExPolygons>> &segmentation)
|
|
{
|
|
const Print *print = print_object.print();
|
|
if (print == nullptr || segmentation.empty())
|
|
return false;
|
|
|
|
const PrintConfig &print_cfg = print->config();
|
|
const DynamicPrintConfig &full_cfg = print->full_print_config();
|
|
coordf_t indentation_mm = float_from_full_config(full_cfg, "mixed_filament_surface_indentation",
|
|
coordf_t(print_cfg.mixed_filament_surface_indentation.value));
|
|
indentation_mm = std::clamp(indentation_mm, coordf_t(-2.f), coordf_t(2.f));
|
|
if (std::abs(indentation_mm) <= EPSILON)
|
|
return false;
|
|
|
|
const size_t num_physical = print_cfg.filament_colour.size();
|
|
const size_t num_channels = segmentation.front().size();
|
|
if (num_channels <= num_physical)
|
|
return false;
|
|
|
|
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
|
const bool expand_outward = indentation_mm < 0.f;
|
|
const float delta_scaled = float(scale_(std::abs(double(indentation_mm))));
|
|
if (delta_scaled <= float(EPSILON))
|
|
return false;
|
|
|
|
size_t changed_layers = 0;
|
|
size_t changed_states = 0;
|
|
size_t emptied_states = 0;
|
|
size_t overlap_clipped_states = 0;
|
|
size_t outside_trimmed_states = 0;
|
|
|
|
for (size_t layer_id = 0; layer_id < segmentation.size(); ++layer_id) {
|
|
if (segmentation[layer_id].size() != num_channels)
|
|
continue;
|
|
|
|
bool layer_changed = false;
|
|
ExPolygons outside_trim_band;
|
|
ExPolygons occupied;
|
|
if (expand_outward) {
|
|
for (size_t channel_idx = 0; channel_idx < num_channels; ++channel_idx) {
|
|
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
|
|
if (state_masks.empty())
|
|
continue;
|
|
|
|
const unsigned int state_id = unsigned(segmentation_channel_filament_id(channel_idx));
|
|
if (!mixed_mgr.is_mixed(state_id, num_physical))
|
|
append(occupied, state_masks);
|
|
}
|
|
if (occupied.size() > 1)
|
|
occupied = union_ex(occupied);
|
|
} else {
|
|
ExPolygons layer_masks;
|
|
for (size_t channel_idx = 0; channel_idx < num_channels; ++channel_idx) {
|
|
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
|
|
if (!state_masks.empty())
|
|
append(layer_masks, state_masks);
|
|
}
|
|
if (!layer_masks.empty()) {
|
|
if (layer_masks.size() > 1)
|
|
layer_masks = union_ex(layer_masks);
|
|
|
|
ExPolygons layer_inner = offset_ex(layer_masks, -delta_scaled);
|
|
if (!layer_inner.empty() && layer_inner.size() > 1)
|
|
layer_inner = union_ex(layer_inner);
|
|
|
|
outside_trim_band = layer_inner.empty() ? layer_masks : diff_ex(layer_masks, layer_inner, ApplySafetyOffset::Yes);
|
|
if (!outside_trim_band.empty() && outside_trim_band.size() > 1)
|
|
outside_trim_band = union_ex(outside_trim_band);
|
|
}
|
|
}
|
|
|
|
for (size_t channel_idx = num_physical; channel_idx < num_channels; ++channel_idx) {
|
|
ExPolygons &state_masks = segmentation[layer_id][channel_idx];
|
|
if (state_masks.empty())
|
|
continue;
|
|
|
|
const unsigned int state_id = unsigned(segmentation_channel_filament_id(channel_idx));
|
|
if (!mixed_mgr.is_mixed(state_id, num_physical))
|
|
continue;
|
|
|
|
ExPolygons adjusted;
|
|
if (expand_outward) {
|
|
adjusted = offset_ex(state_masks, delta_scaled);
|
|
if (!adjusted.empty() && adjusted.size() > 1)
|
|
adjusted = union_ex(adjusted);
|
|
|
|
if (!adjusted.empty() && !occupied.empty()) {
|
|
ExPolygons clipped = diff_ex(adjusted, occupied, ApplySafetyOffset::Yes);
|
|
if (std::abs(area(clipped)) + EPSILON < std::abs(area(adjusted)))
|
|
++overlap_clipped_states;
|
|
adjusted = std::move(clipped);
|
|
if (!adjusted.empty() && adjusted.size() > 1)
|
|
adjusted = union_ex(adjusted);
|
|
}
|
|
} else {
|
|
adjusted = outside_trim_band.empty() ? state_masks : diff_ex(state_masks, outside_trim_band, ApplySafetyOffset::Yes);
|
|
if (std::abs(area(adjusted)) + EPSILON < std::abs(area(state_masks)))
|
|
++outside_trimmed_states;
|
|
if (!adjusted.empty() && adjusted.size() > 1)
|
|
adjusted = union_ex(adjusted);
|
|
}
|
|
|
|
state_masks = std::move(adjusted);
|
|
if (state_masks.empty())
|
|
++emptied_states;
|
|
++changed_states;
|
|
layer_changed = true;
|
|
|
|
if (expand_outward && !state_masks.empty()) {
|
|
append(occupied, state_masks);
|
|
if (occupied.size() > 1)
|
|
occupied = union_ex(occupied);
|
|
}
|
|
}
|
|
|
|
if (layer_changed)
|
|
++changed_layers;
|
|
}
|
|
|
|
if (changed_states == 0)
|
|
return false;
|
|
|
|
BOOST_LOG_TRIVIAL(warning) << "Mixed surface indentation applied"
|
|
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
|
|
<< " indentation_mm=" << indentation_mm
|
|
<< " direction=" << (expand_outward ? "outward" : "inward")
|
|
<< " changed_layers=" << changed_layers
|
|
<< " changed_states=" << changed_states
|
|
<< " emptied_states=" << emptied_states
|
|
<< " overlap_clipped_states=" << overlap_clipped_states
|
|
<< " outside_trimmed_states=" << outside_trimmed_states;
|
|
return true;
|
|
}
|
|
|
|
// Apply per-component surface offset bias to mixed-channel segmentation masks. Positive offsets
|
|
// contract the channel (revealing siblings); negative offsets expand the channel and steal area
|
|
// from siblings. Disabled when dithering local-Z mode owns the layer instead.
|
|
static bool apply_mixed_component_surface_offsets(PrintObject &print_object, std::vector<std::vector<ExPolygons>> &segmentation)
|
|
{
|
|
const Print *print = print_object.print();
|
|
if (print == nullptr || segmentation.empty())
|
|
return false;
|
|
|
|
const PrintConfig &print_cfg = print->config();
|
|
const DynamicPrintConfig &full_cfg = print->full_print_config();
|
|
if (bool_from_full_config(full_cfg, "dithering_local_z_mode", print_cfg.dithering_local_z_mode.value))
|
|
return false;
|
|
if (!bool_from_full_config(full_cfg, "mixed_filament_component_bias_enabled", print_cfg.mixed_filament_component_bias_enabled.value))
|
|
return false;
|
|
|
|
const size_t num_physical = print_cfg.filament_colour.size();
|
|
const size_t num_channels = segmentation.front().size();
|
|
if (num_channels <= num_physical + 1)
|
|
return false;
|
|
|
|
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
|
bool has_component_offsets = false;
|
|
for (const MixedFilament &mf : mixed_mgr.mixed_filaments()) {
|
|
if (!mf.enabled || mf.deleted)
|
|
continue;
|
|
if (std::abs(mf.component_a_surface_offset) > EPSILON || std::abs(mf.component_b_surface_offset) > EPSILON) {
|
|
has_component_offsets = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!has_component_offsets)
|
|
return false;
|
|
|
|
size_t changed_layers = 0;
|
|
size_t changed_states = 0;
|
|
size_t emptied_states = 0;
|
|
size_t expanded_states = 0;
|
|
size_t contracted_states = 0;
|
|
|
|
for (size_t layer_id = 0; layer_id < segmentation.size(); ++layer_id) {
|
|
if (segmentation[layer_id].size() != num_channels)
|
|
continue;
|
|
|
|
const Layer *layer = layer_id < size_t(print_object.layer_count()) ? print_object.get_layer(int(layer_id)) : nullptr;
|
|
const float layer_print_z = layer ? float(layer->print_z) : 0.f;
|
|
const float layer_height = layer ? float(layer->height) : 0.f;
|
|
bool layer_changed = false;
|
|
|
|
for (size_t channel_idx = 1; channel_idx < num_channels; ++channel_idx) {
|
|
ExPolygons &state_masks = segmentation[layer_id][channel_idx];
|
|
if (state_masks.empty())
|
|
continue;
|
|
|
|
const unsigned int state_id = segmentation_channel_filament_id(channel_idx);
|
|
if (!mixed_mgr.is_mixed(state_id, num_physical))
|
|
continue;
|
|
|
|
const coordf_t offset_mm = clamped_mixed_component_surface_offset(mixed_mgr,
|
|
print_cfg,
|
|
state_id,
|
|
num_physical,
|
|
int(layer_id),
|
|
layer_print_z,
|
|
layer_height);
|
|
if (std::abs(offset_mm) <= EPSILON)
|
|
continue;
|
|
|
|
const float delta_scaled = float(scale_(std::abs(double(offset_mm))));
|
|
if (delta_scaled <= float(EPSILON))
|
|
continue;
|
|
|
|
ExPolygons adjusted = offset_mm > 0 ? offset_ex(state_masks, -delta_scaled) : offset_ex(state_masks, delta_scaled);
|
|
if (!adjusted.empty() && adjusted.size() > 1)
|
|
adjusted = union_ex(adjusted);
|
|
|
|
if (offset_mm < 0 && !adjusted.empty()) {
|
|
ExPolygons occupied_other;
|
|
for (size_t other_idx = 0; other_idx < num_channels; ++other_idx) {
|
|
if (other_idx == channel_idx)
|
|
continue;
|
|
if (!segmentation[layer_id][other_idx].empty())
|
|
append(occupied_other, segmentation[layer_id][other_idx]);
|
|
}
|
|
if (occupied_other.size() > 1)
|
|
occupied_other = union_ex(occupied_other);
|
|
if (!occupied_other.empty()) {
|
|
ExPolygons clipped = diff_ex(adjusted, occupied_other, ApplySafetyOffset::Yes);
|
|
adjusted = std::move(clipped);
|
|
if (!adjusted.empty() && adjusted.size() > 1)
|
|
adjusted = union_ex(adjusted);
|
|
}
|
|
}
|
|
|
|
state_masks = std::move(adjusted);
|
|
if (state_masks.empty())
|
|
++emptied_states;
|
|
if (offset_mm < 0)
|
|
++expanded_states;
|
|
else
|
|
++contracted_states;
|
|
++changed_states;
|
|
layer_changed = true;
|
|
}
|
|
|
|
if (layer_changed)
|
|
++changed_layers;
|
|
}
|
|
|
|
if (changed_states == 0)
|
|
return false;
|
|
|
|
BOOST_LOG_TRIVIAL(warning) << "Mixed component surface offsets applied"
|
|
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
|
|
<< " changed_layers=" << changed_layers
|
|
<< " changed_states=" << changed_states
|
|
<< " contracted_states=" << contracted_states
|
|
<< " expanded_states=" << expanded_states
|
|
<< " emptied_states=" << emptied_states;
|
|
return true;
|
|
}
|
|
|
|
// Region-level mixed-component surface bias for objects without MM-painted segmentation.
|
|
// Operates directly on LayerRegion::slices after they have been built. Negative offsets
|
|
// expand the mixed region and steal area from non-mixed siblings; positive offsets contract.
|
|
static bool apply_mixed_region_surface_offsets(PrintObject &print_object)
|
|
{
|
|
const Print *print = print_object.print();
|
|
if (print == nullptr || print_object.layer_count() == 0)
|
|
return false;
|
|
|
|
const PrintConfig &print_cfg = print->config();
|
|
const DynamicPrintConfig &full_cfg = print->full_print_config();
|
|
if (bool_from_full_config(full_cfg, "dithering_local_z_mode", print_cfg.dithering_local_z_mode.value))
|
|
return false;
|
|
if (!bool_from_full_config(full_cfg, "mixed_filament_component_bias_enabled", print_cfg.mixed_filament_component_bias_enabled.value))
|
|
return false;
|
|
|
|
const size_t num_physical = print_cfg.filament_diameter.size();
|
|
if (num_physical == 0)
|
|
return false;
|
|
|
|
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
|
bool has_component_offsets = false;
|
|
for (const MixedFilament &mf : mixed_mgr.mixed_filaments()) {
|
|
if (!mf.enabled || mf.deleted)
|
|
continue;
|
|
if (std::abs(mf.component_a_surface_offset) > EPSILON || std::abs(mf.component_b_surface_offset) > EPSILON) {
|
|
has_component_offsets = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!has_component_offsets)
|
|
return false;
|
|
|
|
size_t changed_layers = 0;
|
|
size_t changed_regions = 0;
|
|
size_t contracted_regions = 0;
|
|
size_t expanded_regions = 0;
|
|
size_t stolen_regions = 0;
|
|
|
|
struct PendingRegionOffset {
|
|
int region_id { -1 };
|
|
coordf_t offset_mm { 0.f };
|
|
ExPolygons adjusted;
|
|
};
|
|
|
|
for (size_t layer_id = 0; layer_id < print_object.layer_count(); ++layer_id) {
|
|
Layer &layer = *print_object.get_layer(int(layer_id));
|
|
std::vector<PendingRegionOffset> pending;
|
|
pending.reserve(size_t(layer.region_count()));
|
|
|
|
for (int region_id = 0; region_id < layer.region_count(); ++region_id) {
|
|
LayerRegion *layerm = layer.get_region(region_id);
|
|
if (layerm == nullptr || layerm->slices.empty())
|
|
continue;
|
|
|
|
const unsigned int filament_id = unsigned(std::max(0, layerm->region().config().wall_filament.value));
|
|
if (!mixed_mgr.is_mixed(filament_id, num_physical))
|
|
continue;
|
|
|
|
const coordf_t offset_mm = clamped_mixed_component_surface_offset(mixed_mgr,
|
|
print_cfg,
|
|
filament_id,
|
|
num_physical,
|
|
int(layer_id),
|
|
float(layer.print_z),
|
|
float(layer.height));
|
|
if (std::abs(offset_mm) <= EPSILON)
|
|
continue;
|
|
|
|
const float delta_scaled = float(scale_(std::abs(double(offset_mm))));
|
|
if (delta_scaled <= float(EPSILON))
|
|
continue;
|
|
|
|
const ExPolygons original = to_expolygons(layerm->slices.surfaces);
|
|
ExPolygons adjusted = offset_ex(original, offset_mm > 0 ? -delta_scaled : delta_scaled);
|
|
if (!adjusted.empty() && adjusted.size() > 1)
|
|
adjusted = union_ex(adjusted);
|
|
|
|
pending.push_back({ region_id, offset_mm, std::move(adjusted) });
|
|
}
|
|
|
|
if (pending.empty())
|
|
continue;
|
|
|
|
bool layer_changed = false;
|
|
for (const PendingRegionOffset &entry : pending) {
|
|
LayerRegion *layerm = layer.get_region(entry.region_id);
|
|
if (layerm == nullptr)
|
|
continue;
|
|
|
|
if (entry.offset_mm < 0 && !entry.adjusted.empty()) {
|
|
for (int other_region_id = 0; other_region_id < layer.region_count(); ++other_region_id) {
|
|
if (other_region_id == entry.region_id)
|
|
continue;
|
|
|
|
LayerRegion *other = layer.get_region(other_region_id);
|
|
if (other == nullptr || other->slices.empty())
|
|
continue;
|
|
|
|
ExPolygons stolen = intersection_ex(other->slices.surfaces, entry.adjusted);
|
|
if (stolen.empty())
|
|
continue;
|
|
|
|
Polygons remaining = diff(to_polygons(other->slices.surfaces), entry.adjusted);
|
|
other->slices.set(union_ex(remaining), stInternal);
|
|
++stolen_regions;
|
|
layer_changed = true;
|
|
}
|
|
}
|
|
|
|
layerm->slices.set(entry.adjusted, stInternal);
|
|
++changed_regions;
|
|
if (entry.offset_mm > 0)
|
|
++contracted_regions;
|
|
else
|
|
++expanded_regions;
|
|
layer_changed = true;
|
|
}
|
|
|
|
if (layer_changed)
|
|
++changed_layers;
|
|
}
|
|
|
|
if (changed_regions == 0)
|
|
return false;
|
|
|
|
BOOST_LOG_TRIVIAL(warning) << "Mixed region surface offsets applied"
|
|
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
|
|
<< " changed_layers=" << changed_layers
|
|
<< " changed_regions=" << changed_regions
|
|
<< " contracted_regions=" << contracted_regions
|
|
<< " expanded_regions=" << expanded_regions
|
|
<< " stolen_regions=" << stolen_regions;
|
|
return true;
|
|
}
|
|
|
|
template<typename ThrowOnCancel>
|
|
void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel)
|
|
{
|
|
// Returns fuzzy skin segmentation based on painting in the fuzzy skin painting gizmo.
|
|
std::vector<std::vector<ExPolygons>> segmentation = fuzzy_skin_segmentation_by_painting(print_object, throw_on_cancel);
|
|
assert(segmentation.size() == print_object.layer_count());
|
|
|
|
struct ByRegion
|
|
{
|
|
ExPolygons expolygons;
|
|
bool needs_merge { false };
|
|
};
|
|
|
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))), [&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range<size_t> &range) {
|
|
const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
|
|
auto it_layer_range = layer_range_first(layer_ranges, print_object.get_layer(int(range.begin()))->slice_z);
|
|
|
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
|
throw_on_cancel();
|
|
|
|
Layer &layer = *print_object.get_layer(int(layer_idx));
|
|
it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z);
|
|
const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range;
|
|
|
|
assert(segmentation[layer_idx].size() == 2);
|
|
const ExPolygons &fuzzy_skin_segmentation = segmentation[layer_idx][1];
|
|
const BoundingBox fuzzy_skin_segmentation_bbox = get_extents(fuzzy_skin_segmentation);
|
|
if (fuzzy_skin_segmentation.empty())
|
|
continue;
|
|
|
|
// Split LayerRegions by painted fuzzy skin regions.
|
|
// layer_range.fuzzy_skin_painted_regions are sorted by parent PrintObject region ID.
|
|
std::vector<ByRegion> by_region(layer.region_count());
|
|
auto it_fuzzy_skin_region_begin = layer_range.fuzzy_skin_painted_regions.cbegin();
|
|
for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) {
|
|
if (it_fuzzy_skin_region_begin == layer_range.fuzzy_skin_painted_regions.cend())
|
|
continue;
|
|
|
|
const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx);
|
|
const PrintRegion &parent_print_region = parent_layer_region.region();
|
|
assert(parent_print_region.print_object_region_id() == parent_layer_region_idx);
|
|
if (parent_layer_region.slices.empty())
|
|
continue;
|
|
|
|
// Find the first FuzzySkinPaintedRegion, which overrides the parent PrintRegion.
|
|
auto it_fuzzy_skin_region = std::find_if(it_fuzzy_skin_region_begin, layer_range.fuzzy_skin_painted_regions.cend(), [&layer_range, &parent_print_region](const auto &fuzzy_skin_region) {
|
|
return fuzzy_skin_region.parent_print_object_region_id(layer_range) == parent_print_region.print_object_region_id();
|
|
});
|
|
|
|
if (it_fuzzy_skin_region == layer_range.fuzzy_skin_painted_regions.cend())
|
|
continue; // This LayerRegion isn't overrides by any FuzzySkinPaintedRegion.
|
|
|
|
assert(it_fuzzy_skin_region->parent_print_object_region(layer_range) == &parent_print_region);
|
|
|
|
// Update the beginning FuzzySkinPaintedRegion iterator for the next iteration.
|
|
it_fuzzy_skin_region_begin = std::next(it_fuzzy_skin_region);
|
|
|
|
const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices.surfaces);
|
|
Polygons layer_region_remaining_polygons = to_polygons(parent_layer_region.slices.surfaces);
|
|
// Don't trim by self, it is not reliable.
|
|
if (parent_layer_region_bbox.overlap(fuzzy_skin_segmentation_bbox) && it_fuzzy_skin_region->region != &parent_print_region) {
|
|
// Steal from this region.
|
|
const int target_region_id = it_fuzzy_skin_region->region->print_object_region_id();
|
|
ExPolygons stolen = intersection_ex(parent_layer_region.slices.surfaces, fuzzy_skin_segmentation);
|
|
if (!stolen.empty()) {
|
|
ByRegion &dst = by_region[target_region_id];
|
|
if (dst.expolygons.empty()) {
|
|
dst.expolygons = std::move(stolen);
|
|
} else {
|
|
append(dst.expolygons, std::move(stolen));
|
|
dst.needs_merge = true;
|
|
}
|
|
}
|
|
|
|
// Trim slices of this LayerRegion by the fuzzy skin region.
|
|
layer_region_remaining_polygons = diff(layer_region_remaining_polygons, fuzzy_skin_segmentation);
|
|
|
|
// Filter out unprintable polygons. Detailed explanation is inside apply_mm_segmentation.
|
|
if (!layer_region_remaining_polygons.empty()) {
|
|
layer_region_remaining_polygons = opening(union_ex(layer_region_remaining_polygons), scaled<float>(5. * EPSILON), scaled<float>(5. * EPSILON));
|
|
}
|
|
}
|
|
|
|
if (!layer_region_remaining_polygons.empty()) {
|
|
ByRegion &dst = by_region[parent_print_region.print_object_region_id()];
|
|
if (dst.expolygons.empty()) {
|
|
dst.expolygons = union_ex(layer_region_remaining_polygons);
|
|
} else {
|
|
append(dst.expolygons, union_ex(layer_region_remaining_polygons));
|
|
dst.needs_merge = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Re-create Surfaces of LayerRegions.
|
|
for (int region_id = 0; region_id < layer.region_count(); ++region_id) {
|
|
ByRegion &src = by_region[region_id];
|
|
if (src.needs_merge) {
|
|
// Multiple regions were merged into one.
|
|
src.expolygons = closing_ex(src.expolygons, scaled<float>(10. * EPSILON));
|
|
}
|
|
|
|
layer.get_region(region_id)->slices.set(std::move(src.expolygons), stInternal);
|
|
}
|
|
}
|
|
}); // end of parallel_for
|
|
}
|
|
|
|
// 1) Decides Z positions of the layers,
|
|
// 2) Initializes layers and their regions
|
|
// 3) Slices the object meshes
|
|
// 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes
|
|
// 5) Applies size compensation (offsets the slices in XY plane)
|
|
// 6) Replaces bad slices by the slices reconstructed from the upper/lower layer
|
|
// Resulting expolygons of layer regions are marked as Internal.
|
|
//
|
|
// this should be idempotent
|
|
void PrintObject::slice_volumes()
|
|
{
|
|
BOOST_LOG_TRIVIAL(info) << "Slicing volumes..." << log_memory_info();
|
|
const Print *print = this->print();
|
|
const auto throw_on_cancel_callback = std::function<void()>([print](){ print->throw_if_canceled(); });
|
|
|
|
// Clear old LayerRegions, allocate for new PrintRegions.
|
|
for (Layer* layer : m_layers) {
|
|
//BBS: should delete all LayerRegionPtr to avoid memory leak
|
|
while (!layer->m_regions.empty()) {
|
|
if (layer->m_regions.back())
|
|
delete layer->m_regions.back();
|
|
layer->m_regions.pop_back();
|
|
}
|
|
layer->m_regions.reserve(m_shared_regions->all_regions.size());
|
|
for (const std::unique_ptr<PrintRegion> &pr : m_shared_regions->all_regions)
|
|
layer->m_regions.emplace_back(new LayerRegion(layer, pr.get()));
|
|
}
|
|
|
|
std::vector<float> slice_zs = zs_from_layers(m_layers);
|
|
std::vector<VolumeSlices> objSliceByVolume;
|
|
if (!slice_zs.empty()) {
|
|
objSliceByVolume = slice_volumes_inner(
|
|
print->config(), this->config(), this->trafo_centered(),
|
|
this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, throw_on_cancel_callback);
|
|
}
|
|
|
|
//BBS: "model_part" volumes are grouded according to their connections
|
|
//const auto scaled_resolution = scaled<double>(print->config().resolution.value);
|
|
//firstLayerObjSliceByVolume = findPartVolumes(objSliceByVolume, this->model_object()->volumes);
|
|
//groupingVolumes(objSliceByVolumeParts, firstLayerObjSliceByGroups, scaled_resolution);
|
|
//applyNegtiveVolumes(this->model_object()->volumes, objSliceByVolume, firstLayerObjSliceByGroups, scaled_resolution);
|
|
firstLayerObjSliceByVolume = objSliceByVolume;
|
|
|
|
std::vector<std::vector<ExPolygons>> region_slices =
|
|
slices_to_regions(print->config(), *this, this->model_object()->volumes, *m_shared_regions, slice_zs,
|
|
std::move(objSliceByVolume), PrintObject::clip_multipart_objects, throw_on_cancel_callback);
|
|
|
|
for (size_t region_id = 0; region_id < region_slices.size(); ++ region_id) {
|
|
std::vector<ExPolygons> &by_layer = region_slices[region_id];
|
|
for (size_t layer_id = 0; layer_id < by_layer.size(); ++ layer_id)
|
|
m_layers[layer_id]->regions()[region_id]->slices.append(std::move(by_layer[layer_id]), stInternal);
|
|
}
|
|
region_slices.clear();
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - removing top empty layers";
|
|
while (! m_layers.empty()) {
|
|
const Layer *layer = m_layers.back();
|
|
if (! layer->empty())
|
|
break;
|
|
delete layer;
|
|
m_layers.pop_back();
|
|
}
|
|
if (! m_layers.empty())
|
|
m_layers.back()->upper_layer = nullptr;
|
|
m_print->throw_if_canceled();
|
|
|
|
this->apply_conical_overhang();
|
|
|
|
// Capture once: needed both inside the MMU block and in the wall-only Local-Z
|
|
// fallback after fuzzy-skin segmentation, so the dithering_local_z_whole_objects
|
|
// path can build a Local-Z plan even when no MM painting is present.
|
|
const PrintConfig &print_cfg = m_print->config();
|
|
const DynamicPrintConfig &full_cfg = m_print->full_print_config();
|
|
const bool local_z_whole_objects_enabled =
|
|
bool_from_full_config(full_cfg, "dithering_local_z_whole_objects",
|
|
print_cfg.dithering_local_z_whole_objects.value);
|
|
|
|
// Is any ModelVolume multi-material painted?
|
|
if (const auto& volumes = this->model_object()->volumes;
|
|
m_print->config().filament_diameter.size() > 1 && // BBS
|
|
std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mmu_segmentation_facets.empty(); }) != volumes.end()) {
|
|
|
|
// If XY Size compensation is also enabled, notify the user that XY Size compensation
|
|
// would not be used because the object is multi-material painted.
|
|
if (m_config.xy_hole_compensation.value != 0.f || m_config.xy_contour_compensation.value != 0.f) {
|
|
this->active_step_add_warning(
|
|
PrintStateBase::WarningLevel::CRITICAL,
|
|
L("An object's XY size compensation will not be used because it is also color-painted.\nXY Size "
|
|
"compensation cannot be combined with color-painting."));
|
|
BOOST_LOG_TRIVIAL(info) << "xy compensation will not work for object " << this->model_object()->name << " for multi filament.";
|
|
}
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - MMU segmentation";
|
|
std::vector<std::vector<ExPolygons>> mm_segmentation = multi_material_segmentation_by_painting(*this, [print]() { print->throw_if_canceled(); });
|
|
apply_mixed_surface_indentation(*this, mm_segmentation);
|
|
apply_mixed_component_surface_offsets(*this, mm_segmentation);
|
|
// Same-layer pointillisme is applied in G-code path domain (segment-level assignment),
|
|
// not by XY state mask splitting, to avoid boolean-induced voids.
|
|
BOOST_LOG_TRIVIAL(info) << "Same-layer pointillisme uses path-domain G-code segmentation";
|
|
std::vector<std::vector<ExPolygons>> local_z_segmentation =
|
|
local_z_whole_objects_enabled
|
|
? local_z_planner_segmentation_with_whole_object_mixed_wall(*this, mm_segmentation)
|
|
: mm_segmentation;
|
|
build_local_z_plan(*this, local_z_segmentation, [print]() { print->throw_if_canceled(); });
|
|
apply_mm_segmentation(*this, std::move(mm_segmentation), [print]() { print->throw_if_canceled(); });
|
|
}
|
|
|
|
apply_mixed_region_surface_offsets(*this);
|
|
|
|
if (local_z_whole_objects_enabled && this->local_z_intervals().empty()) {
|
|
std::vector<std::vector<ExPolygons>> whole_object_local_z_segmentation =
|
|
whole_object_local_z_segmentation_by_mixed_wall(*this);
|
|
if (!whole_object_local_z_segmentation.empty())
|
|
build_local_z_plan(*this, whole_object_local_z_segmentation, [print]() { print->throw_if_canceled(); });
|
|
}
|
|
|
|
// Is any ModelVolume fuzzy skin painted?
|
|
if (this->model_object()->is_fuzzy_skin_painted()) {
|
|
// If XY Size compensation is also enabled, notify the user that XY Size compensation
|
|
// would not be used because the object has custom fuzzy skin painted.
|
|
if (m_config.xy_hole_compensation.value != 0.f || m_config.xy_contour_compensation.value != 0.f) {
|
|
this->active_step_add_warning(
|
|
PrintStateBase::WarningLevel::CRITICAL,
|
|
_u8L("An object has enabled XY Size compensation which will not be used because it is also fuzzy skin painted.\nXY Size "
|
|
"compensation cannot be combined with fuzzy skin painting.") +
|
|
"\n" + (_u8L("Object name")) + ": " + this->model_object()->name);
|
|
}
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - Fuzzy skin segmentation";
|
|
apply_fuzzy_skin_segmentation(*this, [print]() { print->throw_if_canceled(); });
|
|
}
|
|
|
|
apply_surface_emboss_mixed_region_override(*this, [print]() { print->throw_if_canceled(); });
|
|
|
|
InterlockingGenerator::generate_interlocking_structure(this, [print]() { print->throw_if_canceled(); });
|
|
m_print->throw_if_canceled();
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin";
|
|
{
|
|
// Compensation value, scaled. Only applying the negative scaling here, as the positive scaling has already been applied during slicing.
|
|
const size_t num_extruders = print->config().filament_diameter.size();
|
|
const auto xy_hole_scaled = (num_extruders > 1 && this->is_mm_painted()) ? scaled<float>(0.f) : scaled<float>(m_config.xy_hole_compensation.value);
|
|
const auto xy_contour_scaled = (num_extruders > 1 && this->is_mm_painted()) ? scaled<float>(0.f) : scaled<float>(m_config.xy_contour_compensation.value);
|
|
const float elephant_foot_compensation_scaled = (m_config.raft_layers == 0) ?
|
|
// Only enable Elephant foot compensation if printing directly on the print bed.
|
|
float(scale_(m_config.elefant_foot_compensation.value)) :
|
|
0.f;
|
|
// Uncompensated slices for the layers in case the Elephant foot compensation is applied.
|
|
std::vector<ExPolygons> lslices_elfoot_uncompensated;
|
|
lslices_elfoot_uncompensated.resize(elephant_foot_compensation_scaled > 0 ? std::min(m_config.elefant_foot_compensation_layers.value, (int)m_layers.size()) : 0);
|
|
//BBS: this part has been changed a lot to support seperated contour and hole size compensation
|
|
tbb::parallel_for(
|
|
tbb::blocked_range<size_t>(0, m_layers.size()),
|
|
[this, xy_hole_scaled, xy_contour_scaled, elephant_foot_compensation_scaled, &lslices_elfoot_uncompensated](const tbb::blocked_range<size_t>& range) {
|
|
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
|
|
m_print->throw_if_canceled();
|
|
Layer *layer = m_layers[layer_id];
|
|
// Apply size compensation and perform clipping of multi-part objects.
|
|
float elfoot = elephant_foot_compensation_scaled > 0 && layer_id < m_config.elefant_foot_compensation_layers.value ?
|
|
elephant_foot_compensation_scaled - (elephant_foot_compensation_scaled / m_config.elefant_foot_compensation_layers.value) * layer_id :
|
|
0.f;
|
|
if (layer->m_regions.size() == 1) {
|
|
// Optimized version for a single region layer.
|
|
// Single region, growing or shrinking.
|
|
LayerRegion *layerm = layer->m_regions.front();
|
|
if (elfoot > 0) {
|
|
// Apply the elephant foot compensation and store the original layer slices without the Elephant foot compensation applied.
|
|
ExPolygons expolygons_to_compensate = to_expolygons(std::move(layerm->slices.surfaces));
|
|
if (xy_contour_scaled > 0 || xy_hole_scaled > 0) {
|
|
expolygons_to_compensate = _shrink_contour_holes(std::max(0.f, xy_contour_scaled),
|
|
std::max(0.f, xy_hole_scaled),
|
|
expolygons_to_compensate);
|
|
}
|
|
if (xy_contour_scaled < 0 || xy_hole_scaled < 0) {
|
|
expolygons_to_compensate = _shrink_contour_holes(std::min(0.f, xy_contour_scaled),
|
|
std::min(0.f, xy_hole_scaled),
|
|
expolygons_to_compensate);
|
|
}
|
|
lslices_elfoot_uncompensated[layer_id] = expolygons_to_compensate;
|
|
layerm->slices.set(
|
|
union_ex(
|
|
Slic3r::elephant_foot_compensation(expolygons_to_compensate,
|
|
layerm->flow(frExternalPerimeter), unscale<double>(elfoot))),
|
|
stInternal);
|
|
} else {
|
|
// Apply the XY contour and hole size compensation.
|
|
if (xy_contour_scaled != 0.0f || xy_hole_scaled != 0.0f) {
|
|
ExPolygons expolygons = to_expolygons(std::move(layerm->slices.surfaces));
|
|
if (xy_contour_scaled > 0 || xy_hole_scaled > 0) {
|
|
expolygons = _shrink_contour_holes(std::max(0.f, xy_contour_scaled),
|
|
std::max(0.f, xy_hole_scaled),
|
|
expolygons);
|
|
}
|
|
if (xy_contour_scaled < 0 || xy_hole_scaled < 0) {
|
|
expolygons = _shrink_contour_holes(std::min(0.f, xy_contour_scaled),
|
|
std::min(0.f, xy_hole_scaled),
|
|
expolygons);
|
|
}
|
|
layerm->slices.set(std::move(expolygons), stInternal);
|
|
}
|
|
}
|
|
} else {
|
|
float max_growth = std::max(xy_hole_scaled, xy_contour_scaled);
|
|
float min_growth = std::min(xy_hole_scaled, xy_contour_scaled);
|
|
ExPolygons merged_poly_for_holes_growing;
|
|
if (max_growth > 0) {
|
|
//BBS: merge polygons because region can cut "holes".
|
|
//Then, cut them to give them again later to their region
|
|
merged_poly_for_holes_growing = layer->merged(float(SCALED_EPSILON));
|
|
merged_poly_for_holes_growing = _shrink_contour_holes(std::max(0.f, xy_contour_scaled),
|
|
std::max(0.f, xy_hole_scaled),
|
|
union_ex(merged_poly_for_holes_growing));
|
|
|
|
// BBS: clipping regions, priority is given to the first regions.
|
|
Polygons processed;
|
|
for (size_t region_id = 0; region_id < layer->regions().size(); ++region_id) {
|
|
ExPolygons slices = to_expolygons(std::move(layer->m_regions[region_id]->slices.surfaces));
|
|
if (max_growth > 0.f) {
|
|
slices = intersection_ex(offset_ex(slices, max_growth), merged_poly_for_holes_growing);
|
|
}
|
|
|
|
//BBS: Trim by the slices of already processed regions.
|
|
if (region_id > 0)
|
|
slices = diff_ex(to_polygons(std::move(slices)), processed);
|
|
if (region_id + 1 < layer->regions().size())
|
|
// Collect the already processed regions to trim the to be processed regions.
|
|
polygons_append(processed, slices);
|
|
layer->m_regions[region_id]->slices.set(std::move(slices), stInternal);
|
|
}
|
|
}
|
|
if (min_growth < 0.f || elfoot > 0.f) {
|
|
// Apply the negative XY compensation. (the ones that is <0)
|
|
ExPolygons trimming;
|
|
static const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5);
|
|
if (elfoot > 0.f) {
|
|
ExPolygons expolygons_to_compensate = offset_ex(layer->merged(eps), -eps);
|
|
lslices_elfoot_uncompensated[layer_id] = expolygons_to_compensate;
|
|
trimming = Slic3r::elephant_foot_compensation(expolygons_to_compensate,
|
|
layer->m_regions.front()->flow(frExternalPerimeter), unscale<double>(elfoot));
|
|
} else {
|
|
trimming = layer->merged(float(SCALED_EPSILON));
|
|
}
|
|
if (min_growth < 0.0f)
|
|
trimming = _shrink_contour_holes(std::min(0.f, xy_contour_scaled),
|
|
std::min(0.f, xy_hole_scaled),
|
|
trimming);
|
|
//BBS: trim surfaces
|
|
for (size_t region_id = 0; region_id < layer->regions().size(); ++region_id) {
|
|
// BBS: split trimming result by region
|
|
ExPolygons contour_exp = to_expolygons(std::move(layer->regions()[region_id]->slices.surfaces));
|
|
|
|
layer->regions()[region_id]->slices.set(intersection_ex(contour_exp, to_polygons(trimming)), stInternal);
|
|
}
|
|
}
|
|
}
|
|
// Merge all regions' slices to get islands, chain them by a shortest path.
|
|
layer->make_slices();
|
|
}
|
|
});
|
|
if (elephant_foot_compensation_scaled > 0.f && ! m_layers.empty()) {
|
|
// The Elephant foot has been compensated, therefore the elefant_foot_compensation_layers layer's lslices are shrank with the Elephant foot compensation value.
|
|
// Store the uncompensated value there.
|
|
assert(m_layers.front()->id() == 0);
|
|
//BBS: sort the lslices_elfoot_uncompensated according to shortest path before saving
|
|
//Otherwise the travel of the layer layer would be mess.
|
|
for (int i = 0; i < lslices_elfoot_uncompensated.size(); i++) {
|
|
ExPolygons &expolygons_uncompensated = lslices_elfoot_uncompensated[i];
|
|
Points ordering_points;
|
|
ordering_points.reserve(expolygons_uncompensated.size());
|
|
for (const ExPolygon &ex : expolygons_uncompensated)
|
|
ordering_points.push_back(ex.contour.first_point());
|
|
std::vector<Points::size_type> order = chain_points(ordering_points);
|
|
ExPolygons lslices_sorted;
|
|
lslices_sorted.reserve(expolygons_uncompensated.size());
|
|
for (size_t i : order)
|
|
lslices_sorted.emplace_back(std::move(expolygons_uncompensated[i]));
|
|
m_layers[i]->lslices = std::move(lslices_sorted);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_print->throw_if_canceled();
|
|
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - end";
|
|
}
|
|
|
|
void PrintObject::apply_conical_overhang() {
|
|
BOOST_LOG_TRIVIAL(info) << "Make overhang printable...";
|
|
|
|
if (m_layers.empty()) {
|
|
return;
|
|
}
|
|
|
|
const double conical_overhang_angle = this->config().make_overhang_printable_angle;
|
|
if (conical_overhang_angle == 90.0) {
|
|
return;
|
|
}
|
|
const double angle_radians = conical_overhang_angle * M_PI / 180.;
|
|
const double max_hole_area = this->config().make_overhang_printable_hole_size; // in MM^2
|
|
const double tan_angle = tan(angle_radians); // the XY-component of the angle
|
|
BOOST_LOG_TRIVIAL(info) << "angle " << angle_radians << " maxHoleArea " << max_hole_area << " tan_angle "
|
|
<< tan_angle;
|
|
const coordf_t layer_thickness = m_config.layer_height.value;
|
|
const coordf_t max_dist_from_lower_layer = tan_angle * layer_thickness; // max dist which can be bridged, in MM
|
|
BOOST_LOG_TRIVIAL(info) << "layer_thickness " << layer_thickness << " max_dist_from_lower_layer "
|
|
<< max_dist_from_lower_layer;
|
|
|
|
// Pre-scale config
|
|
const coordf_t scaled_max_dist_from_lower_layer = -float(scale_(max_dist_from_lower_layer));
|
|
const coordf_t scaled_max_hole_area = float(scale_(scale_(max_hole_area)));
|
|
|
|
|
|
for (auto i = m_layers.rbegin() + 1; i != m_layers.rend(); ++i) {
|
|
m_print->throw_if_canceled();
|
|
Layer *layer = *i;
|
|
Layer *upper_layer = layer->upper_layer;
|
|
|
|
if (upper_layer->empty()) {
|
|
continue;
|
|
}
|
|
|
|
// Skip if entire layer has this disabled
|
|
if (std::all_of(layer->m_regions.begin(), layer->m_regions.end(),
|
|
[](const LayerRegion *r) { return r->slices.empty() || !r->region().config().make_overhang_printable; })) {
|
|
continue;
|
|
}
|
|
|
|
//layer->export_region_slices_to_svg_debug("layer_before_conical_overhang");
|
|
//upper_layer->export_region_slices_to_svg_debug("upper_layer_before_conical_overhang");
|
|
|
|
|
|
// Merge the upper layer because we want to offset the entire layer uniformly, otherwise
|
|
// the model could break at the region boundary.
|
|
auto upper_poly = upper_layer->merged(float(SCALED_EPSILON));
|
|
upper_poly = union_ex(upper_poly);
|
|
|
|
// Merge layer for the same reason
|
|
auto current_poly = layer->merged(float(SCALED_EPSILON));
|
|
current_poly = union_ex(current_poly);
|
|
|
|
// Avoid closing up of recessed holes in the base of a model.
|
|
// Detects when a hole is completely covered by the layer above and removes the hole from the layer above before
|
|
// adding it in.
|
|
// This should have no effect any time a hole in a layer interacts with any polygon in the layer above
|
|
if (scaled_max_hole_area > 0.0) {
|
|
|
|
// Now go through all the holes in the current layer and check if they intersect anything in the layer above
|
|
// If not, then they're the top of a hole and should be cut from the layer above before the union
|
|
for (auto layer_polygon : current_poly) {
|
|
for (auto hole : layer_polygon.holes) {
|
|
if (std::abs(hole.area()) < scaled_max_hole_area) {
|
|
ExPolygon hole_poly(hole);
|
|
auto hole_with_above = intersection_ex(upper_poly, hole_poly);
|
|
if (!hole_with_above.empty()) {
|
|
// The hole had some intersection with the above layer, check if it's a complete overlap
|
|
auto hole_difference = xor_ex(hole_with_above, hole_poly);
|
|
if (hole_difference.empty()) {
|
|
// The layer above completely cover it, remove it from the layer above
|
|
upper_poly = diff_ex(upper_poly, hole_poly);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now offset the upper layer to be added into current layer
|
|
upper_poly = offset_ex(upper_poly, scaled_max_dist_from_lower_layer);
|
|
|
|
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) {
|
|
// export_to_svg(debug_out_path("Surface-obj-%d-layer-%d-region-%d.svg", id().id, layer->id(), region_id).c_str(),
|
|
// layer->m_regions[region_id]->slices.surfaces);
|
|
|
|
// Disable on given region
|
|
if (!upper_layer->m_regions[region_id]->region().config().make_overhang_printable) {
|
|
continue;
|
|
}
|
|
|
|
// Calculate the scaled upper poly that belongs to current region
|
|
auto p = union_ex(intersection_ex(upper_layer->m_regions[region_id]->slices.surfaces, upper_poly));
|
|
|
|
// Remove all islands that have already been fully covered by current layer
|
|
p.erase(std::remove_if(p.begin(), p.end(), [¤t_poly](const ExPolygon& ex) {
|
|
return diff_ex(ex, current_poly).empty();
|
|
}), p.end());
|
|
|
|
// And now union it with current region
|
|
ExPolygons layer_polygons = to_expolygons(layer->m_regions[region_id]->slices.surfaces);
|
|
layer->m_regions[region_id]->slices.set(union_ex(layer_polygons, p), stInternal);
|
|
|
|
// Then remove it from all other regions, to avoid overlapping regions
|
|
for (size_t other_region = 0; other_region < this->num_printing_regions(); ++other_region) {
|
|
if (other_region == region_id) {
|
|
continue;
|
|
}
|
|
ExPolygons s = to_expolygons(layer->m_regions[other_region]->slices.surfaces);
|
|
layer->m_regions[other_region]->slices.set(diff_ex(s, p, ApplySafetyOffset::Yes), stInternal);
|
|
}
|
|
}
|
|
//layer->export_region_slices_to_svg_debug("layer_after_conical_overhang");
|
|
}
|
|
}
|
|
|
|
//BBS: this function is used to offset contour and holes of expolygons seperately by different value
|
|
ExPolygons PrintObject::_shrink_contour_holes(double contour_delta, double hole_delta, const ExPolygons& polys) const
|
|
{
|
|
ExPolygons new_ex_polys;
|
|
for (const ExPolygon& ex_poly : polys) {
|
|
Polygons contours;
|
|
Polygons holes;
|
|
//BBS: modify hole
|
|
for (const Polygon& hole : ex_poly.holes) {
|
|
if (hole_delta != 0) {
|
|
for (Polygon& newHole : offset(hole, -hole_delta)) {
|
|
newHole.make_counter_clockwise();
|
|
holes.emplace_back(std::move(newHole));
|
|
}
|
|
} else {
|
|
holes.push_back(hole);
|
|
holes.back().make_counter_clockwise();
|
|
}
|
|
}
|
|
//BBS: modify contour
|
|
if (contour_delta != 0) {
|
|
Polygons new_contours = offset(ex_poly.contour, contour_delta);
|
|
if (new_contours.size() == 0)
|
|
continue;
|
|
contours.insert(contours.end(), std::make_move_iterator(new_contours.begin()), std::make_move_iterator(new_contours.end()));
|
|
} else {
|
|
contours.push_back(ex_poly.contour);
|
|
}
|
|
ExPolygons temp = diff_ex(union_(contours), union_(holes));
|
|
new_ex_polys.insert(new_ex_polys.end(), std::make_move_iterator(temp.begin()), std::make_move_iterator(temp.end()));
|
|
}
|
|
return union_ex(new_ex_polys);
|
|
}
|
|
|
|
std::vector<Polygons> PrintObject::slice_support_volumes(const ModelVolumeType model_volume_type) const
|
|
{
|
|
auto it_volume = this->model_object()->volumes.begin();
|
|
auto it_volume_end = this->model_object()->volumes.end();
|
|
for (; it_volume != it_volume_end && (*it_volume)->type() != model_volume_type; ++ it_volume) ;
|
|
std::vector<Polygons> slices;
|
|
if (it_volume != it_volume_end) {
|
|
// Found at least a single support volume of model_volume_type.
|
|
std::vector<float> zs = zs_from_layers(this->layers());
|
|
std::vector<char> merge_layers;
|
|
bool merge = false;
|
|
const Print *print = this->print();
|
|
auto throw_on_cancel_callback = std::function<void()>([print](){ print->throw_if_canceled(); });
|
|
MeshSlicingParamsEx params;
|
|
params.trafo = this->trafo_centered();
|
|
for (; it_volume != it_volume_end; ++ it_volume)
|
|
if ((*it_volume)->type() == model_volume_type) {
|
|
std::vector<ExPolygons> slices2 = slice_volume(*(*it_volume), zs, params, throw_on_cancel_callback);
|
|
if (slices.empty()) {
|
|
slices.reserve(slices2.size());
|
|
for (ExPolygons &src : slices2)
|
|
slices.emplace_back(to_polygons(std::move(src)));
|
|
} else if (!slices2.empty()) {
|
|
if (merge_layers.empty())
|
|
merge_layers.assign(zs.size(), false);
|
|
for (size_t i = 0; i < zs.size(); ++ i) {
|
|
if (slices[i].empty())
|
|
slices[i] = to_polygons(std::move(slices2[i]));
|
|
else if (! slices2[i].empty()) {
|
|
append(slices[i], to_polygons(std::move(slices2[i])));
|
|
merge_layers[i] = true;
|
|
merge = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (merge) {
|
|
std::vector<Polygons*> to_merge;
|
|
to_merge.reserve(zs.size());
|
|
for (size_t i = 0; i < zs.size(); ++ i)
|
|
if (merge_layers[i])
|
|
to_merge.emplace_back(&slices[i]);
|
|
tbb::parallel_for(
|
|
tbb::blocked_range<size_t>(0, to_merge.size()),
|
|
[&to_merge](const tbb::blocked_range<size_t> &range) {
|
|
for (size_t i = range.begin(); i < range.end(); ++ i)
|
|
*to_merge[i] = union_(*to_merge[i]);
|
|
});
|
|
}
|
|
}
|
|
return slices;
|
|
}
|
|
|
|
// ============================================================
|
|
// Local-Z plan generator — pair cadence path (Task 29)
|
|
// ============================================================
|
|
|
|
// Channel 0 in MM segmentation is the background/default region.
|
|
// Channels 1..N correspond to 1-based filament IDs.
|
|
static inline unsigned int segmentation_channel_filament_id(size_t channel_idx)
|
|
{
|
|
return unsigned(channel_idx);
|
|
}
|
|
|
|
// Average nozzle width across a mixed filament's referenced components. Used as
|
|
// the reference width when clamping mixed-component surface offsets.
|
|
static float mixed_filament_reference_nozzle_mm(const MixedFilament &mixed_row, const ConfigOptionFloats &nozzle_diameters)
|
|
{
|
|
std::vector<float> samples;
|
|
samples.reserve(2);
|
|
|
|
auto append_if_valid = [&samples, &nozzle_diameters](unsigned int component_id) {
|
|
if (component_id >= 1 && component_id <= nozzle_diameters.size())
|
|
samples.emplace_back(std::max(0.05f, float(nozzle_diameters.get_at(component_id - 1))));
|
|
};
|
|
|
|
append_if_valid(mixed_row.component_a);
|
|
append_if_valid(mixed_row.component_b);
|
|
|
|
if (samples.empty())
|
|
return 0.4f;
|
|
return std::accumulate(samples.begin(), samples.end(), 0.0f) / float(samples.size());
|
|
}
|
|
|
|
// Resolve the per-layer mixed-component surface offset for `filament_id` and
|
|
// clamp it to MixedFilamentManager::max_component_surface_offset_mm() based on
|
|
// the average nozzle width of the mixed row's referenced components.
|
|
static coordf_t clamped_mixed_component_surface_offset(const MixedFilamentManager &mixed_mgr,
|
|
const PrintConfig &print_cfg,
|
|
unsigned int filament_id,
|
|
size_t num_physical,
|
|
int layer_index,
|
|
float layer_print_z,
|
|
float layer_height,
|
|
bool force_height_weighted)
|
|
{
|
|
const MixedFilament *mixed_row = mixed_mgr.mixed_filament_from_id(filament_id, num_physical);
|
|
if (mixed_row == nullptr)
|
|
return 0.f;
|
|
|
|
const coordf_t offset_mm = coordf_t(mixed_mgr.component_surface_offset(
|
|
filament_id,
|
|
num_physical,
|
|
layer_index,
|
|
layer_print_z,
|
|
layer_height,
|
|
force_height_weighted));
|
|
if (std::abs(offset_mm) <= EPSILON)
|
|
return 0.f;
|
|
|
|
const float reference_nozzle = mixed_filament_reference_nozzle_mm(*mixed_row, print_cfg.nozzle_diameter);
|
|
const coordf_t limit_mm = coordf_t(MixedFilamentManager::max_component_surface_offset_mm(reference_nozzle));
|
|
return std::clamp(offset_mm, -limit_mm, limit_mm);
|
|
}
|
|
|
|
// --- pass-height helpers ---
|
|
|
|
static bool fit_pass_heights_to_interval(std::vector<double> &passes, double base_height, double lo, double hi)
|
|
{
|
|
if (passes.empty() || base_height <= EPSILON)
|
|
return false;
|
|
|
|
double sum = std::accumulate(passes.begin(), passes.end(), 0.0);
|
|
double delta = base_height - sum;
|
|
|
|
auto within = [lo, hi](double h) { return h >= lo - EPSILON && h <= hi + EPSILON; };
|
|
if (std::abs(delta) > EPSILON) {
|
|
if (within(passes.back() + delta)) {
|
|
passes.back() += delta;
|
|
delta = 0.0;
|
|
} else if (delta > 0.0) {
|
|
for (size_t i = passes.size(); i > 0 && delta > EPSILON; --i) {
|
|
double &h = passes[i - 1];
|
|
const double room = hi - h;
|
|
if (room <= EPSILON)
|
|
continue;
|
|
const double take = std::min(room, delta);
|
|
h += take;
|
|
delta -= take;
|
|
}
|
|
} else {
|
|
for (size_t i = passes.size(); i > 0 && delta < -EPSILON; --i) {
|
|
double &h = passes[i - 1];
|
|
const double room = h - lo;
|
|
if (room <= EPSILON)
|
|
continue;
|
|
const double take = std::min(room, -delta);
|
|
h -= take;
|
|
delta += take;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (std::abs(delta) > 1e-6)
|
|
return false;
|
|
return std::all_of(passes.begin(), passes.end(), within);
|
|
}
|
|
|
|
static bool sanitize_local_z_pass_heights(std::vector<double> &passes, double base_height, double lower_bound, double upper_bound)
|
|
{
|
|
if (passes.empty() || base_height <= EPSILON)
|
|
return false;
|
|
|
|
const double lo = std::max<double>(0.01, lower_bound);
|
|
const double hi = std::max<double>(lo, upper_bound);
|
|
for (double &h : passes) {
|
|
if (!std::isfinite(h))
|
|
h = lo;
|
|
h = std::clamp(h, lo, hi);
|
|
}
|
|
return fit_pass_heights_to_interval(passes, base_height, lo, hi);
|
|
}
|
|
|
|
static std::vector<double> build_uniform_local_z_pass_heights(double base_height,
|
|
double lo,
|
|
double hi,
|
|
size_t max_passes_limit = 0)
|
|
{
|
|
std::vector<double> out;
|
|
if (base_height <= EPSILON)
|
|
return out;
|
|
|
|
size_t min_passes = size_t(std::max<double>(1.0, std::ceil((base_height - EPSILON) / hi)));
|
|
size_t max_passes = size_t(std::max<double>(1.0, std::floor((base_height + EPSILON) / lo)));
|
|
size_t pass_count = min_passes;
|
|
|
|
if (max_passes >= min_passes) {
|
|
const double target_step = 0.5 * (lo + hi);
|
|
const size_t target_passes =
|
|
size_t(std::max<double>(1.0, std::llround(base_height / std::max<double>(target_step, EPSILON))));
|
|
pass_count = std::clamp(target_passes, min_passes, max_passes);
|
|
}
|
|
|
|
if (max_passes_limit > 0) {
|
|
const size_t capped_limit = std::max<size_t>(1, max_passes_limit);
|
|
if (pass_count > capped_limit)
|
|
pass_count = capped_limit;
|
|
}
|
|
|
|
if (pass_count == 1 && base_height >= 2.0 * lo - EPSILON && max_passes >= 2)
|
|
pass_count = 2;
|
|
|
|
if (pass_count <= 1) {
|
|
out.emplace_back(base_height);
|
|
return out;
|
|
}
|
|
|
|
const double uniform_height = base_height / double(pass_count);
|
|
out.assign(pass_count, uniform_height);
|
|
|
|
double accumulated = 0.0;
|
|
for (size_t i = 0; i + 1 < out.size(); ++i)
|
|
accumulated += out[i];
|
|
out.back() = std::max<double>(EPSILON, base_height - accumulated);
|
|
return out;
|
|
}
|
|
|
|
static std::vector<double> build_uniform_local_z_pass_heights_exact(double base_height,
|
|
double lower_bound,
|
|
double upper_bound,
|
|
size_t pass_count)
|
|
{
|
|
if (base_height <= EPSILON || pass_count == 0)
|
|
return {};
|
|
|
|
const double lo = std::max<double>(0.01, lower_bound);
|
|
const double hi = std::max<double>(lo, upper_bound);
|
|
if (pass_count == 1)
|
|
return { base_height };
|
|
|
|
if (double(pass_count) * lo > base_height + EPSILON || double(pass_count) * hi < base_height - EPSILON)
|
|
return {};
|
|
|
|
std::vector<double> out(pass_count, base_height / double(pass_count));
|
|
if (!fit_pass_heights_to_interval(out, base_height, lo, hi))
|
|
return {};
|
|
return out;
|
|
}
|
|
|
|
static inline void compute_local_z_gradient_component_heights(int mix_b_percent, double lower_bound, double upper_bound,
|
|
double &h_a, double &h_b)
|
|
{
|
|
const int mix_b = std::clamp(mix_b_percent, 0, 100);
|
|
const double pct_b = double(mix_b) / 100.0;
|
|
const double pct_a = 1.0 - pct_b;
|
|
const double lo = std::max<double>(0.01, lower_bound);
|
|
const double hi = std::max<double>(lo, upper_bound);
|
|
h_a = lo + pct_a * (hi - lo);
|
|
h_b = lo + pct_b * (hi - lo);
|
|
}
|
|
|
|
static bool choose_local_z_start_with_component_a(const std::vector<double> &pass_heights,
|
|
double expected_h_a,
|
|
double expected_h_b,
|
|
size_t cadence_index)
|
|
{
|
|
double err_ab = 0.0;
|
|
double err_ba = 0.0;
|
|
for (size_t pass_i = 0; pass_i < pass_heights.size(); ++pass_i) {
|
|
const double expected_ab = (pass_i % 2) == 0 ? expected_h_a : expected_h_b;
|
|
const double expected_ba = (pass_i % 2) == 0 ? expected_h_b : expected_h_a;
|
|
err_ab += std::abs(pass_heights[pass_i] - expected_ab);
|
|
err_ba += std::abs(pass_heights[pass_i] - expected_ba);
|
|
}
|
|
|
|
if (err_ab + 1e-6 < err_ba)
|
|
return true;
|
|
if (err_ba + 1e-6 < err_ab)
|
|
return false;
|
|
|
|
if (std::abs(expected_h_a - expected_h_b) <= 1e-6)
|
|
return (cadence_index % 2) == 0;
|
|
|
|
return expected_h_a >= expected_h_b;
|
|
}
|
|
|
|
static std::vector<double> build_local_z_alternating_pass_heights(double base_height,
|
|
double lower_bound,
|
|
double upper_bound,
|
|
double gradient_h_a,
|
|
double gradient_h_b,
|
|
size_t max_passes_limit = 0)
|
|
{
|
|
if (base_height <= EPSILON)
|
|
return {};
|
|
|
|
const double lo = std::max<double>(0.01, lower_bound);
|
|
const double hi = std::max<double>(lo, upper_bound);
|
|
if (base_height < 2.0 * lo - EPSILON)
|
|
return { base_height };
|
|
|
|
const double cycle_h = std::max<double>(EPSILON, gradient_h_a + gradient_h_b);
|
|
const double ratio_a = std::clamp(gradient_h_a / cycle_h, 0.0, 1.0);
|
|
|
|
size_t min_passes = size_t(std::max<double>(2.0, std::ceil((base_height - EPSILON) / hi)));
|
|
if ((min_passes % 2) != 0)
|
|
++min_passes;
|
|
|
|
size_t max_passes = size_t(std::max<double>(2.0, std::floor((base_height + EPSILON) / lo)));
|
|
if ((max_passes % 2) != 0)
|
|
--max_passes;
|
|
if (max_passes_limit > 0) {
|
|
size_t capped_limit = std::max<size_t>(2, max_passes_limit);
|
|
if ((capped_limit % 2) != 0)
|
|
--capped_limit;
|
|
if (capped_limit >= 2)
|
|
max_passes = std::min(max_passes, capped_limit);
|
|
}
|
|
if (max_passes < 2)
|
|
return build_uniform_local_z_pass_heights(base_height, lo, hi, max_passes_limit);
|
|
if (min_passes > max_passes)
|
|
min_passes = max_passes;
|
|
if (min_passes < 2)
|
|
min_passes = 2;
|
|
if ((min_passes % 2) != 0)
|
|
++min_passes;
|
|
if (min_passes > max_passes)
|
|
return build_uniform_local_z_pass_heights(base_height, lo, hi, max_passes_limit);
|
|
|
|
const double target_step = 0.5 * (lo + hi);
|
|
size_t target_passes =
|
|
size_t(std::max<double>(2.0, std::llround(base_height / std::max<double>(target_step, EPSILON))));
|
|
if ((target_passes % 2) != 0) {
|
|
const size_t round_up = (target_passes < max_passes) ? (target_passes + 1) : max_passes;
|
|
const size_t round_down = (target_passes > min_passes) ? (target_passes - 1) : min_passes;
|
|
if (round_up > max_passes)
|
|
target_passes = round_down;
|
|
else if (round_down < min_passes)
|
|
target_passes = round_up;
|
|
else {
|
|
const size_t up_dist = round_up - target_passes;
|
|
const size_t down_dist = target_passes - round_down;
|
|
target_passes = (up_dist <= down_dist) ? round_up : round_down;
|
|
}
|
|
}
|
|
target_passes = std::clamp(target_passes, min_passes, max_passes);
|
|
|
|
bool has_best = false;
|
|
std::vector<double> best_passes;
|
|
double best_ratio_error = 0.0;
|
|
size_t best_pass_distance = 0;
|
|
double best_max_height = 0.0;
|
|
size_t best_pass_count = 0;
|
|
|
|
for (size_t pass_count = min_passes; pass_count <= max_passes; pass_count += 2) {
|
|
const size_t pair_count = pass_count / 2;
|
|
if (pair_count == 0)
|
|
continue;
|
|
const double pair_h = base_height / double(pair_count);
|
|
|
|
const double h_a_min = std::max(lo, pair_h - hi);
|
|
const double h_a_max = std::min(hi, pair_h - lo);
|
|
if (h_a_min > h_a_max + EPSILON)
|
|
continue;
|
|
|
|
const double h_a = std::clamp(pair_h * ratio_a, h_a_min, h_a_max);
|
|
const double h_b = pair_h - h_a;
|
|
|
|
std::vector<double> out;
|
|
out.reserve(pass_count);
|
|
for (size_t pair_idx = 0; pair_idx < pair_count; ++pair_idx) {
|
|
out.emplace_back(h_a);
|
|
out.emplace_back(h_b);
|
|
}
|
|
if (!fit_pass_heights_to_interval(out, base_height, lo, hi))
|
|
continue;
|
|
|
|
const double ratio_actual = (h_a + h_b > EPSILON) ? (h_a / (h_a + h_b)) : 0.5;
|
|
const double ratio_error = std::abs(ratio_actual - ratio_a);
|
|
const size_t pass_distance =
|
|
(pass_count > target_passes) ? (pass_count - target_passes) : (target_passes - pass_count);
|
|
const double max_height = std::max(h_a, h_b);
|
|
|
|
const bool better_ratio = !has_best || (ratio_error + 1e-6 < best_ratio_error);
|
|
const bool similar_ratio = has_best && std::abs(ratio_error - best_ratio_error) <= 1e-6;
|
|
const bool better_distance = similar_ratio && (pass_distance < best_pass_distance);
|
|
const bool similar_distance = similar_ratio && (pass_distance == best_pass_distance);
|
|
const bool better_max_height = similar_distance && (max_height + 1e-6 < best_max_height);
|
|
const bool similar_max_height = similar_distance && std::abs(max_height - best_max_height) <= 1e-6;
|
|
const bool better_pass_count = similar_max_height && (pass_count > best_pass_count);
|
|
|
|
if (better_ratio || better_distance || better_max_height || better_pass_count) {
|
|
has_best = true;
|
|
best_passes = std::move(out);
|
|
best_ratio_error = ratio_error;
|
|
best_pass_distance = pass_distance;
|
|
best_max_height = max_height;
|
|
best_pass_count = pass_count;
|
|
}
|
|
}
|
|
|
|
if (has_best)
|
|
return best_passes;
|
|
return build_uniform_local_z_pass_heights(base_height, lo, hi, max_passes_limit);
|
|
}
|
|
|
|
static std::vector<double> build_local_z_two_pass_heights(double base_height,
|
|
double lower_bound,
|
|
double upper_bound,
|
|
double gradient_h_a,
|
|
double gradient_h_b)
|
|
{
|
|
if (base_height <= EPSILON)
|
|
return {};
|
|
|
|
const double lo = std::max<double>(0.01, lower_bound);
|
|
const double hi = std::max<double>(lo, upper_bound);
|
|
if (base_height < 2.0 * lo - EPSILON || base_height > 2.0 * hi + EPSILON)
|
|
return { base_height };
|
|
|
|
const double cycle_h = std::max<double>(EPSILON, gradient_h_a + gradient_h_b);
|
|
const double ratio_a = std::clamp(gradient_h_a / cycle_h, 0.0, 1.0);
|
|
|
|
const double h_a_min = std::max(lo, base_height - hi);
|
|
const double h_a_max = std::min(hi, base_height - lo);
|
|
if (h_a_min > h_a_max + EPSILON)
|
|
return { base_height };
|
|
|
|
const double h_a = std::clamp(base_height * ratio_a, h_a_min, h_a_max);
|
|
const double h_b = base_height - h_a;
|
|
|
|
std::vector<double> out { h_a, h_b };
|
|
if (!fit_pass_heights_to_interval(out, base_height, lo, hi))
|
|
return { base_height };
|
|
return out;
|
|
}
|
|
|
|
static std::vector<double> build_local_z_shared_pass_heights(double base_height, double lower_bound, double upper_bound)
|
|
{
|
|
if (base_height <= EPSILON)
|
|
return {};
|
|
|
|
const double lo = std::max<double>(0.01, lower_bound);
|
|
const double hi = std::max<double>(lo, upper_bound);
|
|
if (base_height < 2.0 * lo - EPSILON)
|
|
return { base_height };
|
|
|
|
double h_small = lo;
|
|
double h_large = base_height - h_small;
|
|
if (h_large > hi + EPSILON) {
|
|
h_large = hi;
|
|
h_small = base_height - h_large;
|
|
}
|
|
if (h_small < lo - EPSILON || h_small > hi + EPSILON ||
|
|
h_large < lo - EPSILON || h_large > hi + EPSILON)
|
|
return build_uniform_local_z_pass_heights(base_height, lo, hi);
|
|
|
|
std::vector<double> out { h_small, h_large };
|
|
if (!fit_pass_heights_to_interval(out, base_height, lo, hi))
|
|
return build_uniform_local_z_pass_heights(base_height, lo, hi);
|
|
if (out.size() == 2 && out[0] > out[1])
|
|
std::swap(out[0], out[1]);
|
|
return out;
|
|
}
|
|
|
|
static std::vector<double> build_local_z_pass_heights(double base_height,
|
|
double lower_bound,
|
|
double upper_bound,
|
|
double preferred_a,
|
|
double preferred_b,
|
|
size_t max_passes_limit = 0)
|
|
{
|
|
if (base_height <= EPSILON)
|
|
return {};
|
|
|
|
const double lo = std::max<double>(0.01, lower_bound);
|
|
const double hi = std::max<double>(lo, upper_bound);
|
|
|
|
std::vector<double> cadence_unit;
|
|
if (preferred_a > EPSILON)
|
|
cadence_unit.push_back(std::clamp(preferred_a, lo, hi));
|
|
if (preferred_b > EPSILON)
|
|
cadence_unit.push_back(std::clamp(preferred_b, lo, hi));
|
|
|
|
if (!cadence_unit.empty()) {
|
|
std::vector<double> out;
|
|
out.reserve(size_t(std::ceil(base_height / lo)) + 2);
|
|
|
|
double z_used = 0.0;
|
|
size_t idx = 0;
|
|
size_t guard = 0;
|
|
while (z_used + cadence_unit[idx] < base_height - EPSILON && guard++ < 100000) {
|
|
out.push_back(cadence_unit[idx]);
|
|
z_used += cadence_unit[idx];
|
|
idx = (idx + 1) % cadence_unit.size();
|
|
}
|
|
|
|
const double remainder = base_height - z_used;
|
|
if (remainder > EPSILON)
|
|
out.push_back(remainder);
|
|
|
|
if (fit_pass_heights_to_interval(out, base_height, lo, hi) &&
|
|
(max_passes_limit == 0 || out.size() <= max_passes_limit))
|
|
return out;
|
|
|
|
if (max_passes_limit > 0 && preferred_a > EPSILON && preferred_b > EPSILON)
|
|
return build_local_z_alternating_pass_heights(base_height,
|
|
lower_bound,
|
|
upper_bound,
|
|
preferred_a,
|
|
preferred_b,
|
|
max_passes_limit);
|
|
}
|
|
|
|
return build_uniform_local_z_pass_heights(base_height, lo, hi, max_passes_limit);
|
|
}
|
|
|
|
// --- gradient component ID / weight decoders ---
|
|
|
|
static std::vector<unsigned int> decode_gradient_component_ids(const MixedFilament &mf, size_t num_physical)
|
|
{
|
|
std::vector<unsigned int> ids;
|
|
if (mf.gradient_component_ids.empty() || num_physical == 0)
|
|
return ids;
|
|
|
|
bool seen[10] = { false };
|
|
ids.reserve(mf.gradient_component_ids.size());
|
|
for (const char c : mf.gradient_component_ids) {
|
|
if (c < '1' || c > '9')
|
|
continue;
|
|
const unsigned int id = unsigned(c - '0');
|
|
if (id == 0 || id > num_physical || seen[id])
|
|
continue;
|
|
seen[id] = true;
|
|
ids.emplace_back(id);
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
static std::vector<int> decode_gradient_component_weights(const MixedFilament &mf, size_t expected_components)
|
|
{
|
|
std::vector<int> out;
|
|
if (mf.gradient_component_weights.empty() || expected_components == 0)
|
|
return out;
|
|
|
|
std::string token;
|
|
for (const char c : mf.gradient_component_weights) {
|
|
if (c >= '0' && c <= '9') {
|
|
token.push_back(c);
|
|
continue;
|
|
}
|
|
if (!token.empty()) {
|
|
out.emplace_back(std::max(0, std::atoi(token.c_str())));
|
|
token.clear();
|
|
}
|
|
}
|
|
if (!token.empty())
|
|
out.emplace_back(std::max(0, std::atoi(token.c_str())));
|
|
if (out.size() != expected_components)
|
|
return {};
|
|
|
|
int sum = 0;
|
|
for (const int v : out)
|
|
sum += std::max(0, v);
|
|
if (sum <= 0)
|
|
return {};
|
|
return out;
|
|
}
|
|
|
|
// --- weighted sequence builder ---
|
|
|
|
static void reduce_weight_counts_to_cycle_limit(std::vector<int> &counts, size_t cycle_limit)
|
|
{
|
|
if (counts.empty() || cycle_limit == 0)
|
|
return;
|
|
|
|
int total = std::accumulate(counts.begin(), counts.end(), 0);
|
|
if (total <= 0 || size_t(total) <= cycle_limit)
|
|
return;
|
|
|
|
std::vector<size_t> positive_indices;
|
|
positive_indices.reserve(counts.size());
|
|
for (size_t i = 0; i < counts.size(); ++i)
|
|
if (counts[i] > 0)
|
|
positive_indices.emplace_back(i);
|
|
|
|
if (positive_indices.empty()) {
|
|
counts.assign(counts.size(), 0);
|
|
return;
|
|
}
|
|
|
|
std::vector<int> reduced(counts.size(), 0);
|
|
if (cycle_limit < positive_indices.size()) {
|
|
std::sort(positive_indices.begin(), positive_indices.end(), [&counts](size_t lhs, size_t rhs) {
|
|
if (counts[lhs] != counts[rhs])
|
|
return counts[lhs] > counts[rhs];
|
|
return lhs < rhs;
|
|
});
|
|
for (size_t i = 0; i < cycle_limit; ++i)
|
|
reduced[positive_indices[i]] = 1;
|
|
counts = std::move(reduced);
|
|
return;
|
|
}
|
|
|
|
size_t remaining_slots = cycle_limit;
|
|
for (const size_t idx : positive_indices) {
|
|
reduced[idx] = 1;
|
|
--remaining_slots;
|
|
}
|
|
|
|
int total_extras = 0;
|
|
std::vector<int> extra_counts(counts.size(), 0);
|
|
for (const size_t idx : positive_indices) {
|
|
extra_counts[idx] = std::max(0, counts[idx] - 1);
|
|
total_extras += extra_counts[idx];
|
|
}
|
|
if (remaining_slots == 0 || total_extras <= 0) {
|
|
counts = std::move(reduced);
|
|
return;
|
|
}
|
|
|
|
std::vector<double> remainders(counts.size(), -1.0);
|
|
size_t assigned_slots = 0;
|
|
for (const size_t idx : positive_indices) {
|
|
if (extra_counts[idx] == 0)
|
|
continue;
|
|
const double exact = double(remaining_slots) * double(extra_counts[idx]) / double(total_extras);
|
|
const int assigned = int(std::floor(exact));
|
|
reduced[idx] += assigned;
|
|
assigned_slots += size_t(assigned);
|
|
remainders[idx] = exact - double(assigned);
|
|
}
|
|
|
|
size_t missing_slots = remaining_slots > assigned_slots ? (remaining_slots - assigned_slots) : size_t(0);
|
|
while (missing_slots > 0) {
|
|
size_t best_idx = size_t(-1);
|
|
double best_remainder = -1.0;
|
|
int best_extra = -1;
|
|
for (const size_t idx : positive_indices) {
|
|
if (extra_counts[idx] == 0)
|
|
continue;
|
|
if (remainders[idx] > best_remainder ||
|
|
(std::abs(remainders[idx] - best_remainder) <= 1e-9 && extra_counts[idx] > best_extra) ||
|
|
(std::abs(remainders[idx] - best_remainder) <= 1e-9 && extra_counts[idx] == best_extra && idx < best_idx)) {
|
|
best_idx = idx;
|
|
best_remainder = remainders[idx];
|
|
best_extra = extra_counts[idx];
|
|
}
|
|
}
|
|
if (best_idx == size_t(-1))
|
|
break;
|
|
++reduced[best_idx];
|
|
remainders[best_idx] = -1.0;
|
|
--missing_slots;
|
|
}
|
|
|
|
counts = std::move(reduced);
|
|
}
|
|
|
|
static std::vector<unsigned int> build_weighted_gradient_sequence(const std::vector<unsigned int> &ids,
|
|
const std::vector<int> &weights,
|
|
size_t max_cycle_limit = 0)
|
|
{
|
|
if (ids.empty())
|
|
return {};
|
|
|
|
std::vector<unsigned int> filtered_ids;
|
|
std::vector<int> counts;
|
|
filtered_ids.reserve(ids.size());
|
|
counts.reserve(ids.size());
|
|
for (size_t i = 0; i < ids.size(); ++i) {
|
|
const int w = (i < weights.size()) ? std::max(0, weights[i]) : 0;
|
|
if (w <= 0)
|
|
continue;
|
|
filtered_ids.emplace_back(ids[i]);
|
|
counts.emplace_back(w);
|
|
}
|
|
if (filtered_ids.empty()) {
|
|
filtered_ids = ids;
|
|
counts.assign(ids.size(), 1);
|
|
}
|
|
|
|
int g = 0;
|
|
for (const int c : counts)
|
|
g = std::gcd(g, std::max(1, c));
|
|
if (g > 1) {
|
|
for (int &c : counts)
|
|
c = std::max(1, c / g);
|
|
}
|
|
|
|
constexpr size_t k_max_cycle = 48;
|
|
const size_t effective_cycle_limit =
|
|
max_cycle_limit > 0 ? std::min(k_max_cycle, std::max<size_t>(1, max_cycle_limit)) : k_max_cycle;
|
|
reduce_weight_counts_to_cycle_limit(counts, effective_cycle_limit);
|
|
|
|
std::vector<unsigned int> reduced_ids;
|
|
std::vector<int> reduced_counts;
|
|
reduced_ids.reserve(filtered_ids.size());
|
|
reduced_counts.reserve(counts.size());
|
|
for (size_t i = 0; i < counts.size(); ++i) {
|
|
if (counts[i] <= 0)
|
|
continue;
|
|
reduced_ids.emplace_back(filtered_ids[i]);
|
|
reduced_counts.emplace_back(counts[i]);
|
|
}
|
|
if (reduced_ids.empty())
|
|
return {};
|
|
filtered_ids = std::move(reduced_ids);
|
|
counts = std::move(reduced_counts);
|
|
|
|
const int total = std::accumulate(counts.begin(), counts.end(), 0);
|
|
if (total <= 0)
|
|
return {};
|
|
|
|
const size_t cycle = size_t(total);
|
|
std::vector<unsigned int> sequence;
|
|
sequence.reserve(cycle);
|
|
std::vector<int> emitted(counts.size(), 0);
|
|
for (size_t pos = 0; pos < cycle; ++pos) {
|
|
size_t best_idx = 0;
|
|
double best_score = -1e9;
|
|
for (size_t i = 0; i < counts.size(); ++i) {
|
|
const double target = double(pos + 1) * double(counts[i]) / double(total);
|
|
const double score = target - double(emitted[i]);
|
|
if (score > best_score) {
|
|
best_score = score;
|
|
best_idx = i;
|
|
}
|
|
}
|
|
++emitted[best_idx];
|
|
sequence.emplace_back(filtered_ids[best_idx]);
|
|
}
|
|
return sequence;
|
|
}
|
|
|
|
// --- eligibility / pair helpers ---
|
|
|
|
static size_t unique_extruder_count(const std::vector<unsigned int> &sequence, size_t num_physical)
|
|
{
|
|
if (sequence.empty() || num_physical == 0)
|
|
return 0;
|
|
|
|
std::vector<bool> seen(num_physical + 1, false);
|
|
size_t unique_count = 0;
|
|
for (const unsigned int extruder_id : sequence) {
|
|
if (extruder_id == 0 || extruder_id > num_physical)
|
|
continue;
|
|
if (!seen[extruder_id]) {
|
|
seen[extruder_id] = true;
|
|
++unique_count;
|
|
}
|
|
}
|
|
return unique_count;
|
|
}
|
|
|
|
static bool local_z_eligible_mixed_row(const MixedFilament &mf)
|
|
{
|
|
// Local-Z flow-height modulation applies to all mixed rows resolved as A/B
|
|
// blends on model surfaces. Exclude explicit manual patterns and same-layer
|
|
// pointillism rows, which have their own distribution semantics.
|
|
return mf.enabled &&
|
|
mf.manual_pattern.empty() &&
|
|
mf.distribution_mode != int(MixedFilament::SameLayerPointillisme);
|
|
}
|
|
|
|
// Returns false for pair-cadence rows (handled by Task 29); true for 3+ component rows (Task 31).
|
|
static bool local_z_direct_multicolor_row(const MixedFilament &mf,
|
|
size_t num_physical,
|
|
std::vector<unsigned int> *component_ids = nullptr,
|
|
std::vector<int> *component_weights = nullptr)
|
|
{
|
|
if (!local_z_eligible_mixed_row(mf))
|
|
return false;
|
|
|
|
const std::vector<unsigned int> ids = decode_gradient_component_ids(mf, num_physical);
|
|
if (ids.size() < 3)
|
|
return false;
|
|
|
|
if (component_ids != nullptr)
|
|
*component_ids = ids;
|
|
if (component_weights != nullptr) {
|
|
std::vector<int> weights = decode_gradient_component_weights(mf, ids.size());
|
|
if (weights.empty())
|
|
weights.assign(ids.size(), 1);
|
|
*component_weights = std::move(weights);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
struct LocalZActivePair
|
|
{
|
|
unsigned int component_a = 0;
|
|
unsigned int component_b = 0;
|
|
int mix_b_percent = 50;
|
|
bool uses_layer_cycle_sequence = false;
|
|
|
|
bool valid_pair(size_t num_physical) const
|
|
{
|
|
return component_a > 0 && component_a <= num_physical &&
|
|
component_b > 0 && component_b <= num_physical &&
|
|
component_a != component_b;
|
|
}
|
|
};
|
|
|
|
static void append_local_z_pair_option(std::vector<LocalZActivePair> &out,
|
|
unsigned int component_a,
|
|
unsigned int component_b,
|
|
int weight_a,
|
|
int weight_b)
|
|
{
|
|
if (component_a == 0 || component_b == 0 || component_a == component_b)
|
|
return;
|
|
|
|
LocalZActivePair pair;
|
|
pair.component_a = component_a;
|
|
pair.component_b = component_b;
|
|
pair.uses_layer_cycle_sequence = true;
|
|
|
|
const int safe_weight_a = std::max(0, weight_a);
|
|
const int safe_weight_b = std::max(0, weight_b);
|
|
const int pair_total = std::max(1, safe_weight_a + safe_weight_b);
|
|
pair.mix_b_percent =
|
|
std::clamp(int(std::lround(100.0 * double(safe_weight_b) / double(pair_total))), 1, 99);
|
|
out.emplace_back(pair);
|
|
}
|
|
|
|
static std::vector<LocalZActivePair> build_local_z_pair_cycle_for_row(const MixedFilament &mf, size_t num_physical)
|
|
{
|
|
std::vector<LocalZActivePair> pair_options;
|
|
if (!mf.enabled || num_physical == 0 || mf.distribution_mode == int(MixedFilament::Simple))
|
|
return pair_options;
|
|
|
|
const std::vector<unsigned int> gradient_ids = decode_gradient_component_ids(mf, num_physical);
|
|
if (gradient_ids.size() < 3)
|
|
return pair_options;
|
|
|
|
std::vector<int> gradient_weights = decode_gradient_component_weights(mf, gradient_ids.size());
|
|
if (gradient_weights.empty())
|
|
gradient_weights.assign(gradient_ids.size(), 1);
|
|
|
|
std::vector<int> pair_weights;
|
|
if (gradient_ids.size() >= 4) {
|
|
append_local_z_pair_option(pair_options, gradient_ids[0], gradient_ids[1], gradient_weights[0], gradient_weights[1]);
|
|
append_local_z_pair_option(pair_options, gradient_ids[2], gradient_ids[3], gradient_weights[2], gradient_weights[3]);
|
|
pair_weights.emplace_back(std::max(1, gradient_weights[0] + gradient_weights[1]));
|
|
pair_weights.emplace_back(std::max(1, gradient_weights[2] + gradient_weights[3]));
|
|
} else {
|
|
append_local_z_pair_option(pair_options, gradient_ids[0], gradient_ids[1], gradient_weights[0], gradient_weights[1]);
|
|
append_local_z_pair_option(pair_options, gradient_ids[0], gradient_ids[2], gradient_weights[0], gradient_weights[2]);
|
|
append_local_z_pair_option(pair_options, gradient_ids[1], gradient_ids[2], gradient_weights[1], gradient_weights[2]);
|
|
pair_weights.emplace_back(std::max(1, gradient_weights[0] + gradient_weights[1]));
|
|
pair_weights.emplace_back(std::max(1, gradient_weights[0] + gradient_weights[2]));
|
|
pair_weights.emplace_back(std::max(1, gradient_weights[1] + gradient_weights[2]));
|
|
}
|
|
|
|
if (pair_options.size() < 2 || pair_options.size() != pair_weights.size())
|
|
return {};
|
|
|
|
std::vector<unsigned int> pair_ids(pair_options.size(), 0);
|
|
for (size_t idx = 0; idx < pair_ids.size(); ++idx)
|
|
pair_ids[idx] = unsigned(idx + 1);
|
|
|
|
const size_t max_pair_layers =
|
|
mf.local_z_max_sublayers >= 2 ? std::max<size_t>(1, size_t(mf.local_z_max_sublayers) / 2) : size_t(0);
|
|
const std::vector<unsigned int> pair_sequence = build_weighted_gradient_sequence(pair_ids, pair_weights, max_pair_layers);
|
|
if (pair_sequence.empty())
|
|
return {};
|
|
|
|
std::vector<LocalZActivePair> out;
|
|
out.reserve(pair_sequence.size());
|
|
for (const unsigned int pair_token : pair_sequence) {
|
|
if (pair_token < 1 || pair_token > pair_options.size())
|
|
continue;
|
|
out.emplace_back(pair_options[size_t(pair_token - 1)]);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Task 31: Direct multicolor pass heights solver (3+ components).
|
|
// Allocates sub-layer pass heights proportional to component weights,
|
|
// respecting lo/hi bounds. Falls back to uniform if no valid split found.
|
|
static std::vector<double> build_local_z_direct_multicolor_pass_heights(const MixedFilament &mf,
|
|
const std::vector<int> &component_weights,
|
|
double base_height,
|
|
double lower_bound,
|
|
double upper_bound,
|
|
size_t component_count)
|
|
{
|
|
if (base_height <= EPSILON || component_count == 0)
|
|
return {};
|
|
|
|
const double lo = std::max<double>(0.01, lower_bound);
|
|
const double hi = std::max<double>(lo, upper_bound);
|
|
const size_t min_passes = size_t(std::max<double>(1.0, std::ceil((base_height - EPSILON) / hi)));
|
|
const size_t max_passes = size_t(std::max<double>(1.0, std::floor((base_height + EPSILON) / lo)));
|
|
if (max_passes == 0)
|
|
return { base_height };
|
|
|
|
std::vector<int> positive_weights;
|
|
positive_weights.reserve(component_weights.size());
|
|
for (const int weight : component_weights)
|
|
if (weight > 0)
|
|
positive_weights.emplace_back(weight);
|
|
if (positive_weights.empty())
|
|
positive_weights.assign(component_count, 1);
|
|
|
|
const int total_weight = std::max(1, std::accumulate(positive_weights.begin(), positive_weights.end(), 0));
|
|
std::vector<double> component_targets;
|
|
component_targets.reserve(positive_weights.size());
|
|
size_t ideal_passes = 0;
|
|
for (const int weight : positive_weights) {
|
|
const double target = base_height * double(weight) / double(total_weight);
|
|
component_targets.emplace_back(target);
|
|
ideal_passes += size_t(std::max<double>(1.0, std::ceil((target - EPSILON) / hi)));
|
|
}
|
|
|
|
size_t pass_limit = max_passes;
|
|
if (mf.local_z_max_sublayers >= 2)
|
|
pass_limit = std::min(pass_limit, size_t(std::max(2, mf.local_z_max_sublayers)));
|
|
pass_limit = std::max(pass_limit, min_passes);
|
|
|
|
size_t desired_passes = std::clamp(std::max(component_targets.size(), ideal_passes), min_passes, pass_limit);
|
|
|
|
std::vector<double> bins = component_targets;
|
|
while (bins.size() > desired_passes) {
|
|
std::sort(bins.begin(), bins.end());
|
|
const double merged = bins[0] + bins[1];
|
|
bins.erase(bins.begin(), bins.begin() + 2);
|
|
bins.emplace_back(merged);
|
|
}
|
|
|
|
while (bins.size() < desired_passes) {
|
|
auto it = std::max_element(bins.begin(), bins.end());
|
|
if (it == bins.end())
|
|
break;
|
|
const double value = *it;
|
|
if (value < 2.0 * lo - EPSILON)
|
|
break;
|
|
double first = std::clamp(value * 0.5, lo, value - lo);
|
|
double second = value - first;
|
|
if (first < lo - EPSILON || second < lo - EPSILON)
|
|
break;
|
|
*it = first;
|
|
bins.emplace_back(second);
|
|
}
|
|
|
|
if (bins.empty())
|
|
return build_uniform_local_z_pass_heights(base_height, lo, hi, desired_passes);
|
|
|
|
std::sort(bins.begin(), bins.end(), std::greater<double>());
|
|
for (double &value : bins)
|
|
value = std::clamp(value, lo, hi);
|
|
if (fit_pass_heights_to_interval(bins, base_height, lo, hi))
|
|
return bins;
|
|
|
|
for (size_t pass_count = desired_passes; pass_count >= min_passes; --pass_count) {
|
|
std::vector<double> exact = build_uniform_local_z_pass_heights_exact(base_height, lower_bound, upper_bound, pass_count);
|
|
if (!exact.empty())
|
|
return exact;
|
|
if (pass_count == min_passes)
|
|
break;
|
|
}
|
|
|
|
return build_uniform_local_z_pass_heights(base_height, lo, hi, desired_passes);
|
|
}
|
|
|
|
// Task 31: Direct multicolor sequence solver with carry-over.
|
|
// Assigns each pass to a component proportionally using remaining-need scoring.
|
|
// carry_error_mm accumulates the rounding residual across nominal layers.
|
|
static std::vector<unsigned int> build_local_z_direct_multicolor_sequence(const std::vector<unsigned int> &component_ids,
|
|
const std::vector<int> &component_weights,
|
|
const std::vector<double> &pass_heights,
|
|
std::vector<double> &carry_error_mm)
|
|
{
|
|
if (component_ids.empty() || pass_heights.empty())
|
|
return {};
|
|
|
|
std::vector<unsigned int> filtered_ids;
|
|
std::vector<int> filtered_weights;
|
|
filtered_ids.reserve(component_ids.size());
|
|
filtered_weights.reserve(component_ids.size());
|
|
for (size_t idx = 0; idx < component_ids.size(); ++idx) {
|
|
const int weight = idx < component_weights.size() ? std::max(0, component_weights[idx]) : 0;
|
|
if (weight <= 0)
|
|
continue;
|
|
filtered_ids.emplace_back(component_ids[idx]);
|
|
filtered_weights.emplace_back(weight);
|
|
}
|
|
if (filtered_ids.empty()) {
|
|
filtered_ids = component_ids;
|
|
filtered_weights.assign(component_ids.size(), 1);
|
|
}
|
|
if (filtered_ids.empty())
|
|
return {};
|
|
|
|
if (carry_error_mm.size() != filtered_ids.size())
|
|
carry_error_mm.assign(filtered_ids.size(), 0.0);
|
|
|
|
const double total_height = std::accumulate(pass_heights.begin(), pass_heights.end(), 0.0);
|
|
const int total_weight = std::max(1, std::accumulate(filtered_weights.begin(), filtered_weights.end(), 0));
|
|
|
|
std::vector<double> desired_heights(filtered_ids.size(), 0.0);
|
|
for (size_t idx = 0; idx < filtered_ids.size(); ++idx)
|
|
desired_heights[idx] = total_height * double(filtered_weights[idx]) / double(total_weight) + carry_error_mm[idx];
|
|
|
|
std::vector<double> assigned_heights(filtered_ids.size(), 0.0);
|
|
std::vector<unsigned int> sequence;
|
|
sequence.reserve(pass_heights.size());
|
|
int previous_choice = -1;
|
|
|
|
for (const double pass_height : pass_heights) {
|
|
size_t best_idx = 0;
|
|
double best_score = -std::numeric_limits<double>::infinity();
|
|
double best_need = -std::numeric_limits<double>::infinity();
|
|
for (size_t idx = 0; idx < filtered_ids.size(); ++idx) {
|
|
const double remaining_need = desired_heights[idx] - assigned_heights[idx];
|
|
double score = remaining_need;
|
|
if (int(idx) == previous_choice)
|
|
score -= 0.35 * pass_height;
|
|
|
|
if (score > best_score + 1e-9 ||
|
|
(std::abs(score - best_score) <= 1e-9 &&
|
|
(remaining_need > best_need + 1e-9 ||
|
|
(std::abs(remaining_need - best_need) <= 1e-9 && filtered_ids[idx] < filtered_ids[best_idx])))) {
|
|
best_idx = idx;
|
|
best_score = score;
|
|
best_need = remaining_need;
|
|
}
|
|
}
|
|
|
|
assigned_heights[best_idx] += pass_height;
|
|
previous_choice = int(best_idx);
|
|
sequence.emplace_back(filtered_ids[best_idx]);
|
|
}
|
|
|
|
for (size_t idx = 0; idx < filtered_ids.size(); ++idx)
|
|
carry_error_mm[idx] = desired_heights[idx] - assigned_heights[idx];
|
|
|
|
const double error_sum = std::accumulate(carry_error_mm.begin(), carry_error_mm.end(), 0.0);
|
|
if (!carry_error_mm.empty() && std::abs(error_sum) > 1e-9) {
|
|
const double correction = error_sum / double(carry_error_mm.size());
|
|
for (double &value : carry_error_mm)
|
|
value -= correction;
|
|
}
|
|
|
|
return sequence;
|
|
}
|
|
|
|
static LocalZActivePair derive_local_z_active_pair(const MixedFilament &mf,
|
|
const std::vector<LocalZActivePair> &pair_cycle,
|
|
size_t num_physical,
|
|
int cadence_index)
|
|
{
|
|
LocalZActivePair out;
|
|
|
|
if (!pair_cycle.empty()) {
|
|
const int cycle_i = int(pair_cycle.size());
|
|
const size_t pos = size_t(((cadence_index % cycle_i) + cycle_i) % cycle_i);
|
|
return pair_cycle[pos];
|
|
}
|
|
|
|
out.component_a = mf.component_a;
|
|
out.component_b = mf.component_b;
|
|
out.mix_b_percent = std::clamp(mf.mix_b_percent, 0, 100);
|
|
out.uses_layer_cycle_sequence = false;
|
|
return out;
|
|
}
|
|
|
|
static std::vector<ExPolygons> collect_local_z_fixed_state_masks_by_extruder(const std::vector<ExPolygons> &layer_segmentation,
|
|
const size_t num_physical)
|
|
{
|
|
std::vector<ExPolygons> masks_by_extruder(num_physical);
|
|
for (size_t channel_idx = 1; channel_idx < layer_segmentation.size(); ++channel_idx) {
|
|
const ExPolygons &state_masks = layer_segmentation[channel_idx];
|
|
if (state_masks.empty())
|
|
continue;
|
|
|
|
const unsigned int state_id = segmentation_channel_filament_id(channel_idx);
|
|
if (state_id == 0 || state_id > num_physical)
|
|
continue;
|
|
append(masks_by_extruder[state_id - 1], state_masks);
|
|
}
|
|
for (ExPolygons &masks : masks_by_extruder)
|
|
if (masks.size() > 1)
|
|
masks = union_ex(masks);
|
|
return masks_by_extruder;
|
|
}
|
|
|
|
static ExPolygons collect_layer_region_slices(const Layer &layer)
|
|
{
|
|
ExPolygons out;
|
|
for (const LayerRegion *layerm : layer.regions())
|
|
append(out, to_expolygons(layerm->slices.surfaces));
|
|
if (!out.empty())
|
|
out = union_ex(out);
|
|
return out;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// surface_emboss_mixed debug subsystem
|
|
// ---------------------------------------------------------------------------
|
|
// Forward declaration for emboss_surface_mixed_shell_override_delta — defined
|
|
// below the debug subsystem alongside apply_surface_emboss_mixed_region_override.
|
|
static float emboss_surface_mixed_shell_override_delta(const LayerRegion &layerm, const ModelVolume &volume);
|
|
|
|
struct SurfaceEmbossMixedDebugCandidate
|
|
{
|
|
const ModelVolume *volume { nullptr };
|
|
int region_id { -1 };
|
|
};
|
|
|
|
static bool has_surface_emboss_mixed_volume(const PrintObject &print_object)
|
|
{
|
|
const Print *print = print_object.print();
|
|
if (print == nullptr)
|
|
return false;
|
|
|
|
const size_t num_physical = print->config().filament_diameter.size();
|
|
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
|
for (const ModelVolume *volume : print_object.model_object()->volumes)
|
|
if (volume->is_model_part() &&
|
|
volume->emboss_shape.has_value() &&
|
|
volume->emboss_shape->projection.use_surface &&
|
|
mixed_mgr.is_mixed(unsigned(std::max(0, volume->extruder_id())), num_physical))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static std::string surface_emboss_mixed_debug_file_path(const PrintObject &print_object)
|
|
{
|
|
return debug_out_path("emboss-mixed/obj-%d-debug.txt", int(print_object.id().id));
|
|
}
|
|
|
|
static void reset_surface_emboss_mixed_debug_file(const PrintObject &print_object)
|
|
{
|
|
std::ofstream out(surface_emboss_mixed_debug_file_path(print_object), std::ios::out | std::ios::trunc);
|
|
out << "surface emboss mixed debug"
|
|
<< " object_id=" << int(print_object.id().id)
|
|
<< " object_name=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
|
|
<< "\n";
|
|
}
|
|
|
|
static void append_surface_emboss_mixed_debug_line(const PrintObject &print_object, const std::string &line)
|
|
{
|
|
std::ofstream out(surface_emboss_mixed_debug_file_path(print_object), std::ios::out | std::ios::app);
|
|
out << line << '\n';
|
|
}
|
|
|
|
static std::vector<SurfaceEmbossMixedDebugCandidate> collect_surface_emboss_mixed_debug_candidates(
|
|
const Layer &layer,
|
|
const PrintObjectRegions::LayerRangeRegions &layer_range,
|
|
const MixedFilamentManager &mixed_mgr,
|
|
size_t num_physical)
|
|
{
|
|
std::vector<SurfaceEmbossMixedDebugCandidate> out;
|
|
std::vector<int> processed_region_ids;
|
|
processed_region_ids.reserve(layer_range.volume_regions.size());
|
|
|
|
for (const PrintObjectRegions::VolumeRegion &volume_region : layer_range.volume_regions) {
|
|
const ModelVolume *volume = volume_region.model_volume;
|
|
if (volume == nullptr || !volume->is_model_part() || !volume->emboss_shape.has_value() || !volume->emboss_shape->projection.use_surface)
|
|
continue;
|
|
if (volume_region.region == nullptr)
|
|
continue;
|
|
|
|
const int region_id = volume_region.region->print_object_region_id();
|
|
if (region_id < 0 || region_id >= layer.region_count())
|
|
continue;
|
|
if (std::find(processed_region_ids.begin(), processed_region_ids.end(), region_id) != processed_region_ids.end())
|
|
continue;
|
|
processed_region_ids.emplace_back(region_id);
|
|
|
|
if (!mixed_mgr.is_mixed(unsigned(std::max(0, volume_region.region->config().wall_filament.value)), num_physical))
|
|
continue;
|
|
|
|
out.push_back({ volume, region_id });
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
static void export_surface_emboss_mixed_layer_svg(
|
|
const char *stage,
|
|
const PrintObject &print_object,
|
|
size_t layer_id,
|
|
const Layer &layer,
|
|
const std::vector<SurfaceEmbossMixedDebugCandidate> &candidates,
|
|
const ExPolygons *overlay,
|
|
const std::string &overlay_legend)
|
|
{
|
|
std::vector<std::pair<ExPolygons, SVG::ExPolygonAttributes>> items;
|
|
items.reserve(size_t(layer.region_count()) + ((overlay != nullptr && !overlay->empty()) ? 1 : 0));
|
|
|
|
for (int region_id = 0; region_id < layer.region_count(); ++region_id) {
|
|
const LayerRegion *layerm = layer.get_region(region_id);
|
|
if (layerm == nullptr || layerm->slices.empty())
|
|
continue;
|
|
|
|
ExPolygons expolygons = to_expolygons(layerm->slices.surfaces);
|
|
if (expolygons.empty())
|
|
continue;
|
|
|
|
const bool is_candidate = std::find_if(candidates.begin(), candidates.end(), [region_id](const auto &candidate) {
|
|
return candidate.region_id == region_id;
|
|
}) != candidates.end();
|
|
|
|
SVG::ExPolygonAttributes attrs(
|
|
"region " + std::to_string(region_id) + " wall=" + std::to_string(layerm->region().config().wall_filament.value),
|
|
is_candidate ? "#3b82f6" : "#bfc5cc",
|
|
is_candidate ? 0.35f : 0.14f);
|
|
attrs.outline_width = scale_(0.05f);
|
|
attrs.color_contour = is_candidate ? "blue" : "black";
|
|
attrs.color_holes = attrs.color_contour;
|
|
items.emplace_back(std::move(expolygons), std::move(attrs));
|
|
}
|
|
|
|
if (overlay != nullptr && !overlay->empty()) {
|
|
SVG::ExPolygonAttributes attrs(overlay_legend, "#ef4444", 0.28f);
|
|
attrs.outline_width = scale_(0.05f);
|
|
attrs.color_contour = "red";
|
|
attrs.color_holes = "red";
|
|
items.emplace_back(*overlay, std::move(attrs));
|
|
}
|
|
|
|
if (!items.empty())
|
|
SVG::export_expolygons(debug_out_path("emboss-mixed/obj-%d-layer-%03d-%s.svg",
|
|
int(print_object.id().id),
|
|
int(layer_id),
|
|
stage),
|
|
items);
|
|
}
|
|
|
|
static void dump_surface_emboss_mixed_layer_state(
|
|
const char *stage,
|
|
const PrintObject &print_object,
|
|
size_t layer_id,
|
|
const Layer &layer,
|
|
const PrintObjectRegions::LayerRangeRegions &layer_range,
|
|
const std::vector<ExPolygons> *segmentation_layer)
|
|
{
|
|
const Print *print = print_object.print();
|
|
if (print == nullptr)
|
|
return;
|
|
|
|
const size_t num_physical = print->config().filament_diameter.size();
|
|
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
|
const std::vector<SurfaceEmbossMixedDebugCandidate> candidates =
|
|
collect_surface_emboss_mixed_debug_candidates(layer, layer_range, mixed_mgr, num_physical);
|
|
if (candidates.empty())
|
|
return;
|
|
|
|
std::ostringstream header;
|
|
header << std::fixed << std::setprecision(4)
|
|
<< "stage=" << stage
|
|
<< " layer=" << layer_id
|
|
<< " print_z=" << layer.print_z
|
|
<< " slice_z=" << layer.slice_z
|
|
<< " regions=" << layer.region_count()
|
|
<< " candidates=" << candidates.size();
|
|
append_surface_emboss_mixed_debug_line(print_object, header.str());
|
|
|
|
for (int region_id = 0; region_id < layer.region_count(); ++region_id) {
|
|
const LayerRegion *layerm = layer.get_region(region_id);
|
|
if (layerm == nullptr)
|
|
continue;
|
|
const double slice_area = layerm->slices.empty() ? 0.0 : std::abs(area(to_expolygons(layerm->slices.surfaces)));
|
|
std::ostringstream line;
|
|
line << std::fixed << std::setprecision(4)
|
|
<< " region=" << region_id
|
|
<< " wall=" << layerm->region().config().wall_filament.value
|
|
<< " sparse=" << layerm->region().config().sparse_infill_filament.value
|
|
<< " solid=" << layerm->region().config().solid_infill_filament.value
|
|
<< " area=" << slice_area;
|
|
append_surface_emboss_mixed_debug_line(print_object, line.str());
|
|
}
|
|
|
|
for (const SurfaceEmbossMixedDebugCandidate &candidate : candidates) {
|
|
const LayerRegion *layerm = layer.get_region(candidate.region_id);
|
|
if (layerm == nullptr)
|
|
continue;
|
|
|
|
const double slice_area = layerm->slices.empty() ? 0.0 : std::abs(area(to_expolygons(layerm->slices.surfaces)));
|
|
const float shell_delta_scaled = emboss_surface_mixed_shell_override_delta(*layerm, *candidate.volume);
|
|
std::ostringstream line;
|
|
line << std::fixed << std::setprecision(4)
|
|
<< " candidate region=" << candidate.region_id
|
|
<< " volume_name=" << candidate.volume->name
|
|
<< " volume_extruder=" << candidate.volume->extruder_id()
|
|
<< " cfg_wall=" << layerm->region().config().wall_filament.value
|
|
<< " depth=" << float(candidate.volume->emboss_shape->projection.depth)
|
|
<< " shell_delta_mm=" << unscale<double>(shell_delta_scaled)
|
|
<< " area=" << slice_area;
|
|
append_surface_emboss_mixed_debug_line(print_object, line.str());
|
|
|
|
if (segmentation_layer != nullptr) {
|
|
const int cfg_wall = layerm->region().config().wall_filament.value;
|
|
if (cfg_wall >= 1 && cfg_wall <= int(segmentation_layer->size())) {
|
|
const double seg_area = std::abs(area((*segmentation_layer)[size_t(cfg_wall - 1)]));
|
|
std::ostringstream seg_line;
|
|
seg_line << std::fixed << std::setprecision(4)
|
|
<< " segmentation channel=" << cfg_wall
|
|
<< " area=" << seg_area;
|
|
append_surface_emboss_mixed_debug_line(print_object, seg_line.str());
|
|
}
|
|
}
|
|
}
|
|
|
|
export_surface_emboss_mixed_layer_svg(stage, print_object, layer_id, layer, candidates, nullptr, "");
|
|
}
|
|
|
|
static float emboss_surface_mixed_shell_override_delta(const LayerRegion &layerm, const ModelVolume &volume)
|
|
{
|
|
if (!volume.emboss_shape.has_value() || !volume.emboss_shape->projection.use_surface)
|
|
return 0.f;
|
|
|
|
const float depth_mm = std::max(0.f, float(volume.emboss_shape->projection.depth));
|
|
if (depth_mm <= EPSILON)
|
|
return 0.f;
|
|
|
|
const PrintRegionConfig &config = layerm.region().config();
|
|
if (config.wall_loops.value <= 0)
|
|
return 0.f;
|
|
|
|
const Flow ext_flow = layerm.flow(frExternalPerimeter);
|
|
const Flow perimeter_flow = layerm.flow(frPerimeter);
|
|
const coord_t shell_scaled = ext_flow.scaled_width() / 2 +
|
|
ext_flow.scaled_spacing() / 2 +
|
|
std::max(0, config.wall_loops.value - 1) * perimeter_flow.scaled_spacing();
|
|
const float shell_depth_mm = float(unscale<double>(shell_scaled));
|
|
const float delta_mm = std::max(0.f, shell_depth_mm - depth_mm);
|
|
return delta_mm <= EPSILON ? 0.f : scaled<float>(delta_mm);
|
|
}
|
|
|
|
// For each LayerRegion that hosts a use_surface emboss volume painted with a mixed filament,
|
|
// override sibling regions' slices that intersect the emboss footprint so that the mixed-color
|
|
// surface paints on top of the neighbor instead of being trimmed away by the modifier hierarchy.
|
|
template<typename ThrowOnCancel>
|
|
static bool apply_surface_emboss_mixed_region_override(PrintObject &print_object, ThrowOnCancel throw_on_cancel)
|
|
{
|
|
const Print *print = print_object.print();
|
|
if (print == nullptr || print_object.layer_count() == 0 || print_object.shared_regions() == nullptr)
|
|
return false;
|
|
|
|
const size_t num_physical = print->config().filament_diameter.size();
|
|
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
|
const auto &volumes = print_object.model_object()->volumes;
|
|
if (num_physical == 0 ||
|
|
std::find_if(volumes.begin(), volumes.end(), [&mixed_mgr, num_physical](const ModelVolume *volume) {
|
|
return volume->is_model_part() &&
|
|
volume->emboss_shape.has_value() &&
|
|
volume->emboss_shape->projection.use_surface &&
|
|
mixed_mgr.is_mixed(unsigned(std::max(0, volume->extruder_id())), num_physical);
|
|
}) == volumes.end())
|
|
return false;
|
|
|
|
const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
|
|
auto it_layer_range = layer_range_first(layer_ranges, print_object.get_layer(0)->slice_z);
|
|
|
|
size_t changed_layers = 0;
|
|
size_t changed_regions = 0;
|
|
size_t stolen_regions = 0;
|
|
|
|
for (size_t layer_id = 0; layer_id < print_object.layer_count(); ++layer_id) {
|
|
throw_on_cancel();
|
|
|
|
Layer &layer = *print_object.get_layer(int(layer_id));
|
|
it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z);
|
|
const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range;
|
|
const std::vector<SurfaceEmbossMixedDebugCandidate> candidates =
|
|
collect_surface_emboss_mixed_debug_candidates(layer, layer_range, mixed_mgr, num_physical);
|
|
if (!candidates.empty())
|
|
dump_surface_emboss_mixed_layer_state("pre-emboss-override", print_object, layer_id, layer, layer_range);
|
|
|
|
bool layer_changed = false;
|
|
ExPolygons layer_masks;
|
|
std::vector<int> processed_region_ids;
|
|
processed_region_ids.reserve(layer_range.volume_regions.size());
|
|
|
|
for (const PrintObjectRegions::VolumeRegion &volume_region : layer_range.volume_regions) {
|
|
const ModelVolume *volume = volume_region.model_volume;
|
|
if (volume == nullptr || !volume->is_model_part() || !volume->emboss_shape.has_value() || !volume->emboss_shape->projection.use_surface)
|
|
continue;
|
|
if (volume_region.region == nullptr)
|
|
continue;
|
|
|
|
const int region_id = volume_region.region->print_object_region_id();
|
|
if (region_id < 0 || region_id >= layer.region_count())
|
|
continue;
|
|
if (std::find(processed_region_ids.begin(), processed_region_ids.end(), region_id) != processed_region_ids.end())
|
|
continue;
|
|
processed_region_ids.emplace_back(region_id);
|
|
|
|
const unsigned int filament_id = unsigned(std::max(0, volume_region.region->config().wall_filament.value));
|
|
if (!mixed_mgr.is_mixed(filament_id, num_physical))
|
|
continue;
|
|
|
|
LayerRegion *emboss_layerm = layer.get_region(region_id);
|
|
if (emboss_layerm == nullptr || emboss_layerm->slices.empty())
|
|
continue;
|
|
|
|
ExPolygons override_mask = to_expolygons(emboss_layerm->slices.surfaces);
|
|
if (override_mask.empty())
|
|
continue;
|
|
|
|
if (const float delta_scaled = emboss_surface_mixed_shell_override_delta(*emboss_layerm, *volume);
|
|
delta_scaled > float(EPSILON)) {
|
|
override_mask = offset_ex(override_mask, delta_scaled);
|
|
if (override_mask.empty())
|
|
continue;
|
|
if (layer_masks.empty())
|
|
layer_masks = collect_layer_region_slices(layer);
|
|
if (!layer_masks.empty())
|
|
override_mask = intersection_ex(override_mask, layer_masks);
|
|
if (override_mask.empty())
|
|
continue;
|
|
}
|
|
|
|
{
|
|
std::ostringstream line;
|
|
line << std::fixed << std::setprecision(4)
|
|
<< "stage=override-mask"
|
|
<< " layer=" << layer_id
|
|
<< " region=" << region_id
|
|
<< " volume_name=" << volume->name
|
|
<< " volume_extruder=" << volume->extruder_id()
|
|
<< " cfg_wall=" << volume_region.region->config().wall_filament.value
|
|
<< " depth=" << float(volume->emboss_shape->projection.depth)
|
|
<< " shell_delta_mm=" << unscale<double>(emboss_surface_mixed_shell_override_delta(*emboss_layerm, *volume))
|
|
<< " mask_area=" << std::abs(area(override_mask));
|
|
append_surface_emboss_mixed_debug_line(print_object, line.str());
|
|
}
|
|
const std::string overlay_stage = "override-mask-r" + std::to_string(region_id);
|
|
export_surface_emboss_mixed_layer_svg(overlay_stage.c_str(),
|
|
print_object,
|
|
layer_id,
|
|
layer,
|
|
candidates,
|
|
&override_mask,
|
|
"override mask");
|
|
|
|
ExPolygons emboss_slices = to_expolygons(emboss_layerm->slices.surfaces);
|
|
bool emboss_changed = false;
|
|
|
|
for (int target_region_id = 0; target_region_id < layer.region_count(); ++target_region_id) {
|
|
if (target_region_id == region_id)
|
|
continue;
|
|
|
|
LayerRegion *target_layerm = layer.get_region(target_region_id);
|
|
if (target_layerm == nullptr || target_layerm->slices.empty())
|
|
continue;
|
|
if (target_layerm->region().config().wall_filament.value == int(filament_id))
|
|
continue;
|
|
|
|
ExPolygons stolen = intersection_ex(target_layerm->slices.surfaces, override_mask);
|
|
if (stolen.empty())
|
|
continue;
|
|
|
|
append(emboss_slices, stolen);
|
|
emboss_changed = true;
|
|
++stolen_regions;
|
|
|
|
std::ostringstream line;
|
|
line << std::fixed << std::setprecision(4)
|
|
<< "stage=override-steal"
|
|
<< " layer=" << layer_id
|
|
<< " emboss_region=" << region_id
|
|
<< " from_region=" << target_region_id
|
|
<< " from_wall=" << target_layerm->region().config().wall_filament.value
|
|
<< " stolen_area=" << std::abs(area(stolen));
|
|
append_surface_emboss_mixed_debug_line(print_object, line.str());
|
|
|
|
Polygons remaining = diff(to_polygons(target_layerm->slices.surfaces), override_mask);
|
|
if (!remaining.empty())
|
|
remaining = opening(union_ex(remaining), scaled<float>(5. * EPSILON), scaled<float>(5. * EPSILON));
|
|
target_layerm->slices.set(union_ex(remaining), stInternal);
|
|
layer_changed = true;
|
|
}
|
|
|
|
if (emboss_changed) {
|
|
if (emboss_slices.size() > 1)
|
|
emboss_slices = closing_ex(emboss_slices, scaled<float>(10. * EPSILON));
|
|
emboss_layerm->slices.set(std::move(emboss_slices), stInternal);
|
|
++changed_regions;
|
|
layer_changed = true;
|
|
} else {
|
|
std::ostringstream line;
|
|
line << "stage=override-no-steal"
|
|
<< " layer=" << layer_id
|
|
<< " region=" << region_id
|
|
<< " volume_name=" << volume->name;
|
|
append_surface_emboss_mixed_debug_line(print_object, line.str());
|
|
}
|
|
}
|
|
|
|
if (!candidates.empty())
|
|
dump_surface_emboss_mixed_layer_state("post-emboss-override", print_object, layer_id, layer, layer_range);
|
|
|
|
if (layer_changed)
|
|
++changed_layers;
|
|
}
|
|
|
|
if (changed_regions == 0)
|
|
return false;
|
|
|
|
BOOST_LOG_TRIVIAL(warning) << "Surface emboss mixed-region override applied"
|
|
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
|
|
<< " changed_layers=" << changed_layers
|
|
<< " changed_regions=" << changed_regions
|
|
<< " stolen_regions=" << stolen_regions;
|
|
return true;
|
|
}
|
|
|
|
// Clip fixed-state extruder masks for a single pass of a split interval.
|
|
static std::vector<ExPolygons> build_local_z_transition_fixed_masks_for_pass(
|
|
const std::vector<ExPolygons> ¤t_masks_by_extruder,
|
|
const std::vector<ExPolygons> &prev_masks_by_extruder,
|
|
const std::vector<ExPolygons> &next_masks_by_extruder,
|
|
const size_t pass_idx,
|
|
const size_t pass_count)
|
|
{
|
|
if (pass_count <= 1)
|
|
return current_masks_by_extruder;
|
|
|
|
std::vector<ExPolygons> pass_masks_by_extruder(current_masks_by_extruder.size());
|
|
const bool is_lowest_pass = pass_idx == 0;
|
|
const bool is_highest_pass = pass_idx + 1 >= pass_count;
|
|
|
|
for (size_t extruder_idx = 0; extruder_idx < current_masks_by_extruder.size(); ++extruder_idx) {
|
|
const ExPolygons ¤t_masks = current_masks_by_extruder[extruder_idx];
|
|
if (current_masks.empty())
|
|
continue;
|
|
|
|
const ExPolygons prev_masks = extruder_idx < prev_masks_by_extruder.size() ? prev_masks_by_extruder[extruder_idx] : ExPolygons();
|
|
const ExPolygons next_masks = extruder_idx < next_masks_by_extruder.size() ? next_masks_by_extruder[extruder_idx] : ExPolygons();
|
|
|
|
const ExPolygons current_and_prev = prev_masks.empty() ? ExPolygons() : intersection_ex(current_masks, prev_masks);
|
|
const ExPolygons current_and_next = next_masks.empty() ? ExPolygons() : intersection_ex(current_masks, next_masks);
|
|
const ExPolygons persistent =
|
|
current_and_prev.empty() || current_and_next.empty() ? ExPolygons() : intersection_ex(current_and_prev, current_and_next);
|
|
|
|
ExPolygons entering = current_and_next;
|
|
if (!entering.empty() && !current_and_prev.empty())
|
|
entering = diff_ex(entering, current_and_prev);
|
|
|
|
ExPolygons exiting = current_and_prev;
|
|
if (!exiting.empty() && !current_and_next.empty())
|
|
exiting = diff_ex(exiting, current_and_next);
|
|
|
|
ExPolygons covered;
|
|
if (!persistent.empty())
|
|
append(covered, persistent);
|
|
if (!entering.empty())
|
|
append(covered, entering);
|
|
if (!exiting.empty())
|
|
append(covered, exiting);
|
|
if (covered.size() > 1)
|
|
covered = union_ex(covered);
|
|
|
|
const ExPolygons isolated = covered.empty() ? current_masks : diff_ex(current_masks, covered);
|
|
|
|
ExPolygons assigned;
|
|
if (!persistent.empty())
|
|
append(assigned, persistent);
|
|
if (is_lowest_pass && !exiting.empty())
|
|
append(assigned, exiting);
|
|
if (is_highest_pass) {
|
|
if (!entering.empty())
|
|
append(assigned, entering);
|
|
if (!isolated.empty())
|
|
append(assigned, isolated);
|
|
}
|
|
if (assigned.size() > 1)
|
|
assigned = union_ex(assigned);
|
|
pass_masks_by_extruder[extruder_idx] = std::move(assigned);
|
|
}
|
|
|
|
return pass_masks_by_extruder;
|
|
}
|
|
|
|
// --- Task 30: whole-object segmentation merge ---
|
|
|
|
// Rasterize wall_filament assignments from LayerRegion into a per-layer segmentation channel map.
|
|
// Only layers with mixed-filament assignments are populated.
|
|
static std::vector<std::vector<ExPolygons>> whole_object_local_z_segmentation_by_mixed_wall(const PrintObject &print_object)
|
|
{
|
|
std::vector<std::vector<ExPolygons>> segmentation;
|
|
|
|
const Print *print = print_object.print();
|
|
if (print == nullptr || print_object.layer_count() == 0)
|
|
return segmentation;
|
|
|
|
const size_t num_physical = print->config().filament_colour.size();
|
|
const size_t num_total = print->mixed_filament_manager().total_filaments(num_physical);
|
|
if (num_total <= num_physical)
|
|
return segmentation;
|
|
|
|
segmentation.assign(print_object.layer_count(), std::vector<ExPolygons>(num_total + 1));
|
|
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
|
size_t mixed_region_layers = 0;
|
|
size_t mixed_region_count = 0;
|
|
|
|
for (size_t layer_id = 0; layer_id < print_object.layer_count(); ++layer_id) {
|
|
const Layer &layer = *print_object.get_layer(int(layer_id));
|
|
bool layer_has_mixed_region = false;
|
|
for (int region_id = 0; region_id < layer.region_count(); ++region_id) {
|
|
const LayerRegion *layerm = layer.get_region(region_id);
|
|
if (layerm == nullptr || layerm->slices.empty())
|
|
continue;
|
|
|
|
const unsigned int filament_id = unsigned(std::max(0, layerm->region().config().wall_filament.value));
|
|
if (!mixed_mgr.is_mixed(filament_id, num_physical))
|
|
continue;
|
|
if (filament_id >= segmentation[layer_id].size())
|
|
continue;
|
|
|
|
ExPolygons state_masks = to_expolygons(layerm->slices.surfaces);
|
|
if (state_masks.empty())
|
|
continue;
|
|
|
|
append(segmentation[layer_id][filament_id], std::move(state_masks));
|
|
layer_has_mixed_region = true;
|
|
++mixed_region_count;
|
|
}
|
|
|
|
if (layer_has_mixed_region) {
|
|
++mixed_region_layers;
|
|
for (size_t channel_idx = num_physical + 1; channel_idx < segmentation[layer_id].size(); ++channel_idx) {
|
|
ExPolygons &state_masks = segmentation[layer_id][channel_idx];
|
|
if (state_masks.size() > 1)
|
|
state_masks = union_ex(state_masks);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mixed_region_count == 0)
|
|
return {};
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "Local-Z whole-object wall segmentation prepared"
|
|
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
|
|
<< " mixed_region_layers=" << mixed_region_layers
|
|
<< " mixed_region_count=" << mixed_region_count
|
|
<< " physical_filaments=" << num_physical
|
|
<< " total_filaments=" << num_total;
|
|
return segmentation;
|
|
}
|
|
|
|
// Merge painted mm_segmentation with whole-object wall masks; painted areas dominate.
|
|
// Gate: dithering_local_z_whole_objects=true.
|
|
static std::vector<std::vector<ExPolygons>> local_z_planner_segmentation_with_whole_object_mixed_wall(
|
|
const PrintObject &print_object,
|
|
const std::vector<std::vector<ExPolygons>> &paint_segmentation)
|
|
{
|
|
const Print *print = print_object.print();
|
|
if (print == nullptr || paint_segmentation.empty())
|
|
return paint_segmentation;
|
|
|
|
std::vector<std::vector<ExPolygons>> augmented = whole_object_local_z_segmentation_by_mixed_wall(print_object);
|
|
if (augmented.empty())
|
|
return paint_segmentation;
|
|
|
|
const size_t num_physical = print->config().filament_colour.size();
|
|
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
|
size_t overlay_layers = 0;
|
|
size_t overlay_mixed_channels = 0;
|
|
size_t physical_override_layers = 0;
|
|
|
|
for (size_t layer_id = 0; layer_id < augmented.size() && layer_id < paint_segmentation.size(); ++layer_id) {
|
|
if (augmented[layer_id].size() < paint_segmentation[layer_id].size())
|
|
augmented[layer_id].resize(paint_segmentation[layer_id].size());
|
|
|
|
ExPolygons painted_overrides;
|
|
for (size_t channel_idx = 1; channel_idx < paint_segmentation[layer_id].size(); ++channel_idx) {
|
|
const ExPolygons &state_masks = paint_segmentation[layer_id][channel_idx];
|
|
if (!state_masks.empty())
|
|
append(painted_overrides, state_masks);
|
|
}
|
|
if (painted_overrides.size() > 1)
|
|
painted_overrides = union_ex(painted_overrides);
|
|
|
|
bool layer_has_overlay = false;
|
|
if (!painted_overrides.empty()) {
|
|
bool clipped_for_physical_override = false;
|
|
for (size_t channel_idx = num_physical + 1; channel_idx < augmented[layer_id].size(); ++channel_idx) {
|
|
ExPolygons &state_masks = augmented[layer_id][channel_idx];
|
|
if (state_masks.empty())
|
|
continue;
|
|
const ExPolygons clipped_masks = diff_ex(state_masks, painted_overrides);
|
|
if (clipped_masks.size() != state_masks.size())
|
|
clipped_for_physical_override = true;
|
|
state_masks = clipped_masks;
|
|
}
|
|
if (clipped_for_physical_override)
|
|
++physical_override_layers;
|
|
layer_has_overlay = true;
|
|
}
|
|
|
|
for (size_t channel_idx = 1; channel_idx < paint_segmentation[layer_id].size(); ++channel_idx) {
|
|
const ExPolygons &state_masks = paint_segmentation[layer_id][channel_idx];
|
|
if (state_masks.empty())
|
|
continue;
|
|
|
|
const unsigned int state_id = segmentation_channel_filament_id(channel_idx);
|
|
if (channel_idx >= augmented[layer_id].size())
|
|
augmented[layer_id].resize(channel_idx + 1);
|
|
|
|
append(augmented[layer_id][channel_idx], state_masks);
|
|
layer_has_overlay = true;
|
|
if (mixed_mgr.is_mixed(state_id, num_physical))
|
|
++overlay_mixed_channels;
|
|
}
|
|
|
|
for (size_t channel_idx = num_physical + 1; channel_idx < augmented[layer_id].size(); ++channel_idx) {
|
|
ExPolygons &state_masks = augmented[layer_id][channel_idx];
|
|
if (state_masks.size() > 1)
|
|
state_masks = union_ex(state_masks);
|
|
}
|
|
if (layer_has_overlay)
|
|
++overlay_layers;
|
|
}
|
|
|
|
if (overlay_layers > 0) {
|
|
BOOST_LOG_TRIVIAL(info) << "Local-Z planner merged whole-object mixed wall masks with painted overrides"
|
|
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
|
|
<< " overlay_layers=" << overlay_layers
|
|
<< " overlay_mixed_channels=" << overlay_mixed_channels
|
|
<< " physical_override_layers=" << physical_override_layers;
|
|
}
|
|
return augmented;
|
|
}
|
|
|
|
// --- main plan builder ---
|
|
|
|
// Returns a pointillism sequence for mf (currently disabled / returns empty).
|
|
static std::vector<unsigned int> pointillism_sequence_for_row_lz(const MixedFilament &mf, size_t num_physical)
|
|
{
|
|
(void)mf; (void)num_physical;
|
|
return {};
|
|
}
|
|
|
|
template<typename ThrowOnCancel>
|
|
static void build_local_z_plan(PrintObject &print_object, const std::vector<std::vector<ExPolygons>> &segmentation, ThrowOnCancel throw_on_cancel)
|
|
{
|
|
print_object.clear_local_z_plan();
|
|
|
|
const Print *print = print_object.print();
|
|
const std::string object_name = print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>");
|
|
if (print == nullptr || print_object.layer_count() == 0 || segmentation.size() != print_object.layer_count()) {
|
|
BOOST_LOG_TRIVIAL(debug) << "Local-Z plan skipped: invalid preconditions"
|
|
<< " object=" << object_name;
|
|
return;
|
|
}
|
|
|
|
const DynamicPrintConfig &full_cfg = print->full_print_config();
|
|
const PrintConfig &print_cfg = print->config();
|
|
const bool local_z_mode = bool_from_full_config(full_cfg, "dithering_local_z_mode", print_cfg.dithering_local_z_mode.value);
|
|
const bool local_z_whole_objects =
|
|
bool_from_full_config(full_cfg, "dithering_local_z_whole_objects", print_cfg.dithering_local_z_whole_objects.value);
|
|
const bool local_z_direct_multicolor =
|
|
bool_from_full_config(full_cfg, "dithering_local_z_direct_multicolor",
|
|
print_cfg.dithering_local_z_direct_multicolor.value);
|
|
if (!local_z_mode) {
|
|
BOOST_LOG_TRIVIAL(debug) << "Local-Z plan skipped: mode disabled"
|
|
<< " object=" << object_name;
|
|
return;
|
|
}
|
|
coordf_t mixed_lower = float_from_full_config(full_cfg, "mixed_filament_height_lower_bound",
|
|
coordf_t(print_cfg.mixed_filament_height_lower_bound.value));
|
|
coordf_t mixed_upper = float_from_full_config(full_cfg, "mixed_filament_height_upper_bound",
|
|
coordf_t(print_cfg.mixed_filament_height_upper_bound.value));
|
|
coordf_t preferred_a = float_from_full_config(full_cfg, "mixed_color_layer_height_a",
|
|
coordf_t(print_cfg.mixed_color_layer_height_a.value));
|
|
coordf_t preferred_b = float_from_full_config(full_cfg, "mixed_color_layer_height_b",
|
|
coordf_t(print_cfg.mixed_color_layer_height_b.value));
|
|
mixed_lower = std::max<coordf_t>(0.01f, mixed_lower);
|
|
mixed_upper = std::max<coordf_t>(mixed_lower, mixed_upper);
|
|
preferred_a = std::max<coordf_t>(0.f, preferred_a);
|
|
preferred_b = std::max<coordf_t>(0.f, preferred_b);
|
|
|
|
const size_t num_physical = print_cfg.filament_colour.size();
|
|
if (num_physical == 0) {
|
|
BOOST_LOG_TRIVIAL(warning) << "Local-Z plan skipped: no physical filaments"
|
|
<< " object=" << object_name;
|
|
return;
|
|
}
|
|
|
|
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
|
const auto &mixed_rows = mixed_mgr.mixed_filaments();
|
|
|
|
// Direct multicolor rows (3+ components): stubs for Task 31 — treat as pair cadence.
|
|
std::vector<std::vector<unsigned int>> row_direct_component_ids(mixed_rows.size());
|
|
std::vector<std::vector<int>> row_direct_component_weights(mixed_rows.size());
|
|
std::vector<std::vector<double>> row_direct_component_error_mm(mixed_rows.size());
|
|
std::vector<uint8_t> row_uses_direct_multicolor_solver(mixed_rows.size(), uint8_t(0));
|
|
if (local_z_direct_multicolor && preferred_a <= EPSILON && preferred_b <= EPSILON) {
|
|
for (size_t row_idx = 0; row_idx < mixed_rows.size(); ++row_idx) {
|
|
if (local_z_direct_multicolor_row(mixed_rows[row_idx],
|
|
num_physical,
|
|
&row_direct_component_ids[row_idx],
|
|
&row_direct_component_weights[row_idx])) {
|
|
row_uses_direct_multicolor_solver[row_idx] = uint8_t(1);
|
|
row_direct_component_error_mm[row_idx].assign(row_direct_component_ids[row_idx].size(), 0.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pointillism rows (same-layer stripe): skip Local-Z if active.
|
|
std::vector<uint8_t> pointillism_row_eligible(mixed_rows.size(), uint8_t(0));
|
|
for (size_t row_idx = 0; row_idx < mixed_rows.size(); ++row_idx) {
|
|
const std::vector<unsigned int> sequence = pointillism_sequence_for_row_lz(mixed_rows[row_idx], num_physical);
|
|
if (unique_extruder_count(sequence, num_physical) >= 2)
|
|
pointillism_row_eligible[row_idx] = uint8_t(1);
|
|
}
|
|
|
|
size_t pointillism_rows = 0;
|
|
if (!pointillism_row_eligible.empty()) {
|
|
std::vector<uint8_t> pointillism_row_active(pointillism_row_eligible.size(), uint8_t(0));
|
|
for (size_t layer_id = 0; layer_id < segmentation.size(); ++layer_id) {
|
|
const auto &layer_segmentation = segmentation[layer_id];
|
|
for (size_t channel_idx = 1; channel_idx < layer_segmentation.size(); ++channel_idx) {
|
|
if (layer_segmentation[channel_idx].empty())
|
|
continue;
|
|
const unsigned int state_id = segmentation_channel_filament_id(channel_idx);
|
|
if (!mixed_mgr.is_mixed(state_id, num_physical))
|
|
continue;
|
|
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
|
|
if (mixed_idx < 0 || size_t(mixed_idx) >= pointillism_row_eligible.size())
|
|
continue;
|
|
const size_t row_idx = size_t(mixed_idx);
|
|
if (pointillism_row_eligible[row_idx] == 0 || pointillism_row_active[row_idx] != 0)
|
|
continue;
|
|
pointillism_row_active[row_idx] = uint8_t(1);
|
|
++pointillism_rows;
|
|
}
|
|
}
|
|
}
|
|
if (pointillism_rows > 0) {
|
|
BOOST_LOG_TRIVIAL(warning) << "Local-Z plan skipped: interleaved stripe mixed pattern active"
|
|
<< " object=" << object_name
|
|
<< " interleaved_rows=" << pointillism_rows;
|
|
return;
|
|
}
|
|
|
|
// Build pair-cadence cycles for each non-direct-multicolor row.
|
|
std::vector<std::vector<LocalZActivePair>> row_pair_cycles(mixed_rows.size());
|
|
std::vector<uint8_t> row_uses_layer_cycle_pair(mixed_rows.size(), uint8_t(0));
|
|
for (size_t row_idx = 0; row_idx < mixed_rows.size(); ++row_idx) {
|
|
if (row_uses_direct_multicolor_solver[row_idx] != 0)
|
|
continue;
|
|
row_pair_cycles[row_idx] = build_local_z_pair_cycle_for_row(mixed_rows[row_idx], num_physical);
|
|
if (!row_pair_cycles[row_idx].empty())
|
|
row_uses_layer_cycle_pair[row_idx] = uint8_t(1);
|
|
}
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "Local-Z plan start"
|
|
<< " object=" << object_name
|
|
<< " layers=" << print_object.layer_count()
|
|
<< " mixed_lower=" << mixed_lower
|
|
<< " mixed_upper=" << mixed_upper
|
|
<< " preferred_a=" << preferred_a
|
|
<< " preferred_b=" << preferred_b
|
|
<< " direct_multicolor=" << (local_z_direct_multicolor ? 1 : 0)
|
|
<< " physical_filaments=" << num_physical;
|
|
|
|
std::vector<LocalZInterval> intervals;
|
|
std::vector<SubLayerPlan> plans;
|
|
intervals.reserve(print_object.layer_count());
|
|
|
|
size_t mixed_intervals = 0;
|
|
size_t split_intervals = 0;
|
|
size_t non_split_mixed_intervals = 0;
|
|
size_t total_generated_sublayer_cnt = 0;
|
|
size_t total_mixed_state_layers = 0;
|
|
size_t forced_height_resolve_calls = 0;
|
|
size_t forced_height_resolve_invalid_target = 0;
|
|
size_t split_passes_total = 0;
|
|
size_t split_passes_with_painted_masks = 0;
|
|
size_t split_intervals_without_painted_masks = 0;
|
|
size_t strict_ab_assignments = 0;
|
|
size_t alternating_height_intervals = 0;
|
|
size_t shared_multi_row_fallback_intervals = 0;
|
|
constexpr size_t LOCAL_Z_MAX_ISOLATED_ACTIVE_ROWS = 2;
|
|
constexpr size_t LOCAL_Z_MAX_ISOLATED_MASK_COMPONENTS = 24;
|
|
constexpr size_t LOCAL_Z_MAX_ISOLATED_MASK_VERTICES = 1200;
|
|
constexpr bool LOCAL_Z_SHARED_FALLBACK_ENABLED = false;
|
|
|
|
std::vector<int> row_cadence_index(mixed_rows.size(), 0);
|
|
std::vector<int> row_layer_cycle_index(mixed_rows.size(), 0);
|
|
std::vector<uint8_t> row_active_prev_layer(mixed_rows.size(), uint8_t(0));
|
|
|
|
for (size_t layer_id = 0; layer_id < print_object.layer_count(); ++layer_id) {
|
|
throw_on_cancel();
|
|
|
|
const Layer &layer = *print_object.get_layer(int(layer_id));
|
|
LocalZInterval interval;
|
|
interval.layer_id = layer_id;
|
|
interval.z_lo = layer.print_z - layer.height;
|
|
interval.z_hi = layer.print_z;
|
|
interval.base_height = layer.height;
|
|
interval.sublayer_height = layer.height;
|
|
interval.first_sublayer_idx = plans.size();
|
|
|
|
ExPolygons mixed_masks;
|
|
size_t mixed_state_count = 0;
|
|
std::vector<uint8_t> row_active_this_layer(mixed_rows.size(), uint8_t(0));
|
|
size_t dominant_mixed_idx = size_t(-1);
|
|
double dominant_mixed_area = -1.0;
|
|
double dominant_gradient_h_a = 0.0;
|
|
double dominant_gradient_h_b = 0.0;
|
|
bool dominant_gradient_valid = false;
|
|
|
|
for (size_t channel_idx = 0; channel_idx < segmentation[layer_id].size(); ++channel_idx) {
|
|
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
|
|
if (state_masks.empty())
|
|
continue;
|
|
const unsigned int state_id = segmentation_channel_filament_id(channel_idx);
|
|
if (!mixed_mgr.is_mixed(state_id, num_physical))
|
|
continue;
|
|
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
|
|
if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size())
|
|
continue;
|
|
const MixedFilament &mf = mixed_rows[size_t(mixed_idx)];
|
|
if (!local_z_eligible_mixed_row(mf))
|
|
continue;
|
|
|
|
interval.has_mixed_paint = true;
|
|
row_active_this_layer[size_t(mixed_idx)] = uint8_t(1);
|
|
++mixed_state_count;
|
|
append(mixed_masks, state_masks);
|
|
|
|
const double mixed_area = std::abs(area(state_masks));
|
|
if (mixed_area > dominant_mixed_area) {
|
|
dominant_mixed_area = mixed_area;
|
|
dominant_mixed_idx = size_t(mixed_idx);
|
|
}
|
|
}
|
|
|
|
for (size_t row_idx = 0; row_idx < row_active_this_layer.size(); ++row_idx) {
|
|
if (row_active_this_layer[row_idx] != 0 && row_active_prev_layer[row_idx] == 0) {
|
|
if (row_uses_direct_multicolor_solver[row_idx] != 0 &&
|
|
row_direct_component_error_mm[row_idx].size() == row_direct_component_ids[row_idx].size())
|
|
std::fill(row_direct_component_error_mm[row_idx].begin(), row_direct_component_error_mm[row_idx].end(), 0.0);
|
|
|
|
const bool can_sync_to_dominant =
|
|
local_z_whole_objects &&
|
|
dominant_mixed_idx < mixed_rows.size() &&
|
|
dominant_mixed_idx != row_idx &&
|
|
row_active_this_layer[dominant_mixed_idx] != 0;
|
|
if (can_sync_to_dominant) {
|
|
row_cadence_index[row_idx] = row_cadence_index[dominant_mixed_idx];
|
|
row_layer_cycle_index[row_idx] = row_layer_cycle_index[dominant_mixed_idx];
|
|
} else {
|
|
row_cadence_index[row_idx] = 0;
|
|
row_layer_cycle_index[row_idx] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<LocalZActivePair> row_active_pairs(mixed_rows.size());
|
|
for (size_t row_idx = 0; row_idx < row_active_this_layer.size(); ++row_idx) {
|
|
if (row_active_this_layer[row_idx] == 0 || !local_z_eligible_mixed_row(mixed_rows[row_idx]))
|
|
continue;
|
|
if (row_uses_direct_multicolor_solver[row_idx] != 0)
|
|
continue;
|
|
|
|
const int cadence_index = row_uses_layer_cycle_pair[row_idx] != 0
|
|
? row_layer_cycle_index[row_idx]
|
|
: row_cadence_index[row_idx];
|
|
row_active_pairs[row_idx] =
|
|
derive_local_z_active_pair(mixed_rows[row_idx], row_pair_cycles[row_idx], num_physical, cadence_index);
|
|
}
|
|
|
|
if (dominant_mixed_idx < mixed_rows.size()) {
|
|
const LocalZActivePair &dominant_pair = row_active_pairs[dominant_mixed_idx];
|
|
const int dominant_mix_b_percent =
|
|
dominant_pair.valid_pair(num_physical) ? dominant_pair.mix_b_percent : mixed_rows[dominant_mixed_idx].mix_b_percent;
|
|
if (row_uses_direct_multicolor_solver[dominant_mixed_idx] == 0) {
|
|
compute_local_z_gradient_component_heights(dominant_mix_b_percent, mixed_lower, mixed_upper,
|
|
dominant_gradient_h_a, dominant_gradient_h_b);
|
|
dominant_gradient_valid = true;
|
|
}
|
|
}
|
|
|
|
total_mixed_state_layers += mixed_state_count;
|
|
if (!mixed_masks.empty())
|
|
mixed_masks = union_ex(mixed_masks);
|
|
if (interval.has_mixed_paint)
|
|
++mixed_intervals;
|
|
|
|
const ExPolygons layer_masks = collect_layer_region_slices(layer);
|
|
ExPolygons base_masks = layer_masks;
|
|
if (interval.has_mixed_paint && !base_masks.empty() && !mixed_masks.empty()) {
|
|
base_masks = diff_ex(base_masks, mixed_masks);
|
|
if (!base_masks.empty()) {
|
|
const Polygons filtered = opening(to_polygons(base_masks), scaled<float>(5. * EPSILON), scaled<float>(5. * EPSILON));
|
|
base_masks = union_ex(filtered);
|
|
}
|
|
}
|
|
|
|
const size_t active_mixed_rows = size_t(std::count(row_active_this_layer.begin(), row_active_this_layer.end(), uint8_t(1)));
|
|
std::vector<ExPolygons> row_state_masks(mixed_rows.size());
|
|
std::vector<unsigned int> row_state_ids(mixed_rows.size(), 0);
|
|
std::vector<ExPolygons> fixed_state_masks_by_extruder(num_physical);
|
|
|
|
for (size_t channel_idx = 0; channel_idx < segmentation[layer_id].size(); ++channel_idx) {
|
|
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
|
|
if (state_masks.empty())
|
|
continue;
|
|
const unsigned int state_id = segmentation_channel_filament_id(channel_idx);
|
|
if (state_id >= 1 && state_id <= num_physical) {
|
|
append(fixed_state_masks_by_extruder[state_id - 1], state_masks);
|
|
continue;
|
|
}
|
|
if (!mixed_mgr.is_mixed(state_id, num_physical))
|
|
continue;
|
|
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
|
|
if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size())
|
|
continue;
|
|
const size_t row_idx = size_t(mixed_idx);
|
|
if (row_active_this_layer[row_idx] == 0 || !local_z_eligible_mixed_row(mixed_rows[row_idx]))
|
|
continue;
|
|
row_state_ids[row_idx] = state_id;
|
|
append(row_state_masks[row_idx], state_masks);
|
|
}
|
|
for (ExPolygons &masks : row_state_masks)
|
|
if (masks.size() > 1)
|
|
masks = union_ex(masks);
|
|
for (ExPolygons &masks : fixed_state_masks_by_extruder)
|
|
if (masks.size() > 1)
|
|
masks = union_ex(masks);
|
|
|
|
const std::vector<ExPolygons> prev_fixed_state_masks_by_extruder =
|
|
layer_id > 0 ? collect_local_z_fixed_state_masks_by_extruder(segmentation[layer_id - 1], num_physical)
|
|
: std::vector<ExPolygons>(num_physical);
|
|
const std::vector<ExPolygons> next_fixed_state_masks_by_extruder =
|
|
layer_id + 1 < segmentation.size() ? collect_local_z_fixed_state_masks_by_extruder(segmentation[layer_id + 1], num_physical)
|
|
: std::vector<ExPolygons>(num_physical);
|
|
|
|
ExPolygons fixed_state_masks_union;
|
|
for (const ExPolygons &masks : fixed_state_masks_by_extruder)
|
|
if (!masks.empty())
|
|
append(fixed_state_masks_union, masks);
|
|
if (fixed_state_masks_union.size() > 1)
|
|
fixed_state_masks_union = union_ex(fixed_state_masks_union);
|
|
|
|
if (interval.has_mixed_paint && local_z_whole_objects && !fixed_state_masks_union.empty()) {
|
|
if (!base_masks.empty()) {
|
|
base_masks = diff_ex(base_masks, fixed_state_masks_union);
|
|
if (!base_masks.empty()) {
|
|
const Polygons filtered = opening(to_polygons(base_masks), scaled<float>(5. * EPSILON), scaled<float>(5. * EPSILON));
|
|
base_masks = union_ex(filtered);
|
|
}
|
|
}
|
|
append(mixed_masks, fixed_state_masks_union);
|
|
if (mixed_masks.size() > 1)
|
|
mixed_masks = union_ex(mixed_masks);
|
|
}
|
|
|
|
if (local_z_whole_objects && !fixed_state_masks_union.empty()) {
|
|
constexpr double LOCAL_Z_WHOLE_OBJECT_FIXED_GUARD_MM = 0.10;
|
|
ExPolygons fixed_state_guard_masks = offset_ex(fixed_state_masks_union, float(scale_(LOCAL_Z_WHOLE_OBJECT_FIXED_GUARD_MM)));
|
|
if (fixed_state_guard_masks.empty())
|
|
fixed_state_guard_masks = fixed_state_masks_union;
|
|
else if (fixed_state_guard_masks.size() > 1)
|
|
fixed_state_guard_masks = union_ex(fixed_state_guard_masks);
|
|
|
|
for (ExPolygons &masks : row_state_masks) {
|
|
if (masks.empty())
|
|
continue;
|
|
masks = diff_ex(masks, fixed_state_guard_masks);
|
|
if (masks.size() > 1)
|
|
masks = union_ex(masks);
|
|
}
|
|
}
|
|
|
|
size_t active_row_mask_components = 0;
|
|
size_t active_row_mask_vertices = 0;
|
|
for (size_t row_idx = 0; row_idx < row_state_masks.size(); ++row_idx)
|
|
if (row_active_this_layer[row_idx] != 0) {
|
|
active_row_mask_components += row_state_masks[row_idx].size();
|
|
for (const ExPolygon &expoly : row_state_masks[row_idx]) {
|
|
active_row_mask_vertices += expoly.contour.points.size();
|
|
for (const Polygon &hole : expoly.holes)
|
|
active_row_mask_vertices += hole.points.size();
|
|
}
|
|
}
|
|
|
|
std::vector<std::vector<double>> isolated_row_pass_heights(mixed_rows.size());
|
|
bool isolated_multi_row_mode = false;
|
|
const bool shared_multi_row_fallback =
|
|
LOCAL_Z_SHARED_FALLBACK_ENABLED &&
|
|
interval.has_mixed_paint &&
|
|
preferred_a <= EPSILON &&
|
|
preferred_b <= EPSILON &&
|
|
active_mixed_rows > 1 &&
|
|
(active_mixed_rows > LOCAL_Z_MAX_ISOLATED_ACTIVE_ROWS ||
|
|
active_row_mask_components > LOCAL_Z_MAX_ISOLATED_MASK_COMPONENTS ||
|
|
active_row_mask_vertices > LOCAL_Z_MAX_ISOLATED_MASK_VERTICES);
|
|
if (shared_multi_row_fallback)
|
|
++shared_multi_row_fallback_intervals;
|
|
|
|
if (interval.has_mixed_paint &&
|
|
preferred_a <= EPSILON &&
|
|
preferred_b <= EPSILON &&
|
|
!shared_multi_row_fallback &&
|
|
active_mixed_rows > 1) {
|
|
size_t isolated_rows_with_split = 0;
|
|
for (size_t row_idx = 0; row_idx < row_active_this_layer.size(); ++row_idx) {
|
|
if (row_active_this_layer[row_idx] == 0)
|
|
continue;
|
|
|
|
std::vector<double> row_passes;
|
|
if (row_uses_direct_multicolor_solver[row_idx] != 0) {
|
|
row_passes = build_local_z_direct_multicolor_pass_heights(mixed_rows[row_idx],
|
|
row_direct_component_weights[row_idx],
|
|
interval.base_height,
|
|
mixed_lower,
|
|
mixed_upper,
|
|
row_direct_component_ids[row_idx].size());
|
|
} else {
|
|
double row_h_a = 0.0;
|
|
double row_h_b = 0.0;
|
|
const LocalZActivePair &active_pair = row_active_pairs[row_idx];
|
|
const int row_mix_b_percent =
|
|
active_pair.valid_pair(num_physical) ? active_pair.mix_b_percent : mixed_rows[row_idx].mix_b_percent;
|
|
compute_local_z_gradient_component_heights(row_mix_b_percent, mixed_lower, mixed_upper, row_h_a, row_h_b);
|
|
row_passes = active_pair.uses_layer_cycle_sequence
|
|
? build_local_z_two_pass_heights(interval.base_height, mixed_lower, mixed_upper, row_h_a, row_h_b)
|
|
: build_local_z_alternating_pass_heights(interval.base_height,
|
|
mixed_lower,
|
|
mixed_upper,
|
|
row_h_a,
|
|
row_h_b);
|
|
}
|
|
if (row_passes.empty())
|
|
row_passes.emplace_back(interval.base_height);
|
|
if (!sanitize_local_z_pass_heights(row_passes, interval.base_height, mixed_lower, mixed_upper))
|
|
row_passes = build_uniform_local_z_pass_heights(interval.base_height, mixed_lower, mixed_upper);
|
|
if (row_passes.size() > 1)
|
|
++isolated_rows_with_split;
|
|
isolated_row_pass_heights[row_idx] = std::move(row_passes);
|
|
}
|
|
if (isolated_rows_with_split > 0) {
|
|
isolated_multi_row_mode = true;
|
|
++alternating_height_intervals;
|
|
}
|
|
}
|
|
|
|
std::vector<double> pass_heights;
|
|
if (interval.has_mixed_paint && !isolated_multi_row_mode) {
|
|
if (preferred_a <= EPSILON && preferred_b <= EPSILON) {
|
|
if (shared_multi_row_fallback) {
|
|
pass_heights = build_local_z_shared_pass_heights(interval.base_height, mixed_lower, mixed_upper);
|
|
if (pass_heights.size() > 1)
|
|
++alternating_height_intervals;
|
|
} else if (dominant_mixed_idx < mixed_rows.size() &&
|
|
row_uses_direct_multicolor_solver[dominant_mixed_idx] != 0) {
|
|
pass_heights = build_local_z_direct_multicolor_pass_heights(mixed_rows[dominant_mixed_idx],
|
|
row_direct_component_weights[dominant_mixed_idx],
|
|
interval.base_height,
|
|
mixed_lower,
|
|
mixed_upper,
|
|
row_direct_component_ids[dominant_mixed_idx].size());
|
|
if (pass_heights.size() > 1)
|
|
++alternating_height_intervals;
|
|
} else if (dominant_gradient_valid) {
|
|
const bool dominant_uses_pair_cycle =
|
|
dominant_mixed_idx < mixed_rows.size() && row_active_pairs[dominant_mixed_idx].uses_layer_cycle_sequence;
|
|
pass_heights = dominant_uses_pair_cycle
|
|
? build_local_z_two_pass_heights(interval.base_height, mixed_lower, mixed_upper,
|
|
dominant_gradient_h_a, dominant_gradient_h_b)
|
|
: build_local_z_alternating_pass_heights(interval.base_height,
|
|
mixed_lower,
|
|
mixed_upper,
|
|
dominant_gradient_h_a,
|
|
dominant_gradient_h_b);
|
|
if (pass_heights.size() > 1)
|
|
++alternating_height_intervals;
|
|
} else {
|
|
pass_heights = build_uniform_local_z_pass_heights(interval.base_height, mixed_lower, mixed_upper);
|
|
}
|
|
} else {
|
|
pass_heights = build_local_z_pass_heights(interval.base_height,
|
|
mixed_lower,
|
|
mixed_upper,
|
|
preferred_a,
|
|
preferred_b);
|
|
}
|
|
} else
|
|
pass_heights.emplace_back(interval.base_height);
|
|
|
|
if (interval.has_mixed_paint) {
|
|
if (!sanitize_local_z_pass_heights(pass_heights, interval.base_height, mixed_lower, mixed_upper))
|
|
pass_heights = build_uniform_local_z_pass_heights(interval.base_height, mixed_lower, mixed_upper);
|
|
}
|
|
|
|
// Stabilise pass ordering: small pass first so A/B cadence stays consistent.
|
|
if (interval.has_mixed_paint &&
|
|
preferred_a <= EPSILON &&
|
|
preferred_b <= EPSILON &&
|
|
pass_heights.size() == 2 &&
|
|
pass_heights[0] > pass_heights[1])
|
|
std::swap(pass_heights[0], pass_heights[1]);
|
|
|
|
const bool split_interval = interval.has_mixed_paint && (isolated_multi_row_mode || pass_heights.size() > 1);
|
|
const bool force_height_resolve = true;
|
|
auto build_whole_object_fixed_plans = [&](size_t first_pass_index) {
|
|
std::vector<SubLayerPlan> fixed_plans;
|
|
if (!local_z_whole_objects || fixed_state_masks_union.empty() || interval.base_height <= EPSILON)
|
|
return fixed_plans;
|
|
|
|
const std::vector<double> fixed_z_cuts {
|
|
interval.z_lo,
|
|
interval.z_lo + 0.5 * interval.base_height,
|
|
interval.z_hi
|
|
};
|
|
const size_t fixed_pass_count = fixed_z_cuts.size() - 1;
|
|
const size_t fixed_dependency_group = mixed_rows.size() + 1;
|
|
for (size_t fixed_pass_idx = 0; fixed_pass_idx < fixed_pass_count; ++fixed_pass_idx) {
|
|
const double z_lo = fixed_z_cuts[fixed_pass_idx];
|
|
const double z_hi = fixed_z_cuts[fixed_pass_idx + 1];
|
|
const double pass_height = z_hi - z_lo;
|
|
if (pass_height <= EPSILON)
|
|
continue;
|
|
|
|
const std::vector<ExPolygons> fixed_masks_for_pass =
|
|
build_local_z_transition_fixed_masks_for_pass(fixed_state_masks_by_extruder,
|
|
prev_fixed_state_masks_by_extruder,
|
|
next_fixed_state_masks_by_extruder,
|
|
fixed_pass_idx,
|
|
fixed_pass_count);
|
|
|
|
SubLayerPlan fixed_plan;
|
|
fixed_plan.layer_id = layer_id;
|
|
fixed_plan.pass_index = first_pass_index + fixed_plans.size();
|
|
fixed_plan.split_interval = true;
|
|
fixed_plan.z_lo = z_lo;
|
|
fixed_plan.z_hi = z_hi;
|
|
fixed_plan.print_z = z_hi;
|
|
fixed_plan.flow_height = pass_height;
|
|
fixed_plan.dependency_group = fixed_dependency_group;
|
|
fixed_plan.dependency_order = fixed_pass_idx;
|
|
fixed_plan.painted_masks_by_extruder.assign(num_physical, ExPolygons());
|
|
fixed_plan.fixed_painted_masks_by_extruder.assign(num_physical, ExPolygons());
|
|
|
|
bool plan_has_fixed_masks = false;
|
|
for (size_t extruder_idx = 0; extruder_idx < fixed_masks_for_pass.size() &&
|
|
extruder_idx < fixed_plan.fixed_painted_masks_by_extruder.size();
|
|
++extruder_idx) {
|
|
if (fixed_masks_for_pass[extruder_idx].empty())
|
|
continue;
|
|
append(fixed_plan.fixed_painted_masks_by_extruder[extruder_idx], fixed_masks_for_pass[extruder_idx]);
|
|
plan_has_fixed_masks = true;
|
|
}
|
|
if (!plan_has_fixed_masks)
|
|
continue;
|
|
|
|
for (ExPolygons &masks : fixed_plan.fixed_painted_masks_by_extruder)
|
|
if (masks.size() > 1)
|
|
masks = union_ex(masks);
|
|
fixed_plans.emplace_back(std::move(fixed_plan));
|
|
}
|
|
return fixed_plans;
|
|
};
|
|
|
|
if (split_interval) {
|
|
++split_intervals;
|
|
bool interval_has_split_painted_masks = false;
|
|
|
|
if (isolated_multi_row_mode) {
|
|
std::vector<SubLayerPlan> isolated_plans;
|
|
isolated_plans.reserve(std::max<size_t>(2, active_mixed_rows * 2));
|
|
|
|
for (size_t row_idx = 0; row_idx < row_active_this_layer.size(); ++row_idx) {
|
|
if (row_active_this_layer[row_idx] == 0)
|
|
continue;
|
|
const ExPolygons &state_masks = row_state_masks[row_idx];
|
|
if (state_masks.empty())
|
|
continue;
|
|
|
|
const std::vector<double> &row_passes_raw = isolated_row_pass_heights[row_idx];
|
|
const std::vector<double> row_passes = row_passes_raw.empty()
|
|
? std::vector<double>{ interval.base_height }
|
|
: row_passes_raw;
|
|
const LocalZActivePair &active_pair = row_active_pairs[row_idx];
|
|
const bool uses_direct_multicolor = row_uses_direct_multicolor_solver[row_idx] != 0;
|
|
const bool valid_pair = active_pair.valid_pair(num_physical);
|
|
const int orientation_cadence_index = active_pair.uses_layer_cycle_sequence
|
|
? row_layer_cycle_index[row_idx]
|
|
: row_cadence_index[row_idx];
|
|
const std::vector<unsigned int> direct_sequence = uses_direct_multicolor
|
|
? build_local_z_direct_multicolor_sequence(row_direct_component_ids[row_idx],
|
|
row_direct_component_weights[row_idx],
|
|
row_passes,
|
|
row_direct_component_error_mm[row_idx])
|
|
: std::vector<unsigned int>();
|
|
|
|
bool start_with_a = true;
|
|
if (!uses_direct_multicolor && valid_pair && preferred_a <= EPSILON && preferred_b <= EPSILON) {
|
|
double row_h_a = 0.0;
|
|
double row_h_b = 0.0;
|
|
compute_local_z_gradient_component_heights(active_pair.mix_b_percent, mixed_lower, mixed_upper, row_h_a, row_h_b);
|
|
start_with_a = choose_local_z_start_with_component_a(row_passes, row_h_a, row_h_b, orientation_cadence_index);
|
|
}
|
|
|
|
double z_cursor = interval.z_lo;
|
|
bool row_used = false;
|
|
size_t row_dependency_order = 0;
|
|
for (size_t pass_i = 0; pass_i < row_passes.size(); ++pass_i) {
|
|
if (z_cursor >= interval.z_hi - EPSILON)
|
|
break;
|
|
|
|
const double pass_height = std::min<double>(row_passes[pass_i], interval.z_hi - z_cursor);
|
|
if (pass_height <= EPSILON)
|
|
continue;
|
|
const double z_next = std::min<double>(interval.z_hi, z_cursor + pass_height);
|
|
|
|
SubLayerPlan plan;
|
|
plan.layer_id = layer_id;
|
|
plan.pass_index = isolated_plans.size();
|
|
plan.split_interval = true;
|
|
plan.z_lo = z_cursor;
|
|
plan.z_hi = z_next;
|
|
plan.print_z = z_next;
|
|
plan.flow_height = pass_height;
|
|
plan.dependency_group = row_idx + 1;
|
|
plan.dependency_order = row_dependency_order++;
|
|
plan.painted_masks_by_extruder.assign(num_physical, ExPolygons());
|
|
plan.fixed_painted_masks_by_extruder.assign(num_physical, ExPolygons());
|
|
++split_passes_total;
|
|
++forced_height_resolve_calls;
|
|
|
|
unsigned int target_extruder = 0;
|
|
if (uses_direct_multicolor) {
|
|
if (pass_i < direct_sequence.size())
|
|
target_extruder = direct_sequence[pass_i];
|
|
} else if (valid_pair) {
|
|
const bool even_pass = (pass_i % 2) == 0;
|
|
target_extruder = even_pass
|
|
? (start_with_a ? active_pair.component_a : active_pair.component_b)
|
|
: (start_with_a ? active_pair.component_b : active_pair.component_a);
|
|
++strict_ab_assignments;
|
|
}
|
|
if (target_extruder == 0) {
|
|
const unsigned int state_id = row_state_ids[row_idx];
|
|
if (state_id != 0) {
|
|
const int resolve_cadence_index = active_pair.uses_layer_cycle_sequence
|
|
? row_layer_cycle_index[row_idx]
|
|
: row_cadence_index[row_idx];
|
|
target_extruder = mixed_mgr.resolve(state_id, num_physical,
|
|
resolve_cadence_index,
|
|
float(plan.print_z),
|
|
float(plan.flow_height),
|
|
force_height_resolve);
|
|
}
|
|
}
|
|
if (target_extruder == 0 || target_extruder > num_physical) {
|
|
++forced_height_resolve_invalid_target;
|
|
} else {
|
|
append(plan.painted_masks_by_extruder[target_extruder - 1], state_masks);
|
|
++split_passes_with_painted_masks;
|
|
interval_has_split_painted_masks = true;
|
|
}
|
|
for (ExPolygons &masks : plan.painted_masks_by_extruder)
|
|
if (masks.size() > 1)
|
|
masks = union_ex(masks);
|
|
|
|
isolated_plans.emplace_back(std::move(plan));
|
|
row_used = true;
|
|
if (!uses_direct_multicolor && !active_pair.uses_layer_cycle_sequence)
|
|
++row_cadence_index[row_idx];
|
|
z_cursor = z_next;
|
|
}
|
|
if (row_used && !uses_direct_multicolor && active_pair.uses_layer_cycle_sequence)
|
|
++row_layer_cycle_index[row_idx];
|
|
}
|
|
|
|
if (!isolated_plans.empty()) {
|
|
auto sort_local_z_plans = [](std::vector<SubLayerPlan> &plans) {
|
|
std::sort(plans.begin(), plans.end(), [](const SubLayerPlan &lhs, const SubLayerPlan &rhs) {
|
|
if (std::abs(lhs.print_z - rhs.print_z) > EPSILON)
|
|
return lhs.print_z < rhs.print_z;
|
|
if (std::abs(lhs.z_lo - rhs.z_lo) > EPSILON)
|
|
return lhs.z_lo < rhs.z_lo;
|
|
return lhs.pass_index < rhs.pass_index;
|
|
});
|
|
};
|
|
|
|
sort_local_z_plans(isolated_plans);
|
|
|
|
std::vector<SubLayerPlan> fixed_plans = build_whole_object_fixed_plans(isolated_plans.size());
|
|
if (!fixed_plans.empty()) {
|
|
isolated_plans.insert(isolated_plans.end(),
|
|
std::make_move_iterator(fixed_plans.begin()),
|
|
std::make_move_iterator(fixed_plans.end()));
|
|
sort_local_z_plans(isolated_plans);
|
|
}
|
|
|
|
double min_flow_height = isolated_plans.front().flow_height;
|
|
double max_flow_height = isolated_plans.front().flow_height;
|
|
for (size_t idx = 0; idx < isolated_plans.size(); ++idx) {
|
|
isolated_plans[idx].pass_index = idx;
|
|
min_flow_height = std::min(min_flow_height, isolated_plans[idx].flow_height);
|
|
max_flow_height = std::max(max_flow_height, isolated_plans[idx].flow_height);
|
|
for (ExPolygons &masks : isolated_plans[idx].painted_masks_by_extruder)
|
|
if (masks.size() > 1)
|
|
masks = union_ex(masks);
|
|
for (ExPolygons &masks : isolated_plans[idx].fixed_painted_masks_by_extruder)
|
|
if (masks.size() > 1)
|
|
masks = union_ex(masks);
|
|
if (std::any_of(isolated_plans[idx].fixed_painted_masks_by_extruder.begin(),
|
|
isolated_plans[idx].fixed_painted_masks_by_extruder.end(),
|
|
[](const ExPolygons &masks) { return !masks.empty(); })) {
|
|
++split_passes_with_painted_masks;
|
|
interval_has_split_painted_masks = true;
|
|
}
|
|
}
|
|
isolated_plans.back().base_masks = base_masks;
|
|
interval.sublayer_height = min_flow_height;
|
|
for (SubLayerPlan &plan : isolated_plans) {
|
|
plans.emplace_back(std::move(plan));
|
|
++interval.sublayer_count;
|
|
++total_generated_sublayer_cnt;
|
|
}
|
|
}
|
|
} else {
|
|
// Shared pass-height path: all active rows use the same split.
|
|
std::vector<uint8_t> start_with_component_a(mixed_rows.size(), uint8_t(1));
|
|
std::vector<std::vector<unsigned int>> row_direct_pass_sequences(mixed_rows.size());
|
|
size_t single_dependency_group = 0;
|
|
size_t active_dependency_rows = 0;
|
|
for (size_t row_idx = 0; row_idx < row_state_masks.size(); ++row_idx) {
|
|
if (row_state_masks[row_idx].empty() || row_state_ids[row_idx] == 0)
|
|
continue;
|
|
++active_dependency_rows;
|
|
single_dependency_group = row_idx + 1;
|
|
}
|
|
if (active_dependency_rows != 1)
|
|
single_dependency_group = 0;
|
|
if (preferred_a <= EPSILON && preferred_b <= EPSILON) {
|
|
for (size_t row_idx = 0; row_idx < row_active_this_layer.size(); ++row_idx) {
|
|
if (row_active_this_layer[row_idx] == 0 || !local_z_eligible_mixed_row(mixed_rows[row_idx]))
|
|
continue;
|
|
if (row_uses_direct_multicolor_solver[row_idx] != 0) {
|
|
row_direct_pass_sequences[row_idx] =
|
|
build_local_z_direct_multicolor_sequence(row_direct_component_ids[row_idx],
|
|
row_direct_component_weights[row_idx],
|
|
pass_heights,
|
|
row_direct_component_error_mm[row_idx]);
|
|
continue;
|
|
}
|
|
|
|
const LocalZActivePair &active_pair = row_active_pairs[row_idx];
|
|
if (!active_pair.valid_pair(num_physical))
|
|
continue;
|
|
|
|
double row_h_a = 0.0;
|
|
double row_h_b = 0.0;
|
|
const int orientation_cadence_index = active_pair.uses_layer_cycle_sequence
|
|
? row_layer_cycle_index[row_idx]
|
|
: row_cadence_index[row_idx];
|
|
compute_local_z_gradient_component_heights(active_pair.mix_b_percent, mixed_lower, mixed_upper, row_h_a, row_h_b);
|
|
start_with_component_a[row_idx] =
|
|
choose_local_z_start_with_component_a(pass_heights, row_h_a, row_h_b, orientation_cadence_index)
|
|
? uint8_t(1) : uint8_t(0);
|
|
}
|
|
}
|
|
|
|
double z_cursor = interval.z_lo;
|
|
size_t pass_idx = 0;
|
|
interval.sublayer_height = *std::min_element(pass_heights.begin(), pass_heights.end());
|
|
std::vector<uint8_t> row_seen_sequence_in_interval(mixed_rows.size(), uint8_t(0));
|
|
|
|
for (const double pass_height_nominal : pass_heights) {
|
|
if (z_cursor >= interval.z_hi - EPSILON)
|
|
break;
|
|
const double pass_height = std::min<double>(pass_height_nominal, interval.z_hi - z_cursor);
|
|
const double z_next = std::min<double>(interval.z_hi, z_cursor + pass_height);
|
|
|
|
SubLayerPlan plan;
|
|
plan.layer_id = layer_id;
|
|
plan.pass_index = pass_idx;
|
|
plan.split_interval = true;
|
|
plan.z_lo = z_cursor;
|
|
plan.z_hi = z_next;
|
|
plan.print_z = z_next;
|
|
plan.flow_height = pass_height;
|
|
plan.dependency_group = single_dependency_group;
|
|
plan.dependency_order = pass_idx;
|
|
plan.painted_masks_by_extruder.assign(num_physical, ExPolygons());
|
|
plan.fixed_painted_masks_by_extruder.assign(num_physical, ExPolygons());
|
|
++split_passes_total;
|
|
bool pass_has_painted_masks = false;
|
|
std::vector<uint8_t> row_seen_in_pass(mixed_rows.size(), uint8_t(0));
|
|
|
|
for (size_t row_idx = 0; row_idx < row_state_masks.size(); ++row_idx) {
|
|
const ExPolygons &state_masks = row_state_masks[row_idx];
|
|
if (state_masks.empty())
|
|
continue;
|
|
const unsigned int state_id = row_state_ids[row_idx];
|
|
if (state_id == 0)
|
|
continue;
|
|
const MixedFilament &mf = mixed_rows[row_idx];
|
|
if (!local_z_eligible_mixed_row(mf))
|
|
continue;
|
|
const LocalZActivePair &active_pair = row_active_pairs[row_idx];
|
|
const bool uses_direct_multicolor = row_uses_direct_multicolor_solver[row_idx] != 0;
|
|
row_seen_in_pass[row_idx] = uint8_t(1);
|
|
if (!uses_direct_multicolor && active_pair.uses_layer_cycle_sequence)
|
|
row_seen_sequence_in_interval[row_idx] = uint8_t(1);
|
|
++forced_height_resolve_calls;
|
|
|
|
unsigned int target_extruder = 0;
|
|
if (uses_direct_multicolor) {
|
|
if (pass_idx < row_direct_pass_sequences[row_idx].size())
|
|
target_extruder = row_direct_pass_sequences[row_idx][pass_idx];
|
|
} else if (active_pair.valid_pair(num_physical)) {
|
|
const bool start_a = start_with_component_a[row_idx] != 0;
|
|
const bool even_pass = (pass_idx % 2) == 0;
|
|
target_extruder = even_pass
|
|
? (start_a ? active_pair.component_a : active_pair.component_b)
|
|
: (start_a ? active_pair.component_b : active_pair.component_a);
|
|
++strict_ab_assignments;
|
|
}
|
|
if (target_extruder == 0) {
|
|
const int resolve_cadence_index = active_pair.uses_layer_cycle_sequence
|
|
? row_layer_cycle_index[row_idx]
|
|
: row_cadence_index[row_idx];
|
|
target_extruder = mixed_mgr.resolve(state_id, num_physical,
|
|
resolve_cadence_index,
|
|
float(plan.print_z),
|
|
float(plan.flow_height),
|
|
force_height_resolve);
|
|
}
|
|
if (target_extruder == 0 || target_extruder > num_physical) {
|
|
++forced_height_resolve_invalid_target;
|
|
continue;
|
|
}
|
|
append(plan.painted_masks_by_extruder[target_extruder - 1], state_masks);
|
|
pass_has_painted_masks = true;
|
|
}
|
|
for (ExPolygons &masks : plan.painted_masks_by_extruder)
|
|
if (masks.size() > 1)
|
|
masks = union_ex(masks);
|
|
for (ExPolygons &masks : plan.fixed_painted_masks_by_extruder)
|
|
if (masks.size() > 1)
|
|
masks = union_ex(masks);
|
|
if (pass_has_painted_masks) {
|
|
++split_passes_with_painted_masks;
|
|
interval_has_split_painted_masks = true;
|
|
}
|
|
|
|
if (z_next >= interval.z_hi - EPSILON)
|
|
plan.base_masks = base_masks;
|
|
|
|
plans.emplace_back(std::move(plan));
|
|
++interval.sublayer_count;
|
|
++total_generated_sublayer_cnt;
|
|
++pass_idx;
|
|
for (size_t mixed_idx = 0; mixed_idx < row_seen_in_pass.size(); ++mixed_idx)
|
|
if (row_seen_in_pass[mixed_idx] != 0 &&
|
|
row_uses_layer_cycle_pair[mixed_idx] == 0 &&
|
|
row_uses_direct_multicolor_solver[mixed_idx] == 0)
|
|
++row_cadence_index[mixed_idx];
|
|
z_cursor = z_next;
|
|
}
|
|
std::vector<SubLayerPlan> fixed_plans = build_whole_object_fixed_plans(pass_idx);
|
|
for (SubLayerPlan &fixed_plan : fixed_plans) {
|
|
interval.sublayer_height = std::min(interval.sublayer_height, fixed_plan.flow_height);
|
|
plans.emplace_back(std::move(fixed_plan));
|
|
++interval.sublayer_count;
|
|
++total_generated_sublayer_cnt;
|
|
++split_passes_with_painted_masks;
|
|
interval_has_split_painted_masks = true;
|
|
}
|
|
for (size_t row_idx = 0; row_idx < row_seen_sequence_in_interval.size(); ++row_idx)
|
|
if (row_seen_sequence_in_interval[row_idx] != 0 &&
|
|
row_uses_direct_multicolor_solver[row_idx] == 0)
|
|
++row_layer_cycle_index[row_idx];
|
|
}
|
|
if (!interval_has_split_painted_masks)
|
|
++split_intervals_without_painted_masks;
|
|
} else {
|
|
// Non-split interval: single pass.
|
|
if (interval.has_mixed_paint)
|
|
++non_split_mixed_intervals;
|
|
|
|
SubLayerPlan plan;
|
|
plan.layer_id = layer_id;
|
|
plan.pass_index = 0;
|
|
plan.split_interval = false;
|
|
plan.z_lo = interval.z_lo;
|
|
plan.z_hi = interval.z_hi;
|
|
plan.print_z = interval.z_hi;
|
|
plan.flow_height = interval.base_height;
|
|
plan.dependency_order = 0;
|
|
plan.base_masks = base_masks;
|
|
plan.painted_masks_by_extruder.assign(num_physical, ExPolygons());
|
|
plan.fixed_painted_masks_by_extruder.assign(num_physical, ExPolygons());
|
|
std::vector<uint8_t> row_seen_in_interval(mixed_rows.size(), uint8_t(0));
|
|
|
|
for (size_t row_idx = 0; row_idx < row_state_masks.size(); ++row_idx) {
|
|
const ExPolygons &state_masks = row_state_masks[row_idx];
|
|
if (state_masks.empty())
|
|
continue;
|
|
const unsigned int state_id = row_state_ids[row_idx];
|
|
if (state_id == 0)
|
|
continue;
|
|
const MixedFilament &mixed_row = mixed_rows[row_idx];
|
|
if (!local_z_eligible_mixed_row(mixed_row))
|
|
continue;
|
|
row_seen_in_interval[row_idx] = uint8_t(1);
|
|
++forced_height_resolve_calls;
|
|
|
|
unsigned int target_extruder = 0;
|
|
if (row_uses_direct_multicolor_solver[row_idx] != 0) {
|
|
const std::vector<unsigned int> direct_sequence =
|
|
build_local_z_direct_multicolor_sequence(row_direct_component_ids[row_idx],
|
|
row_direct_component_weights[row_idx],
|
|
std::vector<double>{ interval.base_height },
|
|
row_direct_component_error_mm[row_idx]);
|
|
if (!direct_sequence.empty())
|
|
target_extruder = direct_sequence.front();
|
|
} else {
|
|
const int resolve_cadence_index = row_uses_layer_cycle_pair[row_idx] != 0
|
|
? row_layer_cycle_index[row_idx]
|
|
: row_cadence_index[row_idx];
|
|
target_extruder =
|
|
mixed_mgr.resolve(state_id, num_physical,
|
|
resolve_cadence_index,
|
|
float(plan.print_z),
|
|
float(plan.flow_height),
|
|
force_height_resolve);
|
|
}
|
|
if (target_extruder == 0 || target_extruder > num_physical) {
|
|
++forced_height_resolve_invalid_target;
|
|
continue;
|
|
}
|
|
append(plan.painted_masks_by_extruder[target_extruder - 1], state_masks);
|
|
}
|
|
for (size_t extruder_idx = 0; extruder_idx < fixed_state_masks_by_extruder.size(); ++extruder_idx)
|
|
if (!fixed_state_masks_by_extruder[extruder_idx].empty())
|
|
append(plan.fixed_painted_masks_by_extruder[extruder_idx], fixed_state_masks_by_extruder[extruder_idx]);
|
|
for (ExPolygons &masks : plan.painted_masks_by_extruder)
|
|
if (masks.size() > 1)
|
|
masks = union_ex(masks);
|
|
for (ExPolygons &masks : plan.fixed_painted_masks_by_extruder)
|
|
if (masks.size() > 1)
|
|
masks = union_ex(masks);
|
|
|
|
plans.emplace_back(std::move(plan));
|
|
interval.sublayer_count = 1;
|
|
++total_generated_sublayer_cnt;
|
|
|
|
for (size_t mixed_idx = 0; mixed_idx < row_seen_in_interval.size(); ++mixed_idx)
|
|
if (row_seen_in_interval[mixed_idx] != 0 &&
|
|
row_uses_direct_multicolor_solver[mixed_idx] == 0)
|
|
(row_uses_layer_cycle_pair[mixed_idx] != 0
|
|
? row_layer_cycle_index[mixed_idx]
|
|
: row_cadence_index[mixed_idx])++;
|
|
}
|
|
|
|
if (interval.has_mixed_paint) {
|
|
BOOST_LOG_TRIVIAL(debug) << "Local-Z interval"
|
|
<< " object=" << object_name
|
|
<< " layer_id=" << layer_id
|
|
<< " base_height=" << interval.base_height
|
|
<< " split=" << split_interval
|
|
<< " active_mixed_rows=" << active_mixed_rows
|
|
<< " mixed_states=" << mixed_state_count;
|
|
}
|
|
|
|
row_active_prev_layer = row_active_this_layer;
|
|
intervals.emplace_back(std::move(interval));
|
|
}
|
|
|
|
if (!intervals.empty() && !plans.empty()) {
|
|
print_object.set_local_z_plan(std::move(intervals), std::move(plans));
|
|
BOOST_LOG_TRIVIAL(warning) << "Local-Z plan built"
|
|
<< " object=" << object_name
|
|
<< " mixed_intervals=" << mixed_intervals
|
|
<< " split_intervals=" << split_intervals
|
|
<< " non_split_mixed_intervals=" << non_split_mixed_intervals
|
|
<< " split_intervals_without_painted_masks=" << split_intervals_without_painted_masks
|
|
<< " sublayer_passes=" << total_generated_sublayer_cnt
|
|
<< " split_passes_total=" << split_passes_total
|
|
<< " split_passes_with_painted_masks=" << split_passes_with_painted_masks
|
|
<< " alternating_height_intervals=" << alternating_height_intervals
|
|
<< " shared_multi_row_fallback_intervals=" << shared_multi_row_fallback_intervals
|
|
<< " strict_ab_assignments=" << strict_ab_assignments
|
|
<< " mixed_state_layers=" << total_mixed_state_layers
|
|
<< " forced_height_resolve_calls=" << forced_height_resolve_calls
|
|
<< " forced_height_resolve_invalid_target=" << forced_height_resolve_invalid_target
|
|
<< " mixed_lower=" << mixed_lower
|
|
<< " mixed_upper=" << mixed_upper
|
|
<< " preferred_a=" << preferred_a
|
|
<< " preferred_b=" << preferred_b;
|
|
} else {
|
|
BOOST_LOG_TRIVIAL(warning) << "Local-Z plan empty after build"
|
|
<< " object=" << object_name
|
|
<< " intervals=" << intervals.size()
|
|
<< " plans=" << plans.size()
|
|
<< " mixed_intervals=" << mixed_intervals;
|
|
}
|
|
}
|
|
|
|
} // namespace Slic3r
|