|
|
|
|
@@ -53,6 +53,7 @@ using namespace nlohmann;
|
|
|
|
|
|
|
|
|
|
#include "libslic3r/libslic3r.h"
|
|
|
|
|
#include "libslic3r/Config.hpp"
|
|
|
|
|
#include "libslic3r/FilamentMixer.hpp"
|
|
|
|
|
#include "libslic3r/Geometry.hpp"
|
|
|
|
|
#include "libslic3r/GCode.hpp"
|
|
|
|
|
#include "libslic3r/Model.hpp"
|
|
|
|
|
@@ -160,6 +161,7 @@ std::map<int, std::string> cli_errors = {
|
|
|
|
|
{CLI_FILAMENT_CAN_NOT_MAP, "Some filaments cannot be mapped to correct extruders for multi-extruder Printer."},
|
|
|
|
|
{CLI_ONLY_ONE_TPU_SUPPORTED, "Not support printing 2 or more TPU filaments."},
|
|
|
|
|
{CLI_FILAMENTS_NOT_SUPPORTED_BY_EXTRUDER, "Some filaments cannot be printed on the extruder mapped to."},
|
|
|
|
|
{CLI_MIXED_FILAMENT_INVALID, "A mixed filament is invalid: its components are different filament types, or it has no filament of its own."},
|
|
|
|
|
{CLI_SLICING_ERROR, "Failed slicing the model. Please verify the slicing of all plates on Orca Slicer before uploading."},
|
|
|
|
|
{CLI_GCODE_PATH_CONFLICTS, " G-code conflicts detected after slicing. Please make sure the 3mf file can be successfully sliced in the latest Orca Slicer. If the file slices normally in Orca Slicer, try moving the wipe tower further from other models, as we use more conservative parameters for it during upload."},
|
|
|
|
|
{CLI_GCODE_PATH_IN_UNPRINTABLE_AREA, "Found G-code in unprintable area of multi-extruder printers after slicing. Please make sure the 3mf file can be successfully sliced in the latest Orca Slicer."}
|
|
|
|
|
@@ -3585,6 +3587,15 @@ int CLI::run(int argc, char **argv)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A mixed slot never reaches a nozzle, so its row and column stay empty, as in the GUI.
|
|
|
|
|
// Command line options are not merged into m_print_config yet, so they win here.
|
|
|
|
|
const ConfigOptionBools *is_mixed_opt = m_extra_config.option<ConfigOptionBools>("filament_is_mixed");
|
|
|
|
|
if (!is_mixed_opt)
|
|
|
|
|
is_mixed_opt = m_print_config.option<ConfigOptionBools>("filament_is_mixed");
|
|
|
|
|
auto is_mixed_slot = [is_mixed_opt](int idx) {
|
|
|
|
|
return is_mixed_opt && idx < static_cast<int>(is_mixed_opt->values.size()) && is_mixed_opt->values[idx];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (size_t nozzle_id = 0; nozzle_id < new_extruder_count; ++nozzle_id) {
|
|
|
|
|
std::vector<double> flush_vol_mtx = get_flush_volumes_matrix(flush_vol_matrix, nozzle_id, new_extruder_count);
|
|
|
|
|
for (int from_idx = 0; from_idx < project_filament_count; from_idx++) {
|
|
|
|
|
@@ -3594,7 +3605,7 @@ int CLI::run(int argc, char **argv)
|
|
|
|
|
bool is_from_support = filament_is_support->get_at(from_idx);
|
|
|
|
|
for (int to_idx = 0; to_idx < project_filament_count; to_idx++) {
|
|
|
|
|
bool is_to_support = filament_is_support->get_at(to_idx);
|
|
|
|
|
if (from_idx == to_idx) {
|
|
|
|
|
if (from_idx == to_idx || is_mixed_slot(from_idx) || is_mixed_slot(to_idx)) {
|
|
|
|
|
flush_vol_mtx[project_filament_count * from_idx + to_idx] = 0.f;
|
|
|
|
|
} else {
|
|
|
|
|
int flushing_volume = 0;
|
|
|
|
|
@@ -3737,6 +3748,22 @@ int CLI::run(int argc, char **argv)
|
|
|
|
|
// Normalizing after importing the 3MFs / AMFs
|
|
|
|
|
m_print_config.normalize_fdm();
|
|
|
|
|
|
|
|
|
|
// A mixed slot is virtual but still needs a filament entry of its own. Without one, feature
|
|
|
|
|
// filament ids aimed at it fall outside the filament count, are reset to the first filament
|
|
|
|
|
// and the model silently prints in a single colour.
|
|
|
|
|
if (const auto *is_mixed_opt = m_print_config.option<ConfigOptionBools>("filament_is_mixed")) {
|
|
|
|
|
const auto &is_mixed = is_mixed_opt->values;
|
|
|
|
|
for (size_t slot = static_cast<size_t>(std::max(filament_count, 0)); slot < is_mixed.size(); ++slot) {
|
|
|
|
|
if (!is_mixed[slot])
|
|
|
|
|
continue;
|
|
|
|
|
BOOST_LOG_TRIVIAL(error) << boost::format("mixed filament slot %1% has no filament of its own, only %2% filaments are loaded; "
|
|
|
|
|
"load one filament per slot, including each mixed one")
|
|
|
|
|
% (slot + 1) % filament_count;
|
|
|
|
|
record_exit_reson(outfile_dir, CLI_MIXED_FILAMENT_INVALID, 0, cli_errors[CLI_MIXED_FILAMENT_INVALID], sliced_info);
|
|
|
|
|
flush_and_exit(CLI_MIXED_FILAMENT_INVALID);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_print_config.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology", true)->value = printer_technology;
|
|
|
|
|
|
|
|
|
|
bool has_wipe_tower_position = m_print_config.option<ConfigOptionFloats>("wipe_tower_x") && m_print_config.option<ConfigOptionFloats>("wipe_tower_y");
|
|
|
|
|
@@ -3791,6 +3818,15 @@ int CLI::run(int argc, char **argv)
|
|
|
|
|
bool is_smooth_timelapse = false;
|
|
|
|
|
if (enable_timelapse && timelapse_type_opt && (timelapse_type_opt->getInt() == TimelapseType::tlSmooth))
|
|
|
|
|
is_smooth_timelapse = true;
|
|
|
|
|
// A mixed filament swaps between its components every layer, so it needs the tower even when
|
|
|
|
|
// every loaded preset is the same.
|
|
|
|
|
if (disable_wipe_tower_after_mapping) {
|
|
|
|
|
if (const auto *is_mixed_opt = m_print_config.option<ConfigOptionBools>("filament_is_mixed");
|
|
|
|
|
is_mixed_opt && has_any_mixed_filament(is_mixed_opt->values)) {
|
|
|
|
|
disable_wipe_tower_after_mapping = false;
|
|
|
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%, set disable_wipe_tower_after_mapping back to false due to a mixed filament")%__LINE__;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (disable_wipe_tower_after_mapping) {
|
|
|
|
|
if (is_smooth_timelapse)
|
|
|
|
|
{
|
|
|
|
|
@@ -5997,6 +6033,36 @@ int CLI::run(int argc, char **argv)
|
|
|
|
|
flush_and_exit(CLI_ONLY_ONE_TPU_SUPPORTED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Same type gate as the GUI's Sidebar::has_broken_mixed_filament: refuse a plate that uses a
|
|
|
|
|
// mixed slot whose components are different filament types. Missing or out-of-range
|
|
|
|
|
// components never get here, validate() already rejects them for the whole project.
|
|
|
|
|
const auto *is_mixed_opt = m_print_config.option<ConfigOptionBools>("filament_is_mixed");
|
|
|
|
|
const auto *components_opt = m_print_config.option<ConfigOptionStrings>("filament_mixed_components");
|
|
|
|
|
if (is_mixed_opt && components_opt && has_any_mixed_filament(is_mixed_opt->values)) {
|
|
|
|
|
const auto &is_mixed = is_mixed_opt->values;
|
|
|
|
|
const auto &components = components_opt->values;
|
|
|
|
|
const size_t num_physical = static_cast<size_t>(filament_count) - static_cast<size_t>(std::count(is_mixed.begin(), is_mixed.end(), true));
|
|
|
|
|
std::vector<std::string> physical_types(num_physical);
|
|
|
|
|
for (size_t f_index = 0; f_index < num_physical; ++f_index) {
|
|
|
|
|
std::string displayed_type;
|
|
|
|
|
physical_types[f_index] = m_print_config.get_filament_type(displayed_type, static_cast<int>(f_index));
|
|
|
|
|
if (physical_types[f_index].empty())
|
|
|
|
|
physical_types[f_index] = "PLA";
|
|
|
|
|
}
|
|
|
|
|
const std::vector<size_t> mismatched_slots = check_mixed_filament_type_consistency(is_mixed, components, physical_types);
|
|
|
|
|
// plate_filaments has mixed slots expanded to their components; the gate needs the slots.
|
|
|
|
|
const std::vector<int> plate_slots = mismatched_slots.empty() ? std::vector<int>() :
|
|
|
|
|
part_plate->get_extruders_under_cli(true, m_print_config, false);
|
|
|
|
|
for (size_t slot : mismatched_slots) {
|
|
|
|
|
if (std::find(plate_slots.begin(), plate_slots.end(), static_cast<int>(slot) + 1) == plate_slots.end())
|
|
|
|
|
continue;
|
|
|
|
|
BOOST_LOG_TRIVIAL(error) << boost::format("plate %1%: mixed filament %2% mixes components of different filament types")
|
|
|
|
|
% (index + 1) % (slot + 1);
|
|
|
|
|
record_exit_reson(outfile_dir, CLI_MIXED_FILAMENT_INVALID, index + 1, cli_errors[CLI_MIXED_FILAMENT_INVALID], sliced_info);
|
|
|
|
|
flush_and_exit(CLI_MIXED_FILAMENT_INVALID);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (new_extruder_count > 1) {
|
|
|
|
|
std::vector<std::vector<int>> unprintable_filament_vec;
|
|
|
|
|
for (const std::set<int>& filamnt_ids : unprintable_filament_ids) {
|
|
|
|
|
|