mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 13:32:44 +00:00
.
This commit is contained in:
@@ -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
@@ -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, ®ion]() -> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ®ion) 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 ®ion) 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user