mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 19:01:02 +00:00
Revert "Forced pattern , separed to > 9 extruder"
This reverts commit 42b506ad85.
This commit is contained in:
+38
-33
@@ -31,7 +31,6 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <limits>
|
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -3797,35 +3796,25 @@ size_t GCode::get_extruder_id(unsigned int filament_id) const
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Decode a manual pattern string (e.g. "1,2,12,11") into a sequence of
|
// Decode a manual pattern string (e.g. "121212") into a sequence of
|
||||||
// 1-based physical extruder IDs.
|
// 1-based physical extruder IDs, honouring component_a / component_b aliases.
|
||||||
static std::vector<unsigned int> decode_manual_pattern_sequence_for_gcode(
|
static std::vector<unsigned int> decode_manual_pattern_sequence_for_gcode(
|
||||||
const MixedFilament& mf, size_t num_physical)
|
const MixedFilament& mf, size_t num_physical)
|
||||||
{
|
{
|
||||||
std::vector<unsigned int> sequence;
|
std::vector<unsigned int> sequence;
|
||||||
if (mf.manual_pattern.empty() || num_physical == 0)
|
if (mf.manual_pattern.empty())
|
||||||
return sequence;
|
return sequence;
|
||||||
|
sequence.reserve(mf.manual_pattern.size());
|
||||||
const std::string normalized = MixedFilamentManager::normalize_manual_pattern(mf.manual_pattern);
|
for (const char token : mf.manual_pattern) {
|
||||||
if (normalized.empty())
|
unsigned int extruder_id = 0;
|
||||||
return sequence;
|
if (token == '1')
|
||||||
|
extruder_id = mf.component_a;
|
||||||
std::stringstream ss(normalized);
|
else if (token == '2')
|
||||||
std::string token;
|
extruder_id = mf.component_b;
|
||||||
while (std::getline(ss, token, ',')) {
|
else if (token >= '3' && token <= '9')
|
||||||
if (token.empty())
|
extruder_id = unsigned(token - '0');
|
||||||
continue;
|
if (extruder_id >= 1 && extruder_id <= num_physical)
|
||||||
try {
|
sequence.emplace_back(extruder_id);
|
||||||
size_t consumed = 0;
|
|
||||||
const unsigned long value = std::stoul(token, &consumed);
|
|
||||||
if (consumed != token.size() || value == 0 || value > std::numeric_limits<unsigned int>::max())
|
|
||||||
continue;
|
|
||||||
const unsigned int extruder_id = static_cast<unsigned int>(value);
|
|
||||||
if (extruder_id >= 1 && extruder_id <= num_physical)
|
|
||||||
sequence.emplace_back(extruder_id);
|
|
||||||
} catch (...) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return sequence;
|
return sequence;
|
||||||
}
|
}
|
||||||
@@ -5834,16 +5823,32 @@ LayerResult GCode::process_layer(
|
|||||||
return inserted.first->second.empty() ? nullptr : &inserted.first->second;
|
return inserted.first->second.empty() ? nullptr : &inserted.first->second;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Legacy grouped-perimeter pattern split is disabled for numeric comma-delimited
|
// Lambda: if `entity_type == PERIMETERS` and the configured filament has a grouped
|
||||||
// manual patterns; commas now delimit physical filament IDs.
|
// (comma-containing) manual pattern, return that filament's virtual 1-based ID so
|
||||||
|
// that split_extrusion_collection_for_multi_perimeter_pattern() can be used.
|
||||||
|
// Returns 0 when no grouped pattern applies.
|
||||||
auto grouped_manual_pattern_mixed_filament_id =
|
auto grouped_manual_pattern_mixed_filament_id =
|
||||||
[&](const GCode::ObjectByExtruder::Island::Region::Type entity_type,
|
[&](const GCode::ObjectByExtruder::Island::Region::Type entity_type,
|
||||||
const ExtrusionEntityCollection& entities,
|
const ExtrusionEntityCollection& entities,
|
||||||
const PrintRegion& region) -> unsigned int {
|
const PrintRegion& region) -> unsigned int {
|
||||||
(void)entity_type;
|
|
||||||
(void)entities;
|
(void)entities;
|
||||||
(void)region;
|
if (layer_tools.mixed_mgr == nullptr || layer_tools.num_physical == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
if (entity_type != ObjectByExtruder::Island::Region::PERIMETERS)
|
||||||
|
return 0;
|
||||||
|
unsigned int filament_id_1based =
|
||||||
|
layer_tools.extruder_override != 0 ?
|
||||||
|
layer_tools.extruder_override :
|
||||||
|
unsigned(region.config().wall_filament.value);
|
||||||
|
if (!layer_tools.mixed_mgr->is_mixed(filament_id_1based, layer_tools.num_physical))
|
||||||
|
return 0;
|
||||||
|
const MixedFilament* mixed_row = layer_tools.mixed_mgr->mixed_filament_from_id(
|
||||||
|
filament_id_1based, layer_tools.num_physical);
|
||||||
|
if (mixed_row == nullptr)
|
||||||
|
return 0;
|
||||||
|
const std::string normalized_pattern =
|
||||||
|
MixedFilamentManager::normalize_manual_pattern(mixed_row->manual_pattern);
|
||||||
|
return normalized_pattern.find(',') != std::string::npos ? filament_id_1based : 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Storage for dynamically-created split collections that must outlive the
|
// Storage for dynamically-created split collections that must outlive the
|
||||||
@@ -6112,7 +6117,7 @@ LayerResult GCode::process_layer(
|
|||||||
correct_extruder_id >= 0) {
|
correct_extruder_id >= 0) {
|
||||||
|
|
||||||
// 1. Uniform-segment pointillism (SameLayerPointillisme with a
|
// 1. Uniform-segment pointillism (SameLayerPointillisme with a
|
||||||
// simple ratio or a numeric manual pattern sequence).
|
// simple ratio or a non-comma manual pattern).
|
||||||
const unsigned int cfg_filament_id_1based =
|
const unsigned int cfg_filament_id_1based =
|
||||||
layer_tools.extruder_override != 0 ?
|
layer_tools.extruder_override != 0 ?
|
||||||
layer_tools.extruder_override :
|
layer_tools.extruder_override :
|
||||||
@@ -6180,8 +6185,8 @@ LayerResult GCode::process_layer(
|
|||||||
++pointillism_path_split_fallbacks;
|
++pointillism_path_split_fallbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Legacy grouped per-perimeter-index pattern path.
|
// 2. Grouped per-perimeter-index pattern (comma-separated manual
|
||||||
// Kept for compatibility plumbing; currently disabled.
|
// pattern, e.g. "12,21"). Uses resolve_perimeter() via inset_idx.
|
||||||
if (entity_type == ObjectByExtruder::Island::Region::PERIMETERS) {
|
if (entity_type == ObjectByExtruder::Island::Region::PERIMETERS) {
|
||||||
const unsigned int grouped_id =
|
const unsigned int grouped_id =
|
||||||
grouped_manual_pattern_mixed_filament_id(
|
grouped_manual_pattern_mixed_filament_id(
|
||||||
|
|||||||
@@ -81,10 +81,13 @@ bool has_grouped_manual_pattern(const MixedFilamentManager *mixed_mgr,
|
|||||||
size_t num_physical,
|
size_t num_physical,
|
||||||
unsigned int filament_id_1based)
|
unsigned int filament_id_1based)
|
||||||
{
|
{
|
||||||
(void)mixed_mgr;
|
if (!(mixed_mgr && mixed_mgr->is_mixed(filament_id_1based, num_physical)))
|
||||||
(void)num_physical;
|
return false;
|
||||||
(void)filament_id_1based;
|
const MixedFilament *mixed_row = mixed_mgr->mixed_filament_from_id(filament_id_1based, num_physical);
|
||||||
return false;
|
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)
|
void append_unique_preserve_order(std::vector<unsigned int> &dst, unsigned int value)
|
||||||
|
|||||||
+164
-124
@@ -8,7 +8,6 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <limits>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
@@ -605,108 +604,69 @@ static bool decode_pattern_step(char c, char &out)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string trim_manual_pattern_token(const std::string &token)
|
static std::vector<std::string> split_manual_pattern_groups(const std::string &pattern)
|
||||||
{
|
{
|
||||||
size_t lo = 0;
|
std::vector<std::string> groups;
|
||||||
size_t hi = token.size();
|
|
||||||
while (lo < hi && std::isspace(static_cast<unsigned char>(token[lo])))
|
|
||||||
++lo;
|
|
||||||
while (hi > lo && std::isspace(static_cast<unsigned char>(token[hi - 1])))
|
|
||||||
--hi;
|
|
||||||
return token.substr(lo, hi - lo);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool parse_manual_pattern_id_token(const std::string &token, unsigned int &out)
|
|
||||||
{
|
|
||||||
const std::string trimmed = trim_manual_pattern_token(token);
|
|
||||||
if (trimmed.empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (const char c : trimmed)
|
|
||||||
if (!std::isdigit(static_cast<unsigned char>(c)))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
size_t consumed = 0;
|
|
||||||
const unsigned long value = std::stoul(trimmed, &consumed);
|
|
||||||
if (consumed != trimmed.size() || value == 0 || value > std::numeric_limits<unsigned int>::max())
|
|
||||||
return false;
|
|
||||||
out = static_cast<unsigned int>(value);
|
|
||||||
return true;
|
|
||||||
} catch (...) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool parse_manual_pattern_numeric_ids(const std::string &pattern, std::vector<unsigned int> &out)
|
|
||||||
{
|
|
||||||
out.clear();
|
|
||||||
if (pattern.empty())
|
if (pattern.empty())
|
||||||
return false;
|
return groups;
|
||||||
|
|
||||||
std::stringstream ss(pattern);
|
std::string current;
|
||||||
std::string token;
|
for (const char c : pattern) {
|
||||||
while (std::getline(ss, token, ',')) {
|
if (c == ',') {
|
||||||
unsigned int id = 0;
|
if (!current.empty()) {
|
||||||
if (!parse_manual_pattern_id_token(token, id))
|
groups.emplace_back(std::move(current));
|
||||||
return false;
|
current.clear();
|
||||||
out.emplace_back(id);
|
}
|
||||||
}
|
|
||||||
return !out.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string encode_manual_pattern_ids(const std::vector<unsigned int> &ids)
|
|
||||||
{
|
|
||||||
if (ids.empty())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
std::ostringstream out;
|
|
||||||
bool first = true;
|
|
||||||
for (const unsigned int id : ids) {
|
|
||||||
if (id == 0)
|
|
||||||
continue;
|
continue;
|
||||||
if (!first)
|
}
|
||||||
out << ',';
|
current.push_back(c);
|
||||||
first = false;
|
|
||||||
out << id;
|
|
||||||
}
|
}
|
||||||
return out.str();
|
if (!current.empty())
|
||||||
|
groups.emplace_back(std::move(current));
|
||||||
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<unsigned int> decode_manual_pattern_ids_for_physical(const std::string &pattern, size_t num_physical)
|
static std::string flatten_manual_pattern_groups(const std::string &pattern)
|
||||||
{
|
{
|
||||||
std::vector<unsigned int> parsed;
|
std::string flattened;
|
||||||
if (!parse_manual_pattern_numeric_ids(pattern, parsed) || num_physical == 0)
|
flattened.reserve(pattern.size());
|
||||||
return {};
|
for (const char c : pattern)
|
||||||
|
if (c != ',')
|
||||||
|
flattened.push_back(c);
|
||||||
|
return flattened;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<unsigned int> out;
|
static unsigned int physical_filament_from_pattern_step(char token, const MixedFilament &mf, size_t num_physical)
|
||||||
out.reserve(parsed.size());
|
{
|
||||||
for (const unsigned int id : parsed)
|
if (token == '1')
|
||||||
if (id >= 1 && id <= num_physical)
|
return mf.component_a;
|
||||||
out.emplace_back(id);
|
if (token == '2')
|
||||||
return out;
|
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)
|
static int mix_percent_from_normalized_pattern(const std::string &pattern)
|
||||||
{
|
{
|
||||||
std::vector<unsigned int> ids;
|
const std::vector<std::string> groups = split_manual_pattern_groups(pattern);
|
||||||
if (!parse_manual_pattern_numeric_ids(pattern, ids) || ids.empty())
|
if (groups.empty())
|
||||||
return 50;
|
return 50;
|
||||||
|
|
||||||
const unsigned int first = ids.front();
|
// For grouped patterns, blend preview is the average of each perimeter
|
||||||
unsigned int second = 0;
|
// group's own cadence. This keeps simple outer/inner patterns like
|
||||||
for (const unsigned int id : ids) {
|
// "12,21" at 50/50 and "11111112,11121111" at 12.5%.
|
||||||
if (id != first) {
|
double blend_b = 0.0;
|
||||||
second = id;
|
for (const std::string &group : groups) {
|
||||||
break;
|
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);
|
||||||
if (second == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
const int count_second = int(std::count(ids.begin(), ids.end(), second));
|
|
||||||
return clamp_int(int(std::lround(100.0 * double(count_second) / double(ids.size()))), 0, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string normalize_gradient_component_ids(const std::string &components)
|
static std::string normalize_gradient_component_ids(const std::string &components)
|
||||||
@@ -924,16 +884,25 @@ static std::vector<unsigned int> build_weighted_gradient_sequence(const std::vec
|
|||||||
return sequence;
|
return sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
static std::vector<unsigned int> build_grouped_manual_pattern_preview_sequence(const std::string &pattern,
|
||||||
unsigned int component_a,
|
unsigned int component_a,
|
||||||
unsigned int component_b,
|
unsigned int component_b,
|
||||||
size_t num_physical,
|
size_t num_physical,
|
||||||
size_t wall_loops)
|
size_t wall_loops)
|
||||||
{
|
{
|
||||||
(void)component_a;
|
|
||||||
(void)component_b;
|
|
||||||
(void)wall_loops;
|
|
||||||
|
|
||||||
std::vector<unsigned int> sequence;
|
std::vector<unsigned int> sequence;
|
||||||
if (num_physical == 0)
|
if (num_physical == 0)
|
||||||
return sequence;
|
return sequence;
|
||||||
@@ -942,7 +911,49 @@ static std::vector<unsigned int> build_grouped_manual_pattern_preview_sequence(c
|
|||||||
if (normalized.empty())
|
if (normalized.empty())
|
||||||
return sequence;
|
return sequence;
|
||||||
|
|
||||||
return decode_manual_pattern_ids_for_physical(normalized, num_physical);
|
const std::vector<std::string> groups = split_manual_pattern_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;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::pair<int, int> effective_pair_preview_ratios(int percent_b)
|
static std::pair<int, int> effective_pair_preview_ratios(int percent_b)
|
||||||
@@ -1471,8 +1482,10 @@ std::string compute_mixed_filament_display_color(const MixedFilament &entry, con
|
|||||||
std::string mixed_filament_standardized_name(const MixedFilament &entry, size_t num_physical)
|
std::string mixed_filament_standardized_name(const MixedFilament &entry, size_t num_physical)
|
||||||
{
|
{
|
||||||
const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(entry.manual_pattern);
|
const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(entry.manual_pattern);
|
||||||
if (!normalized_pattern.empty())
|
if (!normalized_pattern.empty()) {
|
||||||
return std::string("Pattern ") + normalized_pattern;
|
const std::string flattened = flatten_manual_pattern_groups(normalized_pattern);
|
||||||
|
return std::string("Pattern ") + (flattened.empty() ? normalized_pattern : flattened);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::pair<unsigned int, int>> parts;
|
std::vector<std::pair<unsigned int, int>> parts;
|
||||||
parts.reserve(4);
|
parts.reserve(4);
|
||||||
@@ -1682,31 +1695,31 @@ void MixedFilamentManager::clear_custom_entries()
|
|||||||
|
|
||||||
std::string MixedFilamentManager::normalize_manual_pattern(const std::string &pattern)
|
std::string MixedFilamentManager::normalize_manual_pattern(const std::string &pattern)
|
||||||
{
|
{
|
||||||
if (pattern.empty())
|
std::string normalized;
|
||||||
return std::string();
|
normalized.reserve(pattern.size());
|
||||||
|
bool current_group_has_steps = false;
|
||||||
std::vector<unsigned int> ids;
|
|
||||||
if (pattern.find(',') != std::string::npos) {
|
|
||||||
if (!parse_manual_pattern_numeric_ids(pattern, ids))
|
|
||||||
return std::string();
|
|
||||||
return encode_manual_pattern_ids(ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backward compatibility for legacy compact patterns like "1212" and "A/B".
|
|
||||||
for (char c : pattern) {
|
for (char c : pattern) {
|
||||||
char step = '\0';
|
char step = '\0';
|
||||||
if (decode_pattern_step(c, step)) {
|
if (decode_pattern_step(c, step)) {
|
||||||
ids.emplace_back(unsigned(step - '0'));
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
if (is_pattern_separator(c))
|
if (is_pattern_separator(c))
|
||||||
continue;
|
continue;
|
||||||
|
// Unknown token => invalid pattern.
|
||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
|
if (!normalized.empty() && normalized.back() == ',')
|
||||||
if (ids.empty())
|
|
||||||
return std::string();
|
return std::string();
|
||||||
return encode_manual_pattern_ids(ids);
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
int MixedFilamentManager::mix_percent_from_manual_pattern(const std::string &pattern)
|
int MixedFilamentManager::mix_percent_from_manual_pattern(const std::string &pattern)
|
||||||
@@ -1973,14 +1986,16 @@ unsigned int MixedFilamentManager::resolve(unsigned int filament_id,
|
|||||||
|
|
||||||
const MixedFilament &mf = m_mixed[size_t(mixed_idx)];
|
const MixedFilament &mf = m_mixed[size_t(mixed_idx)];
|
||||||
|
|
||||||
// Manual pattern takes precedence when provided.
|
// Manual pattern takes precedence when provided. Pattern uses repeating
|
||||||
// Pattern stores comma-separated 1-based physical filament IDs.
|
// steps: '1' => component_a, '2' => component_b, '3'..'9' => direct
|
||||||
|
// physical filament IDs.
|
||||||
if (!mf.manual_pattern.empty()) {
|
if (!mf.manual_pattern.empty()) {
|
||||||
const std::string normalized_pattern = normalize_manual_pattern(mf.manual_pattern);
|
const std::string flattened_pattern = flatten_manual_pattern_groups(mf.manual_pattern);
|
||||||
const std::vector<unsigned int> pattern_ids = decode_manual_pattern_ids_for_physical(normalized_pattern, num_physical);
|
if (!flattened_pattern.empty()) {
|
||||||
if (!pattern_ids.empty()) {
|
const int pos = safe_mod(layer_index, int(flattened_pattern.size()));
|
||||||
const int pos = safe_mod(layer_index, int(pattern_ids.size()));
|
const unsigned int resolved = physical_filament_from_pattern_step(flattened_pattern[size_t(pos)], mf, num_physical);
|
||||||
return pattern_ids[size_t(pos)];
|
if (resolved >= 1 && resolved <= num_physical)
|
||||||
|
return resolved;
|
||||||
}
|
}
|
||||||
return mf.component_a;
|
return mf.component_a;
|
||||||
}
|
}
|
||||||
@@ -2034,12 +2049,25 @@ unsigned int MixedFilamentManager::resolve_perimeter(unsigned int filament_id,
|
|||||||
float layer_height,
|
float layer_height,
|
||||||
bool force_height_weighted) const
|
bool force_height_weighted) const
|
||||||
{
|
{
|
||||||
(void)perimeter_index;
|
|
||||||
|
|
||||||
const int mixed_idx = mixed_index_from_filament_id(filament_id, num_physical);
|
const int mixed_idx = mixed_index_from_filament_id(filament_id, num_physical);
|
||||||
if (mixed_idx < 0)
|
if (mixed_idx < 0)
|
||||||
return filament_id;
|
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);
|
return resolve(filament_id, num_physical, layer_index, layer_print_z, layer_height, force_height_weighted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2060,6 +2088,10 @@ unsigned int MixedFilamentManager::effective_painted_region_filament_id(unsigned
|
|||||||
if (mf.distribution_mode == int(MixedFilament::SameLayerPointillisme))
|
if (mf.distribution_mode == int(MixedFilament::SameLayerPointillisme))
|
||||||
return filament_id;
|
return filament_id;
|
||||||
|
|
||||||
|
const std::string normalized_pattern = normalize_manual_pattern(mf.manual_pattern);
|
||||||
|
if (normalized_pattern.find(',') != std::string::npos)
|
||||||
|
return filament_id;
|
||||||
|
|
||||||
const bool is_custom_mixed = mf.custom;
|
const bool is_custom_mixed = mf.custom;
|
||||||
if (!is_custom_mixed && (layer_height_a > 0.f || layer_height_b > 0.f)) {
|
if (!is_custom_mixed && (layer_height_a > 0.f || layer_height_b > 0.f)) {
|
||||||
const float safe_base = std::max<float>(0.01f, base_layer_height);
|
const float safe_base = std::max<float>(0.01f, base_layer_height);
|
||||||
@@ -2091,7 +2123,7 @@ float MixedFilamentManager::component_surface_offset(unsigned int filament_id,
|
|||||||
return 0.f;
|
return 0.f;
|
||||||
|
|
||||||
const std::string normalized_pattern = normalize_manual_pattern(mixed_row->manual_pattern);
|
const std::string normalized_pattern = normalize_manual_pattern(mixed_row->manual_pattern);
|
||||||
if (!normalized_pattern.empty())
|
if (normalized_pattern.find(',') != std::string::npos)
|
||||||
return 0.f;
|
return 0.f;
|
||||||
|
|
||||||
const unsigned int resolved = resolve(filament_id,
|
const unsigned int resolved = resolve(filament_id,
|
||||||
@@ -2125,11 +2157,19 @@ std::vector<unsigned int> MixedFilamentManager::ordered_perimeter_extruders(unsi
|
|||||||
|
|
||||||
const MixedFilament &mf = m_mixed[size_t(mixed_idx)];
|
const MixedFilament &mf = m_mixed[size_t(mixed_idx)];
|
||||||
if (!mf.manual_pattern.empty()) {
|
if (!mf.manual_pattern.empty()) {
|
||||||
const std::string normalized_pattern = normalize_manual_pattern(mf.manual_pattern);
|
const std::vector<std::string> pattern_groups = split_manual_pattern_groups(mf.manual_pattern);
|
||||||
const std::vector<unsigned int> pattern_ids = decode_manual_pattern_ids_for_physical(normalized_pattern, num_physical);
|
if (!pattern_groups.empty()) {
|
||||||
if (!pattern_ids.empty()) {
|
ordered.reserve(pattern_groups.size());
|
||||||
ordered.reserve(pattern_ids.size());
|
for (size_t group_idx = 0; group_idx < pattern_groups.size(); ++group_idx) {
|
||||||
for (const unsigned int resolved : pattern_ids) {
|
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())
|
if (std::find(ordered.begin(), ordered.end(), resolved) == ordered.end())
|
||||||
ordered.emplace_back(resolved);
|
ordered.emplace_back(resolved);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ struct MixedFilament
|
|||||||
// Blend percentage of component B in [0..100].
|
// Blend percentage of component B in [0..100].
|
||||||
int mix_b_percent = 50;
|
int mix_b_percent = 50;
|
||||||
|
|
||||||
// Optional manual pattern for this mixed filament.
|
// Optional manual pattern for this mixed filament. Tokens:
|
||||||
// Canonical format: comma-separated 1-based physical filament IDs
|
// '1' => component_a, '2' => component_b, '3'..'9' => direct physical
|
||||||
// (for example "1,2,12,11").
|
// filament IDs (1-based). Example: "11112222" => AAAABBBB repeating.
|
||||||
std::string manual_pattern;
|
std::string manual_pattern;
|
||||||
|
|
||||||
// Optional explicit gradient multi-color component list, encoded as
|
// Optional explicit gradient multi-color component list, encoded as
|
||||||
@@ -145,7 +145,7 @@ std::pair<int, int> mixed_filament_apparent_pair_percentages(const MixedFilament
|
|||||||
std::string compute_mixed_filament_display_color(const MixedFilament &entry, const MixedFilamentDisplayContext &context);
|
std::string compute_mixed_filament_display_color(const MixedFilament &entry, const MixedFilamentDisplayContext &context);
|
||||||
|
|
||||||
// Build a standardized user-facing mixed filament name.
|
// Build a standardized user-facing mixed filament name.
|
||||||
// - Pattern rows: "Pattern <normalized pattern>" (for example "Pattern 1,2,12,11").
|
// - Pattern rows: "Pattern <flattened pattern>" (for example "Pattern 1212354").
|
||||||
// - Mix rows: "<id>:<pct>% + <id>:<pct>% ..." (for example "1:30% + 2:20% + 3:50%").
|
// - Mix rows: "<id>:<pct>% + <id>:<pct>% ..." (for example "1:30% + 2:20% + 3:50%").
|
||||||
std::string mixed_filament_standardized_name(const MixedFilament &entry, size_t num_physical);
|
std::string mixed_filament_standardized_name(const MixedFilament &entry, size_t num_physical);
|
||||||
|
|
||||||
@@ -197,9 +197,8 @@ public:
|
|||||||
std::string serialize_custom_entries();
|
std::string serialize_custom_entries();
|
||||||
void load_custom_entries(const std::string &serialized, const std::vector<std::string> &filament_colours);
|
void load_custom_entries(const std::string &serialized, const std::vector<std::string> &filament_colours);
|
||||||
|
|
||||||
// Normalize a manual mixed-pattern string into comma-separated numeric IDs.
|
// Normalize a manual mixed-pattern string into compact token form.
|
||||||
// Legacy compact patterns are still accepted for backward compatibility.
|
// Accepts separators and A/B aliases. Returns empty string if invalid.
|
||||||
// Returns empty string if invalid.
|
|
||||||
static std::string normalize_manual_pattern(const std::string &pattern);
|
static std::string normalize_manual_pattern(const std::string &pattern);
|
||||||
static int mix_percent_from_manual_pattern(const std::string &pattern);
|
static int mix_percent_from_manual_pattern(const std::string &pattern);
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
#include <limits>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
@@ -1142,23 +1140,16 @@ static void append_mixed_component_extruders(const MixedFilamentManager &mixed_m
|
|||||||
append_unique_painted_extruder(painting_extruders, unsigned(token - '0'), num_physical_extruders);
|
append_unique_painted_extruder(painting_extruders, unsigned(token - '0'), num_physical_extruders);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(mixed_row->manual_pattern);
|
for (char token : mixed_row->manual_pattern) {
|
||||||
if (!normalized_pattern.empty()) {
|
unsigned int extruder_id = 0;
|
||||||
std::stringstream ss(normalized_pattern);
|
if (token == '1')
|
||||||
std::string token;
|
extruder_id = mixed_row->component_a;
|
||||||
while (std::getline(ss, token, ',')) {
|
else if (token == '2')
|
||||||
if (token.empty())
|
extruder_id = mixed_row->component_b;
|
||||||
continue;
|
else if (token >= '3' && token <= '9')
|
||||||
try {
|
extruder_id = unsigned(token - '0');
|
||||||
size_t consumed = 0;
|
|
||||||
const unsigned long value = std::stoul(token, &consumed);
|
append_unique_painted_extruder(painting_extruders, extruder_id, num_physical_extruders);
|
||||||
if (consumed != token.size() || value == 0 || value > std::numeric_limits<unsigned int>::max())
|
|
||||||
continue;
|
|
||||||
append_unique_painted_extruder(painting_extruders, static_cast<unsigned int>(value), num_physical_extruders);
|
|
||||||
} catch (...) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,6 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <limits>
|
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -51,21 +50,39 @@ namespace Slic3r { namespace GUI {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// -- Plater.cpp:4849 --------------------------------------------------------
|
// -- Plater.cpp:4849 --------------------------------------------------------
|
||||||
static bool parse_manual_pattern_preview_id_token(const std::string &token, unsigned int &out)
|
static std::vector<std::string> split_manual_pattern_preview_groups(const std::string &pattern)
|
||||||
{
|
{
|
||||||
if (token.empty())
|
std::vector<std::string> groups;
|
||||||
return false;
|
if (pattern.empty())
|
||||||
|
return groups;
|
||||||
|
|
||||||
try {
|
std::string current;
|
||||||
size_t consumed = 0;
|
for (const char c : pattern) {
|
||||||
const unsigned long value = std::stoul(token, &consumed);
|
if (c == ',') {
|
||||||
if (consumed != token.size() || value == 0 || value > std::numeric_limits<unsigned int>::max())
|
if (!current.empty()) {
|
||||||
return false;
|
groups.emplace_back(std::move(current));
|
||||||
out = static_cast<unsigned int>(value);
|
current.clear();
|
||||||
return true;
|
}
|
||||||
} catch (...) {
|
continue;
|
||||||
return false;
|
}
|
||||||
|
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,
|
static std::vector<unsigned int> build_grouped_manual_pattern_preview_sequence(const std::string &pattern,
|
||||||
@@ -74,10 +91,6 @@ static std::vector<unsigned int> build_grouped_manual_pattern_preview_sequence(c
|
|||||||
size_t num_physical,
|
size_t num_physical,
|
||||||
size_t wall_loops)
|
size_t wall_loops)
|
||||||
{
|
{
|
||||||
(void)component_a;
|
|
||||||
(void)component_b;
|
|
||||||
(void)wall_loops;
|
|
||||||
|
|
||||||
std::vector<unsigned int> sequence;
|
std::vector<unsigned int> sequence;
|
||||||
if (num_physical == 0)
|
if (num_physical == 0)
|
||||||
return sequence;
|
return sequence;
|
||||||
@@ -86,14 +99,46 @@ static std::vector<unsigned int> build_grouped_manual_pattern_preview_sequence(c
|
|||||||
if (normalized.empty())
|
if (normalized.empty())
|
||||||
return sequence;
|
return sequence;
|
||||||
|
|
||||||
std::stringstream ss(normalized);
|
const std::vector<std::string> groups = split_manual_pattern_preview_groups(normalized);
|
||||||
std::string token;
|
if (groups.empty())
|
||||||
while (std::getline(ss, token, ',')) {
|
return sequence;
|
||||||
unsigned int id = 0;
|
|
||||||
if (!parse_manual_pattern_preview_id_token(token, id))
|
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;
|
continue;
|
||||||
if (id >= 1 && id <= num_physical)
|
cycle = std::lcm(cycle, group.size());
|
||||||
sequence.emplace_back(id);
|
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;
|
return sequence;
|
||||||
@@ -1171,9 +1216,10 @@ void MixedFilamentConfigPanel::build_ui()
|
|||||||
pattern_row->Add(pattern_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap);
|
pattern_row->Add(pattern_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap);
|
||||||
m_pattern_ctrl = new wxTextCtrl(this, wxID_ANY, from_u8(normalized_pattern), wxDefaultPosition,
|
m_pattern_ctrl = new wxTextCtrl(this, wxID_ANY, from_u8(normalized_pattern), wxDefaultPosition,
|
||||||
wxSize(FromDIP(200), -1), wxTE_PROCESS_ENTER);
|
wxSize(FromDIP(200), -1), wxTE_PROCESS_ENTER);
|
||||||
m_pattern_ctrl->SetToolTip(_L("Manual repeating pattern. Enter physical filament IDs as comma-separated numbers. "
|
m_pattern_ctrl->SetToolTip(_L("Manual repeating pattern. Use 1/2 or A/B for component A/B, "
|
||||||
"Values greater than 9 are supported. "
|
"and 3..9 for direct physical filament IDs. "
|
||||||
"Example: 1,2,12,11."));
|
"Use commas to define deeper perimeter patterns, for example 12,21. "
|
||||||
|
"Example: 1/1/1/1/2/2/2/2, 12,21, or 1/2/3/4."));
|
||||||
pattern_row->Add(m_pattern_ctrl, 1, wxALIGN_CENTER_VERTICAL);
|
pattern_row->Add(m_pattern_ctrl, 1, wxALIGN_CENTER_VERTICAL);
|
||||||
root->Add(pattern_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap);
|
root->Add(pattern_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap);
|
||||||
|
|
||||||
@@ -1237,7 +1283,7 @@ void MixedFilamentConfigPanel::build_ui()
|
|||||||
const wxString bias_tooltip =
|
const wxString bias_tooltip =
|
||||||
_L("Positive bias recesses the second filament in the pair; negative bias recesses the first filament.\n\n"
|
_L("Positive bias recesses the second filament in the pair; negative bias recesses the first filament.\n\n"
|
||||||
"The color chip shows which filament the current value affects.\n\n"
|
"The color chip shows which filament the current value affects.\n\n"
|
||||||
"Manual patterns and Local-Z dithering ignore it.");
|
"Grouped wall patterns and Local-Z dithering ignore it.");
|
||||||
|
|
||||||
auto *surface_offset_label = new wxStaticText(this, wxID_ANY, _L("Bias"));
|
auto *surface_offset_label = new wxStaticText(this, wxID_ANY, _L("Bias"));
|
||||||
surface_offset_label->SetForegroundColour(is_dark ? wxColour(236, 236, 236) : wxColour(20, 20, 20));
|
surface_offset_label->SetForegroundColour(is_dark ? wxColour(236, 236, 236) : wxColour(20, 20, 20));
|
||||||
@@ -1379,7 +1425,7 @@ void MixedFilamentConfigPanel::build_ui()
|
|||||||
if (m_pattern_ctrl) {
|
if (m_pattern_ctrl) {
|
||||||
m_mf.distribution_mode = int(MixedFilament::Simple);
|
m_mf.distribution_mode = int(MixedFilament::Simple);
|
||||||
std::string normalized = MixedFilamentManager::normalize_manual_pattern(into_u8(m_pattern_ctrl->GetValue()));
|
std::string normalized = MixedFilamentManager::normalize_manual_pattern(into_u8(m_pattern_ctrl->GetValue()));
|
||||||
if (normalized.empty()) normalized = "1,2";
|
if (normalized.empty()) normalized = "12";
|
||||||
if (into_u8(m_pattern_ctrl->GetValue()) != normalized)
|
if (into_u8(m_pattern_ctrl->GetValue()) != normalized)
|
||||||
m_pattern_ctrl->ChangeValue(from_u8(normalized));
|
m_pattern_ctrl->ChangeValue(from_u8(normalized));
|
||||||
m_mf.manual_pattern = normalized;
|
m_mf.manual_pattern = normalized;
|
||||||
@@ -1639,8 +1685,8 @@ void MixedFilamentConfigPanel::build_ui()
|
|||||||
std::string pattern = into_u8(m_pattern_ctrl->GetValue());
|
std::string pattern = into_u8(m_pattern_ctrl->GetValue());
|
||||||
if (!pattern.empty()) {
|
if (!pattern.empty()) {
|
||||||
const char last = pattern.back();
|
const char last = pattern.back();
|
||||||
if (last != ',')
|
const bool has_sep = last == '/' || last == '-' || last == '_' || last == '|' || last == ':' || last == ';' || last == ',' || last == ' ';
|
||||||
pattern.push_back(',');
|
if (!has_sep) pattern.push_back('/');
|
||||||
}
|
}
|
||||||
pattern += std::to_string(filament_id);
|
pattern += std::to_string(filament_id);
|
||||||
m_pattern_ctrl->ChangeValue(from_u8(pattern));
|
m_pattern_ctrl->ChangeValue(from_u8(pattern));
|
||||||
|
|||||||
@@ -2471,7 +2471,7 @@ Sidebar::Sidebar(Plater *parent)
|
|||||||
mgr.add_custom_filament(1, 2, 50, colors);
|
mgr.add_custom_filament(1, 2, 50, colors);
|
||||||
auto &mfs = mgr.mixed_filaments();
|
auto &mfs = mgr.mixed_filaments();
|
||||||
if (!mfs.empty()) {
|
if (!mfs.empty()) {
|
||||||
mfs.back().manual_pattern = "1,2";
|
mfs.back().manual_pattern = "12";
|
||||||
mfs.back().custom = true;
|
mfs.back().custom = true;
|
||||||
}
|
}
|
||||||
if (ConfigOptionString *opt = wxGetApp().preset_bundle->project_config.option<ConfigOptionString>("mixed_filament_definitions"))
|
if (ConfigOptionString *opt = wxGetApp().preset_bundle->project_config.option<ConfigOptionString>("mixed_filament_definitions"))
|
||||||
|
|||||||
Reference in New Issue
Block a user