diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index 5463f55c20..22eac64784 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -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 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("filament_is_mixed"); + if (!is_mixed_opt) + is_mixed_opt = m_print_config.option("filament_is_mixed"); + auto is_mixed_slot = [is_mixed_opt](int idx) { + return is_mixed_opt && idx < static_cast(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 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("filament_is_mixed")) { + const auto &is_mixed = is_mixed_opt->values; + for (size_t slot = static_cast(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>("printer_technology", true)->value = printer_technology; bool has_wipe_tower_position = m_print_config.option("wipe_tower_x") && m_print_config.option("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("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("filament_is_mixed"); + const auto *components_opt = m_print_config.option("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(filament_count) - static_cast(std::count(is_mixed.begin(), is_mixed.end(), true)); + std::vector 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(f_index)); + if (physical_types[f_index].empty()) + physical_types[f_index] = "PLA"; + } + const std::vector 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 plate_slots = mismatched_slots.empty() ? std::vector() : + 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(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> unprintable_filament_vec; for (const std::set& filamnt_ids : unprintable_filament_ids) { diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 797894442a..c364860531 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -70,6 +70,7 @@ #define CLI_FILAMENT_CAN_NOT_MAP -66 #define CLI_ONLY_ONE_TPU_SUPPORTED -67 #define CLI_FILAMENTS_NOT_SUPPORTED_BY_EXTRUDER -68 +#define CLI_MIXED_FILAMENT_INVALID -69 #define CLI_SLICING_ERROR -100 #define CLI_GCODE_PATH_CONFLICTS -101 diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index d38ab5e0ac..b695cc15d4 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -1716,7 +1716,7 @@ std::vector PartPlate::get_extruders(bool conside_custom_gcode, const Dynam return plate_extruders; } -std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config) const +std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config, bool expand_mixed_slots) const { std::vector plate_extruders; @@ -1877,7 +1877,7 @@ std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D // 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. - { + if (expand_mixed_slots) { auto* is_mixed_opt = full_config.option("filament_is_mixed"); auto* comp_strs_opt = full_config.option("filament_mixed_components"); if (is_mixed_opt && comp_strs_opt && has_any_mixed_filament(is_mixed_opt->values)) { diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index 6d7eb18beb..e913ebaabf 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -350,7 +350,8 @@ public: // get used filaments from config, 1 based idx std::vector get_extruders(bool conside_custom_gcode = false) const; std::vector get_extruders(bool conside_custom_gcode, const DynamicPrintConfig& glb_config, const DynamicPrintConfig& project_config) const; - std::vector get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config) const; + // expand_mixed_slots = false keeps mixed filament slots as slots instead of their components. + std::vector get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config, bool expand_mixed_slots = true) const; std::vector get_extruders_without_support(bool conside_custom_gcode = false) const; // get used filaments from gcode result, 1 based idx std::vector get_used_filaments();