mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-01 07:12:07 +00:00
added support for multi perimeter patterns, issue 34
This commit is contained in:
@@ -170,7 +170,9 @@ public:
|
||||
, m_can_reverse(rhs.m_can_reverse)
|
||||
, m_role(rhs.m_role)
|
||||
, m_no_extrusion(rhs.m_no_extrusion)
|
||||
{}
|
||||
{
|
||||
this->inset_idx = rhs.inset_idx;
|
||||
}
|
||||
ExtrusionPath(ExtrusionPath &&rhs)
|
||||
: polyline(std::move(rhs.polyline))
|
||||
, mm3_per_mm(rhs.mm3_per_mm)
|
||||
@@ -179,7 +181,9 @@ public:
|
||||
, m_can_reverse(rhs.m_can_reverse)
|
||||
, m_role(rhs.m_role)
|
||||
, m_no_extrusion(rhs.m_no_extrusion)
|
||||
{}
|
||||
{
|
||||
this->inset_idx = rhs.inset_idx;
|
||||
}
|
||||
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs)
|
||||
: polyline(polyline)
|
||||
, mm3_per_mm(rhs.mm3_per_mm)
|
||||
@@ -188,7 +192,9 @@ public:
|
||||
, m_can_reverse(rhs.m_can_reverse)
|
||||
, m_role(rhs.m_role)
|
||||
, m_no_extrusion(rhs.m_no_extrusion)
|
||||
{}
|
||||
{
|
||||
this->inset_idx = rhs.inset_idx;
|
||||
}
|
||||
ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs)
|
||||
: polyline(std::move(polyline))
|
||||
, mm3_per_mm(rhs.mm3_per_mm)
|
||||
@@ -197,7 +203,9 @@ public:
|
||||
, m_can_reverse(rhs.m_can_reverse)
|
||||
, m_role(rhs.m_role)
|
||||
, m_no_extrusion(rhs.m_no_extrusion)
|
||||
{}
|
||||
{
|
||||
this->inset_idx = rhs.inset_idx;
|
||||
}
|
||||
|
||||
ExtrusionPath& operator=(const ExtrusionPath& rhs) {
|
||||
m_can_reverse = rhs.m_can_reverse;
|
||||
@@ -207,6 +215,7 @@ public:
|
||||
this->width = rhs.width;
|
||||
this->height = rhs.height;
|
||||
this->polyline = rhs.polyline;
|
||||
this->inset_idx = rhs.inset_idx;
|
||||
return *this;
|
||||
}
|
||||
ExtrusionPath& operator=(ExtrusionPath&& rhs) {
|
||||
@@ -217,6 +226,7 @@ public:
|
||||
this->width = rhs.width;
|
||||
this->height = rhs.height;
|
||||
this->polyline = std::move(rhs.polyline);
|
||||
this->inset_idx = rhs.inset_idx;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -4008,6 +4008,53 @@ static bool split_extrusion_collection_for_pointillism_paths(
|
||||
return out_stats.segment_count > 0;
|
||||
}
|
||||
|
||||
static bool split_extrusion_collection_for_multi_perimeter_pattern(
|
||||
const ExtrusionEntityCollection& source,
|
||||
const MixedFilamentManager& mixed_mgr,
|
||||
unsigned int mixed_filament_id,
|
||||
size_t num_physical,
|
||||
int layer_index,
|
||||
std::vector<std::unique_ptr<ExtrusionEntityCollection>>& out_by_extruder,
|
||||
size_t& out_bucket_count)
|
||||
{
|
||||
out_by_extruder.clear();
|
||||
out_by_extruder.resize(num_physical);
|
||||
out_bucket_count = 0;
|
||||
|
||||
if (source.entities.empty() || num_physical == 0)
|
||||
return false;
|
||||
|
||||
size_t split_entities = 0;
|
||||
ExtrusionEntityCollection flattened = source.flatten(false);
|
||||
for (const ExtrusionEntity* entity : flattened.entities) {
|
||||
if (entity == nullptr)
|
||||
continue;
|
||||
|
||||
int perimeter_index = entity->inset_idx;
|
||||
if (perimeter_index < 0)
|
||||
perimeter_index = entity->role() == erExternalPerimeter ? 0 : 1;
|
||||
|
||||
const unsigned int extruder_id = mixed_mgr.resolve_perimeter(
|
||||
mixed_filament_id, num_physical, layer_index, perimeter_index);
|
||||
if (extruder_id == 0 || extruder_id > num_physical)
|
||||
continue;
|
||||
|
||||
std::unique_ptr<ExtrusionEntityCollection>& bucket = out_by_extruder[extruder_id - 1];
|
||||
if (!bucket) {
|
||||
bucket = std::make_unique<ExtrusionEntityCollection>();
|
||||
bucket->no_sort = source.no_sort;
|
||||
}
|
||||
bucket->append(*entity);
|
||||
++split_entities;
|
||||
}
|
||||
|
||||
for (const std::unique_ptr<ExtrusionEntityCollection>& bucket : out_by_extruder) {
|
||||
if (bucket && !bucket->entities.empty())
|
||||
++out_bucket_count;
|
||||
}
|
||||
return split_entities > 0;
|
||||
}
|
||||
|
||||
inline std::vector<GCode::ObjectByExtruder::Island>& object_islands_by_extruder(
|
||||
std::map<unsigned int, std::vector<GCode::ObjectByExtruder>>& by_extruder,
|
||||
unsigned int extruder_id,
|
||||
@@ -4750,6 +4797,27 @@ LayerResult GCode::process_layer(const Print& print,
|
||||
auto inserted = pointillism_sequence_cache.emplace(filament_id_1based, std::move(sequence));
|
||||
return inserted.first->second.empty() ? nullptr : &inserted.first->second;
|
||||
};
|
||||
auto grouped_manual_pattern_mixed_filament_id =
|
||||
[&layer_tools, &configured_filament_id_1based](const ExtrusionEntityCollection& entities,
|
||||
const PrintRegion& region) -> unsigned int {
|
||||
if (layer_tools.mixed_mgr == nullptr || layer_tools.num_physical == 0)
|
||||
return 0;
|
||||
|
||||
auto has_grouped_pattern = [&layer_tools](unsigned int filament_id_1based) -> bool {
|
||||
if (!layer_tools.mixed_mgr->is_mixed(filament_id_1based, layer_tools.num_physical))
|
||||
return false;
|
||||
const MixedFilament* mixed_row = layer_tools.mixed_mgr->mixed_filament_from_id(filament_id_1based, layer_tools.num_physical);
|
||||
if (mixed_row == nullptr)
|
||||
return false;
|
||||
const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(mixed_row->manual_pattern);
|
||||
return normalized_pattern.find(',') != std::string::npos;
|
||||
};
|
||||
|
||||
const unsigned int configured_filament_id = configured_filament_id_1based(entities, region);
|
||||
if (has_grouped_pattern(configured_filament_id))
|
||||
return configured_filament_id;
|
||||
return 0;
|
||||
};
|
||||
// Compensate perimeter clipping at mixed-mask boundaries to avoid cracks from exact centerline clipping.
|
||||
constexpr double LOCAL_Z_PERIMETER_MASK_EXPAND_MM = 0.10;
|
||||
// Keep base exclusion smaller than mixed-pass inclusion to guarantee a slight overlap
|
||||
@@ -5216,6 +5284,47 @@ LayerResult GCode::process_layer(const Print& print,
|
||||
|
||||
// This extrusion is part of certain Region, which tells us which extruder should be used for it:
|
||||
int correct_extruder_id = layer_tools.extruder(*filtered_extrusions, region);
|
||||
if (!is_anything_overridden &&
|
||||
entity_type == ObjectByExtruder::Island::Region::PERIMETERS &&
|
||||
layer_tools.mixed_mgr != nullptr &&
|
||||
layer_tools.num_physical > 0 &&
|
||||
correct_extruder_id >= 0) {
|
||||
const unsigned int mixed_filament_id =
|
||||
grouped_manual_pattern_mixed_filament_id(*filtered_extrusions, region);
|
||||
if (mixed_filament_id != 0) {
|
||||
std::vector<std::unique_ptr<ExtrusionEntityCollection>> split_by_extruder;
|
||||
size_t bucket_count = 0;
|
||||
if (split_extrusion_collection_for_multi_perimeter_pattern(*filtered_extrusions,
|
||||
*layer_tools.mixed_mgr,
|
||||
mixed_filament_id,
|
||||
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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let's recover vector of extruder overrides:
|
||||
const WipingExtrusions::ExtruderPerCopy* entity_overrides = nullptr;
|
||||
|
||||
@@ -60,6 +60,45 @@ unsigned int resolve_mixed_with_layer_heights(const MixedFilamentManager *mixed_
|
||||
return mixed_mgr->resolve(filament_id_1based, num_physical, layer_index, layer_print_z, layer_height);
|
||||
}
|
||||
|
||||
bool has_grouped_manual_pattern(const MixedFilamentManager *mixed_mgr,
|
||||
size_t num_physical,
|
||||
unsigned int filament_id_1based)
|
||||
{
|
||||
if (!(mixed_mgr && mixed_mgr->is_mixed(filament_id_1based, num_physical)))
|
||||
return false;
|
||||
const MixedFilament *mixed_row = mixed_mgr->mixed_filament_from_id(filament_id_1based, num_physical);
|
||||
if (mixed_row == nullptr)
|
||||
return false;
|
||||
const std::string normalized = MixedFilamentManager::normalize_manual_pattern(mixed_row->manual_pattern);
|
||||
return normalized.find(',') != std::string::npos;
|
||||
}
|
||||
|
||||
void append_unique_preserve_order(std::vector<unsigned int> &dst, unsigned int value)
|
||||
{
|
||||
if (std::find(dst.begin(), dst.end(), value) == dst.end())
|
||||
dst.emplace_back(value);
|
||||
}
|
||||
|
||||
unsigned int grouped_manual_pattern_mixed_filament_id_for_layer(const LayerTools& layer_tools,
|
||||
unsigned int configured_filament_id_1based)
|
||||
{
|
||||
if (layer_tools.mixed_mgr == nullptr || layer_tools.num_physical == 0)
|
||||
return 0;
|
||||
|
||||
if (has_grouped_manual_pattern(layer_tools.mixed_mgr, layer_tools.num_physical, configured_filament_id_1based))
|
||||
return configured_filament_id_1based;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void remove_duplicates_preserve_order(std::vector<unsigned int> &values)
|
||||
{
|
||||
std::vector<unsigned int> ordered;
|
||||
ordered.reserve(values.size());
|
||||
for (unsigned int value : values)
|
||||
append_unique_preserve_order(ordered, value);
|
||||
values = std::move(ordered);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -639,11 +678,34 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
||||
}
|
||||
|
||||
if (something_nonoverriddable){
|
||||
unsigned int wall_ext = (extruder_override == 0) ? region.config().wall_filament.value : extruder_override;
|
||||
wall_ext = resolve_mixed(wall_ext, layerCount, float(layer->print_z), float(layer->height));
|
||||
layer_tools.extruders.emplace_back(wall_ext);
|
||||
if (layerCount == 0) {
|
||||
firstLayerExtruders.emplace_back(wall_ext);
|
||||
const unsigned int configured_wall = (extruder_override == 0) ? region.config().wall_filament.value : extruder_override;
|
||||
unsigned int wall_ext = resolve_mixed(configured_wall, layerCount, float(layer->print_z), float(layer->height));
|
||||
const unsigned int grouped_id =
|
||||
grouped_manual_pattern_mixed_filament_id_for_layer(layer_tools, configured_wall);
|
||||
if (grouped_id != 0) {
|
||||
const std::vector<unsigned int> ordered =
|
||||
m_mixed_mgr->ordered_perimeter_extruders(grouped_id,
|
||||
m_num_physical,
|
||||
layerCount,
|
||||
float(layer->print_z),
|
||||
float(layer->height));
|
||||
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 &&
|
||||
std::find(firstLayerExtruders.begin(), firstLayerExtruders.end(), int(extruder_id)) == firstLayerExtruders.end())
|
||||
firstLayerExtruders.emplace_back(int(extruder_id));
|
||||
}
|
||||
} else {
|
||||
layer_tools.extruders.emplace_back(wall_ext);
|
||||
if (layerCount == 0)
|
||||
firstLayerExtruders.emplace_back(wall_ext);
|
||||
}
|
||||
} else {
|
||||
layer_tools.extruders.emplace_back(wall_ext);
|
||||
if (layerCount == 0)
|
||||
firstLayerExtruders.emplace_back(wall_ext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,8 +758,10 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
||||
const_cast<PrintObject&>(object).object_first_layer_wall_extruders = firstLayerExtruders;
|
||||
|
||||
for (auto& layer : m_layer_tools) {
|
||||
// Sort and remove duplicates
|
||||
sort_remove_duplicates(layer.extruders);
|
||||
if (layer.preserve_extruder_order)
|
||||
remove_duplicates_preserve_order(layer.extruders);
|
||||
else
|
||||
sort_remove_duplicates(layer.extruders);
|
||||
|
||||
// make sure that there are some tools for each object layer (e.g. tall wiping object will result in empty extruders vector)
|
||||
if (layer.extruders.empty() && layer.has_object)
|
||||
@@ -739,6 +803,10 @@ void ToolOrdering::reorder_extruders(unsigned int last_extruder_id)
|
||||
if (lt.extruders.front() == 0)
|
||||
// Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
|
||||
lt.extruders.erase(lt.extruders.begin());
|
||||
if (lt.preserve_extruder_order) {
|
||||
last_extruder_id = lt.extruders.back();
|
||||
continue;
|
||||
}
|
||||
// Reorder the extruders to start with the last one.
|
||||
for (size_t i = 1; i < lt.extruders.size(); ++ i)
|
||||
if (lt.extruders[i] == last_extruder_id) {
|
||||
@@ -789,27 +857,29 @@ void ToolOrdering::reorder_extruders(std::vector<unsigned int> tool_order_layer0
|
||||
// Reorder the extruders of first layer
|
||||
{
|
||||
LayerTools& lt = m_layer_tools[0];
|
||||
std::vector<unsigned int> layer0_extruders = lt.extruders;
|
||||
lt.extruders.clear();
|
||||
for (unsigned int extruder_id : tool_order_layer0) {
|
||||
auto iter = std::find(layer0_extruders.begin(), layer0_extruders.end(), extruder_id);
|
||||
if (iter != layer0_extruders.end()) {
|
||||
lt.extruders.push_back(extruder_id);
|
||||
*iter = (unsigned int)-1;
|
||||
if (!lt.preserve_extruder_order) {
|
||||
std::vector<unsigned int> layer0_extruders = lt.extruders;
|
||||
lt.extruders.clear();
|
||||
for (unsigned int extruder_id : tool_order_layer0) {
|
||||
auto iter = std::find(layer0_extruders.begin(), layer0_extruders.end(), extruder_id);
|
||||
if (iter != layer0_extruders.end()) {
|
||||
lt.extruders.push_back(extruder_id);
|
||||
*iter = (unsigned int)-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int extruder_id : layer0_extruders) {
|
||||
if (extruder_id == 0)
|
||||
continue;
|
||||
for (unsigned int extruder_id : layer0_extruders) {
|
||||
if (extruder_id == 0)
|
||||
continue;
|
||||
|
||||
if (extruder_id != (unsigned int)-1)
|
||||
lt.extruders.push_back(extruder_id);
|
||||
}
|
||||
if (extruder_id != (unsigned int)-1)
|
||||
lt.extruders.push_back(extruder_id);
|
||||
}
|
||||
|
||||
// all extruders are zero
|
||||
if (lt.extruders.empty()) {
|
||||
lt.extruders.push_back(tool_order_layer0[0]);
|
||||
// all extruders are zero
|
||||
if (lt.extruders.empty()) {
|
||||
lt.extruders.push_back(tool_order_layer0[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -825,6 +895,10 @@ void ToolOrdering::reorder_extruders(std::vector<unsigned int> tool_order_layer0
|
||||
if (lt.extruders.front() == 0)
|
||||
// Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
|
||||
lt.extruders.erase(lt.extruders.begin());
|
||||
if (lt.preserve_extruder_order) {
|
||||
last_extruder_id = lt.extruders.back();
|
||||
continue;
|
||||
}
|
||||
// Reorder the extruders to start with the last one.
|
||||
for (size_t i = 1; i < lt.extruders.size(); ++i)
|
||||
if (lt.extruders[i] == last_extruder_id) {
|
||||
@@ -1039,6 +1113,10 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume()
|
||||
LayerTools& lt = m_layer_tools[i];
|
||||
if (lt.extruders.empty())
|
||||
continue;
|
||||
if (lt.preserve_extruder_order) {
|
||||
current_extruder_id = lt.extruders.back();
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<int> custom_extruder_seq;
|
||||
if (get_custom_seq(i, custom_extruder_seq) && !custom_extruder_seq.empty()) {
|
||||
|
||||
@@ -117,6 +117,7 @@ public:
|
||||
bool has_support = false;
|
||||
// Zero based extruder IDs, ordered to minimize tool switches.
|
||||
std::vector<unsigned int> extruders;
|
||||
bool preserve_extruder_order = false;
|
||||
// If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with.
|
||||
// If not overriden, it is set to 0.
|
||||
unsigned int extruder_override = 0;
|
||||
|
||||
@@ -383,7 +383,7 @@ static bool parse_row_definition(const std::string &row,
|
||||
while (std::getline(ss, token, ','))
|
||||
tokens.emplace_back(trim_copy(token));
|
||||
|
||||
if (tokens.size() < 4 || tokens.size() > 13)
|
||||
if (tokens.size() < 4)
|
||||
return false;
|
||||
|
||||
int values[5] = { 0, 0, 1, 1, 50 };
|
||||
@@ -436,6 +436,10 @@ static bool parse_row_definition(const std::string &row,
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> pattern_tokens;
|
||||
pattern_tokens.reserve(tokens.size() > token_idx ? tokens.size() - token_idx : 1);
|
||||
if (!manual_pattern.empty())
|
||||
pattern_tokens.push_back(manual_pattern);
|
||||
for (size_t i = token_idx; i < tokens.size(); ++i) {
|
||||
const std::string &tok = tokens[i];
|
||||
if (tok.empty())
|
||||
@@ -472,7 +476,17 @@ static bool parse_row_definition(const std::string &row,
|
||||
stable_id = parsed_stable_id;
|
||||
continue;
|
||||
}
|
||||
manual_pattern = tok;
|
||||
pattern_tokens.push_back(tok);
|
||||
}
|
||||
|
||||
if (!pattern_tokens.empty()) {
|
||||
std::ostringstream joined_pattern;
|
||||
for (size_t i = 0; i < pattern_tokens.size(); ++i) {
|
||||
if (i != 0)
|
||||
joined_pattern << ',';
|
||||
joined_pattern << pattern_tokens[i];
|
||||
}
|
||||
manual_pattern = joined_pattern.str();
|
||||
}
|
||||
|
||||
// Compatibility for early same-layer prototype rows.
|
||||
@@ -504,14 +518,69 @@ static bool decode_pattern_step(char c, char &out)
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<std::string> split_manual_pattern_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 std::string flatten_manual_pattern_groups(const std::string &pattern)
|
||||
{
|
||||
std::string flattened;
|
||||
flattened.reserve(pattern.size());
|
||||
for (const char c : pattern)
|
||||
if (c != ',')
|
||||
flattened.push_back(c);
|
||||
return flattened;
|
||||
}
|
||||
|
||||
static unsigned int physical_filament_from_pattern_step(char token, const MixedFilament &mf, size_t num_physical)
|
||||
{
|
||||
if (token == '1')
|
||||
return mf.component_a;
|
||||
if (token == '2')
|
||||
return mf.component_b;
|
||||
if (token >= '3' && token <= '9') {
|
||||
const unsigned int direct = unsigned(token - '0');
|
||||
if (direct >= 1 && direct <= num_physical)
|
||||
return direct;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mix_percent_from_normalized_pattern(const std::string &pattern)
|
||||
{
|
||||
if (pattern.empty())
|
||||
const std::vector<std::string> groups = split_manual_pattern_groups(pattern);
|
||||
if (groups.empty())
|
||||
return 50;
|
||||
// Legacy blend ratio for UI preview: count component-B aliases only.
|
||||
// Tokens '3'..'9' are direct physical filament IDs and are ignored here.
|
||||
const int count_b = int(std::count(pattern.begin(), pattern.end(), '2'));
|
||||
return clamp_int(int(std::lround(100.0 * double(count_b) / double(pattern.size()))), 0, 100);
|
||||
|
||||
// For grouped patterns, blend preview is the average of each perimeter
|
||||
// group's own cadence. This keeps simple outer/inner patterns like
|
||||
// "12,21" at 50/50 and "11111112,11121111" at 12.5%.
|
||||
double blend_b = 0.0;
|
||||
for (const std::string &group : groups) {
|
||||
if (group.empty())
|
||||
continue;
|
||||
const int count_b = int(std::count(group.begin(), group.end(), '2'));
|
||||
blend_b += double(count_b) / double(group.size());
|
||||
}
|
||||
return clamp_int(int(std::lround(100.0 * blend_b / double(groups.size()))), 0, 100);
|
||||
}
|
||||
|
||||
static std::string normalize_gradient_component_ids(const std::string &components)
|
||||
@@ -859,10 +928,19 @@ std::string MixedFilamentManager::normalize_manual_pattern(const std::string &pa
|
||||
{
|
||||
std::string normalized;
|
||||
normalized.reserve(pattern.size());
|
||||
bool current_group_has_steps = false;
|
||||
for (char c : pattern) {
|
||||
char step = '\0';
|
||||
if (decode_pattern_step(c, step)) {
|
||||
normalized.push_back(step);
|
||||
current_group_has_steps = true;
|
||||
continue;
|
||||
}
|
||||
if (c == ',') {
|
||||
if (!current_group_has_steps)
|
||||
return std::string();
|
||||
normalized.push_back(',');
|
||||
current_group_has_steps = false;
|
||||
continue;
|
||||
}
|
||||
if (is_pattern_separator(c))
|
||||
@@ -870,9 +948,16 @@ std::string MixedFilamentManager::normalize_manual_pattern(const std::string &pa
|
||||
// Unknown token => invalid pattern.
|
||||
return std::string();
|
||||
}
|
||||
if (!normalized.empty() && normalized.back() == ',')
|
||||
return std::string();
|
||||
return normalized;
|
||||
}
|
||||
|
||||
int MixedFilamentManager::mix_percent_from_manual_pattern(const std::string &pattern)
|
||||
{
|
||||
return mix_percent_from_normalized_pattern(normalize_manual_pattern(pattern));
|
||||
}
|
||||
|
||||
void MixedFilamentManager::apply_gradient_settings(int gradient_mode,
|
||||
float lower_bound,
|
||||
float upper_bound,
|
||||
@@ -1119,16 +1204,12 @@ unsigned int MixedFilamentManager::resolve(unsigned int filament_id,
|
||||
// steps: '1' => component_a, '2' => component_b, '3'..'9' => direct
|
||||
// physical filament IDs.
|
||||
if (!mf.manual_pattern.empty()) {
|
||||
const int pos = safe_mod(layer_index, int(mf.manual_pattern.size()));
|
||||
const char token = mf.manual_pattern[size_t(pos)];
|
||||
if (token == '2')
|
||||
return mf.component_b;
|
||||
if (token == '1')
|
||||
return mf.component_a;
|
||||
if (token >= '3' && token <= '9') {
|
||||
const unsigned int direct = unsigned(token - '0');
|
||||
if (direct >= 1 && direct <= num_physical)
|
||||
return direct;
|
||||
const std::string flattened_pattern = flatten_manual_pattern_groups(mf.manual_pattern);
|
||||
if (!flattened_pattern.empty()) {
|
||||
const int pos = safe_mod(layer_index, int(flattened_pattern.size()));
|
||||
const unsigned int resolved = physical_filament_from_pattern_step(flattened_pattern[size_t(pos)], mf, num_physical);
|
||||
if (resolved >= 1 && resolved <= num_physical)
|
||||
return resolved;
|
||||
}
|
||||
return mf.component_a;
|
||||
}
|
||||
@@ -1174,6 +1255,78 @@ unsigned int MixedFilamentManager::resolve(unsigned int filament_id,
|
||||
return (pos < mf.ratio_a) ? mf.component_a : mf.component_b;
|
||||
}
|
||||
|
||||
unsigned int MixedFilamentManager::resolve_perimeter(unsigned int filament_id,
|
||||
size_t num_physical,
|
||||
int layer_index,
|
||||
int perimeter_index,
|
||||
float layer_print_z,
|
||||
float layer_height,
|
||||
bool force_height_weighted) const
|
||||
{
|
||||
const int mixed_idx = mixed_index_from_filament_id(filament_id, num_physical);
|
||||
if (mixed_idx < 0)
|
||||
return filament_id;
|
||||
|
||||
const MixedFilament &mf = m_mixed[size_t(mixed_idx)];
|
||||
if (!mf.manual_pattern.empty()) {
|
||||
const std::vector<std::string> pattern_groups = split_manual_pattern_groups(mf.manual_pattern);
|
||||
if (!pattern_groups.empty()) {
|
||||
const size_t group_idx = size_t(std::max(0, perimeter_index));
|
||||
const std::string &group = pattern_groups[std::min(group_idx, pattern_groups.size() - 1)];
|
||||
if (!group.empty()) {
|
||||
const int pos = safe_mod(layer_index, int(group.size()));
|
||||
const unsigned int resolved = physical_filament_from_pattern_step(group[size_t(pos)], mf, num_physical);
|
||||
if (resolved >= 1 && resolved <= num_physical)
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resolve(filament_id, num_physical, layer_index, layer_print_z, layer_height, force_height_weighted);
|
||||
}
|
||||
|
||||
std::vector<unsigned int> MixedFilamentManager::ordered_perimeter_extruders(unsigned int filament_id,
|
||||
size_t num_physical,
|
||||
int layer_index,
|
||||
float layer_print_z,
|
||||
float layer_height,
|
||||
bool force_height_weighted) const
|
||||
{
|
||||
std::vector<unsigned int> ordered;
|
||||
|
||||
const int mixed_idx = mixed_index_from_filament_id(filament_id, num_physical);
|
||||
if (mixed_idx < 0) {
|
||||
ordered.emplace_back(filament_id);
|
||||
return ordered;
|
||||
}
|
||||
|
||||
const MixedFilament &mf = m_mixed[size_t(mixed_idx)];
|
||||
if (!mf.manual_pattern.empty()) {
|
||||
const std::vector<std::string> pattern_groups = split_manual_pattern_groups(mf.manual_pattern);
|
||||
if (!pattern_groups.empty()) {
|
||||
ordered.reserve(pattern_groups.size());
|
||||
for (size_t group_idx = 0; group_idx < pattern_groups.size(); ++group_idx) {
|
||||
const unsigned int resolved = resolve_perimeter(filament_id,
|
||||
num_physical,
|
||||
layer_index,
|
||||
int(group_idx),
|
||||
layer_print_z,
|
||||
layer_height,
|
||||
force_height_weighted);
|
||||
if (resolved < 1 || resolved > num_physical)
|
||||
continue;
|
||||
if (std::find(ordered.begin(), ordered.end(), resolved) == ordered.end())
|
||||
ordered.emplace_back(resolved);
|
||||
}
|
||||
if (!ordered.empty())
|
||||
return ordered;
|
||||
}
|
||||
}
|
||||
|
||||
ordered.emplace_back(resolve(filament_id, num_physical, layer_index, layer_print_z, layer_height, force_height_weighted));
|
||||
return ordered;
|
||||
}
|
||||
|
||||
int MixedFilamentManager::mixed_index_from_filament_id(unsigned int filament_id, size_t num_physical) const
|
||||
{
|
||||
if (filament_id <= num_physical)
|
||||
|
||||
@@ -146,6 +146,7 @@ public:
|
||||
// Normalize a manual mixed-pattern string into compact token form.
|
||||
// Accepts separators and A/B aliases. Returns empty string if invalid.
|
||||
static std::string normalize_manual_pattern(const std::string &pattern);
|
||||
static int mix_percent_from_manual_pattern(const std::string &pattern);
|
||||
|
||||
// ---- Queries --------------------------------------------------------
|
||||
|
||||
@@ -164,6 +165,19 @@ public:
|
||||
float layer_print_z = 0.f,
|
||||
float layer_height = 0.f,
|
||||
bool force_height_weighted = false) const;
|
||||
unsigned int resolve_perimeter(unsigned int filament_id,
|
||||
size_t num_physical,
|
||||
int layer_index,
|
||||
int perimeter_index,
|
||||
float layer_print_z = 0.f,
|
||||
float layer_height = 0.f,
|
||||
bool force_height_weighted = false) const;
|
||||
std::vector<unsigned int> ordered_perimeter_extruders(unsigned int filament_id,
|
||||
size_t num_physical,
|
||||
int layer_index,
|
||||
float layer_print_z = 0.f,
|
||||
float layer_height = 0.f,
|
||||
bool force_height_weighted = false) const;
|
||||
|
||||
// Map virtual filament ID (1-based, after physical IDs) to index into
|
||||
// m_mixed. Virtual IDs enumerate enabled mixed rows only.
|
||||
|
||||
Reference in New Issue
Block a user