mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-28 19:31:22 +00:00
Port mixed filament engine fixes from BambuStudio
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
||||||
@@ -551,4 +552,278 @@ void expand_mixed_slots_in_unprintables(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sanitize_mixed_gradient_curve_array(std::vector<std::string>& vals)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < vals.size(); ++i) {
|
||||||
|
if (vals[i].empty())
|
||||||
|
continue;
|
||||||
|
// parse_gradient_curve returns empty for both "empty input" and "<2 valid points";
|
||||||
|
// we already skipped empty, so an empty result means a corrupted single-point slot.
|
||||||
|
if (parse_gradient_curve(vals[i]).empty()) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "sanitize_mixed_gradient_curve_array: slot "
|
||||||
|
<< i << " curve \"" << vals[i]
|
||||||
|
<< "\" has fewer than 2 valid points; clearing to linear";
|
||||||
|
vals[i].clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool try_parse_mixed_components_strict(const std::string &str,
|
||||||
|
std::vector<unsigned int> &components,
|
||||||
|
std::string &err)
|
||||||
|
{
|
||||||
|
components.clear();
|
||||||
|
if (str.empty()) {
|
||||||
|
err = "empty component list";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::istringstream ss(str);
|
||||||
|
std::string token;
|
||||||
|
while (std::getline(ss, token, ',')) {
|
||||||
|
if (token.empty()) {
|
||||||
|
err = "empty component index";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const long val = std::stol(token);
|
||||||
|
if (val < 1) {
|
||||||
|
err = "component index must be >= 1 (got " + token + ")";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
components.push_back(static_cast<unsigned int>(val));
|
||||||
|
} catch (...) {
|
||||||
|
err = "invalid component index \"" + token + "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (components.size() < 2) {
|
||||||
|
err = "at least 2 components required (got " + std::to_string(components.size()) + ")";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::set<unsigned int> seen;
|
||||||
|
for (unsigned int c : components) {
|
||||||
|
if (!seen.insert(c).second) {
|
||||||
|
err = "duplicate component index " + std::to_string(c);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool try_parse_mixed_ratios_strict(const std::string &str,
|
||||||
|
size_t n_components,
|
||||||
|
std::string &err)
|
||||||
|
{
|
||||||
|
if (str.empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
CNumericLocalesSetter c_locale_setter;
|
||||||
|
std::vector<double> ratios;
|
||||||
|
std::istringstream ss(str);
|
||||||
|
std::string token;
|
||||||
|
while (std::getline(ss, token, ',')) {
|
||||||
|
if (token.empty()) {
|
||||||
|
err = "empty ratio value";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const double val = std::stod(token);
|
||||||
|
if (!(val > 0.0)) {
|
||||||
|
err = "ratio must be positive (got " + token + ")";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ratios.push_back(val);
|
||||||
|
} catch (...) {
|
||||||
|
err = "invalid ratio \"" + token + "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ratios.size() != n_components) {
|
||||||
|
err = "expected " + std::to_string(n_components) + " ratio(s), got "
|
||||||
|
+ std::to_string(ratios.size());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validate_gradient_range_strict(const std::string &str, std::string &err)
|
||||||
|
{
|
||||||
|
if (str.empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
CNumericLocalesSetter c_locale_setter;
|
||||||
|
float v0 = 0.f, v1 = 0.f;
|
||||||
|
if (std::sscanf(str.c_str(), "%f,%f", &v0, &v1) != 2) {
|
||||||
|
err = "expected two comma-separated floats, e.g. \"0.10,0.90\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(v0 > 0.f && v0 < 1.f && v1 > 0.f && v1 < 1.f)) {
|
||||||
|
err = "start and end ratios must be in (0, 1)";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void append_error(std::map<std::string, std::string> &errors,
|
||||||
|
const std::string &key,
|
||||||
|
const std::string &msg)
|
||||||
|
{
|
||||||
|
auto it = errors.find(key);
|
||||||
|
if (it == errors.end())
|
||||||
|
errors.emplace(key, msg);
|
||||||
|
else
|
||||||
|
it->second += "; " + msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool has_mixed_sub_params_specified(
|
||||||
|
const std::vector<std::string> &comp_strs,
|
||||||
|
const std::vector<std::string> &ratio_strs,
|
||||||
|
const std::vector<unsigned char> &gradient_flags)
|
||||||
|
{
|
||||||
|
for (const std::string &s : comp_strs)
|
||||||
|
if (!s.empty()) return true;
|
||||||
|
for (const std::string &s : ratio_strs)
|
||||||
|
if (!s.empty()) return true;
|
||||||
|
for (unsigned char g : gradient_flags)
|
||||||
|
if (g) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mixed_string_array_was_specified(const std::vector<std::string> &vals)
|
||||||
|
{
|
||||||
|
for (const std::string &s : vals)
|
||||||
|
if (!s.empty())
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mixed_bool_array_was_specified(const std::vector<unsigned char> &vals)
|
||||||
|
{
|
||||||
|
for (unsigned char v : vals)
|
||||||
|
if (v)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_mixed_array_size_required(std::map<std::string, std::string> &errors,
|
||||||
|
const std::string &opt_key,
|
||||||
|
size_t actual_size,
|
||||||
|
size_t expected_size)
|
||||||
|
{
|
||||||
|
if (actual_size != expected_size) {
|
||||||
|
append_error(errors, opt_key,
|
||||||
|
"array size " + std::to_string(actual_size)
|
||||||
|
+ " does not match filament slot count " + std::to_string(expected_size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::string, std::string> validate_mixed_filament_params(
|
||||||
|
const std::vector<unsigned char> &is_mixed,
|
||||||
|
const std::vector<std::string> &comp_strs,
|
||||||
|
const std::vector<std::string> &ratio_strs,
|
||||||
|
const std::vector<unsigned char> &gradient_flags,
|
||||||
|
const std::vector<std::string> &gradient_range_strs,
|
||||||
|
const std::vector<std::string> &gradient_curve_strs)
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> errors;
|
||||||
|
|
||||||
|
if (has_mixed_sub_params_specified(comp_strs, ratio_strs, gradient_flags)
|
||||||
|
&& !has_any_mixed_filament(is_mixed)) {
|
||||||
|
append_error(errors, "filament_is_mixed",
|
||||||
|
"must be set when mixed filament parameters are specified");
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!has_any_mixed_filament(is_mixed))
|
||||||
|
return errors;
|
||||||
|
|
||||||
|
const size_t slot_count = is_mixed.size();
|
||||||
|
|
||||||
|
// Rule 1: mixed filament model → components & ratios arrays must cover every slot.
|
||||||
|
check_mixed_array_size_required(errors, "filament_mixed_components", comp_strs.size(), slot_count);
|
||||||
|
check_mixed_array_size_required(errors, "filament_mixed_sublayer_ratios", ratio_strs.size(), slot_count);
|
||||||
|
|
||||||
|
// Rule 2: gradient passed (any slot true) → gradient & range arrays must cover every slot.
|
||||||
|
const bool gradient_specified = mixed_bool_array_was_specified(gradient_flags);
|
||||||
|
if (gradient_specified) {
|
||||||
|
check_mixed_array_size_required(errors, "filament_mixed_gradient", gradient_flags.size(), slot_count);
|
||||||
|
check_mixed_array_size_required(errors, "filament_mixed_gradient_range", gradient_range_strs.size(), slot_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 3: curve passed (any non-empty entry) → curve array must cover every slot.
|
||||||
|
const bool curve_specified = mixed_string_array_was_specified(gradient_curve_strs);
|
||||||
|
if (curve_specified)
|
||||||
|
check_mixed_array_size_required(errors, "filament_mixed_gradient_curve", gradient_curve_strs.size(), slot_count);
|
||||||
|
|
||||||
|
size_t num_physical = 0;
|
||||||
|
for (unsigned char v : is_mixed)
|
||||||
|
if (!v) ++num_physical;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < is_mixed.size(); ++i) {
|
||||||
|
if (!is_mixed[i])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const std::string slot = "slot " + std::to_string(i + 1);
|
||||||
|
const std::string comp_str = i < comp_strs.size() ? comp_strs[i] : "";
|
||||||
|
|
||||||
|
std::vector<unsigned int> components;
|
||||||
|
std::string comp_err;
|
||||||
|
if (!try_parse_mixed_components_strict(comp_str, components, comp_err)) {
|
||||||
|
append_error(errors, "filament_mixed_components", slot + ": " + comp_err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned int c : components) {
|
||||||
|
if (c > num_physical) {
|
||||||
|
append_error(errors, "filament_mixed_components",
|
||||||
|
slot + ": component " + std::to_string(c)
|
||||||
|
+ " out of range (max physical filament index is "
|
||||||
|
+ std::to_string(num_physical) + ")");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c == i + 1) {
|
||||||
|
append_error(errors, "filament_mixed_components",
|
||||||
|
slot + ": cannot reference itself as a component");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const size_t idx0 = static_cast<size_t>(c - 1);
|
||||||
|
if (idx0 < is_mixed.size() && is_mixed[idx0]) {
|
||||||
|
append_error(errors, "filament_mixed_components",
|
||||||
|
slot + ": component " + std::to_string(c)
|
||||||
|
+ " references a mixed filament slot");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ratio_err;
|
||||||
|
const std::string ratio_str = i < ratio_strs.size() ? ratio_strs[i] : "";
|
||||||
|
if (!try_parse_mixed_ratios_strict(ratio_str, components.size(), ratio_err))
|
||||||
|
append_error(errors, "filament_mixed_sublayer_ratios", slot + ": " + ratio_err);
|
||||||
|
|
||||||
|
const bool gradient_on = i < gradient_flags.size() && gradient_flags[i];
|
||||||
|
if (gradient_on) {
|
||||||
|
if (components.size() != 2) {
|
||||||
|
append_error(errors, "filament_mixed_gradient",
|
||||||
|
slot + ": gradient requires exactly 2 components");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gradient_specified) {
|
||||||
|
std::string range_err;
|
||||||
|
const std::string range_str = i < gradient_range_strs.size() ? gradient_range_strs[i] : "";
|
||||||
|
if (!validate_gradient_range_strict(range_str, range_err))
|
||||||
|
append_error(errors, "filament_mixed_gradient_range", slot + ": " + range_err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curve_specified) {
|
||||||
|
const std::string curve_str = i < gradient_curve_strs.size() ? gradient_curve_strs[i] : "";
|
||||||
|
if (!curve_str.empty() && parse_gradient_curve(curve_str).empty())
|
||||||
|
append_error(errors, "filament_mixed_gradient_curve",
|
||||||
|
slot + ": invalid curve (need at least 2 valid control points)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#define SLIC3R_FILAMENT_MIXER_HPP
|
#define SLIC3R_FILAMENT_MIXER_HPP
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -139,6 +140,25 @@ void expand_mixed_slots_in_unprintables(
|
|||||||
const std::vector<unsigned char> &is_mixed,
|
const std::vector<unsigned char> &is_mixed,
|
||||||
const std::vector<std::string> &comp_strs);
|
const std::vector<std::string> &comp_strs);
|
||||||
|
|
||||||
|
// Clear any non-empty gradient-curve slot that parses to fewer than 2 control points.
|
||||||
|
// Heals per-slot arrays corrupted by the legacy "|" separator collision between
|
||||||
|
// PresetBundle::export_selections / load_selections (which used "|" as the inter-slot
|
||||||
|
// delimiter) and serialize_gradient_curve / parse_gradient_curve (which use "|" as the
|
||||||
|
// intra-slot control-point delimiter). Such a round-trip splits a multi-point curve
|
||||||
|
// across adjacent slots, leaving single-point entries that fail MakerWorld's strict
|
||||||
|
// "curve needs >= 2 points" check. Clearing them falls back to the linear range.
|
||||||
|
void sanitize_mixed_gradient_curve_array(std::vector<std::string>& vals);
|
||||||
|
|
||||||
|
// Validate mixed-color (混色) parameters. Returns error messages keyed by option name.
|
||||||
|
// Slot details are included in the message text (1-based slot index).
|
||||||
|
std::map<std::string, std::string> validate_mixed_filament_params(
|
||||||
|
const std::vector<unsigned char> &is_mixed,
|
||||||
|
const std::vector<std::string> &comp_strs,
|
||||||
|
const std::vector<std::string> &ratio_strs,
|
||||||
|
const std::vector<unsigned char> &gradient_flags,
|
||||||
|
const std::vector<std::string> &gradient_range_strs,
|
||||||
|
const std::vector<std::string> &gradient_curve_strs);
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
#endif // SLIC3R_FILAMENT_MIXER_HPP
|
#endif // SLIC3R_FILAMENT_MIXER_HPP
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "../Preset.hpp"
|
#include "../Preset.hpp"
|
||||||
#include "../Utils.hpp"
|
#include "../Utils.hpp"
|
||||||
#include "../LocalesUtils.hpp"
|
#include "../LocalesUtils.hpp"
|
||||||
|
#include "../FilamentMixer.hpp"
|
||||||
#include "../GCode.hpp"
|
#include "../GCode.hpp"
|
||||||
#include "../Geometry.hpp"
|
#include "../Geometry.hpp"
|
||||||
#include "../GCode/ThumbnailData.hpp"
|
#include "../GCode/ThumbnailData.hpp"
|
||||||
@@ -246,6 +247,8 @@ static constexpr const char* BUILD_TAG = "build";
|
|||||||
static constexpr const char* ITEM_TAG = "item";
|
static constexpr const char* ITEM_TAG = "item";
|
||||||
static constexpr const char* METADATA_TAG = "metadata";
|
static constexpr const char* METADATA_TAG = "metadata";
|
||||||
static constexpr const char* FILAMENT_TAG = "filament";
|
static constexpr const char* FILAMENT_TAG = "filament";
|
||||||
|
static constexpr const char* MIXED_FILAMENT_TAG = "mixed_filament";
|
||||||
|
static constexpr const char* MIXED_FILAMENT_COMPONENTS_TAG = "components";
|
||||||
static constexpr const char* SLICE_WARNING_TAG = "warning";
|
static constexpr const char* SLICE_WARNING_TAG = "warning";
|
||||||
static constexpr const char* WARNING_MSG_TAG = "msg";
|
static constexpr const char* WARNING_MSG_TAG = "msg";
|
||||||
static constexpr const char *FILAMENT_ID_TAG = "id";
|
static constexpr const char *FILAMENT_ID_TAG = "id";
|
||||||
@@ -1315,6 +1318,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
bool _handle_end_config_metadata();
|
bool _handle_end_config_metadata();
|
||||||
|
|
||||||
bool _handle_start_config_filament(const char** attributes, unsigned int num_attributes);
|
bool _handle_start_config_filament(const char** attributes, unsigned int num_attributes);
|
||||||
|
bool _handle_start_config_mixed_filament(const char** attributes, unsigned int num_attributes);
|
||||||
bool _handle_end_config_filament();
|
bool _handle_end_config_filament();
|
||||||
|
|
||||||
bool _handle_start_config_warning(const char** attributes, unsigned int num_attributes);
|
bool _handle_start_config_warning(const char** attributes, unsigned int num_attributes);
|
||||||
@@ -2694,6 +2698,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", load project config file successfully from %1%\n") %dest_file;
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", load project config file successfully from %1%\n") %dest_file;
|
||||||
|
|
||||||
|
// Heal any gradient-curve slots corrupted by the legacy "|" separator collision
|
||||||
|
// (see FilamentMixer::sanitize_mixed_gradient_curve_array). The 3MF JSON itself
|
||||||
|
// is safe (";" + C-style escape), but older projects saved through the buggy
|
||||||
|
// export_selections/load_selections path may already carry single-point entries
|
||||||
|
// that fail MakerWorld's "curve needs >= 2 points" check.
|
||||||
|
if (auto* curve_opt = config.option<ConfigOptionStrings>("filament_mixed_gradient_curve"))
|
||||||
|
Slic3r::sanitize_mixed_gradient_curve_array(curve_opt->values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3511,6 +3523,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
res = _handle_start_config_plater_instance(attributes, num_attributes);
|
res = _handle_start_config_plater_instance(attributes, num_attributes);
|
||||||
else if (::strcmp(FILAMENT_TAG, name) == 0)
|
else if (::strcmp(FILAMENT_TAG, name) == 0)
|
||||||
res = _handle_start_config_filament(attributes, num_attributes);
|
res = _handle_start_config_filament(attributes, num_attributes);
|
||||||
|
else if (::strcmp(MIXED_FILAMENT_TAG, name) == 0)
|
||||||
|
res = _handle_start_config_mixed_filament(attributes, num_attributes);
|
||||||
else if (::strcmp(SLICE_WARNING_TAG, name) == 0)
|
else if (::strcmp(SLICE_WARNING_TAG, name) == 0)
|
||||||
res = _handle_start_config_warning(attributes, num_attributes);
|
res = _handle_start_config_warning(attributes, num_attributes);
|
||||||
else if (::strcmp(NOZZLE_TAG, name) == 0)
|
else if (::strcmp(NOZZLE_TAG, name) == 0)
|
||||||
@@ -4684,6 +4698,23 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _BBS_3MF_Importer::_handle_start_config_mixed_filament(const char** attributes, unsigned int num_attributes)
|
||||||
|
{
|
||||||
|
if (m_curr_plater) {
|
||||||
|
std::string id = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_ID_TAG);
|
||||||
|
std::string type = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_TYPE_TAG);
|
||||||
|
std::string color = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_COLOR_TAG);
|
||||||
|
std::string components = bbs_get_attribute_value_string(attributes, num_attributes, MIXED_FILAMENT_COMPONENTS_TAG);
|
||||||
|
PlateMixedFilamentInfo mixed_info;
|
||||||
|
mixed_info.id = atoi(id.c_str());
|
||||||
|
mixed_info.type = type;
|
||||||
|
mixed_info.color = color;
|
||||||
|
mixed_info.components = components;
|
||||||
|
m_curr_plater->mixed_filaments_info.push_back(mixed_info);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool _BBS_3MF_Importer::_handle_end_config_filament()
|
bool _BBS_3MF_Importer::_handle_end_config_filament()
|
||||||
{
|
{
|
||||||
// do nothing
|
// do nothing
|
||||||
@@ -8488,6 +8519,17 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
<< FILAMENT_USED_FOR_SUPPORT << "=\"" << std::boolalpha << it->used_for_support << "\"/>\n";
|
<< FILAMENT_USED_FOR_SUPPORT << "=\"" << std::boolalpha << it->used_for_support << "\"/>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mixed (virtual) filaments used by this plate. These are resolved to physical
|
||||||
|
// components before g-code statistics, so they are not present in the <filament>
|
||||||
|
// list above and are recorded separately here.
|
||||||
|
for (auto it = plate_data->mixed_filaments_info.begin(); it != plate_data->mixed_filaments_info.end(); it++)
|
||||||
|
{
|
||||||
|
stream << " <" << MIXED_FILAMENT_TAG << " " << FILAMENT_ID_TAG << "=\"" << std::to_string(it->id) << "\" "
|
||||||
|
<< FILAMENT_TYPE_TAG << "=\"" << it->type << "\" "
|
||||||
|
<< FILAMENT_COLOR_TAG << "=\"" << it->color << "\" "
|
||||||
|
<< MIXED_FILAMENT_COMPONENTS_TAG << "=\"" << it->components << "\"/>\n";
|
||||||
|
}
|
||||||
|
|
||||||
for (auto it = plate_data->warnings.begin(); it != plate_data->warnings.end(); it++) {
|
for (auto it = plate_data->warnings.begin(); it != plate_data->warnings.end(); it++) {
|
||||||
stream << " <" << SLICE_WARNING_TAG << " msg=\"" << it->msg << "\" level=\"" << std::to_string(it->level) << "\" error_code =\"" << it->error_code << "\" />\n";
|
stream << " <" << SLICE_WARNING_TAG << " msg=\"" << it->msg << "\" level=\"" << std::to_string(it->level) << "\" error_code =\"" << it->error_code << "\" />\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,18 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Mixed (virtual) filament used by a plate. Mixed filaments are virtual slots that get
|
||||||
|
// resolved to their physical components before g-code statistics, so they never appear in
|
||||||
|
// slice_filaments_info. They are recorded here separately so a plate's mixed-color usage
|
||||||
|
// can be recovered from slice_info.
|
||||||
|
struct PlateMixedFilamentInfo
|
||||||
|
{
|
||||||
|
int id{0}; // 1-based virtual filament slot id
|
||||||
|
std::string type;
|
||||||
|
std::string color; // blended display color, "#RRGGBB"
|
||||||
|
std::string components; // 1-based physical component ids, comma separated, e.g. "1,3"
|
||||||
|
};
|
||||||
|
|
||||||
//BBS: define plate data list related structures
|
//BBS: define plate data list related structures
|
||||||
struct PlateData
|
struct PlateData
|
||||||
{
|
{
|
||||||
@@ -89,6 +101,8 @@ struct PlateData
|
|||||||
std::string first_layer_time;
|
std::string first_layer_time;
|
||||||
std::string plate_name;
|
std::string plate_name;
|
||||||
std::vector<FilamentInfo> slice_filaments_info;
|
std::vector<FilamentInfo> slice_filaments_info;
|
||||||
|
// Mixed (virtual) filaments used by this plate; empty when no mixed filament is used.
|
||||||
|
std::vector<PlateMixedFilamentInfo> mixed_filaments_info;
|
||||||
std::vector<size_t> skipped_objects;
|
std::vector<size_t> skipped_objects;
|
||||||
DynamicPrintConfig config;
|
DynamicPrintConfig config;
|
||||||
bool is_support_used {false};
|
bool is_support_used {false};
|
||||||
|
|||||||
@@ -4195,6 +4195,8 @@ void GCode::export_layer_filaments(GCodeProcessorResult* result)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result->used_mixed_filaments = m_print->get_slice_used_mixed_filaments();
|
||||||
|
|
||||||
result->optimal_assignment.clear();
|
result->optimal_assignment.clear();
|
||||||
result->optimal_assignment.reserve(filament_map.size());
|
result->optimal_assignment.reserve(filament_map.size());
|
||||||
for (int nozzle_id : filament_map)
|
for (int nozzle_id : filament_map)
|
||||||
@@ -6859,7 +6861,7 @@ LayerResult GCode::process_layer(
|
|||||||
if (instance_to_print.object_by_extruder.support && !instance_to_print.object_by_extruder.support->empty()) {
|
if (instance_to_print.object_by_extruder.support && !instance_to_print.object_by_extruder.support->empty()) {
|
||||||
if (use_per_volume) {
|
if (use_per_volume) {
|
||||||
m_nominal_z = obj_sub_z;
|
m_nominal_z = obj_sub_z;
|
||||||
gcode += m_writer.travel_to_z(obj_sub_z, "restore Z for support");
|
m_need_change_layer_lift_z = true;
|
||||||
}
|
}
|
||||||
ExtrusionRole support_role = instance_to_print.object_by_extruder.support_extrusion_role;
|
ExtrusionRole support_role = instance_to_print.object_by_extruder.support_extrusion_role;
|
||||||
gcode += this->extrude_support(*instance_to_print.object_by_extruder.support, support_role);
|
gcode += this->extrude_support(*instance_to_print.object_by_extruder.support, support_role);
|
||||||
@@ -6897,7 +6899,7 @@ LayerResult GCode::process_layer(
|
|||||||
if (!layer_tools.mixed_sub_layer_groups.empty()) {
|
if (!layer_tools.mixed_sub_layer_groups.empty()) {
|
||||||
m_writer.add_object_end_labels(gcode);
|
m_writer.add_object_end_labels(gcode);
|
||||||
m_nominal_z = print_z;
|
m_nominal_z = print_z;
|
||||||
gcode += m_writer.travel_to_z(print_z, "restore Z after sublayers");
|
m_need_change_layer_lift_z = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2543,6 +2543,7 @@ void GCodeProcessorResult::reset() {
|
|||||||
spiral_vase_mode = false;
|
spiral_vase_mode = false;
|
||||||
layer_filaments.clear();
|
layer_filaments.clear();
|
||||||
filament_change_sequence.clear();
|
filament_change_sequence.clear();
|
||||||
|
used_mixed_filaments.clear();
|
||||||
nozzle_change_sequence.clear();
|
nozzle_change_sequence.clear();
|
||||||
optimal_assignment.clear();
|
optimal_assignment.clear();
|
||||||
filament_change_count_map.clear();
|
filament_change_count_map.clear();
|
||||||
|
|||||||
@@ -306,6 +306,9 @@ class Print;
|
|||||||
std::unordered_map<std::vector<unsigned int>, std::vector<std::pair<int, int>>,FilamentSequenceHash> layer_filaments;
|
std::unordered_map<std::vector<unsigned int>, std::vector<std::pair<int, int>>,FilamentSequenceHash> layer_filaments;
|
||||||
std::vector<unsigned int> nozzle_change_sequence;
|
std::vector<unsigned int> nozzle_change_sequence;
|
||||||
std::vector<unsigned int> filament_change_sequence;
|
std::vector<unsigned int> filament_change_sequence;
|
||||||
|
// 0-based mixed (virtual) filament slots actually used on this plate.
|
||||||
|
// Recorded before resolve_mixed_filaments expands them to physical components.
|
||||||
|
std::vector<unsigned int> used_mixed_filaments;
|
||||||
std::vector<int> optimal_assignment;
|
std::vector<int> optimal_assignment;
|
||||||
// first key stores `from` filament, second keys stores the `to` filament
|
// first key stores `from` filament, second keys stores the `to` filament
|
||||||
std::map<std::pair<int,int>, int > filament_change_count_map;
|
std::map<std::pair<int,int>, int > filament_change_count_map;
|
||||||
@@ -357,6 +360,7 @@ class Print;
|
|||||||
printer_extruder_id = other.printer_extruder_id;
|
printer_extruder_id = other.printer_extruder_id;
|
||||||
layer_filaments = other.layer_filaments;
|
layer_filaments = other.layer_filaments;
|
||||||
filament_change_sequence = other.filament_change_sequence;
|
filament_change_sequence = other.filament_change_sequence;
|
||||||
|
used_mixed_filaments = other.used_mixed_filaments;
|
||||||
nozzle_change_sequence = other.nozzle_change_sequence;
|
nozzle_change_sequence = other.nozzle_change_sequence;
|
||||||
optimal_assignment = other.optimal_assignment;
|
optimal_assignment = other.optimal_assignment;
|
||||||
filament_change_count_map = other.filament_change_count_map;
|
filament_change_count_map = other.filament_change_count_map;
|
||||||
|
|||||||
@@ -1015,7 +1015,7 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_
|
|||||||
|
|
||||||
//FIXME this is a hack to get the ball rolling.
|
//FIXME this is a hack to get the ball rolling.
|
||||||
for (LayerTools < : m_layer_tools)
|
for (LayerTools < : m_layer_tools)
|
||||||
lt.has_wipe_tower |= (lt.has_object && (config.timelapse_type == TimelapseType::tlSmooth || lt.wipe_tower_partitions > 0))
|
lt.has_wipe_tower |= ((lt.has_object || lt.has_support) && (config.timelapse_type == TimelapseType::tlSmooth || lt.wipe_tower_partitions > 0))
|
||||||
|| lt.print_z < object_bottom_z + EPSILON;
|
|| lt.print_z < object_bottom_z + EPSILON;
|
||||||
|
|
||||||
// Test for a raft, insert additional wipe tower layer to fill in the raft separation gap.
|
// Test for a raft, insert additional wipe tower layer to fill in the raft separation gap.
|
||||||
@@ -1056,6 +1056,84 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure wipe tower vertical continuity:
|
||||||
|
//
|
||||||
|
// (1) Any existing LayerTools sandwiched between two has_wipe_tower layers must itself be a
|
||||||
|
// wipe-tower layer. The LayerTools entry already exists, but it has neither object nor
|
||||||
|
// support geometry (has_object == false && has_support == false), so the marking pass
|
||||||
|
// above leaves has_wipe_tower == false. Happens e.g. when one object is fully floating
|
||||||
|
// above another and the support_top_z_distance / support_bottom_z_distance gap leaves an
|
||||||
|
// interior layer with no object and no support (e.g. B top z=20.4, A first layer z=20.8,
|
||||||
|
// the z=20.6 LayerTools entry exists but stays unmarked).
|
||||||
|
//
|
||||||
|
// (2) When two adjacent has_wipe_tower layers are farther apart than max_layer_height and no
|
||||||
|
// LayerTools entry exists between them, insert virtual wipe-tower-only layers to bridge
|
||||||
|
// the gap. Happens with raft: BambuStudio's raft contact layer can be thicker than
|
||||||
|
// max_layer_height (e.g. raft base top z=0.2, raft contact top z=0.5 — gap 0.3 > 0.28),
|
||||||
|
// and there is no LayerTools entry between those two z values.
|
||||||
|
//
|
||||||
|
// wipe_tower_partitions has already been max-propagated downward above, so partition counts
|
||||||
|
// on the filled-in / inserted layers stay consistent.
|
||||||
|
{
|
||||||
|
int first_wt_idx = -1;
|
||||||
|
int last_wt_idx = -1;
|
||||||
|
for (int i = 0; i < (int)m_layer_tools.size(); ++i)
|
||||||
|
if (m_layer_tools[i].has_wipe_tower) {
|
||||||
|
if (first_wt_idx < 0) first_wt_idx = i;
|
||||||
|
last_wt_idx = i;
|
||||||
|
}
|
||||||
|
for (int i = first_wt_idx + 1; i < last_wt_idx; ++i) {
|
||||||
|
LayerTools < = m_layer_tools[i];
|
||||||
|
lt.has_wipe_tower = true;
|
||||||
|
// GCode::process_layer emits wipe-tower G-code inside `for (extruder_id : layer_tools.extruders)`.
|
||||||
|
// An empty extruders vector here would silently skip wipe tower output, leaving the tower
|
||||||
|
// physically floating. Seed from the nearest non-empty neighbor so the loop actually runs.
|
||||||
|
if (lt.extruders.empty()) {
|
||||||
|
unsigned int seed_extruder = 0;
|
||||||
|
bool found_seed = false;
|
||||||
|
for (int j = i - 1; j >= 0; --j)
|
||||||
|
if (!m_layer_tools[j].extruders.empty()) {
|
||||||
|
seed_extruder = m_layer_tools[j].extruders.back();
|
||||||
|
found_seed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!found_seed)
|
||||||
|
for (int j = i + 1; j < (int)m_layer_tools.size(); ++j)
|
||||||
|
if (!m_layer_tools[j].extruders.empty()) {
|
||||||
|
seed_extruder = m_layer_tools[j].extruders.front();
|
||||||
|
found_seed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (found_seed)
|
||||||
|
lt.extruders.push_back(seed_extruder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk adjacent has_wipe_tower pairs and split oversized gaps. Re-evaluate the same i
|
||||||
|
// after each insertion so very large gaps get split into multiple layers.
|
||||||
|
for (int i = 0; i + 1 < (int)m_layer_tools.size(); ) {
|
||||||
|
LayerTools < = m_layer_tools[i];
|
||||||
|
LayerTools <_next = m_layer_tools[i + 1];
|
||||||
|
if (!lt.has_wipe_tower || !lt_next.has_wipe_tower) {
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
coordf_t gap = lt_next.print_z - lt.print_z;
|
||||||
|
if (gap <= max_layer_height + EPSILON) {
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LayerTools lt_new(0.5 * (lt.print_z + lt_next.print_z));
|
||||||
|
lt_new.has_wipe_tower = true;
|
||||||
|
if (!lt_next.extruders.empty())
|
||||||
|
lt_new.extruders.push_back(lt_next.extruders.front());
|
||||||
|
else if (!lt.extruders.empty())
|
||||||
|
lt_new.extruders.push_back(lt.extruders.back());
|
||||||
|
lt_new.wipe_tower_partitions = lt_next.wipe_tower_partitions;
|
||||||
|
m_layer_tools.insert(m_layer_tools.begin() + i + 1, lt_new);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If the model contains empty layers (such as https://github.com/prusa3d/Slic3r/issues/1266), there might be layers
|
// If the model contains empty layers (such as https://github.com/prusa3d/Slic3r/issues/1266), there might be layers
|
||||||
// that were not marked as has_wipe_tower, even when they should have been. This produces a crash with soluble supports
|
// that were not marked as has_wipe_tower, even when they should have been. This produces a crash with soluble supports
|
||||||
// and maybe other problems. We will therefore go through layer_tools and detect and fix this.
|
// and maybe other problems. We will therefore go through layer_tools and detect and fix this.
|
||||||
@@ -2081,6 +2159,18 @@ void ToolOrdering::resolve_mixed_filaments(const PrintConfig &config)
|
|||||||
const auto &comp_strs = config.filament_mixed_components.values;
|
const auto &comp_strs = config.filament_mixed_components.values;
|
||||||
const auto &ratio_strs = config.filament_mixed_sublayer_ratios.values;
|
const auto &ratio_strs = config.filament_mixed_sublayer_ratios.values;
|
||||||
|
|
||||||
|
// Capture mixed slots that actually appear on layers before they are expanded to
|
||||||
|
// physical components. Assigned-but-unused mixed slots never enter layer_tools.
|
||||||
|
m_used_mixed_filaments.clear();
|
||||||
|
if (has_any_mixed_filament(is_mixed)) {
|
||||||
|
std::set<unsigned int> used;
|
||||||
|
for (const LayerTools < : m_layer_tools)
|
||||||
|
for (unsigned int ext : lt.extruders)
|
||||||
|
if (ext < is_mixed.size() && is_mixed[ext])
|
||||||
|
used.insert(ext);
|
||||||
|
m_used_mixed_filaments.assign(used.begin(), used.end());
|
||||||
|
}
|
||||||
|
|
||||||
if (!has_any_mixed_filament(is_mixed))
|
if (!has_any_mixed_filament(is_mixed))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -290,6 +290,9 @@ public:
|
|||||||
|
|
||||||
// For a multi-material print, the printing extruders are ordered in the order they shall be primed.
|
// For a multi-material print, the printing extruders are ordered in the order they shall be primed.
|
||||||
const std::vector<unsigned int>& all_extruders() const { return m_all_printing_extruders; }
|
const std::vector<unsigned int>& all_extruders() const { return m_all_printing_extruders; }
|
||||||
|
// 0-based mixed (virtual) slots that appeared on layers before resolve_mixed_filaments
|
||||||
|
// expanded them to physical components.
|
||||||
|
const std::vector<unsigned int>& used_mixed_filaments() const { return m_used_mixed_filaments; }
|
||||||
|
|
||||||
// Find LayerTools with the closest print_z.
|
// Find LayerTools with the closest print_z.
|
||||||
const LayerTools& tools_for_layer(coordf_t print_z) const;
|
const LayerTools& tools_for_layer(coordf_t print_z) const;
|
||||||
@@ -376,6 +379,7 @@ private:
|
|||||||
unsigned int m_last_printing_extruder = (unsigned int)-1;
|
unsigned int m_last_printing_extruder = (unsigned int)-1;
|
||||||
// All extruders, which extrude some material over m_layer_tools.
|
// All extruders, which extrude some material over m_layer_tools.
|
||||||
std::vector<unsigned int> m_all_printing_extruders;
|
std::vector<unsigned int> m_all_printing_extruders;
|
||||||
|
std::vector<unsigned int> m_used_mixed_filaments;
|
||||||
const DynamicPrintConfig* m_print_full_config = nullptr;
|
const DynamicPrintConfig* m_print_full_config = nullptr;
|
||||||
const PrintConfig* m_print_config_ptr = nullptr;
|
const PrintConfig* m_print_config_ptr = nullptr;
|
||||||
|
|
||||||
|
|||||||
@@ -2760,6 +2760,9 @@ static void load_mixed_filament_settings(DynamicPrintConfig &project_config, con
|
|||||||
vals = std::move(curves);
|
vals = std::move(curves);
|
||||||
}
|
}
|
||||||
vals.resize(n_filaments, std::string{});
|
vals.resize(n_filaments, std::string{});
|
||||||
|
// Heal legacy corruption: clear any non-empty slot that ended up with < 2 points
|
||||||
|
// (e.g. a curve split across slots by the old "|" delimiter). Falls back to linear.
|
||||||
|
Slic3r::sanitize_mixed_gradient_curve_array(vals);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+39
-28
@@ -1388,6 +1388,13 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
|||||||
// #4043
|
// #4043
|
||||||
if (total_copies_count > 1 && m_config.print_sequence != PrintSequence::ByObject)
|
if (total_copies_count > 1 && m_config.print_sequence != PrintSequence::ByObject)
|
||||||
return {L("Please select \"By object\" print sequence to print multiple objects in spiral vase mode."), nullptr, "spiral_mode"};
|
return {L("Please select \"By object\" print sequence to print multiple objects in spiral vase mode."), nullptr, "spiral_mode"};
|
||||||
|
// A mixed (virtual) filament always resolves to multiple physical components, which
|
||||||
|
// spiral vase cannot print.
|
||||||
|
const auto &is_mixed = m_config.filament_is_mixed.values;
|
||||||
|
for (const PrintObject *object : m_objects)
|
||||||
|
for (unsigned int ext : object->object_extruders())
|
||||||
|
if (ext < is_mixed.size() && is_mixed[ext])
|
||||||
|
return {L("Spiral (vase) mode does not work when an object contains more than one material."), nullptr, "spiral_mode"};
|
||||||
assert(m_objects.size() == 1);
|
assert(m_objects.size() == 1);
|
||||||
const auto all_regions = m_objects.front()->all_regions();
|
const auto all_regions = m_objects.front()->all_regions();
|
||||||
if (all_regions.size() > 1) {
|
if (all_regions.size() > 1) {
|
||||||
@@ -2595,6 +2602,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
|||||||
// Order object instances for sequential print.
|
// Order object instances for sequential print.
|
||||||
print_object_instances_ordering = sort_object_instances_by_model_order(*this);
|
print_object_instances_ordering = sort_object_instances_by_model_order(*this);
|
||||||
std::vector<unsigned int> first_layer_used_filaments;
|
std::vector<unsigned int> first_layer_used_filaments;
|
||||||
|
std::vector<unsigned int> used_mixed_filaments;
|
||||||
std::vector<std::vector<unsigned int>> all_filaments;
|
std::vector<std::vector<unsigned int>> all_filaments;
|
||||||
for (print_object_instance_sequential_active = print_object_instances_ordering.begin(); print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) {
|
for (print_object_instance_sequential_active = print_object_instances_ordering.begin(); print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) {
|
||||||
tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id);
|
tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id);
|
||||||
@@ -2604,10 +2612,14 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
|||||||
if (idx == 0)
|
if (idx == 0)
|
||||||
first_layer_used_filaments.insert(first_layer_used_filaments.end(), layer_filament.begin(), layer_filament.end());
|
first_layer_used_filaments.insert(first_layer_used_filaments.end(), layer_filament.begin(), layer_filament.end());
|
||||||
}
|
}
|
||||||
|
used_mixed_filaments.insert(used_mixed_filaments.end(),
|
||||||
|
tool_ordering.used_mixed_filaments().begin(), tool_ordering.used_mixed_filaments().end());
|
||||||
}
|
}
|
||||||
sort_remove_duplicates(first_layer_used_filaments);
|
sort_remove_duplicates(first_layer_used_filaments);
|
||||||
|
sort_remove_duplicates(used_mixed_filaments);
|
||||||
auto used_filaments = collect_sorted_used_filaments(all_filaments);
|
auto used_filaments = collect_sorted_used_filaments(all_filaments);
|
||||||
this->set_slice_used_filaments(first_layer_used_filaments,used_filaments);
|
this->set_slice_used_filaments(first_layer_used_filaments,used_filaments);
|
||||||
|
this->set_slice_used_mixed_filaments(used_mixed_filaments);
|
||||||
|
|
||||||
auto physical_unprintables = this->get_physical_unprintable_filaments(used_filaments);
|
auto physical_unprintables = this->get_physical_unprintable_filaments(used_filaments);
|
||||||
auto geometric_unprintables = this->get_geometric_unprintable_filaments();
|
auto geometric_unprintables = this->get_geometric_unprintable_filaments();
|
||||||
@@ -2717,6 +2729,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
|||||||
first_layer_used_filaments = tool_ordering.layer_tools().front().extruders;
|
first_layer_used_filaments = tool_ordering.layer_tools().front().extruders;
|
||||||
|
|
||||||
this->set_slice_used_filaments(first_layer_used_filaments, tool_ordering.all_extruders());
|
this->set_slice_used_filaments(first_layer_used_filaments, tool_ordering.all_extruders());
|
||||||
|
this->set_slice_used_mixed_filaments(tool_ordering.used_mixed_filaments());
|
||||||
has_wipe_tower = this->has_wipe_tower() && tool_ordering.has_wipe_tower();
|
has_wipe_tower = this->has_wipe_tower() && tool_ordering.has_wipe_tower();
|
||||||
initial_extruder_id = tool_ordering.first_extruder();
|
initial_extruder_id = tool_ordering.first_extruder();
|
||||||
print_object_instances_ordering = chain_print_object_instances(*this);
|
print_object_instances_ordering = chain_print_object_instances(*this);
|
||||||
@@ -4034,38 +4047,36 @@ void Print::_make_wipe_tower()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Check whether there are any layers in m_tool_ordering, which are marked with has_wipe_tower,
|
// Check whether there are any layers in m_tool_ordering, which are marked with has_wipe_tower,
|
||||||
// they print neither object, nor support. These layers are above the raft and below the object, and they
|
// they print neither object, nor support. Each such layer needs a virtual support layer
|
||||||
// shall be added to the support layers to be printed.
|
// counterpart in m_objects.front() so that GCode::collect_layers_to_print picks it up and the
|
||||||
// see https://github.com/prusa3d/PrusaSlicer/issues/607
|
// wipe tower G-code is actually emitted for that z. Such layers appear in two scenarios:
|
||||||
|
// - above the raft, between raft top and the first real object layer
|
||||||
|
// (see https://github.com/prusa3d/PrusaSlicer/issues/607);
|
||||||
|
// - between two real wipe-tower layers, when one object is fully floating above another and
|
||||||
|
// the support_top_z_distance / support_bottom_z_distance gap leaves interior z values with
|
||||||
|
// neither object nor support (continuity fill in ToolOrdering::fill_wipe_tower_partitions).
|
||||||
|
// The previous implementation only handled the first contiguous run starting at the first
|
||||||
|
// virtual layer, which made the second scenario silently produce empty wipe-tower layers.
|
||||||
{
|
{
|
||||||
size_t idx_begin = size_t(-1);
|
auto &support_layers = m_objects.front()->support_layers();
|
||||||
size_t idx_end = m_wipe_tower_data.tool_ordering.layer_tools().size();
|
auto it_layer = support_layers.begin();
|
||||||
// Find the first wipe tower layer, which does not have a counterpart in an object or a support layer.
|
const size_t idx_end = m_wipe_tower_data.tool_ordering.layer_tools().size();
|
||||||
for (size_t i = 0; i < idx_end; ++ i) {
|
for (size_t i = 0; i < idx_end; ++ i) {
|
||||||
const LayerTools < = m_wipe_tower_data.tool_ordering.layer_tools()[i];
|
LayerTools < = const_cast<LayerTools&>(m_wipe_tower_data.tool_ordering.layer_tools()[i]);
|
||||||
if (lt.has_wipe_tower && ! lt.has_object && ! lt.has_support) {
|
if (! (lt.has_wipe_tower && ! lt.has_object && ! lt.has_support))
|
||||||
idx_begin = i;
|
continue;
|
||||||
break;
|
while (it_layer != support_layers.end() && (*it_layer)->print_z + EPSILON < lt.print_z)
|
||||||
}
|
|
||||||
}
|
|
||||||
if (idx_begin != size_t(-1)) {
|
|
||||||
// Find the position in m_objects.first()->support_layers to insert these new support layers.
|
|
||||||
double wipe_tower_new_layer_print_z_first = m_wipe_tower_data.tool_ordering.layer_tools()[idx_begin].print_z;
|
|
||||||
auto it_layer = m_objects.front()->support_layers().begin();
|
|
||||||
auto it_end = m_objects.front()->support_layers().end();
|
|
||||||
for (; it_layer != it_end && (*it_layer)->print_z - EPSILON < wipe_tower_new_layer_print_z_first; ++ it_layer);
|
|
||||||
// Find the stopper of the sequence of wipe tower layers, which do not have a counterpart in an object or a support layer.
|
|
||||||
for (size_t i = idx_begin; i < idx_end; ++ i) {
|
|
||||||
LayerTools < = const_cast<LayerTools&>(m_wipe_tower_data.tool_ordering.layer_tools()[i]);
|
|
||||||
if (! (lt.has_wipe_tower && ! lt.has_object && ! lt.has_support))
|
|
||||||
break;
|
|
||||||
lt.has_support = true;
|
|
||||||
// Insert the new support layer.
|
|
||||||
double height = lt.print_z - (i == 0 ? 0. : m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z);
|
|
||||||
//FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway.
|
|
||||||
it_layer = m_objects.front()->insert_support_layer(it_layer, -1, 0, height, lt.print_z, lt.print_z - 0.5 * height);
|
|
||||||
++ it_layer;
|
++ it_layer;
|
||||||
|
if (it_layer != support_layers.end() && std::abs((*it_layer)->print_z - lt.print_z) < EPSILON) {
|
||||||
|
lt.has_support = true;
|
||||||
|
++ it_layer;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
lt.has_support = true;
|
||||||
|
double height = lt.print_z - (i == 0 ? 0. : m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z);
|
||||||
|
//FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway.
|
||||||
|
it_layer = m_objects.front()->insert_support_layer(it_layer, -1, 0, height, lt.print_z, lt.print_z - 0.5 * height);
|
||||||
|
++ it_layer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->throw_if_canceled();
|
this->throw_if_canceled();
|
||||||
|
|||||||
@@ -1088,6 +1088,10 @@ public:
|
|||||||
m_slice_used_filaments = used_filaments;
|
m_slice_used_filaments = used_filaments;
|
||||||
}
|
}
|
||||||
std::vector<unsigned int> get_slice_used_filaments(bool first_layer) const { return first_layer ? m_slice_used_filaments_first_layer : m_slice_used_filaments;}
|
std::vector<unsigned int> get_slice_used_filaments(bool first_layer) const { return first_layer ? m_slice_used_filaments_first_layer : m_slice_used_filaments;}
|
||||||
|
void set_slice_used_mixed_filaments(const std::vector<unsigned int> &used_mixed_filaments) {
|
||||||
|
m_slice_used_mixed_filaments = used_mixed_filaments;
|
||||||
|
}
|
||||||
|
const std::vector<unsigned int>& get_slice_used_mixed_filaments() const { return m_slice_used_mixed_filaments; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Determines the unprintable filaments for each extruder based on its physical attributes
|
* @brief Determines the unprintable filaments for each extruder based on its physical attributes
|
||||||
@@ -1355,6 +1359,8 @@ private:
|
|||||||
|
|
||||||
std::vector<unsigned int> m_slice_used_filaments;
|
std::vector<unsigned int> m_slice_used_filaments;
|
||||||
std::vector<unsigned int> m_slice_used_filaments_first_layer;
|
std::vector<unsigned int> m_slice_used_filaments_first_layer;
|
||||||
|
// 0-based mixed (virtual) filament slots actually used on this plate.
|
||||||
|
std::vector<unsigned int> m_slice_used_mixed_filaments;
|
||||||
|
|
||||||
//BBS: plate's origin
|
//BBS: plate's origin
|
||||||
Vec3d m_origin {0, 0, 0};
|
Vec3d m_origin {0, 0, 0};
|
||||||
|
|||||||
@@ -11826,6 +11826,23 @@ std::map<std::string, std::string> validate(const FullPrintConfig &cfg, bool und
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mixed-color (混色) parameter validation.
|
||||||
|
{
|
||||||
|
const auto &is_mixed = cfg.filament_is_mixed.values;
|
||||||
|
const auto &comp_strs = cfg.filament_mixed_components.values;
|
||||||
|
const auto &ratio_strs = cfg.filament_mixed_sublayer_ratios.values;
|
||||||
|
const auto &gradient_flags = cfg.filament_mixed_gradient.values;
|
||||||
|
const auto &range_strs = cfg.filament_mixed_gradient_range.values;
|
||||||
|
const auto &curve_strs = cfg.filament_mixed_gradient_curve.values;
|
||||||
|
|
||||||
|
std::map<std::string, std::string> mixed_errors = validate_mixed_filament_params(
|
||||||
|
is_mixed, comp_strs, ratio_strs, gradient_flags,
|
||||||
|
range_strs, curve_strs);
|
||||||
|
for (const auto &kv : mixed_errors)
|
||||||
|
if (error_message.find(kv.first) == error_message.end())
|
||||||
|
error_message.emplace(kv.first, kv.second);
|
||||||
|
}
|
||||||
|
|
||||||
// The configuration is valid.
|
// The configuration is valid.
|
||||||
return error_message;
|
return error_message;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6492,6 +6492,31 @@ int PartPlateList::store_to_3mf_structure(PlateDataPtrs& plate_data_list, bool w
|
|||||||
}
|
}
|
||||||
//parse filament info
|
//parse filament info
|
||||||
plate_data_item->parse_filament_info(m_plate_list[i]->get_slice_result());
|
plate_data_item->parse_filament_info(m_plate_list[i]->get_slice_result());
|
||||||
|
|
||||||
|
// Record mixed (virtual) filaments actually used on this plate.
|
||||||
|
// Source is ToolOrdering::used_mixed_filaments (slots that appeared in
|
||||||
|
// layer tools before resolve), persisted on GCodeProcessorResult / Print —
|
||||||
|
// not print->extruders() which only reflects assignment.
|
||||||
|
{
|
||||||
|
std::vector<unsigned int> used_mixed;
|
||||||
|
if (auto *slice_result = m_plate_list[i]->get_slice_result())
|
||||||
|
used_mixed = slice_result->used_mixed_filaments;
|
||||||
|
if (used_mixed.empty() && print)
|
||||||
|
used_mixed = print->get_slice_used_mixed_filaments();
|
||||||
|
if (!used_mixed.empty() && print) {
|
||||||
|
const auto &fila_types = print->config().filament_type.values;
|
||||||
|
const auto &fila_colors = print->config().filament_colour.values;
|
||||||
|
const auto &fila_comps = print->config().filament_mixed_components.values;
|
||||||
|
for (unsigned int fid : used_mixed) {
|
||||||
|
PlateMixedFilamentInfo mixed_info;
|
||||||
|
mixed_info.id = (int) fid + 1;
|
||||||
|
if (fid < fila_types.size()) mixed_info.type = fila_types[fid];
|
||||||
|
if (fid < fila_colors.size()) mixed_info.color = fila_colors[fid];
|
||||||
|
if (fid < fila_comps.size()) mixed_info.components = fila_comps[fid];
|
||||||
|
plate_data_item->mixed_filaments_info.push_back(mixed_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "slice result = " << m_plate_list[i]->get_slice_result()
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "slice result = " << m_plate_list[i]->get_slice_result()
|
||||||
<< ", result valid = " << m_plate_list[i]->is_slice_result_valid();
|
<< ", result valid = " << m_plate_list[i]->is_slice_result_valid();
|
||||||
@@ -6558,6 +6583,13 @@ int PartPlateList::load_from_3mf_structure(PlateDataPtrs& plate_data_list, int f
|
|||||||
m_plate_list[index]->slice_filaments_info = plate_data_list[i]->slice_filaments_info;
|
m_plate_list[index]->slice_filaments_info = plate_data_list[i]->slice_filaments_info;
|
||||||
gcode_result->warnings = plate_data_list[i]->warnings;
|
gcode_result->warnings = plate_data_list[i]->warnings;
|
||||||
gcode_result->filament_maps = plate_data_list[i]->filament_maps;
|
gcode_result->filament_maps = plate_data_list[i]->filament_maps;
|
||||||
|
gcode_result->used_mixed_filaments.clear();
|
||||||
|
for (const auto &mixed_info : plate_data_list[i]->mixed_filaments_info) {
|
||||||
|
if (mixed_info.id > 0)
|
||||||
|
gcode_result->used_mixed_filaments.push_back(static_cast<unsigned int>(mixed_info.id - 1));
|
||||||
|
}
|
||||||
|
if (Print *print = dynamic_cast<Print*>(fff_print))
|
||||||
|
print->set_slice_used_mixed_filaments(gcode_result->used_mixed_filaments);
|
||||||
|
|
||||||
// Reconstruct the device-side nozzle grouping from the loaded 3mf so
|
// Reconstruct the device-side nozzle grouping from the loaded 3mf so
|
||||||
// the monitor/preview can map filaments to physical nozzles.
|
// the monitor/preview can map filaments to physical nozzles.
|
||||||
|
|||||||
Reference in New Issue
Block a user