This commit is contained in:
Rad
2026-04-11 02:01:51 +02:00
parent 6022b25f18
commit c2fc960ccc
7 changed files with 432 additions and 87 deletions
+38 -12
View File
@@ -339,21 +339,32 @@ public:
ExtrusionPaths paths;
ExtrusionMultiPath() {}
ExtrusionMultiPath(const ExtrusionMultiPath &rhs) : paths(rhs.paths), m_can_reverse(rhs.m_can_reverse) {}
ExtrusionMultiPath(ExtrusionMultiPath &&rhs) : paths(std::move(rhs.paths)), m_can_reverse(rhs.m_can_reverse) {}
ExtrusionMultiPath(const ExtrusionPaths &paths) : paths(paths) {}
ExtrusionMultiPath(const ExtrusionPath &path) {this->paths.push_back(path); m_can_reverse = path.can_reverse(); }
ExtrusionMultiPath(const ExtrusionMultiPath &rhs) : ExtrusionEntity(rhs), paths(rhs.paths), m_can_reverse(rhs.m_can_reverse) {}
ExtrusionMultiPath(ExtrusionMultiPath &&rhs) : ExtrusionEntity(rhs), paths(std::move(rhs.paths)), m_can_reverse(rhs.m_can_reverse) {}
ExtrusionMultiPath(const ExtrusionPaths &paths) : paths(paths)
{
if (!paths.empty())
this->inset_idx = paths.front().inset_idx;
}
ExtrusionMultiPath(const ExtrusionPath &path)
{
this->paths.push_back(path);
this->inset_idx = path.inset_idx;
m_can_reverse = path.can_reverse();
}
ExtrusionMultiPath &operator=(const ExtrusionMultiPath &rhs)
{
this->paths = rhs.paths;
m_can_reverse = rhs.m_can_reverse;
this->paths = rhs.paths;
this->inset_idx = rhs.inset_idx;
m_can_reverse = rhs.m_can_reverse;
return *this;
}
ExtrusionMultiPath &operator=(ExtrusionMultiPath &&rhs)
{
this->paths = std::move(rhs.paths);
m_can_reverse = rhs.m_can_reverse;
this->paths = std::move(rhs.paths);
this->inset_idx = rhs.inset_idx;
m_can_reverse = rhs.m_can_reverse;
return *this;
}
@@ -404,12 +415,27 @@ public:
ExtrusionPaths paths;
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : m_loop_role(role) {}
ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {}
ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) {}
ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role)
{
if (!paths.empty())
this->inset_idx = paths.front().inset_idx;
}
ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role)
{
if (!this->paths.empty())
this->inset_idx = this->paths.front().inset_idx;
}
ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
{ this->paths.push_back(path); }
{
this->paths.push_back(path);
this->inset_idx = path.inset_idx;
}
ExtrusionLoop(const ExtrusionPath &&path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
{ this->paths.emplace_back(std::move(path)); }
{
this->paths.emplace_back(std::move(path));
if (!this->paths.empty())
this->inset_idx = this->paths.front().inset_idx;
}
bool is_loop() const override{ return true; }
bool can_reverse() const override { return false; }
ExtrusionEntity* clone() const override{ return new ExtrusionLoop (*this); }
+50 -22
View File
@@ -4770,15 +4770,32 @@ LayerResult GCode::process_layer(const Print& print,
auto configured_filament_id_1based = [&layer_tools](const GCode::ObjectByExtruder::Island::Region::Type entity_type,
const ExtrusionEntityCollection& entities,
const PrintRegion& region) -> unsigned int {
auto raw_sparse_infill_filament_id_1based = [&layer_tools, &region]() -> unsigned int {
const PrintRegionConfig &config = region.config();
if (!config.enable_infill_filament_override.value)
return unsigned(config.wall_filament.value);
if (layer_tools.object_layer_count <= 0)
return unsigned(config.sparse_infill_filament.value);
const int first_layers = std::max(0, config.infill_filament_use_base_first_layers.value);
const int last_layers = std::max(0, config.infill_filament_use_base_last_layers.value);
return (layer_tools.layer_index < first_layers ||
layer_tools.layer_index >= layer_tools.object_layer_count - last_layers)
? unsigned(config.wall_filament.value)
: unsigned(config.sparse_infill_filament.value);
};
if (entity_type == GCode::ObjectByExtruder::Island::Region::INFILL) {
if (layer_tools.extruder_override != 0)
return layer_tools.extruder_override;
const ExtrusionRole role = entities.entities.empty() ? erNone : entities.entities.front()->role();
if (role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON)
return layer_tools.sparse_infill_filament(region) + 1;
return raw_sparse_infill_filament_id_1based();
if (is_solid_infill(role))
return layer_tools.solid_infill_filament(region) + 1;
return layer_tools.sparse_infill_filament(region) + 1;
return unsigned(region.config().solid_infill_filament.value);
return raw_sparse_infill_filament_id_1based();
}
return layer_tools.wall_filament(region) + 1;
return layer_tools.extruder_override == 0 ? unsigned(region.config().wall_filament.value) : layer_tools.extruder_override;
};
auto configured_extruder_id = [&layer_tools](const GCode::ObjectByExtruder::Island::Region::Type entity_type,
@@ -5318,28 +5335,39 @@ LayerResult GCode::process_layer(const Print& print,
layer_tools.num_physical,
layer_tools.layer_index,
split_by_extruder,
bucket_count) &&
bucket_count >= 2) {
for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) {
std::unique_ptr<ExtrusionEntityCollection>& split_collection = split_by_extruder[extruder_idx];
if (!split_collection || split_collection->entities.empty())
continue;
const ExtrusionEntityCollection* split_ptr = split_collection.get();
local_z_clipped_collections.emplace_back(std::move(split_collection));
std::vector<ObjectByExtruder::Island>& islands =
object_islands_by_extruder(by_extruder, unsigned(extruder_idx), layer_to_print_idx, layers.size(), n_slices + 1);
for (size_t i = 0; i <= n_slices; ++i) {
const bool last = i == n_slices;
const size_t island_idx = last ? n_slices : slices_test_order[i];
if (last || point_inside_surface(island_idx, split_ptr->first_point())) {
if (islands[island_idx].by_region.empty())
islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region());
islands[island_idx].by_region[region.print_region_id()].append(entity_type, split_ptr, nullptr);
bucket_count)) {
if (bucket_count >= 2) {
for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) {
std::unique_ptr<ExtrusionEntityCollection>& split_collection = split_by_extruder[extruder_idx];
if (!split_collection || split_collection->entities.empty())
continue;
const ExtrusionEntityCollection* split_ptr = split_collection.get();
local_z_clipped_collections.emplace_back(std::move(split_collection));
std::vector<ObjectByExtruder::Island>& islands =
object_islands_by_extruder(by_extruder, unsigned(extruder_idx), layer_to_print_idx, layers.size(), n_slices + 1);
for (size_t i = 0; i <= n_slices; ++i) {
const bool last = i == n_slices;
const size_t island_idx = last ? n_slices : slices_test_order[i];
if (last || point_inside_surface(island_idx, split_ptr->first_point())) {
if (islands[island_idx].by_region.empty())
islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region());
islands[island_idx].by_region[region.print_region_id()].append(entity_type, split_ptr, nullptr);
break;
}
}
}
continue;
}
if (bucket_count == 1) {
for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) {
const std::unique_ptr<ExtrusionEntityCollection>& split_collection = split_by_extruder[extruder_idx];
if (split_collection && !split_collection->entities.empty()) {
// by_extruder keys and LayerTools runtime extruder IDs are zero-based here.
correct_extruder_id = int(extruder_idx);
break;
}
}
}
continue;
}
}
}
+33 -21
View File
@@ -120,6 +120,24 @@ unsigned int grouped_manual_pattern_mixed_filament_id_for_layer(const LayerTools
return 0;
}
unsigned int grouped_manual_pattern_infill_filament_1based(const LayerTools& layer_tools,
const PrintRegion& region,
unsigned int configured_filament_id_1based)
{
const unsigned int grouped_id =
grouped_manual_pattern_mixed_filament_id_for_layer(layer_tools, configured_filament_id_1based);
if (grouped_id == 0)
return 0;
const int innermost_perimeter_index = std::max(0, region.config().wall_loops.value - 1);
return layer_tools.mixed_mgr->resolve_perimeter(grouped_id,
layer_tools.num_physical,
layer_tools.layer_index,
innermost_perimeter_index,
float(layer_tools.print_z),
float(layer_tools.layer_height));
}
void remove_duplicates_preserve_order(std::vector<unsigned int> &values)
{
std::vector<unsigned int> ordered;
@@ -279,14 +297,16 @@ unsigned int LayerTools::sparse_infill_filament(const PrintRegion &region) const
{
assert(region.config().wall_filament.value > 0);
unsigned int id = (this->extruder_override == 0) ? sparse_infill_filament_id_1based(*this, region) : this->extruder_override;
return resolve_mixed_1based(id) - 1;
const unsigned int grouped = grouped_manual_pattern_infill_filament_1based(*this, region, id);
return ((grouped != 0) ? grouped : resolve_mixed_1based(id)) - 1;
}
unsigned int LayerTools::solid_infill_filament(const PrintRegion &region) const
{
assert(region.config().solid_infill_filament.value > 0);
unsigned int id = (this->extruder_override == 0) ? region.config().solid_infill_filament.value : this->extruder_override;
return resolve_mixed_1based(id) - 1;
const unsigned int grouped = grouped_manual_pattern_infill_filament_1based(*this, region, id);
return ((grouped != 0) ? grouped : resolve_mixed_1based(id)) - 1;
}
// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
@@ -295,19 +315,13 @@ unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, c
assert(region.config().wall_filament.value > 0);
assert(region.config().sparse_infill_filament.value > 0);
assert(region.config().solid_infill_filament.value > 0);
// 1 based extruder ID.
unsigned int extruder = 1;
if (this->extruder_override == 0) {
if (extrusions.has_infill()) {
const ExtrusionRole role = extrusions.entities.empty() ? erNone : extrusions.entities.front()->role();
extruder = infill_filament_id_1based(*this, region, role);
} else
extruder = region.config().wall_filament.value;
} else
extruder = this->extruder_override;
extruder = resolve_mixed_1based(extruder);
return (extruder == 0) ? 0 : extruder - 1;
if (extrusions.has_infill()) {
const ExtrusionRole role = extrusions.entities.empty() ? erNone : extrusions.entities.front()->role();
if (internal_solid_infill_uses_sparse_filament(region, role))
return sparse_infill_filament(region);
return is_solid_infill(role) ? solid_infill_filament(region) : sparse_infill_filament(region);
}
return wall_filament(region);
}
static double calc_max_layer_height(const PrintConfig &config, double max_object_layer_height)
@@ -718,8 +732,9 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
layerCount,
float(layer->print_z),
float(layer->height));
if (ordered.size() >= 2) {
layer_tools.preserve_extruder_order = true;
if (!ordered.empty()) {
if (ordered.size() >= 2)
layer_tools.preserve_extruder_order = true;
for (unsigned int extruder_id : ordered) {
layer_tools.extruders.emplace_back(extruder_id);
if (layerCount == 0 &&
@@ -764,10 +779,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
if (something_nonoverriddable || !m_print_config_ptr) {
if (extruder_override == 0) {
if (has_solid_infill)
layer_tools.extruders.emplace_back(resolve_mixed(region.config().solid_infill_filament,
layerCount,
float(layer->print_z),
float(layer->height)));
layer_tools.extruders.emplace_back(layer_tools.solid_infill_filament(region) + 1);
if (has_sparse_infill)
layer_tools.extruders.emplace_back(layer_tools.sparse_infill_filament(region) + 1);
} else if (has_solid_infill || has_sparse_infill)
+36 -1
View File
@@ -46,6 +46,39 @@ unsigned int effective_layer_filament_id(const Layer &layer, unsigned int filame
float(layer.height));
}
unsigned int effective_infill_filament_id(const Layer &layer, const PrintRegionConfig &config, unsigned int filament_id)
{
const unsigned int effective = effective_layer_filament_id(layer, filament_id);
if (effective != filament_id || filament_id == 0)
return effective;
const PrintObject *object = layer.object();
const Print *print = object ? object->print() : nullptr;
if (print == nullptr)
return filament_id;
const size_t num_physical = print->config().filament_diameter.size();
if (num_physical == 0)
return filament_id;
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
const MixedFilament *mixed_row = mixed_mgr.mixed_filament_from_id(filament_id, num_physical);
if (mixed_row == nullptr)
return filament_id;
const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(mixed_row->manual_pattern);
if (normalized_pattern.find(',') == std::string::npos)
return filament_id;
const int innermost_perimeter_index = std::max(0, config.wall_loops.value - 1);
return mixed_mgr.resolve_perimeter(filament_id,
num_physical,
int(layer.id()),
innermost_perimeter_index,
float(layer.print_z),
float(layer.height));
}
bool use_base_infill_filament(const PrintRegionConfig &config, int layer_index, int layer_count)
{
if (!config.enable_infill_filament_override.value)
@@ -71,7 +104,9 @@ unsigned int LayerRegion::extruder(FlowRole role) const
else
filament_id = this->region().extruder(role);
return effective_layer_filament_id(*m_layer, filament_id);
return (role == frInfill || role == frSolidInfill) ?
effective_infill_filament_id(*m_layer, config, filament_id) :
effective_layer_filament_id(*m_layer, filament_id);
}
Flow LayerRegion::flow(FlowRole role) const
+7
View File
@@ -570,6 +570,10 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
// Append paths to collection.
if (!paths.empty()) {
const int inset_idx = int(extrusion->inset_idx);
for (ExtrusionPath &path : paths)
path.inset_idx = inset_idx;
if (extrusion->is_closed) {
ExtrusionLoop extrusion_loop(std::move(paths), pg_extrusion.is_contour ? elrDefault : elrHole);
extrusion_loop.make_counter_clockwise();
@@ -581,6 +585,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
}
assert(extrusion_loop.paths.front().first_point() == extrusion_loop.paths.back().last_point());
extrusion_loop.inset_idx = inset_idx;
extrusion_coll.append(std::move(extrusion_loop));
}
else {
@@ -593,11 +598,13 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
}
ExtrusionMultiPath multi_path;
multi_path.paths.emplace_back(std::move(paths.front()));
multi_path.inset_idx = inset_idx;
for (auto it_path = std::next(paths.begin()); it_path != paths.end(); ++it_path) {
if (multi_path.paths.back().last_point() != it_path->first_point()) {
extrusion_coll.append(ExtrusionMultiPath(std::move(multi_path)));
multi_path = ExtrusionMultiPath();
multi_path.inset_idx = inset_idx;
}
multi_path.paths.emplace_back(std::move(*it_path));
}
+130 -31
View File
@@ -4566,6 +4566,7 @@ struct MixedFilamentPreviewSettings
double preferred_a_height { 0.0 };
double preferred_b_height { 0.0 };
bool local_z_mode { false };
size_t wall_loops { 1 };
};
// Inline editor panel for configuring a single mixed filament
@@ -4634,7 +4635,11 @@ private:
// Helper functions (copied from update_mixed_filament_panel)
static std::vector<unsigned int> decode_gradient_ids(const std::string &s);
static std::string encode_gradient_ids(const std::vector<unsigned int> &ids);
static std::vector<unsigned int> decode_manual_pattern_ids(const std::string &pattern, unsigned int a, unsigned int b);
static std::vector<unsigned int> decode_manual_pattern_ids(const std::string &pattern,
unsigned int a,
unsigned int b,
size_t num_physical,
size_t wall_loops = 0);
static std::vector<int> decode_gradient_weights(const std::string &s, size_t n);
static std::vector<int> normalize_gradient_weights(const std::vector<int> &w, size_t n);
static std::string encode_gradient_weights(const std::vector<int> &w);
@@ -4784,6 +4789,100 @@ private:
};
// Implementation of MixedFilamentConfigPanel helper functions
static std::vector<std::string> split_manual_pattern_preview_groups(const std::string &pattern)
{
std::vector<std::string> groups;
if (pattern.empty())
return groups;
std::string current;
for (const char c : pattern) {
if (c == ',') {
if (!current.empty()) {
groups.emplace_back(std::move(current));
current.clear();
}
continue;
}
current.push_back(c);
}
if (!current.empty())
groups.emplace_back(std::move(current));
return groups;
}
static unsigned int decode_manual_pattern_preview_token(char token, unsigned int component_a, unsigned int component_b, size_t num_physical)
{
unsigned int extruder_id = 0;
if (token == '1')
extruder_id = component_a;
else if (token == '2')
extruder_id = component_b;
else if (token >= '3' && token <= '9')
extruder_id = unsigned(token - '0');
return (extruder_id >= 1 && extruder_id <= num_physical) ? extruder_id : 0;
}
static std::vector<unsigned int> build_grouped_manual_pattern_preview_sequence(const std::string &pattern,
unsigned int component_a,
unsigned int component_b,
size_t num_physical,
size_t wall_loops)
{
std::vector<unsigned int> sequence;
if (num_physical == 0)
return sequence;
const std::string normalized = MixedFilamentManager::normalize_manual_pattern(pattern);
if (normalized.empty())
return sequence;
const std::vector<std::string> groups = split_manual_pattern_preview_groups(normalized);
if (groups.empty())
return sequence;
if (groups.size() == 1) {
sequence.reserve(normalized.size());
for (const char token : normalized) {
const unsigned int extruder_id =
decode_manual_pattern_preview_token(token, component_a, component_b, num_physical);
if (extruder_id != 0)
sequence.emplace_back(extruder_id);
}
return sequence;
}
constexpr size_t k_max_preview_cycle = 48;
size_t cycle = 1;
for (const std::string &group : groups) {
if (group.empty())
continue;
cycle = std::lcm(cycle, group.size());
if (cycle >= k_max_preview_cycle) {
cycle = k_max_preview_cycle;
break;
}
}
const size_t preview_wall_loops = std::max<size_t>(1, wall_loops == 0 ? groups.size() : wall_loops);
sequence.reserve(preview_wall_loops * cycle);
for (size_t layer_idx = 0; layer_idx < cycle; ++layer_idx) {
for (size_t wall_idx = 0; wall_idx < preview_wall_loops; ++wall_idx) {
const std::string &group = groups[std::min(wall_idx, groups.size() - 1)];
if (group.empty())
continue;
const char token = group[layer_idx % group.size()];
const unsigned int extruder_id =
decode_manual_pattern_preview_token(token, component_a, component_b, num_physical);
if (extruder_id != 0)
sequence.emplace_back(extruder_id);
}
}
return sequence;
}
std::vector<unsigned int> MixedFilamentConfigPanel::decode_gradient_ids(const std::string &s)
{
std::vector<unsigned int> ids;
@@ -4816,15 +4915,13 @@ std::string MixedFilamentConfigPanel::encode_gradient_ids(const std::vector<unsi
return out;
}
std::vector<unsigned int> MixedFilamentConfigPanel::decode_manual_pattern_ids(const std::string &pattern, unsigned int a, unsigned int b)
std::vector<unsigned int> MixedFilamentConfigPanel::decode_manual_pattern_ids(const std::string &pattern,
unsigned int a,
unsigned int b,
size_t num_physical,
size_t wall_loops)
{
std::vector<unsigned int> seq;
for (char c : pattern) {
if (c == '1' || c == 'A' || c == 'a') seq.emplace_back(a);
else if (c == '2' || c == 'B' || c == 'b') seq.emplace_back(b);
else if (c >= '3' && c <= '9') seq.emplace_back(unsigned(c - '0'));
}
return seq;
return build_grouped_manual_pattern_preview_sequence(pattern, a, b, num_physical, wall_loops);
}
std::vector<int> MixedFilamentConfigPanel::decode_gradient_weights(const std::string &s, size_t n)
@@ -5947,7 +6044,11 @@ void MixedFilamentConfigPanel::build_ui()
m_mf.pointillism_all_filaments = false;
m_mf.gradient_component_ids.clear();
m_mf.gradient_component_weights.clear();
preview_sequence = decode_manual_pattern_ids(m_mf.manual_pattern, m_mf.component_a, m_mf.component_b);
preview_sequence = decode_manual_pattern_ids(m_mf.manual_pattern,
m_mf.component_a,
m_mf.component_b,
m_num_physical,
m_preview_settings.wall_loops);
} else {
std::vector<unsigned int> selected_ids;
selected_ids.reserve(4);
@@ -6254,7 +6355,11 @@ void MixedFilamentConfigPanel::update_preview()
std::vector<unsigned int> initial_sequence;
if (pattern_row_mode) {
initial_sequence = decode_manual_pattern_ids(normalized_pattern, m_mf.component_a, m_mf.component_b);
initial_sequence = decode_manual_pattern_ids(normalized_pattern,
m_mf.component_a,
m_mf.component_b,
m_num_physical,
m_preview_settings.wall_loops);
} else {
std::vector<unsigned int> initial_gradient_ids = simple_mode ? std::vector<unsigned int>() : decode_gradient_ids(m_mf.gradient_component_ids);
if (initial_gradient_ids.size() >= 3)
@@ -6707,24 +6812,11 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
sequence = filtered_ids;
return sequence;
};
auto decode_manual_pattern_ids = [num_physical](const std::string &pattern, unsigned int component_a, unsigned int component_b) {
std::vector<unsigned int> sequence;
if (num_physical == 0)
return sequence;
const std::string normalized = MixedFilamentManager::normalize_manual_pattern(pattern);
sequence.reserve(normalized.size());
for (const char token : normalized) {
unsigned int extruder_id = 0;
if (token == '1')
extruder_id = component_a;
else if (token == '2')
extruder_id = component_b;
else if (token >= '3' && token <= '9')
extruder_id = unsigned(token - '0');
if (extruder_id >= 1 && extruder_id <= num_physical)
sequence.emplace_back(extruder_id);
}
return sequence;
auto decode_manual_pattern_ids = [num_physical](const std::string &pattern,
unsigned int component_a,
unsigned int component_b,
size_t wall_loops) {
return build_grouped_manual_pattern_preview_sequence(pattern, component_a, component_b, num_physical, wall_loops);
};
const bool height_weighted_mode = get_mixed_mode(false);
int gradient_mode = height_weighted_mode ? 1 : 0;
@@ -6736,6 +6828,9 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
if (print_cfg && print_cfg->has("layer_height"))
nominal_layer_height = float(print_cfg->opt_float("layer_height"));
nominal_layer_height = std::max(0.01f, nominal_layer_height);
size_t wall_loops = 1;
if (print_cfg && print_cfg->has("wall_loops"))
wall_loops = std::max<size_t>(1, size_t(std::max(1, print_cfg->opt_int("wall_loops"))));
const bool local_z_mode = get_mixed_bool("dithering_local_z_mode", false);
float pointillism_pixel_size = std::max(0.f, get_mixed_float("mixed_filament_pointillism_pixel_size", 0.f));
float pointillism_line_gap = std::max(0.f, get_mixed_float("mixed_filament_pointillism_line_gap", 0.f));
@@ -6748,7 +6843,8 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
upper_bound,
preferred_local_z_a,
preferred_local_z_b,
local_z_mode
local_z_mode,
wall_loops
};
auto summarize_sequence = [num_physical](const std::vector<unsigned int> &sequence) {
if (sequence.empty() || num_physical == 0)
@@ -6814,7 +6910,10 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
build_weighted_multi_sequence, preview_settings](const MixedFilament &entry) {
const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(entry.manual_pattern);
if (!normalized_pattern.empty())
return decode_manual_pattern_ids(normalized_pattern, entry.component_a, entry.component_b);
return decode_manual_pattern_ids(normalized_pattern,
entry.component_a,
entry.component_b,
preview_settings.wall_loops);
const bool simple_mode = entry.distribution_mode == int(MixedFilament::Simple);
if (!simple_mode) {
+138
View File
@@ -2,6 +2,8 @@
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/GCode/ToolOrdering.hpp"
#include <sstream>
#include <vector>
@@ -221,6 +223,119 @@ TEST_CASE("Mixed filament perimeter resolver uses grouped manual patterns by ins
CHECK(ordered_layer1[1] == 1);
}
TEST_CASE("Grouped manual perimeter patterns keep grouped resolution on collapsed single-tool layers", "[MixedFilament]")
{
const std::vector<std::string> colors = {"#00FFFF", "#FF00FF"};
MixedFilamentManager mgr;
mgr.add_custom_filament(1, 2, 50, colors);
REQUIRE(mgr.mixed_filaments().size() == 1);
MixedFilament &row = mgr.mixed_filaments().front();
row.manual_pattern = MixedFilamentManager::normalize_manual_pattern("2,12");
REQUIRE(row.manual_pattern == "2,12");
const unsigned int mixed_filament_id = 3;
// The flattened row cadence resolves this layer to component A, but both
// perimeter groups collapse onto physical filament 2. G-code generation
// and tool ordering must keep using the grouped perimeter result here.
CHECK(mgr.resolve(mixed_filament_id, 2, 1) == 1);
const std::vector<unsigned int> ordered_layer1 = mgr.ordered_perimeter_extruders(mixed_filament_id, 2, 1);
REQUIRE(ordered_layer1.size() == 1);
CHECK(ordered_layer1.front() == 2);
CHECK(mgr.resolve_perimeter(mixed_filament_id, 2, 1, 0) == 2);
CHECK(mgr.resolve_perimeter(mixed_filament_id, 2, 1, 1) == 2);
CHECK(mgr.resolve_perimeter(mixed_filament_id, 2, 1, 2) == 2);
}
TEST_CASE("Grouped manual perimeter patterns resolve overlapping singleton inner groups", "[MixedFilament]")
{
const std::vector<std::string> colors = {"#00FFFF", "#FF00FF"};
MixedFilamentManager mgr;
mgr.add_custom_filament(1, 2, 50, colors);
REQUIRE(mgr.mixed_filaments().size() == 1);
MixedFilament &row = mgr.mixed_filaments().front();
row.manual_pattern = MixedFilamentManager::normalize_manual_pattern("12,1");
REQUIRE(row.manual_pattern == "12,1");
const unsigned int mixed_filament_id = 3;
const std::vector<unsigned int> ordered_layer0 = mgr.ordered_perimeter_extruders(mixed_filament_id, 2, 0);
const std::vector<unsigned int> ordered_layer1 = mgr.ordered_perimeter_extruders(mixed_filament_id, 2, 1);
REQUIRE(ordered_layer0.size() == 1);
CHECK(ordered_layer0.front() == 1);
REQUIRE(ordered_layer1.size() == 2);
CHECK(ordered_layer1[0] == 2);
CHECK(ordered_layer1[1] == 1);
CHECK(mgr.resolve_perimeter(mixed_filament_id, 2, 0, 0) == 1);
CHECK(mgr.resolve_perimeter(mixed_filament_id, 2, 0, 1) == 1);
CHECK(mgr.resolve_perimeter(mixed_filament_id, 2, 1, 0) == 2);
CHECK(mgr.resolve_perimeter(mixed_filament_id, 2, 1, 1) == 1);
CHECK(mgr.resolve_perimeter(mixed_filament_id, 2, 2, 0) == 1);
CHECK(mgr.resolve_perimeter(mixed_filament_id, 2, 2, 1) == 1);
}
TEST_CASE("Grouped manual wall patterns make infill follow the innermost perimeter tool", "[MixedFilament]")
{
const std::vector<std::string> colors = {"#00FFFF", "#FF00FF"};
MixedFilamentManager mgr;
mgr.add_custom_filament(1, 2, 50, colors);
REQUIRE(mgr.mixed_filaments().size() == 1);
MixedFilament &row = mgr.mixed_filaments().front();
row.manual_pattern = MixedFilamentManager::normalize_manual_pattern("12,1");
REQUIRE(row.manual_pattern == "12,1");
PrintRegionConfig region_config = static_cast<const PrintRegionConfig &>(FullPrintConfig::defaults());
region_config.wall_filament.value = 3;
region_config.wall_loops.value = 2;
region_config.enable_infill_filament_override.value = false;
region_config.sparse_infill_density.value = 15.;
region_config.sparse_infill_filament.value = 2;
region_config.solid_infill_filament.value = 3;
PrintRegion region(region_config);
LayerTools layer0(0.2);
layer0.layer_index = 0;
layer0.object_layer_count = 6;
layer0.layer_height = 0.2;
layer0.mixed_mgr = &mgr;
layer0.num_physical = 2;
LayerTools layer1(0.4);
layer1.layer_index = 1;
layer1.object_layer_count = 6;
layer1.layer_height = 0.2;
layer1.mixed_mgr = &mgr;
layer1.num_physical = 2;
CHECK(layer0.wall_filament(region) == 0);
CHECK(layer1.wall_filament(region) == 1);
CHECK(layer0.sparse_infill_filament(region) == 0);
CHECK(layer1.sparse_infill_filament(region) == 0);
CHECK(layer0.solid_infill_filament(region) == 0);
CHECK(layer1.solid_infill_filament(region) == 0);
region_config.enable_infill_filament_override.value = true;
region_config.sparse_infill_filament.value = 2;
region_config.solid_infill_filament.value = 2;
PrintRegion overridden_region(region_config);
CHECK(layer0.sparse_infill_filament(overridden_region) == 1);
CHECK(layer1.sparse_infill_filament(overridden_region) == 1);
CHECK(layer0.solid_infill_filament(overridden_region) == 1);
CHECK(layer1.solid_infill_filament(overridden_region) == 1);
}
TEST_CASE("Mixed filament painted-region resolver collapses ordinary mixed rows to the active physical extruder", "[MixedFilament]")
{
const std::vector<std::string> colors = {"#FF0000", "#00FF00"};
@@ -269,3 +384,26 @@ TEST_CASE("ExtrusionPath copies preserve inset index", "[MixedFilament]")
assigned = src;
CHECK(assigned.inset_idx == 3);
}
TEST_CASE("Extrusion loop and multipath entities preserve inset index", "[MixedFilament]")
{
ExtrusionPath src(erPerimeter);
src.inset_idx = 2;
ExtrusionMultiPath multi_from_path(src);
CHECK(multi_from_path.inset_idx == 2);
ExtrusionMultiPath multi_copy(multi_from_path);
CHECK(multi_copy.inset_idx == 2);
ExtrusionMultiPath multi_assigned;
multi_assigned.inset_idx = 0;
multi_assigned = multi_from_path;
CHECK(multi_assigned.inset_idx == 2);
ExtrusionLoop loop_from_path(src);
CHECK(loop_from_path.inset_idx == 2);
ExtrusionLoop loop_copy(loop_from_path);
CHECK(loop_copy.inset_idx == 2);
}