mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-12 03:27:55 +00:00
Color mixing feature (#15347)
# Description This PR ports the color mixing feature from BambuStudio. The port is based on the previous work by @ianalexis in #15231. This PR completes the port and fixes various bugs. Several improvements were also made during the porting process. WIP # Screenshots/Recordings/Graphs <!-- > Please attach relevant screenshots to showcase the UI changes. > Please attach images that can help explain the changes. --> ## Tests <!-- > Please describe the tests that you have conducted to verify the changes made in this PR. --> <!-- > A guide for users on how to download the artifacts from this PR. --> [How to Download Pull Requests Artifacts for Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
#include "libslic3r/FilamentMixer.hpp"
|
||||
#include <future>
|
||||
#include <glad/gl.h>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
@@ -1675,6 +1676,25 @@ std::vector<int> PartPlate::get_extruders(bool conside_custom_gcode) const
|
||||
std::sort(plate_extruders.begin(), plate_extruders.end());
|
||||
auto it_end = std::unique(plate_extruders.begin(), plate_extruders.end());
|
||||
plate_extruders.resize(std::distance(plate_extruders.begin(), it_end));
|
||||
|
||||
// Expand mixed filament slots to their physical components. A mixed slot is virtual and
|
||||
// is never loaded into a tray, so callers (AMS mapping, filament checks) must see the
|
||||
// physical filaments it resolves to instead.
|
||||
{
|
||||
auto& project_config = wxGetApp().preset_bundle->project_config;
|
||||
auto* is_mixed_opt = project_config.option<ConfigOptionBools>("filament_is_mixed");
|
||||
auto* comp_strs_opt = project_config.option<ConfigOptionStrings>("filament_mixed_components");
|
||||
if (is_mixed_opt && comp_strs_opt && has_any_mixed_filament(is_mixed_opt->values)) {
|
||||
std::vector<unsigned int> ext_0based;
|
||||
for (int e : plate_extruders)
|
||||
if (e >= 1) ext_0based.push_back((unsigned int)(e - 1));
|
||||
auto expanded = expand_mixed_filaments(ext_0based, is_mixed_opt->values, comp_strs_opt->values);
|
||||
plate_extruders.clear();
|
||||
for (unsigned int e : expanded)
|
||||
plate_extruders.push_back((int)(e + 1));
|
||||
}
|
||||
}
|
||||
|
||||
return plate_extruders;
|
||||
}
|
||||
|
||||
@@ -1836,6 +1856,24 @@ std::vector<int> PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D
|
||||
std::sort(plate_extruders.begin(), plate_extruders.end());
|
||||
auto it_end = std::unique(plate_extruders.begin(), plate_extruders.end());
|
||||
plate_extruders.resize(std::distance(plate_extruders.begin(), it_end));
|
||||
|
||||
// Expand mixed filament slots to their physical components. A mixed slot is virtual and
|
||||
// is never loaded into a tray, so callers (AMS mapping, filament checks) must see the
|
||||
// physical filaments it resolves to instead.
|
||||
{
|
||||
auto* is_mixed_opt = full_config.option<ConfigOptionBools>("filament_is_mixed");
|
||||
auto* comp_strs_opt = full_config.option<ConfigOptionStrings>("filament_mixed_components");
|
||||
if (is_mixed_opt && comp_strs_opt && has_any_mixed_filament(is_mixed_opt->values)) {
|
||||
std::vector<unsigned int> ext_0based;
|
||||
for (int e : plate_extruders)
|
||||
if (e >= 1) ext_0based.push_back((unsigned int)(e - 1));
|
||||
auto expanded = expand_mixed_filaments(ext_0based, is_mixed_opt->values, comp_strs_opt->values);
|
||||
plate_extruders.clear();
|
||||
for (unsigned int e : expanded)
|
||||
plate_extruders.push_back((int)(e + 1));
|
||||
}
|
||||
}
|
||||
|
||||
return plate_extruders;
|
||||
}
|
||||
|
||||
@@ -1889,6 +1927,25 @@ std::vector<int> PartPlate::get_extruders_without_support(bool conside_custom_gc
|
||||
std::sort(plate_extruders.begin(), plate_extruders.end());
|
||||
auto it_end = std::unique(plate_extruders.begin(), plate_extruders.end());
|
||||
plate_extruders.resize(std::distance(plate_extruders.begin(), it_end));
|
||||
|
||||
// Expand mixed filament slots to their physical components. A mixed slot is virtual and
|
||||
// is never loaded into a tray, so callers (AMS mapping, filament checks) must see the
|
||||
// physical filaments it resolves to instead.
|
||||
{
|
||||
auto& project_config = wxGetApp().preset_bundle->project_config;
|
||||
auto* is_mixed_opt = project_config.option<ConfigOptionBools>("filament_is_mixed");
|
||||
auto* comp_strs_opt = project_config.option<ConfigOptionStrings>("filament_mixed_components");
|
||||
if (is_mixed_opt && comp_strs_opt && has_any_mixed_filament(is_mixed_opt->values)) {
|
||||
std::vector<unsigned int> ext_0based;
|
||||
for (int e : plate_extruders)
|
||||
if (e >= 1) ext_0based.push_back((unsigned int)(e - 1));
|
||||
auto expanded = expand_mixed_filaments(ext_0based, is_mixed_opt->values, comp_strs_opt->values);
|
||||
plate_extruders.clear();
|
||||
for (unsigned int e : expanded)
|
||||
plate_extruders.push_back((int)(e + 1));
|
||||
}
|
||||
}
|
||||
|
||||
return plate_extruders;
|
||||
}
|
||||
|
||||
@@ -1990,6 +2047,50 @@ bool PartPlate::check_tpu_printable_status(const DynamicPrintConfig & config, co
|
||||
return true;
|
||||
}
|
||||
|
||||
// A mixed-color filament alternates between its components constantly. On a single-nozzle
|
||||
// printer every one of those switches is a full filament change plus a purge, so warn before
|
||||
// slicing. Multi-nozzle printers keep the components loaded at once and are not affected.
|
||||
bool PartPlate::check_single_extruder_mixed_filament_risk(const DynamicPrintConfig &config, std::string &warning_text) const
|
||||
{
|
||||
warning_text.clear();
|
||||
|
||||
auto *nozzle_diameter_opt = config.option<ConfigOptionFloatsNullable>("nozzle_diameter");
|
||||
if (!nozzle_diameter_opt || nozzle_diameter_opt->values.size() > 1)
|
||||
return false;
|
||||
|
||||
auto *is_mixed_opt = wxGetApp().preset_bundle->project_config.option<ConfigOptionBools>("filament_is_mixed");
|
||||
if (!is_mixed_opt || !has_any_mixed_filament(is_mixed_opt->values))
|
||||
return false;
|
||||
|
||||
auto is_mixed_slot = [&](int extruder_1based) {
|
||||
size_t idx = (size_t)(extruder_1based - 1);
|
||||
return idx < is_mixed_opt->values.size() && is_mixed_opt->values[idx];
|
||||
};
|
||||
|
||||
const std::string mixed_warn_msg = _u8L("Printing mixed-color filament on a single-extruder printer requires frequent filament changes and flushing, "
|
||||
"which may significantly increase waste and the risk of nozzle / waste-chute clogging.");
|
||||
|
||||
for (int obj_idx = 0; obj_idx < (int)m_model->objects.size(); ++obj_idx) {
|
||||
if (!contain_instance_totally(obj_idx, 0))
|
||||
continue;
|
||||
ModelObject *mo = m_model->objects[obj_idx];
|
||||
int obj_ext = mo->config.has("extruder") ? mo->config.extruder() : 1;
|
||||
if (is_mixed_slot(obj_ext)) {
|
||||
warning_text = mixed_warn_msg;
|
||||
return true;
|
||||
}
|
||||
for (ModelVolume *mv : mo->volumes) {
|
||||
int vol_ext = mv->config.has("extruder") ? mv->config.extruder() : obj_ext;
|
||||
if (is_mixed_slot(vol_ext)) {
|
||||
warning_text = mixed_warn_msg;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PartPlate::check_mixture_of_pla_and_petg(const DynamicPrintConfig &config)
|
||||
{
|
||||
bool has_pla = false;
|
||||
@@ -6384,6 +6485,31 @@ int PartPlateList::store_to_3mf_structure(PlateDataPtrs& plate_data_list, bool w
|
||||
}
|
||||
//parse filament info
|
||||
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 {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "slice result = " << m_plate_list[i]->get_slice_result()
|
||||
<< ", result valid = " << m_plate_list[i]->is_slice_result_valid();
|
||||
@@ -6450,6 +6576,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;
|
||||
gcode_result->warnings = plate_data_list[i]->warnings;
|
||||
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
|
||||
// the monitor/preview can map filaments to physical nozzles.
|
||||
|
||||
Reference in New Issue
Block a user