mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-19 05:31:04 +00:00
Merge branch 'main' into dev/ams-heat
# Conflicts: # src/libslic3r/Preset.cpp # src/libslic3r/PrintConfig.hpp # src/slic3r/GUI/DeviceCore/CMakeLists.txt # src/slic3r/GUI/DeviceCore/DevDefs.h # src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp # src/slic3r/GUI/DeviceCore/DevFilaSystem.h # src/slic3r/GUI/DeviceCore/DevUtilBackend.cpp # src/slic3r/GUI/DeviceCore/DevUtilBackend.h # src/slic3r/GUI/DeviceManager.cpp # src/slic3r/GUI/DeviceManager.hpp # src/slic3r/GUI/SelectMachine.cpp # src/slic3r/GUI/SelectMachine.hpp
This commit is contained in:
commit
246f2d32bf
702 files changed
+85605
-2732
No files matched your search
+96
-4
@@ -1326,6 +1326,14 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
set_temporary_dir(temp_path);
|
||||
|
||||
// The Filament Track Switch flags are live-device state with no meaning in headless slicing;
|
||||
// default both off unless explicitly provided on the command line, so an old 3MF that had
|
||||
// them enabled still slices without the switch behavior.
|
||||
if (!m_extra_config.has("has_filament_switcher"))
|
||||
m_extra_config.set_key_value("has_filament_switcher", new ConfigOptionBool(false));
|
||||
if (!m_extra_config.has("enable_filament_dynamic_map"))
|
||||
m_extra_config.set_key_value("enable_filament_dynamic_map", new ConfigOptionBool(false));
|
||||
|
||||
m_extra_config.apply(m_config, true);
|
||||
m_extra_config.normalize_fdm();
|
||||
|
||||
@@ -1703,11 +1711,13 @@ int CLI::run(int argc, char **argv)
|
||||
const Vec3d &instance_offset = model_instance->get_offset();
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("instance %1% transform {%2%,%3%,%4%} at %5%:%6%")% model_object->name % instance_offset.x() % instance_offset.y() %instance_offset.z() % __FUNCTION__ % __LINE__<< std::endl;
|
||||
}*/
|
||||
current_printer_name = config.option<ConfigOptionString>("printer_settings_id")->value;
|
||||
current_process_name = config.option<ConfigOptionString>("print_settings_id")->value;
|
||||
// Read defensively — a 3mf missing preset ids (e.g. one produced
|
||||
// by a non-GUI writer) would otherwise crash here on the deref.
|
||||
if (const auto *o = config.option<ConfigOptionString>("printer_settings_id")) current_printer_name = o->value;
|
||||
if (const auto *o = config.option<ConfigOptionString>("print_settings_id")) current_process_name = o->value;
|
||||
current_printer_model = config.option<ConfigOptionString>("printer_model", true)->value;
|
||||
current_filaments_name = config.option<ConfigOptionStrings>("filament_settings_id")->values;
|
||||
current_extruder_count = config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
|
||||
if (const auto *o = config.option<ConfigOptionStrings>("filament_settings_id")) current_filaments_name = o->values;
|
||||
if (const auto *o = config.option<ConfigOptionFloats>("nozzle_diameter")) current_extruder_count = o->values.size();
|
||||
current_printer_variant_count = config.option<ConfigOptionStrings>("printer_extruder_variant", true)->values.size();
|
||||
current_print_variant_count = config.option<ConfigOptionStrings>("print_extruder_variant", true)->values.size();
|
||||
current_is_multi_extruder = current_extruder_count > 1;
|
||||
@@ -5937,6 +5947,72 @@ int CLI::run(int argc, char **argv)
|
||||
else
|
||||
filament_maps = part_plate->get_real_filament_maps(m_print_config);
|
||||
|
||||
// Multi-nozzle printers need the per-filament volume assignment as a grouping
|
||||
// input in the manual modes: synthesize it from the per-extruder flow types when
|
||||
// the caller did not provide one (an extruder whose nozzle stats span several
|
||||
// volume types keeps the per-filament choice), and require explicit maps in
|
||||
// nozzle-manual mode.
|
||||
auto max_nozzle_counts_opt = m_print_config.option<ConfigOptionIntsNullable>("extruder_max_nozzle_count");
|
||||
// Skip nil entries: a nullable-int nil is INT_MAX (> 1) and would otherwise falsely pass the gate.
|
||||
bool support_multi_nozzle =
|
||||
max_nozzle_counts_opt &&
|
||||
std::any_of(max_nozzle_counts_opt->values.begin(), max_nozzle_counts_opt->values.end(),
|
||||
[](int v) { return v > 1 && v != ConfigOptionIntsNullable::nil_value(); });
|
||||
if (support_multi_nozzle && (mode == fmmManual || mode == fmmNozzleManual) && (plate_to_slice != 0)) {
|
||||
// Orca: the grouping result is reconstructed purely from the passed maps in
|
||||
// nozzle-manual mode, so all of them must be present (there are no separate
|
||||
// per-nozzle CLI parameters to rebuild them from).
|
||||
if (mode == FilamentMapMode::fmmNozzleManual &&
|
||||
(!m_extra_config.has("filament_volume_map") || !m_extra_config.has("filament_nozzle_map") ||
|
||||
!m_extra_config.has("filament_map"))) {
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< boost::format("%1%, can not find filament_volume_map/filament_nozzle_map/filament_map under Nozzle Manual mode") % __LINE__;
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, index + 1, cli_errors[CLI_INVALID_PARAMS], sliced_info);
|
||||
flush_and_exit(CLI_INVALID_PARAMS);
|
||||
}
|
||||
|
||||
if (mode == fmmManual) {
|
||||
// Build the volume map when absent: filaments on a single-volume extruder
|
||||
// print with that extruder's volume; a mixed-volume extruder keeps the
|
||||
// per-filament choice (default Standard).
|
||||
std::vector<NozzleVolumeType> using_nozzle_volume_type = new_nozzle_volume_type;
|
||||
using_nozzle_volume_type.resize(new_extruder_count, nvtStandard);
|
||||
if (auto extruder_nozzle_stats_opt = m_print_config.option<ConfigOptionStrings>("extruder_nozzle_stats")) {
|
||||
auto nozzle_stats = get_extruder_nozzle_stats(extruder_nozzle_stats_opt->values);
|
||||
for (int e_index = 0; e_index < new_extruder_count && e_index < (int) nozzle_stats.size(); e_index++) {
|
||||
if (nozzle_stats[e_index].size() > 1) {
|
||||
using_nozzle_volume_type[e_index] = nvtHybrid;
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1% : extruder %2%, set nozzle_volume_type to hybrid ") % __LINE__ % (e_index + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<int> &manual_volume_maps = m_extra_config.option<ConfigOptionInts>("filament_volume_map", true)->values;
|
||||
// default to standard flow
|
||||
manual_volume_maps.resize(filament_count, (int) (nvtStandard));
|
||||
for (int f_index = 0; f_index < filament_count && f_index < (int) filament_maps.size(); f_index++) {
|
||||
int f_extruder_index = filament_maps[f_index] - 1;
|
||||
if (f_extruder_index >= 0 && f_extruder_index < new_extruder_count &&
|
||||
using_nozzle_volume_type[f_extruder_index] != nvtHybrid) {
|
||||
manual_volume_maps[f_index] = int(using_nozzle_volume_type[f_extruder_index]);
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1% : filament %2% extruder %3%, set filament_volume_map to %4% ") % __LINE__ % (f_index + 1) % (f_extruder_index + 1) % manual_volume_maps[f_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_extra_config.has("filament_volume_map")) {
|
||||
part_plate->set_filament_volume_maps(m_extra_config.option<ConfigOptionInts>("filament_volume_map")->values);
|
||||
}
|
||||
if (m_extra_config.has("filament_nozzle_map")) {
|
||||
part_plate->set_filament_nozzle_maps(m_extra_config.option<ConfigOptionInts>("filament_nozzle_map")->values);
|
||||
}
|
||||
}
|
||||
else if (!support_multi_nozzle && (mode == fmmNozzleManual)) {
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< boost::format("%1%, Nozzle Manual mode not supported for %2%") % __LINE__ % new_printer_name;
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, index + 1, cli_errors[CLI_INVALID_PARAMS], sliced_info);
|
||||
flush_and_exit(CLI_INVALID_PARAMS);
|
||||
}
|
||||
|
||||
for (int index = 0; index < filament_maps.size(); index++)
|
||||
{
|
||||
int filament_extruder = filament_maps[index];
|
||||
@@ -6175,6 +6251,22 @@ int CLI::run(int argc, char **argv)
|
||||
BOOST_LOG_TRIVIAL(info) << "print::process: first time_using_cache is " << time_using_cache << " secs.";
|
||||
}
|
||||
if (printer_technology == ptFFF) {
|
||||
// Read the engine's final grouping back onto the plate so an exported
|
||||
// project (--export-3mf / gcode.3mf) carries the concrete maps in its
|
||||
// plate settings, matching what a GUI slice persists.
|
||||
// Orca: deliberately gated to multi-extruder printers so single-extruder
|
||||
// exports keep their plate settings unchanged.
|
||||
if (new_extruder_count > 1) {
|
||||
FilamentMapMode current_map_mode = print_fff->config().filament_map_mode.value;
|
||||
if (is_auto_filament_map_mode(current_map_mode)) {
|
||||
part_plate->set_filament_maps(print_fff->get_filament_maps());
|
||||
part_plate->set_filament_volume_maps(print_fff->get_filament_volume_maps());
|
||||
}
|
||||
if (current_map_mode != FilamentMapMode::fmmNozzleManual) {
|
||||
part_plate->set_filament_nozzle_maps(print_fff->get_filament_nozzle_maps());
|
||||
}
|
||||
}
|
||||
|
||||
std::string conflict_result = print_fff->get_conflict_string();
|
||||
if (!conflict_result.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": found slicing result conflict!"<< std::endl;
|
||||
|
||||
@@ -304,6 +304,10 @@ void AppConfig::set_defaults()
|
||||
if (get("show_3d_navigator").empty())
|
||||
set_bool("show_3d_navigator", true);
|
||||
|
||||
// Show the one-time "Filament Track Switch is ready" tip until it has been seen once.
|
||||
if (get("show_fila_switch_tips").empty())
|
||||
set_bool("show_fila_switch_tips", true);
|
||||
|
||||
if (get("show_plate_gridlines").empty())
|
||||
set_bool("show_plate_gridlines", true);
|
||||
|
||||
@@ -800,6 +804,10 @@ std::string AppConfig::load()
|
||||
preset_info.nozzle_volume_type = NozzleVolumeType(cali_it.value()["nozzle_volume_type"].get<int>());
|
||||
if (cali_it.value().contains("bed_type"))
|
||||
preset_info.bed_type = BedType(cali_it.value()["bed_type"].get<int>());
|
||||
if (cali_it.value().contains("nozzle_pos_id"))
|
||||
preset_info.nozzle_pos_id = cali_it.value()["nozzle_pos_id"].get<int>();
|
||||
if (cali_it.value().contains("nozzle_sn"))
|
||||
preset_info.nozzle_sn = cali_it.value()["nozzle_sn"].get<std::string>();
|
||||
cali_info.selected_presets.push_back(preset_info);
|
||||
}
|
||||
}
|
||||
@@ -957,6 +965,8 @@ void AppConfig::save()
|
||||
preset_json["extruder_id"] = filament_preset.extruder_id;
|
||||
preset_json["nozzle_volume_type"] = int(filament_preset.nozzle_volume_type);
|
||||
preset_json["bed_type"] = int(filament_preset.bed_type);
|
||||
preset_json["nozzle_pos_id"] = filament_preset.nozzle_pos_id;
|
||||
preset_json["nozzle_sn"] = filament_preset.nozzle_sn;
|
||||
preset_json["nozzle_diameter"] = filament_preset.nozzle_diameter;
|
||||
preset_json["filament_id"] = filament_preset.filament_id;
|
||||
preset_json["setting_id"] = filament_preset.setting_id;
|
||||
|
||||
@@ -471,6 +471,8 @@ set(lisbslic3r_sources
|
||||
FilamentGroup.cpp
|
||||
FilamentGroupUtils.hpp
|
||||
FilamentGroupUtils.cpp
|
||||
MultiNozzleUtils.hpp
|
||||
MultiNozzleUtils.cpp
|
||||
GCode/ToolOrderUtils.hpp
|
||||
GCode/ToolOrderUtils.cpp
|
||||
FlushVolPredictor.hpp
|
||||
|
||||
@@ -743,6 +743,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
// Apply an override option, possibly a nullable one.
|
||||
//default_index are 0 based
|
||||
bool apply_override(const ConfigOption *rhs, std::vector<int>& default_index) override {
|
||||
if (this->nullable())
|
||||
throw ConfigurationError("Cannot override a nullable ConfigOption.");
|
||||
@@ -778,8 +779,8 @@ public:
|
||||
this->values[i] = rhs_vec->values[i];
|
||||
modified = true;
|
||||
} else {
|
||||
if ((i < default_index.size()) && (default_index[i] - 1 < default_value.size()))
|
||||
this->values[i] = default_value[default_index[i] - 1];
|
||||
if ((i < default_index.size()) && (default_index[i] < default_value.size()))
|
||||
this->values[i] = default_value[default_index[i]];
|
||||
else
|
||||
this->values[i] = default_value[0];
|
||||
}
|
||||
|
||||
@@ -13,11 +13,20 @@ Extruder::Extruder(unsigned int id, GCodeConfig *config, bool share_extruder) :
|
||||
{
|
||||
reset();
|
||||
|
||||
m_config_index = int(m_id);
|
||||
// cache values that are going to be called often
|
||||
m_e_per_mm3 = this->filament_flow_ratio();
|
||||
m_e_per_mm3 /= this->filament_crossection();
|
||||
}
|
||||
|
||||
void Extruder::set_config_index(int idx)
|
||||
{
|
||||
m_config_index = idx < 0 ? int(m_id) : idx;
|
||||
// keep the cached flow term reading the same column as the getters
|
||||
m_e_per_mm3 = this->filament_flow_ratio();
|
||||
m_e_per_mm3 /= this->filament_crossection();
|
||||
}
|
||||
|
||||
unsigned int Extruder::extruder_id() const
|
||||
{
|
||||
assert(m_config);
|
||||
@@ -162,28 +171,28 @@ double Extruder::filament_cost() const
|
||||
|
||||
double Extruder::filament_flow_ratio() const
|
||||
{
|
||||
return m_config->filament_flow_ratio.get_at(m_id);
|
||||
return m_config->filament_flow_ratio.get_at(m_config_index);
|
||||
}
|
||||
|
||||
// Return a "retract_before_wipe" percentage as a factor clamped to <0, 1>
|
||||
double Extruder::retract_before_wipe() const
|
||||
{
|
||||
return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_id) * 0.01));
|
||||
return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_config_index) * 0.01));
|
||||
}
|
||||
|
||||
double Extruder::retraction_length() const
|
||||
{
|
||||
return m_config->retraction_length.get_at(m_id);
|
||||
return m_config->retraction_length.get_at(m_config_index);
|
||||
}
|
||||
|
||||
double Extruder::retract_lift() const
|
||||
{
|
||||
return m_config->z_hop.get_at(m_id);
|
||||
return m_config->z_hop.get_at(m_config_index);
|
||||
}
|
||||
|
||||
int Extruder::retract_speed() const
|
||||
{
|
||||
return int(floor(m_config->retraction_speed.get_at(m_id)+0.5));
|
||||
return int(floor(m_config->retraction_speed.get_at(m_config_index)+0.5));
|
||||
}
|
||||
|
||||
bool Extruder::use_firmware_retraction() const
|
||||
@@ -193,13 +202,13 @@ bool Extruder::use_firmware_retraction() const
|
||||
|
||||
int Extruder::deretract_speed() const
|
||||
{
|
||||
int speed = int(floor(m_config->deretraction_speed.get_at(m_id)+0.5));
|
||||
int speed = int(floor(m_config->deretraction_speed.get_at(m_config_index)+0.5));
|
||||
return (speed > 0) ? speed : this->retract_speed();
|
||||
}
|
||||
|
||||
double Extruder::retract_restart_extra() const
|
||||
{
|
||||
return m_config->retract_restart_extra.get_at(m_id);
|
||||
return m_config->retract_restart_extra.get_at(m_config_index);
|
||||
}
|
||||
|
||||
double Extruder::retract_length_toolchange() const
|
||||
@@ -214,6 +223,8 @@ double Extruder::retract_restart_extra_toolchange() const
|
||||
|
||||
double Extruder::travel_slope() const
|
||||
{
|
||||
// Orca: deliberately keyed by the physical extruder, not the filament column — this read
|
||||
// predates the per-variant merge and switching it would change existing multi-extruder output.
|
||||
return m_config->travel_slope.get_at(extruder_id()) * PI / 180;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,13 @@ public:
|
||||
|
||||
unsigned int id() const { return m_id; }
|
||||
|
||||
// Column of the per-variant filament/override arrays the getters read. Defaults to the
|
||||
// filament id (one column per filament); the g-code generator refreshes it on layer changes
|
||||
// and toolchanges when a per-layer nozzle grouping gives a filament several variant columns.
|
||||
int config_index() const { return m_config_index; }
|
||||
// idx < 0 resets to the filament id. Re-syncs the cached e_per_mm3 flow term.
|
||||
void set_config_index(int idx);
|
||||
|
||||
unsigned int extruder_id() const;
|
||||
double extrude(double dE);
|
||||
double retract(double length, double restart_extra);
|
||||
@@ -51,6 +58,10 @@ public:
|
||||
double retracted() const { return m_retracted; }
|
||||
// Get extra retraction planned after
|
||||
double restart_extra() const { return m_restart_extra; }
|
||||
// Share-aware retracted-length readers (for extruders shared between filaments), consumed by GCodeWriter::get_extruder_retracted_length.
|
||||
bool is_share_extruder() const { return m_share_extruder; }
|
||||
double get_single_retracted_length() const { return m_retracted; }
|
||||
double get_share_retracted_length() const { return m_share_retracted[extruder_id()]; }
|
||||
// Setters for the PlaceholderParser.
|
||||
// Set current extruder position. Only applicable with absolute extruder addressing.
|
||||
void set_position(double e) { m_E = e; }
|
||||
@@ -82,6 +93,8 @@ private:
|
||||
GCodeConfig *m_config;
|
||||
// Print-wide global ID of this extruder.
|
||||
unsigned int m_id;
|
||||
// Column into the per-variant filament/override arrays; equals m_id unless refreshed.
|
||||
int m_config_index{0};
|
||||
// Current state of the extruder axis, may be resetted if use_relative_e_distances.
|
||||
double m_E;
|
||||
// Current state of the extruder tachometer, used to output the extruded_volume() and used_filament() statistics.
|
||||
|
||||
+929
-344
File diff suppressed because it is too large.
Load diff
@@ -13,7 +13,8 @@
|
||||
|
||||
const static int DEFAULT_CLUSTER_SIZE = 16;
|
||||
|
||||
const static int ABSOLUTE_FLUSH_GAP_TOLERANCE = 5;
|
||||
const static int ABSOLUTE_FLUSH_GAP_TOLERANCE = 10;
|
||||
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
@@ -52,12 +53,12 @@ namespace Slic3r
|
||||
|
||||
struct MemoryedGroup {
|
||||
MemoryedGroup() = default;
|
||||
MemoryedGroup(const std::vector<int>& group_, const int cost_, const int prefer_level_) :group(group_), cost(cost_), prefer_level(prefer_level_) {}
|
||||
MemoryedGroup(const std::vector<int>& group_, const double cost_, const int prefer_level_) :group(group_), cost(cost_), prefer_level(prefer_level_) {}
|
||||
bool operator>(const MemoryedGroup& other) const {
|
||||
return prefer_level < other.prefer_level || (prefer_level == other.prefer_level && cost > other.cost);
|
||||
}
|
||||
|
||||
int cost{ 0 };
|
||||
double cost{ 0 };
|
||||
int prefer_level{ 0 };
|
||||
std::vector<int>group;
|
||||
};
|
||||
@@ -75,6 +76,7 @@ namespace Slic3r
|
||||
std::vector<FilamentGroupUtils::FilamentInfo> filament_info;
|
||||
std::vector<std::string> filament_ids;
|
||||
std::vector<std::set<int>> unprintable_filaments;
|
||||
std::map<int, std::set<NozzleVolumeType>> unprintable_volumes;
|
||||
} model_info;
|
||||
|
||||
struct GroupInfo {
|
||||
@@ -82,40 +84,64 @@ namespace Slic3r
|
||||
double max_gap_threshold;
|
||||
FGMode mode;
|
||||
FGStrategy strategy;
|
||||
bool ignore_ext_filament; //wai gua filament
|
||||
bool ignore_ext_filament;
|
||||
bool has_filament_switcher = false;
|
||||
std::vector<int> filament_volume_map;
|
||||
} group_info;
|
||||
|
||||
struct MachineInfo {
|
||||
std::vector<int> max_group_size;
|
||||
std::vector<std::vector<FilamentGroupUtils::MachineFilamentInfo>> machine_filament_info;
|
||||
std::vector<std::pair<std::set<int>, int>> extruder_group_size;
|
||||
std::vector<bool> prefer_non_model_filament;
|
||||
int master_extruder_id;
|
||||
} machine_info;
|
||||
|
||||
struct SpeedInfo{
|
||||
std::unordered_map<int,std::unordered_map<int,double>> filament_print_time;
|
||||
double extruder_change_time;
|
||||
double filament_change_time;
|
||||
bool group_with_time;
|
||||
MultiNozzleUtils::FilamentChangeTimeParams change_time_params;
|
||||
std::vector<bool> ams_preload_enabled;
|
||||
} speed_info;
|
||||
|
||||
struct NozzleInfo {
|
||||
std::map<int, std::vector<int>> extruder_nozzle_list;
|
||||
std::vector<MultiNozzleUtils::NozzleInfo> nozzle_list;
|
||||
std::unordered_map<int, int> nozzle_status;
|
||||
} nozzle_info;
|
||||
};
|
||||
|
||||
std::vector<int> select_best_group_for_ams(const std::vector<std::vector<int>>& map_lists,
|
||||
std::vector<int> select_best_group_for_ams(const std::vector<std::vector<int>> &filament_to_nozzles,
|
||||
const std::vector<MultiNozzleUtils::NozzleInfo>& nozzle_list,
|
||||
const std::vector<unsigned int>& used_filaments,
|
||||
const std::vector<FilamentGroupUtils::FilamentInfo>& used_filament_info,
|
||||
const std::vector<std::vector<FilamentGroupUtils::MachineFilamentInfo>>& machine_filament_info,
|
||||
const bool has_filament_switcher = false,
|
||||
const double color_delta_threshold = 20);
|
||||
|
||||
std::vector<int> optimize_group_for_master_extruder(const std::vector<unsigned int>& used_filaments, const FilamentGroupContext& ctx, const std::vector<int>& filament_map);
|
||||
|
||||
bool can_swap_groups(const int extruder_id_0, const std::set<int>& group_0, const int extruder_id_1, const std::set<int>& group_1, const FilamentGroupContext& ctx);
|
||||
|
||||
std::vector<int> calc_filament_group_for_tpu(const std::set<int>& tpu_filaments, const int filament_nums, const int master_extruder_id);
|
||||
|
||||
class FlushDistanceEvaluator
|
||||
{
|
||||
public:
|
||||
FlushDistanceEvaluator(const FlushMatrix& flush_matrix,const std::vector<unsigned int>&used_filaments,const std::vector<std::vector<unsigned int>>& layer_filaments, double p = 0.65);
|
||||
FlushDistanceEvaluator(const std::vector<FlushMatrix>& flush_matrix,const std::vector<unsigned int>&used_filaments,const std::vector<std::vector<unsigned int>>& layer_filaments, double p = 0.65);
|
||||
~FlushDistanceEvaluator() = default;
|
||||
double get_distance(int idx_a, int idx_b) const;
|
||||
double get_distance(int idx_a, int idx_b, int extruder_id) const;
|
||||
private:
|
||||
std::vector<std::vector<float>>m_distance_matrix;
|
||||
std::vector<std::vector<std::vector<float>>>m_distance_matrix;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class TimeEvaluator
|
||||
{
|
||||
public:
|
||||
TimeEvaluator(const FilamentGroupContext::SpeedInfo& speed_info) : m_speed_info(speed_info) {}
|
||||
double get_estimated_time(const std::vector<int>& filament_map) const;
|
||||
private:
|
||||
FilamentGroupContext::SpeedInfo m_speed_info;
|
||||
};
|
||||
|
||||
class FilamentGroup
|
||||
{
|
||||
using MemoryedGroup = FilamentGroupUtils::MemoryedGroup;
|
||||
@@ -129,11 +155,17 @@ namespace Slic3r
|
||||
public:
|
||||
std::vector<int> calc_filament_group_for_match(int* cost = nullptr);
|
||||
std::vector<int> calc_filament_group_for_flush(int* cost = nullptr);
|
||||
|
||||
std::vector<int> calc_filament_group_for_tpu(int* cost = nullptr);
|
||||
private:
|
||||
std::vector<int> calc_min_flush_group(int* cost = nullptr);
|
||||
std::vector<int> calc_min_flush_group_by_enum(const std::vector<unsigned int>& used_filaments, int* cost = nullptr);
|
||||
std::vector<int> calc_min_flush_group_by_pam2(const std::vector<unsigned int>& used_filaments, int* cost = nullptr, int timeout_ms = 300);
|
||||
|
||||
std::vector<int> calc_group_by_enum(int k, const std::vector<unsigned int>& used_filaments,
|
||||
const std::unordered_map<int, std::vector<int>>& unplaceable_limits, int* cost = nullptr);
|
||||
std::vector<int> calc_group_by_kmedoids(int k, const std::vector<unsigned int>& used_filaments,
|
||||
const std::unordered_map<int, std::vector<int>>& unplaceable_limits, int* cost = nullptr, int timeout_ms = 500);
|
||||
|
||||
std::map<int, int> rebuild_unprintables(const std::vector<unsigned int>& used_filaments, const std::map<int,int>& extruder_unprintables);
|
||||
std::unordered_map<int, std::vector<int>> rebuild_nozzle_unprintables(const std::vector<unsigned int>& used_filaments, const std::unordered_map<int, std::vector<int>>& extruder_unprintables, const std::vector<int>& filament_volume_map);
|
||||
|
||||
std::unordered_map<int, std::vector<int>> try_merge_filaments();
|
||||
void rebuild_context(const std::unordered_map<int, std::vector<int>>& merged_filaments);
|
||||
@@ -141,57 +173,78 @@ namespace Slic3r
|
||||
|
||||
private:
|
||||
FilamentGroupContext ctx;
|
||||
MemoryedGroupHeap m_memoryed_heap;
|
||||
std::vector<std::vector<int>> m_memoryed_groups;
|
||||
|
||||
public:
|
||||
std::optional<std::function<bool(int, std::vector<int>&)>> get_custom_seq;
|
||||
};
|
||||
|
||||
|
||||
class KMediods2
|
||||
std::vector<int> calc_filament_group_for_manual_multi_nozzle(const std::vector<int>& filament_map_manual,const FilamentGroupContext& ctx);
|
||||
|
||||
std::vector<int> calc_filament_group_for_match_multi_nozzle(const FilamentGroupContext& ctx);
|
||||
|
||||
struct FilamentPlanRes
|
||||
{
|
||||
std::vector<int> fil_order;
|
||||
std::vector<int> fil_nozzle_match;
|
||||
};
|
||||
|
||||
std::vector<FilamentPlanRes> plan_filament_nozzle_mapping_and_order(const FilamentGroupContext& ctx);
|
||||
|
||||
|
||||
class KMediods
|
||||
{
|
||||
protected:
|
||||
using MemoryedGroupHeap = FilamentGroupUtils::MemoryedGroupHeap;
|
||||
using MemoryedGroup = FilamentGroupUtils::MemoryedGroup;
|
||||
|
||||
enum INIT_TYPE
|
||||
{
|
||||
Random = 0,
|
||||
Farthest
|
||||
};
|
||||
public:
|
||||
KMediods2(const int elem_count, const std::shared_ptr<FlushDistanceEvaluator>& evaluator, int default_group_id = 0) :
|
||||
m_evaluator{ evaluator },
|
||||
m_elem_count{ elem_count },
|
||||
m_default_group_id{ default_group_id }
|
||||
{
|
||||
m_max_cluster_size = std::vector<int>(m_k, DEFAULT_CLUSTER_SIZE);
|
||||
KMediods(const int k, const int elem_count, const std::shared_ptr<FlushDistanceEvaluator>& evaluator, int default_group_id = 0) {
|
||||
m_k = k;
|
||||
m_evaluator = evaluator;
|
||||
m_max_cluster_size = std::vector<int>(k, DEFAULT_CLUSTER_SIZE);
|
||||
m_elem_count = elem_count;
|
||||
m_default_group_id = default_group_id;
|
||||
}
|
||||
|
||||
// set max group size
|
||||
void set_max_cluster_size(const std::vector<int>& group_size) { m_max_cluster_size = group_size; }
|
||||
|
||||
// key stores elem idx, value stores the cluster id that elem cnanot be placed
|
||||
void set_unplaceable_limits(const std::map<int, int>& placeable_limits) { m_unplaceable_limits = placeable_limits; }
|
||||
void set_cluster_group_size(const std::vector<std::pair<std::set<int>,int>>& cluster_group_size);
|
||||
|
||||
void do_clustering(const FGStrategy& g_strategy,int timeout_ms = 100);
|
||||
// key stores elem, value stores the cluster id that the elem must be placed
|
||||
void set_placable_limits(const std::unordered_map<int, std::vector<int>>& placable_limits) { m_placeable_limits = placable_limits; }
|
||||
|
||||
// key stores elem, value stores the cluster id that the elem cannot be placed
|
||||
void set_unplacable_limits(const std::unordered_map<int, std::vector<int>>& unplacable_limits) { m_unplaceable_limits = unplacable_limits; }
|
||||
|
||||
void set_memory_threshold(double threshold) { memory_threshold = threshold; }
|
||||
MemoryedGroupHeap get_memoryed_groups()const { return memoryed_groups; }
|
||||
|
||||
std::vector<int>get_cluster_labels()const { return m_cluster_labels; }
|
||||
void do_clustering(const FilamentGroupContext& context, int timeout_ms = 100, int retry = 10);
|
||||
std::vector<int> get_cluster_labels()const { return m_cluster_labels; }
|
||||
|
||||
private:
|
||||
std::vector<int>cluster_small_data(const std::map<int, int>& unplaceable_limits, const std::vector<int>& group_size);
|
||||
std::vector<int>assign_cluster_label(const std::vector<int>& center, const std::map<int, int>& unplaceable_limits, const std::vector<int>& group_size, const FGStrategy& strategy);
|
||||
int calc_cost(const std::vector<int>& labels, const std::vector<int>& medoids);
|
||||
protected:
|
||||
FilamentGroupUtils::MemoryedGroupHeap memoryed_groups;
|
||||
std::shared_ptr<FlushDistanceEvaluator> m_evaluator;
|
||||
std::map<int, int>m_unplaceable_limits;
|
||||
std::vector<int>m_cluster_labels;
|
||||
std::vector<int>m_max_cluster_size;
|
||||
bool have_enough_size(const std::vector<int>& cluster_size, const std::vector<std::pair<std::set<int>, int>>& cluster_group_size,int elem_count);
|
||||
// calculate cluster distance
|
||||
int calc_cost(const std::vector<int>& clusters, const std::vector<int>& cluster_centers, int cluster_id = -1);
|
||||
|
||||
const int m_k = 2;
|
||||
// get initial cluster center
|
||||
std::vector<int>init_cluster_center(const std::unordered_map<int, std::vector<int>>& placeable_limits, const std::unordered_map<int, std::vector<int>>& unplaceable_limits, const std::vector<int>& cluster_size, const std::vector<std::pair<std::set<int>, int>>& cluster_group_size, int seed);
|
||||
// assign each elem to the cluster
|
||||
std::vector<int> assign_cluster_label(const std::vector<int>& center, const std::unordered_map<int, std::vector<int>>& placeable_limits, const std::unordered_map<int, std::vector<int>>& unplaceable_limits, const std::vector<int>& group_size, const std::vector<std::pair<std::set<int>, int>>& cluster_group_size);
|
||||
|
||||
protected:
|
||||
MemoryedGroupHeap memoryed_groups;
|
||||
std::shared_ptr<FlushDistanceEvaluator>m_evaluator;
|
||||
std::unordered_map<int, std::vector<int>> m_unplaceable_limits; // key: filament, value: nozzle ids it cannot be assigned to
|
||||
std::unordered_map<int, std::vector<int>> m_placeable_limits; // key: filament, value: nozzle ids it must be assigned to
|
||||
std::vector<int>m_max_cluster_size; // max number of filaments each nozzle can hold
|
||||
std::vector<int>m_cluster_labels; // assignment result, resolved down to nozzle id
|
||||
std::vector<std::pair<std::set<int>,int>> m_cluster_group_size;
|
||||
std::vector<int> m_nozzle_to_extruder;
|
||||
|
||||
|
||||
int m_k;
|
||||
int m_elem_count;
|
||||
int m_default_group_id{ 0 };
|
||||
double memory_threshold{ 0 };
|
||||
|
||||
@@ -274,5 +274,70 @@ namespace FilamentGroupUtils
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int get_estimate_extruder_change_count(const std::vector<std::vector<unsigned int>> &layer_filaments, const MultiNozzleUtils::LayeredNozzleGroupResult &extruder_nozzle_info)
|
||||
{
|
||||
int ret = 0;
|
||||
for (size_t layer_id = 0; layer_id < layer_filaments.size(); ++layer_id) {
|
||||
int extruder_count = extruder_nozzle_info.get_used_extruders(layer_id).size();
|
||||
ret += (extruder_count - 1);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int get_estimate_nozzle_change_count(const std::vector<std::vector<unsigned int>> &layer_filaments, const MultiNozzleUtils::LayeredNozzleGroupResult &extruder_nozzle_info)
|
||||
{
|
||||
int ret = 0;
|
||||
for (size_t layer_id = 0; layer_id < layer_filaments.size(); ++layer_id) {
|
||||
auto extruder_list = extruder_nozzle_info.get_used_extruders(layer_id);
|
||||
for (auto extruder_id : extruder_list) {
|
||||
int nozzle_count = extruder_nozzle_info.get_used_nozzles_in_extruder(extruder_id, layer_id).size();
|
||||
if (nozzle_count > 1) ret += (nozzle_count - 1);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::pair<int, int> get_estimate_extruder_filament_change_count(const MultiNozzleUtils::LayeredNozzleGroupResult &extruder_nozzle_info)
|
||||
{
|
||||
std::pair<int, int> ret{0,0};
|
||||
int layer_nums = extruder_nozzle_info.get_layer_filament_sequences().size();
|
||||
for (int layer_id = 0; layer_id < layer_nums; layer_id++) {
|
||||
std::vector<int> extruders = extruder_nozzle_info.get_used_extruders(layer_id);
|
||||
ret.first = extruders.size() - 1;
|
||||
|
||||
for (auto ext_id : extruders) {
|
||||
int nozzles = extruder_nozzle_info.get_used_nozzles_in_extruder(ext_id, layer_id).size();
|
||||
ret.second += nozzles;
|
||||
}
|
||||
ret.second = std::max(0, ret.second - ret.first);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::map<int,std::vector<int>> build_extruder_nozzle_list(const std::vector<MultiNozzleUtils::NozzleInfo>& nozzle_list)
|
||||
{
|
||||
std::map<int, std::vector<int>> ret;
|
||||
for (auto& nozzle : nozzle_list) {
|
||||
ret[nozzle.extruder_id].emplace_back(nozzle.group_id);
|
||||
}
|
||||
|
||||
for (auto& elem : ret)
|
||||
std::sort(elem.second.begin(), elem.second.end());
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<int> update_used_filament_values(const std::vector<int>& old_values, const std::vector<int>& new_values, const std::vector<unsigned int>& used_filaments)
|
||||
{
|
||||
std::vector<int> res = old_values;
|
||||
for (size_t i = 0; i < used_filaments.size(); ++i) {
|
||||
// Orca: guard against filament ids beyond the map sizes (possible with
|
||||
// mis-normalized per-filament arrays from CLI inputs); skip instead of UB.
|
||||
if (used_filaments[i] >= res.size() || used_filaments[i] >= new_values.size())
|
||||
continue;
|
||||
res[used_filaments[i]] = new_values[used_filaments[i]];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <exception>
|
||||
|
||||
#include "PrintConfig.hpp"
|
||||
#include "MultiNozzleUtils.hpp"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
@@ -31,6 +32,10 @@ namespace Slic3r
|
||||
Color color;
|
||||
std::string type;
|
||||
bool is_support;
|
||||
// How this filament is used across the model. Orca's shipping grouping
|
||||
// algorithm does not read it yet; defaulted so a default-built FilamentInfo
|
||||
// is deterministic. The nozzle-centric engine consumes it later.
|
||||
FilamentUsageType usage_type = FilamentUsageType::ModelOnly;
|
||||
};
|
||||
|
||||
struct MachineFilamentInfo: public FilamentInfo {
|
||||
@@ -80,6 +85,20 @@ namespace Slic3r
|
||||
void extract_unprintable_limit_indices(const std::vector<std::set<int>>& unprintable_elems, const std::vector<unsigned int>& used_filaments, std::unordered_map<int, std::vector<int>>& unplaceable_limits);
|
||||
|
||||
bool check_printable(const std::vector<std::set<int>>& groups, const std::map<int, int>& unprintable);
|
||||
|
||||
// Nozzle-centric grouping helpers. The estimate helpers read a LayeredNozzleGroupResult's
|
||||
// per-layer extruder/nozzle usage; the two builders support building the grouping context
|
||||
// (extruder->nozzle inventory) and writing back a resolved map onto only the used-filament
|
||||
// slots.
|
||||
int get_estimate_extruder_change_count(const std::vector<std::vector<unsigned int>>& layer_filaments, const MultiNozzleUtils::LayeredNozzleGroupResult& extruder_nozzle_info);
|
||||
|
||||
int get_estimate_nozzle_change_count(const std::vector<std::vector<unsigned int>>& layer_filaments, const MultiNozzleUtils::LayeredNozzleGroupResult& extruder_nozzle_info);
|
||||
|
||||
std::pair<int, int> get_estimate_extruder_filament_change_count(const MultiNozzleUtils::LayeredNozzleGroupResult& extruder_nozzle_info);
|
||||
|
||||
std::map<int, std::vector<int>> build_extruder_nozzle_list(const std::vector<MultiNozzleUtils::NozzleInfo>& nozzle_list);
|
||||
|
||||
std::vector<int> update_used_filament_values(const std::vector<int>& old_values, const std::vector<int>& new_values, const std::vector<unsigned int>& used_filaments);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -347,6 +347,7 @@ static constexpr const char* OTHER_LAYERS_PRINT_SEQUENCE_NUMS_ATTR = "other_laye
|
||||
static constexpr const char* SPIRAL_VASE_MODE = "spiral_mode";
|
||||
static constexpr const char* FILAMENT_MAP_MODE_ATTR = "filament_map_mode";
|
||||
static constexpr const char* FILAMENT_MAP_ATTR = "filament_maps";
|
||||
static constexpr const char* FILAMENT_VOL_MAP_ATTR = "filament_volume_maps";
|
||||
static constexpr const char* LIMIT_FILAMENT_MAP_ATTR = "limit_filament_maps";
|
||||
static constexpr const char* GCODE_FILE_ATTR = "gcode_file";
|
||||
static constexpr const char* THUMBNAIL_FILE_ATTR = "thumbnail_file";
|
||||
@@ -699,6 +700,35 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
info.id = it->first;
|
||||
info.used_g = used_filament_g;
|
||||
info.used_m = used_filament_m;
|
||||
|
||||
// Stamp each filament's logical-nozzle assignment onto the saved 3mf so the device/monitor can
|
||||
// reconstruct it. This block runs for every print: reorder_extruders_for_minimum_flush_volume
|
||||
// runs unconditionally and stores a (non-null) 1-nozzle result even for a single-extruder print,
|
||||
// so result->nozzle_group_result is non-null here for single-nozzle printers too. The stamped
|
||||
// nozzle_diameter is the grouping result's rounded matching-key value; the 3mf writer decides the
|
||||
// final saved diameter (see has_multi_nozzle_extruder). group_id and volume_type are unaffected.
|
||||
if (result && result->nozzle_group_result) {
|
||||
auto nozzles_for_filament = result->nozzle_group_result->get_nozzles_for_filament(it->first);
|
||||
if (!nozzles_for_filament.empty()) {
|
||||
info.group_id.reserve(nozzles_for_filament.size());
|
||||
std::set<double> diameters;
|
||||
std::set<NozzleVolumeType> volume_types;
|
||||
for (const auto& nozzle : nozzles_for_filament) {
|
||||
info.group_id.emplace_back(nozzle.group_id);
|
||||
diameters.insert(string_to_double_decimal_point(nozzle.diameter));
|
||||
volume_types.insert(nozzle.volume_type);
|
||||
}
|
||||
std::sort(info.group_id.begin(), info.group_id.end());
|
||||
info.group_id.erase(std::unique(info.group_id.begin(), info.group_id.end()), info.group_id.end());
|
||||
if (!diameters.empty())
|
||||
info.nozzle_diameter = *diameters.begin();
|
||||
if (volume_types.size() > 1)
|
||||
info.nozzle_volume_type = get_nozzle_volume_type_string(nvtHybrid);
|
||||
else if (!volume_types.empty())
|
||||
info.nozzle_volume_type = get_nozzle_volume_type_string(*volume_types.begin());
|
||||
}
|
||||
}
|
||||
|
||||
auto model_volume_it = ps.model_volumes_per_extruder.find(it->first);
|
||||
auto support_volume_it = ps.support_volumes_per_extruder.find(it->first);
|
||||
info.used_for_object = model_volume_it != ps.model_volumes_per_extruder.end() && model_volume_it->second > EPSILON;
|
||||
@@ -706,6 +736,13 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
slice_filaments_info.push_back(info);
|
||||
}
|
||||
|
||||
// Carry the layer-aware grouping result into the plate so the 3mf writer can emit the <nozzle> tags
|
||||
// and the enable_filament_dynamic_map flag. Only a LayeredNozzleGroupResult (the slicer output) is
|
||||
// stored; a device-side StaticNozzleGroupResult loaded from a 3mf is not re-serialized here.
|
||||
auto layered_group_result = std::dynamic_pointer_cast<MultiNozzleUtils::LayeredNozzleGroupResult>(result->nozzle_group_result);
|
||||
if (layered_group_result)
|
||||
nozzle_group_result = *layered_group_result;
|
||||
|
||||
/* only for test
|
||||
GCodeProcessorResult::SliceWarning sw;
|
||||
sw.msg = BED_TEMP_TOO_HIGH_THAN_FILAMENT;
|
||||
@@ -1283,6 +1320,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
bool _handle_start_config_warning(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_warning();
|
||||
|
||||
bool _handle_start_config_nozzle(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_nozzle();
|
||||
|
||||
//BBS: add plater config parse functions
|
||||
bool _handle_start_config_plater(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_plater();
|
||||
@@ -1618,8 +1658,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
plate->is_label_object_enabled = it->second->is_label_object_enabled;
|
||||
plate->skipped_objects = it->second->skipped_objects;
|
||||
plate->slice_filaments_info = it->second->slice_filaments_info;
|
||||
plate->nozzles_info = it->second->nozzles_info;
|
||||
plate->printer_model_id = it->second->printer_model_id;
|
||||
plate->nozzle_diameters = it->second->nozzle_diameters;
|
||||
plate->nozzle_volume_types = it->second->nozzle_volume_types;
|
||||
plate->filament_maps = it->second->filament_maps;
|
||||
plate->filament_change_sequence = it->second->filament_change_sequence;
|
||||
plate->nozzle_change_sequence = it->second->nozzle_change_sequence;
|
||||
@@ -2289,9 +2331,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
plate_data_list[it->first-1]->is_support_used = it->second->is_support_used;
|
||||
plate_data_list[it->first-1]->is_label_object_enabled = it->second->is_label_object_enabled;
|
||||
plate_data_list[it->first-1]->slice_filaments_info = it->second->slice_filaments_info;
|
||||
plate_data_list[it->first-1]->nozzles_info = it->second->nozzles_info;
|
||||
plate_data_list[it->first-1]->skipped_objects = it->second->skipped_objects;
|
||||
plate_data_list[it->first-1]->printer_model_id = it->second->printer_model_id;
|
||||
plate_data_list[it->first-1]->nozzle_diameters = it->second->nozzle_diameters;
|
||||
plate_data_list[it->first-1]->nozzle_volume_types = it->second->nozzle_volume_types;
|
||||
plate_data_list[it->first-1]->filament_maps = it->second->filament_maps;
|
||||
plate_data_list[it->first-1]->filament_change_sequence = it->second->filament_change_sequence;
|
||||
plate_data_list[it->first-1]->nozzle_change_sequence = it->second->nozzle_change_sequence;
|
||||
@@ -3469,6 +3513,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
res = _handle_start_config_filament(attributes, num_attributes);
|
||||
else if (::strcmp(SLICE_WARNING_TAG, name) == 0)
|
||||
res = _handle_start_config_warning(attributes, num_attributes);
|
||||
else if (::strcmp(NOZZLE_TAG, name) == 0)
|
||||
res = _handle_start_config_nozzle(attributes, num_attributes);
|
||||
else if (::strcmp(ASSEMBLE_TAG, name) == 0)
|
||||
res = _handle_start_assemble(attributes, num_attributes);
|
||||
else if (::strcmp(ASSEMBLE_ITEM_TAG, name) == 0)
|
||||
@@ -3503,6 +3549,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
res = _handle_end_config_plater();
|
||||
else if (::strcmp(FILAMENT_TAG, name) == 0)
|
||||
res = _handle_end_config_filament();
|
||||
else if (::strcmp(NOZZLE_TAG, name) == 0)
|
||||
res = _handle_end_config_nozzle();
|
||||
else if (::strcmp(INSTANCE_TAG, name) == 0)
|
||||
res = _handle_end_config_plater_instance();
|
||||
else if (::strcmp(ASSEMBLE_TAG, name) == 0)
|
||||
@@ -4460,6 +4508,21 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
m_curr_plater->config.set_key_value("filament_map", new ConfigOptionInts(filament_map));
|
||||
}
|
||||
}
|
||||
else if (key == FILAMENT_VOL_MAP_ATTR) {
|
||||
if (m_curr_plater){
|
||||
auto filament_volume_map = get_vector_from_string(value);
|
||||
for (size_t idx = 0; idx < filament_volume_map.size(); ++idx) {
|
||||
// The map feeds per-filament slot resolution and grouping. Clamp any
|
||||
// higher volume-type back to Standard(0) on load: Hybrid(2) is only an
|
||||
// in-memory grouping seed that is never persisted, and TPU High Flow(3)
|
||||
// is clamped with the same information loss on every load.
|
||||
if (filament_volume_map[idx] > 1) {
|
||||
filament_volume_map[idx] = 0;
|
||||
}
|
||||
}
|
||||
m_curr_plater->config.set_key_value("filament_volume_map", new ConfigOptionInts(filament_volume_map));
|
||||
}
|
||||
}
|
||||
else if (key == GCODE_FILE_ATTR)
|
||||
{
|
||||
m_curr_plater->gcode_file = value;
|
||||
@@ -4569,6 +4632,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
if (m_curr_plater)
|
||||
m_curr_plater->printer_model_id = value;
|
||||
}
|
||||
else if (key == NOZZLE_VOLUME_TYPE_ATTR)
|
||||
{
|
||||
if (m_curr_plater)
|
||||
m_curr_plater->nozzle_volume_types = value;
|
||||
}
|
||||
else if (key == NOZZLE_DIAMETERS_ATTR)
|
||||
{
|
||||
if (m_curr_plater)
|
||||
@@ -4622,6 +4690,41 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_start_config_nozzle(const char** attributes, unsigned int num_attributes)
|
||||
{
|
||||
// Read the per-plate <nozzle> tags. Older 3mf without <nozzle> tags leave nozzles_info
|
||||
// empty; load_nozzle_infos_with_compatibility then rebuilds the list from the per-filament
|
||||
// group_id / filament_map on the device side.
|
||||
if (m_curr_plater) {
|
||||
// id="0" extruder_id="1" nozzle_diameter="0.4" volume_type="Standard"
|
||||
std::string id = bbs_get_attribute_value_string(attributes, num_attributes, "id");
|
||||
std::string extruder_id = bbs_get_attribute_value_string(attributes, num_attributes, "extruder_id");
|
||||
std::string nozzle_diameter= bbs_get_attribute_value_string(attributes, num_attributes, "nozzle_diameter");
|
||||
std::string volume_type = bbs_get_attribute_value_string(attributes, num_attributes, "volume_type");
|
||||
|
||||
auto volume_type_str_to_enum = ConfigOptionEnum<NozzleVolumeType>::get_enum_values();
|
||||
|
||||
MultiNozzleUtils::NozzleInfo nozzle_info;
|
||||
nozzle_info.group_id = atoi(id.c_str());
|
||||
nozzle_info.extruder_id = atoi(extruder_id.c_str()) - 1;
|
||||
nozzle_info.diameter = nozzle_diameter;
|
||||
|
||||
if (volume_type_str_to_enum.count(volume_type))
|
||||
nozzle_info.volume_type = NozzleVolumeType(volume_type_str_to_enum.at(volume_type));
|
||||
else
|
||||
nozzle_info.volume_type = NozzleVolumeType::nvtStandard;
|
||||
|
||||
m_curr_plater->nozzles_info.push_back(nozzle_info);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_end_config_nozzle()
|
||||
{
|
||||
// do nothing
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_start_config_warning(const char** attributes, unsigned int num_attributes)
|
||||
{
|
||||
if (m_curr_plater) {
|
||||
@@ -7980,6 +8083,18 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
stream << "\"/>\n";
|
||||
}
|
||||
|
||||
ConfigOptionInts* filament_volume_maps_opt = plate_data->config.option<ConfigOptionInts>("filament_volume_map");
|
||||
if (filament_map_mode_opt != nullptr && filament_volume_maps_opt != nullptr) {
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << FILAMENT_VOL_MAP_ATTR << "\" " << VALUE_ATTR << "=\"";
|
||||
const std::vector<int>& volume_values = filament_volume_maps_opt->values;
|
||||
for (int i = 0; i < volume_values.size(); ++i) {
|
||||
stream << volume_values[i];
|
||||
if (i != (volume_values.size() - 1))
|
||||
stream << " ";
|
||||
}
|
||||
stream << "\"/>\n";
|
||||
}
|
||||
|
||||
if (save_gcode)
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << GCODE_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << xml_escape(plate_data->gcode_file) << "\"/>\n";
|
||||
if (!plate_data->gcode_file.empty()) {
|
||||
@@ -8119,7 +8234,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
[](unsigned int filament_id) { return filament_id + 1; });
|
||||
|
||||
const std::string plate_key = "plate_" + std::to_string(idx + 1);
|
||||
sequence_json[plate_key]["sequence"] = filament_sequence;
|
||||
// Dynamic-map plates write the sequence under "filament_sequence"; every other plate (the
|
||||
// whole shipping fleet + H2C static mode) keeps the "sequence" key, so the saved 3mf is
|
||||
// byte-identical to the older format. The reader accepts both.
|
||||
const bool enable_dynamic_map = plate_data->nozzle_group_result && plate_data->nozzle_group_result->is_support_dynamic_nozzle_map();
|
||||
const std::string seq_key = enable_dynamic_map ? "filament_sequence" : "sequence";
|
||||
sequence_json[plate_key][seq_key] = filament_sequence;
|
||||
sequence_json[plate_key]["nozzle_sequence"] = plate_data->nozzle_change_sequence;
|
||||
sequence_json[plate_key]["optimal_assignment"] = plate_data->optimal_assignment;
|
||||
}
|
||||
@@ -8205,6 +8325,18 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
if (nozzle_diameter_option)
|
||||
nozzle_diameters_str = nozzle_diameter_option->serialize();
|
||||
|
||||
// True when any extruder carries a cluster of interchangeable nozzles (max nozzle count
|
||||
// > 1). Such an extruder's per-nozzle diameters are not expressible in the per-extruder
|
||||
// nozzle_diameter config, so the saved <filament>/<nozzle> diameters must come from the
|
||||
// grouping result. For a single-nozzle-per-extruder printer the true diameter is the raw
|
||||
// config value; the grouping result rounds it to the nearest of {0.2,0.4,0.6,0.8} for its
|
||||
// internal matching key, so reading that back would rewrite a non-standard nozzle
|
||||
// (e.g. 0.5 -> 0.4). Use this flag to keep the exact config value in that case.
|
||||
auto* extruder_max_nozzle_count_option = dynamic_cast<const ConfigOptionInts*>(config.option("extruder_max_nozzle_count"));
|
||||
const bool has_multi_nozzle_extruder = extruder_max_nozzle_count_option &&
|
||||
std::any_of(extruder_max_nozzle_count_option->values.begin(), extruder_max_nozzle_count_option->values.end(),
|
||||
[](int v) { return v > 1; });
|
||||
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PRINTER_MODEL_ID_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->printer_model_id << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << NOZZLE_DIAMETERS_ATTR << "\" " << VALUE_ATTR << "=\"" << nozzle_diameters_str << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << TIMELAPSE_TYPE_ATTR << "\" " << VALUE_ATTR << "=\"" << timelapse_type << "\"/>\n";
|
||||
@@ -8214,7 +8346,15 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << OUTSIDE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->toolpath_outside << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SUPPORT_USED_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->is_support_used << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << LABEL_OBJECT_ENABLED_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->is_label_object_enabled << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << ENABLE_FILAMENT_DYNAMIC_MAP_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << false << "\"/>\n";
|
||||
// Report the plate's dynamic-map state from the grouping result. The result is present
|
||||
// for the whole fleet (a static single-nozzle result too), so this if-branch is normally
|
||||
// taken; is_support_dynamic_nozzle_map() is false for any non-dynamic (static /
|
||||
// single-extruder) result ⇒ byte-identical to the previously hard-coded value. The else
|
||||
// is a defensive fallback for a missing result.
|
||||
if (plate_data && plate_data->nozzle_group_result)
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << ENABLE_FILAMENT_DYNAMIC_MAP_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << plate_data->nozzle_group_result->is_support_dynamic_nozzle_map() << "\"/>\n";
|
||||
else
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << ENABLE_FILAMENT_DYNAMIC_MAP_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << false << "\"/>\n";
|
||||
{
|
||||
bool has_filament_switcher = config.has("has_filament_switcher") ? config.opt_bool("has_filament_switcher") : false;
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << HAS_FILAMENT_SWITCHER_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << has_filament_switcher << "\"/>\n";
|
||||
@@ -8329,7 +8469,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
if (std::find(used_nozzle_groups.begin(), used_nozzle_groups.end(), nozzle_group_id) == used_nozzle_groups.end())
|
||||
used_nozzle_groups.push_back(nozzle_group_id);
|
||||
const std::string filament_nozzle_group_id = it->group_id.empty() ? std::to_string(nozzle_group_id) : join_int_list_comma(it->group_id);
|
||||
const double filament_nozzle_diameter = it->nozzle_diameter > 0.0 ? it->nozzle_diameter : get_nozzle_diameter(nozzle_group_id);
|
||||
// Single-nozzle extruders: exact config diameter; clusters keep the result's rounded
|
||||
// value (see has_multi_nozzle_extruder).
|
||||
const double filament_nozzle_diameter = (has_multi_nozzle_extruder && it->nozzle_diameter > 0.0)
|
||||
? it->nozzle_diameter : get_nozzle_diameter(nozzle_group_id);
|
||||
const std::string filament_nozzle_volume_type = it->nozzle_volume_type.empty() ? get_nozzle_volume_type(nozzle_group_id) : it->nozzle_volume_type;
|
||||
|
||||
stream << " <" << FILAMENT_TAG << " " << FILAMENT_ID_TAG << "=\"" << std::to_string(it->id + 1) << "\" "
|
||||
@@ -8349,12 +8492,26 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
stream << " <" << SLICE_WARNING_TAG << " msg=\"" << it->msg << "\" level=\"" << std::to_string(it->level) << "\" error_code =\"" << it->error_code << "\" />\n";
|
||||
}
|
||||
|
||||
for (int nozzle_group_id : used_nozzle_groups) {
|
||||
stream << " <" << NOZZLE_TAG << " "
|
||||
<< "id=\"" << nozzle_group_id << "\" "
|
||||
<< "extruder_id=\"" << nozzle_group_id + 1 << "\" "
|
||||
<< "nozzle_diameter=\"" << get_nozzle_diameter_str(nozzle_group_id) << "\" "
|
||||
<< "volume_type=\"" << get_nozzle_volume_type(nozzle_group_id) << "\"/>\n";
|
||||
// Emit the <nozzle> tags from the grouping result. Single-nozzle-per-extruder printers
|
||||
// override the diameter with the exact config value (see has_multi_nozzle_extruder); the
|
||||
// else is a defensive fallback for a missing result.
|
||||
if (plate_data->nozzle_group_result) {
|
||||
auto used_nozzle_list = plate_data->nozzle_group_result->get_used_nozzles_in_extruder();
|
||||
for (auto& used_nozzle : used_nozzle_list) {
|
||||
if (!has_multi_nozzle_extruder && nozzle_diameter_option &&
|
||||
used_nozzle.extruder_id >= 0 && used_nozzle.extruder_id < (int) nozzle_diameter_option->values.size()) {
|
||||
used_nozzle.diameter = get_nozzle_diameter_str(used_nozzle.extruder_id);
|
||||
}
|
||||
stream << " <" << NOZZLE_TAG << " " << used_nozzle.serialize() << "/>\n";
|
||||
}
|
||||
} else {
|
||||
for (int nozzle_group_id : used_nozzle_groups) {
|
||||
stream << " <" << NOZZLE_TAG << " "
|
||||
<< "id=\"" << nozzle_group_id << "\" "
|
||||
<< "extruder_id=\"" << nozzle_group_id + 1 << "\" "
|
||||
<< "nozzle_diameter=\"" << get_nozzle_diameter_str(nozzle_group_id) << "\" "
|
||||
<< "volume_type=\"" << get_nozzle_volume_type(nozzle_group_id) << "\"/>\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!plate_data->layer_filaments.empty()) {
|
||||
|
||||
@@ -73,6 +73,7 @@ struct PlateData
|
||||
std::map<int, std::pair<int, int>> obj_inst_map;
|
||||
std::string printer_model_id;
|
||||
std::string nozzle_diameters;
|
||||
std::string nozzle_volume_types;
|
||||
std::string gcode_file;
|
||||
std::string gcode_file_md5;
|
||||
std::string thumbnail_file;
|
||||
@@ -102,6 +103,13 @@ struct PlateData
|
||||
std::vector<unsigned int> nozzle_change_sequence;
|
||||
std::vector<int> optimal_assignment;
|
||||
|
||||
// Multi-nozzle grouping surface. nozzles_info accumulates the <nozzle> tags read from a
|
||||
// gcode.3mf; nozzle_group_result is the slicer's per-filament→nozzle assignment carried into the
|
||||
// saved 3mf metadata (write) and reconstructed on load. Both are empty/nullopt for single-nozzle
|
||||
// prints, so the saved-3mf output for single-nozzle printers is byte-identical.
|
||||
std::vector<MultiNozzleUtils::NozzleInfo> nozzles_info;
|
||||
std::optional<MultiNozzleUtils::LayeredNozzleGroupResult> nozzle_group_result;
|
||||
|
||||
// Hexadecimal number,
|
||||
// the 0th digit corresponds to extruder 1
|
||||
// the 1th digit corresponds to extruder 2
|
||||
|
||||
+1188
-231
File diff suppressed because it is too large.
Load diff
+61
-1
@@ -252,7 +252,8 @@ public:
|
||||
std::string travel_to(const Point& point, ExtrusionRole role, std::string comment, double z = DBL_MAX);
|
||||
bool needs_retraction(const Polyline& travel, ExtrusionRole role, LiftType& lift_type);
|
||||
std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::NormalLift, bool apply_instantly = false, ExtrusionRole role = erNone);
|
||||
std::string unretract() { return m_writer.unlift() + m_writer.unretract(); }
|
||||
// extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract.
|
||||
std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); }
|
||||
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1);
|
||||
bool is_BBL_Printer();
|
||||
WipeTowerType wipe_tower_type();
|
||||
@@ -263,6 +264,12 @@ public:
|
||||
// append full config to the given string
|
||||
static void append_full_config(const Print& print, std::string& str);
|
||||
|
||||
// Per-filament config-slot resolvers for the current layer (m_cur_layer_idx): the filament
|
||||
// resolver keys filament-indexed arrays, the nozzle resolver keys (extruder x volume-type)
|
||||
// slot arrays. Both degenerate to filament_id / extruder index on single-volume printers.
|
||||
size_t get_filament_config_index(int filament_id) const;
|
||||
size_t get_nozzle_config_index(int filament_id) const;
|
||||
|
||||
// Object and support extrusions of the same PrintObject at the same print_z.
|
||||
// public, so that it could be accessed by free helper functions from GCode.cpp
|
||||
struct LayerToPrint
|
||||
@@ -394,6 +401,7 @@ private:
|
||||
void check_placeholder_parser_failed();
|
||||
size_t cur_extruder_index() const;
|
||||
size_t get_extruder_id(unsigned int filament_id) const;
|
||||
void update_placeholder_parser_with_variant_params();
|
||||
|
||||
void set_last_pos(const Point &pos) { m_last_pos = Point3(pos, 0); m_last_pos_defined = true; }
|
||||
void set_last_pos(const Point3 &pos) { m_last_pos = pos; m_last_pos_defined = true; }
|
||||
@@ -402,6 +410,11 @@ private:
|
||||
std::string preamble();
|
||||
// BBS
|
||||
std::string change_layer(coordf_t print_z);
|
||||
// Bedslinger model: derive the Y-axis acceleration limit from the machine force/bed-mass config
|
||||
// and the mass already printed. Yields the min machine Y acceleration when the A2L config keys are
|
||||
// unset (i.e. every existing printer), so it is inert for them.
|
||||
void mass_load_limited_machine_acceleration(const PrintStatistics &curr_print_statistics, const Print &print,
|
||||
double &y_acceleration_limit_res, double &accumulated_mass_res);
|
||||
// Orca: pass the complete collection of region perimeters to the extrude loop to check whether the wipe before external loop
|
||||
// should be executed
|
||||
std::string extrude_entity(const ExtrusionEntity& entity,
|
||||
@@ -500,6 +513,18 @@ private:
|
||||
std::string extrude_infill(const Print& print, const std::vector<ObjectByExtruder::Island::Region>& by_region, bool ironing);
|
||||
std::string extrude_support(const ExtrusionEntityCollection& support_fills, const ExtrusionRole support_extrusion_role);
|
||||
|
||||
// Farthest-point timelapse: find the extrusion point farthest from camera (0,0)
|
||||
void compute_farthest_point(const std::vector<LayerToPrint> &layers, int most_used_extruder,
|
||||
const std::map<std::pair<const SupportLayer *, ExtrusionRole>, unsigned int> &support_filaments);
|
||||
// Build the per-layer timelapse snapshot g-code (safe-position or, when skip_pos_pick,
|
||||
// an inline photo at the current head position). Extracted from the former process_layer lambda so the
|
||||
// per-extrusion farthest-point hook (_extrude) can call it too. Identical to the old lambda when the
|
||||
// farthest-point subsystem is disabled (skip_pos_pick=false, m_farthest_point_timelapse.enabled=false).
|
||||
std::string generate_timelapse_gcode(const Print &print, coordf_t print_z, int most_used_extruder,
|
||||
const std::set<size_t> *layer_object_label_ids,
|
||||
const std::vector<const PrintObject*> *printed_objects,
|
||||
bool skip_pos_pick = false);
|
||||
|
||||
// BBS
|
||||
LiftType to_lift_type(ZHopType z_hop_types);
|
||||
|
||||
@@ -556,6 +581,32 @@ private:
|
||||
AvoidCrossingPerimeters m_avoid_crossing_perimeters;
|
||||
RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters;
|
||||
TimelapsePosPicker m_timelapse_pos_picker;
|
||||
|
||||
// Farthest-point timelapse context. Corexy-only refinement layered on top of the existing
|
||||
// timelapse_type. All fields default to the inert state; `enabled` is (re)computed each layer in
|
||||
// process_layer and is false whenever the farthest_point_timelapse config toggle is off, the printer
|
||||
// is i3 (psI3), or timelapse_type is not traditional — so every shipping printer that does not set the
|
||||
// toggle is identical to the previous path.
|
||||
struct FarthestPointTimelapseContext {
|
||||
// Whether farthest-point timelapse is active for this layer
|
||||
bool enabled{false};
|
||||
// The farthest extrusion point from camera (0,0) in global scaled coordinates (includes plate origin + inst.shift)
|
||||
Point farthest_point;
|
||||
// farthest_point converted to mm (gcode coordinate space, includes plate origin)
|
||||
Vec2d farthest_gcode_pos{0, 0};
|
||||
// Extruder index (0-based) that prints the farthest point
|
||||
int farthest_extruder_id{0};
|
||||
// Whether the farthest point is printed by the photo head (most_used_extruder)
|
||||
bool farthest_is_photo_head{false};
|
||||
// Whether inline timelapse gcode has already been inserted on this layer
|
||||
bool inserted_this_layer{false};
|
||||
// The extruder used most on this layer, chosen as the photo head
|
||||
int most_used_extruder{0};
|
||||
// Object labels for the current layer, used when inline timelapse is inserted from extrusion code.
|
||||
std::set<size_t> layer_object_label_ids;
|
||||
};
|
||||
FarthestPointTimelapseContext m_farthest_point_timelapse;
|
||||
|
||||
bool m_enable_loop_clipping;
|
||||
//resonance avoidance
|
||||
bool m_resonance_avoidance;
|
||||
@@ -595,6 +646,9 @@ private:
|
||||
float m_last_layer_z{ 0.0f };
|
||||
float m_max_layer_z{ 0.0f };
|
||||
float m_last_width{ 0.0f };
|
||||
// Bedslinger mass model: cumulative printed mass at the previous layer, used to derive
|
||||
// the current layer mass for the per-layer Y acceleration limit (curr_y_acceleration_limit).
|
||||
double m_last_layer_accumulated_mass{ 0.0 };
|
||||
|
||||
// Always check gcode placeholders when building in debug mode.
|
||||
#if !defined(NDEBUG)
|
||||
@@ -654,12 +708,18 @@ private:
|
||||
int m_start_gcode_filament = -1;
|
||||
std::string m_filament_instances_code;
|
||||
|
||||
// Object layer id of the layer being generated; keys the per-filament config-slot
|
||||
// resolvers. Distinct from m_layer_index (an export progress counter starting at -1).
|
||||
size_t m_cur_layer_idx{0};
|
||||
|
||||
std::set<unsigned int> m_initial_layer_extruders;
|
||||
std::vector<std::vector<unsigned int>> m_sorted_layer_filaments;
|
||||
// BBS
|
||||
int get_bed_temperature(const int extruder_id, const bool is_first_layer, const BedType bed_type) const;
|
||||
int get_highest_bed_temperature(const bool is_first_layer,const Print &print) const;
|
||||
|
||||
void update_layer_related_config(int layer_id);
|
||||
|
||||
double calc_max_volumetric_speed(const double layer_height, const double line_width, const std::string co_str);
|
||||
std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1);
|
||||
bool _needSAFC(const ExtrusionPath &path);
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -6,6 +6,7 @@
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/CustomGCode.hpp"
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
@@ -43,6 +44,23 @@ class Print;
|
||||
Count
|
||||
};
|
||||
|
||||
// Classifies why a wipe-tower / change_filament / time-lapse region is safe to relocate a
|
||||
// pre-heat M104 into, for the pre-heat/pre-cool injector. The shipping time_lapse_gcode
|
||||
// template (timelapse-on by default) emits SKIPPABLE_* on essentially every slice, so the
|
||||
// "timelapse" payload -> stTimelapse classification is exercised widely.
|
||||
enum SkipType
|
||||
{
|
||||
stTimelapse,
|
||||
stHeadWrapDetect,
|
||||
stOther,
|
||||
stNone
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string_view, SkipType> skip_type_map{
|
||||
{"timelapse", SkipType::stTimelapse},
|
||||
{"head_wrap_detect", SkipType::stHeadWrapDetect}
|
||||
};
|
||||
|
||||
struct PrintEstimatedStatistics
|
||||
{
|
||||
enum class ETimeMode : unsigned char
|
||||
@@ -77,6 +95,10 @@ class Print;
|
||||
|
||||
std::array<Mode, static_cast<size_t>(ETimeMode::Count)> modes;
|
||||
unsigned int total_filament_changes;
|
||||
// Number of filament changes that actually re-flush a nozzle (a filament-in-nozzle change
|
||||
// onto a non-empty nozzle), tracked only by the richer multi-nozzle hotend-change time model.
|
||||
// Stays 0 for single-nozzle printers (X1/P1/A1/H2S/A2L), which never enter the two-arg model.
|
||||
unsigned int total_flush_filament_changes;
|
||||
unsigned int total_extruder_changes;
|
||||
float total_filament_load_time;
|
||||
float total_filament_unload_time;
|
||||
@@ -101,6 +123,7 @@ class Print;
|
||||
flush_per_filament.clear();
|
||||
used_filaments_per_role.clear();
|
||||
total_filament_changes = 0;
|
||||
total_flush_filament_changes = 0;
|
||||
total_extruder_changes = 0;
|
||||
total_filament_load_time = 0.0f;
|
||||
total_filament_unload_time = 0.0f;
|
||||
@@ -166,6 +189,14 @@ class Print;
|
||||
ConflictResultOpt conflict_result;
|
||||
GCodeCheckResult gcode_check_result;
|
||||
FilamentPrintableResult filament_printable_reuslt;
|
||||
// The per-filament -> logical-nozzle grouping the slicer computed for this
|
||||
// result, surfaced onto the object the device GUI reads
|
||||
// (plater->background_process().get_current_gcode_result()). Populated only from
|
||||
// Print::get_layered_nozzle_group_result() (ToolOrdering's static L/R + rack subset);
|
||||
// default-empty (null) and read by no g-code emitter, so it is invisible in the emitted
|
||||
// g-code. Consumed by the print-dispatch nozzle mapping (DevNozzleMappingCtrl) via
|
||||
// DevUtilBackend::GetNozzleGroupResult.
|
||||
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> nozzle_group_result;
|
||||
float initial_layer_time;
|
||||
|
||||
struct SettingsIds
|
||||
@@ -263,6 +294,14 @@ class Print;
|
||||
std::vector<SliceWarning> warnings;
|
||||
int nozzle_hrc;
|
||||
std::vector<NozzleType> nozzle_type;
|
||||
// Per-extruder physical hotend type. Fed to the pre-heat injector's TimeProcessContext
|
||||
// (mixed-type X2D workaround). Populated in apply_config; unused until the injector side-pass
|
||||
// consumes it.
|
||||
std::vector<ExtruderType> extruder_types;
|
||||
// Machine-slot layout of the per-variant printer arrays (one entry per (extruder x
|
||||
// volume-type) slot). Populated in apply_config; keys the per-slot machine-limit lookup.
|
||||
std::vector<std::string> printer_extruder_variant;
|
||||
std::vector<int> printer_extruder_id;
|
||||
// first key stores filaments, second keys stores the layer ranges(enclosed) that use the filaments
|
||||
std::unordered_map<std::vector<unsigned int>, std::vector<std::pair<int, int>>,FilamentSequenceHash> layer_filaments;
|
||||
std::vector<unsigned int> nozzle_change_sequence;
|
||||
@@ -271,6 +310,11 @@ class Print;
|
||||
// first key stores `from` filament, second keys stores the `to` filament
|
||||
std::map<std::pair<int,int>, int > filament_change_count_map;
|
||||
|
||||
// Accumulated print time spent inside SKIPPABLE regions, per skip type. Populated by the time
|
||||
// estimator; consumed only downstream. The shipping time_lapse_gcode template emits SKIPPABLE_*
|
||||
// widely, so this is typically populated (stTimelapse) on most slices.
|
||||
std::unordered_map<SkipType, float> skippable_part_time;
|
||||
|
||||
BedType bed_type = BedType::btCount;
|
||||
void reset();
|
||||
|
||||
@@ -304,11 +348,20 @@ class Print;
|
||||
gcode_check_result = other.gcode_check_result;
|
||||
limit_filament_maps = other.limit_filament_maps;
|
||||
filament_printable_reuslt = other.filament_printable_reuslt;
|
||||
// Orca: copy the shared grouping result so a copied result keeps it (shared_ptr =>
|
||||
// memory-safe), rather than leaving a stale pointer on the target. No g-code effect either way.
|
||||
nozzle_group_result = other.nozzle_group_result;
|
||||
// Keep the per-extruder hotend types on a copied result (injector input).
|
||||
extruder_types = other.extruder_types;
|
||||
printer_extruder_variant = other.printer_extruder_variant;
|
||||
printer_extruder_id = other.printer_extruder_id;
|
||||
layer_filaments = other.layer_filaments;
|
||||
filament_change_sequence = other.filament_change_sequence;
|
||||
nozzle_change_sequence = other.nozzle_change_sequence;
|
||||
optimal_assignment = other.optimal_assignment;
|
||||
filament_change_count_map = other.filament_change_count_map;
|
||||
// Keep the SKIPPABLE per-type time on a copied result.
|
||||
skippable_part_time = other.skippable_part_time;
|
||||
initial_layer_time = other.initial_layer_time;
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
time = other.time;
|
||||
@@ -319,6 +372,75 @@ class Print;
|
||||
void unlock() const { result_mutex.unlock(); }
|
||||
};
|
||||
|
||||
// First-pass usage-block descriptors for the pre-heat/pre-cool injector. FilamentUsageBlock
|
||||
// records the [lower,upper) output-line-id span a single filament occupies; ExtruderUsageBlcok
|
||||
// (the "Blcok" typo is intentional) records the span an extruder is active in, with the start/end
|
||||
// filament + logical-nozzle ids and the post-extrusion (pre-switch) partial-free sub-range. Built
|
||||
// during run_post_process, consumed only by the injector side-pass under the enable_pre_heating gate.
|
||||
namespace ExtruderPreHeating
|
||||
{
|
||||
struct FilamentUsageBlock
|
||||
{
|
||||
int filament_id;
|
||||
int extruder_id;
|
||||
int nozzle_id;
|
||||
unsigned int lower_gcode_id;
|
||||
unsigned int upper_gcode_id; // [lower_gcode_id,upper_gcode_id) uses current filament , upper gcode id will be set after finding next block
|
||||
FilamentUsageBlock(int filament_id_, int extruder_id_, int nozzle_id_, unsigned int lower_gcode_id_, unsigned int upper_gcode_id_) :filament_id(filament_id_), extruder_id(extruder_id_), nozzle_id(nozzle_id_), lower_gcode_id(lower_gcode_id_), upper_gcode_id(upper_gcode_id_) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Describle the usage of a exturder in a section
|
||||
*
|
||||
* The strucutre stores the start and end lines of the sections as well as
|
||||
* the filament used at the beginning and end of the section.
|
||||
* Post extrusion means the final extrusion before switching to the next extruder.
|
||||
*
|
||||
* Simplified GCode Flow:
|
||||
* 1.Extruder Change Block (ext0 switch to ext1)
|
||||
* 2.Extruder Usage Block (use ext1 to print)
|
||||
* 3.Extruder Change Block (ext1 switch to ext0)
|
||||
* 4.Extruder Usage Block (use ext0 to print)
|
||||
* 5.Extruder Change Block (ext0 switch to ex1)
|
||||
* ...
|
||||
*
|
||||
* So the construct of extruder usage block relys on two extruder change block
|
||||
*/
|
||||
struct ExtruderUsageBlcok
|
||||
{
|
||||
int extruder_id = -1;
|
||||
unsigned int start_id = -1;
|
||||
unsigned int end_id = -1;
|
||||
int start_filament = -1;
|
||||
int end_filament = -1;
|
||||
int start_nozzle_id = -1;
|
||||
int end_nozzle_id = -1;
|
||||
unsigned int post_extrusion_start_id = -1;
|
||||
unsigned int post_extrusion_end_id = -1;
|
||||
bool ignore_cooling_before_tower = false;
|
||||
|
||||
void initialize_step_1(int extruder_id_, int start_id_, int start_filament_, int start_nozzle_id_) {
|
||||
extruder_id = extruder_id_;
|
||||
start_id = start_id_;
|
||||
start_filament = start_filament_;
|
||||
start_nozzle_id = start_nozzle_id_;
|
||||
};
|
||||
void initialize_step_2(int post_extrusion_start_id_) {
|
||||
post_extrusion_start_id = post_extrusion_start_id_;
|
||||
}
|
||||
void initialize_step_3(int end_id_, int end_filament_, int post_extrusion_end_id_, int end_nozzle_id_) {
|
||||
end_id = end_id_;
|
||||
end_filament = end_filament_;
|
||||
post_extrusion_end_id = post_extrusion_end_id_;
|
||||
end_nozzle_id = end_nozzle_id_;
|
||||
}
|
||||
void reset() {
|
||||
*this = ExtruderUsageBlcok();
|
||||
}
|
||||
ExtruderUsageBlcok() = default;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
class CommandProcessor {
|
||||
public:
|
||||
@@ -347,6 +469,24 @@ class Print;
|
||||
static const std::string VFlush_Start_Tag;
|
||||
static const std::string VFlush_End_Tag;
|
||||
static const std::string External_Purge_Tag;
|
||||
public:
|
||||
// Orca: SKIPPABLE region tags, stored as static strings (the FLUSH idiom above) rather than
|
||||
// a CustomETags/CustomTags array. Public so the emission sites (WipeTower / change_filament
|
||||
// path) can reference them single-sourced.
|
||||
static const std::string Skippable_Start_Tag;
|
||||
static const std::string Skippable_End_Tag;
|
||||
static const std::string Skippable_Type_Tag;
|
||||
// Orca: usage-block builder markers (MACHINE_START_GCODE_END / MACHINE_END_GCODE_START /
|
||||
// NOZZLE_CHANGE_START / NOZZLE_CHANGE_END / CP_TOOLCHANGE_WIPE), stored as static strings (the
|
||||
// FLUSH/SKIPPABLE idiom above) rather than extending the Reserved_Tags arrays — these are
|
||||
// multi-nozzle markers only ever emitted by BBL-printer paths. Public so the emission sites can
|
||||
// reference them single-sourced. The MACHINE_*_GCODE_* emission (GCode.cpp, gated
|
||||
// enable_pre_heating) activates the usage-block builder.
|
||||
static const std::string Machine_Start_GCode_End_Tag;
|
||||
static const std::string Machine_End_GCode_Start_Tag;
|
||||
static const std::string Nozzle_Change_Start_Tag;
|
||||
static const std::string Nozzle_Change_End_Tag;
|
||||
static const std::string Toolchange_Wipe_Tag;
|
||||
public:
|
||||
enum class ETags : unsigned char
|
||||
{
|
||||
@@ -455,6 +595,9 @@ class Print;
|
||||
|
||||
EMoveType move_type{ EMoveType::Noop };
|
||||
ExtrusionRole role{ erNone };
|
||||
// SKIPPABLE tag classification stamped onto each time block. Feeds skippable_part_time
|
||||
// and the injector's SKIPPABLE relocation. stNone unless inside a SKIPPABLE_* region.
|
||||
SkipType skippable_type{ SkipType::stNone };
|
||||
unsigned int move_id{ 0 };
|
||||
unsigned int g1_line_id{ 0 };
|
||||
unsigned int remaining_internal_g1_lines{ 0 };
|
||||
@@ -624,6 +767,25 @@ class Print;
|
||||
|
||||
struct TimeProcessor
|
||||
{
|
||||
// Orca: the insert-line taxonomy + the ordered map of lines the pre-heat/pre-cool injector
|
||||
// splices into the finished g-code, keyed by output-line id. Orca keeps its single-pass
|
||||
// run_post_process (M73 / filament stats / ActualSpeedMove / Backtrace /
|
||||
// machine_tool_change_time) intact and applies this map in a separate, gated ADDITIVE
|
||||
// second file-rewrite pass (run_second_pass_injection); with an empty map that pass is a
|
||||
// byte-for-byte identity rewrite. The map is populated by the PreCoolingInjector.
|
||||
enum InsertLineType
|
||||
{
|
||||
PlaceholderReplace,
|
||||
TimePredict,
|
||||
FilamentChangePredict,
|
||||
ExtruderChangePredict,
|
||||
PreCooling,
|
||||
PreHeating,
|
||||
};
|
||||
|
||||
// first key is line id, second key is content
|
||||
using InsertedLinesMap = std::map<unsigned int, std::vector<std::pair<std::string, InsertLineType>>>;
|
||||
|
||||
struct Planner
|
||||
{
|
||||
// Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks.
|
||||
@@ -651,6 +813,117 @@ class Print;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
// The pre-cool / pre-heat injection engine. It consumes the already-computed per-move time
|
||||
// substrate (moves[i].time[valid_machine_id] / .gcode_id) and the first-pass usage blocks to
|
||||
// locate idle-hotend windows, then emits M632/M400/M104/M633 lines into a
|
||||
// TimeProcessor::InsertedLinesMap that the additive second file-rewrite pass
|
||||
// (run_second_pass_injection) splices into the finished g-code. It is constructed and run ONLY
|
||||
// when m_enable_pre_heating — single-nozzle printers (X1/P1/A1/H2S, flag false) never reach it.
|
||||
// Every input is a const reference bundled from GCodeProcessor members; the injector never
|
||||
// mutates GCodeProcessor state.
|
||||
class PreCoolingInjector {
|
||||
public:
|
||||
struct ExtruderFreeBlock {
|
||||
unsigned int free_lower_gcode_id;
|
||||
unsigned int free_upper_gcode_id;
|
||||
unsigned int partial_free_lower_id; // range of extrusion in wipe tower; without a wipe tower
|
||||
unsigned int partial_free_upper_id; // partial_free lower/upper equal free_lower_gcode_id
|
||||
int last_filament_id;
|
||||
int next_filament_id;
|
||||
int last_nozzle_id;
|
||||
int next_nozzle_id;
|
||||
int extruder_id; // partition key for the pre-heat/pre-cool region (extruder or hotend), not
|
||||
// necessarily a real extruder id
|
||||
bool ignore_cooling_before_tower = false;
|
||||
};
|
||||
|
||||
void process_pre_cooling_and_heating(TimeProcessor::InsertedLinesMap& inserted_operation_lines);
|
||||
void build_extruder_free_blocks(const std::vector<ExtruderPreHeating::FilamentUsageBlock>& filament_usage_blocks, const std::vector<ExtruderPreHeating::ExtruderUsageBlcok>& extruder_usage_blocks);
|
||||
|
||||
PreCoolingInjector(
|
||||
const std::vector<GCodeProcessorResult::MoveVertex>& moves_,
|
||||
const std::vector<std::string>& filament_types_,
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult& nozzle_group_result_,
|
||||
const std::vector<int>& filament_nozzle_temps_,
|
||||
const std::vector<int>& filament_nozzle_temps_initial_layer_,
|
||||
const std::vector<int>& physical_extruder_map_,
|
||||
int valid_machine_id_,
|
||||
float inject_time_threshold_,
|
||||
bool handle_hotend_as_extruder_,
|
||||
bool has_filament_switcher_,
|
||||
const std::vector<int>& pre_cooling_temp_,
|
||||
const std::vector<double>& cooling_rate_,
|
||||
const std::vector<double>& heating_rate_,
|
||||
const std::vector<std::pair<unsigned int, unsigned int>>& skippable_blocks_,
|
||||
const std::vector<int>& extruder_max_nozzle_count_,
|
||||
const std::vector<double>& filament_preheat_temperature_delta_,
|
||||
const std::vector<double>& filament_max_temperature_drop_when_ec_,
|
||||
unsigned int machine_start_gcode_end_id_,
|
||||
unsigned int machine_end_gcode_start_id_,
|
||||
const std::vector<ExtruderType>& extruder_types_,
|
||||
const std::vector<double>& nozzle_diameter_
|
||||
) :
|
||||
moves(moves_),
|
||||
filament_types(filament_types_),
|
||||
nozzle_group_result(nozzle_group_result_),
|
||||
filament_nozzle_temps(filament_nozzle_temps_),
|
||||
filament_nozzle_temps_initial_layer(filament_nozzle_temps_initial_layer_),
|
||||
physical_extruder_map(physical_extruder_map_),
|
||||
valid_machine_id(valid_machine_id_),
|
||||
inject_time_threshold(inject_time_threshold_),
|
||||
handle_hotend_as_extruder(handle_hotend_as_extruder_),
|
||||
has_filament_switcher(has_filament_switcher_),
|
||||
filament_pre_cooling_temps(pre_cooling_temp_),
|
||||
cooling_rate(cooling_rate_),
|
||||
heating_rate(heating_rate_),
|
||||
skippable_blocks(skippable_blocks_),
|
||||
extruder_max_nozzle_count(extruder_max_nozzle_count_),
|
||||
filament_preheat_temperature_delta(filament_preheat_temperature_delta_),
|
||||
filament_max_temperature_drop_when_ec(filament_max_temperature_drop_when_ec_),
|
||||
machine_start_gcode_end_id(machine_start_gcode_end_id_),
|
||||
machine_end_gcode_start_id(machine_end_gcode_start_id_),
|
||||
extruder_types(extruder_types_),
|
||||
nozzle_diameter(nozzle_diameter_)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<ExtruderFreeBlock> m_extruder_free_blocks;
|
||||
const std::vector<GCodeProcessorResult::MoveVertex>& moves;
|
||||
const std::vector<std::string>& filament_types;
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult& nozzle_group_result;
|
||||
const std::vector<int>& filament_nozzle_temps;
|
||||
const std::vector<int>& filament_nozzle_temps_initial_layer;
|
||||
const std::vector<int>& physical_extruder_map;
|
||||
const int valid_machine_id;
|
||||
const float inject_time_threshold;
|
||||
const bool handle_hotend_as_extruder;
|
||||
const bool has_filament_switcher;
|
||||
const std::vector<double>& cooling_rate;
|
||||
const std::vector<double>& heating_rate;
|
||||
const std::vector<int>& filament_pre_cooling_temps; // target cooling temp during post extrusion
|
||||
const std::vector<std::pair<unsigned int, unsigned int>>& skippable_blocks;
|
||||
const std::vector<int>& extruder_max_nozzle_count;
|
||||
const std::vector<double>& filament_preheat_temperature_delta;
|
||||
const std::vector<double>& filament_max_temperature_drop_when_ec;
|
||||
const unsigned int machine_start_gcode_end_id;
|
||||
const unsigned int machine_end_gcode_start_id;
|
||||
const std::vector<ExtruderType>& extruder_types;
|
||||
const std::vector<double>& nozzle_diameter;
|
||||
|
||||
void inject_cooling_heating_command(
|
||||
TimeProcessor::InsertedLinesMap& inserted_operation_lines,
|
||||
const ExtruderFreeBlock& free_block,
|
||||
float curr_temp,
|
||||
float target_temp,
|
||||
bool pre_cooling,
|
||||
bool pre_heating
|
||||
);
|
||||
|
||||
void build_by_filament_blocks(const std::vector<ExtruderPreHeating::FilamentUsageBlock>& filament_usage_blocks);
|
||||
void build_by_extruder_blocks(const std::vector<ExtruderPreHeating::ExtruderUsageBlcok>& extruder_usage_blocks);
|
||||
};
|
||||
public:
|
||||
class SeamsDetector
|
||||
{
|
||||
@@ -795,12 +1068,57 @@ class Print;
|
||||
bool m_flushing; // mark a section with real flush
|
||||
bool m_virtual_flushing; // mark a section with virtual flush, only for statistics
|
||||
bool m_wipe_tower;
|
||||
// Current-section SKIPPABLE state. Set by process_tags when inside a SKIPPABLE_* region;
|
||||
// stamped onto each TimeBlock. The shipping time_lapse_gcode template emits SKIPPABLE_*
|
||||
// widely, so these commonly go active (true / stTimelapse) and stamp blocks on most slices.
|
||||
bool m_skippable{false};
|
||||
SkipType m_skippable_type{SkipType::stNone};
|
||||
int m_object_label_id{-1};
|
||||
float m_print_z{0.0f};
|
||||
std::vector<float> m_remaining_volume;
|
||||
ExtruderTemps m_filament_nozzle_temp;
|
||||
ExtruderTemps m_filament_nozzle_temp_first_layer;
|
||||
std::vector<int> m_physical_extruder_map;
|
||||
// Multi-nozzle context state. Per-extruder max (sub-)nozzle count; >1 marks a multi-nozzle
|
||||
// extruder. Input for the pre-heat/filament-change-time injection model; not yet consumed by
|
||||
// Orca's time estimator, so it is inert for existing printers.
|
||||
std::vector<int> m_extruder_max_nozzle_count{1};
|
||||
// Pre-heat / pre-cool injector estimator inputs. Populated from the config in apply_config
|
||||
// (both overloads) and cleared in reset(), so the PreCoolingInjector has its inputs in place.
|
||||
// Consumed only by the injector two-pass side-pass, gated on m_enable_pre_heating.
|
||||
std::vector<std::string> m_filament_types;
|
||||
std::vector<double> m_nozzle_diameter;
|
||||
std::vector<double> m_hotend_cooling_rate{ 2.f };
|
||||
std::vector<double> m_hotend_heating_rate{ 2.f };
|
||||
std::vector<int> m_filament_pre_cooling_temp{ 0 };
|
||||
std::vector<double> m_filament_preheat_temperature_delta;
|
||||
bool m_enable_pre_heating{ false };
|
||||
bool m_handle_hotend_as_extruder{ false };
|
||||
bool m_has_filament_switcher{ false };
|
||||
// [start,end] output-line-id ranges of each SKIPPABLE region, collected during
|
||||
// run_post_process. The injector relocates pre-heat M104s out of these ranges. The shipping
|
||||
// time_lapse_gcode template emits SKIPPABLE_* widely, so on a timelapse-on slice this is
|
||||
// populated with many timelapse ranges (not empty) — the consumer must expect the common
|
||||
// timelapse case, not only H2C/A2L wipe-tower ranges.
|
||||
std::vector<std::pair<unsigned int, unsigned int>> m_skippable_blocks;
|
||||
// First-pass usage blocks, built in run_post_process and stored on the member so the
|
||||
// injector side-pass can consume them. Filled only when m_enable_pre_heating — single-nozzle
|
||||
// printers (X1/P1/A1/H2S) never build them. They depend on the MACHINE_*_GCODE_* /
|
||||
// NOZZLE_CHANGE_* emission the builder keys off.
|
||||
std::vector<ExtruderPreHeating::FilamentUsageBlock> m_filament_blocks;
|
||||
std::vector<ExtruderPreHeating::ExtruderUsageBlcok> m_extruder_blocks;
|
||||
unsigned int m_machine_start_gcode_end_line_id{ (unsigned int) (-1) };
|
||||
unsigned int m_machine_end_gcode_start_line_id{ (unsigned int) (-1) };
|
||||
// Tracks, during the stream, which filament sits in each physical nozzle and which nozzle each
|
||||
// extruder currently carries. Written by both branches of the two-arg process_filament_change
|
||||
// (the fallback branch does occupancy bookkeeping only); read by the richer change-time model
|
||||
// and by the per-slot machine-limit resolution. Single-nozzle printers never populate it.
|
||||
MultiNozzleUtils::NozzleStatusRecorder m_nozzle_status_recorder;
|
||||
// Nozzle grouping context for slot resolution during the streaming pass. Set before the
|
||||
// replay begins (see initialize_from_context); deliberately separate from
|
||||
// m_result.nozzle_group_result, which is handed over only after the stream for the
|
||||
// pre-heat injector's second pass and gates the richer change-time model.
|
||||
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> m_nozzle_group_result;
|
||||
bool m_manual_filament_change;
|
||||
|
||||
//BBS: x, y offset for gcode generated
|
||||
@@ -825,6 +1143,9 @@ class Print;
|
||||
std::vector<unsigned char> m_last_filament_id;
|
||||
std::vector<unsigned char> m_filament_id;
|
||||
unsigned char m_extruder_id;
|
||||
// Cached get_machine_config_idx() value; its inputs (active extruder + recorder occupancy)
|
||||
// change only on filament-change events, where it is recomputed.
|
||||
int m_machine_config_idx{0};
|
||||
ExtruderColors m_extruder_colors;
|
||||
ExtruderTemps m_extruder_temps;
|
||||
bool m_is_XL_printer = false;
|
||||
@@ -876,6 +1197,11 @@ class Print;
|
||||
public:
|
||||
GCodeProcessor();
|
||||
void init_filament_maps_and_nozzle_type_when_import_only_gcode();
|
||||
// Reprocessing an already-generated g-code (from-previous / imported g-code) does not rebuild
|
||||
// the per-filament nozzle grouping the multi-nozzle device GUI needs. Surface it onto the
|
||||
// result: keep an already-seeded grouping (from initialize_from_context), otherwise synthesize
|
||||
// a default one from the filament map so the result is never left without it.
|
||||
void ensure_nozzle_group_result(int min_filament_count);
|
||||
// check whether the gcode path meets the filament_map grouping requirements
|
||||
bool check_multi_extruder_gcode_valid(const int extruder_size,
|
||||
const Pointfs plate_printable_area,
|
||||
@@ -887,6 +1213,11 @@ class Print;
|
||||
const std::vector<std::set<int>>& unprintable_filament_types );
|
||||
void apply_config(const PrintConfig& config);
|
||||
void set_print(Print* print) { m_print = print; }
|
||||
// Hand the nozzle grouping context to the estimator BEFORE the streaming replay, so the
|
||||
// per-slot machine-limit resolution can follow the active nozzle. Null is fine (slot 0).
|
||||
void initialize_from_context(const std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase>& nozzle_group_result) {
|
||||
m_nozzle_group_result = nozzle_group_result;
|
||||
}
|
||||
|
||||
DynamicConfig export_config_for_render() const;
|
||||
|
||||
@@ -1094,30 +1425,63 @@ class Print;
|
||||
// Processes T line (Select Tool)
|
||||
void process_T(const GCodeReader::GCodeLine& line);
|
||||
void process_T(const std::string_view command);
|
||||
// T variant carrying the H<nozzle> logical-nozzle id parsed off the command line. -1 = absent.
|
||||
void process_T(const std::string_view command, int nozzle_id);
|
||||
void process_M1020(const GCodeReader::GCodeLine &line);
|
||||
|
||||
void process_M622(const GCodeReader::GCodeLine &line);
|
||||
void process_M623(const GCodeReader::GCodeLine &line);
|
||||
|
||||
void process_filament_change(int id);
|
||||
// Richer hotend-change time model distinguishing extruder-switch / nozzle-in-extruder change /
|
||||
// filament-in-nozzle change. Self-gated: for single-nozzle printers it delegates to
|
||||
// process_filament_change(int) so their time estimate — hence exported g-code — is unchanged.
|
||||
void process_filament_change(int id, int nozzle_id);
|
||||
// Destination nozzle of a filament change: the explicit H<nozzle> id when given, else the
|
||||
// filament's first nozzle in the grouping. Shared by the change-time model and the
|
||||
// fallback-path occupancy bookkeeping.
|
||||
std::optional<MultiNozzleUtils::NozzleInfo> resolve_target_nozzle(
|
||||
const MultiNozzleUtils::NozzleGroupResultBase &group, int id, int nozzle_id) const;
|
||||
// Machine slot of the nozzle currently mounted in the active extruder (0 when no grouping
|
||||
// context / unknown extruder — the single-slot layout). Cached in m_machine_config_idx,
|
||||
// recomputed on filament-change events.
|
||||
int get_machine_config_idx() const;
|
||||
// True only for multi-nozzle-capable printers (H2C cluster, or a dual/multi-extruder machine
|
||||
// like H2D/X2D): the gate that admits the richer two-arg hotend-change time model. False for
|
||||
// every single-extruder single-nozzle printer (X1/P1/A1/H2S/A2L).
|
||||
bool use_multi_nozzle_change_time_model() const;
|
||||
|
||||
// post process the file with the given filename to:
|
||||
// 1) add remaining time lines M73 and update moves' gcode ids accordingly
|
||||
// 2) update used filament data
|
||||
void run_post_process();
|
||||
|
||||
// Additive second file-rewrite pass. Splices the pre-heat/pre-cool injector's InsertedLinesMap
|
||||
// into the finished g-code and re-shifts every move's gcode_id by the number of inserted lines
|
||||
// before it. Runs only when m_enable_pre_heating, AFTER run_post_process, so single-nozzle
|
||||
// printers (X1/P1/A1/H2S) never enter it; with an empty map it is a byte-for-byte identity rewrite.
|
||||
void run_second_pass_injection();
|
||||
// Shift each move's gcode_id by the count of injector lines inserted before it. No-op when the
|
||||
// map is empty.
|
||||
void handle_offsets_of_second_process(const TimeProcessor::InsertedLinesMap& inserted_operation_lines);
|
||||
|
||||
//BBS: different path_type is only used for arc move
|
||||
void store_move_vertex(EMoveType type, EMovePathType path_type = EMovePathType::Noop_move, bool internal_only = false);
|
||||
|
||||
void set_extrusion_role(ExtrusionRole role);
|
||||
// Resolve the SKIPPABLE_TYPE payload to a SkipType.
|
||||
void set_skippable_type(const std::string_view type);
|
||||
|
||||
float minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const;
|
||||
float minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const;
|
||||
// Machine limit arrays are indexed by time mode only: [0]=Normal, [1]=Stealth.
|
||||
// Do NOT add an extruder_id parameter — OrcaSlicer does not use BambuStudio's
|
||||
// per-nozzle machine limits (filament_map_2 / get_config_idx_for_filament).
|
||||
// Speed/acceleration limit arrays are slot-major with two mode entries per machine slot:
|
||||
// [slot*2 + mode], slot from get_machine_config_idx() (0 = the only slot on single-variant
|
||||
// printers, whose arrays hold just [Normal, Stealth]). The 2-arg forms read slot 0 and stay
|
||||
// exactly the historical mode-only lookup; jerk and the accelerations below are mode-only.
|
||||
float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int machine_idx) const;
|
||||
float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int machine_idx) const;
|
||||
float get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis, float acceleration) const;
|
||||
float get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
|
||||
@@ -361,7 +361,8 @@ namespace Slic3r {
|
||||
* @param safe_areas A collection of extended polygons defining the safe areas.
|
||||
* @return Point The nearest point within the safe areas or the default timelapse position if no safe areas exist.
|
||||
*/
|
||||
Point pick_pos_internal(const Point& curr_pos, const ExPolygons& safe_areas, const ExPolygons& path_collision_area, bool detect_path_collision)
|
||||
Point pick_pos_internal(const Point& curr_pos, const ExPolygons& safe_areas, const ExPolygons& path_collision_area, bool detect_path_collision,
|
||||
const std::optional<Point>& farthest_point = std::nullopt)
|
||||
{
|
||||
struct CandidatePoint
|
||||
{
|
||||
@@ -381,7 +382,11 @@ namespace Slic3r {
|
||||
std::priority_queue<CandidatePoint> max_heap;
|
||||
|
||||
const double candidate_point_segment = scale_(5), weight_of_camera=1./3.;
|
||||
auto penaltyFunc = [&weight_of_camera](const Point &curr_post, const Point &CameraPos, const Point &candidatet) -> double {
|
||||
auto penaltyFunc = [&weight_of_camera, &farthest_point](const Point &curr_post, const Point &CameraPos, const Point &candidatet) -> double {
|
||||
if (farthest_point.has_value()) {
|
||||
// Farthest-point timelapse: prefer candidate closest to the farthest point (L1 norm)
|
||||
return (farthest_point.value() - candidatet).cwiseAbs().sum();
|
||||
}
|
||||
// move distance + Camera occlusion penalty function
|
||||
double ret_pen = (curr_post - candidatet).cwiseAbs().sum() - weight_of_camera * (CameraPos - candidatet).cwiseAbs().sum();
|
||||
return ret_pen;
|
||||
@@ -523,7 +528,7 @@ namespace Slic3r {
|
||||
path_collision_area = union_ex(layer_slices_without_curr, rod_limit_areas);
|
||||
}
|
||||
|
||||
return pick_pos_internal(center_p, safe_area,path_collision_area, by_object);
|
||||
return pick_pos_internal(center_p, safe_area,path_collision_area, by_object, ctx.farthest_point);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -610,4 +615,50 @@ namespace Slic3r {
|
||||
return *m_all_layer_pos;
|
||||
}
|
||||
|
||||
// Whether the head can travel to X0 without crossing any other instance that is
|
||||
// taller than the current print position.
|
||||
bool TimelapsePosPicker::get_is_clear_to_x0(const PosPickCtx &ctx)
|
||||
{
|
||||
bool by_object = m_print_seq == PrintSequence::ByObject;
|
||||
std::vector<const PrintObject *> object_list = get_object_list(ctx.printed_objects);
|
||||
|
||||
auto range_intersect = [](int left1, int right1, int left2, int right2) {
|
||||
if (left1 <= left2 && left2 <= right1) return true;
|
||||
if (left2 <= left1 && left1 <= right2) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
ExPolygons unclear_area;
|
||||
const Layer *layer = ctx.curr_layer;
|
||||
float z_target = layer->print_z;
|
||||
float z_low = layer->print_z - 0.5;
|
||||
float z_high = layer->print_z + 0.5;
|
||||
|
||||
for (auto &obj : object_list) {
|
||||
for (auto &instance : obj->instances()) {
|
||||
auto instance_bbox = get_real_instance_bbox(instance);
|
||||
bool is_curr_obj = ( obj == object_list.back() ) || ( !by_object ),
|
||||
higher_than_curr_pos = instance_bbox.max.z() > z_target;
|
||||
if (!is_curr_obj && range_intersect(instance_bbox.min.z(), instance_bbox.max.z(), z_low, z_high)) {
|
||||
ExPolygon expoly;
|
||||
expoly.contour = {{scale_(instance_bbox.min.x()), scale_(instance_bbox.min.y())},
|
||||
{scale_(instance_bbox.max.x()), scale_(instance_bbox.min.y())},
|
||||
{scale_(instance_bbox.max.x()), scale_(instance_bbox.max.y())},
|
||||
{scale_(instance_bbox.min.x()), scale_(instance_bbox.max.y())}};
|
||||
expoly.contour = expand_object_projection(expoly.contour, by_object, higher_than_curr_pos);
|
||||
unclear_area.emplace_back(std::move(expoly));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Point curr_pos_in_plate = {ctx.curr_pos.x() - scale_(m_plate_offset.x()), ctx.curr_pos.y() - scale_(m_plate_offset.y())};
|
||||
for (const ExPolygon &expoly : unclear_area) {
|
||||
BoundingBox bbox = expoly.contour.bounding_box();
|
||||
if (curr_pos_in_plate.y() < bbox.min.y() || curr_pos_in_plate.y() > bbox.max.y()) continue;
|
||||
if (bbox.min.x() <= curr_pos_in_plate.x()) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,9 @@ namespace Slic3r {
|
||||
int picture_extruder_id; // the extruder id to take picture
|
||||
int curr_extruder_id;
|
||||
std::optional<std::vector<const PrintObject*>> printed_objects; // printed objects, only have value in by object mode
|
||||
// Farthest-point timelapse: plate-relative scaled point; when set, pick_pos_internal
|
||||
// biases the picked snapshot position toward this point (nullopt → legacy camera-occlusion loss).
|
||||
std::optional<Point> farthest_point;
|
||||
};
|
||||
|
||||
// data are stored without plate offset
|
||||
@@ -32,6 +35,9 @@ namespace Slic3r {
|
||||
~TimelapsePosPicker() = default;
|
||||
|
||||
Point pick_pos(const PosPickCtx& ctx);
|
||||
// Is the path to X0 clear of other (taller) instances? Drives the
|
||||
// `clear_to_x0` timelapse-gcode variable (g39 clamping detection).
|
||||
bool get_is_clear_to_x0(const PosPickCtx& ctx);
|
||||
void init(const Print* print, const Point& plate_offset);
|
||||
void reset();
|
||||
private:
|
||||
|
||||
@@ -7,13 +7,100 @@
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
// ==================== MaxFlowWithLowerBounds ====================
|
||||
struct MaxFlowWithLowerBounds {
|
||||
public:
|
||||
|
||||
void add_edge(int from, int to, int capacity);
|
||||
|
||||
bool bfs();
|
||||
int dfs(int u, int f);
|
||||
int solve(std::vector<int>& matching);
|
||||
|
||||
public:
|
||||
std::vector<int> l_nodes;
|
||||
std::vector<int> r_nodes;
|
||||
std::vector<Edge> edges;
|
||||
std::vector<std::vector<int>> adj;
|
||||
std::vector<int> level;
|
||||
std::vector<int> it;
|
||||
|
||||
int total_nodes{ -1 };
|
||||
int source_id{ -1 };
|
||||
int sink_id{ -1 };
|
||||
};
|
||||
|
||||
void MaxFlowWithLowerBounds::add_edge(int from, int to, int capacity)
|
||||
{
|
||||
adj[from].emplace_back(edges.size());
|
||||
edges.emplace_back(from, to, capacity, 0);
|
||||
// also add the reverse residual edge with zero capacity
|
||||
adj[to].emplace_back(edges.size());
|
||||
edges.emplace_back(to, from, 0, 0);
|
||||
}
|
||||
|
||||
bool MaxFlowWithLowerBounds::bfs() {
|
||||
level.assign(total_nodes, -1);
|
||||
std::queue<int> q;
|
||||
q.push(source_id);
|
||||
level[source_id] = 0;
|
||||
|
||||
while (!q.empty()) {
|
||||
int u = q.front(); q.pop();
|
||||
for (int eid : adj[u]) {
|
||||
Edge &e = edges[eid];
|
||||
if (e.flow < e.capacity && level[e.to] == -1) {
|
||||
level[e.to] = level[u] + 1;
|
||||
q.push(e.to);
|
||||
}
|
||||
}
|
||||
}
|
||||
return level[sink_id] != -1;
|
||||
}
|
||||
|
||||
int MaxFlowWithLowerBounds::dfs(int u, int f) {
|
||||
if (u == sink_id) return f;
|
||||
for (int &i = it[u]; i < (int)adj[u].size(); ++i) {
|
||||
int eid = adj[u][i];
|
||||
Edge &e = edges[eid];
|
||||
if (e.flow < e.capacity && level[e.to] == level[u] + 1) {
|
||||
int pushed = dfs(e.to, std::min(f, e.capacity - e.flow));
|
||||
if (pushed > 0) {
|
||||
e.flow += pushed;
|
||||
edges[eid ^ 1].flow -= pushed;
|
||||
return pushed;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MaxFlowWithLowerBounds::solve(std::vector<int>& matching) {
|
||||
int flow = 0;
|
||||
while (bfs()) {
|
||||
it.assign(total_nodes, 0);
|
||||
while (int pushed = dfs(source_id, MaxFlowGraph::INF))
|
||||
flow += pushed;
|
||||
}
|
||||
|
||||
int L = l_nodes.size();
|
||||
int R = r_nodes.size();
|
||||
// collect l-r matches
|
||||
matching.resize(l_nodes.size(), MaxFlowGraph::INVALID_ID);
|
||||
for (int u = 0; u < L; ++u) {
|
||||
for (int eid : adj[u]) {
|
||||
Edge &e = edges[eid];
|
||||
if (e.flow > 0 && e.to >= L && e.to < L + R) {
|
||||
matching[e.from] = e.to - L;
|
||||
}
|
||||
}
|
||||
}
|
||||
return flow;
|
||||
}
|
||||
|
||||
// ==================== MinCostMaxFlow ====================
|
||||
struct MinCostMaxFlow {
|
||||
public:
|
||||
struct Edge {
|
||||
int from, to, capacity, cost, flow;
|
||||
Edge(int u, int v, int cap, int cst) : from(u), to(v), capacity(cap), cost(cst), flow(0) {}
|
||||
};
|
||||
|
||||
std::vector<int> solve();
|
||||
void add_edge(int from, int to, int capacity, int cost);
|
||||
bool spfa(int source, int sink);
|
||||
@@ -107,15 +194,10 @@ namespace Slic3r
|
||||
{
|
||||
if (l_nodes[idx_in_left] == -1) {
|
||||
return 0;
|
||||
//TODO: test more here
|
||||
int sum = 0;
|
||||
for (int i = 0; i < matrix.size(); ++i)
|
||||
sum += matrix[i][idx_in_right];
|
||||
sum /= matrix.size();
|
||||
return -sum;
|
||||
}
|
||||
|
||||
return matrix[l_nodes[idx_in_left]][r_nodes[idx_in_right]];
|
||||
float val = matrix[l_nodes[idx_in_left]][r_nodes[idx_in_right]];
|
||||
return std::min(static_cast<int>(val), MaxFlowGraph::MCMF_MAX_EDGE_COST);
|
||||
}
|
||||
|
||||
|
||||
@@ -123,27 +205,40 @@ namespace Slic3r
|
||||
const std::unordered_map<int, std::vector<int>>& uv_link_limits,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits,
|
||||
const std::vector<int>& u_capacity,
|
||||
const std::vector<int>& v_capacity)
|
||||
const std::vector<int>& v_capacity,
|
||||
const std::vector<std::pair<std::set<int>,int>>& v_group_capacity)
|
||||
{
|
||||
assert(u_capacity.empty() || u_capacity.size() == u_nodes.size());
|
||||
assert(v_capacity.empty() || v_capacity.size() == v_nodes.size());
|
||||
l_nodes = u_nodes;
|
||||
r_nodes = v_nodes;
|
||||
total_nodes = u_nodes.size() + v_nodes.size() + 2;
|
||||
total_nodes = u_nodes.size() + v_nodes.size() + v_group_capacity.size() + 2;
|
||||
source_id = total_nodes - 2;
|
||||
sink_id = total_nodes - 1;
|
||||
|
||||
adj.resize(total_nodes);
|
||||
|
||||
std::vector<int>v_node_to(v_nodes.size(), sink_id);
|
||||
for (size_t gid = 0; gid < v_group_capacity.size(); ++gid) {
|
||||
for (auto vid : v_group_capacity[gid].first)
|
||||
v_node_to[vid] = l_nodes.size() + r_nodes.size() + gid;
|
||||
}
|
||||
|
||||
// add edge from source to left nodes
|
||||
for (int idx = 0; idx < l_nodes.size(); ++idx) {
|
||||
int capacity = u_capacity.empty() ? 1 : u_capacity[idx];
|
||||
add_edge(source_id, idx, capacity);
|
||||
}
|
||||
// add edge from right nodes to sink node
|
||||
// add edge from right nodes to v_node_to(sink node or temp group node)
|
||||
for (int idx = 0; idx < r_nodes.size(); ++idx) {
|
||||
int capacity = v_capacity.empty() ? 1 : v_capacity[idx];
|
||||
add_edge(l_nodes.size() + idx, sink_id, capacity);
|
||||
add_edge(l_nodes.size() + idx, v_node_to[idx], capacity);
|
||||
}
|
||||
|
||||
// add edge from temp group node to sink node
|
||||
for (int idx = 0; idx < v_group_capacity.size(); ++idx) {
|
||||
int capacity = v_group_capacity[idx].second;
|
||||
add_edge(l_nodes.size() + r_nodes.size() + idx, sink_id, capacity);
|
||||
}
|
||||
|
||||
// add edge from left nodes to right nodes
|
||||
@@ -269,6 +364,301 @@ namespace Slic3r
|
||||
return m_solver->solve();
|
||||
}
|
||||
|
||||
// ==================== GeneralMinCostLowerBoundsSolver ====================
|
||||
GeneralMinCostLowerBoundsSolver::~GeneralMinCostLowerBoundsSolver() = default;
|
||||
|
||||
GeneralMinCostLowerBoundsSolver::GeneralMinCostLowerBoundsSolver(const std::vector<FlushMatrix> &matrix_,
|
||||
const std::vector<int> &u_nodes,
|
||||
const std::vector<int> &v_nodes,
|
||||
const std::vector<int> &v_nodes_group,
|
||||
const std::unordered_map<int, std::vector<int>> &uv_link_limits,
|
||||
const std::unordered_map<int, std::vector<int>> &uv_unlink_limits)
|
||||
{
|
||||
flush_matrix = matrix_;
|
||||
l_nodes = u_nodes;
|
||||
r_nodes = v_nodes;
|
||||
r_nodes_group = v_nodes_group;
|
||||
m_uv_link_limits = uv_link_limits;
|
||||
m_uv_unlink_limits = uv_unlink_limits;
|
||||
num_groups = *std::max_element(r_nodes_group.begin(), r_nodes_group.end()) + 1;
|
||||
|
||||
m_solver_lower_bounds = std::make_unique<MaxFlowWithLowerBounds>();
|
||||
m_solver_min_cost = std::make_unique<MinCostMaxFlow>();
|
||||
}
|
||||
|
||||
std::vector<int> GeneralMinCostLowerBoundsSolver::solve()
|
||||
{
|
||||
// group nodes that do not need a lower-bound constraint
|
||||
std::unordered_set<int> no_lower_group;
|
||||
for (int i = 0; i < r_nodes.size(); i++) {
|
||||
if (r_nodes[i] >= 0)
|
||||
no_lower_group.insert(r_nodes_group[i]);
|
||||
}
|
||||
|
||||
// 1. build the lower-bound network graph
|
||||
build_feasible_graph(no_lower_group);
|
||||
|
||||
// 2. compute the max flow
|
||||
int need = 0;
|
||||
for (int d : demand)
|
||||
if (d > 0) need += d;
|
||||
std::vector<int> feasible_matching;
|
||||
int pushed_flow = m_solver_lower_bounds->solve(feasible_matching);
|
||||
assert(need == pushed_flow);
|
||||
|
||||
// 3. convert the lower-bound max-flow network into a min-cost-max-flow network
|
||||
build_graph_with_feasible_result();
|
||||
// 4. compute the min-cost max-flow
|
||||
auto min_cost_matching = m_solver_min_cost->solve();
|
||||
|
||||
return min_cost_matching;
|
||||
}
|
||||
|
||||
void GeneralMinCostLowerBoundsSolver::build_feasible_graph(const std::unordered_set<int> &no_lower_groups)
|
||||
{
|
||||
m_solver_lower_bounds->l_nodes = l_nodes;
|
||||
m_solver_lower_bounds->r_nodes = r_nodes;
|
||||
m_solver_lower_bounds->total_nodes = l_nodes.size() + r_nodes.size() + num_groups + 2;
|
||||
|
||||
m_solver_lower_bounds->source_id = m_solver_lower_bounds->total_nodes - 2;
|
||||
m_solver_lower_bounds->sink_id = m_solver_lower_bounds->total_nodes - 1;
|
||||
m_solver_lower_bounds->adj.resize(m_solver_lower_bounds->total_nodes);
|
||||
demand.resize(m_solver_lower_bounds->total_nodes, 0);
|
||||
|
||||
const int L = m_solver_lower_bounds->l_nodes.size();
|
||||
const int R = m_solver_lower_bounds->r_nodes.size();
|
||||
|
||||
// source -> l
|
||||
for (int i = 0; i < L; ++i)
|
||||
m_solver_lower_bounds->add_edge(m_solver_lower_bounds->source_id, i, 1);
|
||||
|
||||
// u -> v (with link/unlink limits)
|
||||
for (int i = 0; i < L; ++i) {
|
||||
if (auto it = m_uv_link_limits.find(i); it != m_uv_link_limits.end()) {
|
||||
for (int j : it->second)
|
||||
m_solver_lower_bounds->add_edge(i, L + j, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::optional<std::vector<int>> unlink_limits;
|
||||
if (auto it = m_uv_unlink_limits.find(i); it != m_uv_unlink_limits.end())
|
||||
unlink_limits = it->second;
|
||||
|
||||
for (int j = 0; j < R; ++j) {
|
||||
if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end())
|
||||
continue;
|
||||
m_solver_lower_bounds->add_edge(i, L + j, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// r -> group
|
||||
for (int j = 0; j < R; ++j) {
|
||||
int g = r_nodes_group[j];
|
||||
m_solver_lower_bounds->add_edge(L + j, L + R + g, 1);
|
||||
}
|
||||
|
||||
// group -> sink (lower bound = 1)
|
||||
for (int g = 0; g < num_groups; ++g) {
|
||||
if (no_lower_groups.count(g))
|
||||
m_solver_lower_bounds->add_edge(L + R + g, m_solver_lower_bounds->sink_id, R);
|
||||
else
|
||||
add_edge_with_lower_bound(L + R + g, m_solver_lower_bounds->sink_id, 1, R, 0);
|
||||
}
|
||||
|
||||
max_flow_edges = m_solver_lower_bounds->edges.size();
|
||||
|
||||
// support lower bounds, add super source super sink
|
||||
super_source = m_solver_lower_bounds->total_nodes++;
|
||||
super_sink = m_solver_lower_bounds->total_nodes++;
|
||||
|
||||
m_solver_lower_bounds->adj.resize(m_solver_lower_bounds->total_nodes);
|
||||
demand.resize(m_solver_lower_bounds->total_nodes, 0);
|
||||
|
||||
for (int i = 0; i < super_source; ++i) {
|
||||
if (demand[i] > 0) {
|
||||
m_solver_lower_bounds->add_edge(super_source, i, demand[i]);
|
||||
} else if (demand[i] < 0) {
|
||||
m_solver_lower_bounds->add_edge(i, super_sink, -demand[i]);
|
||||
}
|
||||
}
|
||||
m_solver_lower_bounds->add_edge(m_solver_lower_bounds->sink_id, m_solver_lower_bounds->source_id, MaxFlowGraph::INF);
|
||||
source_id = m_solver_lower_bounds->source_id;
|
||||
sink_id = m_solver_lower_bounds->sink_id;
|
||||
m_solver_lower_bounds->source_id = super_source;
|
||||
m_solver_lower_bounds->sink_id = super_sink;
|
||||
}
|
||||
|
||||
void GeneralMinCostLowerBoundsSolver::build_graph_with_feasible_result()
|
||||
{
|
||||
for (auto&lb:lower_bound_edges){
|
||||
m_solver_lower_bounds->edges[lb.edge_id].flow += lb.lower;
|
||||
m_solver_lower_bounds->edges[lb.edge_id ^ 1].flow -= lb.lower;
|
||||
}
|
||||
|
||||
m_solver_min_cost->l_nodes = m_solver_lower_bounds->l_nodes;
|
||||
m_solver_min_cost->r_nodes = m_solver_lower_bounds->r_nodes;
|
||||
|
||||
m_solver_min_cost->source_id = source_id;
|
||||
m_solver_min_cost->sink_id = sink_id;
|
||||
m_solver_min_cost->total_nodes = sink_id + 1;
|
||||
|
||||
m_solver_min_cost->edges = m_solver_lower_bounds->edges;
|
||||
m_solver_min_cost->edges.erase(m_solver_min_cost->edges.begin() + max_flow_edges, m_solver_min_cost->edges.end());
|
||||
|
||||
m_solver_min_cost->adj = m_solver_lower_bounds->adj;
|
||||
m_solver_min_cost->adj.resize(m_solver_min_cost->total_nodes);
|
||||
for (auto &node_edges : m_solver_min_cost->adj) {
|
||||
node_edges.erase(std::remove_if(node_edges.begin(), node_edges.end(), [this](int val) {return val >= this->max_flow_edges;}), node_edges.end());
|
||||
}
|
||||
|
||||
|
||||
for (auto& e : m_solver_min_cost->edges) {
|
||||
int L = m_solver_min_cost->l_nodes.size();
|
||||
int R = m_solver_min_cost->r_nodes.size();
|
||||
|
||||
if (e.from < L && e.to >= L && e.to < L + R) {
|
||||
int idx_in_left = e.from;
|
||||
int idx_in_right = e.to - L;
|
||||
int group_id = r_nodes_group[idx_in_right];
|
||||
|
||||
if (r_nodes[idx_in_right] == -1) continue;
|
||||
e.cost = flush_matrix[group_id][l_nodes[idx_in_left]][r_nodes[idx_in_right]];
|
||||
}
|
||||
}
|
||||
}
|
||||
void GeneralMinCostLowerBoundsSolver::add_edge_with_lower_bound(int from, int to, int lower, int upper, int cost)
|
||||
{
|
||||
int eid = m_solver_lower_bounds->edges.size();
|
||||
m_solver_lower_bounds->add_edge(from, to, upper - lower);
|
||||
|
||||
lower_bound_edges.push_back({eid, lower});
|
||||
demand[from] -= lower;
|
||||
demand[to] += lower;
|
||||
}
|
||||
|
||||
// ==================== GroupMinCostFlowSolver ====================
|
||||
GroupMinCostFlowSolver::~GroupMinCostFlowSolver() = default;
|
||||
|
||||
GroupMinCostFlowSolver::GroupMinCostFlowSolver(const std::vector<FlushMatrix> &matrix_,
|
||||
const std::vector<int> &u_nodes,
|
||||
const std::vector<int> &v_nodes,
|
||||
const std::vector<int> &v_nodes_group,
|
||||
const std::unordered_map<int, std::vector<int>> &uv_link_limits,
|
||||
const std::unordered_map<int, std::vector<int>> &uv_unlink_limits)
|
||||
{
|
||||
flush_matrix = matrix_;
|
||||
l_nodes = u_nodes;
|
||||
r_nodes = v_nodes;
|
||||
r_nodes_group = v_nodes_group;
|
||||
m_uv_link_limits = uv_link_limits;
|
||||
m_uv_unlink_limits = uv_unlink_limits;
|
||||
num_groups = *std::max_element(r_nodes_group.begin(), r_nodes_group.end()) + 1;
|
||||
|
||||
m_solver = std::make_unique<MinCostMaxFlow>();
|
||||
build_graph();
|
||||
}
|
||||
|
||||
int GroupMinCostFlowSolver::get_flush_cost(int l_idx, int r_idx)
|
||||
{
|
||||
if (r_nodes[r_idx] == -1)
|
||||
return 0;
|
||||
int group_id = r_nodes_group[r_idx];
|
||||
return (int)flush_matrix[group_id][l_nodes[l_idx]][r_nodes[r_idx]];
|
||||
}
|
||||
|
||||
void GroupMinCostFlowSolver::build_graph()
|
||||
{
|
||||
const int L = (int)l_nodes.size();
|
||||
const int R = (int)r_nodes.size();
|
||||
const int G = num_groups;
|
||||
|
||||
m_solver->l_nodes = l_nodes;
|
||||
m_solver->r_nodes = r_nodes;
|
||||
m_solver->total_nodes = L + R + G + 2;
|
||||
m_solver->source_id = L + R + G;
|
||||
m_solver->sink_id = L + R + G + 1;
|
||||
m_solver->adj.resize(m_solver->total_nodes);
|
||||
|
||||
int max_flush = 0;
|
||||
for (const auto &mat : flush_matrix)
|
||||
for (const auto &row : mat)
|
||||
for (float v : row)
|
||||
max_flush = std::max(max_flush, (int)v);
|
||||
int bonus = max_flush * L + 1;
|
||||
|
||||
// source -> l_i
|
||||
for (int i = 0; i < L; ++i)
|
||||
m_solver->add_edge(m_solver->source_id, i, 1, 0);
|
||||
|
||||
// l_i -> r_j (with link/unlink limits)
|
||||
for (int i = 0; i < L; ++i) {
|
||||
if (auto it = m_uv_link_limits.find(i); it != m_uv_link_limits.end()) {
|
||||
for (int j : it->second)
|
||||
m_solver->add_edge(i, L + j, 1, get_flush_cost(i, j));
|
||||
continue;
|
||||
}
|
||||
|
||||
std::optional<std::vector<int>> unlink_limits;
|
||||
if (auto it = m_uv_unlink_limits.find(i); it != m_uv_unlink_limits.end())
|
||||
unlink_limits = it->second;
|
||||
|
||||
for (int j = 0; j < R; ++j) {
|
||||
if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end())
|
||||
continue;
|
||||
m_solver->add_edge(i, L + j, 1, get_flush_cost(i, j));
|
||||
}
|
||||
}
|
||||
|
||||
// r_j -> group_g
|
||||
// Compute per-nozzle incoming edge count as capacity upper bound.
|
||||
// When unlink_limits restrict multiple filaments to the same nozzle,
|
||||
// capacity=1 would block valid assignments. Using the actual in-degree
|
||||
// allows the necessary flow while still preserving nozzle-level balance
|
||||
// (a nozzle with fewer forced filaments keeps a tighter cap).
|
||||
// The first unit carries a small nozzle-bonus to encourage spreading
|
||||
// filaments across distinct nozzles within the same group.
|
||||
int nozzle_bonus = max_flush + 1;
|
||||
std::vector<int> r_in_degree(R, 0);
|
||||
for (int i = 0; i < L; ++i) {
|
||||
if (auto it = m_uv_link_limits.find(i); it != m_uv_link_limits.end()) {
|
||||
for (int j : it->second)
|
||||
r_in_degree[j]++;
|
||||
continue;
|
||||
}
|
||||
std::optional<std::vector<int>> unlink_limits;
|
||||
if (auto it = m_uv_unlink_limits.find(i); it != m_uv_unlink_limits.end())
|
||||
unlink_limits = it->second;
|
||||
for (int j = 0; j < R; ++j) {
|
||||
if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end())
|
||||
continue;
|
||||
r_in_degree[j]++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < R; ++j) {
|
||||
int g = r_nodes_group[j];
|
||||
int cap = std::max(r_in_degree[j], 1);
|
||||
// First unit gets -nozzle_bonus to prefer using distinct nozzles
|
||||
m_solver->add_edge(L + j, L + R + g, 1, -nozzle_bonus);
|
||||
if (cap > 1)
|
||||
m_solver->add_edge(L + j, L + R + g, cap - 1, 0);
|
||||
}
|
||||
|
||||
// group_g -> sink (split: first unit gets -bonus, rest gets 0)
|
||||
// bonus >> nozzle_bonus, so group coverage always takes priority
|
||||
for (int g = 0; g < G; ++g) {
|
||||
m_solver->add_edge(L + R + g, m_solver->sink_id, 1, -bonus);
|
||||
if (L > 1)
|
||||
m_solver->add_edge(L + R + g, m_solver->sink_id, L - 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> GroupMinCostFlowSolver::solve()
|
||||
{
|
||||
return m_solver->solve();
|
||||
}
|
||||
|
||||
// ==================== MinFlushFlowSolver ====================
|
||||
MinFlushFlowSolver::~MinFlushFlowSolver()
|
||||
{
|
||||
}
|
||||
@@ -277,7 +667,8 @@ namespace Slic3r
|
||||
const std::unordered_map<int, std::vector<int>>& uv_link_limits,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits,
|
||||
const std::vector<int>& u_capacity,
|
||||
const std::vector<int>& v_capacity)
|
||||
const std::vector<int>& v_capacity,
|
||||
const std::vector<std::pair<std::set<int>,int>>&v_group_capacity)
|
||||
{
|
||||
assert(u_capacity.empty() || u_capacity.size() == u_nodes.size());
|
||||
assert(v_capacity.empty() || v_capacity.size() == v_nodes.size());
|
||||
@@ -286,13 +677,19 @@ namespace Slic3r
|
||||
m_solver->l_nodes = u_nodes;
|
||||
m_solver->r_nodes = v_nodes;
|
||||
|
||||
m_solver->total_nodes = u_nodes.size() + v_nodes.size() + 2;
|
||||
m_solver->total_nodes = u_nodes.size() + v_nodes.size() + v_group_capacity.size() + 2;
|
||||
|
||||
m_solver->source_id =m_solver->total_nodes - 2;
|
||||
m_solver->sink_id = m_solver->total_nodes - 1;
|
||||
|
||||
m_solver->adj.resize(m_solver->total_nodes);
|
||||
|
||||
std::vector<int> v_node_to(v_nodes.size(), m_solver->sink_id);
|
||||
for (size_t gid = 0; gid < v_group_capacity.size(); ++gid) {
|
||||
for (auto vid : v_group_capacity[gid].first)
|
||||
v_node_to[vid] = m_solver->l_nodes.size() + m_solver->r_nodes.size() + gid;
|
||||
}
|
||||
|
||||
// add edge from source to left nodes,cost to 0
|
||||
for (int i = 0; i < m_solver->l_nodes.size(); ++i) {
|
||||
int capacity = u_capacity.empty() ? 1 : u_capacity[i];
|
||||
@@ -301,7 +698,12 @@ namespace Slic3r
|
||||
// add edge from right nodes to sink,cost to 0
|
||||
for (int i = 0; i < m_solver->r_nodes.size(); ++i) {
|
||||
int capacity = v_capacity.empty() ? 1 : v_capacity[i];
|
||||
m_solver->add_edge(m_solver->l_nodes.size() + i, m_solver->sink_id, capacity, 0);
|
||||
m_solver->add_edge(m_solver->l_nodes.size() + i, v_node_to[i], capacity, 0);
|
||||
}
|
||||
// add edge from temp group node to sink node
|
||||
for(int i=0;i<v_group_capacity.size();++i){
|
||||
int capacity = v_group_capacity[i].second;
|
||||
m_solver->add_edge(m_solver->l_nodes.size() + m_solver->r_nodes.size() + i, m_solver->sink_id, capacity, 0);
|
||||
}
|
||||
// add edge from left node to right nodes
|
||||
for (int i = 0; i < m_solver->l_nodes.size(); ++i) {
|
||||
@@ -602,12 +1004,125 @@ namespace Slic3r
|
||||
}
|
||||
|
||||
|
||||
// Single-nozzle flush-minimizing reorder over one filament set / one flush matrix, with an
|
||||
// optional seed filament. Extracted from the group loop so the multi-nozzle reorder can call it
|
||||
// per physical nozzle.
|
||||
// TODO: add custom sequence
|
||||
static int reorder_filaments_for_minimum_flush_volume_base(const std::vector<unsigned int>& filament_lists,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filaments,
|
||||
const FlushMatrix& flush_matrix,
|
||||
const std::function<bool(int, std::vector<int>&)> get_custom_seq,
|
||||
std::vector<std::vector<unsigned int>>* filament_sequences,
|
||||
std::optional<unsigned int> initial_filament_id = std::nullopt)
|
||||
{
|
||||
constexpr int max_n_with_forcast = 5;
|
||||
using uint128_t = boost::multiprecision::uint128_t;
|
||||
|
||||
if (filament_sequences) {
|
||||
filament_sequences->clear();
|
||||
filament_sequences->reserve(layer_filaments.size());
|
||||
}
|
||||
auto filament_list_to_hash_key = [](const std::vector<unsigned int>& curr_layer_filaments, const std::vector<unsigned int>& next_layer_filaments,
|
||||
const std::optional<unsigned int>& prev_filament, bool use_forcast) -> uint128_t {
|
||||
uint128_t hash_key = 0;
|
||||
// 31-0 bit define current layer extruder,63-32 bit define next layer extruder,95~64 define prev extruder
|
||||
if (prev_filament) hash_key |= (uint128_t(1) << (64 + *prev_filament));
|
||||
|
||||
if (use_forcast) {
|
||||
for (auto item : next_layer_filaments) { hash_key |= (uint128_t(1) << (32 + item)); }
|
||||
}
|
||||
|
||||
for (auto item : curr_layer_filaments) { hash_key |= (uint128_t(1) << item); }
|
||||
return hash_key;
|
||||
};
|
||||
|
||||
int cost = 0;
|
||||
std::map<size_t, std::vector<unsigned int>> custom_layer_sequence_map;
|
||||
std::unordered_map<uint128_t, std::pair<float, std::vector<unsigned int>>> caches;
|
||||
std::unordered_set<unsigned int> filament_sets(filament_lists.begin(), filament_lists.end());
|
||||
std::optional<unsigned int> curr_filament_id;
|
||||
// use the provided initial filament id as the starting state when it is valid
|
||||
if (initial_filament_id.has_value() && *initial_filament_id < flush_matrix.size()) {
|
||||
curr_filament_id = initial_filament_id;
|
||||
}
|
||||
|
||||
for (size_t layer = 0; layer < layer_filaments.size(); ++layer){
|
||||
const auto& curr_lf = layer_filaments[layer];
|
||||
std::vector<int> custom_filament_seq;
|
||||
if (get_custom_seq && get_custom_seq(layer, custom_filament_seq) && !custom_filament_seq.empty()) {
|
||||
std::vector<unsigned int> unsign_custom_extruder_seq;
|
||||
for (int extruder : custom_filament_seq) {
|
||||
unsigned int unsign_extruder = static_cast<unsigned int>(extruder) - 1;
|
||||
auto it = std::find(layer_filaments[layer].begin(), layer_filaments[layer].end(), unsign_extruder);
|
||||
if (it != layer_filaments[layer].end())
|
||||
unsign_custom_extruder_seq.emplace_back(unsign_extruder);
|
||||
}
|
||||
assert(layer_filaments[layer].size() == unsign_custom_extruder_seq.size());
|
||||
|
||||
custom_layer_sequence_map[layer] = unsign_custom_extruder_seq;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t layer = 0; layer < layer_filaments.size(); ++layer) {
|
||||
const auto& curr_lf = layer_filaments[layer];
|
||||
|
||||
if(auto iter = custom_layer_sequence_map.find(layer); iter != custom_layer_sequence_map.end()){
|
||||
auto sequence_in_group = collect_filaments_in_groups<unsigned int>(std::unordered_set<unsigned int>(filament_lists.begin(),filament_lists.end()), iter->second);
|
||||
|
||||
std::optional<unsigned int> prev = curr_filament_id;
|
||||
for (auto& f: sequence_in_group){
|
||||
if(prev)
|
||||
cost += flush_matrix[*prev][f];
|
||||
prev = f;
|
||||
}
|
||||
|
||||
if(!sequence_in_group.empty()){
|
||||
curr_filament_id = sequence_in_group.back();
|
||||
}
|
||||
|
||||
if(filament_sequences)
|
||||
filament_sequences->emplace_back(sequence_in_group);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<unsigned int> filament_used = collect_filaments_in_groups<unsigned int>(filament_sets, curr_lf);
|
||||
std::vector<unsigned int> next_lf;
|
||||
if (layer + 1 < layer_filaments.size()) next_lf = layer_filaments[layer + 1];
|
||||
std::vector<unsigned int> filament_used_next_layer = collect_filaments_in_groups<unsigned int>(filament_sets, next_lf);
|
||||
|
||||
bool use_forcast = false;
|
||||
float tmp_cost = 0;
|
||||
std::vector<unsigned int> sequence;
|
||||
uint128_t hash_key = filament_list_to_hash_key(filament_used, filament_used_next_layer, curr_filament_id, use_forcast);
|
||||
if (auto iter = caches.find(hash_key); iter != caches.end()) {
|
||||
tmp_cost = iter->second.first;
|
||||
sequence = iter->second.second;
|
||||
}
|
||||
else {
|
||||
sequence = get_extruders_order(flush_matrix, filament_used, filament_used_next_layer, curr_filament_id, use_forcast, &tmp_cost);
|
||||
caches[hash_key] = { tmp_cost,sequence };
|
||||
}
|
||||
|
||||
if (filament_sequences)
|
||||
filament_sequences->emplace_back(sequence);
|
||||
|
||||
if (!sequence.empty())
|
||||
curr_filament_id = sequence.back();
|
||||
|
||||
cost += tmp_cost;
|
||||
}
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
int reorder_filaments_for_minimum_flush_volume(const std::vector<unsigned int>& filament_lists,
|
||||
const std::vector<int>& filament_maps,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filaments,
|
||||
const std::vector<FlushMatrix>& flush_matrix,
|
||||
std::optional<std::function<bool(int, std::vector<int>&)>> get_custom_seq,
|
||||
std::vector<std::vector<unsigned int>>* filament_sequences)
|
||||
std::vector<std::vector<unsigned int>>* filament_sequences,
|
||||
const std::unordered_map<int, int>& nozzle_status)
|
||||
{
|
||||
//only when layer filament num <= 5,we do forcast
|
||||
constexpr int max_n_with_forcast = 5;
|
||||
@@ -670,6 +1185,12 @@ namespace Slic3r
|
||||
if (groups[idx].empty())
|
||||
continue;
|
||||
std::optional<unsigned int>current_extruder_id;
|
||||
// seed the group (nozzle) with the filament already loaded, if nozzle_status supplies one
|
||||
if (auto it = nozzle_status.find(static_cast<int>(idx)); it != nozzle_status.end() && it->second >= 0) {
|
||||
unsigned int initial_fil = static_cast<unsigned int>(it->second);
|
||||
if (initial_fil < flush_matrix[idx].size())
|
||||
current_extruder_id = initial_fil;
|
||||
}
|
||||
|
||||
std::unordered_map<uint128_t, std::pair<float, std::vector<unsigned int>>> caches;
|
||||
|
||||
@@ -775,4 +1296,174 @@ namespace Slic3r
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
int reorder_filaments_for_multi_nozzle_extruder(const std::vector<unsigned int>& filament_lists,
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult& nozzle_group_result,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filaments,
|
||||
const std::vector<FlushMatrix>& flush_matrix,
|
||||
const std::function<bool(int, std::vector<int>&)> get_custom_seq,
|
||||
std::vector<std::vector<unsigned int>>* filament_sequences,
|
||||
const MultiNozzleUtils::NozzleStatusRecorder& initial_status)
|
||||
{
|
||||
std::map<int,std::set<unsigned int>> nozzle_filament_groups;
|
||||
std::map<int,std::set<int>> extruder_to_nozzle;
|
||||
|
||||
for(auto filament_idx : filament_lists){
|
||||
auto nozzle_info = nozzle_group_result.get_nozzle_for_filament(filament_idx, -1);
|
||||
if (!nozzle_info)
|
||||
continue;
|
||||
nozzle_filament_groups[nozzle_info->group_id].insert(filament_idx);
|
||||
extruder_to_nozzle[nozzle_info->extruder_id].insert(nozzle_info->group_id);
|
||||
}
|
||||
|
||||
std::map<size_t, std::vector<unsigned int>>custom_layer_sequence_map;// save the filament sequences of custom layer
|
||||
for (size_t layer = 0; layer < layer_filaments.size(); ++layer){
|
||||
const auto& curr_lf = layer_filaments[layer];
|
||||
std::vector<int> custom_filament_seq;
|
||||
if (get_custom_seq && get_custom_seq(layer, custom_filament_seq) && !custom_filament_seq.empty()) {
|
||||
std::vector<unsigned int> unsign_custom_extruder_seq;
|
||||
for (int extruder : custom_filament_seq) {
|
||||
unsigned int unsign_extruder = static_cast<unsigned int>(extruder) - 1;
|
||||
auto it = std::find(layer_filaments[layer].begin(), layer_filaments[layer].end(), unsign_extruder);
|
||||
if (it != layer_filaments[layer].end())
|
||||
unsign_custom_extruder_seq.emplace_back(unsign_extruder);
|
||||
}
|
||||
assert(layer_filaments[layer].size() == unsign_custom_extruder_seq.size());
|
||||
|
||||
custom_layer_sequence_map[layer] = unsign_custom_extruder_seq;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::map<int, std::vector<std::vector<unsigned int>>> nozzle_filament_sequences;
|
||||
bool store_sequence = filament_sequences != nullptr;
|
||||
|
||||
int cost = 0;
|
||||
for(auto& group : nozzle_filament_groups){
|
||||
int nozzle_id = group.first;
|
||||
auto& filament_in_nozzle = group.second;
|
||||
|
||||
int extruder_id = 0;
|
||||
for(auto& [ext, nozzle_set] : extruder_to_nozzle){
|
||||
if(nozzle_set.count(nozzle_id)){
|
||||
extruder_id = ext;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(filament_in_nozzle.empty())
|
||||
continue;
|
||||
|
||||
std::vector<unsigned int> filament_vec_in_nozzle(filament_in_nozzle.begin(), filament_in_nozzle.end());
|
||||
|
||||
int initial_fil = initial_status.get_filament_in_nozzle(nozzle_id);
|
||||
std::optional<unsigned int> initial_fil_id = (initial_fil >= 0 && initial_fil < flush_matrix[extruder_id].size())? std::optional<unsigned int>(initial_fil) : std::nullopt;
|
||||
|
||||
std::vector<std::vector<unsigned int>> filament_seq;
|
||||
cost += reorder_filaments_for_minimum_flush_volume_base(filament_vec_in_nozzle, layer_filaments, flush_matrix[extruder_id], get_custom_seq,
|
||||
store_sequence ? &filament_seq : nullptr, initial_fil_id);
|
||||
if(store_sequence)
|
||||
nozzle_filament_sequences.emplace(nozzle_id, std::move(filament_seq));
|
||||
|
||||
}
|
||||
|
||||
if(!store_sequence)
|
||||
return cost;
|
||||
|
||||
std::vector<int> extruders;
|
||||
std::map<int, std::vector<int>> nozzles_per_extruder;
|
||||
for (auto& [extruder_id, nozzle_set] : extruder_to_nozzle) {
|
||||
extruders.push_back(extruder_id);
|
||||
nozzles_per_extruder[extruder_id] = std::vector<int>(
|
||||
nozzle_set.begin(), nozzle_set.end()
|
||||
);
|
||||
}
|
||||
|
||||
filament_sequences->clear();
|
||||
filament_sequences->resize(layer_filaments.size());
|
||||
|
||||
// No filament in filament_lists resolved to a nozzle in nozzle_group_result
|
||||
// (e.g. a degenerate input where a layer references a filament index outside the range's
|
||||
// grouping map). Emit each layer's filaments in their given order so the caller still gets a
|
||||
// valid per-layer sequence, and skip the cross-nozzle reorder. Guards the unchecked
|
||||
// max_element(extruders) below, which would dereference end() on an empty range.
|
||||
if (extruders.empty()) {
|
||||
for (size_t layer = 0; layer < layer_filaments.size(); ++layer)
|
||||
(*filament_sequences)[layer] = layer_filaments[layer];
|
||||
return cost;
|
||||
}
|
||||
|
||||
auto get_extruder_for_filament = [nozzle_group_result](unsigned int filament_idx) {
|
||||
auto nozzle = nozzle_group_result.get_nozzle_for_filament(filament_idx, -1);
|
||||
if (!nozzle)
|
||||
return -1;
|
||||
return nozzle->extruder_id;
|
||||
};
|
||||
|
||||
auto get_nozzle_idx_for_filament = [nozzles_per_extruder, nozzle_group_result](unsigned int filament_idx)->int {
|
||||
auto nozzle = nozzle_group_result.get_nozzle_for_filament(filament_idx, -1);
|
||||
if (!nozzle)
|
||||
return -1;
|
||||
return std::find(nozzles_per_extruder.at(nozzle->extruder_id).begin(), nozzles_per_extruder.at(nozzle->extruder_id).end(), nozzle->group_id) - nozzles_per_extruder.at(nozzle->extruder_id).begin();
|
||||
};
|
||||
|
||||
int initial_extruder = initial_status.get_current_extruder_id();
|
||||
int last_extruder_idx = (initial_extruder >= 0 && initial_extruder < extruders.size())? initial_extruder : 0;
|
||||
// set size to max extruder_id in case extruder_id is not continuous
|
||||
std::vector<int> last_nozzle_idx(*std::max_element(extruders.begin(),extruders.end()) + 1,0);
|
||||
for (int ext_id = 0; ext_id < static_cast<int>(last_nozzle_idx.size()); ext_id++) {
|
||||
int initial_nozzle = initial_status.get_nozzle_in_extruder(ext_id);
|
||||
auto ext_nozzles = nozzles_per_extruder[ext_id];
|
||||
auto it = std::find(ext_nozzles.begin(), ext_nozzles.end(), initial_nozzle);
|
||||
if (it != ext_nozzles.end())
|
||||
last_nozzle_idx[ext_id] = static_cast<int>(std::distance(ext_nozzles.begin(), it));
|
||||
}
|
||||
|
||||
for (size_t layer = 0; layer < layer_filaments.size(); ++layer) {
|
||||
auto& out_seq = (*filament_sequences)[layer];
|
||||
|
||||
if (custom_layer_sequence_map.find(layer) != custom_layer_sequence_map.end()) {
|
||||
out_seq = custom_layer_sequence_map[layer];
|
||||
if (!out_seq.empty()) {
|
||||
last_extruder_idx = get_extruder_for_filament(out_seq.back());
|
||||
for (auto filament : out_seq) {
|
||||
int cur_ext_id = get_extruder_for_filament(filament);
|
||||
last_nozzle_idx[cur_ext_id] = get_nozzle_idx_for_filament(filament);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (last_extruder_idx == -1)
|
||||
last_extruder_idx = 0;
|
||||
|
||||
int curr_last_extruder_idx = last_extruder_idx;
|
||||
auto curr_last_nozzle_idx = last_nozzle_idx;
|
||||
for (int i = 0; i < extruders.size(); ++i) {
|
||||
int extruder_id = extruders[(last_extruder_idx + i) % extruders.size()];
|
||||
auto& base_nozzles = nozzles_per_extruder[extruder_id];
|
||||
|
||||
bool has_seq = false;
|
||||
if (last_nozzle_idx[extruder_id] == -1)
|
||||
last_nozzle_idx[extruder_id] = 0;
|
||||
|
||||
for (int j = 0; j < base_nozzles.size(); ++j) {
|
||||
int nozzle_idx = (last_nozzle_idx[extruder_id] + j) % base_nozzles.size();
|
||||
int nozzle_id = base_nozzles[nozzle_idx];
|
||||
const auto& frag = nozzle_filament_sequences[nozzle_id][layer];
|
||||
if (frag.empty())
|
||||
continue;
|
||||
has_seq = true;
|
||||
curr_last_nozzle_idx[extruder_id] = nozzle_idx;
|
||||
out_seq.insert(out_seq.end(), frag.begin(), frag.end());
|
||||
}
|
||||
|
||||
if (has_seq)
|
||||
curr_last_extruder_idx = extruder_id;
|
||||
}
|
||||
last_extruder_idx = curr_last_extruder_idx;
|
||||
last_nozzle_idx = curr_last_nozzle_idx;
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,10 @@
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include "../MultiNozzleUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -15,21 +18,27 @@ using FlushMatrix = std::vector<std::vector<float>>;
|
||||
namespace MaxFlowGraph {
|
||||
const int INF = std::numeric_limits<int>::max();
|
||||
const int INVALID_ID = -1;
|
||||
// Upper bound for MCMF edge cost to prevent int overflow in SPFA causing infinite loops
|
||||
constexpr int MCMF_MAX_EDGE_COST = 10000000;
|
||||
}
|
||||
|
||||
// Namespace-scope edge shared by the max-flow / min-cost-max-flow solvers below.
|
||||
// The default cost keeps the plain max-flow solvers (which never read cost) source-compatible.
|
||||
struct Edge
|
||||
{
|
||||
int from, to, capacity, cost, flow;
|
||||
Edge(int u, int v, int cap, int cst = 0) : from(u), to(v), capacity(cap), cost(cst), flow(0) {}
|
||||
};
|
||||
|
||||
class MaxFlowSolver
|
||||
{
|
||||
private:
|
||||
struct Edge {
|
||||
int from, to, capacity, flow;
|
||||
Edge(int u, int v, int cap) :from(u), to(v), capacity(cap), flow(0) {}
|
||||
};
|
||||
public:
|
||||
MaxFlowSolver(const std::vector<int>& u_nodes, const std::vector<int>& v_nodes,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_link_limits = {},
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits = {},
|
||||
const std::vector<int>& u_capacity = {},
|
||||
const std::vector<int>& v_capacity = {}
|
||||
const std::vector<int>& v_capacity = {},
|
||||
const std::vector<std::pair<std::set<int>, int>>& v_group_capacity = {}
|
||||
);
|
||||
std::vector<int> solve();
|
||||
|
||||
@@ -47,6 +56,7 @@ private:
|
||||
|
||||
|
||||
struct MinCostMaxFlow;
|
||||
struct MaxFlowWithLowerBounds;
|
||||
|
||||
class GeneralMinCostSolver
|
||||
{
|
||||
@@ -61,6 +71,84 @@ private:
|
||||
std::unique_ptr<MinCostMaxFlow> m_solver;
|
||||
};
|
||||
|
||||
class GeneralMinCostLowerBoundsSolver
|
||||
{
|
||||
public:
|
||||
GeneralMinCostLowerBoundsSolver(
|
||||
const std::vector<FlushMatrix> &matrix_,
|
||||
const std::vector<int>& u_nodes,
|
||||
const std::vector<int>& v_nodes,
|
||||
const std::vector<int>& v_nodes_group,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_link_limits = {},
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits = {});
|
||||
|
||||
std::vector<int> solve();
|
||||
~GeneralMinCostLowerBoundsSolver();
|
||||
|
||||
private:
|
||||
void build_feasible_graph(const std::unordered_set<int>& no_lower_groups);
|
||||
|
||||
void build_graph_with_feasible_result();
|
||||
|
||||
void add_edge_with_lower_bound(int from, int to, int lower, int upper, int cost);
|
||||
|
||||
int get_distance(const int idx_in_left,const int idx_in_right);
|
||||
|
||||
private:
|
||||
std::unique_ptr<MaxFlowWithLowerBounds> m_solver_lower_bounds;
|
||||
std::unique_ptr<MinCostMaxFlow> m_solver_min_cost;
|
||||
|
||||
std::vector<FlushMatrix> flush_matrix;
|
||||
std::vector<int> l_nodes;
|
||||
std::vector<int> r_nodes;
|
||||
std::vector<int> r_nodes_group;
|
||||
std::unordered_map<int, std::vector<int>> m_uv_link_limits;
|
||||
std::unordered_map<int, std::vector<int>> m_uv_unlink_limits;
|
||||
int num_groups = 0;
|
||||
|
||||
// support lower bounds
|
||||
struct LowerBoundEdge{
|
||||
int edge_id;
|
||||
int lower;
|
||||
};
|
||||
|
||||
std::vector<int> demand;
|
||||
std::vector<LowerBoundEdge> lower_bound_edges;
|
||||
|
||||
int super_source = -1;
|
||||
int super_sink = -1;
|
||||
int source_id = -1;
|
||||
int sink_id = -1;
|
||||
int max_flow_edges = 0;
|
||||
};
|
||||
|
||||
class GroupMinCostFlowSolver
|
||||
{
|
||||
public:
|
||||
GroupMinCostFlowSolver(
|
||||
const std::vector<FlushMatrix> &matrix_,
|
||||
const std::vector<int> &u_nodes,
|
||||
const std::vector<int> &v_nodes,
|
||||
const std::vector<int> &v_nodes_group,
|
||||
const std::unordered_map<int, std::vector<int>> &uv_link_limits = {},
|
||||
const std::unordered_map<int, std::vector<int>> &uv_unlink_limits = {});
|
||||
|
||||
std::vector<int> solve();
|
||||
~GroupMinCostFlowSolver();
|
||||
|
||||
private:
|
||||
void build_graph();
|
||||
int get_flush_cost(int l_idx, int r_idx);
|
||||
|
||||
std::unique_ptr<MinCostMaxFlow> m_solver;
|
||||
std::vector<FlushMatrix> flush_matrix;
|
||||
std::vector<int> l_nodes;
|
||||
std::vector<int> r_nodes;
|
||||
std::vector<int> r_nodes_group;
|
||||
std::unordered_map<int, std::vector<int>> m_uv_link_limits;
|
||||
std::unordered_map<int, std::vector<int>> m_uv_unlink_limits;
|
||||
int num_groups = 0;
|
||||
};
|
||||
|
||||
class MinFlushFlowSolver
|
||||
{
|
||||
@@ -71,7 +159,8 @@ public:
|
||||
const std::unordered_map<int, std::vector<int>>& uv_link_limits = {},
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits = {},
|
||||
const std::vector<int>& u_capacity = {},
|
||||
const std::vector<int>& v_capacity = {}
|
||||
const std::vector<int>& v_capacity = {},
|
||||
const std::vector<std::pair<std::set<int>, int>>& v_group_capacity = {}
|
||||
);
|
||||
std::vector<int> solve();
|
||||
~MinFlushFlowSolver();
|
||||
@@ -108,7 +197,19 @@ int reorder_filaments_for_minimum_flush_volume(const std::vector<unsigned int> &
|
||||
const std::vector<std::vector<unsigned int>> &layer_filaments,
|
||||
const std::vector<FlushMatrix> &flush_matrix,
|
||||
std::optional<std::function<bool(int, std::vector<int> &)>> get_custom_seq,
|
||||
std::vector<std::vector<unsigned int>> *filament_sequences);
|
||||
std::vector<std::vector<unsigned int>> *filament_sequences,
|
||||
const std::unordered_map<int, int>& nozzle_status = {});
|
||||
|
||||
// Order filaments within a per-nozzle grouping result (multi-nozzle extruders). Threads a
|
||||
// NozzleStatusRecorder describing the initial physical nozzle occupancy so the reorder can reward
|
||||
// keeping an already-loaded filament in place.
|
||||
int reorder_filaments_for_multi_nozzle_extruder(const std::vector<unsigned int>& filament_lists,
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult& nozzle_group_result,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filaments,
|
||||
const std::vector<FlushMatrix>& flush_matrix,
|
||||
const std::function<bool(int,std::vector<int>&)> get_custom_seq,
|
||||
std::vector<std::vector<unsigned int>> * filament_sequences,
|
||||
const MultiNozzleUtils::NozzleStatusRecorder& initial_status = {});
|
||||
|
||||
}
|
||||
#endif // !TOOL_ORDER_UTILS_HPP
|
||||
File diff suppressed because it is too large.
Load diff
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include "../FilamentGroup.hpp"
|
||||
#include "../MultiNozzleUtils.hpp"
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
@@ -98,19 +99,23 @@ private:
|
||||
struct FilamentChangeStats
|
||||
{
|
||||
int filament_flush_weight{0};
|
||||
// flush_filament_change_count counts filament changes that actually flush a physical nozzle.
|
||||
// It replaces the former (dead, never populated) extruder_change_count. For single-nozzle-per-
|
||||
// extruder printers it equals the per-extruder filament_change_count, so GUI stat displays are
|
||||
// unchanged.
|
||||
int flush_filament_change_count{0};
|
||||
int filament_change_count{0};
|
||||
int extruder_change_count{0};
|
||||
|
||||
void clear(){
|
||||
filament_flush_weight = 0;
|
||||
filament_change_count = 0;
|
||||
extruder_change_count = 0;
|
||||
flush_filament_change_count = 0;
|
||||
}
|
||||
|
||||
FilamentChangeStats& operator+=(const FilamentChangeStats& other) {
|
||||
this->filament_flush_weight += other.filament_flush_weight;
|
||||
this->filament_change_count += other.filament_change_count;
|
||||
this->extruder_change_count += other.extruder_change_count;
|
||||
this->flush_filament_change_count += other.flush_filament_change_count;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -118,7 +123,7 @@ struct FilamentChangeStats
|
||||
FilamentChangeStats ret;
|
||||
ret.filament_flush_weight = this->filament_flush_weight + other.filament_flush_weight;
|
||||
ret.filament_change_count = this->filament_change_count + other.filament_change_count;
|
||||
ret.extruder_change_count = this->extruder_change_count + other.extruder_change_count;
|
||||
ret.flush_filament_change_count = this->flush_filament_change_count + other.flush_filament_change_count;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -236,12 +241,44 @@ public:
|
||||
bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().has_wipe_tower; }
|
||||
|
||||
int get_most_used_extruder() const { return most_used_extruder; }
|
||||
|
||||
// Logical (extruder, nozzle) grouping of the used filaments, built during reorder.
|
||||
// For single-nozzle printers this is one logical nozzle per extruder (nozzle id == extruder id).
|
||||
// Consumed by GCode (get_nozzle_id / get_first_nozzle_for_filament).
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult &get_layered_nozzle_group_result() const { return m_nozzle_group_result; }
|
||||
|
||||
// Physical nozzle occupancy threading for the sequential (by-object) selector regroup: the
|
||||
// setter seeds both the initial recorder (the state the per-layer plan starts from) and the
|
||||
// running recorder (read back after sort_and_build_data via get_nozzle_status()), so each
|
||||
// object's plan continues from the nozzle state the previous object ended with.
|
||||
const MultiNozzleUtils::NozzleStatusRecorder &get_nozzle_status() const { return m_nozzle_status; }
|
||||
void set_nozzle_status(const MultiNozzleUtils::NozzleStatusRecorder &status) { m_initial_nozzle_status = status; m_nozzle_status = status; }
|
||||
/*
|
||||
* called in single extruder mode, the value in map are all 0
|
||||
* called in dual extruder mode, the value in map will be 0 or 1
|
||||
* 0 based group id
|
||||
*/
|
||||
static std::vector<int> get_recommended_filament_maps(const std::vector<std::vector<unsigned int>>& layer_filaments, const Print* print,const FilamentMapMode mode, const std::vector<std::set<int>>& physical_unprintables, const std::vector<std::set<int>>& geometric_unprintables);
|
||||
// Nozzle-centric grouping. Returns a nozzle-aware LayeredNozzleGroupResult instead of a plain
|
||||
// extruder-level std::vector<int>. Callers derive the 0/1-based extruder map via
|
||||
// result.get_extruder_map(). unprintable_volumes / nozzle_status default empty for the static
|
||||
// path; the per-layer engine supplies non-empty values.
|
||||
static MultiNozzleUtils::LayeredNozzleGroupResult get_recommended_filament_maps(const std::vector<std::vector<unsigned int>>& layer_filaments, const Print* print,const FilamentMapMode mode, const std::vector<std::set<int>>& physical_unprintables, const std::vector<std::set<int>>& geometric_unprintables, const std::map<int, std::set<NozzleVolumeType>>& unprintable_volumes = {}, const std::unordered_map<int, int>& nozzle_status = {});
|
||||
|
||||
// Wrap stitched per-layer filament->nozzle maps from a sequential (by-object) selector regroup
|
||||
// into one print-wide result. nozzle_map_per_layer / layer_filaments / layer_sequences are the
|
||||
// per-object planned layers concatenated in print order; nozzle_map_per_layer is taken by value
|
||||
// and normalized in place. The nozzle list is rebuilt from the print's grouping context. Returns
|
||||
// an empty result when the wrap fails. Lives here (not in Print) to reach the file-local
|
||||
// grouping-context builder.
|
||||
static MultiNozzleUtils::LayeredNozzleGroupResult build_sequential_group_result(
|
||||
Print* print,
|
||||
std::vector<std::vector<int>> nozzle_map_per_layer,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filaments,
|
||||
const std::vector<std::vector<unsigned int>>& layer_sequences,
|
||||
const std::vector<unsigned int>& used_filaments,
|
||||
const std::vector<std::set<int>>& physical_unprintables,
|
||||
const std::vector<std::set<int>>& geometric_unprintables,
|
||||
const std::map<int, std::set<NozzleVolumeType>>& unprintable_volumes);
|
||||
|
||||
// should be called after doing reorder
|
||||
FilamentChangeStats get_filament_change_stats(FilamentChangeMode mode);
|
||||
@@ -283,6 +320,13 @@ private:
|
||||
FilamentChangeStats m_stats_by_single_extruder;
|
||||
FilamentChangeStats m_stats_by_multi_extruder_curr;
|
||||
FilamentChangeStats m_stats_by_multi_extruder_best;
|
||||
MultiNozzleUtils::LayeredNozzleGroupResult m_nozzle_group_result;
|
||||
// Physical nozzle occupancy threaded through the per-layer selector regroup.
|
||||
// m_initial_nozzle_status seeds the first combo range (empty for a fresh slice — there is no
|
||||
// device continuation state); m_nozzle_status carries the running state out of the plan. Inert
|
||||
// for every printer except an H2C profile that enables the filament selector (is_dynamic_group_reorder).
|
||||
MultiNozzleUtils::NozzleStatusRecorder m_initial_nozzle_status;
|
||||
MultiNozzleUtils::NozzleStatusRecorder m_nozzle_status;
|
||||
|
||||
int most_used_extruder;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
@@ -1493,6 +1494,21 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi
|
||||
m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value)
|
||||
{
|
||||
m_flat_ironing = (m_flat_ironing && m_use_gap_wall);
|
||||
|
||||
// Prime-tower heating during wipe. m_is_multiple_nozzle mirrors the gate used in ToolOrdering/GCode
|
||||
// (std::any_of extruder_max_nozzle_count > 1); it is false for every current printer, so the
|
||||
// heating-during-wipe logic in toolchange_wipe_new is inert.
|
||||
m_hotend_heating_rate = config.hotend_heating_rate.values;
|
||||
m_physical_extruder_map = config.physical_extruder_map.values;
|
||||
m_is_multiple_nozzle = std::any_of(config.extruder_max_nozzle_count.values.begin(),
|
||||
config.extruder_max_nozzle_count.values.end(),
|
||||
[](int v) { return v > 1; });
|
||||
|
||||
// Per-extruder printable-height clamp. Empty for single-extruder printers
|
||||
// (extruder_printable_height = []), so is_valid_last_layer is inert there.
|
||||
m_printable_height = config.extruder_printable_height.values;
|
||||
m_last_layer_id.assign(config.nozzle_diameter.size(), -1);
|
||||
|
||||
// Read absolute value of first layer speed, if given as percentage,
|
||||
// it is taken over following default. Speeds from config are not
|
||||
// easily accessible here.
|
||||
@@ -1543,6 +1559,10 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
||||
//while (m_filpar.size() < idx+1) // makes sure the required element is in the vector
|
||||
m_filpar.push_back(FilamentParameters());
|
||||
|
||||
// Orca: one row per filament, indexed by the raw filament id. Under a per-layer nozzle
|
||||
// grouping the per-variant arrays may hold several columns per filament; the tower has no
|
||||
// layer dimension here, so it keeps the filament's first column (tower x per-layer
|
||||
// grouping is a documented follow-up).
|
||||
m_filpar[idx].material = config.filament_type.get_at(idx);
|
||||
m_filpar[idx].is_soluble = config.wipe_tower_filament == 0 ? config.filament_soluble.get_at(idx) : (idx != size_t(config.wipe_tower_filament - 1));
|
||||
// BBS
|
||||
@@ -1558,8 +1578,12 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
||||
}
|
||||
m_filpar[idx].tower_interface_pre_extrusion_dist = config.filament_tower_interface_pre_extrusion_dist.get_at(idx);
|
||||
m_filpar[idx].tower_interface_pre_extrusion_length = config.filament_tower_interface_pre_extrusion_length.get_at(idx);
|
||||
// PETG pre-extrusion offset reuses the tower-interface pre-extrusion distance. Only read by the
|
||||
// has_filament_switcher-gated PETG branch in get_next_pos (inert fleet-wide).
|
||||
m_filpar[idx].petg_pre_extrusion_offset_dist = config.filament_tower_interface_pre_extrusion_dist.get_at(idx);
|
||||
m_filpar[idx].tower_ironing_area = config.filament_tower_ironing_area.get_at(idx);
|
||||
m_filpar[idx].tower_interface_purge_length = config.filament_tower_interface_purge_volume.get_at(idx);
|
||||
m_filpar[idx].filament_cooling_before_tower = config.filament_cooling_before_tower.get_at(idx);
|
||||
|
||||
// If this is a single extruder MM printer, we will use all the SE-specific config values.
|
||||
// Otherwise, the defaults will be used to turn off the SE stuff.
|
||||
@@ -1651,6 +1675,27 @@ Vec2f WipeTower::get_next_pos(const WipeTower::box_coordinates &cleaning_box, fl
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
// Shift the wipe start outward for a PETG pre-extrusion on filament-switcher devices, clamped to the
|
||||
// shared printable bed. Gated on m_has_filament_switcher, which is false for the whole shipping fleet
|
||||
// (no profile sets the key), so is_petg_pre_extrusion is always false and res is returned unchanged.
|
||||
// The tower-interface contact branch is deliberately NOT applied here (enable_tower_interface_features
|
||||
// DOES ship on H2C/X2D; applying it would change their g-code); is_contact_pre_extrusion is computed
|
||||
// only as the guard that gives the contact path priority over PETG.
|
||||
bool is_contact_pre_extrusion = interface_layer && m_enable_tower_interface_features;
|
||||
bool is_petg_pre_extrusion = !is_contact_pre_extrusion && is_petg_filament(m_current_tool) && m_has_filament_switcher;
|
||||
if (is_petg_pre_extrusion) {
|
||||
Vec2f stop_pos = res;
|
||||
float offset_dist = m_filpar[m_current_tool].petg_pre_extrusion_offset_dist;
|
||||
auto printer_bbx = unscaled(get_extents(m_shared_print_bed)); // BoundingBoxBase<Vec2d>
|
||||
printer_bbx.translate((-m_wipe_tower_pos - m_rib_offset).cast<double>());
|
||||
if (stop_pos.x() < m_wipe_tower_width / 2.f)
|
||||
stop_pos = Vec2f(stop_pos.x() - offset_dist, stop_pos.y());
|
||||
else
|
||||
stop_pos = Vec2f(stop_pos.x() + offset_dist, stop_pos.y());
|
||||
if (stop_pos.x() < printer_bbx.min[0]) stop_pos.x() = printer_bbx.min[0];
|
||||
if (stop_pos.x() > printer_bbx.max[0]) stop_pos.x() = printer_bbx.max[0];
|
||||
res = stop_pos;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -2645,6 +2690,11 @@ bool WipeTower::is_tpu_filament(int filament_id) const
|
||||
return m_filpar[filament_id].material == "TPU";
|
||||
}
|
||||
|
||||
bool WipeTower::is_petg_filament(int filament_id) const
|
||||
{
|
||||
return m_filpar[filament_id].material == "PETG";
|
||||
}
|
||||
|
||||
// BBS: consider both soluable and support properties
|
||||
// Return index of first toolchange that switches to non-soluble and non-support extruder
|
||||
// ot -1 if there is no such toolchange.
|
||||
@@ -2717,6 +2767,9 @@ void WipeTower::get_wall_skip_points(const WipeTowerInfo &layer)
|
||||
float spacing = m_layer_info->extra_spacing;
|
||||
if (has_tpu_filament() && m_layer_info->extra_spacing < m_tpu_fixed_spacing) spacing = 1;
|
||||
float nozzle_change_depth = tool_change.nozzle_change_depth * spacing;
|
||||
// Drop the nozzle-change depth on an extruder's final layer above its printable height
|
||||
// (inert unless is_valid_last_layer clamps, i.e. multi-extruder near Z-max).
|
||||
if (!is_valid_last_layer(old_filament, m_cur_layer_id, layer.z)) nozzle_change_depth = 0.f;
|
||||
//float nozzle_change_depth = tool_change.nozzle_change_depth * (has_tpu_filament() ? m_tpu_fixed_spacing : layer.extra_spacing);
|
||||
auto* block = get_block_by_category(m_filpar[new_filament].category, false);
|
||||
if (!block)
|
||||
@@ -2760,7 +2813,10 @@ void WipeTower::get_wall_skip_points(const WipeTowerInfo &layer)
|
||||
WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool solid_toolchange,bool solid_nozzlechange)
|
||||
{
|
||||
m_nozzle_change_result.gcode.clear();
|
||||
if (!m_filament_map.empty() && new_tool < m_filament_map.size() && m_filament_map[m_current_tool] != m_filament_map[new_tool]) {
|
||||
// Skip the cross-extruder nozzle change (ramming) on an extruder's final layer above its printable
|
||||
// height. is_valid_last_layer is inert unless multi-extruder near Z-max.
|
||||
if (!m_filament_map.empty() && new_tool < m_filament_map.size() && m_filament_map[m_current_tool] != m_filament_map[new_tool]
|
||||
&& is_valid_last_layer(m_current_tool, m_cur_layer_id, m_z_pos)) {
|
||||
m_nozzle_change_result = nozzle_change_new(m_current_tool, new_tool, solid_nozzlechange);
|
||||
}
|
||||
|
||||
@@ -3411,23 +3467,103 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat
|
||||
x_to_wipe = solid_tool_toolchange ? std::numeric_limits<float>::max(): x_to_wipe;
|
||||
float target_speed = is_first_layer() ? std::min(m_first_layer_speed * 60.f, 4800.f) : 4800.f;
|
||||
target_speed = solid_tool_toolchange ? 20.f * 60.f : target_speed;
|
||||
float wipe_speed = 0.33f * target_speed;
|
||||
// Nominal wipe-speed schedule. The applied wipe_speed is nominal_speed * speed_factor; speed_factor
|
||||
// stays 1.0 unless the H2C prime-tower heating-during-wipe model below slows the wipe so the hotend
|
||||
// can reach temperature (nominal_speed == wipe_speed when speed_factor == 1, i.e. single-nozzle).
|
||||
float nominal_speed = 0.33f * target_speed;
|
||||
|
||||
m_left_to_right = ((m_cur_layer_id + 3) % 4 >= 2);
|
||||
|
||||
bool is_from_up = (m_cur_layer_id % 2 == 1);
|
||||
|
||||
// Prime-tower heating during wipe. Everything here is gated on m_is_multiple_nozzle (false for every
|
||||
// current printer); the lambdas emit nothing until add_M104_by_requirement's gate opens, so the
|
||||
// single-nozzle wipe is untouched.
|
||||
// WipeSpeedMap mirrors the nominal schedule above and is read only by estimate_wipe_time. It is a
|
||||
// std::array (stack, no per-call heap allocation); values depend on runtime target_speed so it
|
||||
// cannot be static const.
|
||||
const std::array<float, 5> WipeSpeedMap{0.33f * target_speed, 0.375f * target_speed, 0.458f * target_speed,
|
||||
0.875f * target_speed, std::min(target_speed, 0.875f * target_speed + 50.f)};
|
||||
auto estimate_wipe_time = [&cleaning_box, &x_to_wipe, &xr, &xl, &dy, &WipeSpeedMap, &solid_tool_toolchange]() -> float {
|
||||
int n = std::ceil(x_to_wipe / (xr - xl));
|
||||
if (solid_tool_toolchange) n = (cleaning_box.lu[1] - cleaning_box.ld[1]) / dy;
|
||||
float one_line_len = xr - xl;
|
||||
float time = std::numeric_limits<float>::max();
|
||||
if (n <= 1)
|
||||
time = one_line_len / WipeSpeedMap[0];
|
||||
else if (n <= 2)
|
||||
time = one_line_len / WipeSpeedMap[0] + one_line_len / WipeSpeedMap[1];
|
||||
else if (n <= 3)
|
||||
time = one_line_len / WipeSpeedMap[0] + one_line_len / WipeSpeedMap[1] + one_line_len / WipeSpeedMap[2];
|
||||
else if (n <= 4)
|
||||
time = one_line_len / WipeSpeedMap[0] + one_line_len / WipeSpeedMap[1] + one_line_len / WipeSpeedMap[2] + one_line_len / WipeSpeedMap[3];
|
||||
else {
|
||||
time = one_line_len / WipeSpeedMap[0] + one_line_len / WipeSpeedMap[1] + one_line_len / WipeSpeedMap[2] + one_line_len / WipeSpeedMap[3];
|
||||
time += (n - 4) * one_line_len / WipeSpeedMap[4];
|
||||
}
|
||||
return time * 60.f;
|
||||
};
|
||||
// Emit the arriving-hotend pre-heat inside the M632/M633 nozzle-change barrier. `M632 S<tool>[ H<nozzle>]
|
||||
// M N` opens the barrier (M = firmware nozzle-change flag, N = slicer generated), the M104 sets the
|
||||
// arriving hotend temp, and `M633` closes it. H2C's grouping is static (no dynamic nozzle map), so the
|
||||
// H<nozzle> field is omitted (a dynamic nozzle map would supply a real nozzle id, a static map -1 => no
|
||||
// H). The counterproductive fan-on (M106 S255) used for departing-tool cooldown is intentionally
|
||||
// omitted, since this is a pre-HEAT of the arriving tool. The whole helper is only ever called from
|
||||
// add_M104_by_requirement, which is gated on m_is_multiple_nozzle (extruder_max_nozzle_count>1) => H2C
|
||||
// only; every other printer's wipe tower is untouched. The M632 M-flag is itself a firmware barrier, so
|
||||
// a preceding M400 wait is subsumed.
|
||||
auto format_line_M104 = [this](int target_temp, int target_extruder = -1, bool wait_for_moves = true, const std::string &comment = "") {
|
||||
std::string buffer;
|
||||
buffer += "M632 S" + std::to_string(m_current_tool) + " M N\n";
|
||||
buffer += "M104";
|
||||
if (target_extruder != -1 && target_extruder < (int) m_physical_extruder_map.size())
|
||||
buffer += (" T" + std::to_string(m_physical_extruder_map[target_extruder]));
|
||||
buffer += " S" + std::to_string(target_temp) + " N0"; // N0 means the gcode is generated by the slicer
|
||||
if (!comment.empty()) buffer += " ;" + comment;
|
||||
buffer += '\n';
|
||||
buffer += "M633\n";
|
||||
(void) wait_for_moves; // the M632 M-flag barrier replaces the former M400 wait
|
||||
return buffer;
|
||||
};
|
||||
// Suppress the pre-heat M104 on the first layer and on solid (contact) toolchanges (should_heating).
|
||||
// m_is_multiple_nozzle folds in the H2C gate so single-nozzle output is untouched.
|
||||
// Orca: the arriving extruder id is resolved as m_filament_map[tool]-1 (layer-static) because Orca's
|
||||
// wipe tower is extruder-level rather than tracking a per-layer nozzle map.
|
||||
bool should_heating = m_is_multiple_nozzle && m_filpar[m_current_tool].filament_cooling_before_tower > EPSILON &&
|
||||
!solid_tool_toolchange && !is_first_layer();
|
||||
auto add_M104_by_requirement = [&writer, &format_line_M104, &should_heating, this]() {
|
||||
if (m_filpar[m_current_tool].filament_cooling_before_tower < EPSILON) return;
|
||||
if (!should_heating) return;
|
||||
float target_temp = is_first_layer() ? m_filpar[m_current_tool].nozzle_temperature_initial_layer : m_filpar[m_current_tool].nozzle_temperature;
|
||||
writer.append(format_line_M104(target_temp, m_filament_map[m_current_tool] - 1));
|
||||
};
|
||||
float speed_factor = 1.f;
|
||||
if (should_heating) {
|
||||
// The heating-slowdown scaling is disabled — no additional heating time is required, so
|
||||
// speed_factor stays 1.0. The structure and estimate_wipe_time/WipeSpeedMap are retained for
|
||||
// future H2C tuning; the divide-by-zero/bounds guard is preserved in the commented body below.
|
||||
// int extruder_id = m_filament_map[m_current_tool] - 1;
|
||||
// if (extruder_id >= 0 && extruder_id < (int) m_hotend_heating_rate.size() && m_hotend_heating_rate[extruder_id] > 0.) {
|
||||
// float estimate_time = estimate_wipe_time();
|
||||
// float heat_time = m_filpar[m_current_tool].filament_cooling_before_tower / m_hotend_heating_rate[extruder_id];
|
||||
// if (estimate_time < heat_time) speed_factor = estimate_time / heat_time;
|
||||
// }
|
||||
(void) estimate_wipe_time; // retain scaffolding above without an unused-lambda warning
|
||||
}
|
||||
float wipe_speed = nominal_speed * speed_factor;
|
||||
|
||||
// now the wiping itself:
|
||||
for (int i = 0; true; ++i) {
|
||||
if (i != 0) {
|
||||
if (wipe_speed < 0.34f * target_speed)
|
||||
wipe_speed = 0.375f * target_speed;
|
||||
else if (wipe_speed < 0.377 * target_speed)
|
||||
wipe_speed = 0.458f * target_speed;
|
||||
else if (wipe_speed < 0.46f * target_speed)
|
||||
wipe_speed = 0.875f * target_speed;
|
||||
if (nominal_speed < 0.34f * target_speed)
|
||||
nominal_speed = 0.375f * target_speed;
|
||||
else if (nominal_speed < 0.377 * target_speed)
|
||||
nominal_speed = 0.458f * target_speed;
|
||||
else if (nominal_speed < 0.46f * target_speed)
|
||||
nominal_speed = 0.875f * target_speed;
|
||||
else
|
||||
wipe_speed = std::min(target_speed, wipe_speed + 50.f);
|
||||
nominal_speed = std::min(target_speed, nominal_speed + 50.f);
|
||||
wipe_speed = nominal_speed * speed_factor;
|
||||
}
|
||||
|
||||
bool need_change_flow = need_thick_bridge_flow(writer.y());
|
||||
@@ -3453,6 +3589,7 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat
|
||||
} else
|
||||
writer.travel(writer.x() + 1.5 * ironing_length, writer.y(), 240.);
|
||||
writer.retract(-retract_length, retract_speed);
|
||||
add_M104_by_requirement(); // Pre-heat the arriving hotend during the wipe
|
||||
writer.extrude(xr + wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed);
|
||||
} else {
|
||||
float dx = xl - wipe_tower_wall_infill_overlap * m_perimeter_width - writer.pos().x();
|
||||
@@ -3468,9 +3605,11 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat
|
||||
}else
|
||||
writer.travel(writer.x() - 1.5 * ironing_length, writer.y(), 240.);
|
||||
writer.retract(-retract_length, retract_speed);
|
||||
add_M104_by_requirement(); // Pre-heat the arriving hotend during the wipe
|
||||
writer.extrude(xl - wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed);
|
||||
}
|
||||
} else {
|
||||
if (i == 0) add_M104_by_requirement(); // Pre-heat the arriving hotend during the wipe
|
||||
if (m_left_to_right)
|
||||
writer.extrude(xr + wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed);
|
||||
else
|
||||
@@ -3571,6 +3710,47 @@ bool WipeTower::is_in_same_extruder(int filament_id_1, int filament_id_2)
|
||||
return m_filament_map[filament_id_1] == m_filament_map[filament_id_2];
|
||||
}
|
||||
|
||||
// Per-extruder printable-height clamp: is an extruder still allowed to print on this wipe-tower layer,
|
||||
// or is it its final layer above the extruder's printable height?
|
||||
// Orca: the arriving extruder id is resolved as m_filament_map[tool]-1 (1-based map, layer-static),
|
||||
// because Orca's wipe tower is extruder-level rather than tracking a per-layer nozzle map (the same
|
||||
// idiom the pre-heat path uses in toolchange_wipe_new). Gated on m_is_multi_extruder so that
|
||||
// single-extruder printers (including ones whose extruder_printable_height defaults to {0}) always
|
||||
// return true and leave wipe-tower g-code unchanged.
|
||||
bool WipeTower::is_valid_last_layer(int tool, int layer_id, double layer_z) const
|
||||
{
|
||||
if (!m_is_multi_extruder)
|
||||
return true;
|
||||
int extruder_id = (tool >= 0 && tool < (int) m_filament_map.size()) ? m_filament_map[tool] - 1 : -1;
|
||||
if (extruder_id < 0 || extruder_id >= (int) m_printable_height.size() || extruder_id >= (int) m_last_layer_id.size())
|
||||
return true;
|
||||
if (m_last_layer_id[extruder_id] == layer_id && layer_z > m_printable_height[extruder_id])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Records, per extruder, the last wipe-tower layer index that uses it, so is_valid_last_layer can
|
||||
// recognise the extruder's final layer. Inert for single-extruder printers (early return);
|
||||
// bounds-checked because m_filament_map may be empty/short.
|
||||
void WipeTower::set_nozzle_last_layer_id()
|
||||
{
|
||||
if (!m_is_multi_extruder)
|
||||
return;
|
||||
for (int idx = 0; idx < (int) m_plan.size(); ++idx) {
|
||||
const auto &info = m_plan[idx];
|
||||
for (const auto &tc : info.tool_changes) {
|
||||
int old_tool = (int) tc.old_tool;
|
||||
int new_tool = (int) tc.new_tool;
|
||||
int old_ext = (old_tool >= 0 && old_tool < (int) m_filament_map.size()) ? m_filament_map[old_tool] - 1 : -1;
|
||||
int new_ext = (new_tool >= 0 && new_tool < (int) m_filament_map.size()) ? m_filament_map[new_tool] - 1 : -1;
|
||||
if (old_ext >= 0 && old_ext < (int) m_last_layer_id.size())
|
||||
m_last_layer_id[old_ext] = idx;
|
||||
if (new_ext >= 0 && new_ext < (int) m_last_layer_id.size())
|
||||
m_last_layer_id[new_ext] = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WipeTower::reset_block_status()
|
||||
{
|
||||
for (auto &block : m_wipe_tower_blocks) {
|
||||
@@ -3797,6 +3977,7 @@ void WipeTower::plan_tower_new()
|
||||
}
|
||||
|
||||
update_all_layer_depth(max_depth);
|
||||
set_nozzle_last_layer_id(); // record per-extruder last layer for is_valid_last_layer
|
||||
float diagonal = sqrt(m_wipe_tower_depth * m_wipe_tower_depth + m_wipe_tower_width * m_wipe_tower_width);
|
||||
m_rib_length = std::max({m_rib_length, diagonal});
|
||||
m_rib_length += m_extra_rib_length;
|
||||
@@ -3916,9 +4097,12 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
|
||||
int candidate_id = -1;
|
||||
for (size_t idx = 0; idx < layer.tool_changes.size(); ++idx) {
|
||||
if (idx == 0) {
|
||||
if (layer.tool_changes[idx].old_tool == wall_filament_id)
|
||||
// An extruder's last-layer filament above its printable height cannot supply the
|
||||
// outer wall. is_valid_last_layer is inert unless it clamps.
|
||||
if (layer.tool_changes[idx].old_tool == wall_filament_id && is_valid_last_layer(layer.tool_changes[idx].old_tool, m_cur_layer_id, layer.z))
|
||||
return wall_filament_id;
|
||||
else if (m_filpar[layer.tool_changes[idx].old_tool].category == m_filpar[wall_filament_id].category) {
|
||||
else if (m_filpar[layer.tool_changes[idx].old_tool].category == m_filpar[wall_filament_id].category &&
|
||||
is_valid_last_layer(layer.tool_changes[idx].old_tool, m_cur_layer_id, layer.z)) {
|
||||
candidate_id = layer.tool_changes[idx].old_tool;
|
||||
}
|
||||
}
|
||||
@@ -4017,6 +4201,10 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
|
||||
finish_layer_filament = wall_idx;
|
||||
}
|
||||
|
||||
// Cancel a block on the last layer above its extruder's printable height.
|
||||
// is_valid_last_layer is inert unless multi-extruder near Z-max.
|
||||
if (!is_valid_last_layer(finish_layer_filament, m_cur_layer_id, layer.z)) continue;
|
||||
|
||||
ToolChangeResult finish_block_tcr;
|
||||
if (interface_solid || (block.solid_infill[m_cur_layer_id] && block.filament_adhesiveness_category != m_filament_categories[finish_layer_filament])) {
|
||||
interface_solid = interface_solid && !((block.solid_infill[m_cur_layer_id] && block.filament_adhesiveness_category != m_filament_categories[finish_layer_filament]));//noly reduce speed when
|
||||
|
||||
@@ -313,6 +313,13 @@ public:
|
||||
void set_has_tpu_filament(bool has_tpu) { m_has_tpu_filament = has_tpu; }
|
||||
bool has_tpu_filament() const { return m_has_tpu_filament; }
|
||||
|
||||
// Orca: has_filament_switcher is not a static PrintConfig member, so it is pushed in from Print
|
||||
// via a setter rather than read in the ctor. Device-set only.
|
||||
void set_has_filament_switcher(bool v) { m_has_filament_switcher = v; }
|
||||
// The region every extruder can reach, used to clamp the PETG pre-extrusion offset to the
|
||||
// printable bed.
|
||||
void set_shared_print_bed(const Polygons &bed) { m_shared_print_bed = bed; }
|
||||
|
||||
struct FilamentParameters {
|
||||
std::string material = "PLA";
|
||||
int category;
|
||||
@@ -341,8 +348,14 @@ public:
|
||||
float wipe_dist;
|
||||
float tower_interface_pre_extrusion_dist = 0.f;
|
||||
float tower_interface_pre_extrusion_length = 0.f;
|
||||
// Outward shift of the wipe start for a PETG pre-extrusion on filament-switcher devices;
|
||||
// set from filament_tower_interface_pre_extrusion_dist.
|
||||
float petg_pre_extrusion_offset_dist = 0.f;
|
||||
float tower_ironing_area = 4.f;
|
||||
float tower_interface_purge_length = 0.f;
|
||||
// Distance (in mm of filament) that a hotend is allowed to pre-cool before the
|
||||
// tower is reached; drives the prime-tower heating-during-wipe model (multi-nozzle only).
|
||||
float filament_cooling_before_tower = 0.f;
|
||||
};
|
||||
|
||||
|
||||
@@ -462,6 +475,22 @@ private:
|
||||
bool m_adhesion = true;
|
||||
GCodeFlavor m_gcode_flavor;
|
||||
|
||||
// Multi-nozzle prime-tower heating during wipe. m_is_multiple_nozzle gates the whole
|
||||
// feature; it is false for every current (single-nozzle) printer (extruder_max_nozzle_count
|
||||
// defaults to 1), so the pre-heat/pre-cool path is inert and wipe-tower g-code is unchanged.
|
||||
bool m_is_multiple_nozzle = false;
|
||||
std::vector<double> m_hotend_heating_rate; // config.hotend_heating_rate (deg/s per extruder)
|
||||
std::vector<int> m_physical_extruder_map; // logical extruder -> physical tool number (M104 T param)
|
||||
|
||||
// Per-extruder printable-height clamp. m_printable_height = config.extruder_printable_height
|
||||
// (per-extruder Z limit; empty for single-extruder printers, [320,325] for H2D). m_last_layer_id
|
||||
// records, per extruder, the last wipe-tower layer that uses it. is_valid_last_layer() is gated on
|
||||
// m_is_multi_extruder so single-extruder wipe-tower g-code is unchanged; the clamp only bites a
|
||||
// multi-extruder wipe tower whose final per-extruder layer exceeds that extruder's printable
|
||||
// height (near the Z limit).
|
||||
std::vector<double> m_printable_height;
|
||||
std::vector<int> m_last_layer_id;
|
||||
|
||||
// Bed properties
|
||||
enum {
|
||||
RectangularBed,
|
||||
@@ -501,6 +530,11 @@ private:
|
||||
bool m_flat_ironing=false;
|
||||
bool m_enable_tower_interface_features=false;
|
||||
bool m_enable_tower_interface_cooldown_during_tower=false;
|
||||
// Filament-switcher device flag + shared printable bed for the PETG pre-extrusion offset.
|
||||
// m_has_filament_switcher is false for the whole shipping fleet (no profile sets the key), so
|
||||
// the PETG branch in get_next_pos never runs -> no change fleet-wide.
|
||||
bool m_has_filament_switcher=false;
|
||||
Polygons m_shared_print_bed;
|
||||
bool m_prev_layer_had_interface=false;
|
||||
bool m_current_layer_has_interface=false;
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
@@ -520,6 +554,7 @@ private:
|
||||
void save_on_last_wipe();
|
||||
|
||||
bool is_tpu_filament(int filament_id) const;
|
||||
bool is_petg_filament(int filament_id) const;
|
||||
|
||||
// BBS
|
||||
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
|
||||
@@ -586,6 +621,12 @@ private:
|
||||
const box_coordinates &cleaning_box,
|
||||
float wipe_volume);
|
||||
void get_wall_skip_points(const WipeTowerInfo &layer);
|
||||
|
||||
// Per-extruder printable-height clamp (see m_printable_height). is_valid_last_layer returns
|
||||
// false only for a multi-extruder wipe tower's final per-extruder layer that exceeds that
|
||||
// extruder's printable height; returns true (no clamp) in every other case.
|
||||
bool is_valid_last_layer(int tool, int layer_id, double layer_z) const;
|
||||
void set_nozzle_last_layer_id();
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1333,6 +1333,10 @@ void WipeTower2::set_extruder(size_t idx, const PrintConfig& config)
|
||||
//while (m_filpar.size() < idx+1) // makes sure the required element is in the vector
|
||||
m_filpar.push_back(FilamentParameters());
|
||||
|
||||
// Orca: one row per filament, indexed by the raw filament id. Under a per-layer nozzle
|
||||
// grouping the per-variant arrays may hold several columns per filament; the tower has no
|
||||
// layer dimension here, so it keeps the filament's first column (tower x per-layer
|
||||
// grouping is a documented follow-up).
|
||||
m_filpar[idx].material = config.filament_type.get_at(idx);
|
||||
if (m_wipe_tower_filament > 0)
|
||||
m_filpar[idx].is_soluble = (idx != size_t(m_wipe_tower_filament - 1));
|
||||
|
||||
@@ -595,23 +595,15 @@ std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, boo
|
||||
|
||||
std::string GCodeWriter::toolchange_prefix() const
|
||||
{
|
||||
std::string gcode = "T";
|
||||
// Orca: the manual-filament-change tag must stay ahead of the flavor selection so
|
||||
// MMU manual-change handling keeps working.
|
||||
if (config.manual_filament_change)
|
||||
gcode = ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Manual_Tool_Change) + "T";
|
||||
else {
|
||||
if (m_is_bbl_printers)
|
||||
gcode = "M1020 S";
|
||||
else {
|
||||
if (FLAVOR_IS(gcfMakerWare))
|
||||
gcode = "M135 T";
|
||||
else if (FLAVOR_IS(gcfSailfish))
|
||||
gcode = "M108 T";
|
||||
}
|
||||
}
|
||||
return gcode;
|
||||
return ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Manual_Tool_Change) + "T";
|
||||
return FLAVOR_IS(gcfMakerWare) ? "M135 T" :
|
||||
FLAVOR_IS(gcfSailfish) ? "M108 T" : "T";
|
||||
}
|
||||
|
||||
std::string GCodeWriter::toolchange(unsigned int filament_id)
|
||||
std::string GCodeWriter::toolchange(unsigned int filament_id, int nozzle_id)
|
||||
{
|
||||
// set the new extruder
|
||||
auto filament_extruder_iter = Slic3r::lower_bound_by_predicate(m_filament_extruders.begin(), m_filament_extruders.end(), [filament_id](const Extruder &e) { return e.id() < filament_id; });
|
||||
@@ -623,9 +615,16 @@ std::string GCodeWriter::toolchange(unsigned int filament_id)
|
||||
// return the toolchange command
|
||||
// if we are running a single-extruder setup, just set the extruder and return nothing
|
||||
std::ostringstream gcode;
|
||||
// Orca: also emit for non-BBL single-extruder multi-filament setups (MMU-style).
|
||||
if (this->multiple_extruders || (this->config.filament_diameter.values.size() > 1 && !is_bbl_printers())) {
|
||||
// Orca: call toolchange_prefix() to get the correct command prefix based on the configuration and flavor.
|
||||
gcode << this->toolchange_prefix() << filament_id;
|
||||
// Orca: manual filament change keeps its tag line even on BBL machines, so the
|
||||
// M1020 form must not shadow it. nozzle_id is signed: the null-safe nozzle
|
||||
// lookup legitimately yields -1 ("no specific nozzle"), matching the literal
|
||||
// H-1 the stock change templates emit; an unsigned would wrap.
|
||||
if (m_is_bbl_printers && !config.manual_filament_change)
|
||||
gcode << "M1020 S" << filament_id << " H" << nozzle_id;
|
||||
else
|
||||
gcode << this->toolchange_prefix() << filament_id;
|
||||
if (GCodeWriter::full_gcode_comment)
|
||||
gcode << " ; change extruder";
|
||||
gcode << "\n";
|
||||
@@ -634,6 +633,25 @@ std::string GCodeWriter::toolchange(unsigned int filament_id)
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
// Current parked-retract length of the filament's extruder, share-aware. m_filament_extruders is
|
||||
// sorted by id (see toolchange), so a lower_bound lookup finds the entry; unknown filament ids
|
||||
// degrade to 0 rather than dereferencing end().
|
||||
double GCodeWriter::get_extruder_retracted_length(const int filament_id)
|
||||
{
|
||||
double res = 0.0;
|
||||
auto filament_extruder_iter = Slic3r::lower_bound_by_predicate(m_filament_extruders.begin(), m_filament_extruders.end(),
|
||||
[filament_id](const Extruder &e) { return (int) e.id() < filament_id; });
|
||||
if (filament_extruder_iter == m_filament_extruders.end() || (int) filament_extruder_iter->id() != filament_id)
|
||||
return res;
|
||||
|
||||
if (filament_extruder_iter->is_share_extruder())
|
||||
res = filament_extruder_iter->get_share_retracted_length();
|
||||
else
|
||||
res = filament_extruder_iter->get_single_retracted_length();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker)
|
||||
{
|
||||
assert(F > 0.);
|
||||
@@ -1103,7 +1121,7 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std
|
||||
return gcode;
|
||||
}
|
||||
|
||||
std::string GCodeWriter::unretract()
|
||||
std::string GCodeWriter::unretract(float extra_retract)
|
||||
{
|
||||
std::string gcode;
|
||||
|
||||
@@ -1119,7 +1137,9 @@ std::string GCodeWriter::unretract()
|
||||
//BBS
|
||||
// use G1 instead of G0 because G0 will blend the restart with the previous travel move
|
||||
GCodeG1Formatter w;
|
||||
w.emit_e(filament()->E());
|
||||
// extra_retract over-extrudes for the PETG pre-extrusion; 0 by
|
||||
// default -> identical to the plain deretract E position.
|
||||
w.emit_e(filament()->E() + extra_retract);
|
||||
w.emit_f(filament()->deretract_speed() * 60.);
|
||||
//BBS
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, " ; unretract");
|
||||
@@ -1252,8 +1272,9 @@ std::string GCodeWriter::set_extruder(unsigned int filament_id)
|
||||
auto filament_ext_it = Slic3r::lower_bound_by_predicate(m_filament_extruders.begin(), m_filament_extruders.end(), [filament_id](const Extruder &e) { return e.id() < filament_id; });
|
||||
unsigned int extruder_id = filament_ext_it->extruder_id();
|
||||
assert(filament_ext_it != m_filament_extruders.end() && filament_ext_it->id() == filament_id);
|
||||
//TODO: optmize here, pass extruder_id to toolchange
|
||||
return this->need_toolchange(filament_id) ? this->toolchange(filament_id) : "";
|
||||
// Orca: writer-only context (calibration paths) has no nozzle grouping; the
|
||||
// filament's own extruder id is the correct degenerate nozzle value.
|
||||
return this->need_toolchange(filament_id) ? this->toolchange(filament_id, (int) extruder_id) : "";
|
||||
}
|
||||
|
||||
void GCodeWriter::init_extruder(unsigned int filament_id)
|
||||
|
||||
@@ -67,10 +67,13 @@ public:
|
||||
bool need_toolchange(unsigned int filament_id) const;
|
||||
std::string set_extruder(unsigned int filament_id);
|
||||
void init_extruder(unsigned int filament_id);
|
||||
// Current parked-retract length of a filament's extruder (share-aware). Used for the
|
||||
// new_extruder_retracted_length change-filament placeholder. Returns 0 if the filament is unknown.
|
||||
double get_extruder_retracted_length(const int filament_id);
|
||||
// Prefix of the toolchange G-code line, to be used by the CoolingBuffer to separate sections of the G-code
|
||||
// printed with the same extruder.
|
||||
std::string toolchange_prefix() const;
|
||||
std::string toolchange(unsigned int filament_id);
|
||||
std::string toolchange(unsigned int filament_id, int nozzle_id);
|
||||
std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string());
|
||||
// SoftFever NOTE: the returned speed is mm/minute
|
||||
double get_current_speed() const { return m_current_speed;}
|
||||
@@ -84,7 +87,9 @@ public:
|
||||
std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false);
|
||||
std::string retract(bool before_wipe = false, double retract_length = 0);
|
||||
std::string retract_for_toolchange(bool before_wipe = false, double retract_length = 0);
|
||||
std::string unretract();
|
||||
// extra_retract adds a small over-extrusion to the deretract move (PETG pre-extrusion).
|
||||
// Default 0 -> byte-identical to the plain deretract.
|
||||
std::string unretract(float extra_retract = 0.f);
|
||||
// do lift instantly
|
||||
std::string eager_lift(const LiftType type);
|
||||
// record a lift request, do realy lift in next travel
|
||||
|
||||
@@ -0,0 +1,970 @@
|
||||
#include "MultiNozzleUtils.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "ProjectTask.hpp" // Slic3r::FilamentInfo (StaticNozzleGroupResult / load_nozzle_infos_with_compatibility)
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
// Multi-nozzle support.
|
||||
|
||||
namespace Slic3r { namespace MultiNozzleUtils {
|
||||
// ==================== tool function implementations ====================
|
||||
std::vector<NozzleInfo> build_nozzle_list(std::vector<NozzleGroupInfo> nozzle_groups)
|
||||
{
|
||||
std::vector<NozzleInfo> ret;
|
||||
std::sort(nozzle_groups.begin(), nozzle_groups.end());
|
||||
int nozzle_id = 0;
|
||||
for (auto& group : nozzle_groups) {
|
||||
for (int i = 0; i < group.nozzle_count; ++i) {
|
||||
NozzleInfo tmp;
|
||||
tmp.diameter = group.diameter;
|
||||
tmp.extruder_id = group.extruder_id;
|
||||
tmp.volume_type = group.volume_type;
|
||||
tmp.group_id = nozzle_id++;
|
||||
ret.emplace_back(std::move(tmp));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> build_nozzle_list(double diameter, const std::vector<int>& filament_nozzle_map, const std::vector<int>& filament_volume_map, const std::vector<int>& filament_map)
|
||||
{
|
||||
std::string diameter_str = format_diameter_to_str(diameter);
|
||||
std::map<int, std::vector<int>> nozzle_to_filaments;
|
||||
for(size_t idx = 0; idx < filament_nozzle_map.size(); ++idx){
|
||||
int nozzle_id = filament_nozzle_map[idx];
|
||||
nozzle_to_filaments[nozzle_id].emplace_back(static_cast<int>(idx));
|
||||
}
|
||||
std::vector<NozzleInfo> ret;
|
||||
for(auto& elem : nozzle_to_filaments){
|
||||
int nozzle_id = elem.first;
|
||||
auto& filaments = elem.second;
|
||||
NozzleInfo info;
|
||||
info.diameter = diameter_str;
|
||||
info.group_id = nozzle_id;
|
||||
info.extruder_id = filament_map[filaments.front()];
|
||||
info.volume_type = NozzleVolumeType(filament_volume_map[filaments.front()]);
|
||||
ret.emplace_back(std::move(info));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void normalize_nozzle_map_per_layer(std::vector<std::vector<int>> &layer_filament_nozzle_maps,
|
||||
const std::vector<std::vector<unsigned int>> &layer_filaments)
|
||||
{
|
||||
if (layer_filament_nozzle_maps.empty())
|
||||
return;
|
||||
|
||||
const int total_layers = static_cast<int>(layer_filament_nozzle_maps.size());
|
||||
int filament_count = 0;
|
||||
for (const auto &layer_map : layer_filament_nozzle_maps)
|
||||
filament_count = std::max(filament_count, static_cast<int>(layer_map.size()));
|
||||
|
||||
auto layer_uses_filament = [](const std::vector<unsigned int> &filaments, int filament_id) {
|
||||
return std::find(filaments.begin(), filaments.end(), static_cast<unsigned int>(filament_id)) != filaments.end();
|
||||
};
|
||||
|
||||
std::vector<int> last_used_nozzle(filament_count, -1);
|
||||
std::unordered_map<int, int> first_used_nozzle;
|
||||
std::unordered_map<int, int> first_used_layer;
|
||||
|
||||
// Forward pass: layers that extrude a filament define its nozzle; layers that don't inherit
|
||||
// the nozzle it last used (carry-forward), remembering the first-ever nozzle for the back-fill.
|
||||
for (int layer_id = 0; layer_id < total_layers; ++layer_id) {
|
||||
auto &layer_map = layer_filament_nozzle_maps[layer_id];
|
||||
const auto &used = layer_id < static_cast<int>(layer_filaments.size()) ? layer_filaments[layer_id] : std::vector<unsigned int>();
|
||||
|
||||
for (int filament_id = 0; filament_id < static_cast<int>(layer_map.size()); ++filament_id) {
|
||||
if (layer_uses_filament(used, filament_id)) {
|
||||
last_used_nozzle[filament_id] = layer_map[filament_id];
|
||||
if (first_used_nozzle.count(filament_id) == 0) {
|
||||
first_used_nozzle[filament_id] = layer_map[filament_id];
|
||||
first_used_layer[filament_id] = layer_id;
|
||||
}
|
||||
} else if (last_used_nozzle[filament_id] >= 0) {
|
||||
layer_map[filament_id] = last_used_nozzle[filament_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Back-fill pass: layers before a filament's first use inherit the first nozzle it ever uses.
|
||||
for (int layer_id = 0; layer_id < total_layers; ++layer_id) {
|
||||
auto &layer_map = layer_filament_nozzle_maps[layer_id];
|
||||
for (int filament_id = 0; filament_id < static_cast<int>(layer_map.size()); ++filament_id) {
|
||||
if (first_used_layer.count(filament_id) != 0 && layer_id < first_used_layer[filament_id])
|
||||
layer_map[filament_id] = first_used_nozzle[filament_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== LayeredNozzleGroupResult ====================
|
||||
static bool has_filament_mapped_to_multiple_nozzles(const std::vector<std::vector<int>> &layer_filament_nozzle_maps,
|
||||
const std::vector<unsigned int> &used_filaments)
|
||||
{
|
||||
if (layer_filament_nozzle_maps.empty() || used_filaments.empty())
|
||||
return false;
|
||||
|
||||
for (auto filament_id_u : used_filaments) {
|
||||
int filament_id = static_cast<int>(filament_id_u);
|
||||
std::set<int> nozzle_ids;
|
||||
|
||||
for (size_t layer_id = 0; layer_id < layer_filament_nozzle_maps.size(); ++layer_id) {
|
||||
const auto &map = layer_filament_nozzle_maps[layer_id];
|
||||
if (filament_id < 0 || filament_id >= static_cast<int>(map.size()))
|
||||
continue;
|
||||
|
||||
int nozzle_id = map[filament_id];
|
||||
if (nozzle_id < 0)
|
||||
continue;
|
||||
|
||||
nozzle_ids.insert(nozzle_id);
|
||||
if (nozzle_ids.size() > 1)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<LayeredNozzleGroupResult> LayeredNozzleGroupResult::create(
|
||||
const std::vector<int>& filament_nozzle_map,
|
||||
const std::vector<NozzleInfo>& nozzle_list,
|
||||
const std::vector<unsigned int>& used_filaments)
|
||||
{
|
||||
if (filament_nozzle_map.empty() || nozzle_list.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
LayeredNozzleGroupResult result(false);
|
||||
result._default_filament_nozzle_map = filament_nozzle_map;
|
||||
result._nozzle_list = nozzle_list;
|
||||
result._used_filaments = used_filaments;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<LayeredNozzleGroupResult> LayeredNozzleGroupResult::create(
|
||||
const std::vector<std::vector<int>>& layer_filament_nozzle_maps,
|
||||
const std::vector<NozzleInfo>& nozzle_list,
|
||||
const std::vector<unsigned int>& used_filaments,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filament_sequences)
|
||||
{
|
||||
if (layer_filament_nozzle_maps.empty() || nozzle_list.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool support_dynamic_nozzle_map = has_filament_mapped_to_multiple_nozzles(layer_filament_nozzle_maps, used_filaments);
|
||||
LayeredNozzleGroupResult result(support_dynamic_nozzle_map);
|
||||
result._layer_filament_nozzle_maps = layer_filament_nozzle_maps;
|
||||
result._layer_filament_sequences = layer_filament_sequences;
|
||||
result._nozzle_list = nozzle_list;
|
||||
result._used_filaments = used_filaments;
|
||||
|
||||
if (!layer_filament_nozzle_maps.empty()) {
|
||||
result._default_filament_nozzle_map = layer_filament_nozzle_maps[0];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<LayeredNozzleGroupResult> LayeredNozzleGroupResult::create(
|
||||
const std::vector<unsigned int>& used_filaments,
|
||||
const std::vector<int>& filament_map,
|
||||
const std::vector<int>& filament_volume_map,
|
||||
const std::vector<int>& filament_nozzle_map,
|
||||
const std::vector<std::map<NozzleVolumeType, int>> &nozzle_count,
|
||||
float diameter)
|
||||
{
|
||||
std::vector<NozzleGroupInfo> nozzle_groups;
|
||||
for (size_t extruder_id = 0; extruder_id < nozzle_count.size(); ++extruder_id) {
|
||||
for (auto elem : nozzle_count[extruder_id]) {
|
||||
NozzleGroupInfo group_info;
|
||||
group_info.diameter = format_diameter_to_str(diameter);
|
||||
group_info.volume_type = elem.first;
|
||||
group_info.nozzle_count = elem.second;
|
||||
group_info.extruder_id = static_cast<int>(extruder_id);
|
||||
nozzle_groups.emplace_back(group_info);
|
||||
}
|
||||
}
|
||||
|
||||
auto nozzle_list = build_nozzle_list(nozzle_groups);
|
||||
std::vector<bool> used_nozzle(nozzle_list.size(), false);
|
||||
std::map<int, int> input_nozzle_id_to_output;
|
||||
std::vector<int> output_nozzle_map(filament_nozzle_map.size(), 0);
|
||||
|
||||
for (auto filament_idx : used_filaments) {
|
||||
NozzleVolumeType req_type = NozzleVolumeType(filament_volume_map[filament_idx]);
|
||||
int req_extruder = filament_map[filament_idx];
|
||||
int input_nozzle_idx = filament_nozzle_map[filament_idx];
|
||||
|
||||
if (input_nozzle_id_to_output.find(input_nozzle_idx) != input_nozzle_id_to_output.end()) {
|
||||
output_nozzle_map[filament_idx] = input_nozzle_id_to_output[input_nozzle_idx];
|
||||
continue;
|
||||
}
|
||||
|
||||
int output_nozzle_idx = -1;
|
||||
for (size_t nozzle_idx = 0; nozzle_idx < nozzle_list.size(); ++nozzle_idx) {
|
||||
if (used_nozzle[nozzle_idx]) continue;
|
||||
|
||||
auto &nozzle_info = nozzle_list[nozzle_idx];
|
||||
if (!(nozzle_info.extruder_id == req_extruder && nozzle_info.volume_type == req_type)) continue;
|
||||
|
||||
output_nozzle_idx = static_cast<int>(nozzle_idx);
|
||||
input_nozzle_id_to_output[input_nozzle_idx] = output_nozzle_idx;
|
||||
used_nozzle[nozzle_idx] = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (output_nozzle_idx == -1) { return std::nullopt; }
|
||||
output_nozzle_map[filament_idx] = output_nozzle_idx;
|
||||
}
|
||||
|
||||
return create(output_nozzle_map, nozzle_list, used_filaments);
|
||||
}
|
||||
|
||||
bool LayeredNozzleGroupResult::are_filaments_same_extruder(int filament_id1, int filament_id2, int layer_id) const
|
||||
{
|
||||
std::optional<NozzleInfo> nozzle_info1 = get_nozzle_for_filament(filament_id1, layer_id);
|
||||
std::optional<NozzleInfo> nozzle_info2 = get_nozzle_for_filament(filament_id2, layer_id);
|
||||
|
||||
if (!nozzle_info1 || !nozzle_info2) return false;
|
||||
|
||||
return nozzle_info1->extruder_id == nozzle_info2->extruder_id;
|
||||
}
|
||||
|
||||
bool LayeredNozzleGroupResult::are_filaments_same_nozzle(int filament_id1, int filament_id2, int layer_id) const
|
||||
{
|
||||
std::optional<NozzleInfo> nozzle_info1 = get_nozzle_for_filament(filament_id1, layer_id);
|
||||
std::optional<NozzleInfo> nozzle_info2 = get_nozzle_for_filament(filament_id2, layer_id);
|
||||
if (!nozzle_info1 || !nozzle_info2) return false;
|
||||
|
||||
return nozzle_info1->group_id == nozzle_info2->group_id;
|
||||
}
|
||||
|
||||
int LayeredNozzleGroupResult::get_extruder_count() const
|
||||
{
|
||||
std::set<int> extruder_ids;
|
||||
for (const auto &nozzle : _nozzle_list) { extruder_ids.insert(nozzle.extruder_id); }
|
||||
return static_cast<int>(extruder_ids.size());
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> LayeredNozzleGroupResult::get_used_nozzles_in_extruder(int target_extruder_id) const
|
||||
{
|
||||
return get_used_nozzles_in_extruder(target_extruder_id, -1);
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> LayeredNozzleGroupResult::get_used_nozzles_in_extruder(int target_extruder_id, int layer_id) const
|
||||
{
|
||||
std::set<int> nozzle_ids;
|
||||
std::vector<NozzleInfo> result;
|
||||
|
||||
std::vector<unsigned int> target_filaments = get_used_filaments(layer_id);
|
||||
|
||||
for (unsigned int filament_id : target_filaments) {
|
||||
if (layer_id != -1) {
|
||||
auto nozzle_opt = get_nozzle_for_filament(static_cast<int>(filament_id), layer_id);
|
||||
if (nozzle_opt) {
|
||||
if (target_extruder_id == -1 || nozzle_opt->extruder_id == target_extruder_id) { nozzle_ids.insert(nozzle_opt->group_id); }
|
||||
}
|
||||
} else {
|
||||
auto nozzles = get_nozzles_for_filament(static_cast<int>(filament_id));
|
||||
for (const auto &nozzle : nozzles) {
|
||||
if (target_extruder_id == -1 || nozzle.extruder_id == target_extruder_id) { nozzle_ids.insert(nozzle.group_id); }
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int nozzle_id : nozzle_ids) {
|
||||
if (nozzle_id >= 0 && nozzle_id < static_cast<int>(_nozzle_list.size())) { result.push_back(_nozzle_list[nozzle_id]); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<int> LayeredNozzleGroupResult::get_used_extruders() const
|
||||
{
|
||||
return get_used_extruders(-1);
|
||||
}
|
||||
|
||||
std::vector<int> LayeredNozzleGroupResult::get_used_extruders(int layer_id) const
|
||||
{
|
||||
std::set<int> used_extruders;
|
||||
// used filaments on the given layer (or globally)
|
||||
std::vector<unsigned int> target_filaments = get_used_filaments(layer_id);
|
||||
for (auto filament_id : target_filaments) {
|
||||
if (layer_id != -1) {
|
||||
// single-layer: nozzle used by this filament on this layer
|
||||
auto nozzle_opt = get_nozzle_for_filament(static_cast<int>(filament_id), layer_id);
|
||||
if (nozzle_opt) { used_extruders.insert(nozzle_opt->extruder_id); }
|
||||
} else {
|
||||
// global: every nozzle this filament uses across all layers
|
||||
auto nozzles = get_nozzles_for_filament(static_cast<int>(filament_id));
|
||||
for (const auto &nozzle : nozzles) { used_extruders.insert(nozzle.extruder_id); }
|
||||
}
|
||||
}
|
||||
return std::vector<int>(used_extruders.begin(), used_extruders.end());
|
||||
}
|
||||
|
||||
std::vector<int> LayeredNozzleGroupResult::get_extruder_map(bool zero_based, int layer_id) const
|
||||
{
|
||||
const std::vector<int> &filament_nozzle_map = get_layer_filament_nozzle_map(layer_id);
|
||||
std::vector<int> extruder_map(filament_nozzle_map.size());
|
||||
for (size_t idx = 0; idx < filament_nozzle_map.size(); ++idx) {
|
||||
int nozzle_id = filament_nozzle_map[idx];
|
||||
if (nozzle_id >= 0 && nozzle_id < static_cast<int>(_nozzle_list.size())) {
|
||||
extruder_map[idx] = _nozzle_list[nozzle_id].extruder_id;
|
||||
} else {
|
||||
extruder_map[idx] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (zero_based) return extruder_map;
|
||||
|
||||
auto new_filament_map = extruder_map;
|
||||
std::transform(new_filament_map.begin(), new_filament_map.end(), new_filament_map.begin(), [](int val) { return val + 1; });
|
||||
return new_filament_map;
|
||||
}
|
||||
|
||||
std::vector<int> LayeredNozzleGroupResult::get_nozzle_map(int layer_id) const
|
||||
{
|
||||
const std::vector<int> &filament_nozzle_map = get_layer_filament_nozzle_map(layer_id);
|
||||
std::vector<int> nozzle_map(filament_nozzle_map.size());
|
||||
for (size_t idx = 0; idx < filament_nozzle_map.size(); ++idx) {
|
||||
int nozzle_id = filament_nozzle_map[idx];
|
||||
if (nozzle_id >= 0 && nozzle_id < static_cast<int>(_nozzle_list.size())) {
|
||||
nozzle_map[idx] = _nozzle_list[nozzle_id].group_id;
|
||||
} else {
|
||||
nozzle_map[idx] = -1;
|
||||
}
|
||||
}
|
||||
return nozzle_map;
|
||||
}
|
||||
|
||||
std::vector<int> LayeredNozzleGroupResult::get_volume_map(int layer_id) const
|
||||
{
|
||||
const std::vector<int> &filament_nozzle_map = get_layer_filament_nozzle_map(layer_id);
|
||||
std::vector<int> volume_map(filament_nozzle_map.size());
|
||||
for (size_t idx = 0; idx < filament_nozzle_map.size(); ++idx) {
|
||||
int nozzle_id = filament_nozzle_map[idx];
|
||||
if (nozzle_id >= 0 && nozzle_id < static_cast<int>(_nozzle_list.size())) {
|
||||
volume_map[idx] = _nozzle_list[nozzle_id].volume_type;
|
||||
} else {
|
||||
volume_map[idx] = -1;
|
||||
}
|
||||
}
|
||||
return volume_map;
|
||||
}
|
||||
|
||||
std::vector<unsigned int> LayeredNozzleGroupResult::get_used_filaments(int layer_id) const
|
||||
{
|
||||
if (layer_id < 0) { return _used_filaments; }
|
||||
if (layer_id >= static_cast<int>(_layer_filament_nozzle_maps.size())) { return _used_filaments; }
|
||||
|
||||
if (!_layer_filament_sequences.empty() && layer_id < static_cast<int>(_layer_filament_sequences.size())) {
|
||||
return _layer_filament_sequences[layer_id];
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<NozzleInfo> LayeredNozzleGroupResult::get_nozzle_for_filament(int filament_id, int layer_id) const
|
||||
{
|
||||
const std::vector<int> &filament_nozzle_map = get_layer_filament_nozzle_map(layer_id);
|
||||
|
||||
if (filament_id < 0 || filament_id >= static_cast<int>(filament_nozzle_map.size())) { return std::nullopt; }
|
||||
|
||||
int nozzle_id = filament_nozzle_map[filament_id];
|
||||
return get_nozzle_from_id(nozzle_id);
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> LayeredNozzleGroupResult::get_nozzles_for_filament(int filament_id) const
|
||||
{
|
||||
std::set<int> nozzle_ids;
|
||||
|
||||
if (!support_dynamic_nozzle_map) {
|
||||
if (filament_id >= 0 && filament_id < static_cast<int>(_default_filament_nozzle_map.size())) {
|
||||
nozzle_ids.insert(_default_filament_nozzle_map[filament_id]);
|
||||
}
|
||||
} else {
|
||||
int start_layer = 0;
|
||||
int end_layer = static_cast<int>(_layer_filament_nozzle_maps.size());
|
||||
|
||||
for (int i = start_layer; i < end_layer; ++i) {
|
||||
const auto &map = _layer_filament_nozzle_maps[i];
|
||||
if (filament_id >= 0 && filament_id < static_cast<int>(map.size())) {
|
||||
nozzle_ids.insert(map[filament_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> result;
|
||||
for (int id : nozzle_ids) {
|
||||
if (id >= 0 && id < static_cast<int>(_nozzle_list.size())) { result.push_back(_nozzle_list[id]); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<NozzleInfo> LayeredNozzleGroupResult::get_first_nozzle_for_filament(int filament_id) const
|
||||
{
|
||||
if (filament_id < 0) return std::nullopt;
|
||||
|
||||
if (!support_dynamic_nozzle_map) {
|
||||
if (filament_id >= static_cast<int>(_default_filament_nozzle_map.size())) return std::nullopt;
|
||||
return get_nozzle_from_id(_default_filament_nozzle_map[filament_id]);
|
||||
}
|
||||
|
||||
for (size_t layer = 0; layer < _layer_filament_nozzle_maps.size(); ++layer) {
|
||||
auto layer_used_filaments = get_used_filaments(layer);
|
||||
if (std::find(layer_used_filaments.begin(), layer_used_filaments.end(), static_cast<unsigned int>(filament_id)) == layer_used_filaments.end()){
|
||||
continue;
|
||||
}
|
||||
const auto &map = _layer_filament_nozzle_maps[layer];
|
||||
if (filament_id >= 0 && filament_id < static_cast<int>(map.size())) {
|
||||
int nozzle_id = map[filament_id];
|
||||
auto nozzle = get_nozzle_from_id(nozzle_id);
|
||||
if (nozzle) return nozzle;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<NozzleInfo> LayeredNozzleGroupResult::get_nozzle_from_id(int nozzle_id) const
|
||||
{
|
||||
if (nozzle_id < 0 || nozzle_id >= static_cast<int>(_nozzle_list.size())) { return std::nullopt; }
|
||||
return _nozzle_list[nozzle_id];
|
||||
}
|
||||
|
||||
int LayeredNozzleGroupResult::get_extruder_id(int filament_id, int layer_id) const
|
||||
{
|
||||
auto nozzle_info = get_nozzle_for_filament(filament_id, layer_id);
|
||||
return nozzle_info ? nozzle_info->extruder_id : -1;
|
||||
}
|
||||
|
||||
int LayeredNozzleGroupResult::get_nozzle_id(int filament_id, int layer_id) const
|
||||
{
|
||||
auto nozzle_info = get_nozzle_for_filament(filament_id, layer_id);
|
||||
return nozzle_info ? nozzle_info->group_id : -1;
|
||||
}
|
||||
|
||||
const std::vector<int> &LayeredNozzleGroupResult::get_layer_filament_nozzle_map(int layer_id) const
|
||||
{
|
||||
if (layer_id >= 0 && layer_id < static_cast<int>(_layer_filament_nozzle_maps.size())) { return _layer_filament_nozzle_maps[layer_id]; }
|
||||
return _default_filament_nozzle_map;
|
||||
}
|
||||
|
||||
// ==================== filament-change-time model ====================
|
||||
FilamentChangeSimResult simulate_filament_change_time(
|
||||
const std::vector<int>& logical_filaments,
|
||||
const std::vector<NozzleInfo>& nozzle_list,
|
||||
const std::vector<int>& filament_change_seq,
|
||||
const std::vector<int>& nozzle_change_seq,
|
||||
const std::vector<int>& group_of_filament,
|
||||
const FilamentChangeTimeParams& time_params,
|
||||
const std::vector<bool>& ams_preload_enabled,
|
||||
bool calc_sliced_time)
|
||||
{
|
||||
FilamentChangeSimResult result;
|
||||
if (logical_filaments.empty() || nozzle_list.empty() || filament_change_seq.empty() || nozzle_change_seq.empty())
|
||||
return result;
|
||||
|
||||
// Re-map the parameter semantics:
|
||||
// standard = AMS -> selector -> extruder (full path), selector = selector -> extruder (short path)
|
||||
// so AMS -> selector = standard - selector
|
||||
const float load_ams_to_selector = time_params.standard_load_time - time_params.selector_load_time;
|
||||
const float unload_ams_to_selector = time_params.standard_unload_time - time_params.selector_unload_time;
|
||||
const float load_selector_to_ext = time_params.selector_load_time;
|
||||
const float unload_ext_to_selector = time_params.selector_unload_time;
|
||||
|
||||
// nozzle_id -> extruder_id
|
||||
std::unordered_map<int, int> nozzle_to_extruder;
|
||||
nozzle_to_extruder.reserve(nozzle_list.size());
|
||||
for (const auto& nozzle : nozzle_list)
|
||||
nozzle_to_extruder[nozzle.group_id] = nozzle.extruder_id;
|
||||
|
||||
// filament_id -> AMS group
|
||||
std::unordered_map<int, int> filament_to_group;
|
||||
filament_to_group.reserve(logical_filaments.size());
|
||||
for (size_t i = 0; i < logical_filaments.size(); ++i)
|
||||
filament_to_group[logical_filaments[i]] = group_of_filament[i];
|
||||
|
||||
const auto get_group = [&](int filament_id) -> int {
|
||||
auto it = filament_to_group.find(filament_id);
|
||||
return it != filament_to_group.end() ? it->second : -1;
|
||||
};
|
||||
|
||||
const auto is_preload_enabled = [&](int group_id) -> bool {
|
||||
if (group_id < 0 || group_id >= static_cast<int>(ams_preload_enabled.size()))
|
||||
return false;
|
||||
return ams_preload_enabled[group_id];
|
||||
};
|
||||
|
||||
// Filament location states
|
||||
enum class Location { IN_AMS, IN_SELECTOR, IN_EXTRUDER };
|
||||
std::unordered_map<int, Location> filament_location; // filament_id -> current location
|
||||
std::unordered_map<int, int> filament_extruder; // filament_id -> extruder it sits in (only valid when IN_EXTRUDER)
|
||||
std::unordered_map<int, int> extruder_filament; // extruder_id -> currently loaded filament
|
||||
// group_id -> filaments currently occupying that AMS channel (IN_SELECTOR or IN_EXTRUDER)
|
||||
std::unordered_map<int, std::unordered_set<int>> ams_group_occupied;
|
||||
|
||||
filament_location.reserve(logical_filaments.size());
|
||||
filament_extruder.reserve(logical_filaments.size());
|
||||
|
||||
// Initial state: every filament is in the AMS, every extruder is empty
|
||||
for (int f : logical_filaments)
|
||||
filament_location[f] = Location::IN_AMS;
|
||||
|
||||
// Slicer-estimate simulator: use NozzleStatusRecorder to track what each nozzle/extruder holds during slicing
|
||||
NozzleStatusRecorder sliced_recorder;
|
||||
|
||||
const size_t seq_len = std::min(filament_change_seq.size(), nozzle_change_seq.size());
|
||||
double actual_time = 0.0;
|
||||
double sliced_time = 0.0;
|
||||
|
||||
for (size_t i = 0; i < seq_len; ++i) {
|
||||
int B = filament_change_seq[i];
|
||||
int nozzle_id = nozzle_change_seq[i];
|
||||
|
||||
auto nozzle_iter = nozzle_to_extruder.find(nozzle_id);
|
||||
if (nozzle_iter == nozzle_to_extruder.end()) continue;
|
||||
|
||||
int E = nozzle_iter->second; // target extruder
|
||||
|
||||
// Step 0: compute the slicer-estimated time
|
||||
// Slicer estimate: simulate the slicer's view (no selector awareness);
|
||||
// count a load/unload when nozzle_in_extruder_change || filament_in_nozzle_change
|
||||
if (calc_sliced_time) {
|
||||
int old_nozzle_in_E = sliced_recorder.get_nozzle_in_extruder(E);
|
||||
int old_filament_in_nozzle = sliced_recorder.get_filament_in_nozzle(nozzle_id);
|
||||
int old_filament_in_ext = sliced_recorder.get_filament_in_nozzle(old_nozzle_in_E);
|
||||
|
||||
bool nozzle_change = (old_nozzle_in_E != nozzle_id);
|
||||
bool filament_change = (old_filament_in_nozzle != B);
|
||||
|
||||
if (nozzle_change || filament_change) {
|
||||
if (old_filament_in_ext != -1)
|
||||
sliced_time += time_params.standard_unload_time;
|
||||
sliced_time += time_params.standard_load_time;
|
||||
}
|
||||
sliced_recorder.set_nozzle_status(nozzle_id, B, E);
|
||||
}
|
||||
|
||||
// Step 1: find the filament A currently loaded in the target extruder E
|
||||
int A = -1;
|
||||
{
|
||||
auto it = extruder_filament.find(E);
|
||||
if (it != extruder_filament.end())
|
||||
A = it->second;
|
||||
}
|
||||
|
||||
int group_B = get_group(B);
|
||||
int group_A = (A != -1) ? get_group(A) : -1;
|
||||
|
||||
// Step 2: clear B's AMS-channel occupancy
|
||||
auto group_it = ams_group_occupied.find(group_B);
|
||||
if (group_it != ams_group_occupied.end()) {
|
||||
for (int X : group_it->second) {
|
||||
if (X == B) continue;
|
||||
// X shares B's AMS channel, retreat it to the AMS to make way
|
||||
Location loc_X = filament_location[X];
|
||||
if (loc_X == Location::IN_EXTRUDER) {
|
||||
actual_time += unload_ext_to_selector + unload_ams_to_selector;
|
||||
int E2 = filament_extruder[X];
|
||||
extruder_filament.erase(E2);
|
||||
filament_extruder.erase(X);
|
||||
} else if (loc_X == Location::IN_SELECTOR) {
|
||||
actual_time += unload_ams_to_selector;
|
||||
}
|
||||
filament_location[X] = Location::IN_AMS;
|
||||
}
|
||||
group_it->second.clear();
|
||||
}
|
||||
|
||||
// Step 3: A exits E (while A is still in the extruder)
|
||||
// Step 3.5: pre-load B (in parallel with Step 3)
|
||||
// actual time = max(Step 3, Step 3.5)
|
||||
bool step3_executed = false;
|
||||
float step3_time = 0.0f;
|
||||
if (A != -1 && A != B && filament_location[A] == Location::IN_EXTRUDER) {
|
||||
if (is_preload_enabled(group_A) && group_A != group_B) {
|
||||
step3_time = unload_ext_to_selector;
|
||||
filament_location[A] = Location::IN_SELECTOR;
|
||||
} else {
|
||||
step3_time = unload_ext_to_selector + unload_ams_to_selector;
|
||||
filament_location[A] = Location::IN_AMS;
|
||||
ams_group_occupied[group_A].erase(A);
|
||||
}
|
||||
extruder_filament.erase(E);
|
||||
filament_extruder.erase(A);
|
||||
step3_executed = true;
|
||||
}
|
||||
|
||||
float step3_5_time = 0.0f;
|
||||
if (step3_executed &&
|
||||
filament_location[B] == Location::IN_AMS &&
|
||||
group_A != group_B &&
|
||||
is_preload_enabled(group_B)) {
|
||||
step3_5_time = load_ams_to_selector;
|
||||
filament_location[B] = Location::IN_SELECTOR;
|
||||
ams_group_occupied[group_B].insert(B);
|
||||
}
|
||||
|
||||
actual_time += std::max(step3_time, step3_5_time);
|
||||
|
||||
// Step 4: push B into E
|
||||
// Step 6: pre-load the next filament C (in parallel with Step 4)
|
||||
// actual time = max(Step 4, Step 6)
|
||||
float step4_time = 0.0f;
|
||||
Location loc_B = filament_location[B];
|
||||
if (loc_B == Location::IN_AMS) {
|
||||
step4_time = load_ams_to_selector + load_selector_to_ext;
|
||||
} else if (loc_B == Location::IN_SELECTOR) {
|
||||
step4_time = load_selector_to_ext;
|
||||
}
|
||||
|
||||
// Step 5: update state
|
||||
extruder_filament[E] = B;
|
||||
filament_location[B] = Location::IN_EXTRUDER;
|
||||
filament_extruder[B] = E;
|
||||
ams_group_occupied[group_B].insert(B);
|
||||
|
||||
float step6_time = 0.0f;
|
||||
if (i + 1 < seq_len) {
|
||||
int C = filament_change_seq[i + 1];
|
||||
int group_C = get_group(C);
|
||||
if (filament_location[C] == Location::IN_AMS &&
|
||||
group_C != group_B &&
|
||||
is_preload_enabled(group_C) &&
|
||||
ams_group_occupied[group_C].empty()) {
|
||||
step6_time = load_ams_to_selector;
|
||||
filament_location[C] = Location::IN_SELECTOR;
|
||||
ams_group_occupied[group_C].insert(C);
|
||||
}
|
||||
}
|
||||
|
||||
actual_time += std::max(step4_time, step6_time);
|
||||
}
|
||||
|
||||
result.actual_time = actual_time;
|
||||
result.sliced_time = sliced_time;
|
||||
return result;
|
||||
}
|
||||
|
||||
// ==================== NozzleStatusRecorder implementation ====================
|
||||
|
||||
bool NozzleStatusRecorder::is_nozzle_empty(int nozzle_id) const
|
||||
{
|
||||
auto iter = nozzle_filament_status.find(nozzle_id);
|
||||
if (iter == nozzle_filament_status.end()) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int NozzleStatusRecorder::get_filament_in_nozzle(int nozzle_id) const
|
||||
{
|
||||
auto iter = nozzle_filament_status.find(nozzle_id);
|
||||
if (iter == nozzle_filament_status.end()) return -1;
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
int NozzleStatusRecorder::get_nozzle_in_extruder(int extruder_id) const
|
||||
{
|
||||
auto iter = extruder_nozzle_status.find(extruder_id);
|
||||
if (iter == extruder_nozzle_status.end()) return -1;
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
void NozzleStatusRecorder::set_nozzle_status(int nozzle_id, int filament_id, int extruder_id)
|
||||
{
|
||||
nozzle_filament_status[nozzle_id] = filament_id;
|
||||
if (extruder_id != -1) {
|
||||
extruder_nozzle_status[extruder_id] = nozzle_id;
|
||||
}
|
||||
}
|
||||
|
||||
void NozzleStatusRecorder::clear_nozzle_status(int nozzle_id)
|
||||
{
|
||||
auto iter = nozzle_filament_status.find(nozzle_id);
|
||||
if (iter == nozzle_filament_status.end()) return;
|
||||
nozzle_filament_status.erase(iter);
|
||||
}
|
||||
|
||||
int LayeredNozzleGroupResult::estimate_seq_flush_weight(const std::vector<std::vector<std::vector<float>>>& flush_matrix, const std::vector<int>& filament_change_seq) const
|
||||
{
|
||||
auto get_weight_from_volume = [](float volume){
|
||||
return static_cast<int>(volume * 1.26 * 0.01);
|
||||
};
|
||||
|
||||
float total_flush_volume = 0;
|
||||
NozzleStatusRecorder recorder;
|
||||
for(auto filament: filament_change_seq){
|
||||
auto nozzle = get_nozzle_for_filament(filament, -1);
|
||||
if(!nozzle)
|
||||
continue;
|
||||
|
||||
int extruder_id = nozzle->extruder_id;
|
||||
int nozzle_id = nozzle->group_id;
|
||||
int last_filament = recorder.get_filament_in_nozzle(nozzle_id);
|
||||
|
||||
if(last_filament!= -1 && last_filament != filament){
|
||||
// bounds check to avoid out-of-range access
|
||||
if (extruder_id >= 0 && extruder_id < static_cast<int>(flush_matrix.size()) &&
|
||||
last_filament >= 0 && last_filament < static_cast<int>(flush_matrix[extruder_id].size()) &&
|
||||
filament >= 0 && filament < static_cast<int>(flush_matrix[extruder_id][last_filament].size())) {
|
||||
float flush_volume = flush_matrix[extruder_id][last_filament][filament];
|
||||
total_flush_volume += flush_volume;
|
||||
}
|
||||
}
|
||||
recorder.set_nozzle_status(nozzle_id, filament);
|
||||
}
|
||||
|
||||
return get_weight_from_volume(total_flush_volume);
|
||||
}
|
||||
|
||||
// ==================== StaticNozzleGroupResult ====================
|
||||
|
||||
std::optional<StaticNozzleGroupResult> StaticNozzleGroupResult::create(
|
||||
const std::vector<FilamentInfo>& filaments_info,
|
||||
const std::vector<NozzleInfo>& nozzles_info,
|
||||
const std::vector<int>& filament_change_seq,
|
||||
const std::vector<int>& nozzle_change_seq,
|
||||
bool support_dynamic_nozzle_map)
|
||||
{
|
||||
if (filaments_info.empty() || nozzles_info.empty()) return std::nullopt;
|
||||
|
||||
std::map<int, NozzleInfo> nozzle_list_map;
|
||||
std::map<int, std::set<int>> filament_to_nozzles;
|
||||
|
||||
for (auto nozzle_info : nozzles_info)
|
||||
nozzle_list_map[nozzle_info.group_id] = nozzle_info;
|
||||
|
||||
for (auto filament_info : filaments_info) {
|
||||
auto fil_id = filament_info.id;
|
||||
auto nozzles_id = filament_info.group_id;
|
||||
std::set<int> nozzles_set(nozzles_id.begin(), nozzles_id.end());
|
||||
// Backward compat with older (single-nozzle) gcode.3mf: filament has no group_id, avoid an empty map.
|
||||
if (nozzles_set.empty()) {
|
||||
for (const auto& nozzle_entry : nozzle_list_map)
|
||||
nozzles_set.insert(nozzle_entry.first);
|
||||
}
|
||||
filament_to_nozzles[fil_id] = nozzles_set;
|
||||
}
|
||||
|
||||
StaticNozzleGroupResult result(support_dynamic_nozzle_map);
|
||||
result._filament_to_nozzles = filament_to_nozzles;
|
||||
result._nozzle_list_map = nozzle_list_map;
|
||||
result._filament_change_seq = filament_change_seq;
|
||||
result._nozzle_change_seq = nozzle_change_seq;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<NozzleInfo> StaticNozzleGroupResult::get_nozzle_from_id(int nozzle_id) const
|
||||
{
|
||||
auto iter = _nozzle_list_map.find(nozzle_id);
|
||||
if (iter == _nozzle_list_map.end()) { return std::nullopt; }
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
int StaticNozzleGroupResult::get_extruder_count() const
|
||||
{
|
||||
std::set<int> extruder_ids;
|
||||
for (const auto &elem : _nozzle_list_map) { extruder_ids.insert(elem.second.extruder_id); }
|
||||
return static_cast<int>(extruder_ids.size());
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> StaticNozzleGroupResult::get_used_nozzles_in_extruder(int target_extruder_id) const
|
||||
{
|
||||
std::vector<NozzleInfo> result;
|
||||
for (const auto &elem : _nozzle_list_map) {
|
||||
const auto &nozzle = elem.second;
|
||||
if (target_extruder_id == -1 || nozzle.extruder_id == target_extruder_id) {
|
||||
result.push_back(nozzle);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<int> StaticNozzleGroupResult::get_used_extruders() const
|
||||
{
|
||||
std::set<int> used_extruders;
|
||||
for (const auto &elem : _nozzle_list_map) { used_extruders.insert(elem.second.extruder_id); }
|
||||
return std::vector<int>(used_extruders.begin(), used_extruders.end());
|
||||
}
|
||||
|
||||
std::vector<unsigned int> StaticNozzleGroupResult::get_used_filaments() const
|
||||
{
|
||||
std::vector<unsigned int> used_filaments;
|
||||
used_filaments.reserve(_filament_to_nozzles.size());
|
||||
for (const auto &elem : _filament_to_nozzles) {
|
||||
if (elem.first >= 0) {
|
||||
used_filaments.push_back(static_cast<unsigned int>(elem.first));
|
||||
}
|
||||
}
|
||||
return used_filaments;
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> StaticNozzleGroupResult::get_nozzles_for_filament(int filament_id) const
|
||||
{
|
||||
auto iter = _filament_to_nozzles.find(filament_id);
|
||||
if (iter == _filament_to_nozzles.end()) { return std::vector<NozzleInfo>(); }
|
||||
|
||||
std::vector<NozzleInfo> result;
|
||||
for (int nozzle_id : iter->second) {
|
||||
auto nozzle_iter = _nozzle_list_map.find(nozzle_id);
|
||||
if (nozzle_iter != _nozzle_list_map.end()) {
|
||||
result.push_back(nozzle_iter->second);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<NozzleInfo> StaticNozzleGroupResult::get_first_nozzle_for_filament(int filament_id) const
|
||||
{
|
||||
if (filament_id < 0) return std::nullopt;
|
||||
|
||||
if (!_filament_change_seq.empty() && _filament_change_seq.size() == _nozzle_change_seq.size()) {
|
||||
for (size_t idx = 0; idx < _filament_change_seq.size(); ++idx) {
|
||||
if (_filament_change_seq[idx] == filament_id) {
|
||||
int nozzle_id = _nozzle_change_seq[idx];
|
||||
auto nozzle = get_nozzle_from_id(nozzle_id);
|
||||
if (nozzle) return nozzle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto iter = _filament_to_nozzles.find(filament_id);
|
||||
if (iter == _filament_to_nozzles.end()) return std::nullopt;
|
||||
|
||||
for (int nozzle_id : iter->second) {
|
||||
auto nozzle = get_nozzle_from_id(nozzle_id);
|
||||
if (nozzle) return nozzle;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// ==================== serialization ====================
|
||||
|
||||
std::string NozzleInfo::serialize() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "id=\"" << group_id << "\" "
|
||||
<< "extruder_id=\"" << extruder_id + 1 << "\" "
|
||||
<< "nozzle_diameter=\"" << diameter << "\" "
|
||||
<< "volume_type=\"" << get_nozzle_volume_type_string(volume_type) << "\"";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string NozzleGroupInfo::serialize() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << extruder_id << "-"
|
||||
<< std::setprecision(2) << diameter << "-"
|
||||
<< get_nozzle_volume_type_string(volume_type) << "-"
|
||||
<< nozzle_count;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::optional<NozzleGroupInfo> NozzleGroupInfo::deserialize(const std::string &str)
|
||||
{
|
||||
std::istringstream iss(str);
|
||||
std::string token;
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
while (std::getline(iss, token, '-')) { tokens.push_back(token); }
|
||||
|
||||
if (tokens.size() != 4) { return std::nullopt; }
|
||||
|
||||
try {
|
||||
int extruder_id = std::stoi(tokens[0]);
|
||||
std::string diameter = tokens[1];
|
||||
NozzleVolumeType volume_type = NozzleVolumeType(ConfigOptionEnum<NozzleVolumeType>::get_enum_values().at(tokens[2]));
|
||||
int nozzle_count = std::stoi(tokens[3]);
|
||||
|
||||
return NozzleGroupInfo(diameter, volume_type, extruder_id, nozzle_count);
|
||||
} catch (const std::exception &) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> load_nozzle_infos_with_compatibility(
|
||||
const std::vector<NozzleInfo>& nozzle_infos,
|
||||
const std::vector<FilamentInfo>& filament_infos,
|
||||
const std::vector<int>& filament_map,
|
||||
const std::vector<NozzleVolumeType>& extruder_volume_types,
|
||||
const std::vector<double>& nozzle_diameter
|
||||
)
|
||||
{
|
||||
bool has_nozzle_info = !nozzle_infos.empty();
|
||||
bool has_valid_filament_info = !filament_infos.empty() && std::all_of(filament_infos.begin(), filament_infos.end(), [](const FilamentInfo& info){
|
||||
return info.group_id.size() == 1;
|
||||
});
|
||||
|
||||
if(!has_nozzle_info && !has_valid_filament_info){
|
||||
BOOST_LOG_TRIVIAL(warning)<<__FUNCTION__ << ": building nozzle list from filament map and volume types";
|
||||
|
||||
// Backward compatibility for older gcode.3mf:
|
||||
// - nozzle_diameter is always present and its size defines extruder count.
|
||||
// - filament_map may be missing; treat it as [0, 0, ...] for each extruder.
|
||||
// - extruder_volume_types may be missing; treat it as all Standard.
|
||||
const size_t extruder_count = nozzle_diameter.size();
|
||||
|
||||
std::vector<NozzleVolumeType> volume_types_fixed = extruder_volume_types;
|
||||
volume_types_fixed.resize(extruder_count, NozzleVolumeType::nvtStandard);
|
||||
|
||||
std::vector<NozzleInfo> result;
|
||||
result.reserve(extruder_count);
|
||||
for (size_t extruder_id = 0; extruder_id < extruder_count; ++extruder_id) {
|
||||
NozzleInfo info;
|
||||
info.diameter = format_diameter_to_str(nozzle_diameter[extruder_id]);
|
||||
info.group_id = static_cast<int>(extruder_id);
|
||||
info.extruder_id = static_cast<int>(extruder_id);
|
||||
info.volume_type = volume_types_fixed[extruder_id];
|
||||
result.emplace_back(std::move(info));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if(!has_nozzle_info){
|
||||
BOOST_LOG_TRIVIAL(info)<<__FUNCTION__ << ": building nozzle list from filament info";
|
||||
std::map<int, NozzleInfo> nozzle_map; // group_id -> NozzleInfo
|
||||
for(auto& filament : filament_infos){
|
||||
int group_id = filament.group_id.front();
|
||||
if(group_id < 0 || nozzle_map.find(group_id) != nozzle_map.end()){
|
||||
continue;
|
||||
}
|
||||
|
||||
auto volume_type_str_to_enum = ConfigOptionEnum<NozzleVolumeType>::get_enum_values();
|
||||
|
||||
NozzleInfo info;
|
||||
info.diameter = format_diameter_to_str(filament.nozzle_diameter);
|
||||
info.group_id = group_id;
|
||||
// Orca: bounds-check filament_map[filament.id] so a malformed 3mf (filament id
|
||||
// beyond the map) degrades to extruder 0 instead of dereferencing out of range.
|
||||
info.extruder_id = (filament.id >= 0 && filament.id < static_cast<int>(filament_map.size()))
|
||||
? filament_map[filament.id] - 1
|
||||
: 0; // to 0-based
|
||||
|
||||
if (volume_type_str_to_enum.count(filament.nozzle_volume_type))
|
||||
info.volume_type = NozzleVolumeType(volume_type_str_to_enum.at(filament.nozzle_volume_type));
|
||||
else {
|
||||
info.volume_type = NozzleVolumeType::nvtStandard;
|
||||
}
|
||||
|
||||
nozzle_map[group_id] = std::move(info);
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> ret;
|
||||
for(auto& elem : nozzle_map){
|
||||
ret.emplace_back(elem.second);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto result = nozzle_infos;
|
||||
std::sort(result.begin(), result.end());
|
||||
BOOST_LOG_TRIVIAL(info)<<__FUNCTION__ << ": using new 3mf format with " << result.size() << " nozzle infos.";
|
||||
return result;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::MultiNozzleUtils
|
||||
@@ -0,0 +1,296 @@
|
||||
#ifndef MULTI_NOZZLE_UTILS_HPP
|
||||
#define MULTI_NOZZLE_UTILS_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
// Multi-nozzle support types.
|
||||
// Declares the filament-grouping result types the slicing pipeline needs, plus the analytic
|
||||
// filament-change-time model (FilamentChangeTimeParams, NozzleStatusRecorder,
|
||||
// FilamentChangeSimResult, simulate_filament_change_time) — self-contained analytic code that
|
||||
// never touches the time estimator; its first consumer is the filament_group golden harness.
|
||||
// The gcode.3mf serialization surface lives here too: NozzleInfo/NozzleGroupInfo
|
||||
// serialize+deserialize, the device-side StaticNozzleGroupResult,
|
||||
// load_nozzle_infos_with_compatibility (the backward-compat 3mf reader) and
|
||||
// LayeredNozzleGroupResult::estimate_seq_flush_weight. The change-time-tuning helpers
|
||||
// calc_filament_change_gap_for_assignment / find_optimal_physical_assignment (used only by the
|
||||
// AMS pre-load optimizer, a later feature) are not implemented here.
|
||||
|
||||
namespace Slic3r {
|
||||
struct FilamentInfo; // Slic3r::FilamentInfo (ProjectTask.hpp) — consumed by StaticNozzleGroupResult / the 3mf reader
|
||||
namespace MultiNozzleUtils {
|
||||
|
||||
// Information about a single logical nozzle.
|
||||
struct NozzleInfo
|
||||
{
|
||||
std::string diameter;
|
||||
NozzleVolumeType volume_type;
|
||||
int extruder_id{-1}; // logical extruder id
|
||||
int group_id{-1}; // logical nozzle id
|
||||
|
||||
std::string serialize() const;
|
||||
|
||||
bool operator<(const NozzleInfo& other) const {
|
||||
if(group_id != other.group_id) return group_id < other.group_id;
|
||||
if(extruder_id != other.extruder_id) return extruder_id < other.extruder_id;
|
||||
if(volume_type != other.volume_type) return volume_type < other.volume_type;
|
||||
return diameter < other.diameter;
|
||||
}
|
||||
};
|
||||
|
||||
// A group of identical nozzles on one extruder (diameter + volume type + count).
|
||||
struct NozzleGroupInfo
|
||||
{
|
||||
std::string diameter;
|
||||
NozzleVolumeType volume_type;
|
||||
int extruder_id;
|
||||
int nozzle_count;
|
||||
|
||||
NozzleGroupInfo() = default;
|
||||
|
||||
NozzleGroupInfo(const std::string& nozzle_diameter_, const NozzleVolumeType volume_type_, const int extruder_id_, const int nozzle_count_)
|
||||
: diameter(nozzle_diameter_), volume_type(volume_type_), extruder_id(extruder_id_), nozzle_count(nozzle_count_)
|
||||
{}
|
||||
|
||||
inline bool operator<(const NozzleGroupInfo &rhs) const
|
||||
{
|
||||
if (extruder_id != rhs.extruder_id) return extruder_id < rhs.extruder_id;
|
||||
if (diameter != rhs.diameter) return diameter < rhs.diameter;
|
||||
if (volume_type != rhs.volume_type) return volume_type < rhs.volume_type;
|
||||
return nozzle_count < rhs.nozzle_count;
|
||||
}
|
||||
|
||||
bool is_same_type(const NozzleGroupInfo &rhs) const
|
||||
{
|
||||
return diameter == rhs.diameter && volume_type == rhs.volume_type && extruder_id == rhs.extruder_id;
|
||||
}
|
||||
|
||||
inline bool operator==(const NozzleGroupInfo &rhs) const
|
||||
{
|
||||
return diameter == rhs.diameter && volume_type == rhs.volume_type && extruder_id == rhs.extruder_id && nozzle_count == rhs.nozzle_count;
|
||||
}
|
||||
|
||||
std::string serialize() const;
|
||||
static std::optional<NozzleGroupInfo> deserialize(const std::string& str);
|
||||
};
|
||||
|
||||
// Load/unload time constants used by the filament-change-time model.
|
||||
// Consumed by simulate_filament_change_time() below and carried by the grouping-context
|
||||
// substrate (FilamentGroupContext::SpeedInfo).
|
||||
struct FilamentChangeTimeParams
|
||||
{
|
||||
float selector_load_time{0.0f};
|
||||
float selector_unload_time{0.0f};
|
||||
float standard_load_time{0.0f};
|
||||
float standard_unload_time{0.0f};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Abstract base for a nozzle-grouping result.
|
||||
*/
|
||||
class NozzleGroupResultBase
|
||||
{
|
||||
protected:
|
||||
bool support_dynamic_nozzle_map{false}; // whether dynamic (selector) mapping is used
|
||||
|
||||
public:
|
||||
NozzleGroupResultBase(bool support_dynamic_map = false) : support_dynamic_nozzle_map(support_dynamic_map) {}
|
||||
virtual ~NozzleGroupResultBase() = default;
|
||||
|
||||
virtual std::optional<NozzleInfo> get_nozzle_from_id(int nozzle_id) const = 0;
|
||||
virtual std::optional<NozzleInfo> get_first_nozzle_for_filament(int filament_id) const = 0; // logical nozzle a filament first uses
|
||||
|
||||
virtual std::vector<NozzleInfo> get_nozzles_for_filament(int filament_id) const = 0; // every nozzle a filament may use (across all layers)
|
||||
|
||||
bool is_support_dynamic_nozzle_map() const { return support_dynamic_nozzle_map; }
|
||||
|
||||
virtual int get_extruder_count() const = 0;
|
||||
|
||||
virtual std::vector<NozzleInfo> get_used_nozzles_in_extruder(int extruder_id =-1) const = 0;
|
||||
virtual std::vector<int> get_used_extruders() const = 0;
|
||||
virtual std::vector<unsigned int> get_used_filaments() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Layer-aware nozzle-grouping result.
|
||||
* Used by the back-end slicing code; supports per-layer nozzle mapping.
|
||||
*/
|
||||
class LayeredNozzleGroupResult : public NozzleGroupResultBase
|
||||
{
|
||||
private:
|
||||
std::vector<std::vector<int>> _layer_filament_nozzle_maps; // per-layer filament -> nozzle map
|
||||
std::vector<std::vector<unsigned int>> _layer_filament_sequences; // per-layer filament print order
|
||||
std::vector<int> _default_filament_nozzle_map; // global filament -> nozzle map
|
||||
std::vector<unsigned int> _used_filaments; // all used filament indices
|
||||
std::vector<NozzleInfo> _nozzle_list; // global nozzle list
|
||||
|
||||
public:
|
||||
LayeredNozzleGroupResult(bool support_dynamic_map = false) : NozzleGroupResultBase(support_dynamic_map) {}
|
||||
|
||||
// No selector: one global filament->nozzle map.
|
||||
static std::optional<LayeredNozzleGroupResult> create(
|
||||
const std::vector<int>& filament_nozzle_map,
|
||||
const std::vector<NozzleInfo>& nozzle_list,
|
||||
const std::vector<unsigned int>& used_filaments);
|
||||
|
||||
// Selector: built from per-layer maps (each layer may differ).
|
||||
static std::optional<LayeredNozzleGroupResult> create(
|
||||
const std::vector<std::vector<int>>& layer_filament_nozzle_maps,
|
||||
const std::vector<NozzleInfo>& nozzle_list,
|
||||
const std::vector<unsigned int>& used_filaments,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filament_sequences);
|
||||
|
||||
// Multi-nozzle without selector: resolve each requested logical nozzle to a physical nozzle.
|
||||
static std::optional<LayeredNozzleGroupResult> create(
|
||||
const std::vector<unsigned int>& used_filaments,
|
||||
const std::vector<int>& filament_map,
|
||||
const std::vector<int>& filament_volume_map,
|
||||
const std::vector<int>& filament_nozzle_map,
|
||||
const std::vector<std::map<NozzleVolumeType, int>>& nozzle_count,
|
||||
float diameter);
|
||||
|
||||
bool are_filaments_same_extruder(int filament_id1, int filament_id2, int layer_id = -1) const;
|
||||
bool are_filaments_same_nozzle(int filament_id1, int filament_id2, int layer_id = -1) const;
|
||||
int get_extruder_count() const override;
|
||||
|
||||
std::vector<NozzleInfo> get_used_nozzles_in_extruder(int target_extruder_id = -1) const override;
|
||||
std::vector<NozzleInfo> get_used_nozzles_in_extruder(int target_extruder_id, int layer_id) const; // layer_id=-1 uses default map
|
||||
std::vector<int> get_used_extruders() const override;
|
||||
std::vector<int> get_used_extruders(int layer_id) const; // layer_id=-1 returns global extruders
|
||||
|
||||
std::vector<int> get_extruder_map(bool zero_based = true, int layer_id = -1) const;
|
||||
std::vector<int> get_nozzle_map(int layer_id = -1) const;
|
||||
std::vector<int> get_volume_map(int layer_id = -1) const;
|
||||
|
||||
std::vector<unsigned int> get_used_filaments() const override { return _used_filaments; }
|
||||
std::vector<unsigned int> get_used_filaments(int layer_id) const;
|
||||
|
||||
std::optional<NozzleInfo> get_nozzle_for_filament(int filament_id, int layer_id = -1) const;
|
||||
std::vector<NozzleInfo> get_nozzles_for_filament(int filament_id) const override;
|
||||
|
||||
std::optional<NozzleInfo> get_nozzle_from_id(int nozzle_id) const override;
|
||||
std::optional<NozzleInfo> get_first_nozzle_for_filament(int filament_id) const override;
|
||||
int get_extruder_id(int filament_id, int layer_id = -1) const;
|
||||
int get_nozzle_id(int filament_id, int layer_id = -1) const;
|
||||
|
||||
size_t get_layer_count() const { return _layer_filament_nozzle_maps.size(); }
|
||||
const std::vector<int>& get_layer_filament_nozzle_map(int layer_id) const;
|
||||
const std::vector<std::vector<int>> &get_layer_filament_nozzle_maps() const { return _layer_filament_nozzle_maps; }
|
||||
const std::vector<std::vector<unsigned int>>& get_layer_filament_sequences() const { return _layer_filament_sequences; }
|
||||
|
||||
// Estimate the flush weight of a filament-change sequence given the per-extruder flush matrix
|
||||
// (extruder -> from-filament -> to-filament).
|
||||
int estimate_seq_flush_weight(const std::vector<std::vector<std::vector<float>>>& flush_matrix, const std::vector<int>& filament_change_seq) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Layer-less nozzle-grouping result for the device side (static nozzle mapping only).
|
||||
* Reconstructed from a loaded gcode.3mf together with the filament/nozzle change sequences.
|
||||
*/
|
||||
class StaticNozzleGroupResult : public NozzleGroupResultBase
|
||||
{
|
||||
private:
|
||||
std::map<int, std::set<int>> _filament_to_nozzles; // every nozzle a filament may map to
|
||||
std::map<int, NozzleInfo> _nozzle_list_map; // used nozzles, keyed by logical nozzle id
|
||||
std::vector<int> _filament_change_seq; // filament sequence used to resolve first-use
|
||||
std::vector<int> _nozzle_change_seq; // logical-nozzle sequence paired with the filament sequence
|
||||
|
||||
public:
|
||||
StaticNozzleGroupResult(bool support_dynamic_map) : NozzleGroupResultBase(support_dynamic_map) {}
|
||||
// Build from a loaded 3mf, with the filament/nozzle change sequences.
|
||||
static std::optional<StaticNozzleGroupResult> create(
|
||||
const std::vector<FilamentInfo>& filaments_info,
|
||||
const std::vector<NozzleInfo>& nozzles_info,
|
||||
const std::vector<int>& filament_change_seq,
|
||||
const std::vector<int>& nozzle_change_seq,
|
||||
bool support_dynamic_map);
|
||||
|
||||
int get_extruder_count() const override;
|
||||
std::vector<NozzleInfo> get_used_nozzles_in_extruder(int extruder_id = -1) const override;
|
||||
std::vector<int> get_used_extruders() const override;
|
||||
std::vector<unsigned int> get_used_filaments() const override;
|
||||
|
||||
std::optional<NozzleInfo> get_nozzle_from_id(int nozzle_id) const override;
|
||||
|
||||
std::vector<NozzleInfo> get_nozzles_for_filament(int filament_id) const override;
|
||||
std::optional<NozzleInfo> get_first_nozzle_for_filament(int filament_id) const override;
|
||||
};
|
||||
|
||||
// Tracks, during the filament-change simulation, which filament sits in each physical nozzle
|
||||
// and which nozzle each extruder currently carries.
|
||||
class NozzleStatusRecorder
|
||||
{
|
||||
private:
|
||||
std::unordered_map<int, int> nozzle_filament_status; // Track filament in each nozzle
|
||||
std::unordered_map<int, int> extruder_nozzle_status; // Track nozzle for each extruder
|
||||
int current_extruder_id_ = -1; // Track current extruder id
|
||||
|
||||
public:
|
||||
NozzleStatusRecorder() = default;
|
||||
bool is_nozzle_empty(int nozzle_id) const;
|
||||
int get_filament_in_nozzle(int nozzle_id) const;
|
||||
int get_nozzle_in_extruder(int extruder_id) const;
|
||||
int get_current_extruder_id() const { return current_extruder_id_; }
|
||||
|
||||
void clear_nozzle_status(int nozzle_id);
|
||||
void set_current_extruder_id(int extruder_id) { current_extruder_id_ = extruder_id; }
|
||||
|
||||
// Update the status of a nozzle with new filament and extruder information
|
||||
void set_nozzle_status(int nozzle_id, int filament_id, int extruder_id = -1);
|
||||
|
||||
// key: nozzle id, value: filament id (-1 = the nozzle carries no filament)
|
||||
const std::unordered_map<int, int>& get_nozzle_filament_map() const { return nozzle_filament_status; }
|
||||
// key: extruder id, value: nozzle id (-1 = the extruder carries no nozzle)
|
||||
const std::unordered_map<int, int>& get_extruder_nozzle_map() const { return extruder_nozzle_status; }
|
||||
};
|
||||
|
||||
struct FilamentChangeSimResult {
|
||||
double actual_time = 0.0;
|
||||
double sliced_time = 0.0;
|
||||
};
|
||||
|
||||
// Analytic filament-change-time model. Given the used filaments, the nozzle
|
||||
// list, the filament/nozzle change sequences, each filament's AMS group and the load/unload time
|
||||
// constants, it simulates AMS->selector->extruder transport (with optional AMS pre-load overlap)
|
||||
// and returns the actual print time plus the slicer-estimated time. Self-contained: it never
|
||||
// touches the g-code time estimator.
|
||||
FilamentChangeSimResult simulate_filament_change_time(
|
||||
const std::vector<int>& logical_filaments,
|
||||
const std::vector<NozzleInfo>& nozzle_list,
|
||||
const std::vector<int>& filament_change_seq,
|
||||
const std::vector<int>& nozzle_change_seq,
|
||||
const std::vector<int>& group_of_filament,
|
||||
const FilamentChangeTimeParams& time_params,
|
||||
const std::vector<bool>& ams_preload_enabled = {},
|
||||
bool calc_sliced_time = false);
|
||||
|
||||
// ==================== tool functions ====================
|
||||
// Make each filament's per-layer nozzle assignment gap-free: layers where a filament is not
|
||||
// extruded inherit the nozzle it last used (forward carry); layers before its first use inherit
|
||||
// the first nozzle it ever uses (back-fill). Entries on layers where the filament is actually
|
||||
// used stay untouched. Needed for stitched sequential maps, where consumers indexing with an
|
||||
// object-local layer id must resolve the same nozzle as global-id consumers except across a
|
||||
// genuine mid-print reassignment.
|
||||
void normalize_nozzle_map_per_layer(std::vector<std::vector<int>>& layer_filament_nozzle_maps,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filaments);
|
||||
std::vector<NozzleInfo> build_nozzle_list(std::vector<NozzleGroupInfo> info);
|
||||
std::vector<NozzleInfo> build_nozzle_list(double diameter, const std::vector<int>& filament_nozzle_map,
|
||||
const std::vector<int>& filament_volume_map, const std::vector<int>& filament_map);
|
||||
// Load nozzle infos from a gcode.3mf, handling backward compatibility with older 3mf that did not
|
||||
// record standalone <nozzle> tags: falls back to the per-filament group_id/diameter/volume_type, and
|
||||
// (for the oldest single-nozzle 3mf) to the filament_map + extruder volume types + nozzle diameters.
|
||||
std::vector<NozzleInfo> load_nozzle_infos_with_compatibility(
|
||||
const std::vector<NozzleInfo>& nozzle_infos,
|
||||
const std::vector<FilamentInfo>& filament_infos,
|
||||
const std::vector<int>& filament_map,
|
||||
const std::vector<NozzleVolumeType>& extruder_volume_types,
|
||||
const std::vector<double>& nozzle_diameter
|
||||
);
|
||||
} // namespace MultiNozzleUtils
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // MULTI_NOZZLE_UTILS_HPP
|
||||
+192
-15
@@ -27,19 +27,20 @@ namespace Slic3r {
|
||||
namespace orientation {
|
||||
|
||||
struct CostItems {
|
||||
float overhang;
|
||||
float bottom;
|
||||
float bottom_hull;
|
||||
float contour;
|
||||
float area_laf; // area_of_low_angle_faces
|
||||
float area_projected; // area of projected 2D profile
|
||||
float volume;
|
||||
float area_total; // total area of all faces
|
||||
float radius; // radius of bounding box
|
||||
float height_to_bottom_hull_ratio; // affects stability, the lower the better
|
||||
float unprintability;
|
||||
float overhang = 0;
|
||||
float bottom = 0;
|
||||
float bottom_hull = 0;
|
||||
float contour = 0;
|
||||
float area_laf = 0; // area_of_low_angle_faces
|
||||
float area_projected = 0; // area of projected 2D profile
|
||||
float volume = 0;
|
||||
float area_total = 0; // total area of all faces
|
||||
float radius = 0; // radius of bounding box
|
||||
float height_to_bottom_hull_ratio = 0; // affects stability, the lower the better
|
||||
float unprintability = 0;
|
||||
Eigen::VectorXf areas_cooling;
|
||||
CostItems(CostItems const & other) = default;
|
||||
CostItems() { memset(this, 0, sizeof(*this)); }
|
||||
CostItems() = default;
|
||||
static std::string field_names() {
|
||||
return " overhang, bottom, bothull, contour, A_laf, A_prj, unprintability";
|
||||
}
|
||||
@@ -68,10 +69,11 @@ public:
|
||||
Eigen::VectorXf z_max, z_max_hull; // max of projected z
|
||||
Eigen::VectorXf z_median; // median of projected z
|
||||
Eigen::VectorXf z_mean; // mean of projected z
|
||||
Eigen::VectorXf areas_cooling; // weighted areas for cool direction
|
||||
std::vector<Vec3f> face_normals;
|
||||
std::vector<Vec3f> face_normals_hull;
|
||||
OrientParams params;
|
||||
|
||||
bool has_cooling_fan = false;
|
||||
|
||||
std::vector< Vec3f> orientations; // Vec3f == stl_normal
|
||||
std::function<void(unsigned)> progressind = { }; // default empty indicator function
|
||||
@@ -85,6 +87,7 @@ public:
|
||||
orient_mesh = orient_mesh_;
|
||||
mesh = &orient_mesh->mesh;
|
||||
params = params_;
|
||||
has_cooling_fan = orient_mesh->has_cooling_fan;
|
||||
progressind = progressind_;
|
||||
params.ASCENT = cos(PI - orient_mesh->overhang_angle * PI / 180); // use per-object overhang angle
|
||||
|
||||
@@ -158,12 +161,14 @@ public:
|
||||
//To avoid flipping, we need to verify if there are orientations with same unprintability.
|
||||
Vec3f n1 = {0, 0, 1};
|
||||
auto best_orientation = results_vector[0].first;
|
||||
size_t best_index = 0;
|
||||
|
||||
for (int i = 1; i< results_vector.size()-1; i++) {
|
||||
if (abs(results_vector[i].second.unprintability - results_vector[0].second.unprintability) < EPSILON && abs(results_vector[0].first.dot(n1)-1) > EPSILON) {
|
||||
if (abs(results_vector[i].first.dot(n1)-1) < EPSILON*EPSILON) {
|
||||
if (abs(results_vector[i].first.dot(n1)-1) < EPSILON*EPSILON) {
|
||||
best_orientation = n1;
|
||||
break;
|
||||
best_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -172,6 +177,9 @@ public:
|
||||
|
||||
}
|
||||
|
||||
// cooling weights are per-orientation, so take them from the orientation actually chosen
|
||||
areas_cooling = results_vector[best_index].second.areas_cooling;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << std::fixed << std::setprecision(6) << "best:" << best_orientation.transpose() << ", costs:" << results_vector[0].second.field_values();
|
||||
std::cout << std::fixed << std::setprecision(6) << "best:" << best_orientation.transpose() << ", costs:" << results_vector[0].second.field_values() << std::endl;
|
||||
|
||||
@@ -441,6 +449,19 @@ public:
|
||||
Eigen::MatrixXf laf_areas = ((normal_projection_abs.array() < params.LAF_MAX) * (normal_projection_abs.array() > params.LAF_MIN) * (z_max.array() > total_min_z + params.FIRST_LAY_H)).select(areas, 0);
|
||||
costs.area_laf = laf_areas.sum();
|
||||
|
||||
if (has_cooling_fan)
|
||||
{
|
||||
// Angle range of overhang faces requiring cooling
|
||||
float angle_thres_high = -0.6427f;
|
||||
float angle_thres_low = -0.97f;
|
||||
// compute the weighted overhang faces area
|
||||
Eigen::VectorXf ones_f = Eigen::VectorXf::Ones(mesh->facets_count());
|
||||
auto overhang_area_condition = (normal_projection.array() < angle_thres_high && normal_projection.array() > angle_thres_low).eval();
|
||||
Eigen::VectorXf areas_ = (overhang_area_condition * !bottom_condition_2nd).select(areas, 0);
|
||||
Eigen::VectorXf weighted_areas = areas_.cwiseProduct(ones_f - normal_projection);
|
||||
costs.areas_cooling = weighted_areas;
|
||||
}
|
||||
|
||||
// height to bottom_hull_area ratio
|
||||
//float total_max_z = z_projected.maxCoeff();
|
||||
//costs.height_to_bottom_hull_ratio = SQ(total_max_z) / (costs.bottom_hull + 1e-7);
|
||||
@@ -468,6 +489,67 @@ public:
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
Vec3d find_cooling_direction2(Vec3d euler_angles, const Eigen::VectorXf& areas_in, TriangleMesh& mesh)
|
||||
{
|
||||
Vec3f machine_cool_dir = this->orient_mesh->cooling_direction.cast<float>();
|
||||
const size_t num_faces = areas.rows();
|
||||
Vec3f best_direction = { 0, 0, 0 };
|
||||
|
||||
// 1. Make a copy of input mesh, rotate and translate to the best orientation
|
||||
TriangleMesh mesh_copy = TriangleMesh(mesh.its);
|
||||
mesh_copy.rotate_x(euler_angles(0, 0));
|
||||
mesh_copy.rotate_y(euler_angles(1, 0));
|
||||
mesh_copy.rotate_z(euler_angles(2, 0));
|
||||
auto bounding_box = mesh_copy.bounding_box();
|
||||
Eigen::VectorXf translate_distance = bounding_box.min.array().cast<float>();
|
||||
Vec3d mesh_center = mesh_copy.center();
|
||||
mesh_copy.translate(-mesh_center(0), -mesh_center(1), -translate_distance(2));
|
||||
|
||||
// 2. sample cooling direction
|
||||
const size_t sample_nums = 180;
|
||||
std::vector<Vec3f> cool_dirs;
|
||||
for (size_t i = 0; i < sample_nums; i++)
|
||||
{
|
||||
float angle_deg = i * (360.0 / sample_nums);
|
||||
float angle_rad = angle_deg * (PI / 180.0);
|
||||
cool_dirs.push_back(Vec3f{ std::cos(angle_rad), std::sin(angle_rad), 0});
|
||||
}
|
||||
|
||||
// 3. accumulate the weighted projected overhang area, find the max weighted project area direction
|
||||
std::vector<Vec3f> face_normals_copy = its_face_normals(mesh_copy.its);
|
||||
float overhang_projected_max = 0.f;
|
||||
float overhang_projected_origin = 0.f;
|
||||
for (auto cool_dir : cool_dirs)
|
||||
{
|
||||
float overhang_projected_tmp = 0.f;
|
||||
for (size_t i = 0; i < num_faces; i++)
|
||||
{
|
||||
float cool_dir_projection = face_normals_copy[i].dot(cool_dir);
|
||||
if (areas_in[i] > 0 && cool_dir_projection > 0)
|
||||
{
|
||||
overhang_projected_tmp += areas_in[i] * cool_dir_projection;
|
||||
}
|
||||
}
|
||||
if (overhang_projected_tmp > overhang_projected_max)
|
||||
{
|
||||
overhang_projected_max = overhang_projected_tmp;
|
||||
best_direction = cool_dir;
|
||||
}
|
||||
if (cool_dir.dot(machine_cool_dir) > 0.999)
|
||||
{
|
||||
overhang_projected_origin = overhang_projected_tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// The symmetric model has similar overhang projection at all angles, so Z-axis rotation is unnecessary.
|
||||
if (std::abs(overhang_projected_origin - overhang_projected_max) < 1.0f)
|
||||
{
|
||||
best_direction = machine_cool_dir;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "best cooling dir = " << best_direction.transpose() << "\n";
|
||||
return best_direction.cast<double>();
|
||||
}
|
||||
};
|
||||
|
||||
void _orient(OrientMeshs& meshs_,
|
||||
@@ -497,6 +579,13 @@ void _orient(OrientMeshs& meshs_,
|
||||
mesh_.orientation = orienter.process();
|
||||
Geometry::rotation_from_two_vectors(mesh_.orientation, { 0,0,1 }, mesh_.axis, mesh_.angle, &mesh_.rotation_matrix);
|
||||
mesh_.euler_angles = Geometry::extract_euler_angles(mesh_.rotation_matrix);
|
||||
// find cool direction
|
||||
if (mesh_.has_cooling_fan)
|
||||
{
|
||||
mesh_.orientation_vertical = orienter.find_cooling_direction2(mesh_.euler_angles, orienter.areas_cooling, mesh_.mesh);
|
||||
BOOST_LOG_TRIVIAL(info) << "cooling direction: " << mesh_.orientation_vertical.transpose() << "\n";
|
||||
Geometry::rotation_from_two_vectors(mesh_.orientation_vertical, mesh_.cooling_direction, mesh_.axis_vertical, mesh_.angle_vertical, &mesh_.rotation_matrix_vertical);
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(debug) << "rotation_from_two_vectors: " << mesh_.orientation << "; " << mesh_.axis << "; " << mesh_.angle << "; euler: " << mesh_.euler_angles.transpose();
|
||||
}});
|
||||
}
|
||||
@@ -539,6 +628,94 @@ void orient(ModelInstance* instance)
|
||||
instance->rotate(rotation_matrix);
|
||||
}
|
||||
|
||||
void orient_for_cooling(TriangleMesh& mesh, const FanDirection& fan_dir)
|
||||
{
|
||||
Vec3f best_direction{ 0, 0, 0 };
|
||||
Vec3f machine_cool_dir{ 0, 0, 0 };
|
||||
|
||||
if (fan_dir == FanDirection::fdUndefine)
|
||||
{
|
||||
// no cooling fan, do not rotate along z axis
|
||||
return;
|
||||
}
|
||||
else if (fan_dir == FanDirection::fdRight)
|
||||
{
|
||||
machine_cool_dir = { 1, 0, 0 }; // the cooling fan is on the right side.
|
||||
}
|
||||
else
|
||||
{
|
||||
// the cooling fan is on the left side or both side has cooling fans
|
||||
machine_cool_dir = { -1, 0, 0 };
|
||||
}
|
||||
|
||||
// 1. filter the overhang_areas
|
||||
int nfaces = mesh.facets_count();
|
||||
auto face_normals = its_face_normals(mesh.its);
|
||||
|
||||
Eigen::VectorXf normal_projection(nfaces, 1);
|
||||
for (auto i = 0; i < nfaces; i++)
|
||||
{
|
||||
normal_projection(i) = face_normals[i].dot(Vec3f(0, 0, 1));
|
||||
}
|
||||
float angle_thres_high = -0.6427f;
|
||||
float angle_thres_low = -0.97f;
|
||||
// 2. compute the weighted overhang faces area
|
||||
Eigen::VectorXf weighted_areas = Eigen::VectorXf::Zero(nfaces);
|
||||
for (int i = 0; i < nfaces; i++)
|
||||
{
|
||||
if (normal_projection(i) < angle_thres_high && normal_projection(i) > angle_thres_low)
|
||||
{
|
||||
weighted_areas(i) = mesh.its.facet_area(i) * (1.0f - normal_projection(i));
|
||||
}
|
||||
}
|
||||
|
||||
const size_t sample_nums = 180;
|
||||
std::vector<Vec3f> cool_dirs;
|
||||
for (size_t i = 0; i < sample_nums; i++)
|
||||
{
|
||||
float angle_deg = i * (360.0 / sample_nums);
|
||||
float angle_rad = angle_deg * (PI / 180.0);
|
||||
cool_dirs.push_back(Vec3f{ std::cos(angle_rad), std::sin(angle_rad), 0 });
|
||||
}
|
||||
|
||||
// 3. accumulate the weighted projected overhang area, find the max weighted project area direction
|
||||
float overhang_projected_max = 0.f;
|
||||
float overhang_projected_origin = 0.f;
|
||||
for (auto cool_dir : cool_dirs)
|
||||
{
|
||||
float overhang_projected_tmp = 0.f;
|
||||
for (size_t i = 0; i < nfaces; i++)
|
||||
{
|
||||
float cool_dir_projection = face_normals[i].dot(cool_dir);
|
||||
if (weighted_areas[i] > 0 && cool_dir_projection > 0)
|
||||
{
|
||||
overhang_projected_tmp += weighted_areas[i] * cool_dir_projection;
|
||||
}
|
||||
}
|
||||
if (overhang_projected_tmp > overhang_projected_max)
|
||||
{
|
||||
overhang_projected_max = overhang_projected_tmp;
|
||||
best_direction = cool_dir;
|
||||
}
|
||||
if (cool_dir.dot(machine_cool_dir) > 0.999)
|
||||
{
|
||||
overhang_projected_origin = overhang_projected_tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// The symmetric model has similar overhang projection at all angles, so Z-axis rotation is unnecessary.
|
||||
if (std::abs(overhang_projected_origin - overhang_projected_max) < 1.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// rotate the mesh
|
||||
Vec3d axis;
|
||||
double angle;
|
||||
Matrix3d rotation_matrix;
|
||||
Geometry::rotation_from_two_vectors(best_direction.cast<double>(), machine_cool_dir.cast<double>(), axis, angle, &rotation_matrix);
|
||||
mesh.rotate(angle, axis);
|
||||
}
|
||||
|
||||
} // namespace arr
|
||||
} // namespace Slic3r
|
||||
@@ -26,10 +26,18 @@ struct OrientMesh {
|
||||
TriangleMesh mesh; /// The real mesh data
|
||||
double overhang_angle = 30;
|
||||
double angle{ 0 };
|
||||
double angle_vertical{ 0 };
|
||||
Vec3d axis{ 0,0,1 };
|
||||
Vec3d axis_vertical{ 0,0,1 };
|
||||
Vec3d orientation{ 0,0,1 };
|
||||
Matrix3d rotation_matrix;
|
||||
Vec3d euler_angles;
|
||||
Vec3d orientation_vertical{ -1,0,0 };
|
||||
Matrix3d rotation_matrix = Matrix3d::Identity();
|
||||
Matrix3d rotation_matrix_vertical = Matrix3d::Identity();
|
||||
Vec3d euler_angles = {0, 0, 0};
|
||||
Vec3d euler_angles_vertical = {0, 0, 0};
|
||||
Vec3d cooling_direction = {0, 0, 0};
|
||||
bool has_cooling_fan{false};
|
||||
|
||||
std::string name;
|
||||
|
||||
/// Optional setter function which can store arbitrary data in its closure
|
||||
@@ -154,6 +162,9 @@ void orient(ModelObject* obj);
|
||||
|
||||
void orient(ModelInstance* instance);
|
||||
|
||||
// rotate z axis for cooling
|
||||
void orient_for_cooling(TriangleMesh& mesh, const FanDirection& fan_dir);
|
||||
|
||||
}} // namespace Slic3r::orientment
|
||||
|
||||
#endif // MODELORIENT_HPP
|
||||
@@ -252,7 +252,7 @@ void extend_default_config_length(DynamicPrintConfig& config, const bool set_nil
|
||||
auto replace_nil_and_resize = [&](const std::string & key, int length){
|
||||
ConfigOption* raw_ptr = config.option(key);
|
||||
ConfigOptionVectorBase* opt_vec = static_cast<ConfigOptionVectorBase *>(raw_ptr);
|
||||
if(set_nil_to_default && raw_ptr->is_nil() && defaults.has(key) && std::find(filament_extruder_override_keys.begin(), filament_extruder_override_keys.end(), key) == filament_extruder_override_keys.end()){
|
||||
if(set_nil_to_default && raw_ptr->is_nil() && defaults.has(key) && !is_filament_extruder_override_key(key)){
|
||||
opt_vec->clear();
|
||||
opt_vec->resize(length, defaults.option(key));
|
||||
}
|
||||
@@ -1319,7 +1319,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
};
|
||||
|
||||
static std::vector<std::string> s_Preset_filament_options {/*"filament_colour", */ "default_filament_colour", "required_nozzle_HRC", "filament_diameter", "pellet_flow_coefficient", "volumetric_speed_coefficients", "filament_type",
|
||||
"filament_soluble", "filament_is_support", "filament_printable",
|
||||
"filament_soluble", "filament_is_support", "filament_printable", "filament_extruder_compatibility",
|
||||
"filament_max_volumetric_speed", "filament_adaptive_volumetric_speed",
|
||||
"filament_flow_ratio", "filament_density", "filament_adhesiveness_category", "filament_cost", "filament_minimal_purge_on_wipe_tower",
|
||||
"filament_tower_interface_pre_extrusion_dist", "filament_tower_interface_pre_extrusion_length", "filament_tower_ironing_area", "filament_tower_interface_purge_volume",
|
||||
@@ -1354,7 +1354,13 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
|
||||
"filament_multitool_ramming", "filament_multitool_ramming_volume", "filament_multitool_ramming_flow", "activate_chamber_temp_control", "chamber_minimal_temperature",
|
||||
"filament_long_retractions_when_cut","filament_retraction_distances_when_cut", "idle_temperature",
|
||||
//BBS filament change length while the extruder color
|
||||
"filament_change_length","filament_flush_volumetric_speed","filament_flush_temp", "filament_cooling_before_tower",
|
||||
"filament_change_length","filament_flush_volumetric_speed","filament_flush_temp","filament_flush_temp_fast", "filament_cooling_before_tower",
|
||||
// Multi-nozzle pre-cooling / ramming / nozzle-change (nc) filament overrides
|
||||
"filament_ramming_volumetric_speed", "filament_ramming_volumetric_speed_nc",
|
||||
"filament_ramming_travel_time", "filament_ramming_travel_time_nc",
|
||||
"filament_pre_cooling_temperature", "filament_pre_cooling_temperature_nc",
|
||||
"filament_preheat_temperature_delta", "filament_retract_length_nc",
|
||||
"filament_change_length_nc", "filament_prime_volume_nc",
|
||||
"long_retractions_when_ec", "retraction_distances_when_ec",
|
||||
//ams chamber
|
||||
"filament_dev_ams_drying_ams_limitations", "filament_dev_ams_drying_temperature", "filament_dev_ams_drying_time", "filament_dev_ams_drying_heat_distortion_temperature",
|
||||
@@ -1369,6 +1375,8 @@ static std::vector<std::string> s_Preset_machine_limits_options {
|
||||
"machine_min_extruding_rate", "machine_min_travel_rate",
|
||||
"machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e",
|
||||
"machine_max_junction_deviation",
|
||||
// Bedslinger mass/force limits
|
||||
"machine_max_force_Y", "machine_bed_mass_Y", "machine_max_printed_mass",
|
||||
//resonance avoidance ported from qidi slicer
|
||||
"resonance_avoidance", "min_resonance_avoidance_speed", "max_resonance_avoidance_speed",
|
||||
// Orca: input shaping
|
||||
@@ -1386,7 +1394,7 @@ static std::vector<std::string> s_Preset_printer_options {
|
||||
"default_print_profile", "inherits",
|
||||
"silent_mode",
|
||||
"scan_first_layer", "enable_power_loss_recovery", "wrapping_detection_layers", "wrapping_exclude_area", "machine_load_filament_time", "machine_unload_filament_time", "machine_tool_change_time", "time_cost", "machine_pause_gcode", "template_custom_gcode",
|
||||
"nozzle_type", "nozzle_hrc","auxiliary_fan", "nozzle_volume","upward_compatible_machine", "z_hop_types", "travel_slope", "retract_lift_enforce","support_chamber_temp_control","support_air_filtration","printer_structure",
|
||||
"nozzle_type", "nozzle_hrc","auxiliary_fan", "fan_direction", "nozzle_volume","upward_compatible_machine", "z_hop_types", "travel_slope", "retract_lift_enforce","support_chamber_temp_control","support_air_filtration","support_cooling_filter","cooling_filter_enabled","printer_structure","farthest_point_timelapse",
|
||||
"best_object_pos", "head_wrap_detect_zone",
|
||||
"host_type", "print_host", "printhost_apikey", "flashforge_serial_number", "bbl_use_printhost", "printer_agent",
|
||||
"print_host_webui",
|
||||
@@ -1398,7 +1406,13 @@ static std::vector<std::string> s_Preset_printer_options {
|
||||
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", "tool_change_on_wipe_tower",
|
||||
"z_offset",
|
||||
"disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "use_3mf", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut",
|
||||
"bed_temperature_formula", "nozzle_flush_dataset"
|
||||
"bed_temperature_formula", "nozzle_flush_dataset",
|
||||
// Multi-nozzle count + pre-heat model printer options
|
||||
"extruder_max_nozzle_count", "group_algo_with_time", "enable_pre_heating", "hotend_heating_rate", "hotend_cooling_rate",
|
||||
"machine_hotend_change_time", "machine_prepare_compensation_time",
|
||||
// Fast-purge printer flag + device/firmware-facing per-variant extruder-change
|
||||
// deretraction speed (unconsumed by the slicer; carried by H2D/A2L/X2D/P2S machine profiles).
|
||||
"support_fast_purge_mode", "deretract_speed_extruder_change"
|
||||
};
|
||||
|
||||
static std::vector<std::string> s_Preset_sla_print_options {
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
#define BBL_JSON_KEY_BOTTOM_TEXTURE_END_NAME "bottom_texture_end_name"
|
||||
#define BBL_JSON_KEY_USE_DOUBLE_EXTRUDER_DEFAULT_TEXTURE "use_double_extruder_default_texture"
|
||||
#define BBL_JSON_KEY_BOTTOM_TEXTURE_RECT "bottom_texture_rect"
|
||||
#define BBL_JSON_KEY_BOTTOM_TEXTURE_RECT_LONGER "bottom_texture_rect_longer"
|
||||
#define BBL_JSON_KEY_MIDDLE_TEXTURE_RECT "middle_texture_rect"
|
||||
|
||||
#define BBL_JSON_KEY_HOTEND_MODEL "hotend_model"
|
||||
@@ -150,6 +151,7 @@ public:
|
||||
std::string bottom_texture_end_name;
|
||||
std::string use_double_extruder_default_texture;
|
||||
std::string bottom_texture_rect;
|
||||
std::string bottom_texture_rect_longer;
|
||||
std::string middle_texture_rect;
|
||||
std::string hotend_model;
|
||||
PrinterVariant* variant(const std::string &name) {
|
||||
|
||||
+188
-24
@@ -52,9 +52,23 @@ static std::vector<std::string> s_project_options {
|
||||
"wipe_tower_rotation_angle",
|
||||
"curr_bed_type",
|
||||
"flush_multiplier",
|
||||
// Fast-purge mode: project-level purge control, inert at Default.
|
||||
"flush_multiplier_fast",
|
||||
"prime_volume_mode",
|
||||
"nozzle_volume_type",
|
||||
"filament_map_mode",
|
||||
"filament_map"
|
||||
"filament_map",
|
||||
// Per-filament nozzle-volume choice; project-level like filament_map so the per-filament
|
||||
// slot resolution survives preset switches.
|
||||
"filament_volume_map",
|
||||
// Per-filament physical-nozzle choice the grouping engine writes back; project-level so a
|
||||
// saved project round-trips the assignment alongside filament_map/filament_volume_map.
|
||||
"filament_nozzle_map",
|
||||
// Filament Track Switch device state: whether the switch is installed and ready, and
|
||||
// whether dynamic per-nozzle filament mapping is active. Persisted with the project and
|
||||
// restored from a saved 3mf; reset to false on load and set true only by live device sync.
|
||||
"has_filament_switcher",
|
||||
"enable_filament_dynamic_map"
|
||||
};
|
||||
|
||||
//Orca: add custom as default
|
||||
@@ -71,7 +85,8 @@ DynamicPrintConfig PresetBundle::construct_full_config(
|
||||
const DynamicPrintConfig& project_config,
|
||||
std::vector<Preset>& in_filament_presets,
|
||||
bool apply_extruder,
|
||||
std::optional<std::vector<int>> filament_maps_new)
|
||||
std::optional<std::vector<int>> filament_maps_new,
|
||||
std::optional<std::vector<int>> filament_volume_maps_new)
|
||||
{
|
||||
DynamicPrintConfig &printer_config = in_printer_preset.config;
|
||||
DynamicPrintConfig &print_config = in_print_preset.config;
|
||||
@@ -86,12 +101,23 @@ DynamicPrintConfig PresetBundle::construct_full_config(
|
||||
size_t num_filaments = in_filament_presets.size();
|
||||
|
||||
std::vector<int> filament_maps = out.option<ConfigOptionInts>("filament_map")->values;
|
||||
std::vector<int> filament_volume_maps(num_filaments, (int)nvtStandard);
|
||||
|
||||
ConfigOptionInts* filament_volume_map_opt = out.option<ConfigOptionInts>("filament_volume_map");
|
||||
if (filament_maps_new.has_value())
|
||||
filament_maps = *filament_maps_new;
|
||||
if (filament_volume_maps_new.has_value())
|
||||
filament_volume_maps = *filament_volume_maps_new;
|
||||
else if (filament_volume_map_opt && filament_volume_map_opt->values.size() == num_filaments)
|
||||
filament_volume_maps = filament_volume_map_opt->values;
|
||||
|
||||
// in some middle state, they may be different
|
||||
if (filament_maps.size() != num_filaments) {
|
||||
filament_maps.resize(num_filaments, 1);
|
||||
}
|
||||
if (filament_volume_maps.size() != num_filaments) {
|
||||
filament_volume_maps.resize(num_filaments, nvtStandard);
|
||||
}
|
||||
|
||||
auto *extruder_diameter = dynamic_cast<const ConfigOptionFloats *>(out.option("nozzle_diameter"));
|
||||
// Collect the "compatible_printers_condition" and "inherits" values over all presets (print, filaments, printers) into a single vector.
|
||||
@@ -112,17 +138,34 @@ DynamicPrintConfig PresetBundle::construct_full_config(
|
||||
inherits.emplace_back(print_inherits);
|
||||
|
||||
// BBS: update printer config related with variants
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 1, extruder_volume_type_count = 1;
|
||||
bool different_extruder = false;
|
||||
if (apply_extruder) {
|
||||
out.update_values_to_printer_extruders(out, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
|
||||
out.update_values_to_printer_extruders(out, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
|
||||
// update print config related with variants
|
||||
out.update_values_to_printer_extruders(out, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
different_extruder = out.support_different_extruders(extruder_count);
|
||||
extruder_volume_type_count = out.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
if ((extruder_count > 1) || different_extruder) {
|
||||
// Orca: keep processing variant_1 before variant_2 here; variant_2 slots are resolved
|
||||
// against the printer id/variant lists as rewritten by the variant_1 pass, and the
|
||||
// composed values depend on that order. Note the order is load-bearing, not correct
|
||||
// in general: the variant_2 pass reads the original full-width arrays through indices
|
||||
// resolved on the shrunk lists, which mis-reads presets whose variant_2 columns differ
|
||||
// per variant (e.g. X2D machine_max_speed_e/machine_max_acceleration_e). The slicing
|
||||
// path composes variant_2 first and is unaffected; changing the order here would alter
|
||||
// long-standing composed values, so any fix must re-baseline them.
|
||||
out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
|
||||
out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
|
||||
// update print config related with variants
|
||||
out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
}
|
||||
}
|
||||
|
||||
if (num_filaments <= 1) {
|
||||
// BBS: update filament config related with variants
|
||||
DynamicPrintConfig filament_config = in_filament_presets[0].config;
|
||||
if (apply_extruder) filament_config.update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0]);
|
||||
if (apply_extruder && ((extruder_count > 1) || different_extruder))
|
||||
filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0], (NozzleVolumeType)filament_volume_maps[0]);
|
||||
out.apply(filament_config);
|
||||
compatible_printers_condition.emplace_back(in_filament_presets[0].compatible_printers_condition());
|
||||
compatible_prints_condition.emplace_back(in_filament_presets[0].compatible_prints_condition());
|
||||
@@ -145,8 +188,8 @@ DynamicPrintConfig PresetBundle::construct_full_config(
|
||||
filament_temp_configs.resize(num_filaments);
|
||||
for (size_t i = 0; i < num_filaments; ++i) {
|
||||
filament_temp_configs[i] = *(filament_configs[i]);
|
||||
if (apply_extruder)
|
||||
filament_temp_configs[i].update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i]);
|
||||
if (apply_extruder && ((extruder_count > 1) || different_extruder))
|
||||
filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i], (NozzleVolumeType)filament_volume_maps[i]);
|
||||
}
|
||||
|
||||
// loop through options and apply them to the resulting config.
|
||||
@@ -221,6 +264,7 @@ DynamicPrintConfig PresetBundle::construct_full_config(
|
||||
out.option<ConfigOptionString>("printer_settings_id", true)->value = in_printer_preset.name;
|
||||
out.option<ConfigOptionStrings>("filament_ids", true)->values = filament_ids;
|
||||
out.option<ConfigOptionInts>("filament_map", true)->values = filament_maps;
|
||||
out.option<ConfigOptionInts>("filament_volume_map", true)->values = filament_volume_maps;
|
||||
|
||||
auto add_if_some_non_empty = [&out](std::vector<std::string> &&values, const std::string &key) {
|
||||
bool nonempty = false;
|
||||
@@ -348,7 +392,7 @@ PresetBundle::PresetBundle()
|
||||
auto& default_config = this->filaments.default_preset().config;
|
||||
for(const std::string& opt_key : default_config.keys()){
|
||||
ConfigOption* opt = default_config.optptr(opt_key, false);
|
||||
bool is_override_key = std::find(filament_extruder_override_keys.begin(),filament_extruder_override_keys.end(), opt_key) != filament_extruder_override_keys.end();
|
||||
bool is_override_key = is_filament_extruder_override_key(opt_key);
|
||||
if(!is_override_key || !opt->nullable())
|
||||
continue;
|
||||
opt->deserialize("nil",ForwardCompatibilitySubstitutionRule::Disable);
|
||||
@@ -721,6 +765,8 @@ std::optional<FilamentBaseInfo> PresetBundle::get_filament_by_filament_id(const
|
||||
auto iter = std::find(compatible_printers.begin(), compatible_printers.end(), printer_name);
|
||||
if (iter != compatible_printers.end() && config.has("filament_printable")) {
|
||||
info.filament_printable = config.option<ConfigOptionInts>("filament_printable")->values[0];
|
||||
if (config.has("filament_extruder_compatibility"))
|
||||
info.set_filament_extruder_compatibility(config.option<ConfigOptionInts>("filament_extruder_compatibility")->values[0]);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -2674,6 +2720,12 @@ void PresetBundle::update_selections(AppConfig &config)
|
||||
std::vector<int> filament_maps(filament_colors.size(), 1);
|
||||
project_config.option<ConfigOptionInts>("filament_map")->values = filament_maps;
|
||||
|
||||
std::vector<int> filament_nozzle_maps(filament_colors.size(), 0);
|
||||
project_config.option<ConfigOptionInts>("filament_nozzle_map")->values = filament_nozzle_maps;
|
||||
|
||||
std::vector<int> filament_volume_maps(filament_colors.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
project_config.option<ConfigOptionInts>("filament_volume_map")->values = filament_volume_maps;
|
||||
|
||||
std::vector<std::string> extruder_ams_count_str;
|
||||
if (config.has_printer_setting(initial_printer_profile_name, "extruder_ams_count")) {
|
||||
boost::algorithm::split(extruder_ams_count_str, config.get_printer_setting(initial_printer_profile_name, "extruder_ams_count"), boost::algorithm::is_any_of(","));
|
||||
@@ -2818,6 +2870,12 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
||||
std::vector<int> filament_maps(filament_colors.size(), 1);
|
||||
project_config.option<ConfigOptionInts>("filament_map")->values = filament_maps;
|
||||
|
||||
std::vector<int> filament_nozzle_maps(filament_colors.size(), 0);
|
||||
project_config.option<ConfigOptionInts>("filament_nozzle_map")->values = filament_nozzle_maps;
|
||||
|
||||
std::vector<int> filament_volume_maps(filament_colors.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
project_config.option<ConfigOptionInts>("filament_volume_map")->values = filament_volume_maps;
|
||||
|
||||
std::vector<std::string> extruder_ams_count_str;
|
||||
if (config.has_printer_setting(initial_printer_profile_name, "extruder_ams_count")) {
|
||||
boost::algorithm::split(extruder_ams_count_str, config.get_printer_setting(initial_printer_profile_name, "extruder_ams_count"), boost::algorithm::is_any_of(","));
|
||||
@@ -2994,7 +3052,8 @@ void PresetBundle::set_num_filaments(unsigned int n, std::vector<std::string> ne
|
||||
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
|
||||
ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
|
||||
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map");
|
||||
|
||||
ConfigOptionInts* filament_nozzle_map = project_config.option<ConfigOptionInts>("filament_nozzle_map");
|
||||
ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
|
||||
|
||||
filament_color->resize(n);
|
||||
// Sync filament multi colour
|
||||
@@ -3004,6 +3063,8 @@ void PresetBundle::set_num_filaments(unsigned int n, std::vector<std::string> ne
|
||||
}
|
||||
filament_color_type->resize(n);
|
||||
filament_map->values.resize(n, 1);
|
||||
filament_nozzle_map->values.resize(n, 0);
|
||||
filament_volume_map->values.resize(n, static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
ams_multi_color_filment.resize(n);
|
||||
|
||||
// BBS set new filament color to new_color
|
||||
@@ -3031,7 +3092,8 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
|
||||
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
|
||||
ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
|
||||
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map");
|
||||
|
||||
ConfigOptionInts* filament_nozzle_map = project_config.option<ConfigOptionInts>("filament_nozzle_map");
|
||||
ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
|
||||
|
||||
filament_color->resize(n);
|
||||
// Sync filament multi colour
|
||||
@@ -3041,6 +3103,8 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
|
||||
}
|
||||
filament_color_type->resize(n);
|
||||
filament_map->values.resize(n, 1);
|
||||
filament_nozzle_map->values.resize(n, 0);
|
||||
filament_volume_map->values.resize(n, static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
ams_multi_color_filment.resize(n);
|
||||
|
||||
//BBS set new filament color to new_color
|
||||
@@ -3081,15 +3145,25 @@ void PresetBundle::update_num_filaments(unsigned int to_del_flament_id)
|
||||
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
|
||||
ConfigOptionStrings *filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
|
||||
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map");
|
||||
ConfigOptionInts* filament_nozzle_map = project_config.option<ConfigOptionInts>("filament_nozzle_map");
|
||||
ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
|
||||
if (filament_color->values.size() > to_del_flament_id) {
|
||||
filament_color->values.erase(filament_color->values.begin() + to_del_flament_id);
|
||||
if (filament_map->values.size() > to_del_flament_id) {
|
||||
filament_map->values.erase(filament_map->values.begin() + to_del_flament_id);
|
||||
}
|
||||
if (filament_nozzle_map->values.size() > to_del_flament_id) {
|
||||
filament_nozzle_map->values.erase(filament_nozzle_map->values.begin() + to_del_flament_id);
|
||||
}
|
||||
if (filament_volume_map->values.size() > to_del_flament_id) {
|
||||
filament_volume_map->values.erase(filament_volume_map->values.begin() + to_del_flament_id);
|
||||
}
|
||||
}
|
||||
else {
|
||||
filament_color->values.resize(to_del_flament_id);
|
||||
filament_map->values.resize(to_del_flament_id, 1);
|
||||
filament_nozzle_map->values.resize(to_del_flament_id, 0);
|
||||
filament_volume_map->values.resize(to_del_flament_id, static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
}
|
||||
|
||||
// lambda function to erase or resize the container
|
||||
@@ -3312,6 +3386,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
ConfigOptionStrings *filament_color = project_config.option<ConfigOptionStrings>("filament_colour");
|
||||
ConfigOptionStrings *filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
|
||||
ConfigOptionInts * filament_map = project_config.option<ConfigOptionInts>("filament_map");
|
||||
ConfigOptionInts * filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
|
||||
if (color_only) {
|
||||
auto get_map_index = [&ams_infos](const std::vector<AMSMapInfo> &infos, const AMSMapInfo &temp) {
|
||||
for (int i = 0; i < infos.size(); i++) {
|
||||
@@ -3493,6 +3568,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
ams_multi_color_filment = exist_multi_color_filment;
|
||||
this->filament_presets = exist_filament_presets;
|
||||
filament_map->values.resize(exist_filament_presets.size(), 1);
|
||||
filament_volume_map->values.resize(exist_filament_presets.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
}
|
||||
else {//overwrite;
|
||||
bool has_placeholders = std::any_of(ams_infos.begin(), ams_infos.end(),
|
||||
@@ -3547,12 +3623,14 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
this->filament_presets = result_presets;
|
||||
ams_multi_color_filment = result_multi_colors;
|
||||
filament_map->values.resize(total, 1);
|
||||
filament_volume_map->values.resize(total, static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
} else {
|
||||
// BBL: existing wholesale replace
|
||||
filament_color->values = ams_filament_colors;
|
||||
filament_color_type->values = ams_filament_color_types;
|
||||
this->filament_presets = ams_filament_presets;
|
||||
filament_map->values.resize(ams_filament_colors.size(), 1);
|
||||
filament_volume_map->values.resize(ams_filament_colors.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
}
|
||||
|
||||
auto& print_config = this->prints.get_edited_preset().config;
|
||||
@@ -3855,10 +3933,26 @@ bool PresetBundle::support_different_extruders() const
|
||||
return supported;
|
||||
}
|
||||
|
||||
DynamicPrintConfig PresetBundle::full_config(bool apply_extruder, std::optional<std::vector<int>>filament_maps) const
|
||||
std::vector<int> PresetBundle::get_default_nozzle_volume_types_for_filaments(std::vector<int>& f_maps)
|
||||
{
|
||||
std::vector<int> result;
|
||||
int filament_count = f_maps.size();
|
||||
result.resize(filament_count, static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
|
||||
auto opt_nozzle_volume_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(this->project_config.option("nozzle_volume_type"));
|
||||
for (int index = 0; index < filament_count; index++)
|
||||
{
|
||||
if (opt_nozzle_volume_type && opt_nozzle_volume_type->values.size() > (f_maps[index] - 1))
|
||||
result[index] = opt_nozzle_volume_type->values[f_maps[index] - 1];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DynamicPrintConfig PresetBundle::full_config(bool apply_extruder, std::optional<std::vector<int>>filament_maps, std::optional<std::vector<int>> filament_volume_maps) const
|
||||
{
|
||||
return (this->printers.get_edited_preset().printer_technology() == ptFFF) ?
|
||||
this->full_fff_config(apply_extruder, filament_maps) :
|
||||
this->full_fff_config(apply_extruder, filament_maps, filament_volume_maps) :
|
||||
this->full_sla_config();
|
||||
}
|
||||
|
||||
@@ -3872,16 +3966,52 @@ DynamicPrintConfig PresetBundle::full_config_secure(std::optional<std::vector<in
|
||||
config.erase("printhost_cafile");
|
||||
config.erase("printhost_user");
|
||||
config.erase("printhost_password");
|
||||
config.erase("printhost_port");
|
||||
config.erase("printhost_port");
|
||||
return config;
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::vector<float>>> PresetBundle::get_full_flush_matrix(bool with_multiplier) const
|
||||
{
|
||||
auto full_config = this->full_config();
|
||||
int extruder_nums = full_config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
|
||||
std::vector<double> flush_volume_value = full_config.option<ConfigOptionFloats>("flush_volumes_matrix")->values;
|
||||
int filament_nums = full_config.option<ConfigOptionStrings>("filament_type")->values.size();
|
||||
|
||||
std::vector<std::vector<std::vector<float>>> matrix;
|
||||
for (size_t extruder_id = 0; extruder_id < extruder_nums; ++extruder_id) {
|
||||
std::vector<float> flush_matrix(cast<float>(get_flush_volumes_matrix(flush_volume_value, extruder_id, extruder_nums)));
|
||||
std::vector<std::vector<float>> wipe_volumes;
|
||||
for (unsigned int i = 0; i < filament_nums; ++i)
|
||||
wipe_volumes.push_back(std::vector<float>(flush_matrix.begin() + i * filament_nums, flush_matrix.begin() + (i + 1) * filament_nums));
|
||||
|
||||
matrix.emplace_back(wipe_volumes);
|
||||
}
|
||||
|
||||
if (with_multiplier) {
|
||||
// Fast purge mode uses flush_multiplier_fast; the default prime_volume_mode==Default
|
||||
// (or the key absent) reads flush_multiplier, so this is inert.
|
||||
auto* mode_opt = project_config.option<ConfigOptionEnum<PrimeVolumeMode>>("prime_volume_mode");
|
||||
const bool use_fast = mode_opt && mode_opt->value == PrimeVolumeMode::pvmFast;
|
||||
auto* mult_opt = project_config.option<ConfigOptionFloats>(use_fast ? "flush_multiplier_fast" : "flush_multiplier");
|
||||
auto flush_multiplies = mult_opt ? mult_opt->values : project_config.option<ConfigOptionFloats>("flush_multiplier")->values;
|
||||
flush_multiplies.resize(extruder_nums, 1);
|
||||
for (size_t extruder_id = 0; extruder_id < extruder_nums; ++extruder_id) {
|
||||
for (auto& vec : matrix[extruder_id]) {
|
||||
for (auto& v : vec)
|
||||
v *= flush_multiplies[extruder_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
const std::set<std::string> ignore_settings_list ={
|
||||
"inherits",
|
||||
"print_settings_id", "filament_settings_id", "printer_settings_id"
|
||||
};
|
||||
|
||||
DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optional<std::vector<int>> filament_maps_new) const
|
||||
DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optional<std::vector<int>> filament_maps_new, std::optional<std::vector<int>> filament_volume_maps_new) const
|
||||
{
|
||||
DynamicPrintConfig out;
|
||||
out.apply(FullPrintConfig::defaults());
|
||||
@@ -3895,8 +4025,17 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
|
||||
size_t num_filaments = this->filament_presets.size();
|
||||
|
||||
std::vector<int> filament_maps = out.option<ConfigOptionInts>("filament_map")->values;
|
||||
std::vector<int> filament_volume_maps(num_filaments, (int)nvtStandard);
|
||||
|
||||
ConfigOptionInts* filament_volume_map_opt = out.option<ConfigOptionInts>("filament_volume_map");
|
||||
if (filament_maps_new.has_value())
|
||||
filament_maps = *filament_maps_new;
|
||||
if (filament_volume_maps_new.has_value()) {
|
||||
filament_volume_maps = *filament_volume_maps_new;
|
||||
out.option<ConfigOptionInts>("filament_volume_map", true)->values = filament_volume_maps;
|
||||
}
|
||||
else if (filament_volume_map_opt && filament_volume_map_opt->values.size() == num_filaments)
|
||||
filament_volume_maps = filament_volume_map_opt->values;
|
||||
//in some middle state, they may be different
|
||||
if (filament_maps.size() != num_filaments) {
|
||||
filament_maps.resize(num_filaments, 1);
|
||||
@@ -3904,6 +4043,9 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
|
||||
else {
|
||||
assert(filament_maps.size() == num_filaments);
|
||||
}
|
||||
if (filament_volume_maps.size() != num_filaments) {
|
||||
filament_volume_maps.resize(num_filaments, nvtStandard);
|
||||
}
|
||||
|
||||
auto* extruder_diameter = dynamic_cast<const ConfigOptionFloats*>(out.option("nozzle_diameter"));
|
||||
// Collect the "compatible_printers_condition" and "inherits" values over all presets (print, filaments, printers) into a single vector.
|
||||
@@ -3933,18 +4075,34 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
|
||||
different_settings.emplace_back(different_print_settings);
|
||||
|
||||
//BBS: update printer config related with variants
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 1, extruder_volume_type_count = 1;
|
||||
bool different_extruder = false;
|
||||
if (apply_extruder) {
|
||||
out.update_values_to_printer_extruders(out, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
|
||||
out.update_values_to_printer_extruders(out, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
|
||||
//update print config related with variants
|
||||
out.update_values_to_printer_extruders(out, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
different_extruder = out.support_different_extruders(extruder_count);
|
||||
extruder_volume_type_count = out.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
if ((extruder_count > 1) || different_extruder) {
|
||||
// Orca: keep processing variant_1 before variant_2 here; variant_2 slots are resolved
|
||||
// against the printer id/variant lists as rewritten by the variant_1 pass, and the
|
||||
// composed values depend on that order. Note the order is load-bearing, not correct
|
||||
// in general: the variant_2 pass reads the original full-width arrays through indices
|
||||
// resolved on the shrunk lists, which mis-reads presets whose variant_2 columns differ
|
||||
// per variant (e.g. X2D machine_max_speed_e/machine_max_acceleration_e). The slicing
|
||||
// path composes variant_2 first and is unaffected; changing the order here would alter
|
||||
// long-standing composed values, so any fix must re-baseline them.
|
||||
out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
|
||||
out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
|
||||
//update print config related with variants
|
||||
out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
}
|
||||
}
|
||||
|
||||
if (num_filaments <= 1) {
|
||||
//BBS: update filament config related with variants
|
||||
DynamicPrintConfig filament_config = this->filaments.get_edited_preset().config;
|
||||
if (apply_extruder)
|
||||
filament_config.update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0]);
|
||||
if (apply_extruder && ((extruder_count > 1) || different_extruder))
|
||||
filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0], (NozzleVolumeType)filament_volume_maps[0]);
|
||||
out.apply(filament_config);
|
||||
compatible_printers_condition.emplace_back(this->filaments.get_edited_preset().compatible_printers_condition());
|
||||
compatible_prints_condition .emplace_back(this->filaments.get_edited_preset().compatible_prints_condition());
|
||||
@@ -4037,8 +4195,8 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
|
||||
filament_temp_configs.resize(num_filaments);
|
||||
for (size_t i = 0; i < num_filaments; ++i) {
|
||||
filament_temp_configs[i] = *(filament_configs[i]);
|
||||
if (apply_extruder)
|
||||
filament_temp_configs[i].update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i]);
|
||||
if (apply_extruder && ((extruder_count > 1) || different_extruder))
|
||||
filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i], (NozzleVolumeType)filament_volume_maps[i]);
|
||||
}
|
||||
|
||||
// loop through options and apply them to the resulting config.
|
||||
@@ -4282,6 +4440,10 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
};
|
||||
clear_compatible_printers(config);
|
||||
|
||||
// Dynamic per-nozzle filament mapping reflects live device state, not a stored setting;
|
||||
// drop it from any imported config so it only comes from the connected printer.
|
||||
config.erase("enable_filament_dynamic_map");
|
||||
|
||||
#if 0
|
||||
size_t num_extruders = (printer_technology == ptFFF) ?
|
||||
std::min(config.option<ConfigOptionFloats>("nozzle_diameter" )->values.size(),
|
||||
@@ -4770,6 +4932,8 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
model.use_double_extruder_default_texture = it.value();
|
||||
} else if (boost::iequals(it.key(), BBL_JSON_KEY_BOTTOM_TEXTURE_RECT)) {
|
||||
model.bottom_texture_rect = it.value();
|
||||
} else if (boost::iequals(it.key(), BBL_JSON_KEY_BOTTOM_TEXTURE_RECT_LONGER)) {
|
||||
model.bottom_texture_rect_longer = it.value();
|
||||
} else if (boost::iequals(it.key(), BBL_JSON_KEY_MIDDLE_TEXTURE_RECT)) {
|
||||
model.middle_texture_rect = it.value();
|
||||
}
|
||||
|
||||
@@ -72,6 +72,25 @@ struct FilamentBaseInfo
|
||||
bool is_support{ false };
|
||||
bool is_system{ true };
|
||||
int filament_printable = 3;
|
||||
|
||||
// filament_extruder_compatibility packs one compatibility level per extruder into a single
|
||||
// 32-bit int, 3 bits per extruder (up to 10 extruders). Levels: 0 = printable, 1 = error,
|
||||
// 2 = critical warning, 3 = warning (4-7 reserved). extruder_id is 0-based.
|
||||
int get_extruder_compatibility(int extruder_id) const {
|
||||
constexpr int bits_per_extruder = 3;
|
||||
constexpr int extruder_mask = (1 << bits_per_extruder) - 1; // 0x7
|
||||
constexpr int max_extruder_count = 32 / bits_per_extruder; // 10
|
||||
|
||||
if (extruder_id < 0 || extruder_id >= max_extruder_count)
|
||||
return 0;
|
||||
return (m_filament_extruder_compatibility >> (bits_per_extruder * extruder_id)) & extruder_mask;
|
||||
}
|
||||
|
||||
void set_filament_extruder_compatibility(int value) { m_filament_extruder_compatibility = value; }
|
||||
int get_filament_extruder_compatibility() const { return m_filament_extruder_compatibility; }
|
||||
|
||||
private:
|
||||
int m_filament_extruder_compatibility = 0;
|
||||
};
|
||||
|
||||
enum BundleType{
|
||||
@@ -156,7 +175,8 @@ public:
|
||||
const DynamicPrintConfig &project_config,
|
||||
std::vector<Preset> &in_filament_presets,
|
||||
bool apply_extruder,
|
||||
std::optional<std::vector<int>> filament_maps_new);
|
||||
std::optional<std::vector<int>> filament_maps_new,
|
||||
std::optional<std::vector<int>> filament_volume_maps_new = std::nullopt);
|
||||
|
||||
// ORCA: utility function to find the vendor for a given preset name
|
||||
static std::string find_preset_vendor(const std::string& preset_name, Preset::Type type);
|
||||
@@ -364,10 +384,19 @@ public:
|
||||
bool has_defauls_only() const
|
||||
{ return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); }
|
||||
|
||||
DynamicPrintConfig full_config(bool apply_extruder = true, std::optional<std::vector<int>>filament_maps = std::nullopt) const;
|
||||
DynamicPrintConfig full_config(bool apply_extruder = true, std::optional<std::vector<int>>filament_maps = std::nullopt, std::optional<std::vector<int>> filament_volume_maps = std::nullopt) const;
|
||||
// full_config() with the some "useless" config removed.
|
||||
DynamicPrintConfig full_config_secure(std::optional<std::vector<int>>filament_maps = std::nullopt) const;
|
||||
|
||||
// Default per-filament nozzle-volume types: each filament inherits the volume type of the
|
||||
// extruder it maps to (1-based f_maps), Standard when unknown.
|
||||
std::vector<int> get_default_nozzle_volume_types_for_filaments(std::vector<int>& f_maps);
|
||||
|
||||
// Per-extruder flush matrix [extruder_id][from_filament][to_filament] in mm^3, optionally scaled
|
||||
// by the per-extruder flush_multiplier (or flush_multiplier_fast when prime_volume_mode==Fast).
|
||||
// Used by the print-dispatch nozzle-mapping flush-weight estimate.
|
||||
std::vector<std::vector<std::vector<float>>> get_full_flush_matrix(bool with_multiplier = true) const;
|
||||
|
||||
//BBS: add some functions for multiple extruders
|
||||
int get_printer_extruder_count() const;
|
||||
bool support_different_extruders() const;
|
||||
@@ -519,7 +548,7 @@ private:
|
||||
/*ConfigSubstitutions load_config_file_config_bundle(
|
||||
const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);*/
|
||||
|
||||
DynamicPrintConfig full_fff_config(bool apply_extruder, std::optional<std::vector<int>> filament_maps=std::nullopt) const;
|
||||
DynamicPrintConfig full_fff_config(bool apply_extruder, std::optional<std::vector<int>> filament_maps=std::nullopt, std::optional<std::vector<int>> filament_volume_maps=std::nullopt) const;
|
||||
DynamicPrintConfig full_sla_config() const;
|
||||
|
||||
// Orca: used for validation only
|
||||
|
||||
+504
-20
@@ -24,6 +24,7 @@
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <sstream>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
@@ -335,8 +336,11 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "other_layers_print_sequence_nums"
|
||||
|| opt_key == "toolchange_ordering"
|
||||
|| opt_key == "extruder_ams_count"
|
||||
|| opt_key == "extruder_nozzle_stats"
|
||||
|| opt_key == "filament_map_mode"
|
||||
|| opt_key == "filament_map"
|
||||
|| opt_key == "filament_nozzle_map"
|
||||
|| opt_key == "filament_volume_map"
|
||||
|| opt_key == "filament_adhesiveness_category"
|
||||
|| opt_key == "filament_tower_interface_pre_extrusion_dist"
|
||||
|| opt_key == "filament_tower_interface_pre_extrusion_length"
|
||||
@@ -2486,6 +2490,9 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
std::vector<const PrintInstance*>::const_iterator print_object_instance_sequential_active;
|
||||
std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> layers_to_print = GCode::collect_layers_to_print(*this);
|
||||
std::vector<unsigned int> printExtruders;
|
||||
// Cleared on every process so a print-sequence or selector-mode change can never leave
|
||||
// stale object pointers behind; repopulated below only by the sequential selector path.
|
||||
m_sequential_dynamic_orderings.clear();
|
||||
if (this->config().print_sequence == PrintSequence::ByObject) {
|
||||
// Order object instances for sequential print.
|
||||
print_object_instances_ordering = sort_object_instances_by_model_order(*this);
|
||||
@@ -2506,26 +2513,100 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
|
||||
auto physical_unprintables = this->get_physical_unprintable_filaments(used_filaments);
|
||||
auto geometric_unprintables = this->get_geometric_unprintable_filaments();
|
||||
std::vector<int>filament_maps = this->get_filament_maps();
|
||||
auto map_mode = get_filament_map_mode();
|
||||
// get recommended filament map
|
||||
if (map_mode < FilamentMapMode::fmmManual) {
|
||||
filament_maps = ToolOrdering::get_recommended_filament_maps(all_filaments, this, map_mode, physical_unprintables, geometric_unprintables);
|
||||
std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value + 1; });
|
||||
update_filament_maps_to_config(filament_maps);
|
||||
auto filament_unprintable_volumes = this->get_filament_unprintable_flow(used_filaments);
|
||||
// Selector (per-layer regroup) prints skip the static grouping: their print-wide result
|
||||
// is stitched from the per-object plans after the ordering loop below.
|
||||
const bool dynamic_reorder = this->is_dynamic_group_reorder();
|
||||
if (!dynamic_reorder) {
|
||||
std::vector<int>filament_maps = this->get_filament_maps();
|
||||
auto map_mode = get_filament_map_mode();
|
||||
// Grouping returns a nozzle-aware result; the 1-based extruder map for the by-object
|
||||
// path is derived from it. It is computed in every static map mode (in manual modes it
|
||||
// mirrors the user's assignment) and published print-wide: GCode's per-nozzle
|
||||
// placeholder and config-index lookups read it via get_layered_nozzle_group_result(),
|
||||
// and without it sequential exports on multi-nozzle printers see an empty nozzle table
|
||||
// (e.g. nozzle_diameter_at_nozzle_id[]) and custom g-code fails to resolve.
|
||||
auto grouping_result = ToolOrdering::get_recommended_filament_maps(all_filaments, this, map_mode, physical_unprintables, geometric_unprintables, filament_unprintable_volumes);
|
||||
this->set_nozzle_group_result(std::make_shared<MultiNozzleUtils::LayeredNozzleGroupResult>(grouping_result));
|
||||
// Orca: the sequential write-back stays gated to auto modes. In manual modes the
|
||||
// config maps already carry the user's assignment (the per-object ToolOrdering below
|
||||
// consumes them directly), so a write-back would only re-store the pre-slice values;
|
||||
// keeping the gate avoids churning the config on every sequential manual slice.
|
||||
if (map_mode < FilamentMapMode::fmmManual) {
|
||||
auto derived_maps = grouping_result.get_extruder_map(false);
|
||||
if (!derived_maps.empty()) {
|
||||
filament_maps = derived_maps;
|
||||
// Write the maps back: used filaments adopt the engine's extruder/nozzle
|
||||
// choice, unused ones keep their config assignment.
|
||||
// Orca: the config maps are the merge base; fall back to a synthesized base
|
||||
// when no producer sized them to the filament count (CLI runs until the
|
||||
// per-filament synthesis lands there), where indexing per filament would
|
||||
// run out of bounds.
|
||||
std::vector<int> base_filament_map = m_config.filament_map.values;
|
||||
if (base_filament_map.size() != derived_maps.size())
|
||||
base_filament_map.assign(derived_maps.size(), 1);
|
||||
std::vector<int> base_volume_map = m_config.filament_volume_map.values;
|
||||
if (base_volume_map.size() != derived_maps.size())
|
||||
base_volume_map.assign(derived_maps.size(), (int)nvtStandard);
|
||||
update_filament_maps_to_config(FilamentGroupUtils::update_used_filament_values(base_filament_map, derived_maps, used_filaments),
|
||||
FilamentGroupUtils::update_used_filament_values(base_volume_map, grouping_result.get_volume_map(), used_filaments),
|
||||
grouping_result.get_nozzle_map());
|
||||
}
|
||||
}
|
||||
// check map valid both in auto and mannual mode
|
||||
std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) {return value - 1; });
|
||||
}
|
||||
// check map valid both in auto and mannual mode
|
||||
std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) {return value - 1; });
|
||||
|
||||
// print_object_instances_ordering = sort_object_instances_by_max_z(print);
|
||||
const PrintObject *prev_planned_object = nullptr;
|
||||
unsigned int seq_last_extruder = (unsigned int)-1;
|
||||
MultiNozzleUtils::NozzleStatusRecorder nozzle_status;
|
||||
std::vector<std::vector<int>> nozzle_map_per_layer;
|
||||
std::vector<std::vector<unsigned int>> stitched_layer_filaments;
|
||||
print_object_instance_sequential_active = print_object_instances_ordering.begin();
|
||||
for (; 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.sort_and_build_data(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id);
|
||||
const PrintObject *print_object = (*print_object_instance_sequential_active)->print_object;
|
||||
if (dynamic_reorder) {
|
||||
if (print_object != prev_planned_object) {
|
||||
// Plan each unique object once, threading the physical nozzle occupancy and
|
||||
// the previous object's last filament into the next plan; repeated instances
|
||||
// of an object reuse the plan, mirroring the export loop's reuse.
|
||||
ToolOrdering ordering(*print_object, seq_last_extruder);
|
||||
ordering.set_nozzle_status(nozzle_status);
|
||||
ordering.sort_and_build_data(*print_object, seq_last_extruder);
|
||||
nozzle_status = ordering.get_nozzle_status();
|
||||
if (ordering.last_extruder() != static_cast<unsigned int>(-1))
|
||||
seq_last_extruder = ordering.last_extruder();
|
||||
const auto &object_maps = ordering.get_layered_nozzle_group_result().get_layer_filament_nozzle_maps();
|
||||
nozzle_map_per_layer.insert(nozzle_map_per_layer.end(), object_maps.begin(), object_maps.end());
|
||||
// Orca: the stitch input comes from the same orderings that produced the
|
||||
// per-layer maps — the collection loop above is per-instance and seeded -1,
|
||||
// so its layers are misaligned with these plans. layer_tools() of a sorted
|
||||
// ordering already carries the planned per-layer filament order.
|
||||
for (const auto &layer_tool : ordering.layer_tools())
|
||||
stitched_layer_filaments.emplace_back(layer_tool.extruders);
|
||||
m_sequential_dynamic_orderings[print_object] = std::move(ordering);
|
||||
prev_planned_object = print_object;
|
||||
}
|
||||
tool_ordering = m_sequential_dynamic_orderings.at(print_object);
|
||||
} else {
|
||||
tool_ordering = ToolOrdering(*print_object, initial_extruder_id);
|
||||
tool_ordering.sort_and_build_data(*print_object, initial_extruder_id);
|
||||
}
|
||||
if ((initial_extruder_id = tool_ordering.first_extruder()) != static_cast<unsigned int>(-1)) {
|
||||
append(printExtruders, tool_ordering.tools_for_layer(layers_to_print.front().first).extruders);
|
||||
}
|
||||
}
|
||||
if (dynamic_reorder && m_objects.size() > 1) {
|
||||
// Stitch the per-object plans into one print-wide selector result. A single-object
|
||||
// sequential print publishes (and writes back) from its own ordering instead: the
|
||||
// per-object publish gate treats one object as not sequential.
|
||||
auto stitched = ToolOrdering::build_sequential_group_result(this, std::move(nozzle_map_per_layer), stitched_layer_filaments,
|
||||
stitched_layer_filaments, used_filaments, physical_unprintables,
|
||||
geometric_unprintables, filament_unprintable_volumes);
|
||||
this->set_nozzle_group_result(std::make_shared<MultiNozzleUtils::LayeredNozzleGroupResult>(stitched));
|
||||
update_to_config_by_nozzle_group_result(stitched);
|
||||
}
|
||||
}
|
||||
else {
|
||||
tool_ordering = this->tool_ordering();
|
||||
@@ -2662,8 +2743,14 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
|
||||
gcode.do_export(this, path.c_str(), result, thumbnail_cb);
|
||||
gcode.export_layer_filaments(result);
|
||||
//BBS
|
||||
if (result != nullptr)
|
||||
if (result != nullptr) {
|
||||
result->conflict_result = m_conflict_result;
|
||||
// Surface the slicer's per-filament nozzle grouping onto the post-slice result
|
||||
// the device GUI reads. This is the static L/R + rack subset the multi-nozzle path computes;
|
||||
// null for single-nozzle prints where nothing computes it. It is assigned after g-code
|
||||
// generation and read by no emitter, so it does not affect the emitted g-code.
|
||||
result->nozzle_group_result = this->get_layered_nozzle_group_result();
|
||||
}
|
||||
return path.c_str();
|
||||
}
|
||||
|
||||
@@ -3187,16 +3274,70 @@ void Print::finalize_first_layer_convex_hull()
|
||||
m_first_layer_convex_hull = Geometry::convex_hull(m_first_layer_convex_hull.points);
|
||||
}
|
||||
|
||||
void Print::update_filament_maps_to_config(std::vector<int> f_maps)
|
||||
void Print::update_filament_maps_to_config(std::vector<int> f_maps, std::vector<int> f_volume_maps, std::vector<int> f_nozzle_maps)
|
||||
{
|
||||
if (m_config.filament_map.values != f_maps)
|
||||
if ((m_config.filament_map.values != f_maps) || (m_config.filament_volume_map.values != f_volume_maps) || (m_config.filament_nozzle_map.values != f_nozzle_maps))
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament maps changed after pre-slicing.");
|
||||
m_ori_full_print_config.option<ConfigOptionInts>("filament_map", true)->values = f_maps;
|
||||
m_config.filament_map.values = f_maps;
|
||||
|
||||
if (!f_volume_maps.empty()) {
|
||||
m_ori_full_print_config.option<ConfigOptionInts>("filament_volume_map", true)->values = f_volume_maps;
|
||||
m_config.filament_volume_map.values = f_volume_maps;
|
||||
}
|
||||
else {
|
||||
m_ori_full_print_config.option<ConfigOptionInts>("filament_volume_map", true)->values.resize(f_maps.size(), nvtStandard);
|
||||
m_config.filament_volume_map.values.resize(f_maps.size(), nvtStandard);
|
||||
}
|
||||
|
||||
if (!f_nozzle_maps.empty()) {
|
||||
m_ori_full_print_config.option<ConfigOptionInts>("filament_nozzle_map", true)->values = f_nozzle_maps;
|
||||
m_config.filament_nozzle_map.values = f_nozzle_maps;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int extruder_count = 1, extruder_volume_type_count = 1;
|
||||
bool support_multi = m_ori_full_print_config.support_different_extruders(extruder_count);
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
extruder_volume_type_count = m_ori_full_print_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
//filament_map_2
|
||||
// Orca: seed with 0-based extruder indices so the override keying below degenerates to the
|
||||
// plain per-extruder slot when the rebuild loop is skipped; the loop overwrites every
|
||||
// entry when it runs.
|
||||
m_config.filament_map_2.values = f_maps;
|
||||
for (auto& v : m_config.filament_map_2.values)
|
||||
--v;
|
||||
auto opt_extruder_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(m_ori_full_print_config.option("extruder_type"));
|
||||
auto opt_nozzle_volume_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(m_ori_full_print_config.option("nozzle_volume_type"));
|
||||
// Orca: the loop tolerates configs without the extruder options (unit tests, degenerate
|
||||
// presets); the backfill and the override are bounds-checked because the change block above
|
||||
// is skipped when the maps are unchanged, in which case the stored map may be shorter than
|
||||
// the filament count.
|
||||
auto* ori_volume_map = m_ori_full_print_config.option<ConfigOptionInts>("filament_volume_map", true);
|
||||
for (int index = 0; opt_extruder_type && opt_nozzle_volume_type && index < f_maps.size(); index++)
|
||||
{
|
||||
ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(f_maps[index] - 1));
|
||||
NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(f_maps[index] - 1));
|
||||
if (f_volume_maps.empty()) {
|
||||
// No per-filament map supplied: backfill from the extruder's own volume type.
|
||||
if (m_config.filament_volume_map.values.size() > index)
|
||||
m_config.filament_volume_map.values[index] = nozzle_volume_type;
|
||||
if (ori_volume_map->values.size() > index)
|
||||
ori_volume_map->values[index] = nozzle_volume_type;
|
||||
}
|
||||
else if ((extruder_volume_type_count > extruder_count) && (m_config.filament_volume_map.values.size() > index))
|
||||
nozzle_volume_type = (NozzleVolumeType)(m_config.filament_volume_map.values[index]);
|
||||
m_config.filament_map_2.values[index] = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
}
|
||||
|
||||
m_full_print_config = m_ori_full_print_config;
|
||||
m_full_print_config.update_values_to_printer_extruders_for_multiple_filaments(m_full_print_config, filament_options_with_variant, "filament_self_index", "filament_extruder_variant");
|
||||
std::set<std::string> filament_keys = filament_options_with_variant;
|
||||
filament_keys.insert("filament_self_index");
|
||||
if ((extruder_count > 1) || support_multi)
|
||||
m_full_print_config.update_values_to_printer_extruders_for_multiple_filaments(m_full_print_config, extruder_count, extruder_volume_type_count, filament_keys, "filament_self_index", "filament_extruder_variant");
|
||||
|
||||
const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
|
||||
const std::string filament_prefix = "filament_";
|
||||
@@ -3209,16 +3350,147 @@ void Print::update_filament_maps_to_config(std::vector<int> f_maps)
|
||||
const ConfigOption *opt_old_machine = m_config.option(opt_key);
|
||||
|
||||
if (opt_new_filament)
|
||||
compute_filament_override_value(opt_key, opt_old_machine, opt_new_machine, opt_new_filament, m_full_print_config, print_diff, filament_overrides, f_maps);
|
||||
compute_filament_override_value(opt_key, opt_old_machine, opt_new_machine, opt_new_filament, m_full_print_config, print_diff, filament_overrides, m_config.filament_map_2.values);
|
||||
}
|
||||
|
||||
t_config_option_keys keys(filament_options_with_variant.begin(), filament_options_with_variant.end());
|
||||
m_config.apply_only(m_full_print_config, keys, true);
|
||||
if ((extruder_count > 1) || support_multi) {
|
||||
t_config_option_keys keys(filament_options_with_variant.begin(), filament_options_with_variant.end());
|
||||
keys.push_back("filament_self_index");
|
||||
m_config.apply_only(m_full_print_config, keys, true);
|
||||
}
|
||||
if (!print_diff.empty()) {
|
||||
m_placeholder_parser.apply_config(filament_overrides);
|
||||
m_config.apply(filament_overrides);
|
||||
}
|
||||
}
|
||||
update_filament_self_index_cache();
|
||||
m_has_auto_filament_map_result = true;
|
||||
}
|
||||
|
||||
bool Print::collect_filament_variant_uses(const MultiNozzleUtils::LayeredNozzleGroupResult& group_result,
|
||||
const DynamicPrintConfig& config,
|
||||
std::unordered_map<int, std::vector<FilamentVariantUse>>& uses) const
|
||||
{
|
||||
auto opt_filament_type = config.option<ConfigOptionStrings>("filament_type");
|
||||
auto opt_extruder_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(config.option("extruder_type"));
|
||||
if (!opt_filament_type || !opt_extruder_type)
|
||||
return false;
|
||||
|
||||
const size_t filament_count = opt_filament_type->values.size();
|
||||
const size_t extruder_count = opt_extruder_type->values.size();
|
||||
auto add_use = [&](std::set<FilamentVariantUse> &variant_set, const MultiNozzleUtils::NozzleInfo &nozzle) {
|
||||
// Orca: a persisted result can outlive a printer swap; never index the extruder
|
||||
// arrays with a stale nozzle record.
|
||||
if (nozzle.extruder_id < 0 || static_cast<size_t>(nozzle.extruder_id) >= extruder_count)
|
||||
return;
|
||||
FilamentVariantUse use;
|
||||
use.extruder_type = static_cast<ExtruderType>(opt_extruder_type->get_at(nozzle.extruder_id));
|
||||
use.nozzle_volume_type = nozzle.volume_type;
|
||||
use.extruder_id = nozzle.extruder_id;
|
||||
variant_set.insert(use);
|
||||
};
|
||||
for (size_t f_index = 0; f_index < filament_count; ++f_index) {
|
||||
std::set<FilamentVariantUse> variant_set;
|
||||
for (const MultiNozzleUtils::NozzleInfo &nozzle : group_result.get_nozzles_for_filament(static_cast<int>(f_index)))
|
||||
add_use(variant_set, nozzle);
|
||||
// A filament the plan never routes (not printed) still needs a deterministic slot: take
|
||||
// its default-map assignment from the result itself, so both the slice-time write-back
|
||||
// and the apply-time reproduction resolve the same slot even when the surrounding
|
||||
// filament_map has not round-tripped through the plate config in between.
|
||||
if (variant_set.empty()) {
|
||||
if (auto default_nozzle = group_result.get_nozzle_for_filament(static_cast<int>(f_index), -1); default_nozzle.has_value())
|
||||
add_use(variant_set, *default_nozzle);
|
||||
}
|
||||
// Filaments still without a variant stay absent from the map: the slot rebuild then
|
||||
// resolves them from their static filament_map / filament_volume_map assignment.
|
||||
if (!variant_set.empty())
|
||||
uses[static_cast<int>(f_index)] = std::vector<FilamentVariantUse>(variant_set.begin(), variant_set.end());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Print::update_to_config_by_nozzle_group_result(const MultiNozzleUtils::LayeredNozzleGroupResult& group_result)
|
||||
{
|
||||
std::vector<int> derived_maps = group_result.get_extruder_map(false); // 1-based
|
||||
if (derived_maps.empty())
|
||||
return;
|
||||
|
||||
if (!group_result.is_support_dynamic_nozzle_map()) {
|
||||
// No filament actually migrated between nozzles, so the plan reduces to a single
|
||||
// grouping: write all maps like the static paths do, and the next apply re-derives
|
||||
// identical slots from the written maps.
|
||||
std::vector<int> base_filament_map = m_config.filament_map.values;
|
||||
if (base_filament_map.size() != derived_maps.size())
|
||||
base_filament_map.assign(derived_maps.size(), 1);
|
||||
std::vector<int> base_volume_map = m_config.filament_volume_map.values;
|
||||
if (base_volume_map.size() != derived_maps.size())
|
||||
base_volume_map.assign(derived_maps.size(), (int)nvtStandard);
|
||||
const std::vector<unsigned int> used_filaments = group_result.get_used_filaments();
|
||||
update_filament_maps_to_config(FilamentGroupUtils::update_used_filament_values(base_filament_map, derived_maps, used_filaments),
|
||||
FilamentGroupUtils::update_used_filament_values(base_volume_map, group_result.get_volume_map(), used_filaments),
|
||||
group_result.get_nozzle_map());
|
||||
return;
|
||||
}
|
||||
|
||||
// Orca: keep the coarse per-filament extruder map published even though the per-layer truth
|
||||
// lives in the grouping result: the pre-export consumers, the plate read-back after slicing
|
||||
// and the preview panel all key on filament_map. The write is direct — the full map
|
||||
// write-back's single-slot rebuild would undo the per-variant expansion below.
|
||||
m_ori_full_print_config.option<ConfigOptionInts>("filament_map", true)->values = derived_maps;
|
||||
m_config.filament_map.values = derived_maps;
|
||||
|
||||
std::unordered_map<int, std::vector<FilamentVariantUse>> filament_variant_uses;
|
||||
if (!collect_filament_variant_uses(group_result, m_ori_full_print_config, filament_variant_uses)) {
|
||||
// Degenerate config (no filament/extruder typing): fall back to the single-slot
|
||||
// write-back so the maps and overrides stay coherent.
|
||||
update_filament_maps_to_config(derived_maps);
|
||||
return;
|
||||
}
|
||||
|
||||
int extruder_count = 1, extruder_volume_type_count = 1;
|
||||
m_ori_full_print_config.support_different_extruders(extruder_count);
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
extruder_volume_type_count = m_ori_full_print_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
// Note: filament_map_2 keeps its apply-time (static) derivation here; the per-slot machine
|
||||
// indices below key the override merge instead, so nothing on this path reads it. Its other
|
||||
// consumers are the three-map write-back (which recomputes it) and the diagnostic copy in
|
||||
// the g-code header; the time estimator resolves per-(extruder x volume-type) machine limits
|
||||
// from the live nozzle occupancy instead (see GCodeProcessor::get_machine_config_idx).
|
||||
m_full_print_config = m_ori_full_print_config;
|
||||
std::set<std::string> filament_keys = filament_options_with_variant;
|
||||
filament_keys.insert("filament_self_index");
|
||||
std::vector<int> slot_machine_indices;
|
||||
m_full_print_config.update_filament_config_values_for_multiple_extruders(m_full_print_config, filament_variant_uses,
|
||||
extruder_count, extruder_volume_type_count,
|
||||
filament_keys, "filament_self_index", "filament_extruder_variant",
|
||||
&slot_machine_indices);
|
||||
|
||||
const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
|
||||
const std::string filament_prefix = "filament_";
|
||||
t_config_option_keys print_diff;
|
||||
DynamicPrintConfig filament_overrides;
|
||||
for (auto& opt_key: extruder_retract_keys)
|
||||
{
|
||||
const ConfigOption *opt_new_filament = m_full_print_config.option(filament_prefix + opt_key);
|
||||
const ConfigOption *opt_new_machine = m_full_print_config.option(opt_key);
|
||||
const ConfigOption *opt_old_machine = m_config.option(opt_key);
|
||||
|
||||
if (opt_new_filament)
|
||||
compute_filament_override_value(opt_key, opt_old_machine, opt_new_machine, opt_new_filament, m_full_print_config, print_diff, filament_overrides, slot_machine_indices);
|
||||
}
|
||||
|
||||
{
|
||||
t_config_option_keys keys(filament_options_with_variant.begin(), filament_options_with_variant.end());
|
||||
keys.push_back("filament_self_index");
|
||||
m_config.apply_only(m_full_print_config, keys, true);
|
||||
}
|
||||
if (!print_diff.empty()) {
|
||||
m_placeholder_parser.apply_config(filament_overrides);
|
||||
m_config.apply(filament_overrides);
|
||||
}
|
||||
|
||||
update_filament_self_index_cache();
|
||||
m_has_auto_filament_map_result = true;
|
||||
}
|
||||
|
||||
@@ -3232,6 +3504,16 @@ std::vector<int> Print::get_filament_maps() const
|
||||
return m_config.filament_map.values;
|
||||
}
|
||||
|
||||
std::vector<int> Print::get_filament_nozzle_maps() const
|
||||
{
|
||||
return m_config.filament_nozzle_map.values;
|
||||
}
|
||||
|
||||
std::vector<int> Print::get_filament_volume_maps() const
|
||||
{
|
||||
return m_config.filament_volume_map.values;
|
||||
}
|
||||
|
||||
FilamentMapMode Print::get_filament_map_mode() const
|
||||
{
|
||||
return m_config.filament_map_mode;
|
||||
@@ -3271,6 +3553,41 @@ std::vector<std::set<int>> Print::get_physical_unprintable_filaments(const std::
|
||||
return physical_unprintables;
|
||||
}
|
||||
|
||||
std::map<int, std::set<NozzleVolumeType>> Print::get_filament_unprintable_flow(const std::vector<unsigned int> &used_filaments) const
|
||||
{
|
||||
std::map<int, std::set<NozzleVolumeType>> ret;
|
||||
std::vector<std::string> extruder_variant_list = m_config.printer_extruder_variant.values;
|
||||
// A filament that declares no extruder variants carries no flow restriction.
|
||||
const ConfigOptionStrings *filament_variant_opt = m_ori_full_print_config.option<ConfigOptionStrings>("filament_extruder_variant");
|
||||
if (filament_variant_opt == nullptr)
|
||||
return ret;
|
||||
std::vector<std::string> filament_variant_list = filament_variant_opt->values;
|
||||
std::vector<int> filament_self_index;
|
||||
if (!m_ori_full_print_config.has("filament_self_index"))
|
||||
filament_self_index.resize(filament_variant_list.size(), 1);
|
||||
else
|
||||
filament_self_index = m_ori_full_print_config.option<ConfigOptionInts>("filament_self_index")->values;
|
||||
std::unordered_set<int> used_fils_set(used_filaments.begin(), used_filaments.end());
|
||||
|
||||
std::unordered_map<int, std::set<NozzleVolumeType>> filament_variant_map;
|
||||
for(int i = 0; i < filament_variant_list.size(); ++i){
|
||||
NozzleVolumeType volume = convert_to_nvt_type(filament_variant_list[i]);
|
||||
if(volume != nvtHybrid) filament_variant_map[filament_self_index[i]].insert(volume);
|
||||
}
|
||||
|
||||
for (auto iter : filament_variant_map) {
|
||||
int fil_idx = iter.first - 1;
|
||||
if (used_fils_set.find(fil_idx) == used_fils_set.end()) continue;
|
||||
const std::set<NozzleVolumeType> &volumes = iter.second;
|
||||
for (int exd_idx = 0; exd_idx < extruder_variant_list.size(); ++exd_idx) {
|
||||
auto exd_volume = convert_to_nvt_type(extruder_variant_list[exd_idx]);
|
||||
assert(exd_volume != nvtHybrid);
|
||||
if (volumes.find(exd_volume) == volumes.end() && exd_volume != nvtHybrid) ret[fil_idx].insert(exd_volume);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
std::vector<double> Print::get_extruder_printable_height() const
|
||||
{
|
||||
@@ -3310,6 +3627,150 @@ size_t Print::get_extruder_id(unsigned int filament_id) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Region reachable by every extruder = intersection of all per-extruder printable areas.
|
||||
// For single-nozzle printers, or whenever extruder_printable_area is unpopulated / degenerate (all
|
||||
// current single/dual profiles), fall back to the full printable_area so the wipe-tower-center clamp
|
||||
// is identical to the previous full-bed clamp.
|
||||
Polygons Print::get_extruder_shared_printable_polygon() const
|
||||
{
|
||||
const std::vector<Vec2ds>& extruder_printable_areas = m_config.extruder_printable_area.values;
|
||||
if (m_config.nozzle_diameter.size() < 2 || extruder_printable_areas.empty())
|
||||
return {Polygon::new_scale(m_config.printable_area.values)};
|
||||
for (const Vec2ds& area : extruder_printable_areas)
|
||||
if (area.size() < 3)
|
||||
return {Polygon::new_scale(m_config.printable_area.values)};
|
||||
|
||||
Polygons shared_printable_polys = {Polygon::new_scale(extruder_printable_areas.front())};
|
||||
for (size_t i = 1; i < extruder_printable_areas.size(); ++i)
|
||||
shared_printable_polys = intersection(shared_printable_polys, Polygons{Polygon::new_scale(extruder_printable_areas[i])});
|
||||
return shared_printable_polys;
|
||||
}
|
||||
|
||||
// Narrow the stored grouping result to the layer-aware type the slicing pipeline uses.
|
||||
std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> Print::get_layered_nozzle_group_result() const
|
||||
{
|
||||
return std::dynamic_pointer_cast<MultiNozzleUtils::LayeredNozzleGroupResult>(m_nozzle_group_result);
|
||||
}
|
||||
|
||||
// Dynamic (per-layer selector) regroup predicate.
|
||||
// Orca: enable_filament_dynamic_map is a project flag registered in the ConfigDef but NOT a static
|
||||
// PrintConfig member, so it is read from the applied full config. No profile sets it; it is turned
|
||||
// on per project by the "smart filament assign" checkbox (shown when a filament track switch is
|
||||
// ready), so absent-key -> nullptr -> false keeps the static grouping path (identical output) for
|
||||
// everything else. There is no mixed-colour-filament guard (mixed-colour filaments are not
|
||||
// supported). The remaining gates (auto-for-flush mode, multi-extruder machine) read the static
|
||||
// PrintConfig members.
|
||||
bool Print::is_dynamic_group_reorder() const
|
||||
{
|
||||
const auto *opt = m_full_print_config.option<ConfigOptionBool>("enable_filament_dynamic_map");
|
||||
const bool enabled = opt && opt->value;
|
||||
if (!enabled || m_config.filament_map_mode != FilamentMapMode::fmmAutoForFlush || m_config.nozzle_diameter.size() <= 1)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int Print::get_filament_config_indx(int filament_id, int layer_id)
|
||||
{
|
||||
return get_config_index(filament_id, layer_id, m_config.filament_extruder_variant.values, m_filament_self_index, m_filament_index_map);
|
||||
}
|
||||
|
||||
void Print::update_filament_self_index_cache()
|
||||
{
|
||||
std::vector<int> values;
|
||||
if (m_full_print_config.has("filament_self_index")) {
|
||||
values = m_full_print_config.option<ConfigOptionInts>("filament_self_index")->values;
|
||||
} else if (m_ori_full_print_config.has("filament_self_index")) {
|
||||
values = m_ori_full_print_config.option<ConfigOptionInts>("filament_self_index")->values;
|
||||
} else {
|
||||
values = m_config.filament_self_index.values;
|
||||
}
|
||||
|
||||
size_t expected_size = m_config.filament_extruder_variant.values.size();
|
||||
m_filament_self_index.clear();
|
||||
if (expected_size == 0) {
|
||||
m_filament_index_map.clear();
|
||||
m_nozzle_index_map.clear();
|
||||
return;
|
||||
}
|
||||
m_filament_self_index.resize(expected_size, 1);
|
||||
if (!values.empty()) {
|
||||
for (size_t i = 0; i < expected_size; ++i) {
|
||||
int v = i < values.size() ? values[i] : 1;
|
||||
if (v <= 0)
|
||||
v = 1;
|
||||
m_filament_self_index[i] = v;
|
||||
}
|
||||
}
|
||||
m_filament_index_map.clear();
|
||||
m_nozzle_index_map.clear();
|
||||
}
|
||||
|
||||
int Print::get_nozzle_config_index(int filament_id, int layer_id)
|
||||
{
|
||||
// Orca: print_extruder_id/print_extruder_variant are PrintRegionConfig members in this codebase;
|
||||
// the process-wide expanded values live in the default region config (regions never override them).
|
||||
return get_config_index(filament_id, layer_id, m_default_region_config.print_extruder_variant.values, m_default_region_config.print_extruder_id.values, m_nozzle_index_map);
|
||||
}
|
||||
|
||||
int Print::get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, FilamentIndexMap &index_map)
|
||||
{
|
||||
auto group_result = get_layered_nozzle_group_result();
|
||||
// Orca: defensive — when no grouping producer has published a result yet, fall back to the
|
||||
// static identity: one filament-variant column per filament.
|
||||
if (!group_result)
|
||||
return filament_id;
|
||||
auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_id);
|
||||
if (!nozzle_info.has_value()) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__
|
||||
<< boost::format(", Line %1%: could not found group_nozzle_info corresponding to filament_id %2%, layer_id %3%") % __LINE__ % filament_id %
|
||||
layer_id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ExtruderType extruder_type = ExtruderType(m_config.extruder_type.get_at(nozzle_info->extruder_id));
|
||||
NozzleVolumeType nozzle_volume_type = nozzle_info->volume_type;
|
||||
|
||||
FilamentIndexKey key{filament_id, extruder_type, nozzle_volume_type};
|
||||
auto iter = index_map.find(key);
|
||||
if (iter == index_map.end()) {
|
||||
int index = get_config_index_base(nozzle_volume_type, extruder_type, filament_id + 1, variant_list, self_index_list);
|
||||
index_map[key] = index;
|
||||
return index;
|
||||
} else {
|
||||
return index_map[key];
|
||||
}
|
||||
}
|
||||
|
||||
int Print::get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, PrintIndexMap &index_map)
|
||||
{
|
||||
auto group_result = get_layered_nozzle_group_result();
|
||||
// Orca: same static fallback as the filament overload; the slot degenerates to the filament's
|
||||
// extruder column (filament_map is 1 based, get_extruder_id guards the filament id range).
|
||||
if (!group_result)
|
||||
return (int)get_extruder_id(filament_id);
|
||||
auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_id);
|
||||
if (!nozzle_info.has_value()) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__
|
||||
<< boost::format(", Line %1%: could not found group_nozzle_info corresponding to filament_id %2%, layer_id %3%") % __LINE__ % filament_id %
|
||||
layer_id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int extruder_id = nozzle_info->extruder_id + 1; // to 1 based
|
||||
ExtruderType extruder_type = ExtruderType(m_config.extruder_type.get_at(nozzle_info->extruder_id));
|
||||
NozzleVolumeType nozzle_volume_type = nozzle_info->volume_type;
|
||||
|
||||
PrintIndexKey key{filament_id, extruder_id, extruder_type, nozzle_volume_type};
|
||||
auto iter = index_map.find(key);
|
||||
if (iter == index_map.end()) {
|
||||
int index = get_config_index_base(nozzle_volume_type, extruder_type, extruder_id, variant_list, self_index_list);
|
||||
index_map[key] = index;
|
||||
return index;
|
||||
} else {
|
||||
return index_map[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Wipe tower support.
|
||||
bool Print::has_wipe_tower() const
|
||||
{
|
||||
@@ -3470,6 +3931,14 @@ void Print::_make_wipe_tower()
|
||||
m_wipe_tower_data.tool_ordering.empty() ? 0.f : m_wipe_tower_data.tool_ordering.back().print_z, m_wipe_tower_data.tool_ordering.all_extruders());
|
||||
wipe_tower.set_has_tpu_filament(this->has_tpu_filament());
|
||||
wipe_tower.set_filament_map(this->get_filament_maps());
|
||||
// Feed the has_filament_switcher device flag (develop-only dynamic key, read defensively from
|
||||
// the full config — no shipping profile sets it) and the shared printable bed used by the PETG
|
||||
// pre-extrusion offset clamp. Both are inert unless has_filament_switcher is set.
|
||||
{
|
||||
const ConfigOptionBool* hfs = m_full_print_config.option<ConfigOptionBool>("has_filament_switcher");
|
||||
wipe_tower.set_has_filament_switcher(hfs && hfs->value);
|
||||
}
|
||||
wipe_tower.set_shared_print_bed(this->get_extruder_shared_printable_polygon());
|
||||
// Set the extruder & material properties at the wipe tower object.
|
||||
for (size_t i = 0; i < number_of_extruders; ++i)
|
||||
wipe_tower.set_extruder(i, m_config);
|
||||
@@ -3521,7 +3990,10 @@ void Print::_make_wipe_tower()
|
||||
float volume_to_purge = 0;
|
||||
if (pre_filament_id != (unsigned int)(-1) && pre_filament_id != filament_id) {
|
||||
volume_to_purge = multi_extruder_flush[nozzle_id][pre_filament_id][filament_id];
|
||||
volume_to_purge *= m_config.flush_multiplier.get_at(nozzle_id);
|
||||
// Fast purge mode uses flush_multiplier_fast; Default is inert.
|
||||
float flush_multiplier = (m_config.prime_volume_mode == PrimeVolumeMode::pvmFast) ? m_config.flush_multiplier_fast.get_at(nozzle_id)
|
||||
: m_config.flush_multiplier.get_at(nozzle_id);
|
||||
volume_to_purge *= flush_multiplier;
|
||||
volume_to_purge = pre_filament_id == -1 ? 0 :
|
||||
layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_filament_id, filament_id, volume_to_purge);
|
||||
}
|
||||
@@ -3530,8 +4002,10 @@ void Print::_make_wipe_tower()
|
||||
float grab_purge_volume = m_config.grab_length.get_at(nozzle_id) * 2.4; //(diameter/2)^2*PI=2.4
|
||||
volume_to_purge = std::max(0.f, volume_to_purge - grab_purge_volume);
|
||||
|
||||
// Saving mode reduces the prime volume to 15 mm3; Default is inert.
|
||||
float prime_volume = (m_config.prime_volume_mode == PrimeVolumeMode::pvmSaving) ? 15.f : (float) m_config.prime_volume;
|
||||
wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, filament_id,
|
||||
m_config.prime_volume, volume_to_purge);
|
||||
prime_volume, volume_to_purge);
|
||||
current_filament_id = filament_id;
|
||||
nozzle_cur_filament_ids[nozzle_id] = filament_id;
|
||||
}
|
||||
@@ -3785,10 +4259,20 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
||||
GCodeProcessor::s_IsBBLPrinter = is_BBL_printer();
|
||||
const Vec3d origin = this->get_plate_origin();
|
||||
processor.set_xy_offset(origin(0), origin(1));
|
||||
// Reloaded sliced projects re-estimate with the same nozzle-grouping slot context as the
|
||||
// original export; process_file re-derives the device-side nozzle grouping onto the result
|
||||
// (via ensure_nozzle_group_result), so the multi-nozzle send/monitor mapping survives here.
|
||||
if (result != nullptr && result->nozzle_group_result)
|
||||
processor.initialize_from_context(result->nozzle_group_result);
|
||||
//processor.enable_producers(true);
|
||||
processor.process_file(file);
|
||||
|
||||
// filament seq is loaded from file, processor result will override the value
|
||||
auto filament_seq_loaded = result->filament_change_sequence;
|
||||
auto nozzle_seq_loaded = result->nozzle_change_sequence;
|
||||
*result = std::move(processor.extract_result());
|
||||
result->filament_change_sequence = filament_seq_loaded;
|
||||
result->nozzle_change_sequence = nozzle_seq_loaded;
|
||||
} catch (std::exception & /* ex */) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") %file.c_str();
|
||||
throw Slic3r::RuntimeError(
|
||||
|
||||
+131
-2
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "calib.hpp"
|
||||
|
||||
@@ -38,6 +39,7 @@ class SupportLayer;
|
||||
class TreeSupportData;
|
||||
class TreeSupport;
|
||||
class ExtrusionLayers;
|
||||
namespace MultiNozzleUtils { class NozzleGroupResultBase; class LayeredNozzleGroupResult; }
|
||||
|
||||
#define MAX_OUTER_NOZZLE_DIAMETER 4
|
||||
// BBS: move from PrintObjectSlice.cpp
|
||||
@@ -1003,15 +1005,50 @@ public:
|
||||
const WipeTowerData& wipe_tower_data(size_t filaments_cnt = 0) const;
|
||||
const ToolOrdering& tool_ordering() const { return m_tool_ordering; }
|
||||
|
||||
void update_filament_maps_to_config(std::vector<int> f_maps);
|
||||
void update_filament_maps_to_config(std::vector<int> f_maps, std::vector<int> f_volume_maps = std::vector<int>{}, std::vector<int> f_nozzle_maps = std::vector<int>{});
|
||||
// Write-back for a selector (per-layer planned) grouping result. When a filament actually
|
||||
// migrates between nozzle variants, rebuilds the per-slot filament arrays so it holds one
|
||||
// slot per variant and recomputes the extruder retract overrides against the expanded
|
||||
// slots — update_filament_maps_to_config's single-slot rebuild cannot represent a
|
||||
// migration. A result without migration reduces to a single grouping and takes the
|
||||
// three-map write-back like the static paths.
|
||||
void update_to_config_by_nozzle_group_result(const MultiNozzleUtils::LayeredNozzleGroupResult& group_result);
|
||||
void apply_config_for_render(const DynamicConfig &config);
|
||||
|
||||
// 1 based group ids
|
||||
std::vector<int> get_filament_maps() const;
|
||||
FilamentMapMode get_filament_map_mode() const;
|
||||
std::vector<int> get_filament_volume_maps() const;
|
||||
std::vector<int> get_filament_nozzle_maps() const;
|
||||
// get the group label of filament
|
||||
size_t get_extruder_id(unsigned int filament_id) const;
|
||||
|
||||
// The region every extruder can reach,
|
||||
// i.e. the intersection of all per-extruder printable areas. Falls back to the full printable_area
|
||||
// for single-nozzle printers and whenever extruder_printable_area is not populated (all current
|
||||
// single/dual profiles), so the wipe-tower-center clamp is byte-identical to full-bed clamping there.
|
||||
Polygons get_extruder_shared_printable_polygon() const;
|
||||
|
||||
// Logical (extruder, nozzle) grouping result produced by ToolOrdering during reorder.
|
||||
// Consumed by GCode via get_layered_nozzle_group_result()->get_nozzle_id(filament, layer) etc.
|
||||
void set_nozzle_group_result(std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> result) { m_nozzle_group_result = result; }
|
||||
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> get_nozzle_group_result() const { return m_nozzle_group_result; }
|
||||
std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> get_layered_nozzle_group_result() const;
|
||||
|
||||
// True only when the project opts into the per-layer filament selector
|
||||
// (enable_filament_dynamic_map) in auto-for-flush mode on a multi-extruder machine. Gates the
|
||||
// dynamic (per-layer) regroup branch in ToolOrdering::reorder_extruders_for_minimum_flush_volume,
|
||||
// the sequential (by-object) plan stitching in Print::process, and GCode's use of the cached
|
||||
// sequential plans. No profile sets the flag, so the static grouping path (byte-identical
|
||||
// output) is the only one taken unless the user enables the selector.
|
||||
bool is_dynamic_group_reorder() const;
|
||||
|
||||
// Per-object tool orderings planned by the sequential (by-object) selector regroup with
|
||||
// cross-object nozzle-status threading. GCode export must consume these exact plans: a fresh
|
||||
// per-object construction would re-plan from a different seed and diverge from the published
|
||||
// stitched result. Empty on the static path.
|
||||
const std::map<const PrintObject*, ToolOrdering>& sequential_dynamic_orderings() const { return m_sequential_dynamic_orderings; }
|
||||
|
||||
const std::vector<std::vector<DynamicPrintConfig>>& get_extruder_filament_info() const { return m_extruder_filament_info; }
|
||||
void set_extruder_filament_info(const std::vector<std::vector<DynamicPrintConfig>>& filament_info) { m_extruder_filament_info = filament_info; }
|
||||
|
||||
@@ -1037,6 +1074,18 @@ public:
|
||||
*/
|
||||
std::vector<std::set<int>> get_physical_unprintable_filaments(const std::vector<unsigned int>& used_filaments) const;
|
||||
|
||||
/**
|
||||
* @brief Determines the forbidden nozzle volume types for each used filament
|
||||
*
|
||||
* A filament may declare the extruder variants it supports. Every volume type offered by the
|
||||
* printer's extruders that the filament does not support is forbidden for that filament.
|
||||
* Hybrid volumes are ignored on both sides, and filaments declaring no variants are unrestricted.
|
||||
*
|
||||
* @param used_filaments Totally used filaments when slicing
|
||||
* @return A map from used filament index to the set of nozzle volume types it cannot print on
|
||||
*/
|
||||
std::map<int, std::set<NozzleVolumeType>> get_filament_unprintable_flow(const std::vector<unsigned int> &used_filaments) const;
|
||||
|
||||
std::vector<double> get_extruder_printable_height() const;
|
||||
std::vector<Polygons> get_extruder_printable_polygons() const;
|
||||
std::vector<Polygons> get_extruder_unprintable_polygons() const;
|
||||
@@ -1120,7 +1169,12 @@ public:
|
||||
bool is_all_objects_are_short() const {
|
||||
return std::all_of(this->objects().begin(), this->objects().end(), [&](PrintObject* obj) { return obj->height() < scale_(this->config().nozzle_height.value); });
|
||||
}
|
||||
|
||||
|
||||
// Post-slicing config-slot resolvers: map a (filament, layer) pair to the index of its
|
||||
// per-(extruder x volume type) column in the expanded variant arrays, cached by grouping context.
|
||||
int get_filament_config_indx(int filament_id, int layer_id);
|
||||
int get_nozzle_config_index(int filament_id, int layer_id);
|
||||
|
||||
// Orca: Implement prusa's filament shrink compensation approach
|
||||
// Returns if all used filaments have same shrinkage compensations.
|
||||
bool has_same_shrinkage_compensations() const;
|
||||
@@ -1130,6 +1184,57 @@ public:
|
||||
std::tuple<float, float> object_skirt_offset(double margin_height = 0) const;
|
||||
|
||||
protected:
|
||||
struct FilamentIndexKey
|
||||
{
|
||||
int filament_id;
|
||||
ExtruderType extruder;
|
||||
NozzleVolumeType nozzle_volume_type;
|
||||
|
||||
bool operator==(const FilamentIndexKey &other) const
|
||||
{
|
||||
return filament_id == other.filament_id && extruder == other.extruder && nozzle_volume_type == other.nozzle_volume_type;
|
||||
}
|
||||
};
|
||||
|
||||
struct PrintIndexKey
|
||||
{
|
||||
int filament_id;
|
||||
int extruder_id;
|
||||
ExtruderType extruder;
|
||||
NozzleVolumeType nozzle_volume_type;
|
||||
|
||||
bool operator==(const PrintIndexKey &other) const
|
||||
{
|
||||
return filament_id == other.filament_id && extruder_id == other.extruder_id && extruder == other.extruder && nozzle_volume_type == other.nozzle_volume_type;
|
||||
}
|
||||
};
|
||||
|
||||
struct FilamentIndexKeyHash
|
||||
{
|
||||
std::size_t operator()(const FilamentIndexKey &k) const
|
||||
{
|
||||
size_t h1 = std::hash<int>{}(k.filament_id);
|
||||
size_t h2 = std::hash<int>{}(static_cast<int>(k.extruder));
|
||||
size_t h3 = std::hash<int>{}(static_cast<int>(k.nozzle_volume_type));
|
||||
return h1 ^ (h2 << 8) ^ (h3 << 12);
|
||||
}
|
||||
};
|
||||
struct PrintIndexKeyHash
|
||||
{
|
||||
std::size_t operator()(const PrintIndexKey &k) const
|
||||
{
|
||||
size_t h1 = std::hash<int>{}(k.filament_id);
|
||||
size_t h2 = std::hash<int>{}(k.extruder_id);
|
||||
size_t h3 = std::hash<int>{}(static_cast<int>(k.extruder));
|
||||
size_t h4 = std::hash<int>{}(static_cast<int>(k.nozzle_volume_type));
|
||||
return h1 ^ (h2 << 8) ^ (h3 << 12) ^ (h4 << 16);
|
||||
}
|
||||
};
|
||||
using FilamentIndexMap = std::unordered_map<FilamentIndexKey, int, FilamentIndexKeyHash>;
|
||||
using PrintIndexMap = std::unordered_map<PrintIndexKey, int, PrintIndexKeyHash>;
|
||||
int get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, FilamentIndexMap &index_map);
|
||||
int get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, PrintIndexMap &index_map);
|
||||
|
||||
// Invalidates the step, and its depending steps in Print.
|
||||
bool invalidate_step(PrintStep step);
|
||||
|
||||
@@ -1143,6 +1248,16 @@ private:
|
||||
void _make_skirt();
|
||||
void _make_wipe_tower();
|
||||
void finalize_first_layer_convex_hull();
|
||||
void update_filament_self_index_cache();
|
||||
// Deduplicates, per filament, the (extruder type x volume type) variants the grouping
|
||||
// result routes it through; filaments the plan never routes get their default-map
|
||||
// assignment so the slot resolution never depends on the (mutable) filament_map. config
|
||||
// must carry extruder_type; returns false when it does not. Both the slice-time write-back
|
||||
// and the apply-time reproduction call this with m_ori_full_print_config so the two
|
||||
// expansions resolve identical slots.
|
||||
bool collect_filament_variant_uses(const MultiNozzleUtils::LayeredNozzleGroupResult& group_result,
|
||||
const DynamicPrintConfig& config,
|
||||
std::unordered_map<int, std::vector<FilamentVariantUse>>& uses) const;
|
||||
|
||||
// Islands of objects and their supports extruded at the 1st layer.
|
||||
Polygons first_layer_islands() const;
|
||||
@@ -1176,6 +1291,20 @@ private:
|
||||
|
||||
std::vector<std::vector<DynamicPrintConfig>> m_extruder_filament_info;
|
||||
|
||||
// Logical (extruder, nozzle) grouping result, set by ToolOrdering during reorder.
|
||||
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> m_nozzle_group_result;
|
||||
|
||||
// Sequential (by-object) selector plans, keyed by object; see sequential_dynamic_orderings().
|
||||
// Rebuilt (or cleared) on every process().
|
||||
std::map<const PrintObject*, ToolOrdering> m_sequential_dynamic_orderings;
|
||||
|
||||
// Used to cache filament parameter information
|
||||
FilamentIndexMap m_filament_index_map;
|
||||
// Used to cache printer and process parameter information
|
||||
PrintIndexMap m_nozzle_index_map;
|
||||
// save the config value of "filament_self_index"
|
||||
std::vector<int> m_filament_self_index;
|
||||
|
||||
// Following section will be consumed by the GCodeGenerator.
|
||||
ToolOrdering m_tool_ordering;
|
||||
WipeTowerData m_wipe_tower_data {m_tool_ordering};
|
||||
|
||||
+149
-21
@@ -224,7 +224,11 @@ static t_config_option_keys print_config_diffs(
|
||||
const DynamicPrintConfig &new_full_config,
|
||||
DynamicPrintConfig &filament_overrides,
|
||||
int plate_index,
|
||||
std::vector<int>& filament_maps)
|
||||
std::vector<int>& filament_maps,
|
||||
// Per-slot machine indices when the filament arrays hold the per-variant expansion of a
|
||||
// selector result (one slot per variant a filament migrates through); the per-filament
|
||||
// map cannot index the expanded override arrays. Null on the single-slot path.
|
||||
const std::vector<int>* dynamic_override_indices = nullptr)
|
||||
{
|
||||
const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
|
||||
const std::string filament_prefix = "filament_";
|
||||
@@ -240,7 +244,15 @@ static t_config_option_keys print_config_diffs(
|
||||
const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr;
|
||||
|
||||
if (opt_new_filament != nullptr) {
|
||||
compute_filament_override_value(opt_key, opt_old, opt_new, opt_new_filament, new_full_config, print_diff, filament_overrides, filament_maps);
|
||||
std::vector<int> filament_map_indices;
|
||||
if (dynamic_override_indices)
|
||||
filament_map_indices = *dynamic_override_indices;
|
||||
else {
|
||||
filament_map_indices.assign(filament_maps.size(), 0);
|
||||
for (int i = 0; i < filament_maps.size(); i++)
|
||||
filament_map_indices[i] = filament_maps[i] - 1;
|
||||
}
|
||||
compute_filament_override_value(opt_key, opt_old, opt_new, opt_new_filament, new_full_config, print_diff, filament_overrides, filament_map_indices);
|
||||
} else if (*opt_new != *opt_old) {
|
||||
//BBS: add plate_index logic for wipe_tower_x/wipe_tower_y
|
||||
if (!opt_key.compare("wipe_tower_x") || !opt_key.compare("wipe_tower_y")) {
|
||||
@@ -1165,25 +1177,57 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
|
||||
//apply extruder related values
|
||||
std::vector<int> print_variant_index;
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 1, extruder_volume_type_count = 1;
|
||||
bool different_extruder = false;
|
||||
// Filled only when the filament arrays are rebuilt from a persisted selector result below;
|
||||
// print_config_diffs then keys the retract overrides per expanded slot.
|
||||
std::vector<int> dynamic_slot_indices;
|
||||
|
||||
different_extruder = new_full_config.support_different_extruders(extruder_count);
|
||||
extruder_volume_type_count = new_full_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
if (!extruder_applied) {
|
||||
// variant_2 must be processed first, because variant_1 will make `printer_extruder_id` and `printer_extruder_variant` half of the size that makes `get_index_for_extruder` no longer work properly
|
||||
new_full_config.update_values_to_printer_extruders(new_full_config, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
|
||||
new_full_config.update_values_to_printer_extruders(new_full_config, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
|
||||
//update print config related with variants
|
||||
print_variant_index = new_full_config.update_values_to_printer_extruders(new_full_config, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
if ((extruder_count > 1) || different_extruder) {
|
||||
// variant_2 must be processed first, because variant_1 will make `printer_extruder_id` and `printer_extruder_variant` half of the size that makes `get_index_for_extruder` no longer work properly
|
||||
new_full_config.update_values_to_printer_extruders(new_full_config, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
|
||||
new_full_config.update_values_to_printer_extruders(new_full_config, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
|
||||
//update print config related with variants
|
||||
print_variant_index = new_full_config.update_values_to_printer_extruders(new_full_config, extruder_count, extruder_volume_type_count, nozzle_volume_types, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
}
|
||||
else
|
||||
print_variant_index.resize(1, 0);
|
||||
|
||||
m_ori_full_print_config = new_full_config;
|
||||
new_full_config.update_values_to_printer_extruders_for_multiple_filaments(new_full_config, filament_options_with_variant, "filament_self_index", "filament_extruder_variant");
|
||||
|
||||
std::set<std::string> filament_keys = filament_options_with_variant;
|
||||
filament_keys.insert("filament_self_index");
|
||||
// A persisted selector result with an actual migration means the last slice rebuilt the
|
||||
// per-slot filament arrays from it (one slot per variant a filament prints through).
|
||||
// Reproduce that exact expansion here so an unchanged config diffs empty — the expanded
|
||||
// keys invalidate the wipe tower / g-code export, and the placeholder parser aliases
|
||||
// the full config — instead of trimming back to one slot per filament.
|
||||
auto group_result = std::dynamic_pointer_cast<MultiNozzleUtils::LayeredNozzleGroupResult>(this->get_nozzle_group_result());
|
||||
std::unordered_map<int, std::vector<FilamentVariantUse>> filament_variant_uses;
|
||||
if (group_result && group_result->is_support_dynamic_nozzle_map()
|
||||
&& collect_filament_variant_uses(*group_result, m_ori_full_print_config, filament_variant_uses))
|
||||
new_full_config.update_filament_config_values_for_multiple_extruders(m_ori_full_print_config, filament_variant_uses,
|
||||
extruder_count, extruder_volume_type_count, filament_keys,
|
||||
"filament_self_index", "filament_extruder_variant",
|
||||
&dynamic_slot_indices);
|
||||
else if ((extruder_count > 1) || different_extruder)
|
||||
new_full_config.update_values_to_printer_extruders_for_multiple_filaments(m_ori_full_print_config, extruder_count, extruder_volume_type_count, filament_keys,
|
||||
"filament_self_index", "filament_extruder_variant");
|
||||
}
|
||||
else {
|
||||
//should not come here, we can not get the result of print_variant, for the values have been updated
|
||||
//we just use the default values here
|
||||
auto variant_opt = dynamic_cast<const ConfigOptionStrings *>(new_full_config.option("printer_extruder_variant"));
|
||||
print_variant_index.resize(variant_opt->values.size());
|
||||
for (int e_index = 0; e_index < variant_opt->values.size(); e_index++)
|
||||
{
|
||||
print_variant_index[e_index] = e_index;
|
||||
}
|
||||
}
|
||||
// else {
|
||||
// int extruder_count;
|
||||
// bool different_extruder = new_full_config.support_different_extruders(extruder_count);
|
||||
// print_variant_index.resize(extruder_count);
|
||||
// for (int e_index = 0; e_index < extruder_count; e_index++)
|
||||
// {
|
||||
// print_variant_index[e_index] = e_index;
|
||||
// }
|
||||
// }
|
||||
|
||||
auto opt_filament_map = new_full_config.option<ConfigOptionInts>("filament_map");
|
||||
std::vector<int> filament_maps = opt_filament_map ? opt_filament_map->values : std::vector<int>();
|
||||
@@ -1191,7 +1235,15 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
// Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles.
|
||||
DynamicPrintConfig filament_overrides;
|
||||
//BBS: add plate index
|
||||
t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index, filament_maps);
|
||||
t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index, filament_maps,
|
||||
dynamic_slot_indices.empty() ? nullptr : &dynamic_slot_indices);
|
||||
// Orca: filament_map_2 is engine-derived state, never a user input: the rebuild below
|
||||
// recomputes it from filament_map/filament_volume_map/the variant slots on every apply
|
||||
// (all of which are diffed and invalidation-listed on their own), and the grouping
|
||||
// write-back overwrites it during process(). The incoming full config only ever carries
|
||||
// the ConfigDef default, so diffing it would invalidate every print step on each apply
|
||||
// for any multi-extruder printer and permanently invalidate fresh slice results.
|
||||
print_diff.erase(std::remove(print_diff.begin(), print_diff.end(), "filament_map_2"), print_diff.end());
|
||||
t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config, this->m_plate_index);
|
||||
// Collect changes to object and region configs.
|
||||
t_config_option_keys object_diff = m_default_object_config.diff(new_full_config);
|
||||
@@ -1199,10 +1251,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
|
||||
//BBS: process the filament_map related logic
|
||||
std::unordered_set<std::string> print_diff_set(print_diff.begin(), print_diff.end());
|
||||
if (print_diff_set.find("filament_map_mode") == print_diff_set.end())
|
||||
if (!print_diff_set.empty() && print_diff_set.find("filament_map_mode") == print_diff_set.end())
|
||||
{
|
||||
FilamentMapMode map_mode = new_full_config.option<ConfigOptionEnum<FilamentMapMode>>("filament_map_mode", true)->value;
|
||||
if (map_mode < fmmManual) {
|
||||
if (is_auto_filament_map_mode(map_mode)) {
|
||||
if (print_diff_set.find("filament_map") != print_diff_set.end()) {
|
||||
print_diff_set.erase("filament_map");
|
||||
//full_config_diff.erase("filament_map");
|
||||
@@ -1211,9 +1263,29 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
old_opt->set(new_opt);
|
||||
m_config.filament_map = *new_opt;
|
||||
}
|
||||
if (print_diff_set.find("filament_volume_map") != print_diff_set.end()) {
|
||||
print_diff_set.erase("filament_volume_map");
|
||||
//full_config_diff.erase("filament_volume_map");
|
||||
ConfigOptionInts* old_opt = m_full_print_config.option<ConfigOptionInts>("filament_volume_map", true);
|
||||
ConfigOptionInts* new_opt = new_full_config.option<ConfigOptionInts>("filament_volume_map", true);
|
||||
old_opt->set(new_opt);
|
||||
m_config.filament_volume_map = *new_opt;
|
||||
}
|
||||
if (print_diff_set.find("filament_nozzle_map") != print_diff_set.end()) {
|
||||
print_diff_set.erase("filament_nozzle_map");
|
||||
//full_config_diff.erase("filament_nozzle_map");
|
||||
ConfigOptionInts* old_opt = m_full_print_config.option<ConfigOptionInts>("filament_nozzle_map", true);
|
||||
ConfigOptionInts* new_opt = new_full_config.option<ConfigOptionInts>("filament_nozzle_map", true);
|
||||
old_opt->set(new_opt);
|
||||
m_config.filament_nozzle_map = *new_opt;
|
||||
}
|
||||
}
|
||||
else {
|
||||
print_diff_set.erase("extruder_ams_count");
|
||||
if (map_mode == fmmManual) {
|
||||
// filament_nozzle_map is an engine output, not a GUI input, in manual mode
|
||||
print_diff_set.erase("filament_nozzle_map");
|
||||
}
|
||||
std::vector<int> old_filament_map = m_config.filament_map.values;
|
||||
std::vector<int> new_filament_map = new_full_config.option<ConfigOptionInts>("filament_map", true)->values;
|
||||
|
||||
@@ -1230,14 +1302,62 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (same_map)
|
||||
if (same_map) {
|
||||
print_diff_set.erase("filament_map");
|
||||
|
||||
// The extruder retract overrides are keyed by the (unchanged) filament map;
|
||||
// recompute them and drop diffs whose recomputed value matches the current
|
||||
// config, so a cosmetic reordering of unused filaments does not invalidate.
|
||||
const auto& retract_keys = print_config_def.extruder_retract_keys();
|
||||
const std::string filament_prefix = "filament_";
|
||||
std::vector<int> old_f_map_indices(old_filament_map.size(), 0);
|
||||
for (size_t i = 0; i < old_filament_map.size(); i++)
|
||||
old_f_map_indices[i] = old_filament_map[i] - 1;
|
||||
|
||||
for (const auto& rk : retract_keys) {
|
||||
if (print_diff_set.find(rk) == print_diff_set.end())
|
||||
continue;
|
||||
const ConfigOption* opt_old = m_config.option(rk);
|
||||
const ConfigOption* opt_new_m = new_full_config.option(rk);
|
||||
const ConfigOption* opt_new_f = new_full_config.option(filament_prefix + rk);
|
||||
if (opt_old && opt_new_m && opt_new_f) {
|
||||
std::unique_ptr<ConfigOption> opt_recomputed(opt_new_m->clone());
|
||||
opt_recomputed->apply_override(opt_new_f, old_f_map_indices);
|
||||
if (*opt_old == *opt_recomputed)
|
||||
print_diff_set.erase(rk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (print_diff_set.size() != print_diff.size())
|
||||
print_diff.assign(print_diff_set.begin(), print_diff_set.end());
|
||||
}
|
||||
|
||||
//filament_map_2
|
||||
// Orca: seed with 0-based extruder indices so the copy stays a valid slot map even when the
|
||||
// variant options are absent below and the rebuild loop is skipped (unit tests, degenerate
|
||||
// presets); the loop overwrites every entry when it runs.
|
||||
m_config.filament_map_2.values = filament_maps;
|
||||
for (auto& v : m_config.filament_map_2.values)
|
||||
--v;
|
||||
auto opt_extruder_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(new_full_config.option("extruder_type"));
|
||||
auto opt_filament_volume_maps = dynamic_cast<const ConfigOptionInts*>(new_full_config.option("filament_volume_map"));
|
||||
auto opt_nozzle_volume_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(new_full_config.option("nozzle_volume_type"));
|
||||
for (int index = 0; opt_extruder_type && opt_nozzle_volume_type && index < filament_maps.size(); index++)
|
||||
{
|
||||
ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(filament_maps[index] - 1));
|
||||
NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(filament_maps[index] - 1));
|
||||
// Orca: honour the per-filament volume map only when a producer sized it to the filament
|
||||
// count; mis-sized maps (stale project values, CLI runs until the per-filament synthesis
|
||||
// lands there) must not be indexed per filament (see
|
||||
// update_values_to_printer_extruders_for_multiple_filaments for the same guard).
|
||||
if ((extruder_volume_type_count > extruder_count) && opt_filament_volume_maps
|
||||
&& opt_filament_volume_maps->values.size() == filament_maps.size())
|
||||
nozzle_volume_type = (NozzleVolumeType)(opt_filament_volume_maps->values[index]);
|
||||
m_config.filament_map_2.values[index] = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
}
|
||||
|
||||
// Do not use the ApplyStatus as we will use the max function when updating apply_status.
|
||||
unsigned int apply_status = APPLY_STATUS_UNCHANGED;
|
||||
auto update_apply_status = [&apply_status](bool invalidated)
|
||||
@@ -1285,6 +1405,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
m_default_region_config.apply_only(new_full_config, region_diff, true);
|
||||
//m_full_print_config = std::move(new_full_config);
|
||||
m_full_print_config = new_full_config;
|
||||
update_filament_self_index_cache();
|
||||
if (num_extruders != m_config.filament_diameter.size()) {
|
||||
num_extruders = m_config.filament_diameter.size();
|
||||
num_extruders_changed = true;
|
||||
@@ -1657,7 +1778,14 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
m_default_object_config.apply_only(new_full_config, new_changed_keys, true);
|
||||
// Handle changes to regions config defaults
|
||||
m_default_region_config.apply_only(new_full_config, new_changed_keys, true);
|
||||
// Orca: keep the pre-expansion snapshot in sync with this late normalization pass.
|
||||
// The engine map write-back rebuilds m_full_print_config from m_ori_full_print_config
|
||||
// after slicing; a stale snapshot would resurrect the un-normalized values (e.g.
|
||||
// enable_prime_tower on a single-filament print) in the dumped config and spuriously
|
||||
// re-invalidate the g-code on the next apply.
|
||||
m_ori_full_print_config.apply_only(new_full_config, new_changed_keys, true);
|
||||
m_full_print_config = std::move(new_full_config);
|
||||
update_filament_self_index_cache();
|
||||
}
|
||||
|
||||
// All regions now have distinct settings.
|
||||
|
||||
+874
-161
File diff suppressed because it is too large.
Load diff
@@ -46,6 +46,14 @@ enum GCodeFlavor : unsigned char {
|
||||
gcfNoExtrusion
|
||||
};
|
||||
|
||||
// How a filament is used across the model. Part of the multi-nozzle grouping data; not yet
|
||||
// read by the shipping slicer — the nozzle-centric FilamentGroup engine consumes it.
|
||||
enum FilamentUsageType {
|
||||
SupportOnly,
|
||||
ModelOnly,
|
||||
Hybrid
|
||||
};
|
||||
|
||||
|
||||
enum class FuzzySkinType {
|
||||
None,
|
||||
@@ -382,6 +390,13 @@ enum LayerSeq {
|
||||
flsCustomize
|
||||
};
|
||||
|
||||
enum FanDirection {
|
||||
fdUndefine = 0,
|
||||
fdLeft,
|
||||
fdRight,
|
||||
fdBoth
|
||||
};
|
||||
|
||||
static std::unordered_map<NozzleType, std::string>NozzleTypeEumnToStr = {
|
||||
{NozzleType::ntUndefine, "undefine"},
|
||||
{NozzleType::ntHardenedSteel, "hardened_steel"},
|
||||
@@ -465,24 +480,49 @@ enum ExtruderType {
|
||||
enum NozzleVolumeType {
|
||||
nvtStandard = 0,
|
||||
nvtHighFlow,
|
||||
nvtMaxNozzleVolumeType = nvtHighFlow
|
||||
nvtHybrid, // extruder holds a mix of Standard and High Flow sub-nozzles; selectable only for extruders
|
||||
// with more than one sub-nozzle (extruder_max_nozzle_count > 1); matched as Standard for
|
||||
// preset lookup and never emitted in profile variant strings
|
||||
nvtTPUHighFlow, // physical variant, used on H2D/H2DP 0.4 nozzles only
|
||||
// Integer values are serialized as raw ints in 3mf plate metadata and device MQTT, so they MUST stay stable.
|
||||
nvtMaxNozzleVolumeType = nvtTPUHighFlow
|
||||
};
|
||||
|
||||
enum FilamentMapMode {
|
||||
fmmAutoForFlush,
|
||||
fmmAutoForMatch,
|
||||
fmmManual,
|
||||
fmmNozzleManual, // Fully-manual filament->physical-nozzle mapping (filament_nozzle_map). Kept ordered right after fmmManual so every `< fmmManual` "is-auto" check stays correct.
|
||||
fmmDefault
|
||||
};
|
||||
|
||||
// All auto modes are ordered before fmmManual (see the enum ordering note above).
|
||||
inline bool is_auto_filament_map_mode(FilamentMapMode mode) {
|
||||
return mode < fmmManual;
|
||||
}
|
||||
|
||||
// Dual-extruder purge control. Default reproduces the current
|
||||
// per-extruder flush_multiplier + filament_prime_volume behaviour, so absent/default is inert.
|
||||
// Saving -> reduce prime volume to 15 mm3; Fast -> use flush_multiplier_fast + filament_flush_temp_fast.
|
||||
enum PrimeVolumeMode {
|
||||
pvmDefault = 0,
|
||||
pvmSaving,
|
||||
pvmFast
|
||||
};
|
||||
|
||||
extern std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type);
|
||||
|
||||
// Base slot lookup: scans a variant list (paired with its 1-based extruder/filament ids) for the
|
||||
// entry matching the given extruder/volume type and id. Returns 0 when no entry matches.
|
||||
extern int get_config_index_base(NozzleVolumeType volume_type, ExtruderType extruder_type, int variant_id_1based, const std::vector<std::string>& variant_list, const std::vector<int>& variant_ids_1based);
|
||||
|
||||
static std::set<NozzleVolumeType> get_valid_nozzle_volume_type() {
|
||||
std::set<NozzleVolumeType> type;
|
||||
for (int i = 0; i <= nvtMaxNozzleVolumeType; ++i) {
|
||||
auto t = static_cast<NozzleVolumeType>(i);
|
||||
// TODO: Orca: Support hybrid
|
||||
//if (t == nvtHybrid) continue;
|
||||
// Hybrid is not a physical nozzle variant: presets never define it, so it must not
|
||||
// produce a variant string.
|
||||
if (t == nvtHybrid) continue;
|
||||
type.insert(t);
|
||||
}
|
||||
return type;
|
||||
@@ -568,11 +608,20 @@ static std::string get_bed_temp_1st_layer_key(const BedType type)
|
||||
}
|
||||
|
||||
extern const std::vector<std::string> filament_extruder_override_keys;
|
||||
// Full override-key check incl. filament_retract_length_nc (defined outside the generator list).
|
||||
extern bool is_filament_extruder_override_key(const std::string &opt_key);
|
||||
|
||||
// for parse extruder_ams_count
|
||||
extern std::vector<std::map<int, int>> get_extruder_ams_count(const std::vector<std::string> &strs);
|
||||
extern std::vector<std::string> save_extruder_ams_count_to_string(const std::vector<std::map<int, int>> &extruder_ams_count);
|
||||
|
||||
// maps a full extruder variant string (e.g. "Direct Drive High Flow") to its NozzleVolumeType; nvtHybrid if unparsable
|
||||
extern NozzleVolumeType convert_to_nvt_type(const std::string& variant_str);
|
||||
|
||||
// for parse extruder_nozzle_stats (per-extruder physical nozzle inventory by volume type)
|
||||
extern std::vector<std::map<NozzleVolumeType, int>> get_extruder_nozzle_stats(const std::vector<std::string> &strs);
|
||||
extern std::vector<std::string> save_extruder_nozzle_stats_to_string(const std::vector<std::map<NozzleVolumeType, int>> &extruder_nozzle_stats);
|
||||
|
||||
#define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \
|
||||
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names(); \
|
||||
template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values();
|
||||
@@ -662,6 +711,23 @@ class StaticPrintConfig;
|
||||
// Minimum object distance for arrangement, based on printer technology.
|
||||
double min_object_distance(const ConfigBase &cfg);
|
||||
|
||||
// One (extruder type x nozzle volume type) parameter variant a filament prints through, plus a
|
||||
// representative physical extruder observed using it. Ordering (and set-dedup identity) covers
|
||||
// the variant pair only, so the same variant reached through two extruders keeps one config slot.
|
||||
struct FilamentVariantUse
|
||||
{
|
||||
ExtruderType extruder_type{etDirectDrive};
|
||||
NozzleVolumeType nozzle_volume_type{nvtStandard};
|
||||
int extruder_id{0}; // 0-based, first extruder seen using this variant
|
||||
|
||||
bool operator<(const FilamentVariantUse &other) const
|
||||
{
|
||||
if (extruder_type != other.extruder_type)
|
||||
return extruder_type < other.extruder_type;
|
||||
return nozzle_volume_type < other.nozzle_volume_type;
|
||||
}
|
||||
};
|
||||
|
||||
// Slic3r dynamic configuration, used to override the configuration
|
||||
// per object, per modification volume or per printing material.
|
||||
// The dynamic configuration is also used to store user modifications of the print global parameters,
|
||||
@@ -719,9 +785,26 @@ public:
|
||||
//BBS
|
||||
bool is_using_different_extruders();
|
||||
bool support_different_extruders(int& extruder_count) const;
|
||||
// Counts the config slots of a printer: one per (extruder x nozzle volume type) as described by
|
||||
// extruder_nozzle_stats, or simply one per extruder when the stats are absent/mismatched.
|
||||
// Fills nozzle_volume_types with each extruder's volume types in ascending enum order.
|
||||
int get_extruder_nozzle_volume_count(int extruder_count, std::vector<std::vector<NozzleVolumeType>>& nozzle_volume_types) const;
|
||||
int get_index_for_extruder(int extruder_or_filament_id, std::string id_name, ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type, std::string variant_name, unsigned int stride = 1) const;
|
||||
std::vector<int> update_values_to_printer_extruders(DynamicPrintConfig& printer_config, std::set<std::string>& key_set, std::string id_name, std::string variant_name, unsigned int stride = 1, unsigned int extruder_id = 0);
|
||||
void update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, std::set<std::string>& key_set, std::string id_name, std::string variant_name);
|
||||
std::vector<int> update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector<std::vector<NozzleVolumeType>>& nv_types,
|
||||
std::set<std::string>& key_set, std::string id_name, std::string variant_name, unsigned int stride = 1, unsigned int extruder_id = 0, NozzleVolumeType filament_nvt = nvtStandard);
|
||||
void update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::set<std::string>& key_set, std::string id_name, std::string variant_name);
|
||||
// Rebuilds the per-slot filament arrays from a per-layer grouping outcome: a filament that
|
||||
// prints through several (extruder x nozzle volume type) variants keeps one slot per variant
|
||||
// (unlike the single-slot rebuild above), so layer-aware consumers can resolve the slot the
|
||||
// current layer actually prints with. Filaments absent from filament_variant_uses keep a
|
||||
// single slot resolved from filament_map / filament_volume_map. When slot_machine_indices is
|
||||
// non-null it receives one machine-variant slot index per output slot (the nil-value fallback
|
||||
// keying for the extruder retract overrides; a per-filament map cannot index expanded arrays).
|
||||
void update_filament_config_values_for_multiple_extruders(DynamicPrintConfig& printer_config,
|
||||
const std::unordered_map<int, std::vector<FilamentVariantUse>>& filament_variant_uses,
|
||||
int extruder_count, int extruder_nozzle_volume_count,
|
||||
std::set<std::string>& key_set, std::string id_name, std::string variant_name,
|
||||
std::vector<int>* slot_machine_indices = nullptr);
|
||||
|
||||
void update_non_diff_values_to_base_config(DynamicPrintConfig& new_config, const t_config_option_keys& keys, const std::set<std::string>& different_keys, std::string extruder_id_name, std::string extruder_variant_name,
|
||||
std::set<std::string>& key_set1, std::set<std::string>& key_set2);
|
||||
@@ -751,7 +834,7 @@ extern std::set<std::string> filament_dev_options;
|
||||
|
||||
extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1);
|
||||
extern void compute_filament_override_value(const std::string& opt_key, const ConfigOption *opt_old_machine, const ConfigOption *opt_new_machine, const ConfigOption *opt_new_filament, const DynamicPrintConfig& new_full_config,
|
||||
t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector<int>& f_maps);
|
||||
t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector<int>& f_map_indices);
|
||||
|
||||
void handle_legacy_sla(DynamicPrintConfig &config);
|
||||
|
||||
@@ -1355,6 +1438,12 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloats, machine_min_travel_rate))
|
||||
// M205 S... [mm/sec]
|
||||
((ConfigOptionFloats, machine_min_extruding_rate))
|
||||
// Bedslinger mass/force model: drive the per-layer Y-axis
|
||||
// acceleration limit (curr_y_acceleration_limit) and the printed-mass check.
|
||||
// Default 0 => inactive for every existing printer (mass model reads them as disabled).
|
||||
((ConfigOptionFloat, machine_max_force_Y))
|
||||
((ConfigOptionFloat, machine_bed_mass_Y))
|
||||
((ConfigOptionFloat, machine_max_printed_mass))
|
||||
|
||||
//resonance avoidance ported from qidi slicer
|
||||
((ConfigOptionBool, resonance_avoidance))
|
||||
@@ -1409,6 +1498,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionStrings, filament_vendor))
|
||||
((ConfigOptionBools, filament_is_support))
|
||||
((ConfigOptionInts, filament_printable))
|
||||
((ConfigOptionInts, filament_extruder_compatibility))
|
||||
((ConfigOptionFloats, filament_change_length))
|
||||
((ConfigOptionFloats, filament_cost))
|
||||
((ConfigOptionStrings, default_filament_colour))
|
||||
@@ -1417,14 +1507,20 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionInts, required_nozzle_HRC))
|
||||
((ConfigOptionEnum<FilamentMapMode>, filament_map_mode))
|
||||
((ConfigOptionInts, filament_map))
|
||||
((ConfigOptionInts, filament_volume_map))
|
||||
((ConfigOptionInts, filament_nozzle_map))
|
||||
((ConfigOptionInts, filament_map_2)) //used for multi nozzle, map filament to the index identified by extruder+nozzle_volume_type
|
||||
//((ConfigOptionInts, filament_extruder_id))
|
||||
((ConfigOptionStrings, filament_extruder_variant))
|
||||
((ConfigOptionInts, filament_self_index))
|
||||
((ConfigOptionBool, support_object_skip_flush))
|
||||
((ConfigOptionEnum<BedTempFormula>, bed_temperature_formula))
|
||||
((ConfigOptionInts, physical_extruder_map))
|
||||
((ConfigOptionIntsNullable, nozzle_flush_dataset))
|
||||
((ConfigOptionFloatsNullable, filament_flush_volumetric_speed))
|
||||
((ConfigOptionIntsNullable, filament_flush_temp))
|
||||
// Fast-purge flush temperature; consumed only when prime_volume_mode==pvmFast.
|
||||
((ConfigOptionIntsNullable, filament_flush_temp_fast))
|
||||
// BBS
|
||||
((ConfigOptionBool, scan_first_layer))
|
||||
((ConfigOptionEnum<PowerLossRecoveryMode>, enable_power_loss_recovery))
|
||||
@@ -1486,12 +1582,16 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionEnumsGenericNullable,nozzle_type))
|
||||
((ConfigOptionInt, nozzle_hrc))
|
||||
((ConfigOptionBool, auxiliary_fan))
|
||||
((ConfigOptionEnum<FanDirection>, fan_direction))
|
||||
((ConfigOptionBool, support_air_filtration))
|
||||
((ConfigOptionBool, support_cooling_filter))
|
||||
((ConfigOptionBool, cooling_filter_enabled))
|
||||
((ConfigOptionEnum<PrinterStructure>,printer_structure))
|
||||
((ConfigOptionBool, support_chamber_temp_control))
|
||||
((ConfigOptionEnumsGeneric, extruder_type))
|
||||
((ConfigOptionEnumsGeneric, nozzle_volume_type))
|
||||
((ConfigOptionStrings, extruder_ams_count))
|
||||
((ConfigOptionStrings, extruder_nozzle_stats))
|
||||
((ConfigOptionInts, printer_extruder_id))
|
||||
((ConfigOptionInt, master_extruder_id))
|
||||
((ConfigOptionStrings, printer_extruder_variant))
|
||||
@@ -1549,6 +1649,27 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionStrings, small_area_infill_flow_compensation_model))
|
||||
|
||||
((ConfigOptionBool, has_scarf_joint_seam))
|
||||
|
||||
// Multi-nozzle + pre-heating + nozzle-change (nc) keys. Defaults are no-ops for existing
|
||||
// single-nozzle printers; new slicing paths gate on extruder_max_nozzle_count > 1.
|
||||
((ConfigOptionFloat, machine_hotend_change_time))
|
||||
((ConfigOptionFloat, machine_prepare_compensation_time))
|
||||
((ConfigOptionBool, enable_pre_heating))
|
||||
((ConfigOptionFloatsNullable, hotend_cooling_rate))
|
||||
((ConfigOptionFloatsNullable, hotend_heating_rate))
|
||||
((ConfigOptionFloats, filament_change_length_nc))
|
||||
((ConfigOptionFloatsNullable, filament_ramming_travel_time))
|
||||
((ConfigOptionIntsNullable, filament_pre_cooling_temperature))
|
||||
((ConfigOptionFloatsNullable, filament_ramming_volumetric_speed))
|
||||
((ConfigOptionFloatsNullable, filament_ramming_travel_time_nc))
|
||||
((ConfigOptionIntsNullable, filament_pre_cooling_temperature_nc))
|
||||
((ConfigOptionFloatsNullable, filament_ramming_volumetric_speed_nc))
|
||||
((ConfigOptionFloatsNullable, filament_retract_length_nc))
|
||||
((ConfigOptionIntsNullable, extruder_max_nozzle_count))
|
||||
// Printer flag: whether the printer offers the fast-purge mode selector.
|
||||
// Default false; no shipping profile sets it, so the fast-purge UI stays hidden.
|
||||
((ConfigOptionBool, support_fast_purge_mode))
|
||||
|
||||
//ams chamber
|
||||
((ConfigOptionStrings, filament_dev_ams_drying_ams_limitations))
|
||||
((ConfigOptionFloats, filament_dev_ams_drying_temperature))
|
||||
@@ -1704,7 +1825,15 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
|
||||
// BBS: wipe tower is only used for priming
|
||||
((ConfigOptionFloat, prime_volume))
|
||||
// Nozzle-change (nc) prime volume + pre-heat delta
|
||||
((ConfigOptionFloats, filament_prime_volume_nc))
|
||||
((ConfigOptionFloatsNullable, filament_preheat_temperature_delta))
|
||||
((ConfigOptionFloats, flush_multiplier))
|
||||
// Fast-purge mode. Kept out of the g-code config block (banned_keys in
|
||||
// GCode::append_full_config) so registering them leaves the shipping fleet's g-code byte-identical;
|
||||
// consumed only on the prime_volume_mode==pvmFast / pvmSaving branch (default pvmDefault = inert).
|
||||
((ConfigOptionEnum<PrimeVolumeMode>, prime_volume_mode))
|
||||
((ConfigOptionFloats, flush_multiplier_fast))
|
||||
((ConfigOptionFloat, z_offset))
|
||||
// BBS: project filaments
|
||||
((ConfigOptionFloats, filament_colour_new))
|
||||
@@ -1712,6 +1841,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionFloatsNullable, nozzle_volume))
|
||||
((ConfigOptionPoints, start_end_points))
|
||||
((ConfigOptionEnum<TimelapseType>, timelapse_type))
|
||||
// Corexy farthest-point timelapse (default false → inert for existing printers)
|
||||
((ConfigOptionBool, farthest_point_timelapse))
|
||||
((ConfigOptionString, thumbnails))
|
||||
// BBS: move from PrintObjectConfig
|
||||
((ConfigOptionBool, independent_support_layer_height))
|
||||
|
||||
@@ -102,6 +102,9 @@ const std::string& var_dir();
|
||||
// Return a full resource path for a file_name.
|
||||
std::string var(const std::string &file_name);
|
||||
|
||||
// Snap a nozzle diameter to the closest supported value and format it as a string (e.g. 0.4 -> "0.4").
|
||||
std::string format_diameter_to_str(double diameter, int precision = 1);
|
||||
|
||||
// Set a path with various static definition data (for example the initial config bundles).
|
||||
void set_resources_dir(const std::string &path);
|
||||
// Return a full path to the resources directory.
|
||||
|
||||
@@ -83,6 +83,8 @@ public:
|
||||
NozzleVolumeType nozzle_volume_type;
|
||||
BedType bed_type;
|
||||
float nozzle_diameter;
|
||||
int nozzle_pos_id{-1};
|
||||
std::string nozzle_sn;
|
||||
std::string filament_id;
|
||||
std::string setting_id;
|
||||
std::string name;
|
||||
@@ -93,6 +95,8 @@ public:
|
||||
this->extruder_id = other.extruder_id;
|
||||
this->nozzle_volume_type = other.nozzle_volume_type;
|
||||
this->nozzle_diameter = other.nozzle_diameter;
|
||||
this->nozzle_pos_id = other.nozzle_pos_id;
|
||||
this->nozzle_sn = other.nozzle_sn;
|
||||
this->filament_id = other.filament_id;
|
||||
this->setting_id = other.setting_id;
|
||||
this->name = other.name;
|
||||
@@ -123,7 +127,9 @@ public:
|
||||
int ams_id = 0;
|
||||
int slot_id = 0;
|
||||
int cali_idx = -1;
|
||||
int nozzle_pos_id = -1; //-1 means no nozzle pos
|
||||
float nozzle_diameter;
|
||||
std::string nozzle_sn;
|
||||
std::string filament_id;
|
||||
std::string setting_id;
|
||||
std::string name;
|
||||
@@ -140,7 +146,9 @@ struct PACalibIndexInfo
|
||||
int ams_id = 0;
|
||||
int slot_id = 0;
|
||||
int cali_idx = -1; // -1 means default
|
||||
int nozzle_pos_id = -1; //-1 means no nozzle pos
|
||||
float nozzle_diameter;
|
||||
std::string nozzle_sn;
|
||||
std::string filament_id;
|
||||
};
|
||||
|
||||
@@ -148,7 +156,9 @@ struct PACalibExtruderInfo
|
||||
{
|
||||
int extruder_id = 0;
|
||||
NozzleVolumeType nozzle_volume_type;
|
||||
int nozzle_pos_id = -1; //-1 means no nozzle pos
|
||||
float nozzle_diameter;
|
||||
std::string nozzle_sn;
|
||||
std::string filament_id = "";
|
||||
bool use_extruder_id{true};
|
||||
bool use_nozzle_volume_type{true};
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "format.hpp"
|
||||
#include "Platform.hpp"
|
||||
@@ -1497,6 +1501,15 @@ std::string format_memsize(size_t bytes, unsigned int decimals)
|
||||
}
|
||||
}
|
||||
|
||||
std::string format_diameter_to_str(double diameter, int precision)
|
||||
{
|
||||
double candidates[] = {0.2, 0.4, 0.6, 0.8};
|
||||
double best = *std::min_element(std::begin(candidates), std::end(candidates), [diameter](double a, double b) { return std::abs(a - diameter) < std::abs(b - diameter); });
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(precision) << best;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// Returns platform-specific string to be used as log output or parsed in SysInfoDialog.
|
||||
// The latter parses the string with (semi)colons as separators, it should look about as
|
||||
// "desc1: value1; desc2: value2" or similar (spaces should not matter).
|
||||
|
||||
@@ -413,6 +413,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Project.hpp
|
||||
GUI/PublishDialog.cpp
|
||||
GUI/PublishDialog.hpp
|
||||
GUI/PurgeModeDialog.cpp
|
||||
GUI/PurgeModeDialog.hpp
|
||||
GUI/RammingChart.cpp
|
||||
GUI/RammingChart.hpp
|
||||
GUI/RecenterDialog.cpp
|
||||
@@ -496,6 +498,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Widgets/AMSControl.hpp
|
||||
GUI/Widgets/AMSItem.cpp
|
||||
GUI/Widgets/AMSItem.hpp
|
||||
GUI/Widgets/MultiNozzleSync.cpp
|
||||
GUI/Widgets/MultiNozzleSync.hpp
|
||||
GUI/Widgets/AxisCtrlButton.cpp
|
||||
GUI/Widgets/AxisCtrlButton.hpp
|
||||
GUI/SafetyOptionsDialog.hpp
|
||||
|
||||
@@ -1024,7 +1024,8 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
|
||||
const Transform3d& projection_matrix,
|
||||
const GUI::Size& cnv_size,
|
||||
std::function<bool(const GLVolume &)> filter_func,
|
||||
bool partly_inside_enable) const
|
||||
bool partly_inside_enable,
|
||||
std::vector<double> * printable_heights) const
|
||||
{
|
||||
GLVolumeWithIdAndZList to_render = volumes_to_render(volumes, type, view_matrix, filter_func);
|
||||
if (to_render.empty())
|
||||
@@ -1108,7 +1109,24 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
|
||||
//use -1 ad a invalid type
|
||||
shader->set_uniform("print_volume.type", -1);
|
||||
}
|
||||
|
||||
|
||||
// Per-extruder printable-height shading. The flag is set to
|
||||
// 2.0 only for multi-extruder printers (two per-extruder heights); otherwise it is forced to 0.0
|
||||
// on every render so no stale flag survives a multi->single-extruder plate switch, keeping the
|
||||
// shared gouraud shader pixel-identical for single-extruder printers. When active the height
|
||||
// branch reads print_volume.xy_data (the bed rect), so set it explicitly here.
|
||||
std::array<float, 3> extruder_printable_heights = {0.0f, 0.0f, 0.0f};
|
||||
if (printable_heights != nullptr && printable_heights->size() > 1) {
|
||||
extruder_printable_heights[0] = 2.0f;
|
||||
extruder_printable_heights[1] = static_cast<float>((*printable_heights)[0]);
|
||||
extruder_printable_heights[2] = static_cast<float>((*printable_heights)[1]);
|
||||
shader->set_uniform("extruder_printable_heights", extruder_printable_heights);
|
||||
shader->set_uniform("print_volume.xy_data", m_print_volume.data);
|
||||
}
|
||||
else {
|
||||
shader->set_uniform("extruder_printable_heights", extruder_printable_heights);
|
||||
}
|
||||
|
||||
shader->set_uniform("volume_world_matrix", volume.first->world_matrix());
|
||||
shader->set_uniform("slope.actived", m_slope.isGlobalActive && !volume.first->is_modifier && !volume.first->is_wipe_tower);
|
||||
shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>()));
|
||||
|
||||
@@ -497,7 +497,10 @@ public:
|
||||
const Transform3d& projection_matrix,
|
||||
const GUI::Size& cnv_size,
|
||||
std::function<bool(const GLVolume &)> filter_func = std::function<bool(const GLVolume &)>(),
|
||||
bool partly_inside_enable =true
|
||||
bool partly_inside_enable =true,
|
||||
// Per-extruder printable heights (extruder_printable_height); null / size<=1
|
||||
// leaves the shader's extruder_printable_heights flag at 0.0 (single-extruder = inert).
|
||||
std::vector<double> * printable_heights = nullptr
|
||||
) const;
|
||||
|
||||
// Clear the geometry
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
#include "DeviceCore/DevExtruderSystem.h"
|
||||
#include "DeviceCore/DevFilaBlackList.h"
|
||||
#include "DeviceCore/DevFilaSystem.h"
|
||||
#include "DeviceCore/DevFilaSwitch.h"
|
||||
#include "DeviceCore/DevNozzleSystem.h" // DevNozzle / GetNozzleByPosId / GetNozzleRack
|
||||
#include "DeviceCore/DevNozzleRack.h" // DevNozzleRack::IsSupported (rack gating)
|
||||
|
||||
#define FILAMENT_MAX_TEMP 300
|
||||
#define FILAMENT_MIN_TEMP 120
|
||||
@@ -298,7 +301,7 @@ void AMSMaterialsSetting::create_panel_kn(wxWindow* parent)
|
||||
if (language.find("zh") == 0)
|
||||
region = "zh";
|
||||
wxString link_url = wxString::Format("https://wiki.bambulab.com/%s/software/bambu-studio/calibration_pa", region);
|
||||
m_wiki_ctrl = new HyperLink(parent, "Wiki Guide", link_url);
|
||||
m_wiki_ctrl = new HyperLink(parent, _L("Wiki Guide"), link_url);
|
||||
cali_title_sizer->Add(m_ratio_text, 0, wxALIGN_CENTER_VERTICAL);
|
||||
cali_title_sizer->Add(m_wiki_ctrl, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
@@ -531,56 +534,121 @@ void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
|
||||
Close();
|
||||
}
|
||||
|
||||
// Nozzle-context builder for the AMS-edit blacklist check. Factors the blacklist evaluation out of
|
||||
// on_select_ok and threads rack-gated per-nozzle context (extruder id + nozzle flow + nozzle diameter)
|
||||
// into CheckFilamentInfo, returning the accumulate-all CheckResult. As a side effect it resolves
|
||||
// ams_filament_id/ams_setting_id from the matched preset. fila_name is kept as the preset name (not
|
||||
// the short alias) so the name / name_suffix rules match on the non-rack fleet (no unintended
|
||||
// activation of e.g. the PLA Glow rule).
|
||||
static DevFilaBlacklist::CheckResult
|
||||
sCheckFilamentInfo(PresetBundle* preset_bundle,
|
||||
MachineObject* obj,
|
||||
int ams_id,
|
||||
int slot_id,
|
||||
const std::string& filament_id,
|
||||
std::string& ams_filament_id,
|
||||
std::string& ams_setting_id)
|
||||
{
|
||||
DevFilaBlacklist::CheckResult result;
|
||||
if (!preset_bundle || !obj)
|
||||
return result;
|
||||
|
||||
// Orca: there is no lookup struct that also carries setting_id, so resolve the (root) preset by
|
||||
// scanning the filaments collection for a matching filament_id.
|
||||
Preset* fila_preset = nullptr;
|
||||
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); ++it) {
|
||||
if (it->filament_id == filament_id) {
|
||||
fila_preset = &(*it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!fila_preset)
|
||||
return result;
|
||||
|
||||
if (wxGetApp().app_config->get("skip_ams_blacklist_check") != "true") {
|
||||
std::string filamnt_type;
|
||||
fila_preset->get_filament_type(filamnt_type);
|
||||
|
||||
DevFilaBlacklist::CheckFilamentInfo check_info;
|
||||
check_info.dev_id = obj->get_dev_id();
|
||||
check_info.model_id = obj->printer_type;
|
||||
check_info.fila_id = fila_preset->filament_id;
|
||||
check_info.fila_type = filamnt_type;
|
||||
check_info.fila_name = fila_preset->name;
|
||||
check_info.has_filament_switch = obj->GetFilaSwitch()->IsInstalled();
|
||||
check_info.ams_id = ams_id;
|
||||
check_info.slot_id = slot_id;
|
||||
|
||||
if (auto vendor = dynamic_cast<ConfigOptionStrings*>(fila_preset->config.option("filament_vendor"));
|
||||
vendor && !vendor->values.empty()) {
|
||||
check_info.fila_vendor = vendor->values[0];
|
||||
}
|
||||
|
||||
check_info.extruder_id = obj->GetFilaSystem()->GetExtruderIdByAmsId(std::to_string(ams_id));
|
||||
|
||||
// Rack-gated per-nozzle context. For a multi-nozzle main extruder on a rack printer (H2C) the
|
||||
// specific nozzle is resolved later by the print-dispatch rack mapping, so leave
|
||||
// nozzle_flow/nozzle_diameter unset here to keep the high-flow nozzle rules dormant. Every
|
||||
// non-rack printer takes the else branch, threading the reported nozzle flow + diameter and
|
||||
// thereby activating the high-flow blacklist warnings in the AMS-edit dialog for H2D/H2S/X2D/P2S
|
||||
// (and the E3D-kit X1C/P1x).
|
||||
DevNozzleSystem* nozzle_system = obj->GetNozzleSystem();
|
||||
const bool rack_supported = nozzle_system && nozzle_system->GetNozzleRack() &&
|
||||
nozzle_system->GetNozzleRack()->IsSupported();
|
||||
if (check_info.extruder_id == MAIN_EXTRUDER_ID && rack_supported) {
|
||||
; // multi-nozzle main extruder on a rack printer — nozzle resolved later by print dispatch
|
||||
} else {
|
||||
check_info.nozzle_flow = obj->GetFilaSystem()->GetNozzleFlowStringByAmsId(std::to_string(ams_id));
|
||||
if (nozzle_system) {
|
||||
DevNozzle nozzle = nozzle_system->GetNozzleByPosId(check_info.extruder_id.value_or(-1));
|
||||
if (!nozzle.IsEmpty())
|
||||
check_info.nozzle_diameter = nozzle.GetNozzleDiameter();
|
||||
}
|
||||
}
|
||||
|
||||
result = DevFilaBlacklist::check_filaments_in_blacklist(check_info);
|
||||
}
|
||||
|
||||
ams_filament_id = fila_preset->filament_id;
|
||||
ams_setting_id = fila_preset->setting_id;
|
||||
return result;
|
||||
}
|
||||
|
||||
void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
|
||||
{
|
||||
if (!obj)
|
||||
return;
|
||||
|
||||
//get filament id
|
||||
ams_filament_id = "";
|
||||
ams_setting_id = "";
|
||||
|
||||
// the combobox item
|
||||
auto filament_item = map_filament_items[m_comboBox_filament->GetValue().ToStdString()];
|
||||
|
||||
// check filament info (rack-gated per-nozzle blacklist evaluation)
|
||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
if (preset_bundle) {
|
||||
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
|
||||
const auto& fila_check_res = sCheckFilamentInfo(preset_bundle, obj, ams_id, slot_id,
|
||||
filament_item.filament_id, ams_filament_id, ams_setting_id);
|
||||
|
||||
auto filament_item = map_filament_items[m_comboBox_filament->GetValue().ToStdString()];
|
||||
std::string filament_id = filament_item.filament_id;
|
||||
if (it->filament_id.compare(filament_id) == 0) {
|
||||
if (const auto& prohibit_items = fila_check_res.get_items_by_action("prohibition"); !prohibit_items.empty()) {
|
||||
wxString info_msg;
|
||||
for (const auto& item : prohibit_items) { info_msg += item.info_msg + "\n"; }
|
||||
MessageDialog msg_wingow(nullptr, info_msg, _L("Error"), wxICON_WARNING | wxOK);
|
||||
msg_wingow.ShowModal();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//check is it in the filament blacklist
|
||||
if (wxGetApp().app_config->get("skip_ams_blacklist_check") != "true") {
|
||||
bool in_blacklist = false;
|
||||
std::string action;
|
||||
wxString info;
|
||||
std::string filamnt_type;
|
||||
std::string filamnt_name;
|
||||
it->get_filament_type(filamnt_type);
|
||||
|
||||
auto vendor = dynamic_cast<ConfigOptionStrings *>(it->config.option("filament_vendor"));
|
||||
|
||||
if (vendor && (vendor->values.size() > 0)) {
|
||||
std::string vendor_name = vendor->values[0];
|
||||
DevFilaBlacklist::check_filaments_in_blacklist(obj->printer_type, vendor_name, filamnt_type, it->filament_id, ams_id, slot_id, it->name, in_blacklist, action, info);
|
||||
}
|
||||
|
||||
if (in_blacklist) {
|
||||
if (action == "prohibition") {
|
||||
MessageDialog msg_wingow(nullptr, info, _L("Error"), wxICON_WARNING | wxOK);
|
||||
msg_wingow.ShowModal();
|
||||
//m_comboBox_filament->SetSelection(m_filament_selection);
|
||||
return;
|
||||
}
|
||||
else if (action == "warning") {
|
||||
MessageDialog msg_wingow(nullptr, info, _L("Warning"), wxICON_INFORMATION | wxOK);
|
||||
msg_wingow.ShowModal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ams_filament_id = it->filament_id;
|
||||
ams_setting_id = it->setting_id;
|
||||
break;
|
||||
}
|
||||
if (const auto& warning_items = fila_check_res.get_items_by_action("warning"); !warning_items.empty()) {
|
||||
std::vector<FilamentWarningInfo> infos;
|
||||
for (const auto& item : warning_items) {
|
||||
FilamentWarningInfo winfo;
|
||||
winfo.info_msg = item.info_msg;
|
||||
winfo.wiki_url = item.wiki_url;
|
||||
infos.emplace_back(winfo);
|
||||
}
|
||||
FilamentWarningDialog msg_window(nullptr, _L("Warning"), infos);
|
||||
msg_window.ShowModal();
|
||||
}
|
||||
|
||||
wxString nozzle_temp_min = m_input_nozzle_min->GetTextCtrl()->GetValue();
|
||||
|
||||
@@ -22,8 +22,12 @@
|
||||
#include "Plater.hpp"
|
||||
#include "BitmapCache.hpp"
|
||||
#include "BindDialog.hpp"
|
||||
#include "slic3r/GUI/DeviceCore/DevFilaSwitch.h"
|
||||
|
||||
#include "DeviceCore/DevFilaSystem.h"
|
||||
#include "DeviceCore/DevNozzleRack.h"
|
||||
#include "DeviceCore/DevMappingNozzle.h"
|
||||
#include "DeviceTab/wgtDeviceNozzleSelect.h"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
#define MATERIAL_ITEM_SIZE wxSize(FromDIP(65), FromDIP(50))
|
||||
@@ -64,6 +68,7 @@ static void _add_containers(const AmsMapingPopup * win,
|
||||
//m_ams_wheel_mitem = ScalableBitmap(this, "ams_wheel", FromDIP(25));
|
||||
m_ams_wheel_mitem = ScalableBitmap(this, "ams_wheel_narrow", 25);
|
||||
m_ams_not_match = ScalableBitmap(this, "filament_not_mactch", 25);
|
||||
m_rack_nozzle_bitmap = ScalableBitmap(this, "dev_rack_nozzle_print_job", 22);
|
||||
|
||||
m_material_coloul = mcolour;
|
||||
m_material_name = mname;
|
||||
@@ -73,9 +78,7 @@ static void _add_containers(const AmsMapingPopup * win,
|
||||
SetDoubleBuffered(true);
|
||||
#endif //__WINDOWS__
|
||||
|
||||
SetSize(MATERIAL_ITEM_SIZE);
|
||||
SetMinSize(MATERIAL_ITEM_SIZE);
|
||||
SetMaxSize(MATERIAL_ITEM_SIZE);
|
||||
messure_size();
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
|
||||
Bind(wxEVT_PAINT, &MaterialItem::paintEvent, this);
|
||||
@@ -89,6 +92,7 @@ void MaterialItem::msw_rescale() {
|
||||
m_arraw_bitmap_white.msw_rescale();
|
||||
m_transparent_mitem.msw_rescale();
|
||||
m_ams_wheel_mitem.msw_rescale();
|
||||
m_rack_nozzle_bitmap.msw_rescale();
|
||||
}
|
||||
|
||||
void MaterialItem::allow_paint_dropdown(bool flag) {
|
||||
@@ -114,11 +118,22 @@ void MaterialItem::set_ams_info(wxColour col, wxString txt, int ctype, std::vect
|
||||
BOOST_LOG_TRIVIAL(info) << "set_ams_info " << m_ams_name;
|
||||
}
|
||||
|
||||
void MaterialItem::set_nozzle_info(const wxString& mapped_nozzle_str)
|
||||
{
|
||||
if (m_mapped_nozzle_str != mapped_nozzle_str) {
|
||||
m_mapped_nozzle_str = mapped_nozzle_str;
|
||||
messure_size(); // a mapped nozzle adds/removes a row, changing the card height
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialItem::reset_ams_info() {
|
||||
m_ams_name = "-";
|
||||
m_ams_coloul = wxColour(0xCE, 0xCE, 0xCE);
|
||||
m_ams_cols.clear();
|
||||
m_ams_ctype = 0;
|
||||
m_mapped_nozzle_str.Clear();
|
||||
messure_size();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
@@ -215,6 +230,11 @@ void MaterialItem::render(wxDC &dc)
|
||||
acolor = wxColour(0x90, 0x90, 0x90);
|
||||
}
|
||||
|
||||
// Header + AMS name keep their fixed-card positions; a mapped nozzle only adds a row below, so
|
||||
// position this text against the base card height, not the grown one (else it slides down onto
|
||||
// the nozzle row). Identical to GetSize().y when no nozzle row is present.
|
||||
const float base_y = m_mapped_nozzle_str.IsEmpty() ? (float) GetSize().y : (float) MATERIAL_ITEM_SIZE.y;
|
||||
|
||||
// materials name
|
||||
dc.SetFont(::Label::Body_13);
|
||||
|
||||
@@ -227,7 +247,7 @@ void MaterialItem::render(wxDC &dc)
|
||||
}
|
||||
|
||||
auto material_txt_size = dc.GetTextExtent(m_material_name);
|
||||
dc.DrawText(m_material_name, wxPoint((GetSize().x - material_txt_size.x) / 2, ((float)GetSize().y * 2 / 5 - material_txt_size.y) / 2));
|
||||
dc.DrawText(m_material_name, wxPoint((GetSize().x - material_txt_size.x) / 2, (base_y * 2 / 5 - material_txt_size.y) / 2));
|
||||
|
||||
|
||||
|
||||
@@ -238,11 +258,11 @@ void MaterialItem::render(wxDC &dc)
|
||||
if (mapping_txt.size() >= 4) {
|
||||
mapping_txt.insert(mapping_txt.size() / 2, "\n");
|
||||
mapping_txt_size = dc.GetTextExtent(mapping_txt);
|
||||
m_text_pos_y = ((float) GetSize().y * 3 / 5 - mapping_txt_size.y) / 2 + (float) GetSize().y * 2 / 5 - mapping_txt_size.y / 2;
|
||||
m_text_pos_y = (base_y * 3 / 5 - mapping_txt_size.y) / 2 + base_y * 2 / 5 - mapping_txt_size.y / 2;
|
||||
m_text_pos_x = mapping_txt_size.x / 4;
|
||||
} else {
|
||||
mapping_txt_size = dc.GetTextExtent(mapping_txt);
|
||||
m_text_pos_y = ((float) GetSize().y * 3 / 5 - mapping_txt_size.y) / 2 + (float) GetSize().y * 2 / 5;
|
||||
m_text_pos_y = (base_y * 3 / 5 - mapping_txt_size.y) / 2 + base_y * 2 / 5;
|
||||
m_text_pos_x = 0;
|
||||
}
|
||||
|
||||
@@ -258,12 +278,73 @@ void MaterialItem::match(bool mat)
|
||||
Refresh();
|
||||
}
|
||||
|
||||
// A filament can map to several rack nozzles; the joined "R1, R2, ..." string is wrapped to a few
|
||||
// slots per line so the label stays readable inside the narrow card.
|
||||
static constexpr size_t MAPPED_NOZZLE_STR_MAX_CHARS_PER_LINE = 7;
|
||||
|
||||
static wxString s_wrap_mapped_nozzle_str(const wxString& text)
|
||||
{
|
||||
if (text.length() <= MAPPED_NOZZLE_STR_MAX_CHARS_PER_LINE)
|
||||
return text;
|
||||
|
||||
wxArrayString parts = wxSplit(text, ',');
|
||||
wxString wrapped;
|
||||
std::vector<wxString> buffers;
|
||||
for (auto& part : parts) {
|
||||
if (!part.empty())
|
||||
buffers.push_back(part.Trim(false).Trim(true));
|
||||
|
||||
if (buffers.size() > 2) {
|
||||
if (!wrapped.empty())
|
||||
wrapped += "\n";
|
||||
wrapped += buffers[0] + " " + buffers[1] + " " + buffers[2];
|
||||
buffers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (!wrapped.empty())
|
||||
wrapped += "\n";
|
||||
if (buffers.size() > 0)
|
||||
wrapped += buffers[0];
|
||||
if (buffers.size() > 1)
|
||||
wrapped += " " + buffers[1];
|
||||
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
static int s_get_mapped_nozzle_str_line_count(const wxString& text)
|
||||
{
|
||||
if (text.empty())
|
||||
return 0;
|
||||
return std::max(1, static_cast<int>(wxSplit(s_wrap_mapped_nozzle_str(text), '\n', '\0').size()));
|
||||
}
|
||||
|
||||
void MaterialItem::messure_size()
|
||||
{
|
||||
// Without a mapped nozzle the card is a fixed swatch + AMS wheel; a mapped nozzle adds a row
|
||||
// below (glyph + slot label), growing the height by one text line per wrapped line.
|
||||
if (m_mapped_nozzle_str.empty()) {
|
||||
SetSize(MATERIAL_ITEM_SIZE);
|
||||
SetMinSize(MATERIAL_ITEM_SIZE);
|
||||
SetMaxSize(MATERIAL_ITEM_SIZE);
|
||||
} else {
|
||||
const int line_count = s_get_mapped_nozzle_str_line_count(m_mapped_nozzle_str);
|
||||
const wxSize item_size(MATERIAL_ITEM_SIZE.x, FromDIP(84 + std::max(0, line_count - 1) * 10));
|
||||
SetSize(item_size);
|
||||
SetMinSize(item_size);
|
||||
SetMaxSize(item_size);
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialItem::doRender(wxDC& dc)
|
||||
{
|
||||
wxSize size = GetSize();
|
||||
auto mcolor = m_material_coloul;
|
||||
auto acolor = m_ams_coloul;
|
||||
change_the_opacity(acolor);
|
||||
// Header + wheel keep their fixed-card geometry; when a nozzle row is present the extra height
|
||||
// below is used for it, so lay out the top of the card against the base height, not the grown one.
|
||||
const int base_y = m_mapped_nozzle_str.IsEmpty() ? size.y : MATERIAL_ITEM_SIZE.y;
|
||||
|
||||
if (mcolor.Alpha() == 0) {
|
||||
dc.DrawBitmap(m_transparent_mitem.bmp(), FromDIP(1), FromDIP(1));
|
||||
@@ -288,7 +369,7 @@ void MaterialItem::doRender(wxDC& dc)
|
||||
|
||||
//bottom rectangle in wheel bitmap, size is MATERIAL_REC_WHEEL_SIZE(22)
|
||||
auto left = (size.x / 2 - MATERIAL_REC_WHEEL_SIZE.x) / 2 + FromDIP(3);
|
||||
auto up = (size.y * 0.4 + (size.y * 0.6 - MATERIAL_REC_WHEEL_SIZE.y) / 2);
|
||||
auto up = (base_y * 0.4 + (base_y * 0.6 - MATERIAL_REC_WHEEL_SIZE.y) / 2);
|
||||
auto right = left + MATERIAL_REC_WHEEL_SIZE.x - FromDIP(3);
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
//bottom
|
||||
@@ -350,12 +431,12 @@ void MaterialItem::doRender(wxDC& dc)
|
||||
}
|
||||
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.DrawRoundedRectangle(FromDIP(0), FromDIP(0), MATERIAL_ITEM_SIZE.x - FromDIP(0), MATERIAL_ITEM_SIZE.y - FromDIP(0), 5);
|
||||
dc.DrawRoundedRectangle(FromDIP(0), FromDIP(0), MATERIAL_ITEM_SIZE.x - FromDIP(0), size.y - FromDIP(0), 5);
|
||||
|
||||
if (m_selected) {
|
||||
dc.SetPen(wxPen(AMS_CONTROL_BRAND_COLOUR, FromDIP(2)));
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.DrawRoundedRectangle(FromDIP(1), FromDIP(1), MATERIAL_ITEM_SIZE.x - FromDIP(1), MATERIAL_ITEM_SIZE.y - FromDIP(1), 5);
|
||||
dc.DrawRoundedRectangle(FromDIP(1), FromDIP(1), MATERIAL_ITEM_SIZE.x - FromDIP(1), size.y - FromDIP(1), 5);
|
||||
}
|
||||
//#endif
|
||||
if (m_text_pos_y > 0 && m_match) {
|
||||
@@ -368,7 +449,7 @@ void MaterialItem::doRender(wxDC& dc)
|
||||
}
|
||||
|
||||
auto wheel_left = (GetSize().x / 2 - m_ams_wheel_mitem.GetBmpSize().x) / 2 + FromDIP(2);
|
||||
auto wheel_top = ((float)GetSize().y * 0.6 - m_ams_wheel_mitem.GetBmpSize().y) / 2 + (float)GetSize().y * 0.4;
|
||||
auto wheel_top = ((float)base_y * 0.6 - m_ams_wheel_mitem.GetBmpSize().y) / 2 + (float)base_y * 0.4;
|
||||
|
||||
if (!m_match) {
|
||||
wheel_left += m_ams_wheel_mitem.GetBmpSize().x;
|
||||
@@ -380,6 +461,29 @@ void MaterialItem::doRender(wxDC& dc)
|
||||
dc.DrawBitmap(m_ams_wheel_mitem.bmp(), wheel_left - FromDIP(LEFT_OFFSET), wheel_top);
|
||||
}
|
||||
}
|
||||
|
||||
// Rack / filament-switcher printers: draw the physical nozzle the print-dispatch mapping assigned
|
||||
// to this filament — a divider, the rack-nozzle glyph, and the slot label ("R1", "L R", ...) in
|
||||
// the extra row below the wheel. Nothing is drawn when no nozzle is mapped (all other printers).
|
||||
if (!m_mapped_nozzle_str.IsEmpty()) {
|
||||
int row_top = wheel_top + m_ams_wheel_mitem.GetBmpSize().y + FromDIP(4);
|
||||
dc.SetPen(wxPen(wxColour("#CECECE"), 1, wxPENSTYLE_SHORT_DASH)); // Orca: WXCOLOUR_GREY400 macro not in scope here
|
||||
dc.DrawLine(FromDIP(1), row_top, FromDIP(size.x), row_top);
|
||||
|
||||
int bitmap_y = row_top + (size.y - row_top - m_rack_nozzle_bitmap.GetBmpHeight()) / 2;
|
||||
int bitmap_l = wheel_left + (m_ams_wheel_mitem.GetBmpWidth() - m_rack_nozzle_bitmap.GetBmpWidth()) / 2 + FromDIP(2);
|
||||
dc.DrawBitmap(m_rack_nozzle_bitmap.bmp(), bitmap_l, bitmap_y);
|
||||
|
||||
// Orca: BBS width-fits this label via a get_suitable_font_size overload Orca lacks; the wrapped
|
||||
// slot string is short, so a fixed label font renders it cleanly without that helper.
|
||||
const wxString wrapped = s_wrap_mapped_nozzle_str(m_mapped_nozzle_str);
|
||||
dc.SetFont(::Label::Head_12);
|
||||
dc.SetTextForeground(StateColor::darkModeColorFor(wxColour(0x26, 0x2E, 0x30)));
|
||||
const wxSize text_size = dc.GetMultiLineTextExtent(wrapped);
|
||||
int text_x = (size.x + bitmap_l + m_rack_nozzle_bitmap.GetBmpWidth() - text_size.x) / 2;
|
||||
int text_y = row_top + (size.y - row_top - text_size.y) / 2;
|
||||
dc.DrawText(wrapped, text_x, text_y);
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialItem::reset_valid_info() {
|
||||
@@ -725,6 +829,16 @@ AmsMapingPopup::AmsMapingPopup(wxWindow *parent, bool use_in_sync_dialog) :
|
||||
m_right_first_text_panel->SetMaxSize(wxSize(-1, FromDIP(same_height)));
|
||||
|
||||
m_sizer_ams_right->Add(m_right_first_text_panel, 0, wxEXPAND | wxBOTTOM | wxTOP, FromDIP(8));
|
||||
|
||||
// Flush-waste warning (rack printers only). Orca: uses a plain Label here.
|
||||
// Hidden by default => zero layout effect for non-rack printers.
|
||||
m_flush_warning_panel = new Label(m_right_marea_panel);
|
||||
m_flush_warning_panel->SetFont(::Label::Body_12);
|
||||
m_flush_warning_panel->SetForegroundColour(StateColor::darkModeColorFor("#F09A17"));
|
||||
m_flush_warning_panel->SetBackgroundColour(StateColor::darkModeColorFor("#FFFFE0"));
|
||||
m_flush_warning_panel->Show(false);
|
||||
m_sizer_ams_right->Add(m_flush_warning_panel, 0, wxBOTTOM | wxALIGN_LEFT, FromDIP(8));
|
||||
|
||||
m_right_split_ams_sizer = create_split_sizer(m_right_marea_panel, _L("Right AMS"));
|
||||
m_sizer_ams_right->Add(m_right_split_ams_sizer, 0, wxEXPAND, 0);
|
||||
m_sizer_ams_right->Add(m_sizer_ams_basket_right, 0, wxEXPAND|wxTOP, FromDIP(8));
|
||||
@@ -740,6 +854,14 @@ AmsMapingPopup::AmsMapingPopup(wxWindow *parent, bool use_in_sync_dialog) :
|
||||
m_sizer_ams->Add(0, 0, 0, wxEXPAND, FromDIP(15));
|
||||
m_sizer_ams->Add(m_right_marea_panel, 1, wxEXPAND, FromDIP(0));
|
||||
|
||||
// Rack nozzle manual-pick, alongside the AMS area (rack printers only). Hidden by default
|
||||
// => contributes nothing to layout for non-rack printers.
|
||||
m_rack_nozzle_select = new wgtDeviceNozzleRackSelect(this);
|
||||
m_rack_nozzle_select->Bind(EVT_NOZZLE_SELECT_CHANGED, &AmsMapingPopup::OnNozzleMappingSelected, this);
|
||||
m_rack_nozzle_select->Bind(EVT_NOZZLE_SELECT_CLICKED, [this](wxCommandEvent& e) { this->Dismiss(); });
|
||||
m_rack_nozzle_select->Show(false);
|
||||
m_sizer_ams->Add(m_rack_nozzle_select, 0, wxEXPAND | wxTOP | wxLEFT, FromDIP(15));
|
||||
|
||||
m_sizer_main->Add(title_panel, 0, wxEXPAND | wxALL, FromDIP(2));
|
||||
m_sizer_main->Add(m_sizer_ams, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(14));
|
||||
m_sizer_main->Add( 0, 0, 0, wxTOP, FromDIP(14));
|
||||
@@ -782,6 +904,7 @@ void AmsMapingPopup::msw_rescale()
|
||||
m_right_extra_slot->msw_rescale();
|
||||
for (auto item : m_mapping_item_list) { item->msw_rescale(); }
|
||||
for (auto container : m_amsmapping_container_list) { container->msw_rescale(); }
|
||||
if (m_rack_nozzle_select) { m_rack_nozzle_select->Rescale(); }
|
||||
|
||||
Fit();
|
||||
Refresh();
|
||||
@@ -852,6 +975,8 @@ void AmsMapingPopup::on_left_down(wxMouseEvent &evt)
|
||||
auto left = item->GetSize();
|
||||
|
||||
if (pos.x > p_rect.x && pos.y > p_rect.y && pos.x < (p_rect.x + item->GetSize().x) && pos.y < (p_rect.y + item->GetSize().y)) {
|
||||
// Orca: the external spool is un-pickable while a Filament Track Switch is installed (Apple hit-tests here).
|
||||
if (m_fila_switch_installed && (item->m_ams_id == VIRTUAL_TRAY_MAIN_ID || item->m_ams_id == VIRTUAL_TRAY_DEPUTY_ID)) { return; }
|
||||
if (item->m_tray_data.type == TrayType::NORMAL) {
|
||||
if (!m_ext_mapping_filatype_check && (item->m_ams_id == VIRTUAL_TRAY_MAIN_ID || item->m_ams_id == VIRTUAL_TRAY_DEPUTY_ID)) {
|
||||
// Do nothing
|
||||
@@ -1038,7 +1163,7 @@ void AmsMapingPopup::update_items_check_state(const std::vector<FilamentInfo>& a
|
||||
}
|
||||
}
|
||||
|
||||
void AmsMapingPopup::update(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result)
|
||||
void AmsMapingPopup::update(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result, bool use_dynamic_switch, std::optional<PrintFromType> print_type)
|
||||
{
|
||||
//BOOST_LOG_TRIVIAL(info) << "ams_mapping nozzle count " << obj->get_extder_system()->nozzle.size();
|
||||
BOOST_LOG_TRIVIAL(info) << "ams_mapping total count " << obj->GetFilaSystem()->GetAmsCount();
|
||||
@@ -1047,6 +1172,11 @@ void AmsMapingPopup::update(MachineObject* obj, const std::vector<FilamentInfo>&
|
||||
if (!obj) {return;}
|
||||
m_ams_remain_detect_flag = obj->GetFilaSystem()->IsDetectRemainEnabled();
|
||||
|
||||
// Orca: with a Filament Track Switch installed, filament routes through the switch and the external
|
||||
// spool cannot be used, so its mapping slots are rendered greyed-out and un-pickable. Inert (false)
|
||||
// on any printer without a switch — GetFilaSwitch()->IsInstalled() defaults to false.
|
||||
m_fila_switch_installed = obj->GetFilaSwitch()->IsInstalled();
|
||||
|
||||
for (auto& ams_container : m_amsmapping_container_list) {
|
||||
ams_container->Destroy();
|
||||
}
|
||||
@@ -1252,11 +1382,68 @@ void AmsMapingPopup::update(MachineObject* obj, const std::vector<FilamentInfo>&
|
||||
} else {
|
||||
m_right_split_ams_sizer->Show(false);
|
||||
}
|
||||
|
||||
/*rack (manual nozzle pick; rack printers only)*/
|
||||
update_rack_select(obj, use_dynamic_switch, print_type);
|
||||
update_flush_waste(obj);
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void AmsMapingPopup::update_rack_select(MachineObject* obj, bool use_dynamic_switch, std::optional<PrintFromType> print_from_type)
|
||||
{
|
||||
// Orca: MachineObject has no GetNozzleRack() convenience; route through the nozzle system instead.
|
||||
m_rack = obj ? obj->GetNozzleSystem()->GetNozzleRack() : nullptr;
|
||||
|
||||
bool show_rack_select_area = false;
|
||||
if (!m_mapping_from_multi_machines && !m_use_in_sync_dialog &&
|
||||
obj && obj->GetNozzleSystem()->GetNozzleRack()->IsSupported() &&
|
||||
print_from_type.has_value() && print_from_type.value() == PrintFromType::FROM_NORMAL) {
|
||||
const auto& nozzle_pos_vec = obj->get_nozzle_mapping_result()->GetMappedNozzlePosVecByFilaId(m_current_filament_id);
|
||||
m_rack_nozzle_select->UpdatSelectedNozzles(obj->GetNozzleSystem()->GetNozzleRack(), nozzle_pos_vec, use_dynamic_switch, print_from_type);
|
||||
show_rack_select_area = true;
|
||||
}
|
||||
|
||||
if (show_rack_select_area != m_rack_nozzle_select->IsShown()) {
|
||||
m_right_tip_text = show_rack_select_area ? _L("Select Filament && Hotends") : _L("Select Filament");
|
||||
m_right_tips->SetLabel(m_right_tip_text);
|
||||
m_rack_nozzle_select->Show(show_rack_select_area);
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
}
|
||||
|
||||
void AmsMapingPopup::update_flush_waste(MachineObject* obj)
|
||||
{
|
||||
if (!obj) {
|
||||
m_flush_warning_panel->Hide();
|
||||
return;
|
||||
};
|
||||
|
||||
float flush_waste_base = obj->get_nozzle_mapping_result()->GetFlushWeightBase();
|
||||
float flush_waste_current = obj->get_nozzle_mapping_result()->GetFlushWeightCurrent();
|
||||
if ((flush_waste_base != -1) && (flush_waste_current != -1) && flush_waste_current > flush_waste_base) {
|
||||
m_flush_warning_panel->SetLabel(wxString::Format(_L("Printing with the current nozzle may produce an extra %0.2f g of waste."), flush_waste_current - flush_waste_base));
|
||||
m_flush_warning_panel->Show();
|
||||
} else {
|
||||
m_flush_warning_panel->Hide();
|
||||
}
|
||||
}
|
||||
|
||||
void AmsMapingPopup::OnNozzleMappingSelected(wxCommandEvent& evt)
|
||||
{
|
||||
if (auto ptr = m_rack.lock()) {
|
||||
MachineObject* obj = ptr->GetNozzleSystem()->GetOwner();
|
||||
obj->get_nozzle_mapping_result()->SetManualNozzleMappingByFila(m_current_filament_id, m_rack_nozzle_select->GetSelectedNozzlePosID());
|
||||
update_flush_waste(obj);
|
||||
}
|
||||
|
||||
evt.Skip();
|
||||
Dismiss();
|
||||
}
|
||||
|
||||
std::vector<TrayData> AmsMapingPopup::parse_ams_mapping(const std::map<std::string, DevAms*, NumericStrCompare>& amsList)
|
||||
{
|
||||
std::vector<TrayData> m_tray_data;
|
||||
@@ -1367,6 +1554,20 @@ void AmsMapingPopup::add_ext_ams_mapping(TrayData tray_data, MappingItem* item)
|
||||
#ifdef __APPLE__
|
||||
m_mapping_item_list.push_back(item);
|
||||
#endif
|
||||
|
||||
// Orca: a Filament Track Switch cannot route the external spool, so grey the ext slot out and make it
|
||||
// un-pickable, with a tooltip explaining why (the click below is disabled; on_left_down also skips it).
|
||||
if (m_fila_switch_installed) {
|
||||
const wxString disp_name = (tray_data.type == THIRD) ? wxString("?")
|
||||
: (tray_data.type == EMPTY) ? wxString("-")
|
||||
: wxString(tray_data.name);
|
||||
item->set_data(m_tag_material, wxColour(0xEE, 0xEE, 0xEE), disp_name, false, tray_data, true);
|
||||
item->SetToolTip(_L("External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it."));
|
||||
item->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent&) { /* un-pickable while the switch is installed */ });
|
||||
item->set_tray_index(_L("Ext"));
|
||||
return;
|
||||
}
|
||||
|
||||
// set button
|
||||
if (tray_data.type == NORMAL) {
|
||||
if (is_match_material(tray_data.filament_type)) {
|
||||
@@ -2305,12 +2506,25 @@ _GetBackupStatus(unsigned int fila_back_group)
|
||||
|
||||
for (int j = 16; j < 32; j++)/* single ams is from 128*/
|
||||
{
|
||||
// N9 / AMS-Lite-mixed reports its four backup slots in bits 24-27, which decode directly to
|
||||
// tray ids 24-27 (handled in the loop below). Keep those bits out of the single-ams (128+)
|
||||
// range; all other bits keep their existing mapping unchanged.
|
||||
if (j >= 24 && j <= 27)
|
||||
continue;
|
||||
if (fila_back_group & (1 << j))
|
||||
{
|
||||
trayid_group[128 + j - 16] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 24; i <= 27; i++)/* ams lite for N9 */
|
||||
{
|
||||
if (fila_back_group & (1 << i))
|
||||
{
|
||||
trayid_group[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return trayid_group;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,15 @@
|
||||
|
||||
#include "slic3r/GUI/DeviceCore/DevUtil.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#define MAPPING_ITEM_INVALID_REMAIN -1
|
||||
|
||||
namespace Slic3r {
|
||||
class DevNozzleRack;
|
||||
namespace GUI { class wgtDeviceNozzleRackSelect; }
|
||||
}
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
|
||||
@@ -93,6 +100,9 @@ public:
|
||||
//info
|
||||
wxColour m_ams_coloul;
|
||||
wxString m_ams_name;
|
||||
// Physical nozzle(s) the print-dispatch mapping assigned to this filament (e.g. "R1", "L R").
|
||||
// Empty on printers without a nozzle rack or filament switcher; when set, the card grows a row.
|
||||
wxString m_mapped_nozzle_str;
|
||||
int m_ams_ctype = 0;
|
||||
std::vector<wxColour> m_ams_cols = std::vector<wxColour>();
|
||||
//reset
|
||||
@@ -107,6 +117,7 @@ public:
|
||||
ScalableBitmap m_filament_wheel_transparent;
|
||||
ScalableBitmap m_ams_wheel_mitem;
|
||||
ScalableBitmap m_ams_not_match;
|
||||
ScalableBitmap m_rack_nozzle_bitmap;
|
||||
|
||||
bool m_selected {false};
|
||||
bool m_warning{false};
|
||||
@@ -117,6 +128,10 @@ public:
|
||||
void allow_paint_dropdown(bool flag);
|
||||
void set_ams_info(wxColour col, wxString txt, int ctype=0, std::vector<wxColour> cols= std::vector<wxColour>(),bool record_back_info = false);
|
||||
void reset_ams_info();
|
||||
// Set the mapped-nozzle label ("R1", "L R", ...); grows/shrinks the card as the row appears/clears.
|
||||
void set_nozzle_info(const wxString& mapped_nozzle_str);
|
||||
// Size the card: base swatch height, plus one row when a nozzle label is present.
|
||||
void messure_size();
|
||||
|
||||
void disable();
|
||||
void enable();
|
||||
@@ -229,6 +244,9 @@ public:
|
||||
bool m_has_unmatch_filament {false};
|
||||
int m_current_filament_id;
|
||||
ShowType m_show_type{ShowType::RIGHT};
|
||||
// Orca: set from update() — true when a Filament Track Switch is installed, which makes the
|
||||
// external-spool slots un-pickable (false on any printer without a switch, so behavior is unchanged).
|
||||
bool m_fila_switch_installed{false};
|
||||
std::string m_tag_material;
|
||||
wxBoxSizer *m_sizer_main{nullptr};
|
||||
wxBoxSizer *m_sizer_ams{nullptr};
|
||||
@@ -259,12 +277,19 @@ public:
|
||||
wxBoxSizer* m_sizer_split_ams_right;
|
||||
bool m_mapping_from_multi_machines {false};
|
||||
|
||||
// Rack nozzle manual-pick (rack printers only; hidden by default via Show(false)).
|
||||
wgtDeviceNozzleRackSelect* m_rack_nozzle_select{nullptr};
|
||||
Label* m_flush_warning_panel{nullptr}; // Orca: uses a plain Label for the flush warning
|
||||
std::weak_ptr<DevNozzleRack> m_rack;
|
||||
|
||||
void set_sizer_title(wxBoxSizer *sizer, wxString text);
|
||||
wxBoxSizer* create_split_sizer(wxWindow* parent, wxString text);
|
||||
void set_send_win(wxWindow* win) {send_win = win;};
|
||||
void update_materials_list(std::vector<std::string> list);
|
||||
void set_tag_texture(std::string texture);
|
||||
void update(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result);
|
||||
void update(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result, bool use_dynamic_switch = false, std::optional<PrintFromType> print_type = std::nullopt);
|
||||
void update_rack_select(MachineObject* obj, bool use_dynamic_switch, std::optional<PrintFromType> print_type);
|
||||
void update_flush_waste(MachineObject* obj);
|
||||
void update_title(MachineObject* obj);
|
||||
void update_items_check_state(const std::vector<FilamentInfo>& ams_mapping_result);
|
||||
void update_ams_data_multi_machines();
|
||||
@@ -295,6 +320,8 @@ public:
|
||||
void EnableExtMappingFilaTypeCheck(bool to_check = true) { m_ext_mapping_filatype_check = to_check;} ;
|
||||
|
||||
private:
|
||||
void OnNozzleMappingSelected(wxCommandEvent& evt);
|
||||
|
||||
ResetCallback m_reset_callback{nullptr};
|
||||
std::string m_material_index;
|
||||
bool m_only_show_ext_spool{false};
|
||||
|
||||
@@ -231,7 +231,20 @@ void BackgroundSlicingProcess::process_fff()
|
||||
if (m_current_plate->get_real_filament_map_mode(preset_bundle.project_config) < FilamentMapMode::fmmManual) {
|
||||
std::vector<int> f_maps = m_fff_print->get_filament_maps();
|
||||
m_current_plate->set_filament_maps(f_maps);
|
||||
// The engine's concrete per-filament volume assignment is merged into the print
|
||||
// config by the ToolOrdering write-back; reading it back keeps the plate config the
|
||||
// next apply overlays equal to the written-back state (no diff, no re-invalidation)
|
||||
// and persists the auto result (always concrete Std/HF, never the Hybrid seed).
|
||||
std::vector<int> f_volume_maps = m_fff_print->get_filament_volume_maps();
|
||||
m_current_plate->set_filament_volume_maps(f_volume_maps);
|
||||
}
|
||||
if (m_current_plate->get_real_filament_map_mode(preset_bundle.project_config) != FilamentMapMode::fmmNozzleManual) {
|
||||
// The engine-resolved nozzle map is read back for every non-nozzle-manual mode so the
|
||||
// plate config the next apply overlays matches the engine's written-back state
|
||||
// (otherwise the full-config diff would invalidate the g-code on every apply).
|
||||
std::vector<int> f_nozzle_maps = m_fff_print->get_filament_nozzle_maps();
|
||||
m_current_plate->set_filament_nozzle_maps(f_nozzle_maps);
|
||||
}
|
||||
wxCommandEvent evt(m_event_slicing_completed_id);
|
||||
// Post the Slicing Finished message for the G-code viewer to update.
|
||||
// Passing the timestamp
|
||||
@@ -241,6 +254,9 @@ void BackgroundSlicingProcess::process_fff()
|
||||
//BBS: add plate index into render params
|
||||
m_temp_output_path = this->get_current_plate()->get_tmp_gcode_path();
|
||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, [this](const ThumbnailsParams& params) { return this->render_thumbnails(params); });
|
||||
// Orca: BBL printers post-process the g-code in place here and never re-parse it into a fresh
|
||||
// GCodeProcessorResult, so m_gcode_result->nozzle_group_result (consumed by the H2C print-dispatch
|
||||
// nozzle mapping) survives post-processing. No preservation guard is needed on this path.
|
||||
if(m_fff_print->is_BBL_printer())
|
||||
run_post_process_scripts(m_temp_output_path, false, "File", m_temp_output_path, m_fff_print->full_print_config());
|
||||
|
||||
|
||||
@@ -590,7 +590,7 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
||||
sizer_error_code->Add(m_st_txt_error_code, 0, wxALL, 0);
|
||||
|
||||
|
||||
auto st_title_error_desc = new wxStaticText(m_sw_bind_failed_info, wxID_ANY, wxT("Error desc"));
|
||||
auto st_title_error_desc = new wxStaticText(m_sw_bind_failed_info, wxID_ANY, _L("Error desc"));
|
||||
auto st_title_error_desc_doc = new wxStaticText(m_sw_bind_failed_info, wxID_ANY, ": ");
|
||||
m_st_txt_error_desc = new Label(m_sw_bind_failed_info, wxEmptyString);
|
||||
st_title_error_desc->SetForegroundColour(0x909090);
|
||||
@@ -607,7 +607,7 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
||||
sizer_error_desc->Add(st_title_error_desc_doc, 0, wxALL, 0);
|
||||
sizer_error_desc->Add(m_st_txt_error_desc, 0, wxALL, 0);
|
||||
|
||||
auto st_title_extra_info = new wxStaticText(m_sw_bind_failed_info, wxID_ANY, wxT("Extra info"));
|
||||
auto st_title_extra_info = new wxStaticText(m_sw_bind_failed_info, wxID_ANY, _L("Extra info"));
|
||||
auto st_title_extra_info_doc = new wxStaticText(m_sw_bind_failed_info, wxID_ANY, ": ");
|
||||
m_st_txt_extra_info = new Label(m_sw_bind_failed_info, wxEmptyString);
|
||||
st_title_extra_info->SetForegroundColour(0x909090);
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
#include "Plater.hpp"
|
||||
#include "DeviceCore/DevExtruderSystem.h"
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevConfigUtil.h"
|
||||
#include "DeviceCore/DevNozzleSystem.h"
|
||||
#include "DeviceCore/DevNozzleRack.h"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
@@ -26,6 +29,7 @@ namespace GUI {
|
||||
enum CaliColumnType : int {
|
||||
Cali_Name = 0,
|
||||
Cali_Filament,
|
||||
Cali_Nozzle_ID,
|
||||
Cali_Nozzle,
|
||||
Cali_K_Value,
|
||||
Cali_Delete,
|
||||
@@ -33,6 +37,9 @@ enum CaliColumnType : int {
|
||||
Cali_Type_Count
|
||||
};
|
||||
|
||||
// Orca: derive nozzle-volume support from the machine preset's nozzle_volume array
|
||||
// rather than a live device-capability query, to avoid flipping the Nozzle-Flow column
|
||||
// visibility on shipping printers (H2D/X1/P1/A1).
|
||||
bool support_nozzle_volume(const MachineObject* obj)
|
||||
{
|
||||
if (!obj)
|
||||
@@ -49,14 +56,17 @@ bool support_nozzle_volume(const MachineObject* obj)
|
||||
return false;
|
||||
}
|
||||
|
||||
int get_colume_idx(CaliColumnType type, MachineObject* obj)
|
||||
// Rack hotend position code -> label: 0 = fixed ("R"); >=0x10 = rack slot number; else N/A.
|
||||
static wxString nozzle_id_code_to_string(int code)
|
||||
{
|
||||
if (!support_nozzle_volume(obj)
|
||||
&& (type > CaliColumnType::Cali_Nozzle)) {
|
||||
return type - 1;
|
||||
if (code == 0) {
|
||||
return "R";
|
||||
} else if (code >= 0x10) {
|
||||
return wxString::Format("%d", code - 0x10 + 1);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "Nozzle position id is -1 or invalid.";
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
static wxString get_preset_name_by_filament_id(std::string filament_id)
|
||||
@@ -244,6 +254,10 @@ void HistoryWindow::on_device_connected(MachineObject* obj)
|
||||
}
|
||||
m_comboBox_nozzle_dia->SetSelection(selection);
|
||||
|
||||
m_extruder_switch_btn->SetLabels(
|
||||
_L(DevPrinterConfigUtil::get_toolhead_display_name(obj->printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase)),
|
||||
_L(DevPrinterConfigUtil::get_toolhead_display_name(obj->printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase)));
|
||||
|
||||
if (obj->is_multi_extruders())
|
||||
m_extruder_switch_btn->Show();
|
||||
else
|
||||
@@ -320,7 +334,21 @@ void HistoryWindow::enbale_action_buttons(bool enable) {
|
||||
}
|
||||
}
|
||||
|
||||
// The main extruder can carry a nozzle rack (H2C); the Nozzle-ID column only shows for it.
|
||||
bool HistoryWindow::support_nozzle_id_column()
|
||||
{
|
||||
if (!curr_obj || get_extruder_id() != MAIN_EXTRUDER_ID)
|
||||
return false;
|
||||
auto ns = curr_obj->GetNozzleSystem();
|
||||
if (!ns)
|
||||
return false;
|
||||
auto rack = ns->GetNozzleRack();
|
||||
return rack && rack->IsSupported();
|
||||
}
|
||||
|
||||
void HistoryWindow::sync_history_data() {
|
||||
int column_idx = 0;
|
||||
|
||||
Freeze();
|
||||
m_history_data_panel->DestroyChildren();
|
||||
m_history_data_panel->Enable();
|
||||
@@ -331,24 +359,35 @@ void HistoryWindow::sync_history_data() {
|
||||
|
||||
m_history_data_panel->SetSizer(gbSizer, true);
|
||||
|
||||
const bool has_nozzle_id = support_nozzle_id_column();
|
||||
|
||||
auto title_name = new Label(m_history_data_panel, _L("Name"));
|
||||
title_name->SetFont(Label::Head_14);
|
||||
gbSizer->Add(title_name, {0, get_colume_idx(CaliColumnType::Cali_Name, curr_obj) }, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
BOOST_LOG_TRIVIAL(info) << "=====================" << title_name->GetLabelText().ToStdString();
|
||||
gbSizer->Add(title_name, {0, column_idx++ }, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
|
||||
auto title_preset_name = new Label(m_history_data_panel, _L("Filament"));
|
||||
title_preset_name->SetFont(Label::Head_14);
|
||||
gbSizer->Add(title_preset_name, { 0, get_colume_idx(CaliColumnType::Cali_Filament, curr_obj) }, { 1, 1 }, wxBOTTOM, FromDIP(15));
|
||||
gbSizer->Add(title_preset_name, { 0, column_idx++ }, { 1, 1 }, wxBOTTOM, FromDIP(15));
|
||||
|
||||
if (has_nozzle_id) {
|
||||
auto nozzle_id_name = new Label(m_history_data_panel, _L("Nozzle ID"));
|
||||
nozzle_id_name->SetFont(Label::Head_14);
|
||||
{
|
||||
wxString chd_ext_name = _L(DevPrinterConfigUtil::get_toolhead_display_name(curr_obj->printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::LowerCase));
|
||||
nozzle_id_name->SetToolTip(wxString::Format(_L("Note: The hotend number on the %s is tied to the holder. When the hotend is moved to a new holder, its number will update automatically."), chd_ext_name));
|
||||
}
|
||||
gbSizer->Add(nozzle_id_name, {0, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
}
|
||||
|
||||
if (support_nozzle_volume(curr_obj)) {
|
||||
auto nozzle_name = new Label(m_history_data_panel, _L("Nozzle Flow"));
|
||||
nozzle_name->SetFont(Label::Head_14);
|
||||
gbSizer->Add(nozzle_name, {0, get_colume_idx(CaliColumnType::Cali_Nozzle, curr_obj)}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
gbSizer->Add(nozzle_name, {0, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
}
|
||||
|
||||
auto title_k = new Label(m_history_data_panel, _L("Factor K"));
|
||||
title_k->SetFont(Label::Head_14);
|
||||
gbSizer->Add(title_k, { 0, get_colume_idx(CaliColumnType::Cali_K_Value,curr_obj) }, { 1, 1 }, wxBOTTOM, FromDIP(15));
|
||||
gbSizer->Add(title_k, { 0, column_idx++ }, { 1, 1 }, wxBOTTOM, FromDIP(15));
|
||||
|
||||
// Hide
|
||||
//auto title_n = new Label(m_history_data_panel, wxID_ANY, _L("N"));
|
||||
@@ -357,7 +396,12 @@ void HistoryWindow::sync_history_data() {
|
||||
|
||||
auto title_action = new Label(m_history_data_panel, _L("Action"));
|
||||
title_action->SetFont(Label::Head_14);
|
||||
gbSizer->Add(title_action, {0, get_colume_idx(CaliColumnType::Cali_Delete, curr_obj)}, {1, 1});
|
||||
gbSizer->Add(title_action, {0, column_idx++}, {1, 1});
|
||||
|
||||
// grid item num in row (Delete + Edit share one "Action" header)
|
||||
const int column_count = column_idx + 1;
|
||||
// reset column_idx for the data rows
|
||||
column_idx = 0;
|
||||
|
||||
int i = 1;
|
||||
for (auto& result : m_calib_results_history) {
|
||||
@@ -373,13 +417,13 @@ void HistoryWindow::sync_history_data() {
|
||||
n_value->Hide();
|
||||
auto delete_button = new Button(m_history_data_panel, _L("Delete"));
|
||||
delete_button->SetStyle(ButtonStyle::Alert, ButtonType::Window);
|
||||
delete_button->Bind(wxEVT_BUTTON, [this, gbSizer, i, &result](auto& e) {
|
||||
delete_button->Bind(wxEVT_BUTTON, [this, gbSizer, i, &result, column_count](auto& e) {
|
||||
if (m_ui_op_lock) {
|
||||
return;
|
||||
} else {
|
||||
m_ui_op_lock = true;
|
||||
}
|
||||
for (int j = 0; j < HISTORY_WINDOW_ITEMS_COUNT; j++) {
|
||||
for (int j = 0; j < column_count; j++) {
|
||||
auto item = gbSizer->FindItemAtPosition({ i, j });
|
||||
if (item && item->GetWindow())
|
||||
item->GetWindow()->Hide();
|
||||
@@ -392,6 +436,8 @@ void HistoryWindow::sync_history_data() {
|
||||
cali_info.cali_idx = result.cali_idx;
|
||||
cali_info.nozzle_diameter = result.nozzle_diameter;
|
||||
cali_info.filament_id = result.filament_id;
|
||||
cali_info.nozzle_pos_id = result.nozzle_pos_id;
|
||||
cali_info.nozzle_sn = result.nozzle_sn;
|
||||
CalibUtils::delete_PA_calib_result(cali_info);
|
||||
});
|
||||
|
||||
@@ -418,17 +464,23 @@ void HistoryWindow::sync_history_data() {
|
||||
}
|
||||
});
|
||||
|
||||
gbSizer->Add(name_value, {i, get_colume_idx(CaliColumnType::Cali_Name, curr_obj)}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
gbSizer->Add(preset_name_value, {i, get_colume_idx(CaliColumnType::Cali_Filament, curr_obj)}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
gbSizer->Add(name_value, {i, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
gbSizer->Add(preset_name_value, {i, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
if (has_nozzle_id) {
|
||||
wxString nozzle_id = nozzle_id_code_to_string(result.nozzle_pos_id);
|
||||
auto nozzle_id_label = new Label(m_history_data_panel, nozzle_id);
|
||||
gbSizer->Add(nozzle_id_label, {i, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
}
|
||||
if (support_nozzle_volume(curr_obj)) {
|
||||
wxString nozzle_name = get_nozzle_volume_type_name(result.nozzle_volume_type);
|
||||
auto nozzle_name_label = new Label(m_history_data_panel, nozzle_name);
|
||||
gbSizer->Add(nozzle_name_label, {i, get_colume_idx(CaliColumnType::Cali_Nozzle, curr_obj)}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
gbSizer->Add(nozzle_name_label, {i, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
}
|
||||
gbSizer->Add(k_value, {i, get_colume_idx(CaliColumnType::Cali_K_Value, curr_obj)}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
gbSizer->Add(k_value, {i, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
//gbSizer->Add(n_value, { i, 3 }, { 1, 1 }, wxBOTTOM, FromDIP(15));
|
||||
gbSizer->Add(delete_button, {i, get_colume_idx(CaliColumnType::Cali_Delete, curr_obj)}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
gbSizer->Add(edit_button, {i, get_colume_idx(CaliColumnType::Cali_Edit, curr_obj)}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
gbSizer->Add(delete_button, {i, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
gbSizer->Add(edit_button, {i, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
|
||||
column_idx = 0;
|
||||
i++;
|
||||
m_ui_op_lock = false;
|
||||
}
|
||||
@@ -523,13 +575,22 @@ EditCalibrationHistoryDialog::EditCalibrationHistoryDialog(wxWindow
|
||||
if (obj && obj->is_multi_extruders()) {
|
||||
|
||||
Label *extruder_name_title = new Label(top_panel, _L("Extruder"));
|
||||
int extruder_index = obj->is_main_extruder_on_left() ? result.extruder_id : 1 - result.extruder_id;
|
||||
wxString extruder_name = extruder_index == 0 ? _L("Left") : _L("Right");
|
||||
wxString extruder_name = _L(DevPrinterConfigUtil::get_toolhead_display_name(
|
||||
obj->printer_type, result.extruder_id, ToolHeadComponent::Extruder, ToolHeadNameCase::TitleCase, true));
|
||||
Label *extruder_name_value = new Label(top_panel, extruder_name);
|
||||
flex_sizer->Add(extruder_name_title);
|
||||
flex_sizer->Add(extruder_name_value);
|
||||
}
|
||||
|
||||
if (obj && obj->GetNozzleSystem() && obj->GetNozzleSystem()->GetNozzleRack()
|
||||
&& obj->GetNozzleSystem()->GetNozzleRack()->IsSupported() && result.extruder_id == MAIN_EXTRUDER_ID) {
|
||||
Label* nozzle_id_title = new Label(top_panel, _L("Nozzle ID"));
|
||||
wxString nozzle_id = nozzle_id_code_to_string(result.nozzle_pos_id);
|
||||
Label* nozzle_id_value = new Label(top_panel, nozzle_id);
|
||||
flex_sizer->Add(nozzle_id_title);
|
||||
flex_sizer->Add(nozzle_id_value);
|
||||
}
|
||||
|
||||
if (support_nozzle_volume(curr_obj)) {
|
||||
Label *nozzle_name_title = new Label(top_panel, _L("Nozzle"));
|
||||
wxString nozzle_name;
|
||||
@@ -768,14 +829,40 @@ NewCalibrationHistoryDialog::NewCalibrationHistoryDialog(wxWindow *parent, const
|
||||
Label *extruder_name_title = new Label(top_panel, _L("Extruder"));
|
||||
m_comboBox_extruder = new ::ComboBox(top_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, NEW_HISTORY_DIALOG_INPUT_SIZE, 0, nullptr, wxCB_READONLY);
|
||||
wxArrayString extruder_items;
|
||||
extruder_items.push_back(_L("Left"));
|
||||
extruder_items.push_back(_L("Right"));
|
||||
extruder_items.push_back(_L(DevPrinterConfigUtil::get_toolhead_display_name(
|
||||
curr_obj->printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::TitleCase, true)));
|
||||
extruder_items.push_back(_L(DevPrinterConfigUtil::get_toolhead_display_name(
|
||||
curr_obj->printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::TitleCase, true)));
|
||||
m_comboBox_extruder->Set(extruder_items);
|
||||
m_comboBox_extruder->SetSelection(-1);
|
||||
flex_sizer->Add(extruder_name_title);
|
||||
flex_sizer->Add(m_comboBox_extruder);
|
||||
}
|
||||
|
||||
// Nozzle ID (rack printers only): choose the hotend position this manual record belongs to.
|
||||
if (curr_obj->GetNozzleSystem() && curr_obj->GetNozzleSystem()->GetNozzleRack()
|
||||
&& curr_obj->GetNozzleSystem()->GetNozzleRack()->IsSupported()) {
|
||||
auto rack = curr_obj->GetNozzleSystem()->GetNozzleRack();
|
||||
Label *nozzle_id_title = new Label(top_panel, _L("Nozzle ID"));
|
||||
m_comboBox_nozzle_id = new ::ComboBox(top_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, NEW_HISTORY_DIALOG_INPUT_SIZE, 0, nullptr, wxCB_READONLY);
|
||||
m_comboBox_nozzle_id->Bind(wxEVT_COMMAND_COMBOBOX_SELECTED, &NewCalibrationHistoryDialog::on_select_nozzle_pos, this);
|
||||
|
||||
auto main_extder = curr_obj->GetExtderSystem()->GetExtderById(MAIN_EXTRUDER_ID);
|
||||
int r_nozzle_id = main_extder ? main_extder->GetNozzleId() : 0;
|
||||
auto r_nozzle = curr_obj->GetNozzleSystem()->GetExtNozzle(r_nozzle_id);
|
||||
if (r_nozzle.IsNormal()) {
|
||||
m_comboBox_nozzle_id->Append("R", wxNullBitmap, new int{0});
|
||||
}
|
||||
for (auto nozzle_item : rack->GetRackNozzles()) {
|
||||
if (nozzle_item.second.IsNormal()) {
|
||||
m_comboBox_nozzle_id->Append(wxString::Format("%d", nozzle_item.first + 1), wxNullBitmap, new int{0x10 | nozzle_item.first});
|
||||
}
|
||||
}
|
||||
m_comboBox_nozzle_id->SetSelection(-1);
|
||||
flex_sizer->Add(nozzle_id_title);
|
||||
flex_sizer->Add(m_comboBox_nozzle_id);
|
||||
}
|
||||
|
||||
if (support_nozzle_volume(curr_obj)) {
|
||||
Label *nozzle_name_title = new Label(top_panel, _L("Nozzle"));
|
||||
m_comboBox_nozzle_type = new ::ComboBox(top_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, NEW_HISTORY_DIALOG_INPUT_SIZE, 0, nullptr, wxCB_READONLY);
|
||||
@@ -839,6 +926,35 @@ int NewCalibrationHistoryDialog::get_extruder_id(int extruder_index)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NewCalibrationHistoryDialog::get_nozzle_combo_id_code() const
|
||||
{
|
||||
if (!m_comboBox_nozzle_id)
|
||||
return -1;
|
||||
|
||||
auto sel = m_comboBox_nozzle_id->GetSelection();
|
||||
if (sel != wxNOT_FOUND && m_comboBox_nozzle_id->GetClientData(sel))
|
||||
return *(reinterpret_cast<int*>(m_comboBox_nozzle_id->GetClientData(sel)));
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void NewCalibrationHistoryDialog::on_select_nozzle_pos(wxCommandEvent &event)
|
||||
{
|
||||
// Mirror the picked hotend's flow onto the (Orca index-based) nozzle-type combo.
|
||||
if (!curr_obj || !m_comboBox_nozzle_id || !m_comboBox_nozzle_type || !curr_obj->GetNozzleSystem())
|
||||
return;
|
||||
|
||||
int pos = get_nozzle_combo_id_code();
|
||||
if (pos < 0)
|
||||
return;
|
||||
|
||||
DevNozzle nozzle = curr_obj->GetNozzleSystem()->GetNozzleByPosId(pos);
|
||||
if (nozzle.IsNormal()) {
|
||||
NozzleVolumeType volume_type = DevNozzle::ToNozzleVolumeType(nozzle.GetNozzleFlowType());
|
||||
m_comboBox_nozzle_type->SetSelection(static_cast<int>(volume_type));
|
||||
}
|
||||
}
|
||||
|
||||
void NewCalibrationHistoryDialog::on_ok(wxCommandEvent &event)
|
||||
{
|
||||
wxString name = m_name_value->GetTextCtrl()->GetValue();
|
||||
@@ -897,6 +1013,7 @@ void NewCalibrationHistoryDialog::on_ok(wxCommandEvent &event)
|
||||
m_new_result.nozzle_diameter = nozzle_value;
|
||||
m_new_result.filament_id = filament_id;
|
||||
m_new_result.setting_id = setting_id;
|
||||
m_new_result.nozzle_pos_id = get_nozzle_combo_id_code();
|
||||
|
||||
// Check for duplicate names from history
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@ protected:
|
||||
void enbale_action_buttons(bool enable);
|
||||
float get_nozzle_value();
|
||||
int get_extruder_id();
|
||||
bool support_nozzle_id_column();
|
||||
|
||||
void on_click_new_button(wxCommandEvent &event);
|
||||
|
||||
@@ -76,10 +77,12 @@ public:
|
||||
protected:
|
||||
virtual void on_ok(wxCommandEvent &event);
|
||||
virtual void on_cancel(wxCommandEvent &event);
|
||||
void on_select_nozzle_pos(wxCommandEvent &event);
|
||||
|
||||
|
||||
wxArrayString get_all_filaments(const MachineObject *obj);
|
||||
int get_extruder_id(int extruder_index); // extruder_index 0 : left, 1 : right
|
||||
int get_nozzle_combo_id_code() const; // rack hotend position code, -1 if none
|
||||
|
||||
protected:
|
||||
PACalibResult m_new_result;
|
||||
@@ -93,6 +96,7 @@ protected:
|
||||
ComboBox *m_comboBox_filament;
|
||||
ComboBox *m_comboBox_extruder;
|
||||
ComboBox *m_comboBox_nozzle_type;
|
||||
ComboBox *m_comboBox_nozzle_id{nullptr};
|
||||
|
||||
struct FilamentInfos
|
||||
{
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
#include "DeviceCore/DevExtruderSystem.h"
|
||||
#include "DeviceCore/DevFilaBlackList.h"
|
||||
#include "DeviceCore/DevFilaSystem.h"
|
||||
#include "DeviceCore/DevFilaSwitch.h"
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevNozzleSystem.h"
|
||||
#include "DeviceCore/DevStorage.h"
|
||||
|
||||
#define CALIBRATION_LABEL_SIZE wxSize(FromDIP(150), FromDIP(24))
|
||||
@@ -1457,31 +1459,40 @@ bool CalibrationPresetPage::is_filament_in_blacklist(int tray_id, Preset* preset
|
||||
get_tray_ams_and_slot_id(curr_obj, tray_id, ams_id, slot_id, out_tray_id);
|
||||
|
||||
if (wxGetApp().app_config->get("skip_ams_blacklist_check") != "true") {
|
||||
bool in_blacklist = false;
|
||||
std::string action;
|
||||
wxString info;
|
||||
std::string filamnt_type;
|
||||
preset->get_filament_type(filamnt_type);
|
||||
DevFilaBlacklist::CheckFilamentInfo check_info;
|
||||
check_info.dev_id = curr_obj->get_dev_id();
|
||||
check_info.model_id = curr_obj->printer_type;
|
||||
check_info.fila_id = preset->filament_id;
|
||||
preset->get_filament_type(check_info.fila_type);
|
||||
check_info.ams_id = ams_id;
|
||||
check_info.slot_id = slot_id;
|
||||
check_info.has_filament_switch = curr_obj->GetFilaSwitch()->IsInstalled();
|
||||
// fila_name intentionally left empty: the engine recovers it from the selected AMS slot,
|
||||
// preserving the name-match behavior.
|
||||
|
||||
auto vendor = dynamic_cast<ConfigOptionStrings*> (preset->config.option("filament_vendor"));
|
||||
if (vendor && (vendor->values.size() > 0)) {
|
||||
std::string vendor_name = vendor->values[0];
|
||||
DevFilaBlacklist::check_filaments_in_blacklist(curr_obj->printer_type, vendor_name, filamnt_type, preset->filament_id, ams_id, slot_id, "", in_blacklist, action, info);
|
||||
check_info.fila_vendor = vendor->values[0];
|
||||
}
|
||||
|
||||
if (in_blacklist) {
|
||||
error_tips = info.ToUTF8().data();
|
||||
if (action == "prohibition") {
|
||||
return false;
|
||||
}
|
||||
else if (action == "warning") {
|
||||
return true;
|
||||
}
|
||||
const auto &result = DevFilaBlacklist::check_filaments_in_blacklist(check_info);
|
||||
|
||||
if (const auto &prohibition_items = result.get_items_by_action("prohibition"); !prohibition_items.empty()) {
|
||||
wxString combined_msg;
|
||||
for (const auto &item : prohibition_items) { combined_msg += item.info_msg + "\n"; }
|
||||
error_tips = combined_msg.ToUTF8().data();
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
error_tips = "";
|
||||
|
||||
if (const auto &warning_items = result.get_items_by_action("warning"); !warning_items.empty()) {
|
||||
wxString combined_msg;
|
||||
for (const auto &item : warning_items) { combined_msg += item.info_msg + "\n"; }
|
||||
error_tips = combined_msg.ToUTF8().data();
|
||||
return true;
|
||||
}
|
||||
|
||||
error_tips = "";
|
||||
return true;
|
||||
}
|
||||
if (devPrinterUtil::IsVirtualSlot(ams_id)) {
|
||||
if (m_cali_mode == CalibMode::Calib_PA_Line && (m_cali_method == CalibrationMethod::CALI_METHOD_AUTO || m_cali_method == CalibrationMethod::CALI_METHOD_NEW_AUTO)) {
|
||||
@@ -1663,7 +1674,9 @@ bool CalibrationPresetPage::is_nozzle_info_synced() const
|
||||
if (curr_obj->is_nozzle_flow_type_supported()) {
|
||||
if (extruder.GetNozzleFlowType() == NozzleFlowType::NONE_FLOWTYPE)
|
||||
return false;
|
||||
if (int(extruder.GetNozzleFlowType()) - 1 != int(get_nozzle_volume_type(extruder_id)))
|
||||
// Map device flow -> volume type via DevNozzle::ToNozzleVolumeType so U_FLOW resolves to
|
||||
// nvtTPUHighFlow(3); the naive flow-1 would yield nvtHybrid(2). Identical to flow-1 for S/H flow.
|
||||
if (int(DevNozzle::ToNozzleVolumeType(extruder.GetNozzleFlowType())) != int(get_nozzle_volume_type(extruder_id)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2124,7 +2137,7 @@ void CalibrationPresetPage::init_with_machine(MachineObject* obj)
|
||||
}
|
||||
|
||||
if (obj->GetExtderSystem()->GetNozzleFlowType(i) != NozzleFlowType::NONE_FLOWTYPE) {
|
||||
m_left_comboBox_nozzle_volume->SetSelection(obj->GetExtderSystem()->GetNozzleFlowType(i) - 1);
|
||||
m_left_comboBox_nozzle_volume->SetSelection(int(DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(i))));
|
||||
} else {
|
||||
m_left_comboBox_nozzle_volume->SetSelection(0);
|
||||
}
|
||||
@@ -2144,7 +2157,7 @@ void CalibrationPresetPage::init_with_machine(MachineObject* obj)
|
||||
}
|
||||
|
||||
if (obj->GetExtderSystem()->GetNozzleFlowType(i) != NozzleFlowType::NONE_FLOWTYPE) {
|
||||
m_right_comboBox_nozzle_volume->SetSelection(obj->GetExtderSystem()->GetNozzleFlowType(i) - 1);
|
||||
m_right_comboBox_nozzle_volume->SetSelection(int(DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(i))));
|
||||
} else {
|
||||
m_right_comboBox_nozzle_volume->SetSelection(0);
|
||||
}
|
||||
@@ -2182,7 +2195,7 @@ void CalibrationPresetPage::init_with_machine(MachineObject* obj)
|
||||
else {
|
||||
if ((obj->GetExtderSystem()->GetTotalExtderCount() > 0) && (obj->GetExtderSystem()->GetNozzleFlowType(0) != NozzleFlowType::NONE_FLOWTYPE))
|
||||
{
|
||||
m_comboBox_nozzle_volume->SetSelection(obj->GetExtderSystem()->GetNozzleFlowType(0) - 1);
|
||||
m_comboBox_nozzle_volume->SetSelection(int(DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(0))));
|
||||
} else {
|
||||
m_comboBox_nozzle_volume->SetSelection(0);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#include "I18N.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "DeviceCore/DevConfigUtil.h"
|
||||
#include "DeviceCore/DevNozzleSystem.h"
|
||||
#include "DeviceCore/DevNozzleRack.h"
|
||||
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
@@ -95,35 +98,43 @@ enum class GridTextInputType {
|
||||
class GridTextInput : public TextInput
|
||||
{
|
||||
public:
|
||||
GridTextInput(wxWindow* parent, wxString text, wxString label, wxSize size, int col_idx, GridTextInputType type);
|
||||
GridTextInput(wxWindow* parent, wxString text, wxString label, wxSize size, int col_idx, GridTextInputType type, int extruder_id = MAIN_EXTRUDER_ID);
|
||||
int get_col_idx() { return m_col_idx; }
|
||||
void set_col_idx(int idx) { m_col_idx = idx; }
|
||||
int get_extruder_id() { return m_extruder_id; }
|
||||
void set_extruder_id(int id) { m_extruder_id = id; }
|
||||
GridTextInputType get_type() { return m_type; }
|
||||
void set_type(GridTextInputType type) { m_type = type; }
|
||||
private:
|
||||
int m_col_idx;
|
||||
int m_extruder_id{MAIN_EXTRUDER_ID};
|
||||
GridTextInputType m_type;
|
||||
};
|
||||
|
||||
GridTextInput::GridTextInput(wxWindow* parent, wxString text, wxString label, wxSize size, int col_idx, GridTextInputType type)
|
||||
GridTextInput::GridTextInput(wxWindow* parent, wxString text, wxString label, wxSize size, int col_idx, GridTextInputType type, int extruder_id)
|
||||
: TextInput(parent, text, label, "", wxDefaultPosition, size, wxTE_PROCESS_ENTER)
|
||||
, m_col_idx(col_idx)
|
||||
, m_extruder_id(extruder_id)
|
||||
, m_type(type)
|
||||
{
|
||||
}
|
||||
|
||||
class GridComboBox : public ComboBox {
|
||||
public:
|
||||
GridComboBox(wxWindow* parent, wxSize size, int col_idx);
|
||||
GridComboBox(wxWindow* parent, wxSize size, int col_idx, int extruder_id = MAIN_EXTRUDER_ID);
|
||||
int get_col_idx() { return m_col_idx; }
|
||||
void set_col_idx(int idx) { m_col_idx = idx; }
|
||||
int get_extruder_id() { return m_extruder_id; }
|
||||
void set_extruder_id(int id) { m_extruder_id = id; }
|
||||
private:
|
||||
int m_col_idx;
|
||||
int m_extruder_id{MAIN_EXTRUDER_ID};
|
||||
};
|
||||
|
||||
GridComboBox::GridComboBox(wxWindow* parent, wxSize size, int col_idx)
|
||||
GridComboBox::GridComboBox(wxWindow* parent, wxSize size, int col_idx, int extruder_id)
|
||||
: ComboBox(parent, wxID_ANY, "", wxDefaultPosition, size, 0, nullptr)
|
||||
, m_col_idx(col_idx)
|
||||
, m_extruder_id(extruder_id)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -227,7 +238,7 @@ void CaliPASaveAutoPanel::sync_cali_result(const std::vector<PACalibResult>& cal
|
||||
m_calib_results.clear();
|
||||
for (auto& item : cali_result) {
|
||||
if (item.confidence == 0)
|
||||
m_calib_results[item.tray_id] = item;
|
||||
m_calib_results.push_back(item);
|
||||
}
|
||||
m_grid_panel->DestroyChildren();
|
||||
auto grid_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
@@ -281,14 +292,14 @@ void CaliPASaveAutoPanel::sync_cali_result(const std::vector<PACalibResult>& cal
|
||||
wxString tray_name = get_tray_name_by_tray_id(item.tray_id);
|
||||
tray_title->SetLabel(tray_name);
|
||||
|
||||
auto k_value = new GridTextInput(m_grid_panel, "", "", CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, GridTextInputType::K);
|
||||
auto n_value = new GridTextInput(m_grid_panel, "", "", CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, GridTextInputType::N);
|
||||
auto k_value = new GridTextInput(m_grid_panel, "", "", CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, GridTextInputType::K, item.extruder_id);
|
||||
auto n_value = new GridTextInput(m_grid_panel, "", "", CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, GridTextInputType::N, item.extruder_id);
|
||||
k_value->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
|
||||
n_value->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
|
||||
auto k_value_failed = new Label(m_grid_panel, _L("Failed"));
|
||||
auto n_value_failed = new Label(m_grid_panel, _L("Failed"));
|
||||
|
||||
auto comboBox_tray_name = new GridComboBox(m_grid_panel, CALIBRATION_SAVE_INPUT_SIZE, item.tray_id);
|
||||
auto comboBox_tray_name = new GridComboBox(m_grid_panel, CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, item.extruder_id);
|
||||
auto tray_name_failed = new Label(m_grid_panel, " - ");
|
||||
wxArrayString selections;
|
||||
static std::vector<PACalibResult> filtered_results;
|
||||
@@ -395,17 +406,26 @@ void CaliPASaveAutoPanel::save_to_result_from_widgets(wxWindow* window, bool* ou
|
||||
return;
|
||||
|
||||
//operate
|
||||
// The grid can hold two rows with the same tray_id under different extruders
|
||||
// (rack / dual-address configs), so results must be keyed by (tray_id, extruder_id).
|
||||
auto find_result = [this](int tray_id, int extruder_id) -> PACalibResult* {
|
||||
auto it = std::find_if(m_calib_results.begin(), m_calib_results.end(), [tray_id, extruder_id](const PACalibResult& r) {
|
||||
return r.tray_id == tray_id && r.extruder_id == extruder_id;
|
||||
});
|
||||
return it != m_calib_results.end() ? &(*it) : nullptr;
|
||||
};
|
||||
auto input = dynamic_cast<GridTextInput*>(window);
|
||||
if (input && input->IsShown()) {
|
||||
int tray_id = input->get_col_idx();
|
||||
int tray_id = input->get_col_idx();
|
||||
int extruder_id = input->get_extruder_id();
|
||||
if (input->get_type() == GridTextInputType::K) {
|
||||
float k = 0.0f;
|
||||
if (!CalibUtils::validate_input_k_value(input->GetTextCtrl()->GetValue(), &k)) {
|
||||
*out_msg = wxString::Format(_L("Please input a valid value (K in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE);
|
||||
*out_is_valid = false;
|
||||
}
|
||||
else
|
||||
m_calib_results[tray_id].k_value = k;
|
||||
else if (auto* result = find_result(tray_id, extruder_id))
|
||||
result->k_value = k;
|
||||
}
|
||||
else if (input->get_type() == GridTextInputType::N) {
|
||||
}
|
||||
@@ -413,7 +433,8 @@ void CaliPASaveAutoPanel::save_to_result_from_widgets(wxWindow* window, bool* ou
|
||||
|
||||
auto comboBox = dynamic_cast<GridComboBox*>(window);
|
||||
if (comboBox && comboBox->IsShown()) {
|
||||
int tray_id = comboBox->get_col_idx();
|
||||
int tray_id = comboBox->get_col_idx();
|
||||
int extruder_id = comboBox->get_extruder_id();
|
||||
wxString name = comboBox->GetTextCtrl()->GetValue().ToStdString();
|
||||
if (name.IsEmpty()) {
|
||||
*out_msg = _L("Please enter the name you want to save to printer.");
|
||||
@@ -423,7 +444,8 @@ void CaliPASaveAutoPanel::save_to_result_from_widgets(wxWindow* window, bool* ou
|
||||
*out_msg = _L("The name cannot exceed 40 characters.");
|
||||
*out_is_valid = false;
|
||||
}
|
||||
m_calib_results[tray_id].name = into_u8(name);
|
||||
if (auto* result = find_result(tray_id, extruder_id))
|
||||
result->name = into_u8(name);
|
||||
}
|
||||
|
||||
auto childern = window->GetChildren();
|
||||
@@ -441,52 +463,7 @@ bool CaliPASaveAutoPanel::get_result(std::vector<PACalibResult>& out_result) {
|
||||
else
|
||||
save_to_result_from_widgets(m_grid_panel, &is_valid, &err_msg);
|
||||
if (is_valid) {
|
||||
/*
|
||||
std::vector<PACalibResult> to_save_result;
|
||||
for (auto &result : m_calib_results) {
|
||||
auto iter = std::find_if(to_save_result.begin(), to_save_result.end(), [this, &result](const PACalibResult &item) {
|
||||
bool has_same_name = (item.name == result.second.name && item.filament_id == result.second.filament_id);
|
||||
if (m_obj && m_obj->is_multi_extruders()) {
|
||||
has_same_name &= (item.extruder_id == result.second.extruder_id && item.nozzle_volume_type == result.second.nozzle_volume_type);
|
||||
}
|
||||
return has_same_name;
|
||||
});
|
||||
|
||||
if (iter != to_save_result.end()) {
|
||||
MessageDialog msg_dlg(nullptr, _L("Only one of the results with the same name will be saved. Are you sure you want to overwrite the other results?"),
|
||||
wxEmptyString, wxICON_WARNING | wxYES_NO);
|
||||
if (msg_dlg.ShowModal() != wxID_YES) {
|
||||
return false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &result : m_history_results) {
|
||||
auto iter = std::find_if(m_history_results.begin(), m_history_results.end(), [this, &result](const PACalibResult &item) {
|
||||
bool has_same_name = (item.name == result.name && item.filament_id == result.filament_id);
|
||||
if (m_obj && m_obj->is_multi_extruders()) {
|
||||
has_same_name &= (item.extruder_id == result.extruder_id && item.nozzle_volume_type == result.nozzle_volume_type);
|
||||
}
|
||||
return has_same_name;
|
||||
});
|
||||
|
||||
if (iter != m_history_results.end()) {
|
||||
MessageDialog msg_dlg(nullptr,
|
||||
wxString::Format(_L("There is already a historical calibration result with the same name: %s. Are you sure you want to override the historical result?"),
|
||||
result.name),
|
||||
wxEmptyString, wxICON_WARNING | wxYES_NO);
|
||||
if (msg_dlg.ShowModal() != wxID_YES) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
for (auto& result : m_calib_results) {
|
||||
out_result.push_back(result.second);
|
||||
}
|
||||
out_result = m_calib_results;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
@@ -517,11 +494,12 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
|
||||
m_calib_results.clear();
|
||||
for (auto &item : cali_result) {
|
||||
if (item.confidence == 0) {
|
||||
int tray_id = 4 * item.ams_id + item.slot_id;
|
||||
PACalibResult result = item;
|
||||
result.tray_id = 4 * item.ams_id + item.slot_id;
|
||||
if (item.ams_id == VIRTUAL_TRAY_MAIN_ID || item.ams_id == VIRTUAL_TRAY_DEPUTY_ID) {
|
||||
tray_id = item.ams_id;
|
||||
result.tray_id = item.ams_id;
|
||||
}
|
||||
m_calib_results[tray_id] = item;
|
||||
m_calib_results.push_back(result);
|
||||
}
|
||||
}
|
||||
m_multi_extruder_grid_panel->DestroyChildren();
|
||||
@@ -532,14 +510,21 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
|
||||
m_multi_extruder_grid_panel->SetSizer(grid_sizer, true);
|
||||
m_multi_extruder_grid_panel->Bind(wxEVT_LEFT_DOWN, [this](auto &e) { SetFocusIgnoringChildren(); });
|
||||
|
||||
wxStaticBoxSizer *left_sizer = new wxStaticBoxSizer(wxVERTICAL, m_multi_extruder_grid_panel, _L("Left extruder"));
|
||||
wxStaticBoxSizer *right_sizer = new wxStaticBoxSizer(wxVERTICAL, m_multi_extruder_grid_panel, _L("Right extruder"));
|
||||
const std::string& cwsp_pt = m_obj->printer_type;
|
||||
wxStaticBoxSizer *left_sizer = new wxStaticBoxSizer(wxVERTICAL, m_multi_extruder_grid_panel, _L(DevPrinterConfigUtil::get_toolhead_display_name(cwsp_pt, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::SentenceCase)));
|
||||
wxStaticBoxSizer *right_sizer = new wxStaticBoxSizer(wxVERTICAL, m_multi_extruder_grid_panel, _L(DevPrinterConfigUtil::get_toolhead_display_name(cwsp_pt, MAIN_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::SentenceCase)));
|
||||
grid_sizer->Add(left_sizer);
|
||||
grid_sizer->AddSpacer(COLUMN_GAP);
|
||||
grid_sizer->Add(right_sizer);
|
||||
|
||||
wxFlexGridSizer *left_grid_sizer = new wxFlexGridSizer(3, COLUMN_GAP, ROW_GAP);
|
||||
wxFlexGridSizer *right_grid_sizer = new wxFlexGridSizer(3, COLUMN_GAP, ROW_GAP);
|
||||
|
||||
// The main extruder can carry a nozzle rack (H2C); non-rack printers keep the 3-column layout.
|
||||
auto ns = m_obj->GetNozzleSystem();
|
||||
auto rack = ns ? ns->GetNozzleRack() : nullptr;
|
||||
bool has_rack = rack && rack->IsSupported();
|
||||
|
||||
wxFlexGridSizer *right_grid_sizer = new wxFlexGridSizer(has_rack ? 4 : 3, COLUMN_GAP, ROW_GAP);
|
||||
left_sizer->Add(left_grid_sizer);
|
||||
right_sizer->Add(right_grid_sizer);
|
||||
|
||||
@@ -567,29 +552,46 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
|
||||
auto k_title = new Label(m_multi_extruder_grid_panel, _L("Factor K"), 0, CALIBRATION_SAVE_NUMBER_INPUT_SIZE);
|
||||
k_title->SetFont(Label::Head_14);
|
||||
right_grid_sizer->Add(k_title, 1, wxALIGN_CENTER);
|
||||
|
||||
if (has_rack) {
|
||||
auto nozzle_title = new Label(m_multi_extruder_grid_panel, _L("Nozzle ID"), 0, CALIBRATION_SAVE_NUMBER_INPUT_SIZE);
|
||||
nozzle_title->SetFont(Label::Head_14);
|
||||
right_grid_sizer->Add(nozzle_title, 1, wxALIGN_CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, std::string>> preset_names;
|
||||
// key: (extruder_id, tray_id)
|
||||
std::vector<std::pair<std::pair<int, int>, std::string>> preset_names;
|
||||
int i = 1;
|
||||
std::unordered_set<std::string> set;
|
||||
for (auto &info : m_obj->selected_cali_preset) {
|
||||
std::string default_name;
|
||||
// extruder _id
|
||||
// Derive extruder_id from the tray so the extruder/tray key stays consistent
|
||||
// with the calibration result's extruder_id.
|
||||
int extruder_id = 0;
|
||||
if (info.tray_id == VIRTUAL_TRAY_MAIN_ID) {
|
||||
extruder_id = 0;
|
||||
} else if (info.tray_id == VIRTUAL_TRAY_DEPUTY_ID) {
|
||||
extruder_id = 1;
|
||||
} else {
|
||||
int ams_id = info.tray_id / 4;
|
||||
extruder_id = m_obj->get_extruder_id_by_ams_id(std::to_string(ams_id));
|
||||
}
|
||||
{
|
||||
int extruder_id = 0;
|
||||
if (info.tray_id == VIRTUAL_TRAY_MAIN_ID) {
|
||||
extruder_id = 0;
|
||||
} else if (info.tray_id == VIRTUAL_TRAY_DEPUTY_ID) {
|
||||
extruder_id = 1;
|
||||
} else {
|
||||
int ams_id = info.tray_id / 4;
|
||||
extruder_id = m_obj->get_extruder_id_by_ams_id(std::to_string(ams_id));
|
||||
}
|
||||
|
||||
if (extruder_id == 0) {
|
||||
default_name += L("Right Nozzle");
|
||||
} else if (extruder_id == 1){
|
||||
default_name += L("Left Nozzle");
|
||||
if (extruder_id == MAIN_EXTRUDER_ID) {
|
||||
default_name += DevPrinterConfigUtil::get_toolhead_display_name(cwsp_pt, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase);
|
||||
if (has_rack) {
|
||||
default_name += "_";
|
||||
if (info.nozzle_pos_id == 0) {
|
||||
default_name += "R";
|
||||
} else if (info.nozzle_pos_id >= 0x10) {
|
||||
default_name += std::to_string((info.nozzle_pos_id & 0x0f) + 1);
|
||||
} else {
|
||||
default_name += "N/A";
|
||||
}
|
||||
}
|
||||
} else if (extruder_id == DEPUTY_EXTRUDER_ID) {
|
||||
default_name += DevPrinterConfigUtil::get_toolhead_display_name(cwsp_pt, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,7 +616,7 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
|
||||
}
|
||||
}
|
||||
|
||||
preset_names.push_back({info.tray_id, default_name});
|
||||
preset_names.push_back({{extruder_id, info.tray_id}, default_name});
|
||||
}
|
||||
|
||||
bool left_first_add_item = true;
|
||||
@@ -648,14 +650,19 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
|
||||
tray_title->SetBitmap(*get_extruder_color_icon(full_filament_ams_list[item.tray_id].opt_string("filament_colour", 0u), tray_name.ToStdString(), FromDIP(20), FromDIP(20)));
|
||||
tray_title->SetToolTip("");
|
||||
|
||||
auto k_value = new GridTextInput(m_multi_extruder_grid_panel, "", "", CALIBRATION_SAVE_NUMBER_INPUT_SIZE, item.tray_id, GridTextInputType::K);
|
||||
auto n_value = new GridTextInput(m_multi_extruder_grid_panel, "", "", CALIBRATION_SAVE_NUMBER_INPUT_SIZE, item.tray_id, GridTextInputType::N);
|
||||
auto k_value = new GridTextInput(m_multi_extruder_grid_panel, "", "", CALIBRATION_SAVE_NUMBER_INPUT_SIZE, item.tray_id, GridTextInputType::K, item.extruder_id);
|
||||
auto n_value = new GridTextInput(m_multi_extruder_grid_panel, "", "", CALIBRATION_SAVE_NUMBER_INPUT_SIZE, item.tray_id, GridTextInputType::N, item.extruder_id);
|
||||
k_value->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
|
||||
n_value->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
|
||||
auto k_value_failed = new Label(m_multi_extruder_grid_panel, _L("Failed"));
|
||||
auto n_value_failed = new Label(m_multi_extruder_grid_panel, _L("Failed"));
|
||||
|
||||
auto comboBox_tray_name = new GridComboBox(m_multi_extruder_grid_panel, CALIBRATION_SAVE_INPUT_SIZE, item.tray_id);
|
||||
auto nozzle_id_value = new Label(m_multi_extruder_grid_panel, wxEmptyString);
|
||||
nozzle_id_value->SetMinSize(wxSize(FromDIP(180), FromDIP(24)));
|
||||
nozzle_id_value->Wrap(-1);
|
||||
auto nozzle_id_failed = new Label(m_multi_extruder_grid_panel, _L("Failed"));
|
||||
|
||||
auto comboBox_tray_name = new GridComboBox(m_multi_extruder_grid_panel, CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, item.extruder_id);
|
||||
auto tray_name_failed = new Label(m_multi_extruder_grid_panel, " - ");
|
||||
wxArrayString selections;
|
||||
static std::vector<PACalibResult> filtered_results;
|
||||
@@ -671,22 +678,26 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
|
||||
}
|
||||
comboBox_tray_name->Set(selections);
|
||||
|
||||
auto set_edit_mode = [this, k_value, n_value, k_value_failed, n_value_failed, comboBox_tray_name, tray_name_failed](std::string str) {
|
||||
auto set_edit_mode = [this, k_value, n_value, k_value_failed, n_value_failed, nozzle_id_value, nozzle_id_failed, comboBox_tray_name, tray_name_failed](std::string str, bool display_nozzle) {
|
||||
if (str == "normal") {
|
||||
comboBox_tray_name->Show();
|
||||
tray_name_failed->Show(false);
|
||||
k_value->Show();
|
||||
n_value->Show();
|
||||
nozzle_id_value->Show(display_nozzle);
|
||||
k_value_failed->Show(false);
|
||||
n_value_failed->Show(false);
|
||||
nozzle_id_failed->Show(false);
|
||||
}
|
||||
if (str == "failed") {
|
||||
comboBox_tray_name->Show(false);
|
||||
tray_name_failed->Show();
|
||||
k_value->Show(false);
|
||||
n_value->Show(false);
|
||||
nozzle_id_value->Show(false);
|
||||
k_value_failed->Show();
|
||||
n_value_failed->Show();
|
||||
nozzle_id_failed->Show(display_nozzle);
|
||||
}
|
||||
|
||||
// hide n value
|
||||
@@ -698,15 +709,39 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
|
||||
};
|
||||
|
||||
if (!result_failed) {
|
||||
set_edit_mode("normal");
|
||||
set_edit_mode("normal", has_rack && item.extruder_id == MAIN_EXTRUDER_ID);
|
||||
|
||||
auto k_str = wxString::Format("%.3f", item.k_value);
|
||||
auto n_str = wxString::Format("%.3f", item.n_coef);
|
||||
k_value->GetTextCtrl()->SetValue(k_str);
|
||||
n_value->GetTextCtrl()->SetValue(n_str);
|
||||
|
||||
if (has_rack && item.extruder_id == MAIN_EXTRUDER_ID) {
|
||||
wxString nozzle_id_str;
|
||||
if (item.nozzle_pos_id == 0) {
|
||||
nozzle_id_str += "R | ";
|
||||
} else if (item.nozzle_pos_id >= 0x10) {
|
||||
nozzle_id_str += wxString::Format("%d | ", (item.nozzle_pos_id & 0x0f) + 1);
|
||||
} else {
|
||||
nozzle_id_str += "N/A | ";
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "Nozzle position id is -1 or invalid.";
|
||||
}
|
||||
nozzle_id_str += wxString::Format("%.1f mm ", item.nozzle_diameter);
|
||||
switch (item.nozzle_volume_type) {
|
||||
case NozzleVolumeType::nvtStandard: nozzle_id_str += _L("Standard Flow"); break;
|
||||
case NozzleVolumeType::nvtHighFlow: nozzle_id_str += _L("High Flow"); break;
|
||||
case NozzleVolumeType::nvtTPUHighFlow: nozzle_id_str += _L("TPU High Flow"); break;
|
||||
default: break;
|
||||
}
|
||||
nozzle_id_value->SetLabel(nozzle_id_str);
|
||||
}
|
||||
|
||||
for (auto &name : preset_names) {
|
||||
if (item.tray_id == name.first) { comboBox_tray_name->SetValue(from_u8(name.second)); }
|
||||
int extruder_id = name.first.first;
|
||||
int tray_id = name.first.second;
|
||||
if (item.tray_id == tray_id && item.extruder_id == extruder_id) {
|
||||
comboBox_tray_name->SetValue(from_u8(name.second));
|
||||
}
|
||||
}
|
||||
|
||||
comboBox_tray_name->Bind(wxEVT_COMBOBOX, [this, comboBox_tray_name, k_value, n_value](auto &e) {
|
||||
@@ -714,7 +749,7 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
|
||||
auto history = filtered_results[selection];
|
||||
});
|
||||
} else {
|
||||
set_edit_mode("failed");
|
||||
set_edit_mode("failed", has_rack && item.extruder_id == MAIN_EXTRUDER_ID);
|
||||
}
|
||||
|
||||
if ((m_obj->is_main_extruder_on_left() && item.extruder_id == 0)
|
||||
@@ -762,6 +797,14 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
|
||||
} else {
|
||||
right_grid_sizer->Add(k_value_failed, 1, wxEXPAND);
|
||||
}
|
||||
|
||||
if (has_rack) {
|
||||
if (nozzle_id_value->IsShown()) {
|
||||
right_grid_sizer->Add(nozzle_id_value, 1, wxEXPAND);
|
||||
} else {
|
||||
right_grid_sizer->Add(nozzle_id_failed, 1, wxEXPAND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,10 @@ protected:
|
||||
wxPanel* m_part_failed_panel;
|
||||
wxPanel* m_grid_panel{ nullptr };
|
||||
wxPanel* m_multi_extruder_grid_panel{ nullptr };
|
||||
std::map<int, PACalibResult> m_calib_results;// map<tray_id, PACalibResult>
|
||||
// keyed by (tray_id, extruder_id): the same tray_id can appear under two
|
||||
// extruders (rack / dual-address configs), so a tray_id-only map would
|
||||
// silently overwrite one extruder's K/N.
|
||||
std::vector<PACalibResult> m_calib_results;
|
||||
std::vector<PACalibResult> m_history_results;
|
||||
bool m_is_all_failed{ true };
|
||||
MachineObject* m_obj{ nullptr };
|
||||
|
||||
@@ -28,8 +28,8 @@ list(APPEND SLIC3R_GUI_SOURCES
|
||||
GUI/DeviceCore/DevFilaSystem.h
|
||||
GUI/DeviceCore/DevFilaSystem.cpp
|
||||
GUI/DeviceCore/DevFilaSystemCtrl.cpp
|
||||
GUI/DeviceCore/DevUtilBackend.h
|
||||
GUI/DeviceCore/DevUtilBackend.cpp
|
||||
GUI/DeviceCore/DevFilaSwitch.h
|
||||
GUI/DeviceCore/DevFilaSwitch.cpp
|
||||
GUI/DeviceCore/DevFirmware.h
|
||||
GUI/DeviceCore/DevFirmware.cpp
|
||||
GUI/DeviceCore/DevPrintOptions.h
|
||||
@@ -49,10 +49,17 @@ list(APPEND SLIC3R_GUI_SOURCES
|
||||
GUI/DeviceCore/DevManager.cpp
|
||||
GUI/DeviceCore/DevMapping.h
|
||||
GUI/DeviceCore/DevMapping.cpp
|
||||
GUI/DeviceCore/DevMappingNozzle.h
|
||||
GUI/DeviceCore/DevMappingNozzle.cpp
|
||||
GUI/DeviceCore/DevNozzleSystem.h
|
||||
GUI/DeviceCore/DevNozzleSystem.cpp
|
||||
GUI/DeviceCore/DevNozzleRack.h
|
||||
GUI/DeviceCore/DevNozzleRack.cpp
|
||||
GUI/DeviceCore/DevNozzleRackCtrl.cpp
|
||||
GUI/DeviceCore/DevUtil.h
|
||||
GUI/DeviceCore/DevUtil.cpp
|
||||
GUI/DeviceCore/DevUtilBackend.h
|
||||
GUI/DeviceCore/DevUtilBackend.cpp
|
||||
|
||||
)
|
||||
set(SLIC3R_GUI_SOURCES ${SLIC3R_GUI_SOURCES} PARENT_SCOPE)
|
||||
@@ -87,6 +87,28 @@ std::string DevPrinterConfigUtil::get_printer_ext_img(const std::string& type_st
|
||||
return (vec.size() > pos) ? vec[pos] : std::string();
|
||||
};
|
||||
|
||||
std::string DevPrinterConfigUtil::get_filament_load_img(const std::string &type_str, int ext_id, bool has_nozzle_rack)
|
||||
{
|
||||
if (has_nozzle_rack)
|
||||
{
|
||||
const auto &rack_vec = get_value_from_config<std::vector<std::string>>(type_str, "filament_load_image_nozzle_rack");
|
||||
if (!rack_vec.empty())
|
||||
{
|
||||
if (ext_id >= 0 && static_cast<size_t>(ext_id) < rack_vec.size())
|
||||
{
|
||||
return rack_vec[ext_id];
|
||||
}
|
||||
return rack_vec[0];
|
||||
}
|
||||
}
|
||||
const auto &vec = get_value_from_config<std::vector<std::string>>(type_str, "filament_load_image");
|
||||
if (ext_id >= 0 && static_cast<size_t>(ext_id) < vec.size())
|
||||
{
|
||||
return vec[ext_id];
|
||||
}
|
||||
return vec.empty() ? std::string() : vec[0];
|
||||
}
|
||||
|
||||
std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, const std::string& key)
|
||||
{
|
||||
std::vector<std::string> filaments;
|
||||
|
||||
@@ -74,6 +74,7 @@ public:
|
||||
static std::string get_printer_use_ams_type(std::string type_str) { return get_value_from_config<std::string>(type_str, "use_ams_type"); }
|
||||
static std::string get_printer_ams_img(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "printer_use_ams_image"); }
|
||||
static std::string get_printer_ext_img(const std::string& type_str, int pos);//printer_ext_image
|
||||
static std::string get_filament_load_img(const std::string &type_str, int ext_id, bool has_nozzle_rack = false);
|
||||
|
||||
/*fan*/
|
||||
static std::string get_fan_text(const std::string& type_str, const std::string& key);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
enum PrinterArch
|
||||
{
|
||||
@@ -47,6 +48,32 @@ enum DevAmsType : int
|
||||
AMS_LITE = 2, // AMS-Lite
|
||||
N3F = 3, // N3F, AMS 2PRO
|
||||
N3S = 4, // N3S, AMS HT
|
||||
AMS_LITE_MIXED = 5, // AMS-Lite for N9
|
||||
};
|
||||
|
||||
// Device-numbered filament-change step codes. Newer firmware reports the exact change-step
|
||||
// sequence through the AMS (ams.cfs), keyed by these codes, instead of the client hardcoding
|
||||
// steps per model. Distinct from the legacy GUI-side Slic3r::GUI::FilamentStep enum.
|
||||
// STEP_CHECK_POSITION and STEP_CONFIRM_EXTRUDED share code 0x08 by device design.
|
||||
enum DevFilamentStep
|
||||
{
|
||||
STEP_IDLE = 0x00,
|
||||
STEP_PAUSE = 0x01,
|
||||
STEP_HEAT_NOZZLE = 0x02,
|
||||
STEP_CUT_FILAMENT = 0x03,
|
||||
STEP_PULL_CURR_FILAMENT = 0x04,
|
||||
STEP_PUSH_NEW_FILAMENT = 0x05,
|
||||
STEP_GRAB_NEW_FILAMENT = 0x06,
|
||||
STEP_PURGE_OLD_FILAMENT = 0x07,
|
||||
STEP_CHECK_POSITION = 0x08,
|
||||
STEP_SWITCH_EXTRUDER = 0x09,
|
||||
STEP_SWITCH_HOTEND = 0x0A,
|
||||
STEP_AMS_FILA_COOLING = 0x0B,
|
||||
STEP_PUSH_SWITCHER_FILA = 0x0C,
|
||||
STEP_PULL_SWITCHER_FILA = 0x0D,
|
||||
STEP_SWITCHER_SWITCH = 0x0E,
|
||||
STEP_CONFIRM_EXTRUDED = 0x08,
|
||||
STEP_COUNT,
|
||||
};
|
||||
|
||||
// Slots and Tray
|
||||
@@ -56,21 +83,44 @@ enum DevAmsType : int
|
||||
#define VIRTUAL_AMS_MAIN_ID_STR "255"
|
||||
#define VIRTUAL_AMS_DEPUTY_ID_STR "254"
|
||||
|
||||
// Tray index offset for the AMS-Lite-mixed unit (A2L / N9). Its 4 trays occupy
|
||||
// global tray indices 24..27.
|
||||
#define AMS_LITE_MIXED_TRAY_INDEX_OFFSET 24
|
||||
|
||||
#define INVALID_AMS_TEMPERATURE std::numeric_limits<float>::min()
|
||||
|
||||
// (ams_id, slot_id) pair.
|
||||
using DevAmsSlotId = std::pair<int, int>;
|
||||
|
||||
/* Extruder*/
|
||||
#define MAIN_EXTRUDER_ID 0
|
||||
#define DEPUTY_EXTRUDER_ID 1
|
||||
#define UNIQUE_EXTRUDER_ID MAIN_EXTRUDER_ID
|
||||
#define INVALID_EXTRUDER_ID -1
|
||||
|
||||
/* Logical extruder ids (multi-nozzle). */
|
||||
#define LOGIC_UNIQUE_EXTRUDER_ID 0
|
||||
#define LOGIC_L_EXTRUDER_ID 0
|
||||
#define LOGIC_R_EXTRUDER_ID 1
|
||||
|
||||
|
||||
/* Nozzle*/
|
||||
enum NozzleFlowType
|
||||
{
|
||||
NONE_FLOWTYPE,
|
||||
S_FLOW,
|
||||
H_FLOW
|
||||
H_FLOW,
|
||||
U_FLOW, // TPU 1.75 High Flow (device-reported; maps to nvtTPUHighFlow)
|
||||
};
|
||||
|
||||
// Discrete nozzle diameters reported by the device.
|
||||
enum NozzleDiameterType : int
|
||||
{
|
||||
NONE_DIAMETER_TYPE,
|
||||
NOZZLE_DIAMETER_0_2,
|
||||
NOZZLE_DIAMETER_0_4,
|
||||
NOZZLE_DIAMETER_0_6,
|
||||
NOZZLE_DIAMETER_0_8
|
||||
};
|
||||
|
||||
/*Print speed*/
|
||||
@@ -105,4 +155,39 @@ public:
|
||||
static bool IsVirtualSlot(const std::string& ams_id) { return (ams_id == VIRTUAL_AMS_MAIN_ID_STR || ams_id == VIRTUAL_AMS_DEPUTY_ID_STR); }
|
||||
};
|
||||
|
||||
};// namespace Slic3r
|
||||
namespace GUI
|
||||
{
|
||||
// Print source. Defined here (rather than in SelectMachine.hpp) so the shared device-mapping
|
||||
// GUI headers — AmsMappingPopup, wgtDeviceNozzleSelect — can name it without pulling in
|
||||
// SelectMachine.hpp, which would create an include cycle.
|
||||
enum PrintFromType
|
||||
{
|
||||
FROM_NORMAL,
|
||||
FROM_SDCARD_VIEW,
|
||||
};
|
||||
}
|
||||
|
||||
};// namespace Slic3r
|
||||
|
||||
// A nozzle requirement of the sliced plate (or an installed nozzle's identity), compared in the
|
||||
// pre-print slicing-vs-installed nozzle checks.
|
||||
struct NozzleDef
|
||||
{
|
||||
float nozzle_diameter;
|
||||
Slic3r::NozzleFlowType nozzle_flow_type;
|
||||
|
||||
bool operator==(const NozzleDef& other) const
|
||||
{
|
||||
return nozzle_diameter == other.nozzle_diameter && nozzle_flow_type == other.nozzle_flow_type;
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct std::hash<NozzleDef>
|
||||
{
|
||||
std::size_t operator()(const NozzleDef& v) const noexcept
|
||||
{
|
||||
size_t h1 = std::hash<int>{}(v.nozzle_diameter * 1000);
|
||||
size_t h2 = std::hash<int>{}(v.nozzle_flow_type);
|
||||
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "DevExtruderSystem.h"
|
||||
#include "DevNozzleSystem.h"
|
||||
#include "DevFilaSystem.h" // complete type for GetFilaSystem()->GetTrayIndexMap() in GetBackupAmsSlotInGroup
|
||||
|
||||
// TODO: remove this include
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
@@ -48,17 +49,43 @@ namespace Slic3r
|
||||
|
||||
NozzleType DevExtder::GetNozzleType() const
|
||||
{
|
||||
return system->Owner()->GetNozzleSystem()->GetNozzle(m_current_nozzle_id).m_nozzle_type;
|
||||
return system->Owner()->GetNozzleSystem()->GetExtNozzle(m_current_nozzle_id).m_nozzle_type;
|
||||
}
|
||||
|
||||
NozzleFlowType DevExtder::GetNozzleFlowType() const
|
||||
{
|
||||
return system->Owner()->GetNozzleSystem()->GetNozzle(m_current_nozzle_id).m_nozzle_flow;
|
||||
return system->Owner()->GetNozzleSystem()->GetExtNozzle(m_current_nozzle_id).m_nozzle_flow;
|
||||
}
|
||||
|
||||
float DevExtder::GetNozzleDiameter() const
|
||||
{
|
||||
return system->Owner()->GetNozzleSystem()->GetNozzle(m_current_nozzle_id).m_diameter;
|
||||
return system->Owner()->GetNozzleSystem()->GetExtNozzle(m_current_nozzle_id).m_diameter;
|
||||
}
|
||||
|
||||
std::unordered_map<int, bool> DevExtder::GetBackupStatus(unsigned int fila_back_group)
|
||||
{
|
||||
std::unordered_map<int, bool> trayid_group;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if (fila_back_group & (1 << i))
|
||||
{
|
||||
trayid_group[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 16; j <= 23; j++)/* single ams is from 128*/
|
||||
{
|
||||
if (fila_back_group & (1 << j)) {
|
||||
trayid_group[128 + j - 16] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 24; i <= 27; i++) /* ams lite for N9*/
|
||||
{
|
||||
if (fila_back_group & (1 << i)) { trayid_group[i] = true; }
|
||||
}
|
||||
|
||||
return trayid_group;
|
||||
}
|
||||
|
||||
DevExtderSystem::DevExtderSystem(MachineObject* obj)
|
||||
@@ -143,6 +170,24 @@ namespace Slic3r
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<DevAmsSlotId> DevExtderSystem::GetBackupAmsSlotInGroup(const DevAmsSlotId& ams_slot_id)
|
||||
{
|
||||
std::map<int, DevAmsSlotId> tray_map = Owner()->GetFilaSystem()->GetTrayIndexMap();
|
||||
|
||||
std::vector<DevAmsSlotId> backup_group;
|
||||
for (const auto& extruder : m_extders) {
|
||||
for (int fila_backup : extruder.GetFilamBackup()) {
|
||||
for (auto [tray_id, is_valid] : DevExtder::GetBackupStatus(fila_backup)) {
|
||||
if (is_valid && tray_map.find(tray_id) != tray_map.end() && tray_map[tray_id] != ams_slot_id) {
|
||||
backup_group.emplace_back(tray_map[tray_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return backup_group;
|
||||
}
|
||||
|
||||
void ExtderSystemParser::ParseV1_0(const nlohmann::json& print_json, DevExtderSystem* system)
|
||||
{
|
||||
if (system->GetTotalExtderCount() != 1)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include "libslic3r/CommonDefs.hpp"
|
||||
|
||||
#include "slic3r/Utils/json_diff.hpp"
|
||||
@@ -66,6 +67,10 @@ public:
|
||||
bool HasFilamBackup() const { return !m_filam_bak.empty(); }
|
||||
std::vector<int> GetFilamBackup() const { return m_filam_bak; }
|
||||
|
||||
// Decode a filament-backup bit group into { tray_id -> valid }. Bits 0-15 map directly to tray ids,
|
||||
// bits 16-23 to single-AMS tray ids 128+, bits 24-27 to AMS-Lite-mixed (N9) tray ids 24-27.
|
||||
static std::unordered_map<int, bool> GetBackupStatus(unsigned int fila_back_group);
|
||||
|
||||
// ams binding on current extruder
|
||||
const DevAmsSlotInfo& GetSlotPre() const { return m_spre; }
|
||||
const DevAmsSlotInfo& GetSlotNow() const { return m_snow; }
|
||||
@@ -168,6 +173,11 @@ public:
|
||||
bool HasFilamentBackup() const;
|
||||
bool HasFilamentInExt(int exter_id) { return GetExtderById(exter_id) ? GetExtderById(exter_id)->HasFilamentInExt() : false; }
|
||||
|
||||
// List the other AMS slots that back up the given slot within the same filament-backup group,
|
||||
// across all extruders. Uses DevFilaSystem::GetTrayIndexMap() to translate backup bits (incl. the
|
||||
// A2L/N9 AMS-Lite-mixed trays 24-27) into ams/slot ids.
|
||||
std::vector<DevAmsSlotId> GetBackupAmsSlotInGroup(const DevAmsSlotId& ams_slot_id);
|
||||
|
||||
protected:
|
||||
void AddExtder(const DevExtder& ext) { m_extders[ext.GetExtId()] = ext; };
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
|
||||
#include "DevFilaBlackList.h"
|
||||
#include "DevFilaSystem.h"
|
||||
#include "DevManager.h"
|
||||
#include "DevConfigUtil.h"
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
@@ -73,64 +77,97 @@ static std::string _get_filament_name_from_ams(int ams_id, int slot_id)
|
||||
}
|
||||
|
||||
// moved from tao.wang and zhimin.zeng
|
||||
void check_filaments(std::string model_id,
|
||||
std::string tag_vendor,
|
||||
std::string tag_type,
|
||||
int ams_id,
|
||||
int slot_id,
|
||||
std::string tag_name,
|
||||
bool& in_blacklist,
|
||||
std::string& ac,
|
||||
wxString& info,
|
||||
wxString& wiki_url)
|
||||
// Struct-in / struct-out, accumulate-all parser: every matching rule is pushed into
|
||||
// result.action_items[action] and scanning continues (rather than stopping at the first match).
|
||||
void check_filaments(const DevFilaBlacklist::CheckFilamentInfo& check_info, DevFilaBlacklist::CheckResult& result)
|
||||
{
|
||||
std::string tag_type = check_info.fila_type;
|
||||
std::string tag_name = check_info.fila_name;
|
||||
std::string tag_vendor = check_info.fila_vendor;
|
||||
std::string tag_calib_mode = check_info.calib_mode;
|
||||
|
||||
// Orca: the print-send and calibration consumers do not populate fila_name; recover it from the
|
||||
// selected AMS slot so name / name_suffix rules keep matching.
|
||||
if (tag_name.empty())
|
||||
{
|
||||
tag_name = _get_filament_name_from_ams(ams_id, slot_id);
|
||||
tag_name = _get_filament_name_from_ams(check_info.ams_id, check_info.slot_id);
|
||||
}
|
||||
|
||||
in_blacklist = false;
|
||||
std::transform(tag_vendor.begin(), tag_vendor.end(), tag_vendor.begin(), ::tolower);
|
||||
std::transform(tag_type.begin(), tag_type.end(), tag_type.begin(), ::tolower);
|
||||
std::transform(tag_name.begin(), tag_name.end(), tag_name.begin(), ::tolower);
|
||||
std::transform(tag_calib_mode.begin(), tag_calib_mode.end(), tag_calib_mode.begin(), ::tolower);
|
||||
|
||||
DevFilaBlacklist::load_filaments_blacklist_config();
|
||||
if (DevFilaBlacklist::filaments_blacklist.contains("blacklist"))
|
||||
{
|
||||
for (auto filament_item : DevFilaBlacklist::filaments_blacklist["blacklist"])
|
||||
{
|
||||
|
||||
std::string vendor = filament_item.contains("vendor") ? filament_item["vendor"].get<std::string>() : "";
|
||||
std::string type = filament_item.contains("type") ? filament_item["type"].get<std::string>() : "";
|
||||
std::string type_suffix = filament_item.contains("type_suffix") ? filament_item["type_suffix"].get<std::string>() : "";
|
||||
std::string name = filament_item.contains("name") ? filament_item["name"].get<std::string>() : "";
|
||||
std::string slot = filament_item.contains("slot") ? filament_item["slot"].get<std::string>() : "";
|
||||
std::vector<std::string> model_ids = filament_item.contains("model_id") ? filament_item["model_id"].get<std::vector<std::string>>() : std::vector<std::string>();
|
||||
std::string action = filament_item.contains("action") ? filament_item["action"].get<std::string>() : "";
|
||||
std::string description = filament_item.contains("description") ? filament_item["description"].get<std::string>() : "";
|
||||
|
||||
// blacklist items
|
||||
std::string vendor = filament_item.contains("vendor") ? filament_item["vendor"].get<std::string>() : "";
|
||||
std::string type = filament_item.contains("type") ? filament_item["type"].get<std::string>() : "";
|
||||
std::vector<std::string> types = filament_item.contains("types") ? filament_item["types"].get<std::vector<std::string>>() : std::vector<std::string>();
|
||||
std::string type_suffix = filament_item.contains("type_suffix") ? filament_item["type_suffix"].get<std::string>() : "";
|
||||
std::string name_suffix = filament_item.contains("name_suffix") ? filament_item["name_suffix"].get<std::string>() : "";
|
||||
std::string name = filament_item.contains("name") ? filament_item["name"].get<std::string>() : "";
|
||||
std::string slot = filament_item.contains("slot") ? filament_item["slot"].get<std::string>() : "";
|
||||
std::optional<bool> used_for_print_support = filament_item.contains("used_for_print_support") ? filament_item["used_for_print_support"].get<bool>() : std::optional<bool>();
|
||||
std::optional<bool> used_for_print_object = filament_item.contains("used_for_print_object") ? filament_item["used_for_print_object"].get<bool>() : std::optional<bool>();
|
||||
std::vector<std::string> model_ids = filament_item.contains("model_id") ? filament_item["model_id"].get<std::vector<std::string>>() : std::vector<std::string>();
|
||||
std::vector<int> extruder_ids = filament_item.contains("extruder_id") ? filament_item["extruder_id"].get<std::vector<int>>() : std::vector<int>();
|
||||
std::vector<std::string> nozzle_flows = filament_item.contains("nozzle_flows") ? filament_item["nozzle_flows"].get<std::vector<std::string>>() : std::vector<std::string>();
|
||||
std::string calib_mode = filament_item.contains("calib_mode") ? filament_item["calib_mode"].get<std::string>() : "";
|
||||
|
||||
// check model id
|
||||
if (!model_ids.empty() && std::find(model_ids.begin(), model_ids.end(), model_id) == model_ids.end()) { continue; }
|
||||
if (!model_ids.empty() && std::find(model_ids.begin(), model_ids.end(), check_info.model_id) == model_ids.end()) { continue; }
|
||||
|
||||
// A rule keyed on nozzle_flows/nozzle_diameters only matches when check_info carries real
|
||||
// per-nozzle context (threaded by the print-send consumer). Consumers that leave
|
||||
// nozzle_flow/nozzle_diameter unset (AMS edit, calibration, and the no-nozzle-context
|
||||
// fallback) never satisfy the non-empty match below, so those rules stay dormant there.
|
||||
|
||||
// check nozzle flows
|
||||
if (!nozzle_flows.empty() && std::find(nozzle_flows.begin(), nozzle_flows.end(), check_info.nozzle_flow.value_or("")) == nozzle_flows.end()) { continue; }
|
||||
|
||||
// check nozzle diameter
|
||||
const std::vector<float> nozzle_diameters = filament_item.contains("nozzle_diameters") ? filament_item["nozzle_diameters"].get<std::vector<float>>() : std::vector<float>();
|
||||
if (!nozzle_diameters.empty() && check_info.nozzle_diameter.has_value() &&
|
||||
std::find(nozzle_diameters.begin(), nozzle_diameters.end(), check_info.nozzle_diameter.value()) == nozzle_diameters.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check vendor
|
||||
std::transform(vendor.begin(), vendor.end(), vendor.begin(), ::tolower);
|
||||
if (!vendor.empty())
|
||||
{
|
||||
if ((vendor == "bambu lab" && (tag_vendor == vendor)) ||
|
||||
(vendor == "third party" && (tag_vendor != "bambu lab")))
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else
|
||||
if (!((vendor == "bambu lab" && tag_vendor == "bambu lab") ||
|
||||
(vendor == "third party" && tag_vendor != "bambu lab")))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// check calib mode
|
||||
std::transform(calib_mode.begin(), calib_mode.end(), calib_mode.begin(), ::tolower);
|
||||
if (!calib_mode.empty() && calib_mode != tag_calib_mode) { continue; }
|
||||
|
||||
// check type
|
||||
std::transform(type.begin(), type.end(), type.begin(), ::tolower);
|
||||
if (!type.empty() && (type != tag_type)) { continue; }
|
||||
|
||||
// check types (plural): item matches if tag_type is any of the listed types.
|
||||
if (!types.empty())
|
||||
{
|
||||
auto it = std::find_if(types.begin(), types.end(), [&tag_type](std::string ttype) {
|
||||
std::transform(ttype.begin(), ttype.end(), ttype.begin(), ::tolower);
|
||||
return ttype == tag_type;
|
||||
});
|
||||
if (it == types.end()) { continue; }
|
||||
}
|
||||
|
||||
// check type suffix
|
||||
std::transform(type_suffix.begin(), type_suffix.end(), type_suffix.begin(), ::tolower);
|
||||
if (!type_suffix.empty())
|
||||
@@ -143,10 +180,31 @@ void check_filaments(std::string model_id,
|
||||
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
||||
if (!name.empty() && (name != tag_name)) { continue; }
|
||||
|
||||
// check name suffix
|
||||
std::transform(name_suffix.begin(), name_suffix.end(), name_suffix.begin(), ::tolower);
|
||||
if (!name_suffix.empty())
|
||||
{
|
||||
if (tag_name.length() < name_suffix.length()) { continue; }
|
||||
if ((tag_name.substr(tag_name.length() - name_suffix.length()) != name_suffix)) { continue; }
|
||||
}
|
||||
|
||||
// check filament used for print support
|
||||
if (used_for_print_support.has_value() && used_for_print_support != check_info.used_for_print_support) { continue; }
|
||||
|
||||
// check filament used for print object
|
||||
if (used_for_print_object.has_value() && used_for_print_object != check_info.used_for_print_object) { continue; }
|
||||
|
||||
// check filament switch
|
||||
if (filament_item.contains("has_filament_switch"))
|
||||
{
|
||||
bool has_filament_switch = filament_item["has_filament_switch"].get<bool>();
|
||||
if (check_info.has_filament_switch != has_filament_switch) { continue; }
|
||||
}
|
||||
|
||||
// check loc
|
||||
if (!slot.empty())
|
||||
{
|
||||
bool is_virtual_slot = devPrinterUtil::IsVirtualSlot(ams_id);
|
||||
bool is_virtual_slot = devPrinterUtil::IsVirtualSlot(check_info.ams_id);
|
||||
bool check_virtual_slot = (slot == "ext");
|
||||
bool check_ams_slot = (slot == "ams");
|
||||
if (is_virtual_slot && !check_virtual_slot)
|
||||
@@ -159,71 +217,115 @@ void check_filaments(std::string model_id,
|
||||
}
|
||||
}
|
||||
|
||||
if (GUI::wxGetApp().app_config->get("skip_ams_blacklist_check") == "true") {
|
||||
action = "warning";
|
||||
// check extruder id
|
||||
if (!extruder_ids.empty() && check_info.extruder_id.has_value() &&
|
||||
std::find(extruder_ids.begin(), extruder_ids.end(), check_info.extruder_id.value()) == extruder_ids.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
in_blacklist = true;
|
||||
ac = action;
|
||||
info = _L(description);
|
||||
wiki_url = filament_item.contains("wiki") ? filament_item["wiki"].get<std::string>() : "";
|
||||
return;
|
||||
// white items: a matched rule is suppressed when the filament is whitelisted
|
||||
{
|
||||
// contains match
|
||||
std::set<std::string> white_names = filament_item.contains("white_names") ? filament_item["white_names"].get<std::set<std::string>>() : std::set<std::string>();
|
||||
if (!white_names.empty() && !tag_name.empty())
|
||||
{
|
||||
auto it = std::find_if(white_names.begin(), white_names.end(), [&tag_name](std::string white_name) {
|
||||
std::transform(white_name.begin(), white_name.end(), white_name.begin(), ::tolower);
|
||||
return tag_name.find(white_name) != std::string::npos;
|
||||
});
|
||||
if (it != white_names.end()) { continue; }
|
||||
}
|
||||
}
|
||||
{
|
||||
// equal match
|
||||
std::set<std::string> white_fila_ids = filament_item.contains("white_fila_ids") ? filament_item["white_fila_ids"].get<std::set<std::string>>() : std::set<std::string>();
|
||||
if (!white_fila_ids.empty() && !check_info.fila_id.empty())
|
||||
{
|
||||
auto it = std::find_if(white_fila_ids.begin(), white_fila_ids.end(), [&check_info](const std::string& white_fila_id) {
|
||||
return white_fila_id == check_info.fila_id;
|
||||
});
|
||||
if (it != white_fila_ids.end()) { continue; }
|
||||
}
|
||||
}
|
||||
|
||||
// the item is matched: accumulate it (do not stop scanning)
|
||||
DevFilaBlacklist::CheckResultItem result_item;
|
||||
result_item.action = action;
|
||||
result_item.wiki_url = filament_item.contains("wiki") ? filament_item["wiki"].get<std::string>() : "";
|
||||
|
||||
if (description == "%s has a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution.") {
|
||||
result_item.info_msg = wxString::Format(_L(description), check_info.fila_name);
|
||||
} else if (description == "%s filaments are hard and brittle and could break in AMS, and there is also a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution.") {
|
||||
result_item.info_msg = wxString::Format(_L(description), check_info.fila_type);
|
||||
} else if (description == "%s has a risk of nozzle clogging when using 0.4, 0.6, 0.8mm high-flow nozzles. Use with caution.") {
|
||||
result_item.info_msg = wxString::Format(_L(description), check_info.fila_name);
|
||||
} else if (description == "%s may fail to load or unload due to the Filament Track Switch. If you wish to continue.") {
|
||||
result_item.info_msg = wxString::Format(_L(description), check_info.fila_name);
|
||||
} else {
|
||||
result_item.info_msg = _L(description);
|
||||
}
|
||||
|
||||
result.action_items[result_item.action].push_back(result_item);
|
||||
continue;
|
||||
|
||||
// Error in description
|
||||
L("TPU is not supported by AMS.");
|
||||
L("AMS does not support 'Bambu Lab PET-CF'.");
|
||||
L("The current filament doesn't support the E3D high-flow nozzle and can't be used.");
|
||||
L("The current filament doesn't support the TPU high-flow nozzle and can't be used.");
|
||||
L("Auto dynamic flow calibration is not supported for TPU filament.");
|
||||
L("Bambu TPU 85A is not supported for printing with 0.4 mm Standard or High Flow nozzles.");
|
||||
|
||||
// Warning in description
|
||||
L("How to feed TPU filament.");
|
||||
L("How to feed TPU filament on X2D.");
|
||||
L("Please cold pull before printing TPU to avoid clogging. You may use cold pull maintenance on the printer.");
|
||||
L("Damp PVA will become flexible and get stuck inside AMS, please take care to dry it before use.");
|
||||
L("Damp PVA is flexible and may get stuck in extruder. Dry it before use.");
|
||||
L("The rough surface of PLA Glow can accelerate wear on the AMS system, particularly on the internal components of the AMS Lite.");
|
||||
L("PLA Glow may wear the AMS first stage feeder. Use an external spool instead.");
|
||||
L("CF/GF filaments are hard and brittle, it's easy to break or get stuck in AMS, please use with caution.");
|
||||
L("PPS-CF is brittle and could break in bended PTFE tube above Toolhead.");
|
||||
L("PPA-CF is brittle and could break in bended PTFE tube above Toolhead.");
|
||||
L("Default settings may affect print quality. Adjust as needed for best results.");
|
||||
L("%s has a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution.");
|
||||
L("%s filaments are hard and brittle and could break in AMS, and there is also a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution.");
|
||||
L("%s has a risk of nozzle clogging when using 0.4, 0.6, 0.8mm high-flow nozzles. Use with caution.");
|
||||
L("%s may fail to load or unload due to the Filament Track Switch. If you wish to continue.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DevFilaBlacklist::check_filaments_in_blacklist(std::string model_id,
|
||||
std::string tag_vendor,
|
||||
std::string tag_type,
|
||||
const std::string& filament_id,
|
||||
int ams_id,
|
||||
int slot_id,
|
||||
std::string tag_name,
|
||||
bool& in_blacklist,
|
||||
std::string& ac,
|
||||
wxString& info)
|
||||
// Extends the prohibition-only bit check with a 4-level filament_extruder_compatibility model
|
||||
// (0 printable / 1 error / 2 critical warning / 3 warning), bowden-extruder message variants and
|
||||
// toolhead display names for the "%s extruder" name.
|
||||
//
|
||||
// Orca: several deliberate divergences preserve H2D dual-extruder behaviour when the compatibility
|
||||
// key is absent (default 0 = printable):
|
||||
// - The extruder is resolved from the ams_id rather than a threaded check_info.extruder_id with an
|
||||
// early-return: extruder_id is only threaded on the print-send consumer, so falling back on
|
||||
// ams_id keeps the bit-check firing on the AMS-edit / calibration paths.
|
||||
// - The prohibition bit-check keeps check_info.fila_type for its "%s", preserving the exact H2D
|
||||
// AMS-edit message that ships today. The new 4-level messages use the fila_name fallback since
|
||||
// they stay inert until a filament declares the compatibility key (only X2D ships it).
|
||||
// - is_bowden_extruder is assigned correctly here so the Bowden message variants actually fire.
|
||||
void check_filaments_printable(const DevFilaBlacklist::CheckFilamentInfo& check_info, DevFilaBlacklist::CheckResult& result)
|
||||
{
|
||||
wxString wiki_url;
|
||||
check_filaments_in_blacklist_url(model_id, tag_vendor, tag_type, filament_id, ams_id, slot_id, tag_name, in_blacklist, ac, info, wiki_url);
|
||||
}
|
||||
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev) { return; }
|
||||
|
||||
MachineObject* obj = dev->get_selected_machine();
|
||||
if (obj == nullptr || !obj->is_multi_extruders()) { return; }
|
||||
|
||||
bool check_filaments_printable(const std::string &tag_vendor, const std::string &tag_type, const std::string& filament_id, int ams_id, bool &in_blacklist, std::string &ac, wxString &info)
|
||||
{
|
||||
DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev) {
|
||||
return true;
|
||||
}
|
||||
Preset* printer_preset = GUI::get_printer_preset(obj);
|
||||
if (!printer_preset) { return; }
|
||||
|
||||
MachineObject *obj = dev->get_selected_machine();
|
||||
if (obj == nullptr || !obj->is_multi_extruders()) {
|
||||
return true;
|
||||
}
|
||||
ConfigOptionInts* physical_extruder_map_op = dynamic_cast<ConfigOptionInts*>(printer_preset->config.option("physical_extruder_map"));
|
||||
if (!physical_extruder_map_op) { return; }
|
||||
|
||||
Preset *printer_preset = GUI::get_printer_preset(obj);
|
||||
if (!printer_preset)
|
||||
return true;
|
||||
|
||||
ConfigOptionInts *physical_extruder_map_op = dynamic_cast<ConfigOptionInts *>(printer_preset->config.option("physical_extruder_map"));
|
||||
if (!physical_extruder_map_op)
|
||||
return true;
|
||||
std::vector<int> physical_extruder_maps = physical_extruder_map_op->values;
|
||||
int obj_extruder_id = obj->get_extruder_id_by_ams_id(std::to_string(ams_id));
|
||||
int obj_extruder_id = obj->get_extruder_id_by_ams_id(std::to_string(check_info.ams_id));
|
||||
int extruder_idx = obj_extruder_id;
|
||||
for (int index = 0; index < physical_extruder_maps.size(); ++index) {
|
||||
if (physical_extruder_maps[index] == obj_extruder_id) {
|
||||
@@ -232,32 +334,85 @@ bool check_filaments_printable(const std::string &tag_vendor, const std::string
|
||||
}
|
||||
}
|
||||
|
||||
PresetBundle *preset_bundle = GUI::wxGetApp().preset_bundle;
|
||||
std::optional<FilamentBaseInfo> filament_info = preset_bundle->get_filament_by_filament_id(filament_id, printer_preset->name);
|
||||
if (filament_info.has_value() && !(filament_info->filament_printable >> extruder_idx & 1)) {
|
||||
wxString extruder_name = extruder_idx == 0 ? _L("left") : _L("right");
|
||||
ac = "prohibition";
|
||||
info = wxString::Format(_L("%s is not supported by %s extruder."), tag_type, extruder_name);
|
||||
in_blacklist = true;
|
||||
return false;
|
||||
PresetBundle* preset_bundle = GUI::wxGetApp().preset_bundle;
|
||||
std::optional<FilamentBaseInfo> filament_info = preset_bundle->get_filament_by_filament_id(check_info.fila_id, printer_preset->name);
|
||||
if (!filament_info.has_value()) { return; }
|
||||
|
||||
// Physical extruder 0 -> deputy (left), 1 -> main (right); the toolhead display name replaces the
|
||||
// hard-coded left/right labels (falls back to "left"/"right" for printers without an override).
|
||||
int bl_ext_id = (extruder_idx == 0) ? DEPUTY_EXTRUDER_ID : MAIN_EXTRUDER_ID;
|
||||
wxString extruder_name = _L(DevPrinterConfigUtil::get_toolhead_display_name(
|
||||
obj->printer_type, bl_ext_id, ToolHeadComponent::Extruder, ToolHeadNameCase::LowerCase, true));
|
||||
|
||||
// Prohibition-only bit check; keeps fila_type in the "%s" to preserve current behaviour.
|
||||
if (!(filament_info->filament_printable >> extruder_idx & 1)) {
|
||||
DevFilaBlacklist::CheckResultItem item;
|
||||
item.action = "prohibition";
|
||||
item.info_msg = wxString::Format(_L("%s is not supported by %s extruder."), check_info.fila_type, extruder_name);
|
||||
result.action_items[item.action].push_back(item);
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
// Is this a bowden extruder? (drives the message variant for compatibility warnings.)
|
||||
bool is_bowden_extruder = false;
|
||||
auto extruder_type_opt = dynamic_cast<const ConfigOptionEnumsGeneric*>(printer_preset->config.option("extruder_type"));
|
||||
if (extruder_type_opt && (int) extruder_type_opt->values.size() > extruder_idx) {
|
||||
ExtruderType extruder_type = (ExtruderType) extruder_type_opt->values[extruder_idx];
|
||||
is_bowden_extruder = (extruder_type == ExtruderType::etBowden);
|
||||
}
|
||||
|
||||
// Compatibility levels: 0 = printable, 1 = error, 2 = critical warning, 3 = warning (4-7 reserved).
|
||||
std::string fila_name = check_info.fila_name.empty() ? check_info.fila_type : check_info.fila_name;
|
||||
int compatible_val = filament_info->get_extruder_compatibility(extruder_idx);
|
||||
if (compatible_val == 0) {
|
||||
// printable, nothing to report
|
||||
} else if (compatible_val == 1) {
|
||||
DevFilaBlacklist::CheckResultItem item;
|
||||
item.action = "prohibition";
|
||||
item.info_msg = wxString::Format(_L("%s is not supported by %s extruder."), fila_name, extruder_name);
|
||||
result.action_items[item.action].push_back(item);
|
||||
} else if (compatible_val == 2) {
|
||||
DevFilaBlacklist::CheckResultItem item;
|
||||
item.action = "warning";
|
||||
item.info_msg = is_bowden_extruder
|
||||
? wxString::Format(_L("There may be critical print quality issues when printing '%s' with %s Bowden extruder. Use with caution!"), fila_name, extruder_name)
|
||||
: wxString::Format(_L("There may be critical print quality issues when printing '%s' with %s extruder. Use with caution!"), fila_name, extruder_name);
|
||||
result.action_items[item.action].push_back(item);
|
||||
} else if (compatible_val == 3) {
|
||||
DevFilaBlacklist::CheckResultItem item;
|
||||
item.action = "warning";
|
||||
item.info_msg = is_bowden_extruder
|
||||
? wxString::Format(_L("There may be print quality issues when printing '%s' with %s Bowden extruder. Use with caution."), fila_name, extruder_name)
|
||||
: wxString::Format(_L("There may be print quality issues when printing '%s' with %s extruder. Use with caution."), fila_name, extruder_name);
|
||||
result.action_items[item.action].push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
void DevFilaBlacklist::check_filaments_in_blacklist_url(std::string model_id, std::string tag_vendor, std::string tag_type, const std::string& filament_id, int ams_id, int slot_id, std::string tag_name, bool& in_blacklist, std::string& ac, wxString& info, wxString& wiki_url)
|
||||
DevFilaBlacklist::CheckResult DevFilaBlacklist::check_filaments_in_blacklist(const CheckFilamentInfo& info)
|
||||
{
|
||||
if (ams_id < 0 || slot_id < 0)
|
||||
CheckResult result;
|
||||
if (info.ams_id < 0 || info.slot_id < 0)
|
||||
{
|
||||
return;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!check_filaments_printable(tag_vendor, tag_type, filament_id, ams_id, in_blacklist, ac, info))
|
||||
{
|
||||
return;
|
||||
// check the filaments in preset
|
||||
check_filaments_printable(info, result);
|
||||
|
||||
// check the filaments in blacklist file
|
||||
check_filaments(info, result);
|
||||
|
||||
// If skip_ams_blacklist_check is true, demote every prohibition to a warning (aggregate: demote
|
||||
// all at once rather than per-item).
|
||||
if (GUI::wxGetApp().app_config->get("skip_ams_blacklist_check") == "true") {
|
||||
const auto& prohibit_items = result.get_items_by_action("prohibition");
|
||||
if (!prohibit_items.empty()) {
|
||||
for (const auto& prohibit_item : prohibit_items) { result.action_items["warning"].push_back(prohibit_item); }
|
||||
result.action_items.erase("prohibition");
|
||||
}
|
||||
}
|
||||
|
||||
check_filaments(model_id, tag_vendor, tag_type, ams_id, slot_id, tag_name, in_blacklist, ac, info, wiki_url);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <wx/string.h>
|
||||
#include "slic3r/Utils/json_diff.hpp"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
class MachineObject;
|
||||
class DevFilaBlacklist
|
||||
{
|
||||
public:
|
||||
// Struct-in / struct-out check API that accumulates all matching rules,
|
||||
// replacing the earlier out-param, first-match overloads.
|
||||
// The per-nozzle dimensions (extruder_id/nozzle_flow/nozzle_diameter), calib_mode and
|
||||
// has_filament_switch are currently left unset, so the engine behaves exactly as the
|
||||
// old first-match parser for the shipping rule set.
|
||||
struct CheckFilamentInfo
|
||||
{
|
||||
std::string dev_id;
|
||||
std::string model_id;
|
||||
|
||||
std::string fila_id;
|
||||
std::string fila_type;
|
||||
std::string fila_name;
|
||||
std::string fila_vendor;
|
||||
|
||||
std::string calib_mode;
|
||||
bool has_filament_switch = false;
|
||||
|
||||
std::optional<bool> used_for_print_support;// optional
|
||||
std::optional<bool> used_for_print_object;// optional
|
||||
|
||||
int ams_id;
|
||||
int slot_id;
|
||||
|
||||
std::optional<int> extruder_id;// optional
|
||||
std::optional<std::string> nozzle_flow;// optional
|
||||
std::optional<float> nozzle_diameter;// optional
|
||||
};
|
||||
|
||||
struct CheckResultItem
|
||||
{
|
||||
std::string action;// warning/prohibition
|
||||
wxString info_msg;
|
||||
wxString wiki_url;
|
||||
};
|
||||
|
||||
struct CheckResult
|
||||
{
|
||||
std::map<std::string, std::vector<CheckResultItem>> action_items;
|
||||
std::vector<CheckResultItem> get_items_by_action(const std::string& action) const
|
||||
{
|
||||
auto it = action_items.find(action);
|
||||
if (it != action_items.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return std::vector<CheckResultItem>();
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
static bool load_filaments_blacklist_config();
|
||||
static void check_filaments_in_blacklist(std::string model_id, std::string tag_vendor, std::string tag_type, const std::string& filament_id, int ams_id, int slot_id, std::string tag_name, bool& in_blacklist, std::string& ac, wxString& info);
|
||||
static void check_filaments_in_blacklist_url(std::string model_id, std::string tag_vendor, std::string tag_type, const std::string& filament_id, int ams_id, int slot_id, std::string tag_name, bool& in_blacklist, std::string& ac, wxString& info, wxString& wiki_url);
|
||||
static CheckResult check_filaments_in_blacklist(const CheckFilamentInfo& info);
|
||||
|
||||
public:
|
||||
static json filaments_blacklist;
|
||||
};// class DevFilaBlacklist
|
||||
|
||||
}// namespace Slic3r
|
||||
}// namespace Slic3r
|
||||
@@ -0,0 +1,131 @@
|
||||
#include "DevExtruderSystem.h"
|
||||
#include "DevFilaSystem.h"
|
||||
#include "DevFilaSwitch.h"
|
||||
#include "DevUtil.h"
|
||||
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
DevFilaSwitch::DevFilaSwitch(MachineObject *owner) { m_owner = owner; }
|
||||
|
||||
bool DevFilaSwitch::IsReady() const
|
||||
{
|
||||
if (!m_is_installed) { return false; }
|
||||
|
||||
const auto& ams_list = m_owner->GetFilaSystem()->GetAmsList();
|
||||
for (const auto& ams_item : ams_list) {
|
||||
if (ams_item.second->GetBindedExtruderSet().empty()) { return false; }
|
||||
if (!ams_item.second->GetSwitcherPos().has_value()) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DevFilaSwitch::Reset()
|
||||
{
|
||||
m_is_installed = false;
|
||||
m_in_a_has_filament.reset();
|
||||
m_in_b_has_filament.reset();
|
||||
m_in_a_slot.reset();
|
||||
m_in_b_slot.reset();
|
||||
m_out_a_extruder_id.reset();
|
||||
m_out_b_extruder_id.reset();
|
||||
m_cali_status = CaliStatus::CALI_IDLE;
|
||||
}
|
||||
|
||||
std::optional<DevAmsTray> DevFilaSwitch::GetInA_Slot() const
|
||||
{
|
||||
auto ams_id_opt = GetInA_SlotId();
|
||||
if (m_owner && ams_id_opt.has_value()) { return m_owner->get_tray(std::to_string(ams_id_opt->first), std::to_string(ams_id_opt->first)); }
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<DevAmsTray> DevFilaSwitch::GetInB_Slot() const
|
||||
{
|
||||
auto ams_id_opt = GetInB_SlotId();
|
||||
if (m_owner && ams_id_opt.has_value()) { return m_owner->get_tray(std::to_string(ams_id_opt->first), std::to_string(ams_id_opt->first)); }
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<DevExtder> DevFilaSwitch::GetOutA_Extruder() const
|
||||
{
|
||||
auto ext_id_opt = GetOutA_ExtruderId();
|
||||
if (m_owner && ext_id_opt.has_value()) { return m_owner->GetExtderSystem()->GetExtderById(ext_id_opt.value()); }
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<DevExtder> DevFilaSwitch::GetOutB_Extruder() const
|
||||
{
|
||||
auto ext_id_opt = GetOutB_ExtruderId();
|
||||
if (m_owner && ext_id_opt.has_value()) { return m_owner->GetExtderSystem()->GetExtderById(ext_id_opt.value()); }
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void DevFilaSwitch::ParseFilaSwitchInfo(const nlohmann::json &print_jj)
|
||||
{
|
||||
if (print_jj.contains("aux")) {
|
||||
const auto &info_bits = DevJsonValParser::GetVal<std::string>(print_jj, "aux");
|
||||
if (m_is_installed != (DevUtil::get_flag_bits(info_bits, 29, 1) == 1)) {
|
||||
m_is_installed = (DevUtil::get_flag_bits(info_bits, 29, 1) == 1);
|
||||
if (!m_is_installed) { Reset(); }
|
||||
};
|
||||
}
|
||||
|
||||
if (print_jj.contains("device") && print_jj["device"].contains("fila_switch")) {
|
||||
const auto &fila_switch_jj = print_jj["device"]["fila_switch"];
|
||||
if (fila_switch_jj.contains("in")) {
|
||||
const auto &in_vec = DevJsonValParser::GetVal<std::vector<int>>(fila_switch_jj, "in");
|
||||
if (in_vec.size() == 2) {
|
||||
if (in_vec[0] != -1) {
|
||||
DevAmsSlotId slot_id;
|
||||
slot_id.first = DevUtil::get_flag_bits(in_vec[0], 8, 8);
|
||||
slot_id.second = DevUtil::get_flag_bits(in_vec[0], 0, 8);
|
||||
m_in_b_slot = slot_id;
|
||||
} else {
|
||||
m_in_b_slot = std::nullopt;
|
||||
};
|
||||
|
||||
if (in_vec[1] != -1) {
|
||||
DevAmsSlotId slot_id;
|
||||
slot_id.first = DevUtil::get_flag_bits(in_vec[1], 8, 8);
|
||||
slot_id.second = DevUtil::get_flag_bits(in_vec[1], 0, 8);
|
||||
m_in_a_slot = slot_id;
|
||||
} else {
|
||||
m_in_a_slot.reset();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (fila_switch_jj.contains("out")) {
|
||||
const auto &out_vec = DevJsonValParser::GetVal<std::vector<int>>(fila_switch_jj, "out");
|
||||
if (out_vec.size() == 2) {
|
||||
if (out_vec[0] != 0xE) {
|
||||
m_out_b_extruder_id = out_vec[0];
|
||||
} else {
|
||||
m_out_b_extruder_id.reset();
|
||||
}
|
||||
|
||||
if (out_vec[1] != 0xE) {
|
||||
m_out_a_extruder_id = out_vec[1];
|
||||
} else {
|
||||
m_out_a_extruder_id.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fila_switch_jj.contains("stat")) { m_cali_status = DevJsonValParser::GetVal<CaliStatus>(fila_switch_jj, "stat"); }
|
||||
|
||||
if (fila_switch_jj.contains("info")) {
|
||||
const auto &info_bits = DevJsonValParser::GetVal<int>(fila_switch_jj, "info");
|
||||
m_in_b_has_filament = DevUtil::get_flag_bits(info_bits, 0, 1);
|
||||
m_in_a_has_filament = DevUtil::get_flag_bits(info_bits, 0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
#include "DevDefs.h"
|
||||
#include <optional>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
// Previous definitions
|
||||
class MachineObject;
|
||||
class DevExtder;
|
||||
class DevAmsTray;
|
||||
|
||||
// Filament Track Switch (H2-family hardware).
|
||||
// The filament blacklist consumes IsInstalled() for the `has_filament_switch`
|
||||
// rule (e.g. PLA Silk). m_is_installed defaults to false and is only raised when
|
||||
// the device reports the switch over MQTT (aux bit 29), so the rule stays inert on
|
||||
// every printer that does not report an installed switch.
|
||||
class DevFilaSwitch
|
||||
{
|
||||
public:
|
||||
enum class CaliStatus : int
|
||||
{
|
||||
CALI_IDLE = 0,
|
||||
CALI_STEPING = 1,
|
||||
};
|
||||
|
||||
enum class CaliStep : int
|
||||
{
|
||||
CALI_IDLE = 0,
|
||||
CALI_SWITCHING = 1,
|
||||
CALI_SWITCH_CHECK = 2,
|
||||
CALI_FILA_CHECK = 3,
|
||||
CALI_FILA_TO_AMS = 4,
|
||||
CALI_FILA_TO_SWITCH = 5,
|
||||
CALI_FILA_BACK = 9,
|
||||
CALI_FINISHED = 14,
|
||||
};
|
||||
|
||||
enum SwitchPos : int
|
||||
{
|
||||
POS_IN_B = 0,
|
||||
POS_IN_A = 1,
|
||||
};
|
||||
|
||||
public:
|
||||
DevFilaSwitch(MachineObject* owner);
|
||||
virtual ~DevFilaSwitch() = default;
|
||||
|
||||
public:
|
||||
bool IsInstalled() const { return m_is_installed; };
|
||||
|
||||
// Ready once installed and every AMS has resolved both its extruder binding and its
|
||||
// switcher track position from the device. The send dialog and sidebar sync UX only
|
||||
// treat the switch as usable when this is true.
|
||||
bool IsReady() const;
|
||||
|
||||
std::optional<bool> IsInA_HasFilament() const { return m_in_a_has_filament; };
|
||||
std::optional<bool> IsInB_HasFilament() const { return m_in_b_has_filament; };
|
||||
|
||||
std::optional<DevAmsTray> GetInA_Slot() const;
|
||||
std::optional<DevAmsTray> GetInB_Slot() const;
|
||||
std::optional<DevAmsSlotId> GetInA_SlotId() const { return m_in_a_slot; };
|
||||
std::optional<DevAmsSlotId> GetInB_SlotId() const { return m_in_b_slot; };
|
||||
|
||||
std::optional<DevExtder> GetOutA_Extruder() const;
|
||||
std::optional<DevExtder> GetOutB_Extruder() const;
|
||||
std::optional<int> GetOutA_ExtruderId() const { return m_out_a_extruder_id; };
|
||||
std::optional<int> GetOutB_ExtruderId() const { return m_out_b_extruder_id; };
|
||||
|
||||
CaliStatus GetCaliStatus() const { return m_cali_status; };
|
||||
|
||||
void Reset();
|
||||
void ParseFilaSwitchInfo(const nlohmann::json& print_jj);
|
||||
|
||||
private:
|
||||
MachineObject* m_owner;
|
||||
|
||||
bool m_is_installed = false;
|
||||
|
||||
std::optional<bool> m_in_a_has_filament;
|
||||
std::optional<bool> m_in_b_has_filament;
|
||||
|
||||
std::optional<DevAmsSlotId> m_in_a_slot;
|
||||
std::optional<DevAmsSlotId> m_in_b_slot;
|
||||
|
||||
std::optional<int> m_out_a_extruder_id;
|
||||
std::optional<int> m_out_b_extruder_id;
|
||||
|
||||
CaliStatus m_cali_status = CaliStatus::CALI_IDLE;
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "DevFilaSystem.h"
|
||||
#include "DevNozzleSystem.h" // DevNozzle / DevNozzleSystem for GetNozzleFlowStringByAmsId
|
||||
|
||||
// TODO: remove this include
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
@@ -117,7 +118,7 @@ DevAms::DevAms(const std::string& ams_id, int nozzle_id, int type)
|
||||
m_ams_id = ams_id;
|
||||
m_ext_id = nozzle_id;
|
||||
m_ams_type = (AmsType)type;
|
||||
assert(EXT_SPOOL < type && m_ams_type <= N3S);
|
||||
assert(EXT_SPOOL < type && m_ams_type <= AMS_LITE_MIXED);
|
||||
}
|
||||
|
||||
DevAms::~DevAms()
|
||||
@@ -143,7 +144,8 @@ static unordered_map<int, wxString> s_ams_display_formats = {
|
||||
wxString DevAms::GetDisplayName() const
|
||||
{
|
||||
wxString ams_display_format;
|
||||
auto iter = s_ams_display_formats.find(m_ams_type);
|
||||
// GetAmsType() maps AMS_LITE_MIXED -> AMS_LITE so N9 shows the AMS-Lite name.
|
||||
auto iter = s_ams_display_formats.find(GetAmsType());
|
||||
if (iter != s_ams_display_formats.end())
|
||||
{
|
||||
ams_display_format = iter->second;
|
||||
@@ -172,11 +174,13 @@ wxString DevAms::GetDisplayName() const
|
||||
|
||||
int DevAms::GetSlotCount() const
|
||||
{
|
||||
if (m_ams_type == AMS || m_ams_type == AMS_LITE || m_ams_type == N3F)
|
||||
// GetAmsType() maps AMS_LITE_MIXED -> AMS_LITE, so N9 reports 4 slots like AMS-Lite.
|
||||
auto ams_type = GetAmsType();
|
||||
if (ams_type == AMS || ams_type == AMS_LITE || ams_type == N3F)
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
else if (m_ams_type == N3S)
|
||||
else if (ams_type == N3S)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
@@ -285,6 +289,44 @@ int DevFilaSystem::GetExtruderIdByAmsId(const std::string& ams_id) const
|
||||
return 0; // not found
|
||||
}
|
||||
|
||||
std::string DevFilaSystem::GetNozzleFlowStringByAmsId(const std::string& ams_id) const
|
||||
{
|
||||
auto extruder_id = GetExtruderIdByAmsId(ams_id);
|
||||
auto nozzle = GetOwner()->GetNozzleSystem()->GetExtNozzle(extruder_id);
|
||||
return DevNozzle::GetNozzleFlowTypeString(nozzle.GetNozzleFlowType());
|
||||
}
|
||||
|
||||
std::map<int, DevAmsSlotId> DevFilaSystem::GetTrayIndexMap()
|
||||
{
|
||||
std::map<int, DevAmsSlotId> tray_id_map;
|
||||
tray_id_map[VIRTUAL_TRAY_MAIN_ID] = DevAmsSlotId{VIRTUAL_TRAY_MAIN_ID, 0};
|
||||
tray_id_map[VIRTUAL_TRAY_DEPUTY_ID] = DevAmsSlotId{VIRTUAL_TRAY_DEPUTY_ID, 0};
|
||||
|
||||
for (auto& [ams_id, ams_item] : GetAmsList()) {
|
||||
for (auto &[slot_id, slot_item] : ams_item->GetTrays()) {
|
||||
if (ams_item && slot_item) {
|
||||
try {
|
||||
int ams_id_int = stoi(ams_id);
|
||||
int slot_id_int = stoi(slot_id);
|
||||
int tray_index = -1;
|
||||
if (ams_item->GetAmsType() == DevAms::N3S) {
|
||||
tray_index = ams_id_int;
|
||||
} else if(ams_item->GetAmsType() == DevAms::AMS_LITE && ams_item->IsAmsLiteMixed()) {
|
||||
tray_index = 24 + slot_id_int;
|
||||
} else {
|
||||
tray_index = (ams_id_int * 4 + slot_id_int);
|
||||
}
|
||||
tray_id_map[tray_index] = {ams_id_int, slot_id_int};
|
||||
} catch(...) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "invalid ams id: " << ams_id << " or slot id: " << slot_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tray_id_map;
|
||||
}
|
||||
|
||||
bool DevFilaSystem::IsAmsSettingUp() const
|
||||
{
|
||||
int setting_up_stat = DevUtil::get_flag_bits(m_ams_cali_stat, 0, 8);
|
||||
@@ -331,6 +373,15 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
|
||||
if (!key_field_only)
|
||||
{
|
||||
// Newer firmware sends the exact filament-change step sequence in ams.cfs, so the
|
||||
// client no longer hardcodes the steps per model. Absent on current firmware, which
|
||||
// keeps the list empty and the legacy hardcoded/ams_status_sub step path in effect.
|
||||
if (jj["ams"].contains("cfs")) {
|
||||
system->m_filament_change_steps = DevJsonValParser::GetVal<std::vector<DevFilamentStep>>(jj["ams"], "cfs");
|
||||
} else {
|
||||
system->m_filament_change_steps.clear();
|
||||
}
|
||||
|
||||
if (jj["ams"].contains("tray_read_done_bits"))
|
||||
{
|
||||
obj->tray_read_done_bits = stol(jj["ams"]["tray_read_done_bits"].get<std::string>(), nullptr, 16);
|
||||
@@ -388,17 +439,43 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
int type_id = 1; // 0:dummy 1:ams 2:ams-lite 3:n3f 4:n3s
|
||||
|
||||
/*ams info*/
|
||||
std::set<int> binded_extruder_set;
|
||||
std::optional<DevFilaSwitch::SwitchPos> binded_switcher_pos;
|
||||
if (it->contains("info")) {
|
||||
const std::string& info = (*it)["info"].get<std::string>();
|
||||
type_id = DevUtil::get_flag_bits(info, 0, 4);
|
||||
extuder_id = DevUtil::get_flag_bits(info, 8, 4);
|
||||
if (extuder_id == 0xE && obj->GetFilaSwitch()->IsInstalled()) {
|
||||
int bind_switch_in = DevUtil::get_flag_bits(info, 24, 4);
|
||||
if (bind_switch_in == 0 || bind_switch_in == 1) {
|
||||
binded_extruder_set = { MAIN_EXTRUDER_ID, DEPUTY_EXTRUDER_ID };
|
||||
}
|
||||
|
||||
if (bind_switch_in == 0) {
|
||||
binded_switcher_pos = DevFilaSwitch::SwitchPos::POS_IN_B;
|
||||
} else if (bind_switch_in == 1) {
|
||||
binded_switcher_pos = DevFilaSwitch::SwitchPos::POS_IN_A;
|
||||
}
|
||||
|
||||
// Orca: the switch feeds every AMS to both extruders; pin a deterministic
|
||||
// single-extruder id so legacy GetExtruderId() consumers keep a valid value
|
||||
// while switch-aware code reads the binding set instead.
|
||||
extuder_id = MAIN_EXTRUDER_ID;
|
||||
} else if (extuder_id != 0xE) {
|
||||
binded_extruder_set = { extuder_id };
|
||||
}
|
||||
} else {
|
||||
if (!obj->is_enable_ams_np && obj->get_printer_ams_type() == "f1") {
|
||||
type_id = DevAms::AMS_LITE;
|
||||
}
|
||||
binded_extruder_set = { MAIN_EXTRUDER_ID };
|
||||
}
|
||||
|
||||
/*AMS without initialization*/
|
||||
// Orca: an AMS reporting extruder 0xE without a Filament Track Switch installed has
|
||||
// no usable extruder binding; drop it, preserving the existing display for
|
||||
// half-initialized printers. With the switch installed the 0xE case is remapped
|
||||
// above to both extruders and falls through to normal handling.
|
||||
if (extuder_id == 0xE)
|
||||
{
|
||||
ams_id_set.erase(ams_id);
|
||||
@@ -430,6 +507,13 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
/*set ams type flag*/
|
||||
curr_ams->SetAmsType(type_id);
|
||||
|
||||
// Refresh the switch-aware extruder binding on every push (both create and
|
||||
// update paths) so it can't go stale after an AMS is re-homed to another
|
||||
// extruder. Without the switch the set holds the single bound extruder and
|
||||
// the track position stays empty.
|
||||
curr_ams->m_binded_extruder_set = binded_extruder_set;
|
||||
curr_ams->m_binded_switcher_pos = binded_switcher_pos;
|
||||
|
||||
|
||||
/*set ams exist flag*/
|
||||
try
|
||||
@@ -442,6 +526,11 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
{
|
||||
curr_ams->m_exist = (obj->ams_exist_bits & (1 << ams_id_int)) != 0 ? true : false;
|
||||
}
|
||||
else if (type_id == DevAms::AMS_LITE_MIXED)
|
||||
{
|
||||
// Mixed AMS-Lite (A2L / N9) exist flag lives at bit 12.
|
||||
curr_ams->m_exist = DevUtil::get_flag_bits(obj->ams_exist_bits, 12);
|
||||
}
|
||||
else
|
||||
{
|
||||
curr_ams->m_exist = DevUtil::get_flag_bits(obj->ams_exist_bits, 4 + (ams_id_int - 128));
|
||||
@@ -679,6 +768,11 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
{
|
||||
curr_tray->is_exists = (obj->tray_exist_bits & (1 << (ams_id_int * 4 + tray_id_int))) != 0 ? true : false;
|
||||
}
|
||||
else if (type_id == DevAms::AMS_LITE_MIXED)
|
||||
{
|
||||
// Mixed AMS-Lite (A2L / N9) trays occupy tray-exist bits 24..27.
|
||||
curr_tray->is_exists = DevUtil::get_flag_bits(obj->tray_exist_bits, AMS_LITE_MIXED_TRAY_INDEX_OFFSET + tray_id_int);
|
||||
}
|
||||
else
|
||||
{
|
||||
curr_tray->is_exists = DevUtil::get_flag_bits(obj->tray_exist_bits, 16 + (ams_id_int - 128));
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
|
||||
#include "DevDefs.h"
|
||||
#include "DevFilaAmsSetting.h"
|
||||
#include "DevFilaSwitch.h"
|
||||
#include "DevUtil.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
@@ -142,6 +145,7 @@ public:
|
||||
static constexpr AmsType AMS_LITE = DevAmsType::AMS_LITE;
|
||||
static constexpr AmsType N3F = DevAmsType::N3F;
|
||||
static constexpr AmsType N3S = DevAmsType::N3S;
|
||||
static constexpr AmsType AMS_LITE_MIXED = DevAmsType::AMS_LITE_MIXED;
|
||||
|
||||
public:
|
||||
|
||||
@@ -209,11 +213,16 @@ public:
|
||||
|
||||
void SetAmsType(int type) { m_ams_type = (AmsType)type; }
|
||||
void SetAmsType(AmsType type) { m_ams_type = type; }
|
||||
AmsType GetAmsType() const { return m_ams_type; }
|
||||
// AMS-Lite-mixed (N9) is a variant of AMS-Lite: report AMS_LITE to generic callers so all
|
||||
// existing AMS_LITE handling applies. Mixed-specific code uses IsAmsLiteMixed().
|
||||
AmsType GetAmsType() const { return m_ams_type == AMS_LITE_MIXED ? AMS_LITE : m_ams_type; }
|
||||
|
||||
// exist or not
|
||||
bool IsExist() const { return m_exist; }
|
||||
|
||||
// AMS-Lite-mixed for N9 (A2L)
|
||||
bool IsAmsLiteMixed() const { return m_ams_type == AMS_LITE_MIXED; }
|
||||
|
||||
// slots
|
||||
int GetSlotCount() const;
|
||||
DevAmsTray* GetTray(const std::string& tray_id) const;
|
||||
@@ -222,6 +231,20 @@ public:
|
||||
// installed on the extruder
|
||||
int GetExtruderId() const { return m_ext_id; }
|
||||
|
||||
// Extruders this AMS can currently feed. Without a Filament Track Switch installed this
|
||||
// is the single {m_ext_id}; with the switch installed the device binds it to both extruders.
|
||||
const std::set<int>& GetBindedExtruderSet() const { return m_binded_extruder_set; }
|
||||
|
||||
// The bound extruder when exactly one is bound, empty otherwise. Used to attribute an AMS
|
||||
// to a single extruder on printers without the switch.
|
||||
std::optional<int> GetUniqueBindedExtruderId() const
|
||||
{
|
||||
return m_binded_extruder_set.size() == 1 ? std::optional<int>(*m_binded_extruder_set.begin()) : std::nullopt;
|
||||
}
|
||||
|
||||
// Which switch input track (A/B) feeds this AMS, set only when a Filament Track Switch is installed.
|
||||
std::optional<DevFilaSwitch::SwitchPos> GetSwitcherPos() const { return m_binded_switcher_pos; }
|
||||
|
||||
// temperature and humidity
|
||||
float GetCurrentTemperature() const { return m_current_temperature; }
|
||||
|
||||
@@ -247,6 +270,12 @@ private:
|
||||
AmsType m_ams_type = AmsType::AMS;
|
||||
std::string m_ams_id;
|
||||
int m_ext_id;//extruder id
|
||||
// Orca: keeps the legacy single m_ext_id for existing consumers and carries the
|
||||
// switch-aware binding alongside it (BambuStudio stores only the set). Without a Filament
|
||||
// Track Switch the set is {m_ext_id}; with one installed the device binds both extruders and
|
||||
// records which input track (A/B) feeds this AMS.
|
||||
std::set<int> m_binded_extruder_set;
|
||||
std::optional<DevFilaSwitch::SwitchPos> m_binded_switcher_pos;
|
||||
bool m_exist = false;
|
||||
|
||||
// slots and trays
|
||||
@@ -319,9 +348,16 @@ public:
|
||||
DevAmsTray* GetAmsTray(const std::string& ams_id, const std::string& tray_id) const;
|
||||
void CollectAmsColors(std::vector<wxColour>& ams_colors) const;
|
||||
|
||||
// Map a linear tray index -> {ams_id, slot_id}. Includes the two virtual (external-spool) trays,
|
||||
// N3S single-slot units, and the A2L/N9 AMS-Lite-mixed layout (trays 24-27).
|
||||
std::map<int, DevAmsSlotId> GetTrayIndexMap();
|
||||
|
||||
// extruder
|
||||
int GetExtruderIdByAmsId(const std::string& ams_id) const;
|
||||
|
||||
// nozzle: untranslated flow-type string of the extruder bound to this ams (for blacklist matching)
|
||||
std::string GetNozzleFlowStringByAmsId(const std::string& ams_id) const;
|
||||
|
||||
/* AMS settings*/
|
||||
DevAmsSystemSetting& GetAmsSystemSetting() { return m_ams_system_setting; }
|
||||
std::optional<bool> IsDetectOnInsertEnabled() const { return m_ams_system_setting.IsDetectOnInsertEnabled(); };
|
||||
@@ -331,6 +367,11 @@ public:
|
||||
|
||||
std::weak_ptr<DevAmsSystemFirmwareSwitch> GetAmsFirmwareSwitch() const { return m_ams_firmware_switch;}
|
||||
|
||||
// filament change steps
|
||||
// The exact change-step sequence reported by the AMS (ams.cfs). Empty on current firmware,
|
||||
// which leaves the legacy hardcoded/ams_status_sub step handling in effect.
|
||||
const std::vector<DevFilamentStep>& GetFilamentChangeSteps() const { return m_filament_change_steps; }
|
||||
|
||||
public:
|
||||
// ctrls
|
||||
int CtrlAmsReset() const;
|
||||
@@ -348,6 +389,9 @@ private:
|
||||
/* ams properties */
|
||||
int m_ams_cali_stat = 0;
|
||||
|
||||
// Change-step sequence reported by the AMS (ams.cfs); empty when firmware doesn't send it.
|
||||
std::vector<DevFilamentStep> m_filament_change_steps;
|
||||
|
||||
std::map<std::string, DevAms*, NumericStrCompare> amsList;// key: ams[id], start with 0
|
||||
|
||||
DevAmsSystemSetting m_ams_system_setting{ this };
|
||||
|
||||
@@ -44,6 +44,8 @@ public:
|
||||
bool isLaszer() const { return product_name.Contains("Laser"); }
|
||||
bool isCuttingModule() const { return product_name.Contains("Cutting Module"); }
|
||||
bool isExtinguishSystem() const { return product_name.Contains("Extinguishing System"); }
|
||||
bool isWTM() const { return name.find("wtm") != std::string::npos; } // nozzle (rack hotend) module firmware
|
||||
bool isFilaTrackSwitch() const { return product_name.Contains("Filament Track"); }
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -95,6 +95,10 @@ namespace Slic3r
|
||||
{
|
||||
result.id = ams_id + slot_id;
|
||||
}
|
||||
else if (type == DevAms::AMS_LITE_MIXED)
|
||||
{
|
||||
result.id = AMS_LITE_MIXED_TRAY_INDEX_OFFSET + slot_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.id = ams_id * 4 + slot_id;
|
||||
@@ -117,6 +121,11 @@ namespace Slic3r
|
||||
{
|
||||
std::string ams_id = ams->second->GetAmsId();
|
||||
auto ams_type = ams->second->GetAmsType();
|
||||
// GetAmsType() maps mixed -> AMS_LITE; recover the mixed type so N9 trays index at 24+slot.
|
||||
if (ams_type == DevAms::AMS_LITE && ams->second->IsAmsLiteMixed())
|
||||
{
|
||||
ams_type = DevAms::AMS_LITE_MIXED;
|
||||
}
|
||||
for (auto tray = ams->second->GetTrays().begin(); tray != ams->second->GetTrays().end(); tray++)
|
||||
{
|
||||
int ams_id = atoi(ams->first.c_str());
|
||||
@@ -126,6 +135,10 @@ namespace Slic3r
|
||||
{
|
||||
tray_index = ams_id * 4 + tray_id;
|
||||
}
|
||||
else if (ams_type == DevAms::AMS_LITE_MIXED)
|
||||
{
|
||||
tray_index = AMS_LITE_MIXED_TRAY_INDEX_OFFSET + tray_id;
|
||||
}
|
||||
else if (ams_type == DevAms::N3S)
|
||||
{
|
||||
tray_index = ams_id + tray_id;
|
||||
|
||||
@@ -0,0 +1,485 @@
|
||||
#include "DevMapping.h"
|
||||
#include "DevMappingNozzle.h"
|
||||
|
||||
#include "DevNozzleRack.h"
|
||||
#include "DevNozzleSystem.h"
|
||||
|
||||
#include "DevUtil.h"
|
||||
#include "DevUtilBackend.h"
|
||||
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/BackgroundSlicingProcess.hpp"
|
||||
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
using namespace nlohmann;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void MachineObject::clear_auto_nozzle_mapping()
|
||||
{
|
||||
if (m_nozzle_mapping_ptr) {
|
||||
m_nozzle_mapping_ptr->Clear();
|
||||
}
|
||||
}
|
||||
|
||||
static std::string s_get_diameter_str(float diameter)
|
||||
{
|
||||
return (boost::format("%.2f") % diameter).str();
|
||||
}
|
||||
|
||||
static std::string s_get_diameter_str(const std::string& diameter)
|
||||
{
|
||||
try {
|
||||
float dia = boost::lexical_cast<float>(diameter);
|
||||
return s_get_diameter_str(dia);
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to boost::lexical_cast: " << diameter;
|
||||
return diameter;
|
||||
}
|
||||
|
||||
try {
|
||||
float dia = std::stof(diameter);
|
||||
return s_get_diameter_str(dia);
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " std::stof: " << diameter;
|
||||
return diameter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// get auto nozzle mapping through AP
|
||||
// warnings:
|
||||
// (1) the fila_id here is 1 based index
|
||||
// (2) the AP wants the diameter string with 2 decimal places
|
||||
int DevNozzleMappingCtrl::CtrlGetAutoNozzleMappingV0(Slic3r::GUI::Plater* plater,
|
||||
const std::vector<FilamentInfo>& ams_mapping,
|
||||
int flow_cali_opt, int pa_value)
|
||||
{
|
||||
Clear();
|
||||
|
||||
m_plater = plater;
|
||||
if (!plater) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": plater is nullptr";
|
||||
return -1;
|
||||
}
|
||||
|
||||
GCodeProcessorResult* gcode_result = plater->background_process().get_current_gcode_result();
|
||||
if (!gcode_result) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result is nullptr";
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto& result = gcode_result->nozzle_group_result;
|
||||
if (!result) {
|
||||
assert(false && "gcode_result->nozzle_group_result");
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result->nozzle_group_result is NULL";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ams_mapping.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": the ams mapping is empty";
|
||||
return -1;// the ams mapping is empty
|
||||
}
|
||||
|
||||
json command_jj;
|
||||
command_jj["print"]["command"] = "get_auto_nozzle_mapping";
|
||||
m_sequence_id = std::to_string(MachineObject::m_sequence_id++);
|
||||
command_jj["print"]["sequence_id"] = m_sequence_id;
|
||||
command_jj["print"]["calibration"] = flow_cali_opt;
|
||||
command_jj["print"]["extrude_cali_manual_mode"] = pa_value;
|
||||
|
||||
// filament seq
|
||||
json filament_seq_jj;
|
||||
int max_fila_id = 0;
|
||||
std::unordered_set<int> filaid_set;
|
||||
for (int idx = 0; idx < gcode_result->filament_change_sequence.size(); idx++) {
|
||||
int fila_id = gcode_result->filament_change_sequence[idx];
|
||||
if (filaid_set.count(fila_id) == 0) {
|
||||
filaid_set.insert(fila_id);
|
||||
filament_seq_jj[fila_id + 1] = idx;
|
||||
max_fila_id = std::max(max_fila_id, fila_id + 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (int fila_id = 0; fila_id <= max_fila_id; fila_id++) {
|
||||
if (filament_seq_jj[fila_id].is_null()) {
|
||||
filament_seq_jj[fila_id] = -1;// fill the used fila_id with -1
|
||||
}
|
||||
}
|
||||
|
||||
command_jj["print"]["filament_seq"] = filament_seq_jj;
|
||||
|
||||
// ams mapping
|
||||
std::vector<int> ams_mapping_vec(33, 0xFFFF);/* AP ask to fill them*/
|
||||
for (auto item : ams_mapping) {
|
||||
try {
|
||||
int ams_id = stoi(item.ams_id);
|
||||
int slot_id = !item.slot_id.empty() ? stoi(item.slot_id) : 0;
|
||||
ams_mapping_vec[item.id + 1] = (ams_id << 8) | slot_id;/*using ams_id << 8 | slot_id*/
|
||||
} catch (std::exception& e) {
|
||||
assert(false && "invalid ams_id or slot_id");
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " exception: " << e.what();
|
||||
ams_mapping_vec[item.id + 1] = item.tray_id;
|
||||
}
|
||||
}
|
||||
command_jj["print"]["ams_mapping"] = ams_mapping_vec;
|
||||
|
||||
// filament info
|
||||
json filament_info_jj;
|
||||
for (int fila_id = 0; fila_id < ams_mapping.size(); fila_id++) {
|
||||
const auto& fila = ams_mapping[fila_id];
|
||||
const auto& nozzle_list = result->get_nozzles_for_filament(fila.id);
|
||||
if (nozzle_list.empty()) {
|
||||
assert(false && "nozzle_list should not be empty");
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "nozzle_list should not be nullptr";
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& nozzle_info : nozzle_list) {
|
||||
json fila_item_jj;
|
||||
fila_item_jj["id"] = (fila.id + 1);
|
||||
fila_item_jj["direction"] = (nozzle_info.extruder_id == LOGIC_L_EXTRUDER_ID ? 1 : 2);
|
||||
fila_item_jj["group"] = nozzle_info.group_id;
|
||||
fila_item_jj["nozzle_d"] = s_get_diameter_str(nozzle_info.diameter);
|
||||
fila_item_jj["nozzle_v"] = (nozzle_info.volume_type == NozzleVolumeType::nvtHighFlow) ? "High Flow" : "Standard";
|
||||
fila_item_jj["cate"] = fila.filament_id;
|
||||
fila_item_jj["color"] = fila.color;
|
||||
filament_info_jj.push_back(fila_item_jj);
|
||||
}
|
||||
}
|
||||
|
||||
command_jj["print"]["fila_info"] = filament_info_jj;
|
||||
|
||||
// nozzle info
|
||||
json nozzle_info_jj;
|
||||
const auto& nozzle_system = m_obj->GetNozzleSystem();
|
||||
const auto& extruder_nozzles = nozzle_system->GetExtNozzles();
|
||||
for (const auto& nozzle : extruder_nozzles) {
|
||||
if (nozzle.second.IsNormal()) {
|
||||
json nozzle_item_jj;
|
||||
nozzle_item_jj["pos"] = nozzle.second.GetNozzleId();
|
||||
if (nozzle_system->GetReplaceNozzleTar().has_value() && nozzle_item_jj["pos"] == MAIN_EXTRUDER_ID) {
|
||||
nozzle_item_jj["pos"] = nozzle_system->GetReplaceNozzleTar().value();// special case of tar_id. see protocol definition
|
||||
}
|
||||
|
||||
nozzle_item_jj["nozzle_d"] = s_get_diameter_str(nozzle.second.GetNozzleDiameter());
|
||||
nozzle_item_jj["nozzle_v"] = DevNozzle::ToNozzleFlowString(nozzle.second.GetNozzleFlowType());
|
||||
nozzle_item_jj["wear"] = nozzle.second.GetNozzleWear();
|
||||
nozzle_item_jj["cate"] = nozzle.second.GetFilamentId();
|
||||
nozzle_item_jj["color"] = nozzle.second.GetFilamentColor();
|
||||
nozzle_info_jj.push_back(nozzle_item_jj);
|
||||
}
|
||||
}
|
||||
|
||||
const auto& rack_nozzles = nozzle_system->GetNozzleRack()->GetRackNozzles();
|
||||
for (const auto& nozzle : rack_nozzles) {
|
||||
if (nozzle.second.IsNormal()) {
|
||||
json nozzle_item_jj;
|
||||
nozzle_item_jj["pos"] = (nozzle.second.GetNozzleId() + 0x10);
|
||||
if (nozzle_system->GetReplaceNozzleTar().has_value() && nozzle_item_jj["pos"] == MAIN_EXTRUDER_ID) {
|
||||
nozzle_item_jj["pos"] = nozzle_system->GetReplaceNozzleTar().value();// special case of tar_id. see protocol definition
|
||||
}
|
||||
|
||||
nozzle_item_jj["nozzle_d"] = s_get_diameter_str(nozzle.second.GetNozzleDiameter());
|
||||
nozzle_item_jj["nozzle_v"] = DevNozzle::ToNozzleFlowString(nozzle.second.GetNozzleFlowType());
|
||||
nozzle_item_jj["wear"] = nozzle.second.GetNozzleWear();
|
||||
nozzle_item_jj["cate"] = nozzle.second.GetFilamentId();
|
||||
nozzle_item_jj["color"] = nozzle.second.GetFilamentColor();
|
||||
nozzle_info_jj.push_back(nozzle_item_jj);
|
||||
}
|
||||
}
|
||||
|
||||
command_jj["print"]["nozzle_info"] = nozzle_info_jj;
|
||||
m_req_version = NozzleMappingVersion::Version_V0;
|
||||
return m_obj->publish_json(command_jj);
|
||||
}
|
||||
|
||||
int DevNozzleMappingCtrl::CtrlGetAutoNozzleMappingV1(Slic3r::GUI::Plater* plater)
|
||||
{
|
||||
Clear();
|
||||
m_plater = plater;
|
||||
if (!m_plater) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater);
|
||||
if (!nozzle_group_res) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result->nozzle_group_result is NULL";
|
||||
return -1;
|
||||
}
|
||||
|
||||
json command_jj;
|
||||
command_jj["print"]["command"] = "get_auto_nozzle_mapping";
|
||||
m_sequence_id = std::to_string(MachineObject::m_sequence_id++);
|
||||
command_jj["print"]["sequence_id"] = m_sequence_id;
|
||||
|
||||
json nozzle_group_info_jj;
|
||||
std::unordered_set<int> used_logic_groups;
|
||||
const auto& used_logic_nozzles = nozzle_group_res->get_used_nozzles_in_extruder();
|
||||
for (const auto& used_logic_nozzle : used_logic_nozzles) {
|
||||
if (used_logic_groups.count(used_logic_nozzle.group_id) != 0) {
|
||||
continue;
|
||||
}
|
||||
used_logic_groups.insert(used_logic_nozzle.group_id);
|
||||
|
||||
json nozzle_info;
|
||||
nozzle_info["id"] = used_logic_nozzle.group_id;
|
||||
nozzle_info["ext"] = (used_logic_nozzle.extruder_id + 1);
|
||||
|
||||
try {
|
||||
nozzle_info["dia"] = std::stof(used_logic_nozzle.diameter);
|
||||
} catch(const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": e=" << e.what();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (used_logic_nozzle.volume_type == NozzleVolumeType::nvtHighFlow) {
|
||||
nozzle_info["vol"] = "High Flow";
|
||||
} else if(used_logic_nozzle.volume_type == NozzleVolumeType::nvtStandard){
|
||||
nozzle_info["vol"] = "Standard";
|
||||
} else if(used_logic_nozzle.volume_type == NozzleVolumeType::nvtTPUHighFlow){
|
||||
nozzle_info["vol"] = "TPU Flow";
|
||||
} else {
|
||||
assert(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
nozzle_group_info_jj.push_back(nozzle_info);
|
||||
}
|
||||
command_jj["print"]["group_info"] = nozzle_group_info_jj;
|
||||
|
||||
|
||||
m_req_version = NozzleMappingVersion::Version_V1;
|
||||
command_jj["print"]["version"] = (int)m_req_version;
|
||||
return m_obj->publish_json(command_jj);
|
||||
}
|
||||
|
||||
|
||||
void DevNozzleMappingCtrl::ParseAutoNozzleMapping(const json& print_jj)
|
||||
{
|
||||
if (print_jj.contains("command") && print_jj["command"].get<std::string>() == "get_auto_nozzle_mapping") {
|
||||
if (print_jj.contains("sequence_id") && print_jj["sequence_id"] == m_sequence_id) {
|
||||
Clear();
|
||||
DevJsonValParser::ParseVal(print_jj, "result", m_result);
|
||||
DevJsonValParser::ParseVal(print_jj, "reason", m_mqtt_reason);
|
||||
DevJsonValParser::ParseVal(print_jj, "errno", m_errno);
|
||||
DevJsonValParser::ParseVal(print_jj, "detail", m_detail_json);
|
||||
DevJsonValParser::ParseVal(print_jj, "type", m_type);
|
||||
DevJsonValParser::ParseVal(print_jj, "version", m_ack_version);
|
||||
|
||||
if (print_jj.contains("mapping")) {
|
||||
m_nozzle_mapping_json = print_jj["mapping"];
|
||||
const auto& mapping = print_jj["mapping"].get<std::vector<int>>();
|
||||
for (int fila_id = 0; fila_id < mapping.size(); ++fila_id) {
|
||||
m_nozzle_mapping[fila_id] = mapping[fila_id];
|
||||
}
|
||||
}
|
||||
|
||||
m_flush_weight_base = GetFlushWeight(m_obj);
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": get_auto_nozzle_mapping: " << m_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DevNozzleMappingCtrl::Clear()
|
||||
{
|
||||
m_sequence_id.clear();
|
||||
m_result.clear();
|
||||
m_mqtt_reason.clear();
|
||||
m_type.clear();
|
||||
m_errno = 0;
|
||||
m_detail_msg.clear();
|
||||
m_detail_json.clear();
|
||||
m_nozzle_mapping.clear();
|
||||
m_nozzle_mapping_json.clear();
|
||||
|
||||
m_flush_weight_base = -1;
|
||||
m_flush_weight_current = -1;
|
||||
m_ack_version = NozzleMappingVersion::Version_V0;
|
||||
}
|
||||
|
||||
|
||||
void DevNozzleMappingCtrl::SetManualNozzleMappingByFila(int fila_id, int nozzle_pos_id)
|
||||
{
|
||||
if (nozzle_pos_id == MAIN_EXTRUDER_ID && m_obj->GetNozzleSystem()->GetReplaceNozzleTar().has_value()){
|
||||
nozzle_pos_id = m_obj->GetNozzleSystem()->GetReplaceNozzleTar().value();// special case of tar_id. see protocol definition
|
||||
}
|
||||
|
||||
if (m_nozzle_mapping[fila_id] != nozzle_pos_id) {
|
||||
m_nozzle_mapping[fila_id] = nozzle_pos_id;
|
||||
m_flush_weight_current = GetFlushWeight(m_obj);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": fila_id=" << fila_id << ", nozzle_pos_id=" << nozzle_pos_id;
|
||||
}
|
||||
|
||||
try {
|
||||
auto mapping = m_nozzle_mapping_json.get<std::vector<int>>();
|
||||
if (mapping.at(fila_id) != nozzle_pos_id) {
|
||||
mapping[fila_id] = nozzle_pos_id;
|
||||
m_nozzle_mapping_json = mapping;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": updated to " << m_nozzle_mapping_json.dump();
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": exception: " << e.what();
|
||||
};
|
||||
}
|
||||
|
||||
static std::unordered_set<int> sGetLogicExtruders(const std::vector<MultiNozzleUtils::NozzleInfo>& nozzle_infos)
|
||||
{
|
||||
std::unordered_set<int> extruder_ids;
|
||||
for (const auto& nozzle : nozzle_infos) {
|
||||
if (nozzle.extruder_id >= 0) {
|
||||
extruder_ids.insert(nozzle.extruder_id);
|
||||
}
|
||||
}
|
||||
|
||||
return extruder_ids;
|
||||
};
|
||||
|
||||
|
||||
std::vector<int> DevNozzleMappingCtrl::GetMappedNozzlePosVecByFilaId(int fila_id) const
|
||||
{
|
||||
std::vector<int> physic_nozzle_pos_vec;
|
||||
auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater);
|
||||
if (!nozzle_group_res) {
|
||||
return physic_nozzle_pos_vec;
|
||||
}
|
||||
|
||||
const auto& used_logic_nozzles = nozzle_group_res->get_nozzles_for_filament(fila_id);
|
||||
if (m_ack_version == NozzleMappingVersion::Version_V0) {
|
||||
if (auto iter = m_nozzle_mapping.find(fila_id); iter != m_nozzle_mapping.end()) {
|
||||
if (m_obj->GetNozzleSystem()->GetReplaceNozzleTar().has_value() &&
|
||||
iter->second == m_obj->GetNozzleSystem()->GetReplaceNozzleTar().value()) {
|
||||
physic_nozzle_pos_vec.push_back(MAIN_EXTRUDER_ID);
|
||||
} else {
|
||||
physic_nozzle_pos_vec.push_back(iter->second);
|
||||
}
|
||||
} else {
|
||||
const auto& used_logic_extruders = sGetLogicExtruders(used_logic_nozzles);
|
||||
if (used_logic_extruders.size() == 1 && *used_logic_extruders.begin() == LOGIC_L_EXTRUDER_ID) {
|
||||
physic_nozzle_pos_vec.push_back(1);
|
||||
return physic_nozzle_pos_vec; // only left extruder is used
|
||||
}
|
||||
}
|
||||
} else if (m_ack_version == NozzleMappingVersion::Version_V1) {
|
||||
for (const auto& used_logic_nozzle : used_logic_nozzles) {
|
||||
if (auto iter = m_nozzle_mapping.find(used_logic_nozzle.group_id); iter != m_nozzle_mapping.end()) {
|
||||
if (m_obj->GetNozzleSystem()->GetReplaceNozzleTar().has_value() &&
|
||||
iter->second == m_obj->GetNozzleSystem()->GetReplaceNozzleTar().value()) {
|
||||
physic_nozzle_pos_vec.push_back(MAIN_EXTRUDER_ID);
|
||||
} else {
|
||||
physic_nozzle_pos_vec.push_back(iter->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return physic_nozzle_pos_vec;
|
||||
}
|
||||
|
||||
wxString DevNozzleMappingCtrl::GetMappedNozzlePosStrByFilaId(int fila_id, const wxString& default_str) const
|
||||
{
|
||||
wxString display_str;
|
||||
const auto& physic_nozzle_pos_vec = GetMappedNozzlePosVecByFilaId(fila_id);
|
||||
for (int idx = 0; idx < physic_nozzle_pos_vec.size(); idx++) {
|
||||
int pos_id = physic_nozzle_pos_vec[idx];
|
||||
if (pos_id == MAIN_EXTRUDER_ID) {
|
||||
display_str += "R";
|
||||
} else if (pos_id == DEPUTY_EXTRUDER_ID) {
|
||||
display_str += "L";
|
||||
} else if (pos_id >= 0x10) {
|
||||
display_str += wxString::Format("R%d", pos_id - 0x10 + 1);// display 1~n for rack hotends
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (idx < physic_nozzle_pos_vec.size() - 1) {
|
||||
display_str += " ";
|
||||
}
|
||||
}
|
||||
|
||||
return !display_str.empty() ? display_str : default_str;
|
||||
}
|
||||
|
||||
// flush weight for multi-nozzle printers: total flush volume across all filament changes
|
||||
float DevNozzleMappingCtrl::GetFlushWeight(Slic3r::MachineObject* obj) const
|
||||
{
|
||||
auto plater = Slic3r::GUI::wxGetApp().plater();
|
||||
if (!plater) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": plater is nullptr";
|
||||
return -1;
|
||||
}
|
||||
|
||||
GCodeProcessorResult* gcode_result = plater->background_process().get_current_gcode_result();
|
||||
if (!gcode_result) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result is nullptr";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (gcode_result->filament_change_sequence.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": filament_change_sequence is empty";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!Slic3r::GUI::wxGetApp().preset_bundle) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": preset_bundle is nullptr";
|
||||
return -1;
|
||||
};
|
||||
|
||||
const std::vector<std::vector<std::vector<float>>>& flush_matrix = Slic3r::GUI::wxGetApp().preset_bundle->get_full_flush_matrix();
|
||||
|
||||
float total_flush_volume = 0;
|
||||
MultiNozzleUtils::NozzleStatusRecorder recorder;
|
||||
for (auto filament : gcode_result->filament_change_sequence) {
|
||||
const auto& nozzle_pos_vec = GetMappedNozzlePosVecByFilaId(filament);
|
||||
if (nozzle_pos_vec.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nozzle_pos = nozzle_pos_vec.at(0);
|
||||
if (nozzle_pos == -1){
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": nozzle_pos is -1 for fila_id=" << filament;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nozzle_info = obj->GetNozzleSystem()->GetNozzleByPosId(nozzle_pos);
|
||||
if (nozzle_info.IsEmpty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": nozzle_info IsEmpty for fila_id=" << filament << ", nozzle_pos=" << nozzle_pos;
|
||||
continue;
|
||||
}
|
||||
|
||||
int extruder_id = nozzle_info.GetLogicExtruderId();
|
||||
int nozzle_id = nozzle_info.GetNozzlePosId();
|
||||
int last_filament = recorder.get_filament_in_nozzle(nozzle_id);
|
||||
|
||||
if (last_filament != -1 && last_filament != filament) {
|
||||
if (flush_matrix.size() > extruder_id &&
|
||||
flush_matrix[extruder_id].size() > last_filament &&
|
||||
flush_matrix[extruder_id][last_filament].size() > filament){
|
||||
total_flush_volume += flush_matrix[extruder_id][last_filament][filament];
|
||||
}
|
||||
else{
|
||||
assert(false && "missing flush volume");
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": missing flush volume for extruder_id=" << extruder_id
|
||||
<< ", last_filament=" << last_filament << ", filament=" << filament;
|
||||
}
|
||||
}
|
||||
|
||||
recorder.set_nozzle_status(nozzle_id, filament);
|
||||
}
|
||||
|
||||
// estimate the flush weight: total_flush_volume * 1.26 * 0.001
|
||||
return total_flush_volume * 1.26 * 0.001;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @file DevMappingNozzle.h
|
||||
* @brief Support nozzle mapping for hotend rack
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
namespace GUI
|
||||
{
|
||||
class Plater;
|
||||
}
|
||||
|
||||
struct FilamentInfo; // libslic3r/ProjectTask.hpp
|
||||
|
||||
class MachineObject;
|
||||
class DevNozzleMappingCtrl
|
||||
{
|
||||
friend class MachineObject;
|
||||
public:
|
||||
enum class NozzleMappingVersion : int {
|
||||
Version_V0 = 0, // V0 means support logical fila_id -> physical nozzle_id mapping
|
||||
Version_V1 = 1, // V1 means support logical nozzle_id -> physical nozzle_id mapping
|
||||
};
|
||||
|
||||
enum class ErrNoV1 : int {
|
||||
Success = 0,
|
||||
ErrorBadRequest3mf = 1,
|
||||
ErrorBadRequestVersion = 2,
|
||||
ErrorBadRequestGroupId = 3,
|
||||
ErrorNoValidNozzles = 4,
|
||||
ErrorBadRequestReadFail = 5,
|
||||
ErrorRackCaliFail = 6,
|
||||
ErrorRackMoving = 7,
|
||||
ErrorRackFull = 8,
|
||||
WarningNotEnoughNozzles = 9
|
||||
};
|
||||
|
||||
public:
|
||||
DevNozzleMappingCtrl(MachineObject* obj) : m_obj(obj) {};
|
||||
|
||||
public:
|
||||
void SetPlater(Slic3r::GUI::Plater* plater) { m_plater = plater; }
|
||||
void Clear();
|
||||
|
||||
bool HasResult() const { return !m_result.empty(); }
|
||||
std::string GetResultStr() const { return m_result; }
|
||||
|
||||
// mqtt error info
|
||||
std::string GetMqttReason() const { return m_mqtt_reason; }
|
||||
|
||||
// command error info
|
||||
int GetErrno() const { return m_errno; }
|
||||
ErrNoV1 GetErrnoV1() const { return static_cast<ErrNoV1>(m_errno); }
|
||||
std::string GetDetailMsg() const { return m_detail_msg; }
|
||||
|
||||
// nozzle mapping
|
||||
std::unordered_map<int, int> GetNozzleMappingMap() const { return m_nozzle_mapping; }
|
||||
nlohmann::json GetNozzleMappingJson() const { return m_nozzle_mapping_json; }
|
||||
void SetManualNozzleMappingByFila(int fila_id, int nozzle_pos_id);
|
||||
|
||||
std::vector<int> GetMappedNozzlePosVecByFilaId(int fila_id) const;// return empy if not mapped
|
||||
wxString GetMappedNozzlePosStrByFilaId(int fila_id, const wxString& default_str = "?") const;
|
||||
|
||||
// flush weight
|
||||
float GetFlushWeightBase() const { return m_flush_weight_base; }
|
||||
float GetFlushWeightCurrent() const { return m_flush_weight_current; }
|
||||
|
||||
public:
|
||||
void ParseAutoNozzleMapping(const nlohmann::json& print_jj);
|
||||
int CtrlGetAutoNozzleMappingV0(Slic3r::GUI::Plater* plater, const std::vector<FilamentInfo>& ams_mapping, int flow_cali_opt, int pa_value);
|
||||
int CtrlGetAutoNozzleMappingV1(Slic3r::GUI::Plater* plater);
|
||||
|
||||
private:
|
||||
float GetFlushWeight(Slic3r::MachineObject* obj) const;
|
||||
|
||||
private:
|
||||
MachineObject* m_obj;
|
||||
Slic3r::GUI::Plater* m_plater = nullptr;
|
||||
|
||||
std::string m_sequence_id;
|
||||
|
||||
std::string m_result;
|
||||
std::string m_mqtt_reason;
|
||||
std::string m_type; // auto or manual
|
||||
|
||||
int m_errno;
|
||||
std::string m_detail_msg;
|
||||
nlohmann::json m_detail_json;
|
||||
|
||||
nlohmann::json m_nozzle_mapping_json;
|
||||
std::unordered_map<int, int> m_nozzle_mapping; // v0: fila_id -> physical_nozzle_id, v1: logical_nozzle_id -> physical_nozzle_id
|
||||
|
||||
float m_flush_weight_base = -1;// the base weight for flush
|
||||
float m_flush_weight_current = -1;// the weight current
|
||||
|
||||
NozzleMappingVersion m_req_version = NozzleMappingVersion::Version_V0;
|
||||
NozzleMappingVersion m_ack_version = NozzleMappingVersion::Version_V0;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#include "DevNozzleRack.h"
|
||||
#include "DevUtil.h"
|
||||
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
|
||||
wxDEFINE_EVENT(DEV_RACK_EVENT_READING_FINISHED, wxCommandEvent);
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
DevNozzleRack::DevNozzleRack(DevNozzleSystem* nozzle_system)
|
||||
: wxEvtHandler(), m_nozzle_system(nozzle_system)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void DevNozzleRack::Reset()
|
||||
{
|
||||
m_position = RACK_POS_UNKNOWN;
|
||||
m_status = RACK_STATUS_UNKNOWN;
|
||||
m_rack_nozzles.clear();
|
||||
}
|
||||
|
||||
void DevNozzleRack::SendReadingFinished()
|
||||
{
|
||||
wxCommandEvent evt(DEV_RACK_EVENT_READING_FINISHED);
|
||||
evt.SetEventObject(this);
|
||||
wxPostEvent(this, evt);
|
||||
}
|
||||
|
||||
DevNozzle DevNozzleRack::GetNozzle(int idx) const
|
||||
{
|
||||
auto iter = m_rack_nozzles.find(idx);
|
||||
if (iter == m_rack_nozzles.end()) {
|
||||
DevNozzle nozzle;
|
||||
nozzle.SetOnRack(true);
|
||||
return nozzle;
|
||||
}
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
DevFirmwareVersionInfo DevNozzleRack::GetNozzleFirmwareInfo(int nozzle_id) const
|
||||
{
|
||||
auto iter = m_rack_nozzles_firmware.find(nozzle_id);
|
||||
return iter != m_rack_nozzles_firmware.end() ? iter->second : DevFirmwareVersionInfo();
|
||||
}
|
||||
|
||||
|
||||
bool DevNozzleRack::HasUnreliableNozzles() const
|
||||
{
|
||||
for (const auto& nozzle : m_rack_nozzles)
|
||||
{
|
||||
if (!nozzle.second.IsInfoReliable())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DevNozzleRack::HasUnknownNozzles() const
|
||||
{
|
||||
for (const auto& nozzle : m_rack_nozzles)
|
||||
{
|
||||
if (nozzle.second.IsUnknown())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int DevNozzleRack::GetKnownNozzleCount() const
|
||||
{
|
||||
int count = 0;
|
||||
for (const auto& nozzle : m_rack_nozzles)
|
||||
{
|
||||
if (!nozzle.second.IsEmpty() && !nozzle.second.IsUnknown())
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void DevNozzleRack::ParseRackInfo(const nlohmann::json& rack_info)
|
||||
{
|
||||
ParseRackInfoV1_0(rack_info);
|
||||
}
|
||||
|
||||
void DevNozzleRack::ParseRackInfoV1_0(const nlohmann::json& rack_info)
|
||||
{
|
||||
DevJsonValParser::ParseVal(rack_info, "stat", m_status, RACK_STATUS_UNKNOWN);
|
||||
if (m_status < RACK_STATUS_UNKNOWN || m_status >= RACK_STATUS_END)
|
||||
{
|
||||
m_status = RACK_STATUS_UNKNOWN; // Reset to default if out of range
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Invalid rack status: " << m_status << ", reset";
|
||||
}
|
||||
|
||||
DevJsonValParser::ParseVal(rack_info, "pos", m_position, RACK_POS_UNKNOWN);
|
||||
if (m_position < RACK_POS_UNKNOWN || m_position >= RACK_POS_END)
|
||||
{
|
||||
m_position = RACK_POS_UNKNOWN; // Reset to default if out of range
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Invalid rack position: " << m_position << ", reset";
|
||||
}
|
||||
|
||||
DevJsonValParser::ParseVal(rack_info, "info", m_cali_status);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
#pragma once
|
||||
#include "libslic3r/CommonDefs.hpp"
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
#include "slic3r/Utils/json_diff.hpp"
|
||||
|
||||
#include "DevNozzleSystem.h"
|
||||
#include "DevFirmware.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
// wx
|
||||
#include <wx/string.h>
|
||||
#include <wx/event.h>
|
||||
|
||||
wxDECLARE_EVENT(DEV_RACK_EVENT_READING_FINISHED, wxCommandEvent);
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
// Previous definitions
|
||||
class DevNozzleSystem;
|
||||
|
||||
class DevNozzleRack: public wxEvtHandler
|
||||
{
|
||||
public:
|
||||
enum RackStatus : int
|
||||
{
|
||||
RACK_STATUS_UNKNOWN = -1,
|
||||
RACK_STATUS_IDLE = 0,
|
||||
RACK_STATUS_HOTEND_CENTRE = 1,
|
||||
RACK_STATUS_TOOLHEAD_CENTRE = 2,
|
||||
RACK_STATUS_CALIBRATE_HOTEND_RACK = 3,
|
||||
RACK_STATUS_CUT_MATERIAL = 4,
|
||||
RACK_STATUS_UNLOCK_HOTEND = 5,
|
||||
RACK_STATUS_LIFT_HOTEND_RACK = 6,
|
||||
RACK_STATUS_PLACE_HOTEND = 7,
|
||||
RACK_STATUS_PICK_HOTEND = 8,
|
||||
RACK_STATUS_LOCK_HOTEND = 9,
|
||||
RACK_STATUS_END,
|
||||
};
|
||||
|
||||
enum RackPos : int
|
||||
{
|
||||
RACK_POS_UNKNOWN = 0,
|
||||
RACK_POS_A_TOP = 1,
|
||||
RACK_POS_B_TOP = 2,
|
||||
RACK_POS_CENTRE = 3,
|
||||
RACK_POS_END,
|
||||
};
|
||||
|
||||
enum RackCaliStatus
|
||||
{
|
||||
Rack_CALI_UNKNOWN = -1,
|
||||
Rack_CALI_NOT = 0,
|
||||
Rack_CALI_OK = 1,
|
||||
};
|
||||
|
||||
public:
|
||||
DevNozzleRack(DevNozzleSystem* nozzle_system);
|
||||
~DevNozzleRack() = default;
|
||||
|
||||
public:
|
||||
// Is supported by the printer
|
||||
bool IsSupported() const { return m_is_supported; };
|
||||
void SetSupported(bool supported) { m_is_supported = supported; }
|
||||
|
||||
// getters
|
||||
DevNozzleSystem* GetNozzleSystem() const { return m_nozzle_system; }
|
||||
|
||||
RackPos GetPosition() const { return m_position; }
|
||||
RackStatus GetStatus() const { return m_status; }
|
||||
RackCaliStatus GetCaliStatus() const { return m_cali_status;}
|
||||
|
||||
DevNozzle GetNozzle(int idx) const;
|
||||
const std::map<int, DevNozzle>& GetRackNozzles() const { return m_rack_nozzles; }
|
||||
|
||||
// status
|
||||
bool HasUnreliableNozzles() const;
|
||||
bool HasUnknownNozzles() const;
|
||||
int GetKnownNozzleCount() const;
|
||||
|
||||
// refreshing
|
||||
int GetReadingIdx() const { return m_nozzle_system->GetReadingIdx(); }
|
||||
int GetReadingCount() const { return m_nozzle_system->GetReadingCount(); }
|
||||
void SendReadingFinished();
|
||||
|
||||
// firmware
|
||||
void AddNozzleFirmwareInfo(int nozzle_id, const DevFirmwareVersionInfo& info) { m_rack_nozzles_firmware[nozzle_id] = info; }
|
||||
void ClearNozzleFirmwareInfo() { m_rack_nozzles_firmware.clear(); }
|
||||
DevFirmwareVersionInfo GetNozzleFirmwareInfo(int nozzle_id) const;
|
||||
|
||||
// setters
|
||||
void Reset();
|
||||
void AddRackNozzle(DevNozzle& nozzle) { nozzle.SetOnRack(true); m_rack_nozzles[nozzle.m_nozzle_id] = nozzle; };
|
||||
void ClearRackNozzles() { m_rack_nozzles.clear(); }
|
||||
|
||||
public:
|
||||
void ParseRackInfo(const nlohmann::json& rack_info);
|
||||
|
||||
void CtrlRackPosMove(RackPos new_pos) const;
|
||||
void CtrlRackPosGoHome() const;
|
||||
|
||||
void CtrlRackConfirmNozzle(int rack_nozzle_id) const;
|
||||
void CtrlRackConfirmAll() const;
|
||||
|
||||
void CrtlRackReadNozzle(int rack_nozzle_id) const;
|
||||
void CtrlRackReadAll(bool gui_check = false) const;
|
||||
bool CtrlCanReadAll() const;
|
||||
|
||||
// the upgrade is not supported
|
||||
// the GUI interface is removed
|
||||
int CtrlRackUpgradeExtruderNozzle() const;
|
||||
int CtrlRackUpgradeRackNozzle(int rack_nozzle_id) const;;
|
||||
int CtrlRackUpgradeAll() const;;
|
||||
bool CtrlCanUpdateAll() const;
|
||||
|
||||
private:
|
||||
void ParseRackInfoV1_0(const nlohmann::json& rack_info);
|
||||
|
||||
int CtrlRackUpgrade(const std::string& module_str) const;
|
||||
|
||||
bool CheckRackMoveWarningDlg() const;
|
||||
|
||||
private:
|
||||
DevNozzleSystem* m_nozzle_system = nullptr;
|
||||
|
||||
bool m_is_supported = false; // Indicates if the nozzle rack is supported by the printer
|
||||
RackPos m_position = RACK_POS_UNKNOWN;
|
||||
RackStatus m_status = RACK_STATUS_UNKNOWN;
|
||||
RackCaliStatus m_cali_status = Rack_CALI_UNKNOWN;
|
||||
|
||||
std::map<int, DevNozzle> m_rack_nozzles; // Map of nozzle ID to DevNozzle objects
|
||||
std::map<int, DevFirmwareVersionInfo> m_rack_nozzles_firmware;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,283 @@
|
||||
#include "DevNozzleRack.h"
|
||||
#include "DevUtil.h"
|
||||
#include "DevExtruderSystem.h"
|
||||
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
#include <wx/thread.h>
|
||||
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
void DevNozzleRack::CtrlRackPosMove(RackPos new_pos) const
|
||||
{
|
||||
if (!CheckRackMoveWarningDlg())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int action_id = -1;
|
||||
if (new_pos == RACK_POS_A_TOP)
|
||||
{
|
||||
action_id = 1;
|
||||
}
|
||||
else if(new_pos == RACK_POS_B_TOP)
|
||||
{
|
||||
action_id = 2;
|
||||
}
|
||||
|
||||
if (action_id != -1)
|
||||
{
|
||||
json j;
|
||||
j["print"]["command"] = "nozzle_holder_ctrl";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
j["print"]["action"] = action_id;
|
||||
m_nozzle_system->GetOwner()->publish_json(j);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DevNozzleRack::CtrlRackPosGoHome() const
|
||||
{
|
||||
if (!CheckRackMoveWarningDlg())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
json j;
|
||||
j["print"]["command"] = "nozzle_holder_ctrl";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
j["print"]["action"] = 0;
|
||||
m_nozzle_system->GetOwner()->publish_json(j);
|
||||
}
|
||||
|
||||
bool DevNozzleRack::CheckRackMoveWarningDlg() const
|
||||
{
|
||||
if (wxThread::IsMain())
|
||||
{
|
||||
static bool s_show_move_warning = true;
|
||||
if (s_show_move_warning)
|
||||
{
|
||||
Slic3r::GUI::MessageDialog dlg(nullptr, _L("The toolhead and hotend rack may move. Please keep your hands away from the chamber."),
|
||||
_L("Warning"), wxICON_WARNING | wxOK);
|
||||
dlg.show_dsa_button();
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
{
|
||||
s_show_move_warning = !dlg.get_checkbox_state();
|
||||
return false;
|
||||
}
|
||||
|
||||
s_show_move_warning = !dlg.get_checkbox_state();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DevNozzleRack::CtrlRackConfirmNozzle(int rack_nozzle_id) const
|
||||
{
|
||||
json j;
|
||||
j["print"]["command"] = "nozzle_info_confirm";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
j["print"]["id"] = (rack_nozzle_id + 16); // from 0x16
|
||||
m_nozzle_system->GetOwner()->publish_json(j);
|
||||
}
|
||||
|
||||
void DevNozzleRack::CtrlRackConfirmAll() const
|
||||
{
|
||||
json j;
|
||||
j["print"]["command"] = "nozzle_info_confirm";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
j["print"]["id"] = 0xff;
|
||||
m_nozzle_system->GetOwner()->publish_json(j);
|
||||
}
|
||||
|
||||
void DevNozzleRack::CrtlRackReadNozzle(int rack_nozzle_id) const
|
||||
{
|
||||
json j;
|
||||
j["print"]["command"] = "holder_nozzle_refresh";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
j["print"]["id"] = (rack_nozzle_id + 16); // from 0x16
|
||||
m_nozzle_system->GetOwner()->publish_json(j);
|
||||
}
|
||||
|
||||
|
||||
bool DevNozzleRack::CtrlCanReadAll() const
|
||||
{
|
||||
//is in print
|
||||
if (m_nozzle_system->GetOwner()->is_in_printing())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_nozzle_system->GetOwner()->is_in_upgrading()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//if have nozzle
|
||||
bool has_nozzle_on_rack = false;
|
||||
for (const auto &nozzle_pair : m_rack_nozzles)
|
||||
{
|
||||
if (!nozzle_pair.second.IsEmpty())
|
||||
{
|
||||
has_nozzle_on_rack = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool has_nozzle_on_ext = false;
|
||||
if (m_nozzle_system->ContainsExtNozzle(MAIN_EXTRUDER_ID)) {
|
||||
has_nozzle_on_ext = true;
|
||||
}
|
||||
|
||||
if (!has_nozzle_on_rack && !has_nozzle_on_ext) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//if is in loading
|
||||
if (m_nozzle_system->GetOwner()->ams_status_main == AMS_STATUS_MAIN_FILAMENT_CHANGE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ext = m_nozzle_system->GetOwner()->GetExtderSystem();
|
||||
if (ext && ext->IsBusyLoading()) return false;
|
||||
|
||||
if (GetReadingCount() > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_status == RACK_STATUS_IDLE;
|
||||
}
|
||||
|
||||
void DevNozzleRack::CtrlRackReadAll(bool gui_check) const
|
||||
{
|
||||
if (gui_check && wxThread::IsMain())
|
||||
{
|
||||
#if 0
|
||||
if (!HasUnknownNozzles()) {
|
||||
Slic3r::GUI::MessageDialog dlg(nullptr, _L("Hotend information may be inaccurate. "
|
||||
"Would you like to re-read the hotend? (Hotend information may change during power-off)."),
|
||||
_L("Warning"), wxICON_WARNING | wxOK | wxYES);
|
||||
dlg.SetButtonLabel(wxID_OK, _L("I confirm all"));
|
||||
dlg.SetButtonLabel(wxID_YES, _L("Re-read all"));
|
||||
|
||||
int rtn = dlg.ShowModal();
|
||||
if (rtn == wxID_OK) {
|
||||
CtrlRackConfirmAll();
|
||||
} else if (rtn == wxID_YES) {
|
||||
if (CheckRackMoveWarningDlg()) {
|
||||
CtrlRackReadAll(false);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (CheckRackMoveWarningDlg()) {
|
||||
CtrlRackReadAll(false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
json j;
|
||||
j["print"]["command"] = "holder_nozzle_refresh";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
j["print"]["id"] = 0xff;
|
||||
m_nozzle_system->GetOwner()->publish_json(j);
|
||||
}
|
||||
|
||||
|
||||
bool DevNozzleRack::CtrlCanUpdateAll() const
|
||||
{
|
||||
if (m_nozzle_system->GetOwner()->is_in_printing())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ext_nozzle = m_nozzle_system->GetExtNozzle(MAIN_EXTRUDER_ID);
|
||||
if (ext_nozzle.IsAbnormal())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
auto ext_nozzle_firmware = m_nozzle_system->GetExtruderNozzleFirmware();
|
||||
if (!ext_nozzle_firmware.sw_new_ver.empty() &&
|
||||
ext_nozzle_firmware.sw_new_ver != ext_nozzle_firmware.sw_ver)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto val : m_rack_nozzles_firmware)
|
||||
{
|
||||
const auto& firmware_info = val.second;
|
||||
if (!firmware_info.sw_new_ver.empty() && firmware_info.sw_new_ver != firmware_info.sw_ver)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (GetNozzle(val.first).IsAbnormal())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int DevNozzleRack::CtrlRackUpgradeExtruderNozzle() const
|
||||
{
|
||||
return CtrlRackUpgrade("wtm");
|
||||
}
|
||||
|
||||
int DevNozzleRack::CtrlRackUpgradeRackNozzle(int rack_nozzle_id) const
|
||||
{
|
||||
return CtrlRackUpgrade("wtm/" + std::to_string(0x10 + rack_nozzle_id));
|
||||
}
|
||||
|
||||
int DevNozzleRack::CtrlRackUpgradeAll() const
|
||||
{
|
||||
return CtrlRackUpgrade("wtm_all");
|
||||
}
|
||||
|
||||
int DevNozzleRack::CtrlRackUpgrade(const std::string& module_str) const
|
||||
{
|
||||
if (wxThread::IsMain())
|
||||
{
|
||||
if (GetReadingCount() > 0)
|
||||
{
|
||||
Slic3r::GUI::MessageDialog dlg(nullptr, _L("Reading the hotends, please wait."), _L("Warning"), wxICON_WARNING | wxOK);
|
||||
dlg.ShowModal();
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool s_show_upgrade_warning = true;
|
||||
if (s_show_upgrade_warning)
|
||||
{
|
||||
Slic3r::GUI::MessageDialog dlg(nullptr, _L("During the hotend upgrade, the toolhead will move. Don't reach into the chamber."),
|
||||
_L("Warning"), wxICON_WARNING | wxOK | wxCANCEL);
|
||||
dlg.show_dsa_button();
|
||||
dlg.SetButtonLabel(wxID_OK, _L("Update"));
|
||||
int rtn = dlg.ShowModal();
|
||||
s_show_upgrade_warning = !dlg.get_checkbox_state();
|
||||
if (rtn != wxID_OK)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json j;
|
||||
j["upgrade"]["command"] = "wtm_upgrade";
|
||||
j["upgrade"]["module"] = module_str;
|
||||
j["upgrade"]["src_id"] = 1;
|
||||
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
return m_nozzle_system->GetOwner()->publish_json(j);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,35 +1,413 @@
|
||||
#include "DevExtruderSystem.h"
|
||||
|
||||
#include "DevNozzleRack.h"
|
||||
#include "DevNozzleSystem.h"
|
||||
#include "DevUtil.h"
|
||||
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
DevNozzle DevNozzleSystem::GetNozzle(int id) const
|
||||
// ---- DevNozzle: flow/volume conversions (Standard / High Flow / TPU High Flow) ----------------------
|
||||
// Device-reported U_FLOW nozzles map to nvtTPUHighFlow so a synced TPU-HF rack activates the H2C
|
||||
// change_filament_gcode TPU-kit branch. nvtHybrid is a slicer-only sentinel with no device
|
||||
// representation, so ToNozzleFlowType(nvtHybrid) falls through to NONE_FLOWTYPE.
|
||||
|
||||
NozzleFlowType DevNozzle::ToNozzleFlowType(const NozzleVolumeType& type)
|
||||
{
|
||||
if (m_nozzles.find(id) != m_nozzles.end())
|
||||
switch (type) {
|
||||
case NozzleVolumeType::nvtStandard: return NozzleFlowType::S_FLOW;
|
||||
case NozzleVolumeType::nvtHighFlow: return NozzleFlowType::H_FLOW;
|
||||
case NozzleVolumeType::nvtTPUHighFlow: return NozzleFlowType::U_FLOW;
|
||||
default: return NozzleFlowType::NONE_FLOWTYPE;
|
||||
}
|
||||
}
|
||||
|
||||
NozzleVolumeType DevNozzle::ToNozzleVolumeType(const NozzleFlowType& type)
|
||||
{
|
||||
switch (type) {
|
||||
case NozzleFlowType::S_FLOW: return NozzleVolumeType::nvtStandard;
|
||||
case NozzleFlowType::H_FLOW: return NozzleVolumeType::nvtHighFlow;
|
||||
case NozzleFlowType::U_FLOW: return NozzleVolumeType::nvtTPUHighFlow;
|
||||
default: {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "nozzle flow type None convert to nozzle volume type Standard";
|
||||
return NozzleVolumeType::nvtStandard;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wxString DevNozzle::GetNozzleFlowTypeStr(NozzleFlowType type)
|
||||
{
|
||||
switch (type) {
|
||||
case NozzleFlowType::H_FLOW: return _L("High Flow");
|
||||
case NozzleFlowType::S_FLOW: return _L("Standard");
|
||||
case NozzleFlowType::U_FLOW: return _L("TPU High Flow");
|
||||
default: break;
|
||||
}
|
||||
|
||||
return _L("Unknown");
|
||||
}
|
||||
|
||||
// Untranslated flow-type literal used for filaments_blacklist.json nozzle_flows matching.
|
||||
// Keep the raw English strings; the translated GetNozzleFlowTypeStr must not be used for JSON matching.
|
||||
std::string DevNozzle::GetNozzleFlowTypeString(NozzleFlowType type)
|
||||
{
|
||||
switch (type) {
|
||||
case NozzleFlowType::H_FLOW: return "High Flow";
|
||||
case NozzleFlowType::S_FLOW: return "Standard";
|
||||
case NozzleFlowType::U_FLOW: return "TPU High Flow";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// Untranslated flow-type literal serialized into the get_auto_nozzle_mapping payload.
|
||||
// Differs from GetNozzleFlowTypeString only in the fallback: "" (empty) rather than "Unknown".
|
||||
std::string DevNozzle::ToNozzleFlowString(const NozzleFlowType& type)
|
||||
{
|
||||
switch (type) {
|
||||
case NozzleFlowType::S_FLOW: return "Standard";
|
||||
case NozzleFlowType::H_FLOW: return "High Flow";
|
||||
case NozzleFlowType::U_FLOW: return "TPU High Flow";
|
||||
default: return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
wxString DevNozzle::GetNozzleTypeStr(NozzleType type)
|
||||
{
|
||||
switch (type) {
|
||||
case Slic3r::ntHardenedSteel: return _L("Hardened Steel");
|
||||
case Slic3r::ntStainlessSteel: return _L("Stainless Steel");
|
||||
case Slic3r::ntTungstenCarbide: return _L("Tungsten Carbide");
|
||||
default: break;
|
||||
}
|
||||
|
||||
return _L("Unknown");
|
||||
}
|
||||
|
||||
// ---- DevNozzle: status ------------------------------------------------------------------------------
|
||||
|
||||
bool DevNozzle::IsInfoReliable() const
|
||||
{
|
||||
if (IsEmpty()) { return false; }
|
||||
return DevUtil::get_flag_bits(m_stat, 0, 1) == 0;
|
||||
}
|
||||
|
||||
bool DevNozzle::IsNormal() const
|
||||
{
|
||||
if (IsEmpty()) { return false; }
|
||||
return DevUtil::get_flag_bits(m_stat, 1, 2) == 0;
|
||||
}
|
||||
|
||||
bool DevNozzle::IsAbnormal() const
|
||||
{
|
||||
return DevUtil::get_flag_bits(m_stat, 1, 2) == (1 << 0);
|
||||
}
|
||||
|
||||
bool DevNozzle::IsUnknown() const
|
||||
{
|
||||
return DevUtil::get_flag_bits(m_stat, 1, 2) == (1 << 1);
|
||||
}
|
||||
|
||||
int DevNozzle::GetNozzlePosId() const
|
||||
{
|
||||
return IsOnRack() ? (m_nozzle_id + 0x10) : m_nozzle_id;
|
||||
}
|
||||
|
||||
NozzleDiameterType DevNozzle::GetNozzleDiameterType() const
|
||||
{
|
||||
if (is_approx(m_diameter, 0.2f))
|
||||
return NozzleDiameterType::NOZZLE_DIAMETER_0_2;
|
||||
else if (is_approx(m_diameter, 0.4f))
|
||||
return NozzleDiameterType::NOZZLE_DIAMETER_0_4;
|
||||
else if (is_approx(m_diameter, 0.6f))
|
||||
return NozzleDiameterType::NOZZLE_DIAMETER_0_6;
|
||||
else if (is_approx(m_diameter, 0.8f))
|
||||
return NozzleDiameterType::NOZZLE_DIAMETER_0_8;
|
||||
else
|
||||
return NozzleDiameterType::NONE_DIAMETER_TYPE;
|
||||
}
|
||||
|
||||
DevFirmwareVersionInfo DevNozzle::GetFirmwareInfo() const
|
||||
{
|
||||
const auto& nozzle_rack = m_nozzle_rack.lock();
|
||||
if (nozzle_rack) {
|
||||
if (IsOnRack()) {
|
||||
return nozzle_rack->GetNozzleFirmwareInfo(m_nozzle_id);
|
||||
} else if (m_nozzle_id == 0) {
|
||||
return nozzle_rack->GetNozzleSystem()->GetExtruderNozzleFirmware();
|
||||
}
|
||||
}
|
||||
|
||||
return DevFirmwareVersionInfo();
|
||||
}
|
||||
|
||||
// ---- DevNozzle: location ----------------------------------------------------------------------------
|
||||
|
||||
int DevNozzle::GetLogicExtruderId() const
|
||||
{
|
||||
int total_ext_count = GetTotalExtruderCount();
|
||||
if (total_ext_count == 1) {
|
||||
return LOGIC_UNIQUE_EXTRUDER_ID;
|
||||
} else if (total_ext_count == 2) {
|
||||
if (AtLeftExtruder()) {
|
||||
return LOGIC_L_EXTRUDER_ID;
|
||||
} else if (AtRightExtruder()) {
|
||||
return LOGIC_R_EXTRUDER_ID;
|
||||
}
|
||||
}
|
||||
|
||||
assert(0);
|
||||
return LOGIC_UNIQUE_EXTRUDER_ID;
|
||||
}
|
||||
|
||||
int DevNozzle::GetExtruderId() const
|
||||
{
|
||||
int total_ext_count = GetTotalExtruderCount();
|
||||
if (total_ext_count == 1) {
|
||||
return MAIN_EXTRUDER_ID;
|
||||
} else if (total_ext_count == 2) {
|
||||
if (AtRightExtruder()) {
|
||||
return MAIN_EXTRUDER_ID;
|
||||
} else if (AtLeftExtruder()) {
|
||||
return DEPUTY_EXTRUDER_ID;
|
||||
}
|
||||
}
|
||||
|
||||
return MAIN_EXTRUDER_ID;
|
||||
}
|
||||
|
||||
bool DevNozzle::AtLeftExtruder() const
|
||||
{
|
||||
assert(GetTotalExtruderCount() == 2);
|
||||
if (IsOnRack()) { return false; }
|
||||
return m_nozzle_id == DEPUTY_EXTRUDER_ID;
|
||||
}
|
||||
|
||||
bool DevNozzle::AtRightExtruder() const
|
||||
{
|
||||
assert(GetTotalExtruderCount() == 2);
|
||||
if (IsOnRack()) { return true; }
|
||||
return m_nozzle_id == MAIN_EXTRUDER_ID;
|
||||
}
|
||||
|
||||
int DevNozzle::GetTotalExtruderCount() const
|
||||
{
|
||||
auto rack = m_nozzle_rack.lock();
|
||||
if (rack) {
|
||||
MachineObject* obj = rack->GetNozzleSystem()->GetOwner();
|
||||
return obj->GetExtderSystem()->GetTotalExtderCount();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---- DevNozzleSystem --------------------------------------------------------------------------------
|
||||
|
||||
DevNozzleSystem::DevNozzleSystem(MachineObject* owner)
|
||||
: m_owner(owner), m_nozzle_rack(std::make_shared<DevNozzleRack>(this))
|
||||
{
|
||||
}
|
||||
|
||||
DevNozzle DevNozzleSystem::GetExtNozzle(int id) const
|
||||
{
|
||||
if (m_ext_nozzles.find(id) != m_ext_nozzles.end())
|
||||
{
|
||||
return m_nozzles.at(id);
|
||||
return m_ext_nozzles.at(id);
|
||||
}
|
||||
|
||||
return DevNozzle();
|
||||
}
|
||||
|
||||
void DevNozzleSystem::Reset()
|
||||
DevNozzle DevNozzleSystem::GetRackNozzle(int idx) const
|
||||
{
|
||||
m_nozzles.clear();
|
||||
m_extder_exist = 0;
|
||||
m_state = 0; // idle state
|
||||
return m_nozzle_rack->GetNozzle(idx);
|
||||
}
|
||||
|
||||
const std::map<int, DevNozzle>& DevNozzleSystem::GetRackNozzles() const
|
||||
{
|
||||
return m_nozzle_rack->GetRackNozzles();
|
||||
}
|
||||
|
||||
void DevNozzleSystem::SetSupportNozzleRack(bool supported)
|
||||
{
|
||||
m_nozzle_rack->SetSupported(supported);
|
||||
}
|
||||
|
||||
const std::vector<DevNozzle> DevNozzleSystem::CollectNozzles(int ext_loc, NozzleFlowType flow_type, float diameter) const
|
||||
{
|
||||
auto s_match = [&](const DevNozzle& nozzle) -> bool {
|
||||
if (nozzle.IsEmpty() || nozzle.IsAbnormal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (diameter >= 0.0f && nozzle.m_diameter != diameter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_owner->is_nozzle_flow_type_supported() && flow_type != nozzle.GetNozzleFlowType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
std::vector<DevNozzle> result;
|
||||
|
||||
auto ext_nozzle = GetExtNozzle(ext_loc);
|
||||
if (s_match(ext_nozzle)) {
|
||||
result.push_back(ext_nozzle);
|
||||
}
|
||||
|
||||
if (ext_loc == MAIN_EXTRUDER_ID) {
|
||||
auto rack_nozzles = m_nozzle_rack->GetRackNozzles();
|
||||
for (auto rack_nozzle : rack_nozzles) {
|
||||
if (s_match(rack_nozzle.second)) {
|
||||
result.push_back(rack_nozzle.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<MultiNozzleUtils::NozzleGroupInfo> DevNozzleSystem::GetNozzleGroups() const
|
||||
{
|
||||
std::vector<MultiNozzleUtils::NozzleGroupInfo> nozzle_groups;
|
||||
|
||||
auto nozzle_in_extruder = this->GetExtNozzles();
|
||||
for (auto& elem : nozzle_in_extruder) {
|
||||
auto& nozzle = elem.second;
|
||||
MultiNozzleUtils::NozzleGroupInfo info;
|
||||
info.extruder_id = nozzle.GetLogicExtruderId();
|
||||
info.diameter = format_diameter_to_str(nozzle.m_diameter);
|
||||
info.volume_type = DevNozzle::ToNozzleVolumeType(nozzle.m_nozzle_flow);
|
||||
info.nozzle_count = 1;
|
||||
nozzle_groups.emplace_back(std::move(info));
|
||||
}
|
||||
|
||||
auto nozzle_rack = this->GetNozzleRack();
|
||||
if (!nozzle_rack)
|
||||
return nozzle_groups;
|
||||
|
||||
auto nozzle_in_rack = nozzle_rack->GetRackNozzles(); // nozzles in rack
|
||||
for (auto& elem : nozzle_in_rack) {
|
||||
auto& nozzle = elem.second;
|
||||
if (nozzle.IsUnknown() || nozzle.IsAbnormal())
|
||||
continue;
|
||||
int extruder_id = nozzle.GetLogicExtruderId();
|
||||
std::string diameter = format_diameter_to_str(nozzle.m_diameter);
|
||||
NozzleVolumeType volume_type = DevNozzle::ToNozzleVolumeType(nozzle.m_nozzle_flow);
|
||||
|
||||
bool found = false;
|
||||
for (auto& group : nozzle_groups) {
|
||||
if (group.extruder_id == extruder_id &&
|
||||
group.volume_type == volume_type &&
|
||||
group.diameter == diameter) {
|
||||
found = true;
|
||||
group.nozzle_count += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
nozzle_groups.emplace_back(diameter, volume_type, extruder_id, 1);
|
||||
}
|
||||
return nozzle_groups;
|
||||
}
|
||||
|
||||
bool DevNozzleSystem::IsRackMaximumInstalled() const
|
||||
{
|
||||
auto ext_nozzle = GetExtNozzle(MAIN_EXTRUDER_ID);
|
||||
if (ext_nozzle.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& rack_nozzles = m_nozzle_rack->GetRackNozzles();
|
||||
if (rack_nozzles.size() < 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto item : rack_nozzles) {
|
||||
if (item.second.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DevNozzleSystem::HasUnreliableNozzles() const
|
||||
{
|
||||
for (auto nozzle : m_ext_nozzles) {
|
||||
if (!nozzle.second.IsInfoReliable()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_nozzle_rack->HasUnreliableNozzles()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DevNozzleSystem::HasUnknownNozzles() const
|
||||
{
|
||||
for (auto nozzle : m_ext_nozzles) {
|
||||
if (nozzle.second.IsUnknown()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_nozzle_rack->HasUnknownNozzles()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DevNozzleSystem::AddFirmwareInfoWTM(const DevFirmwareVersionInfo& info)
|
||||
{
|
||||
static const std::string s_wtm_prefix = "wtm/";
|
||||
auto pos = info.name.find(s_wtm_prefix);
|
||||
if (pos == std::string::npos) {
|
||||
m_ext_nozzle_firmware_info = info;
|
||||
} else {
|
||||
try {
|
||||
auto str = info.name.substr(s_wtm_prefix.size()); // remove "wtm/" prefix
|
||||
int rack_nozzle_id = std::stoi(str) - 0x10; // rack nozzle IDs start from 0x10
|
||||
m_nozzle_rack->AddNozzleFirmwareInfo(rack_nozzle_id, info);
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Invalid nozzle ID in firmware name: "
|
||||
<< info.name << ", error: " << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DevNozzleSystem::ClearFirmwareInfoWTM()
|
||||
{
|
||||
m_ext_nozzle_firmware_info = DevFirmwareVersionInfo();
|
||||
m_nozzle_rack->ClearNozzleFirmwareInfo();
|
||||
}
|
||||
|
||||
void DevNozzleSystem::ClearNozzles()
|
||||
{
|
||||
m_ext_nozzles.clear();
|
||||
m_nozzle_rack->ClearRackNozzles();
|
||||
}
|
||||
|
||||
// ---- parsing ----------------------------------------------------------------------------------------
|
||||
|
||||
static unordered_map<string, NozzleFlowType> _str2_nozzle_flow_type = {
|
||||
{"S", NozzleFlowType::S_FLOW},
|
||||
{"H", NozzleFlowType::H_FLOW},
|
||||
{"A", NozzleFlowType::S_FLOW},
|
||||
{"X", NozzleFlowType::S_FLOW}
|
||||
{"X", NozzleFlowType::S_FLOW},
|
||||
{"E", NozzleFlowType::H_FLOW}, // E3D high-flow
|
||||
{"U", NozzleFlowType::U_FLOW}, // TPU 1.75 high-flow -> nvtTPUHighFlow
|
||||
};
|
||||
|
||||
static unordered_map<string, NozzleType> _str2_nozzle_type = {
|
||||
@@ -57,6 +435,11 @@ static void s_parse_nozzle_type(const std::string& nozzle_type_str, DevNozzle& n
|
||||
nozzle.m_nozzle_type = _str2_nozzle_type[type_str];
|
||||
}
|
||||
}
|
||||
else if (nozzle_type_str == "N/A")
|
||||
{
|
||||
nozzle.m_nozzle_type = NozzleType::ntUndefine;
|
||||
nozzle.m_nozzle_flow = NozzleFlowType::NONE_FLOWTYPE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,8 +449,9 @@ void DevNozzleSystemParser::ParseV1_0(const nlohmann::json& nozzletype_json,
|
||||
std::optional<int> flag_e3d)
|
||||
{
|
||||
//Since both the old and new protocols push data.
|
||||
// assert(system->m_nozzles.size() < 2);
|
||||
// assert(system->m_ext_nozzles.size() < 2);
|
||||
DevNozzle nozzle;
|
||||
nozzle.SetRack(system->GetNozzleRack());
|
||||
nozzle.m_nozzle_id = 0;
|
||||
nozzle.m_nozzle_flow = NozzleFlowType::S_FLOW; // default flow type
|
||||
|
||||
@@ -112,32 +496,88 @@ void DevNozzleSystemParser::ParseV1_0(const nlohmann::json& nozzletype_json,
|
||||
}
|
||||
}
|
||||
|
||||
system->m_nozzles[nozzle.m_nozzle_id] = nozzle;
|
||||
system->m_ext_nozzles[nozzle.m_nozzle_id] = nozzle;
|
||||
}
|
||||
|
||||
|
||||
void DevNozzleSystemParser::ParseV2_0(const json& nozzle_json, DevNozzleSystem* system)
|
||||
void DevNozzleSystemParser::ParseV2_0(const json& device_json, DevNozzleSystem* system)
|
||||
{
|
||||
system->Reset();
|
||||
|
||||
if (nozzle_json.contains("exist"))
|
||||
if (device_json.contains("nozzle"))
|
||||
{
|
||||
system->m_extder_exist = DevUtil::get_flag_bits(nozzle_json["exist"].get<int>(), 0, 16);
|
||||
const json& nozzle_json = device_json["nozzle"];
|
||||
if (nozzle_json.contains("exist"))
|
||||
{
|
||||
system->m_extder_exist = DevUtil::get_flag_bits(nozzle_json["exist"].get<int>(), 0, 16);
|
||||
}
|
||||
|
||||
if (nozzle_json.contains("state"))
|
||||
{
|
||||
system->m_state_0_4 = DevUtil::get_flag_bits(nozzle_json["state"].get<int>(), 0, 4);
|
||||
int new_reading_idx = DevUtil::get_flag_bits(nozzle_json["state"].get<int>(), 8, 4);
|
||||
int new_reading_count = DevUtil::get_flag_bits(nozzle_json["state"].get<int>(), 4, 4);
|
||||
if (system->m_reading_count != new_reading_count || system->m_reading_idx != new_reading_idx)
|
||||
{
|
||||
system->m_reading_idx = new_reading_idx;
|
||||
system->m_reading_count = new_reading_count;
|
||||
if (system->m_reading_count == 0)
|
||||
{
|
||||
system->m_nozzle_rack->SendReadingFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// replace-nozzle state: a rack nozzle standing in for the toolhead position.
|
||||
// Absent for every non-rack printer's "nozzle" block => both stay std::nullopt (inert).
|
||||
if (nozzle_json.contains("src_id"))
|
||||
{
|
||||
system->m_replace_nozzle_src = std::make_optional(nozzle_json["src_id"].get<int>());
|
||||
}
|
||||
|
||||
if (nozzle_json.contains("tar_id"))
|
||||
{
|
||||
system->m_replace_nozzle_tar = std::make_optional(nozzle_json["tar_id"].get<int>());
|
||||
}
|
||||
|
||||
system->ClearNozzles();
|
||||
for (auto it = nozzle_json["info"].begin(); it != nozzle_json["info"].end(); it++)
|
||||
{
|
||||
DevNozzle nozzle_obj;
|
||||
nozzle_obj.SetRack(system->GetNozzleRack());
|
||||
|
||||
const auto& njon = it.value();
|
||||
nozzle_obj.m_nozzle_id = DevUtil::get_hex_bits(njon["id"].get<int>(), 0);
|
||||
nozzle_obj.m_diameter = njon["diameter"].get<float>();
|
||||
s_parse_nozzle_type(njon["type"].get<std::string>(), nozzle_obj);
|
||||
if (njon.contains("stat"))/*maybe not contains*/
|
||||
{
|
||||
nozzle_obj.SetStatus(njon["stat"].get<int>());
|
||||
}
|
||||
|
||||
DevJsonValParser::ParseVal(njon, "fila_id", nozzle_obj.m_fila_id);
|
||||
DevJsonValParser::ParseVal(njon, "wear", nozzle_obj.m_wear);
|
||||
if (njon.contains("p_t"))/*maybe not contains*/
|
||||
{
|
||||
nozzle_obj.m_nozzle_print_time = njon["p_t"].get<int>();
|
||||
}
|
||||
if (njon.contains("color_m"))/*maybe not contains*/
|
||||
{
|
||||
nozzle_obj.m_filament_clr = njon["color_m"].get<std::string>();
|
||||
}
|
||||
|
||||
if (DevUtil::get_hex_bits(njon["id"].get<int>(), 1) == 1)
|
||||
{
|
||||
system->m_nozzle_rack->AddRackNozzle(nozzle_obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
system->m_ext_nozzles[nozzle_obj.m_nozzle_id] = nozzle_obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nozzle_json.contains("state"))
|
||||
if (device_json.contains("holder"))
|
||||
{
|
||||
system->m_state = DevUtil::get_flag_bits(nozzle_json["state"].get<int>(), 0, 4);
|
||||
}
|
||||
|
||||
for (auto it = nozzle_json["info"].begin(); it != nozzle_json["info"].end(); it++)
|
||||
{
|
||||
DevNozzle nozzle_obj;
|
||||
const auto& njon = it.value();
|
||||
nozzle_obj.m_nozzle_id = njon["id"].get<int>();
|
||||
nozzle_obj.m_diameter = njon["diameter"].get<float>();
|
||||
s_parse_nozzle_type(njon["type"].get<std::string>(), nozzle_obj);
|
||||
system->m_nozzles[nozzle_obj.m_nozzle_id] = nozzle_obj;
|
||||
system->m_nozzle_rack->ParseRackInfo(device_json["holder"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,28 +1,112 @@
|
||||
#pragma once
|
||||
#include "DevDefs.h"
|
||||
#include "DevFirmware.h"
|
||||
|
||||
#include "libslic3r/CommonDefs.hpp"
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
#include "slic3r/Utils/json_diff.hpp"
|
||||
|
||||
#include <wx/string.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
// Device flow-type mapping note (nozzle rack): the device U_FLOW value maps to nvtTPUHighFlow
|
||||
// (TPU High Flow), so a device-synced TPU-HF rack resolves to nvtTPUHighFlow and the H2C
|
||||
// change_filament_gcode TPU-kit branch can activate. nvtHybrid is a slicer-only sentinel with no
|
||||
// device representation, so it stays out of these device flow-type conversions.
|
||||
//
|
||||
// GetExtruderNozzleInfo (and its ExtruderNozzleInfos aggregate) is intentionally omitted here;
|
||||
// the SelectMachine slicing-vs-installed nozzle comparison collects its per-extruder NozzleDef
|
||||
// entries itself (s_get_slicing_extuder_nozzles).
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
// Previous definitions
|
||||
class MachineObject;
|
||||
class DevNozzleRack;
|
||||
|
||||
struct DevNozzle
|
||||
{
|
||||
friend class DevNozzleSystemParser;
|
||||
|
||||
public:
|
||||
int m_nozzle_id = -1;
|
||||
NozzleFlowType m_nozzle_flow = NozzleFlowType::S_FLOW;// 0-common 1-high flow
|
||||
NozzleType m_nozzle_type = NozzleType::ntUndefine;// 0-stainless_steel 1-hardened_steel 5-tungsten_carbide
|
||||
float m_diameter = 0.0f;// unknown until reported by the printer
|
||||
|
||||
public:
|
||||
// flow/volume conversions (Standard / High Flow / TPU High Flow)
|
||||
static NozzleFlowType ToNozzleFlowType(const NozzleVolumeType& type);
|
||||
static NozzleVolumeType ToNozzleVolumeType(const NozzleFlowType& type);
|
||||
|
||||
static wxString GetNozzleFlowTypeStr(NozzleFlowType type);
|
||||
static std::string GetNozzleFlowTypeString(NozzleFlowType type);// untranslated literal ("High Flow"/"Standard"/"TPU High Flow") — the filament blacklist JSON matches these raw strings, so it must NOT use the translated GetNozzleFlowTypeStr (would break non-English locales)
|
||||
static std::string ToNozzleFlowString(const NozzleFlowType& type);// untranslated "Standard"/"High Flow"/"TPU High Flow" ("" for none) — the raw literal serialized into the get_auto_nozzle_mapping payload
|
||||
static wxString GetNozzleTypeStr(NozzleType type);
|
||||
|
||||
public:
|
||||
bool IsEmpty() const { return m_nozzle_id < 0; }
|
||||
|
||||
void SetRack(const std::weak_ptr<DevNozzleRack>& rack) { m_nozzle_rack = rack; }
|
||||
|
||||
int GetNozzleId() const { return m_nozzle_id; }
|
||||
int GetNozzlePosId() const;// physical position id: rack nozzle -> id + 0x10, else id
|
||||
NozzleType GetNozzleType() const { return m_nozzle_type; }
|
||||
NozzleFlowType GetNozzleFlowType() const { return m_nozzle_flow; }
|
||||
NozzleDiameterType GetNozzleDiameterType() const;
|
||||
float GetNozzleDiameter() const { return m_diameter; }
|
||||
float GetNozzleWear() const { return m_wear; }
|
||||
int GetNozzlePrintTime() const { return m_nozzle_print_time; }
|
||||
|
||||
// firmware (rack hotend WTM / extruder nozzle firmware)
|
||||
DevFirmwareVersionInfo GetFirmwareInfo() const;
|
||||
|
||||
// display
|
||||
wxString GetNozzleDiameterStr() const { return wxString::Format("%.1f mm", m_diameter); }
|
||||
wxString GetNozzleFlowTypeStr() const { return GetNozzleFlowTypeStr(m_nozzle_flow); }
|
||||
wxString GetNozzleTypeStr() const { return GetNozzleTypeStr(m_nozzle_type); }
|
||||
|
||||
std::string GetFilamentId() const { return m_fila_id; }
|
||||
std::string GetFilamentColor() const { return m_filament_clr; }
|
||||
|
||||
// location
|
||||
bool AtLeftExtruder() const;
|
||||
bool AtRightExtruder() const;
|
||||
|
||||
int GetLogicExtruderId() const;// warning: logical extruder id
|
||||
int GetExtruderId() const;// warning: physical extruder id
|
||||
|
||||
/* holder nozzle */
|
||||
bool IsOnRack() const { return m_on_rack; }
|
||||
bool IsInfoReliable() const;
|
||||
|
||||
bool IsNormal() const;
|
||||
bool IsAbnormal() const;
|
||||
bool IsUnknown() const;
|
||||
|
||||
void SetOnRack(bool on_rack) { m_on_rack = on_rack; }
|
||||
void SetStatus(int stat) { m_stat = stat; }
|
||||
|
||||
private:
|
||||
int GetTotalExtruderCount() const;
|
||||
|
||||
private:
|
||||
bool m_on_rack = false;
|
||||
|
||||
int m_stat = 0;
|
||||
float m_wear = 0.0f;
|
||||
|
||||
std::string m_fila_id; // main material
|
||||
std::string m_filament_clr; // main color
|
||||
|
||||
std::weak_ptr<DevNozzleRack> m_nozzle_rack; // weak pointer to the nozzle rack
|
||||
int m_nozzle_print_time{0};
|
||||
};
|
||||
|
||||
class DevNozzleSystem
|
||||
{
|
||||
friend class DevNozzleSystemParser;
|
||||
public:
|
||||
DevNozzleSystem(MachineObject* owner) : m_owner(owner) {}
|
||||
private:
|
||||
enum Status : int
|
||||
{
|
||||
@@ -31,27 +115,81 @@ namespace Slic3r
|
||||
};
|
||||
|
||||
public:
|
||||
bool ContainsNozzle(int id) const { return m_nozzles.find(id) != m_nozzles.end(); }
|
||||
DevNozzle GetNozzle(int id) const;
|
||||
const std::map<int, DevNozzle>& GetNozzles() const { return m_nozzles;}
|
||||
bool IsRefreshing() const { return m_state == 1; }
|
||||
DevNozzleSystem(MachineObject* owner);
|
||||
~DevNozzleSystem() = default;
|
||||
|
||||
public:
|
||||
MachineObject* GetOwner() const { return m_owner; }
|
||||
|
||||
// nozzle by position id: pos_id < 0x10 -> extruder nozzle, else rack nozzle at (pos_id - 0x10)
|
||||
DevNozzle GetNozzleByPosId(int pos_id) const { return pos_id < 0x10 ? GetExtNozzle(pos_id) : GetRackNozzle(pos_id - 0x10); }
|
||||
|
||||
// nozzles on extruder
|
||||
bool ContainsExtNozzle(int id) const { return m_ext_nozzles.find(id) != m_ext_nozzles.end(); }
|
||||
DevNozzle GetExtNozzle(int id) const;
|
||||
const std::map<int, DevNozzle>& GetExtNozzles() const { return m_ext_nozzles; }
|
||||
int GetExtNozzleCount() const { return (int) m_ext_nozzles.size(); }
|
||||
|
||||
// nozzles on rack
|
||||
void SetSupportNozzleRack(bool supported);
|
||||
std::shared_ptr<DevNozzleRack> GetNozzleRack() const { return m_nozzle_rack; }
|
||||
DevNozzle GetRackNozzle(int idx) const;
|
||||
const std::map<int, DevNozzle>& GetRackNozzles() const;
|
||||
|
||||
// nozzles on extruder and rack
|
||||
bool IsRackMaximumInstalled() const;// true when the main extruder + all 6 rack slots hold nozzles
|
||||
|
||||
// grouping (drives the MultiNozzleSyncDialog options)
|
||||
const std::vector<DevNozzle> CollectNozzles(int ext_loc, NozzleFlowType flow_type, float diameter = -1.0f) const;
|
||||
std::vector<MultiNozzleUtils::NozzleGroupInfo> GetNozzleGroups() const;
|
||||
|
||||
bool IsIdle() const { return m_state_0_4 == NOZZLE_SYSTEM_IDLE; }
|
||||
bool IsRefreshing() const { return m_state_0_4 == NOZZLE_SYSTEM_REFRESHING; }
|
||||
|
||||
bool HasUnreliableNozzles() const;// any extruder or rack nozzle whose reported info is not reliable
|
||||
bool HasUnknownNozzles() const; // any extruder or rack nozzle of unknown state
|
||||
|
||||
/* reading */
|
||||
int GetReadingIdx() const { return m_reading_idx; }
|
||||
int GetReadingCount() const { return m_reading_count; }
|
||||
|
||||
/* firmware */
|
||||
void AddFirmwareInfoWTM(const DevFirmwareVersionInfo& info);// route a "wtm/<id>" module version to the rack nozzle, else to the extruder nozzle
|
||||
void ClearFirmwareInfoWTM();
|
||||
DevFirmwareVersionInfo GetExtruderNozzleFirmware() const { return m_ext_nozzle_firmware_info; }
|
||||
|
||||
/* replace nozzle (device reports src/tar position while a rack nozzle stands in for the toolhead) */
|
||||
std::optional<int> GetReplaceNozzleSrc() const { return m_replace_nozzle_src; }
|
||||
std::optional<int> GetReplaceNozzleTar() const { return m_replace_nozzle_tar; }
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
void ClearNozzles();
|
||||
|
||||
private:
|
||||
MachineObject* m_owner = nullptr;
|
||||
|
||||
int m_extder_exist = 0; //0- none exist 1-exist, unused
|
||||
int m_state = 0; //0-idle 1-checking, unused
|
||||
std::map<int, DevNozzle> m_nozzles;
|
||||
int m_extder_exist = 0; //0- none exist 1-exist, unused
|
||||
int m_state_0_4 = 0; //0-idle 1-refreshing
|
||||
|
||||
std::optional<int> m_replace_nozzle_src; // replace nozzle source position (device-reported)
|
||||
std::optional<int> m_replace_nozzle_tar; // replace nozzle target position (device-reported)
|
||||
|
||||
/* refreshing */
|
||||
int m_reading_idx = 0;
|
||||
int m_reading_count = 0;
|
||||
|
||||
// nozzles on extruder
|
||||
std::map<int, DevNozzle> m_ext_nozzles;
|
||||
DevFirmwareVersionInfo m_ext_nozzle_firmware_info;
|
||||
|
||||
// nozzles on rack
|
||||
std::shared_ptr<DevNozzleRack> m_nozzle_rack;
|
||||
};
|
||||
|
||||
class DevNozzleSystemParser
|
||||
{
|
||||
public:
|
||||
static void ParseV1_0(const nlohmann::json& nozzletype_json, const nlohmann::json& diameter_json, DevNozzleSystem* system, std::optional<int> flag_e3d);
|
||||
static void ParseV2_0(const json& nozzle_json, DevNozzleSystem* system);
|
||||
static void ParseV2_0(const json& device_json, DevNozzleSystem* system);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -28,6 +28,10 @@ public:
|
||||
static int get_flag_bits(std::string str, int start, int count = 1);
|
||||
static int get_flag_bits(int num, int start, int count = 1, int base = 10);
|
||||
|
||||
// Extract the hex digit at position `pos` (0 = least-significant).
|
||||
// eg. get_hex_bits(16, 1, 10) = 1
|
||||
static int get_hex_bits(int num, int pos, int input_num_base = 10) { return get_flag_bits(num, pos * 4, 4, input_num_base); }
|
||||
|
||||
static float string_to_float(const std::string& str_value);
|
||||
|
||||
static std::string convertToIp(long long ip);
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
#include "DevUtilBackend.h"
|
||||
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/BackgroundSlicingProcess.hpp"
|
||||
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> DevUtilBackend::GetNozzleGroupResult(Slic3r::GUI::Plater* plater)
|
||||
{
|
||||
if (plater && plater->background_process().get_current_gcode_result()) {
|
||||
return plater->background_process().get_current_gcode_result()->nozzle_group_result;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::unordered_map<std::string, DevAmsType> s_ams_type_map = {
|
||||
{"0", DevAmsType::N3F},
|
||||
{"1", DevAmsType::N3S},
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
#include "DevDefs.h"
|
||||
#include "DevFilaSystem.h"
|
||||
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
namespace GUI { class Plater; }
|
||||
|
||||
class DevUtilBackend
|
||||
{
|
||||
@@ -19,6 +21,12 @@ public:
|
||||
DevUtilBackend() = delete;
|
||||
|
||||
public:
|
||||
|
||||
// for rack: the slicer's per-filament -> logical-nozzle grouping for the current plate, read off
|
||||
// the post-slice GCodeProcessorResult (plater->background_process().get_current_gcode_result()).
|
||||
// Returns nullptr when there is no plater / no current result.
|
||||
static std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> GetNozzleGroupResult(Slic3r::GUI::Plater* plater);
|
||||
|
||||
// for filament preset
|
||||
static std::optional<DevFilamentDryingPreset> GetFilamentDryingPreset(const std::string& fila_id);
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "fast_float/fast_float.h"
|
||||
|
||||
#include "DeviceCore/DevFilaSystem.h"
|
||||
#include "DeviceCore/DevFilaSwitch.h"
|
||||
#include "DeviceCore/DevExtensionTool.h"
|
||||
#include "DeviceCore/DevExtruderSystem.h"
|
||||
#include "DeviceCore/DevNozzleSystem.h"
|
||||
@@ -39,6 +40,7 @@
|
||||
#include "DeviceCore/DevHMS.h"
|
||||
|
||||
#include "DeviceCore/DevMapping.h"
|
||||
#include "DeviceCore/DevMappingNozzle.h"
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevUtil.h"
|
||||
|
||||
@@ -567,11 +569,14 @@ MachineObject::MachineObject(DeviceManager* manager, NetworkAgent* agent, std::s
|
||||
m_extension_tool = DevExtensionTool::Create(this);
|
||||
m_nozzle_system = new DevNozzleSystem(this);
|
||||
m_fila_system = std::make_shared<DevFilaSystem>(this);
|
||||
m_fila_switch = new DevFilaSwitch(this);
|
||||
m_hms_system = new DevHMS(this);
|
||||
m_config = new DevConfig(this);
|
||||
|
||||
m_ctrl = new DevCtrl(this);
|
||||
m_print_options = new DevPrintOptions(this);
|
||||
|
||||
m_nozzle_mapping_ptr = std::make_shared<DevNozzleMappingCtrl>(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,6 +618,9 @@ MachineObject::~MachineObject()
|
||||
m_ctrl = nullptr;
|
||||
|
||||
|
||||
delete m_fila_switch;
|
||||
m_fila_switch = nullptr;
|
||||
|
||||
delete m_hms_system;
|
||||
m_hms_system = nullptr;
|
||||
|
||||
@@ -859,7 +867,11 @@ void MachineObject::clear_version_info()
|
||||
laser_version_info = DevFirmwareVersionInfo();
|
||||
cutting_module_version_info = DevFirmwareVersionInfo();
|
||||
extinguish_version_info = DevFirmwareVersionInfo();
|
||||
filatrack_version_info = DevFirmwareVersionInfo();
|
||||
module_vers.clear();
|
||||
// Drop cached rack-hotend (WTM) firmware alongside the module list.
|
||||
// Inert for non-rack printers (rack firmware map is empty).
|
||||
m_nozzle_system->ClearFirmwareInfoWTM();
|
||||
}
|
||||
|
||||
void MachineObject::store_version_info(const DevFirmwareVersionInfo& info)
|
||||
@@ -872,6 +884,13 @@ void MachineObject::store_version_info(const DevFirmwareVersionInfo& info)
|
||||
cutting_module_version_info = info;
|
||||
} else if (info.isExtinguishSystem()) {
|
||||
extinguish_version_info = info;
|
||||
} else if (info.isWTM()) {
|
||||
// Route rack-hotend / extruder-nozzle firmware into the nozzle system so
|
||||
// the rack upgrade UI can read per-nozzle versions. isWTM() is false for every non-rack
|
||||
// printer's modules, so this branch never fires outside H2C.
|
||||
m_nozzle_system->AddFirmwareInfoWTM(info);
|
||||
} else if (info.isFilaTrackSwitch()) {
|
||||
filatrack_version_info = info;
|
||||
}
|
||||
|
||||
module_vers.emplace(info.name, info);
|
||||
@@ -1554,7 +1573,7 @@ int MachineObject::check_resume_condition()
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int MachineObject::command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp, int new_temp)
|
||||
int MachineObject::command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp, int new_temp, std::optional<int> extruder_id)
|
||||
{
|
||||
json j;
|
||||
try {
|
||||
@@ -1586,6 +1605,13 @@ int MachineObject::command_ams_change_filament(bool load, std::string ams_id, st
|
||||
j["print"]["slot_id"] = atoi(slot_id.c_str());
|
||||
}
|
||||
|
||||
// Filament Track Switch: route the load to the chosen extruder. Only present when the
|
||||
// caller supplied a value (FTS-installed+ready path), so every other caller is unchanged.
|
||||
if (extruder_id.has_value())
|
||||
{
|
||||
j["print"]["extruder_id"] = *extruder_id;
|
||||
}
|
||||
|
||||
} catch (const std::exception &) {}
|
||||
return this->publish_json(j);
|
||||
}
|
||||
@@ -1974,6 +2000,10 @@ int MachineObject::command_set_pa_calibration(const std::vector<PACalibResult> &
|
||||
j["print"]["filaments"][i]["n_coef"] = std::to_string(pa_calib_values[i].n_coef);
|
||||
else
|
||||
j["print"]["filaments"][i]["n_coef"] = "0.0";
|
||||
if (pa_calib_values[i].nozzle_pos_id >= 0) {
|
||||
j["print"]["filaments"][i]["nozzle_pos"] = pa_calib_values[i].nozzle_pos_id;
|
||||
j["print"]["filaments"][i]["nozzle_sn"] = pa_calib_values[i].nozzle_sn;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_set: " << j.dump();
|
||||
@@ -1992,6 +2022,10 @@ int MachineObject::command_delete_pa_calibration(const PACalibIndexInfo& pa_cali
|
||||
j["print"]["nozzle_id"] = _generate_nozzle_id(pa_calib.nozzle_volume_type, to_string_nozzle_diameter(pa_calib.nozzle_diameter)).ToStdString();
|
||||
j["print"]["filament_id"] = pa_calib.filament_id;
|
||||
j["print"]["cali_idx"] = pa_calib.cali_idx;
|
||||
if (pa_calib.nozzle_pos_id >= 0) {
|
||||
j["print"]["nozzle_pos"] = pa_calib.nozzle_pos_id;
|
||||
j["print"]["nozzle_sn"] = pa_calib.nozzle_sn;
|
||||
}
|
||||
j["print"]["nozzle_diameter"] = to_string_nozzle_diameter(pa_calib.nozzle_diameter);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_del: " << j.dump();
|
||||
@@ -2012,6 +2046,11 @@ int MachineObject::command_get_pa_calibration_tab(const PACalibExtruderInfo &cal
|
||||
j["print"]["nozzle_id"] = _generate_nozzle_id(calib_info.nozzle_volume_type, to_string_nozzle_diameter(calib_info.nozzle_diameter)).ToStdString();
|
||||
j["print"]["nozzle_diameter"] = to_string_nozzle_diameter(calib_info.nozzle_diameter);
|
||||
|
||||
if (calib_info.nozzle_pos_id >= 0) {
|
||||
j["print"]["nozzle_pos"] = calib_info.nozzle_pos_id;
|
||||
j["print"]["nozzle_sn"] = calib_info.nozzle_sn;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_get: " << j.dump();
|
||||
request_tab_from_bbs = true;
|
||||
return this->publish_json(j);
|
||||
@@ -2038,6 +2077,10 @@ int MachineObject::commnad_select_pa_calibration(const PACalibIndexInfo& pa_cali
|
||||
j["print"]["slot_id"] = pa_calib_info.slot_id;
|
||||
j["print"]["cali_idx"] = pa_calib_info.cali_idx;
|
||||
j["print"]["filament_id"] = pa_calib_info.filament_id;
|
||||
if (pa_calib_info.nozzle_pos_id >= 0) {
|
||||
j["print"]["nozzle_pos"] = pa_calib_info.nozzle_pos_id;
|
||||
j["print"]["nozzle_sn"] = pa_calib_info.nozzle_sn;
|
||||
}
|
||||
j["print"]["nozzle_diameter"] = to_string_nozzle_diameter(pa_calib_info.nozzle_diameter);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_sel: " << j.dump();
|
||||
@@ -2377,6 +2420,7 @@ void MachineObject::reset()
|
||||
jobState_ = 0;
|
||||
m_plate_index = -1;
|
||||
device_cert_installed = false;
|
||||
clear_auto_nozzle_mapping();// reset nozzle mapping
|
||||
|
||||
// reset print_json
|
||||
json empty_j;
|
||||
@@ -2894,6 +2938,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
}
|
||||
|
||||
m_fan->ParseV2_0(jj);
|
||||
m_fila_switch->ParseFilaSwitchInfo(jj);
|
||||
|
||||
if (jj.contains("support_filament_backup")) {
|
||||
if (jj["support_filament_backup"].is_boolean()) {
|
||||
@@ -2998,6 +3043,8 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
|
||||
|
||||
if (jj.contains("command")) {
|
||||
m_nozzle_mapping_ptr->ParseAutoNozzleMapping(jj);
|
||||
|
||||
if (jj["command"].get<std::string>() == "ams_change_filament") {
|
||||
if (jj.contains("errno")) {
|
||||
if (jj["errno"].is_number()) {
|
||||
@@ -4080,6 +4127,14 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
pa_calib_result.nozzle_volume_type = convert_to_nozzle_type((*it)["nozzle_id"].get<std::string>());
|
||||
}
|
||||
|
||||
if ((*it).contains("nozzle_pos")) {
|
||||
pa_calib_result.nozzle_pos_id = (*it)["nozzle_pos"].get<int>();
|
||||
}
|
||||
|
||||
if ((*it).contains("nozzle_sn")) {
|
||||
pa_calib_result.nozzle_sn = (*it)["nozzle_sn"].get<std::string>();
|
||||
}
|
||||
|
||||
if (jj["nozzle_diameter"].is_number_float()) {
|
||||
pa_calib_result.nozzle_diameter = jj["nozzle_diameter"].get<float>();
|
||||
} else if (jj["nozzle_diameter"].is_string()) {
|
||||
@@ -4177,6 +4232,14 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
pa_calib_result.nozzle_volume_type = NozzleVolumeType::nvtStandard;
|
||||
}
|
||||
|
||||
if (it->contains("nozzle_pos")) {
|
||||
pa_calib_result.nozzle_pos_id = (*it)["nozzle_pos"].get<int>();
|
||||
}
|
||||
|
||||
if (it->contains("nozzle_sn")) {
|
||||
pa_calib_result.nozzle_sn = (*it)["nozzle_sn"].get<std::string>();
|
||||
}
|
||||
|
||||
if ((*it)["k_value"].is_number_float())
|
||||
pa_calib_result.k_value = (*it)["k_value"].get<float>();
|
||||
else if ((*it)["k_value"].is_string())
|
||||
@@ -5002,6 +5065,7 @@ void MachineObject::parse_new_info(json print)
|
||||
is_support_user_preset = get_flag_bits(fun, 11);
|
||||
is_support_door_open_check = get_flag_bits(fun, 12);
|
||||
is_support_nozzle_blob_detection = get_flag_bits(fun, 13);
|
||||
m_nozzle_system->SetSupportNozzleRack(get_flag_bits(fun, 60)); // H2C hotend rack support (device-side gate for the sync dialog)
|
||||
is_support_upgrade_kit = get_flag_bits(fun, 14);
|
||||
is_support_internal_timelapse = get_flag_bits(fun, 28);
|
||||
m_support_mqtt_homing = get_flag_bits(fun, 32);
|
||||
@@ -5030,6 +5094,7 @@ void MachineObject::parse_new_info(json print)
|
||||
if (!fun2.empty()) {
|
||||
is_support_print_with_emmc = get_flag_bits_no_border(fun2, 0) == 1;
|
||||
is_support_remote_dry = (get_flag_bits_no_border(fun2, 5) == 1);
|
||||
is_support_check_track_switch_match_slice_printer = get_flag_bits_no_border(fun2, 19) == 1;
|
||||
}
|
||||
|
||||
/*aux*/
|
||||
@@ -5065,7 +5130,10 @@ void MachineObject::parse_new_info(json print)
|
||||
|
||||
DevBed::ParseV2_0(device,m_bed);
|
||||
|
||||
if (device.contains("nozzle")) { DevNozzleSystemParser::ParseV2_0(device["nozzle"], m_nozzle_system); }
|
||||
// Pass the whole device json: ParseV2_0 reads ext/rack nozzles from device["nozzle"] and the
|
||||
// hotend-rack state from device["holder"] (rack data lives outside device["nozzle"]). It is a
|
||||
// no-op for devices that report neither key, so non-rack machines are unaffected.
|
||||
DevNozzleSystemParser::ParseV2_0(device, m_nozzle_system);
|
||||
if (device.contains("extruder")) { ExtderSystemParser::ParseV2_0(device["extruder"], m_extder_system);}
|
||||
if (device.contains("ext_tool")) { DevExtensionToolParser::ParseV2_0(device["ext_tool"], m_extension_tool); }
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <unordered_set>
|
||||
#include <optional>
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include "nlohmann/json.hpp"
|
||||
@@ -83,10 +84,12 @@ class DevExtensionTool;
|
||||
class DevExtderSystem;
|
||||
class DevFan;
|
||||
class DevFilaSystem;
|
||||
class DevFilaSwitch;
|
||||
class DevPrintOptions;
|
||||
class DevHMS;
|
||||
class DevLamp;
|
||||
class DevNozzleSystem;
|
||||
class DevNozzleMappingCtrl;
|
||||
class DeviceManager;
|
||||
class DevStorage;
|
||||
struct DevPrintTaskRatingInfo;
|
||||
@@ -115,6 +118,7 @@ private:
|
||||
DevExtderSystem* m_extder_system;
|
||||
DevNozzleSystem* m_nozzle_system;
|
||||
std::shared_ptr<DevFilaSystem> m_fila_system;
|
||||
DevFilaSwitch* m_fila_switch;
|
||||
DevFan* m_fan;
|
||||
DevBed * m_bed;
|
||||
DevStorage* m_storage;
|
||||
@@ -131,6 +135,11 @@ private:
|
||||
/*Config*/
|
||||
DevConfig* m_config;
|
||||
|
||||
/* print-dispatch nozzle mapping (H2C hotend rack). Created unconditionally in the ctor so
|
||||
get_nozzle_mapping_result() is always valid; stays empty (no result attached) for every
|
||||
non-rack printer. */
|
||||
std::shared_ptr<DevNozzleMappingCtrl> m_nozzle_mapping_ptr;
|
||||
|
||||
public:
|
||||
MachineObject(DeviceManager* manager, NetworkAgent* agent, std::string name, std::string id, std::string ip);
|
||||
~MachineObject();
|
||||
@@ -329,7 +338,12 @@ public:
|
||||
|
||||
DevNozzleSystem* GetNozzleSystem() const { return m_nozzle_system;}
|
||||
|
||||
/* print-dispatch nozzle mapping (H2C hotend rack); result stays empty for non-rack printers */
|
||||
std::shared_ptr<DevNozzleMappingCtrl> get_nozzle_mapping_result() const { return m_nozzle_mapping_ptr; }
|
||||
void clear_auto_nozzle_mapping();// defined in DevMappingNozzle.cpp
|
||||
|
||||
std::shared_ptr<DevFilaSystem> GetFilaSystem() const { return m_fila_system;}
|
||||
DevFilaSwitch* GetFilaSwitch() const { return m_fila_switch;}
|
||||
bool HasAms() const;
|
||||
|
||||
DevLamp* GetLamp() const { return m_lamp; }
|
||||
@@ -365,6 +379,7 @@ public:
|
||||
DevFirmwareVersionInfo laser_version_info;
|
||||
DevFirmwareVersionInfo cutting_module_version_info;
|
||||
DevFirmwareVersionInfo extinguish_version_info;
|
||||
DevFirmwareVersionInfo filatrack_version_info;
|
||||
std::map<std::string, DevFirmwareVersionInfo> module_vers;
|
||||
std::map<std::string, DevFirmwareVersionInfo> new_ver_list;
|
||||
bool m_new_ver_list_exist = false;
|
||||
@@ -621,6 +636,7 @@ public:
|
||||
// fun2
|
||||
bool is_support_print_with_emmc{false};
|
||||
bool is_support_remote_dry = false;
|
||||
bool is_support_check_track_switch_match_slice_printer{false};
|
||||
|
||||
bool installed_upgrade_kit{false};
|
||||
int bed_temperature_limit = -1;
|
||||
@@ -734,7 +750,7 @@ public:
|
||||
int check_resume_condition();
|
||||
// ams controls
|
||||
//int command_ams_switch(int tray_index, int old_temp = 210, int new_temp = 210);
|
||||
int command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp = 210, int new_temp = 210);
|
||||
int command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp = 210, int new_temp = 210, std::optional<int> extruder_id = std::nullopt);
|
||||
int command_ams_user_settings(bool start_read_opt, bool tray_read_opt, bool remain_flag = false);
|
||||
int command_ams_switch_filament(bool switch_filament);
|
||||
int command_ams_air_print_detect(bool air_print_detect);
|
||||
|
||||
@@ -4,5 +4,15 @@ list(APPEND SLIC3R_GUI_SOURCES
|
||||
GUI/DeviceTab/uiAmsHumidityPopup.cpp
|
||||
GUI/DeviceTab/uiDeviceUpdateVersion.h
|
||||
GUI/DeviceTab/uiDeviceUpdateVersion.cpp
|
||||
GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.h
|
||||
GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.cpp
|
||||
GUI/DeviceTab/wgtDeviceNozzleRack.h
|
||||
GUI/DeviceTab/wgtDeviceNozzleRack.cpp
|
||||
GUI/DeviceTab/wgtDeviceNozzleRackUpdate.h
|
||||
GUI/DeviceTab/wgtDeviceNozzleRackUpdate.cpp
|
||||
GUI/DeviceTab/wgtDeviceNozzleSelect.h
|
||||
GUI/DeviceTab/wgtDeviceNozzleSelect.cpp
|
||||
GUI/DeviceTab/wgtMsgBox.h
|
||||
GUI/DeviceTab/wgtMsgBox.cpp
|
||||
)
|
||||
set(SLIC3R_GUI_SOURCES ${SLIC3R_GUI_SOURCES} PARENT_SCOPE)
|
||||
@@ -0,0 +1,700 @@
|
||||
//**********************************************************/
|
||||
/* File: wgtDeviceNozzleRack.cpp
|
||||
* Description: The Device-tab panel with the toolhead nozzle and the H2C induction hotend rack.
|
||||
*
|
||||
* \n class wgtDeviceNozzleRack;
|
||||
* \n class wgtDeviceNozzleRackToolHead;
|
||||
* \n class wgtDeviceNozzleRackArea;
|
||||
* \n class wgtDeviceNozzleRackPos;
|
||||
*
|
||||
* The wgtDeviceNozzleRackNozzleItem tile and the EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED event live in
|
||||
* wgtDeviceNozzleRackNozzleItem.{h,cpp}, so they are not redefined here.
|
||||
//**********************************************************/
|
||||
|
||||
#include "wgtDeviceNozzleRack.h"
|
||||
#include "wgtDeviceNozzleRackUpdate.h"
|
||||
|
||||
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
|
||||
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/MainFrame.hpp"
|
||||
#include "slic3r/GUI/wxExtensions.hpp"
|
||||
|
||||
#include "slic3r/GUI/Widgets/Button.hpp"
|
||||
#include "slic3r/GUI/Widgets/Label.hpp"
|
||||
|
||||
#define WX_DIP_SIZE(x, y) wxSize(FromDIP(x), FromDIP(y))
|
||||
|
||||
#define L_RAW_A_STR _L("Row A")
|
||||
#define L_RAW_B_STR _L("Row B")
|
||||
|
||||
// Orca: StateColor lacks these grey constants, so mirror the values here.
|
||||
static const wxColour WGT_GREY200 = wxColour(248, 248, 248);
|
||||
static const wxColour WGT_GREY300 = wxColour(238, 238, 238);
|
||||
|
||||
static wxColour s_hgreen_clr("#009688");
|
||||
|
||||
namespace Slic3r::GUI
|
||||
{
|
||||
|
||||
// Tints the "yellow" template pixels of a nozzle bitmap with the loaded filament colour.
|
||||
// Duplicates the file-local helper of the same purpose in wgtDeviceNozzleRackNozzleItem.cpp.
|
||||
static wxBitmap SetNozzleBmpColor(const wxBitmap& bmp, const std::string& color_str) {
|
||||
if(color_str.empty()) return bmp;
|
||||
|
||||
wxImage img = bmp.ConvertToImage();
|
||||
wxColour color("#" + color_str);
|
||||
|
||||
for (int y = 0; y < img.GetHeight(); ++y) {
|
||||
for (int x = 0; x < img.GetWidth(); ++x) {
|
||||
unsigned char r = img.GetRed(x, y);
|
||||
unsigned char g = img.GetGreen(x, y);
|
||||
unsigned char b = img.GetBlue(x, y);
|
||||
|
||||
/*replace yellow with color*/
|
||||
if ( r >= 180 && g >= 180 && b <= 150) {
|
||||
img.SetRGB(x, y, color.Red(), color.Green(), color.Blue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return wxBitmap(img, -1, bmp.GetScaleFactor());
|
||||
}
|
||||
|
||||
// Orca: StateColor has no gray button style, so define one file-local. This panel is its only
|
||||
// consumer, so keeping it here avoids touching the shared StateColor widget.
|
||||
static StateColor s_button_style_gray()
|
||||
{
|
||||
return StateColor(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
|
||||
std::pair<wxColour, int>(*wxWHITE, StateColor::Focused),
|
||||
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
|
||||
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
|
||||
}
|
||||
|
||||
wgtDeviceNozzleRack::wgtDeviceNozzleRack(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
|
||||
: wxPanel(parent, id, pos, size, style)
|
||||
{
|
||||
CreateGui();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRack::CreateGui()
|
||||
{
|
||||
m_toolhead_panel = new wgtDeviceNozzleRackToolHead(this);
|
||||
m_rack_area = new wgtDeviceNozzleRackArea(this);
|
||||
|
||||
wxPanel* separator = new wxPanel(this);
|
||||
separator->SetMaxSize(wxSize(FromDIP(1), -1));
|
||||
separator->SetMinSize(wxSize(FromDIP(1), -1));
|
||||
separator->SetBackgroundColour(WGT_GREY300);
|
||||
|
||||
wxSizer* main_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
main_sizer->AddStretchSpacer();
|
||||
main_sizer->Add(m_toolhead_panel, 0, wxEXPAND);
|
||||
main_sizer->Add(separator, 0, wxEXPAND);
|
||||
main_sizer->Add(m_rack_area, 0, wxEXPAND);
|
||||
main_sizer->AddStretchSpacer();
|
||||
|
||||
SetSizer(main_sizer);
|
||||
SetMaxSize(WX_DIP_SIZE(586, -1));
|
||||
SetMinSize(WX_DIP_SIZE(586, -1));
|
||||
SetSize(WX_DIP_SIZE(586, -1));
|
||||
Layout();
|
||||
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRack::UpdateRackInfo(std::shared_ptr<DevNozzleRack> rack)
|
||||
{
|
||||
if (!rack->IsSupported()) { return; }
|
||||
|
||||
m_nozzle_rack = rack;
|
||||
if (m_nozzle_rack.expired()) { return; }
|
||||
|
||||
DevNozzleSystem* nozzle_system = m_nozzle_rack.lock()->GetNozzleSystem();
|
||||
if (nozzle_system)
|
||||
{
|
||||
m_toolhead_panel->UpdateToolHeadInfo(nozzle_system->GetExtNozzle(MAIN_EXTRUDER_ID));
|
||||
m_rack_area->UpdateRackInfo(m_nozzle_rack);
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRack::Rescale()
|
||||
{
|
||||
m_toolhead_panel->Rescale();
|
||||
m_rack_area->Rescale();
|
||||
Layout();
|
||||
}
|
||||
|
||||
class wgtDeviceNozzleRackTitle : public StaticBox
|
||||
{
|
||||
public:
|
||||
wgtDeviceNozzleRackTitle(wxWindow* parent, const wxString& title) : StaticBox(parent)
|
||||
{
|
||||
SetBackgroundColour(WGT_GREY200);
|
||||
SetBorderColor(*wxWHITE);
|
||||
SetCornerRadius(0);
|
||||
|
||||
m_title_label = new Label(this, title);
|
||||
m_title_label->SetFont(Label::Body_14);
|
||||
m_title_label->SetBackgroundColour(WGT_GREY200);
|
||||
|
||||
wxSizer* title_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
title_sizer->AddStretchSpacer();
|
||||
title_sizer->Add(m_title_label, 0, wxEXPAND | wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(5));
|
||||
title_sizer->AddStretchSpacer();
|
||||
SetSizer(title_sizer);
|
||||
};
|
||||
|
||||
public:
|
||||
void SetLabel(const wxString& new_label) { m_title_label->SetLabel(new_label); }
|
||||
|
||||
private:
|
||||
Label* m_title_label;
|
||||
};
|
||||
|
||||
|
||||
void wgtDeviceNozzleRackToolHead::CreateGui()
|
||||
{
|
||||
wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// Create Header
|
||||
wgtDeviceNozzleRackTitle* title_box = new wgtDeviceNozzleRackTitle(this, _L("Toolhead"));
|
||||
mainSizer->Add(title_box, 0, wxEXPAND | wxTOP);
|
||||
mainSizer->AddStretchSpacer();
|
||||
|
||||
// Image
|
||||
m_extruder_nozzle_empty = new ScalableBitmap(this, "dev_rack_toolhead_empty", 98);
|
||||
m_extruder_nozzle_normal = new ScalableBitmap(this, "dev_rack_toolhead_normal", 98);
|
||||
m_toolhead_icon = new wxStaticBitmap(this, wxID_ANY, m_extruder_nozzle_empty->bmp(), wxDefaultPosition, WX_DIP_SIZE(98, 98));
|
||||
mainSizer->Add(m_toolhead_icon, 0, wxALIGN_CENTRE_HORIZONTAL | wxTOP, FromDIP(20));
|
||||
|
||||
// Nozzle info
|
||||
m_nozzle_diamenter_label = new Label(this);
|
||||
m_nozzle_diamenter_label->SetFont(Label::Body_13);
|
||||
m_nozzle_diamenter_label->SetBackgroundColour(*wxWHITE);
|
||||
mainSizer->Add(m_nozzle_diamenter_label, 0, wxALIGN_CENTRE_HORIZONTAL | wxBOTTOM | wxTOP, FromDIP(5));
|
||||
|
||||
m_nozzle_flowtype_label = new Label(this);
|
||||
m_nozzle_flowtype_label->SetFont(Label::Body_13);
|
||||
m_nozzle_flowtype_label->SetBackgroundColour(*wxWHITE);
|
||||
mainSizer->Add(m_nozzle_flowtype_label, 0, wxALIGN_CENTRE_HORIZONTAL);
|
||||
mainSizer->AddStretchSpacer();
|
||||
|
||||
// Set sizer
|
||||
SetSizer(mainSizer);
|
||||
SetMaxSize(WX_DIP_SIZE(132, -1));
|
||||
SetMinSize(WX_DIP_SIZE(132, -1));
|
||||
SetSize(WX_DIP_SIZE(132, -1));
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackToolHead::UpdateToolHeadInfo(const DevNozzle& extruder_nozzle)
|
||||
{
|
||||
/* Labels */
|
||||
if (extruder_nozzle.IsEmpty())
|
||||
{
|
||||
m_nozzle_diamenter_label->Show(false);
|
||||
m_nozzle_flowtype_label->SetLabel(_L("Empty"));
|
||||
}
|
||||
else if (extruder_nozzle.IsUnknown())
|
||||
{
|
||||
m_nozzle_diamenter_label->Show(false);
|
||||
m_nozzle_flowtype_label->SetLabel(_L("Unknown"));
|
||||
}
|
||||
else if (extruder_nozzle.IsAbnormal())
|
||||
{
|
||||
m_nozzle_diamenter_label->Show(false);
|
||||
m_nozzle_flowtype_label->SetLabel(_L("Error"));
|
||||
}
|
||||
else /*extruder_nozzle.IsNormal()*/
|
||||
{
|
||||
m_nozzle_diamenter_label->Show(true);
|
||||
m_nozzle_diamenter_label->SetLabel(extruder_nozzle.GetNozzleDiameterStr());
|
||||
m_nozzle_flowtype_label->SetLabel(extruder_nozzle.GetNozzleFlowTypeStr());
|
||||
}
|
||||
|
||||
/* Icon*/
|
||||
bool extruder_exist = !extruder_nozzle.IsEmpty();
|
||||
if (m_extruder_nozzle_exist != extruder_exist)
|
||||
{
|
||||
m_extruder_nozzle_exist = extruder_exist;
|
||||
m_filament_color = extruder_nozzle.GetFilamentColor();
|
||||
m_toolhead_icon->SetBitmap(m_extruder_nozzle_exist ? SetNozzleBmpColor(m_extruder_nozzle_normal->bmp(), m_filament_color) : m_extruder_nozzle_empty->bmp());
|
||||
m_toolhead_icon->Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackToolHead::Rescale()
|
||||
{
|
||||
m_extruder_nozzle_normal->msw_rescale();
|
||||
m_extruder_nozzle_empty->msw_rescale();
|
||||
m_toolhead_icon->SetBitmap(m_extruder_nozzle_exist ? SetNozzleBmpColor(m_extruder_nozzle_normal->bmp(), m_filament_color) : m_extruder_nozzle_empty->bmp());
|
||||
|
||||
Layout();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackArea::CreateGui()
|
||||
{
|
||||
wxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// Create Header
|
||||
m_title_nozzle_rack = new wgtDeviceNozzleRackTitle(this, _L("Induction Hotend Rack"));
|
||||
main_sizer->Add(m_title_nozzle_rack, 0, wxEXPAND | wxTOP);
|
||||
|
||||
// Create Simple Book
|
||||
m_simple_book = new wxSimplebook(this, wxID_ANY);
|
||||
|
||||
wxSizer* content_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_panel_content = new wxPanel(m_simple_book, wxID_ANY);
|
||||
m_panel_refresh = new wxPanel(m_simple_book, wxID_ANY);
|
||||
|
||||
// Create Hotends ans Rack Position Panel
|
||||
wxSizer* hotends_rack_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
// Hotends
|
||||
m_hotends_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_arow_nozzles_box = CreateNozzleBox( { 0, 2, 4});
|
||||
m_brow_nozzles_box = CreateNozzleBox( { 1, 3, 5});
|
||||
m_hotends_sizer->Add(m_arow_nozzles_box);
|
||||
m_hotends_sizer->Add(m_brow_nozzles_box);
|
||||
hotends_rack_sizer->Add(m_hotends_sizer, 0, wxLEFT, FromDIP(8));
|
||||
|
||||
// Rack
|
||||
m_rack_pos_panel = new wgtDeviceNozzleRackPos(m_panel_content);
|
||||
hotends_rack_sizer->Add(m_rack_pos_panel, 0, wxEXPAND);
|
||||
content_sizer->Add(hotends_rack_sizer, 0);
|
||||
|
||||
wxSizer* btn_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_btn_hotends_infos = new Button(m_panel_content, _L("Hotends Info"));
|
||||
m_btn_hotends_infos->SetFont(Label::Body_12);
|
||||
m_btn_hotends_infos->SetBackgroundColor(s_button_style_gray());
|
||||
m_btn_hotends_infos->SetBackgroundColour(*wxWHITE);
|
||||
m_btn_hotends_infos->Bind(wxEVT_BUTTON, &wgtDeviceNozzleRackArea::OnBtnHotendsInfos, this);
|
||||
|
||||
m_btn_read_all = new Button(m_panel_content, _L("Read All"));
|
||||
m_btn_read_all->SetFont(Label::Body_12);
|
||||
m_btn_read_all->SetBackgroundColor(s_button_style_gray());
|
||||
m_btn_read_all->SetBackgroundColour(*wxWHITE);
|
||||
m_btn_read_all->Bind(wxEVT_BUTTON, &wgtDeviceNozzleRackArea::OnBtnReadAll, this);
|
||||
|
||||
btn_sizer->Add(m_btn_hotends_infos, 0, wxLEFT);
|
||||
btn_sizer->Add(m_btn_read_all, 0, wxLEFT, FromDIP(5));
|
||||
content_sizer->Add(btn_sizer, 0, wxLEFT, FromDIP(10));
|
||||
|
||||
/* refresh panel */
|
||||
wxSizer* refresh_sizer = CreateRefreshBook(m_panel_refresh);
|
||||
|
||||
m_panel_content->SetSizer(content_sizer);
|
||||
m_panel_refresh->SetSizer(refresh_sizer);
|
||||
m_simple_book->AddPage(m_panel_content, "Content");
|
||||
m_simple_book->AddPage(m_panel_refresh, "Refresh");
|
||||
main_sizer->Add(m_simple_book, 1, wxEXPAND);
|
||||
|
||||
m_simple_book->SetSelection(0);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
wxSizer* wgtDeviceNozzleRackArea::CreateRefreshBook(wxPanel* parent)
|
||||
{
|
||||
wxSizer* refresh_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
std::vector<std::string> list{"ams_rfid_1", "ams_rfid_2", "ams_rfid_3", "ams_rfid_4"};
|
||||
m_refresh_icon = new AnimaIcon(parent, wxID_ANY, list, "refresh_printer", 100);
|
||||
m_refresh_icon->SetMinSize(wxSize(FromDIP(25), FromDIP(25)));
|
||||
|
||||
wxSizer* progress_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
Label* progress_prefix = new Label(parent, _L("Reading "));
|
||||
progress_prefix->SetBackgroundColour(*wxWHITE);
|
||||
m_progress_refresh = new Label(parent, "(1/6)");
|
||||
m_progress_refresh->SetFont(Label::Body_14);
|
||||
m_progress_refresh->SetBackgroundColour(*wxWHITE);
|
||||
m_progress_refresh->SetForegroundColour(*wxGREEN);
|
||||
Label* progress_suffix = new Label(parent, " ...");
|
||||
progress_suffix->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
progress_sizer->Add(progress_prefix, 0, wxLEFT);
|
||||
progress_sizer->Add(m_progress_refresh, 0, wxLEFT);
|
||||
progress_sizer->Add(progress_suffix, 0, wxLEFT);
|
||||
|
||||
Label* refresh_tip = new Label(parent, _L("Please wait"));
|
||||
refresh_tip->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
refresh_sizer->Add(0, 0, 1, wxEXPAND, 0);
|
||||
refresh_sizer->Add(m_refresh_icon, 0, wxALIGN_CENTER_HORIZONTAL, 0);
|
||||
refresh_sizer->Add(progress_sizer, 0, wxALIGN_CENTER_HORIZONTAL, FromDIP(0));
|
||||
refresh_sizer->Add(refresh_tip, 0, wxALIGN_CENTER_HORIZONTAL, FromDIP(0));
|
||||
refresh_sizer->Add(0, 0, 1, wxEXPAND, 0);
|
||||
|
||||
return refresh_sizer;
|
||||
}
|
||||
|
||||
StaticBox* wgtDeviceNozzleRackArea::CreateNozzleBox(const std::vector<int> nozzle_idxes)
|
||||
{
|
||||
StaticBox* nozzle_box = new StaticBox(m_panel_content);
|
||||
nozzle_box->SetBackgroundColor(*wxWHITE);
|
||||
nozzle_box->SetBorderColor(*wxWHITE);
|
||||
nozzle_box->SetCornerRadius(0);
|
||||
|
||||
wxSizer* h_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
for (auto start_idx : nozzle_idxes)
|
||||
{
|
||||
wgtDeviceNozzleRackNozzleItem* nozzle_item = new wgtDeviceNozzleRackNozzleItem(nozzle_box, start_idx);
|
||||
m_nozzle_items[start_idx] = nozzle_item;
|
||||
h_sizer->Add(nozzle_item, 0, wxALL, FromDIP(8));
|
||||
}
|
||||
|
||||
nozzle_box->SetSizer(h_sizer);
|
||||
return nozzle_box;
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackArea::UpdateNozzleItems(const std::unordered_map<int, wgtDeviceNozzleRackNozzleItem*>& nozzle_items,
|
||||
std::shared_ptr<DevNozzleRack> nozzle_rack)
|
||||
{
|
||||
for (auto iter : nozzle_items)
|
||||
{
|
||||
iter.second->Update(nozzle_rack);
|
||||
}
|
||||
|
||||
/*update nozzle possition and background*/
|
||||
if (nozzle_rack->GetReadingCount() != 0)
|
||||
{
|
||||
m_progress_refresh->SetLabel(wxString::Format("(%d/%d)", nozzle_rack->GetReadingIdx(), nozzle_rack->GetReadingCount()));
|
||||
if(!m_refresh_icon->IsPlaying()) {
|
||||
m_simple_book->SetSelection(1);
|
||||
m_refresh_icon->Play();
|
||||
}
|
||||
return;
|
||||
} else{
|
||||
m_refresh_icon->Stop();
|
||||
m_simple_book->SetSelection(0);
|
||||
}
|
||||
|
||||
const DevNozzleRack::RackPos new_pos = nozzle_rack->GetPosition();
|
||||
const DevNozzleRack::RackStatus new_status = nozzle_rack->GetStatus();
|
||||
if (m_rack_pos != new_pos || m_rack_status != new_status)
|
||||
{
|
||||
m_rack_pos = new_pos;
|
||||
m_rack_status = new_status;
|
||||
if (m_rack_status == DevNozzleRack::RACK_STATUS_IDLE)
|
||||
{
|
||||
m_hotends_sizer->Clear();
|
||||
if (m_rack_pos == DevNozzleRack::RACK_POS_B_TOP)
|
||||
{
|
||||
m_hotends_sizer->Add(m_brow_nozzles_box);
|
||||
m_hotends_sizer->Add(m_arow_nozzles_box);
|
||||
}
|
||||
else if (m_rack_pos == DevNozzleRack::RACK_POS_A_TOP)
|
||||
{
|
||||
m_hotends_sizer->Add(m_arow_nozzles_box);
|
||||
m_hotends_sizer->Add(m_brow_nozzles_box);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_hotends_sizer->Add(m_arow_nozzles_box);
|
||||
m_hotends_sizer->Add(m_brow_nozzles_box);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackArea::UpdateRackInfo(std::weak_ptr<DevNozzleRack> rack)
|
||||
{
|
||||
m_nozzle_rack = rack;
|
||||
const auto& nozzle_rack = rack.lock();
|
||||
if (nozzle_rack)
|
||||
{
|
||||
UpdateNozzleItems(m_nozzle_items, nozzle_rack);
|
||||
m_rack_pos_panel->UpdateRackPos(nozzle_rack);
|
||||
m_btn_read_all->Enable(nozzle_rack->CtrlCanReadAll());
|
||||
}
|
||||
|
||||
if (m_rack_upgrade_dlg && m_rack_upgrade_dlg->IsShown())
|
||||
{
|
||||
m_rack_upgrade_dlg->UpdateRackInfo(nozzle_rack);
|
||||
}
|
||||
};
|
||||
|
||||
void wgtDeviceNozzleRackArea::OnBtnHotendsInfos(wxCommandEvent& evt)
|
||||
{
|
||||
const auto& nozzle_rack = m_nozzle_rack.lock();
|
||||
if (nozzle_rack)
|
||||
{
|
||||
m_rack_upgrade_dlg = new wgtDeviceNozzleRackUpgradeDlg((wxWindow*)wxGetApp().mainframe, nozzle_rack);
|
||||
m_rack_upgrade_dlg->ShowModal();
|
||||
|
||||
delete m_rack_upgrade_dlg;
|
||||
m_rack_upgrade_dlg = nullptr;
|
||||
}
|
||||
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackArea::OnBtnReadAll(wxCommandEvent& evt)
|
||||
{
|
||||
if (const auto nozzle_rack = m_nozzle_rack.lock())
|
||||
{
|
||||
nozzle_rack->CtrlRackReadAll(true);
|
||||
}
|
||||
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackArea::Rescale()
|
||||
{
|
||||
for (auto item : m_nozzle_items)
|
||||
{
|
||||
item.second->Rescale();
|
||||
}
|
||||
|
||||
m_rack_pos_panel->Rescale();
|
||||
m_btn_hotends_infos->Rescale();
|
||||
m_btn_read_all->Rescale();
|
||||
}
|
||||
|
||||
static void s_set_bg_style(StaticBox* box,
|
||||
ScalableButton* btn,
|
||||
Label* label_row,
|
||||
Label* label_row_status,
|
||||
const wxColour& clr)
|
||||
{
|
||||
box->SetBorderColor(clr);
|
||||
box->SetBackgroundColor(clr);
|
||||
btn->SetBackgroundColour(clr);
|
||||
label_row->SetBackgroundColour(clr);
|
||||
label_row_status->SetBackgroundColour(clr);
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackPos::CreateGui()
|
||||
{
|
||||
// RowA
|
||||
m_rowup_panel = new StaticBox(this, wxID_ANY);
|
||||
m_rowup_panel->SetCornerRadius(0);
|
||||
|
||||
wxBoxSizer* rowa_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
rowa_sizer->AddStretchSpacer();
|
||||
m_btn_rowup = new ScalableButton(m_rowup_panel, wxID_ANY, "dev_rack_row_up", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, false, 25);
|
||||
m_btn_rowup->Bind(wxEVT_ENTER_WINDOW, [this](auto&) { SetCursor(wxCURSOR_HAND); });
|
||||
m_btn_rowup->Bind(wxEVT_LEAVE_WINDOW, [this](auto&) { SetCursor(wxCURSOR_ARROW); });
|
||||
m_btn_rowup->Bind(wxEVT_BUTTON, &wgtDeviceNozzleRackPos::OnMoveRackUp, this);
|
||||
rowa_sizer->Add(m_btn_rowup, 0, wxALIGN_CENTER | wxEXPAND | wxLEFT | wxRIGHT, FromDIP(10));
|
||||
|
||||
m_label_rowup_status = new Label(m_rowup_panel);
|
||||
m_label_rowup_status->SetFont(Label::Body_12);
|
||||
m_label_rowup_status->Show(false);
|
||||
rowa_sizer->Add(m_label_rowup_status, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(10));
|
||||
|
||||
m_label_rowup = new Label(m_rowup_panel);
|
||||
m_label_rowup->SetFont(Label::Body_14);
|
||||
rowa_sizer->Add(m_label_rowup, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(10));
|
||||
rowa_sizer->AddStretchSpacer();
|
||||
|
||||
m_rowup_panel->SetSizer(rowa_sizer);
|
||||
|
||||
// homing
|
||||
m_btn_homing = new ScalableButton(this, wxID_ANY, "dev_rack_home", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, false, 25);
|
||||
m_btn_homing->SetBackgroundColour(WGT_GREY200);
|
||||
m_btn_homing->Bind(wxEVT_ENTER_WINDOW, [this](auto&) { SetCursor(wxCURSOR_HAND); });
|
||||
m_btn_homing->Bind(wxEVT_LEAVE_WINDOW, [this](auto&) { SetCursor(wxCURSOR_ARROW); });
|
||||
m_btn_homing->Bind(wxEVT_BUTTON, &wgtDeviceNozzleRackPos::OnBtnHomingRack, this);
|
||||
|
||||
// Row B
|
||||
m_rowbottom_panel = new StaticBox(this, wxID_ANY);
|
||||
m_rowbottom_panel->SetCornerRadius(0);
|
||||
|
||||
wxBoxSizer* rowb_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
rowb_sizer->AddStretchSpacer();
|
||||
|
||||
m_btn_rowbottom_up = new ScalableButton(m_rowbottom_panel, wxID_ANY, "dev_rack_row_up", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, false, 25);
|
||||
m_btn_rowbottom_up->Bind(wxEVT_BUTTON, &wgtDeviceNozzleRackPos::OnMoveRackDown, this);
|
||||
m_btn_rowbottom_up->Bind(wxEVT_ENTER_WINDOW, [this](auto&) { SetCursor(wxCURSOR_HAND); });
|
||||
m_btn_rowbottom_up->Bind(wxEVT_LEAVE_WINDOW, [this](auto&) { SetCursor(wxCURSOR_ARROW); });
|
||||
rowb_sizer->Add(m_btn_rowbottom_up, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(10));
|
||||
|
||||
m_label_rowbottom_status = new Label(m_rowbottom_panel);
|
||||
m_label_rowbottom_status->SetFont(Label::Body_12);
|
||||
m_label_rowbottom_status->Show(false);
|
||||
rowb_sizer->Add(m_label_rowbottom_status, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(10));
|
||||
|
||||
m_label_rowbottom = new Label(m_rowbottom_panel);
|
||||
m_label_rowbottom->SetFont(Label::Body_14);
|
||||
rowb_sizer->Add(m_label_rowbottom, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(10));
|
||||
rowb_sizer->AddStretchSpacer();
|
||||
|
||||
m_rowbottom_panel->SetSizer(rowb_sizer);
|
||||
|
||||
// bg style
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
s_set_bg_style(m_rowup_panel, m_btn_rowup, m_label_rowup, m_label_rowup_status, *wxWHITE);
|
||||
s_set_bg_style(m_rowbottom_panel, m_btn_rowbottom_up, m_label_rowbottom, m_label_rowbottom_status, *wxWHITE);
|
||||
|
||||
// main sizer
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(m_rowup_panel, 1, wxALIGN_TOP | wxEXPAND | wxALIGN_CENTER);
|
||||
main_sizer->Add(m_btn_homing, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(10));
|
||||
main_sizer->Add(m_rowbottom_panel, 1, wxALIGN_BOTTOM | wxEXPAND | wxALIGN_CENTER);
|
||||
SetSizer(main_sizer);
|
||||
|
||||
SetMinSize(WX_DIP_SIZE(85, -1));
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackPos::UpdateRackPos(const std::shared_ptr<DevNozzleRack>& rack)
|
||||
{
|
||||
m_rack = rack;
|
||||
if (rack)
|
||||
{
|
||||
UpdateRackPos(rack->GetPosition(), rack->GetStatus(), rack->GetReadingCount() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void s_show_label(Label* label, const wxString& text)
|
||||
{
|
||||
label->SetLabel(text);
|
||||
label->Show();
|
||||
}
|
||||
|
||||
static void s_show_label(Label* label, const wxString& text, const wxColour& text_color)
|
||||
{
|
||||
label->SetLabel(text);
|
||||
label->SetForegroundColour(StateColor::darkModeColorFor(text_color));
|
||||
label->Show();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackPos::UpdateRackPos(DevNozzleRack::RackPos new_pos,
|
||||
DevNozzleRack::RackStatus new_status, bool is_reading)
|
||||
{
|
||||
// While reading, both rows show a "Running..." status and the move buttons are hidden.
|
||||
if (is_reading)
|
||||
{
|
||||
s_show_label(m_label_rowup, L_RAW_A_STR, *wxBLACK);
|
||||
s_show_label(m_label_rowup_status, _L("Running..."));
|
||||
|
||||
s_show_label(m_label_rowbottom, L_RAW_B_STR, *wxBLACK);
|
||||
s_show_label(m_label_rowbottom_status, _L("Running..."));
|
||||
|
||||
m_btn_rowup->Show(false);
|
||||
m_btn_rowbottom_up->Show(false);
|
||||
|
||||
m_rack_pos = DevNozzleRack::RACK_POS_UNKNOWN;
|
||||
m_rack_status = DevNozzleRack::RACK_STATUS_UNKNOWN;
|
||||
return;
|
||||
}
|
||||
|
||||
if (new_pos != m_rack_pos || m_rack_status != new_status)
|
||||
{
|
||||
m_rack_pos = new_pos;
|
||||
m_rack_status = new_status;
|
||||
|
||||
if (m_rack_status != DevNozzleRack::RACK_STATUS_IDLE)
|
||||
{
|
||||
s_show_label(m_label_rowup, L_RAW_A_STR, *wxBLACK);
|
||||
s_show_label(m_label_rowup_status, _L("Running..."));
|
||||
|
||||
s_show_label(m_label_rowbottom, L_RAW_B_STR, *wxBLACK);
|
||||
s_show_label(m_label_rowbottom_status, _L("Running..."));
|
||||
|
||||
m_btn_rowup->Show(false);
|
||||
m_btn_rowbottom_up->Show(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (new_pos == DevNozzleRack::RACK_POS_A_TOP)
|
||||
{
|
||||
s_show_label(m_label_rowup, L_RAW_A_STR, s_hgreen_clr);
|
||||
s_show_label(m_label_rowup_status, _L("Raised"));
|
||||
|
||||
m_rowbottom_panel->SetBorderColor(*wxWHITE);
|
||||
m_rowbottom_panel->SetBackgroundColor(*wxWHITE);
|
||||
s_show_label(m_label_rowbottom, L_RAW_B_STR, *wxBLACK);
|
||||
m_label_rowbottom_status->Show(false);
|
||||
|
||||
m_btn_rowup->Show(false);
|
||||
m_btn_rowbottom_up->Show(true);
|
||||
}
|
||||
else if (new_pos == DevNozzleRack::RACK_POS_B_TOP)
|
||||
{
|
||||
s_show_label(m_label_rowup, L_RAW_B_STR, s_hgreen_clr);
|
||||
s_show_label(m_label_rowup_status, _L("Raised"));
|
||||
s_show_label(m_label_rowbottom, L_RAW_A_STR, *wxBLACK);
|
||||
m_label_rowbottom_status->Show(false);
|
||||
|
||||
m_btn_rowup->Show(false);
|
||||
m_btn_rowbottom_up->Show(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
s_show_label(m_label_rowup, L_RAW_A_STR, *wxBLACK);
|
||||
m_label_rowup_status->Show(false);
|
||||
|
||||
s_show_label(m_label_rowbottom, L_RAW_B_STR, *wxBLACK);
|
||||
m_label_rowbottom_status->Show(false);
|
||||
|
||||
m_btn_rowup->Show(true);
|
||||
m_btn_rowbottom_up->Show(true);
|
||||
}
|
||||
}
|
||||
|
||||
Layout();
|
||||
Refresh();
|
||||
}
|
||||
};
|
||||
|
||||
void wgtDeviceNozzleRackPos::OnMoveRackUp(wxCommandEvent& evt)
|
||||
{
|
||||
auto rack = m_rack.lock();
|
||||
if (rack)
|
||||
{
|
||||
if (m_label_rowup->GetLabel() == L_RAW_A_STR)
|
||||
{
|
||||
rack->CtrlRackPosMove(DevNozzleRack::RACK_POS_A_TOP);
|
||||
}
|
||||
else if (m_label_rowup->GetLabel() == L_RAW_B_STR)
|
||||
{
|
||||
rack->CtrlRackPosMove(DevNozzleRack::RACK_POS_B_TOP);
|
||||
}
|
||||
}
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackPos::OnMoveRackDown(wxCommandEvent& evt)
|
||||
{
|
||||
auto rack = m_rack.lock();
|
||||
if (rack)
|
||||
{
|
||||
if (m_label_rowbottom->GetLabel() == L_RAW_A_STR)
|
||||
{
|
||||
rack->CtrlRackPosMove(DevNozzleRack::RACK_POS_A_TOP);
|
||||
}
|
||||
else if (m_label_rowbottom->GetLabel() == L_RAW_B_STR)
|
||||
{
|
||||
rack->CtrlRackPosMove(DevNozzleRack::RACK_POS_B_TOP);
|
||||
}
|
||||
}
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackPos::OnBtnHomingRack(wxCommandEvent& evt)
|
||||
{
|
||||
if (auto rack = m_rack.lock())
|
||||
{
|
||||
rack->CtrlRackPosGoHome();
|
||||
}
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackPos::Rescale()
|
||||
{
|
||||
m_btn_rowup->msw_rescale();
|
||||
m_btn_rowbottom_up->msw_rescale();
|
||||
m_btn_homing->msw_rescale();
|
||||
}
|
||||
|
||||
};// end of namespace Slic3r::GUI
|
||||
@@ -0,0 +1,196 @@
|
||||
//**********************************************************/
|
||||
/* File: wgtDeviceNozzleRack.h
|
||||
* Description: The Device-tab panel with the toolhead nozzle and the H2C induction hotend rack.
|
||||
*
|
||||
* \n class wgtDeviceNozzleRack; // toolhead panel + rack area, side by side
|
||||
* \n class wgtDeviceNozzleRackToolHead;
|
||||
* \n class wgtDeviceNozzleRackArea;
|
||||
* \n class wgtDeviceNozzleRackPos;
|
||||
*
|
||||
* The single-nozzle tile class wgtDeviceNozzleRackNozzleItem lives in its own translation unit
|
||||
* (wgtDeviceNozzleRackNozzleItem.{h,cpp}), so it is included here rather than redeclared.
|
||||
* The Hotends-Info upgrade dialog (wgtDeviceNozzleRackUpgradeDlg) is intentionally not wired yet
|
||||
* (see the TODO markers in the .cpp).
|
||||
//**********************************************************/
|
||||
|
||||
#pragma once
|
||||
#include "slic3r/GUI/DeviceCore/DevNozzleRack.h"
|
||||
|
||||
#include "wgtDeviceNozzleRackNozzleItem.h"
|
||||
|
||||
#include "slic3r/GUI/Widgets/StaticBox.hpp"
|
||||
#include "slic3r/GUI/Widgets/AnimaController.hpp"
|
||||
|
||||
#include <wx/panel.h>
|
||||
#include <wx/simplebook.h>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
// Previous definitions
|
||||
class Button;
|
||||
class Label;
|
||||
class ScalableBitmap;
|
||||
class ScalableButton;
|
||||
namespace Slic3r
|
||||
{
|
||||
struct DevNozzle;
|
||||
class DevNozzleRack;
|
||||
namespace GUI
|
||||
{
|
||||
class wgtDeviceNozzleRackArea;
|
||||
class wgtDeviceNozzleRackNozzleItem;
|
||||
class wgtDeviceNozzleRackToolHead;
|
||||
class wgtDeviceNozzleRackPos;
|
||||
class wgtDeviceNozzleRackTitle;
|
||||
class wgtDeviceNozzleRackUpgradeDlg;
|
||||
}
|
||||
};
|
||||
|
||||
namespace Slic3r::GUI
|
||||
{
|
||||
class wgtDeviceNozzleRack : public wxPanel
|
||||
{
|
||||
public:
|
||||
wgtDeviceNozzleRack(wxWindow* parent,
|
||||
wxWindowID id = wxID_ANY,
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize,
|
||||
long style = wxTAB_TRAVERSAL);
|
||||
~wgtDeviceNozzleRack() = default;
|
||||
|
||||
public:
|
||||
void UpdateRackInfo(std::shared_ptr<DevNozzleRack> rack);
|
||||
void Rescale();
|
||||
|
||||
private:
|
||||
void CreateGui();
|
||||
|
||||
private:
|
||||
std::weak_ptr<DevNozzleRack> m_nozzle_rack;
|
||||
|
||||
// GUI
|
||||
wgtDeviceNozzleRackToolHead* m_toolhead_panel{ nullptr };
|
||||
wgtDeviceNozzleRackArea* m_rack_area{ nullptr };
|
||||
};
|
||||
|
||||
|
||||
class wgtDeviceNozzleRackToolHead : public wxPanel
|
||||
{
|
||||
public:
|
||||
wgtDeviceNozzleRackToolHead(wxWindow* parent) : wxPanel(parent) { CreateGui();}
|
||||
|
||||
public:
|
||||
void UpdateToolHeadInfo(const DevNozzle& extruder_nozzle);
|
||||
void Rescale();
|
||||
|
||||
private:
|
||||
void CreateGui();
|
||||
|
||||
private:
|
||||
bool m_extruder_nozzle_exist = false;
|
||||
std::string m_filament_color;
|
||||
|
||||
// GUI
|
||||
ScalableBitmap* m_extruder_nozzle_normal = nullptr;
|
||||
ScalableBitmap* m_extruder_nozzle_empty = nullptr;
|
||||
wxStaticBitmap* m_toolhead_icon;
|
||||
|
||||
Label* m_nozzle_diamenter_label;
|
||||
Label* m_nozzle_flowtype_label;
|
||||
};
|
||||
|
||||
|
||||
class wgtDeviceNozzleRackArea : public wxPanel
|
||||
{
|
||||
public:
|
||||
wgtDeviceNozzleRackArea(wxWindow* parent) : wxPanel(parent) { CreateGui();}
|
||||
|
||||
public:
|
||||
void UpdateRackInfo(std::weak_ptr<DevNozzleRack> rack);
|
||||
void Rescale();
|
||||
|
||||
private:
|
||||
void CreateGui();
|
||||
StaticBox* CreateNozzleBox(const std::vector<int> nozzle_idxes);
|
||||
wxSizer* CreateRefreshBook(wxPanel* parent);
|
||||
|
||||
// updates
|
||||
void UpdateNozzleItems(const std::unordered_map<int, wgtDeviceNozzleRackNozzleItem*>& nozzle_items,
|
||||
std::shared_ptr<DevNozzleRack> nozzle_rack);
|
||||
|
||||
// events
|
||||
void OnBtnHotendsInfos(wxCommandEvent& evt);
|
||||
void OnBtnReadAll(wxCommandEvent& evt);
|
||||
|
||||
private:
|
||||
std::weak_ptr<DevNozzleRack> m_nozzle_rack;
|
||||
DevNozzleRack::RackPos m_rack_pos = DevNozzleRack::RACK_POS_UNKNOWN;
|
||||
DevNozzleRack::RackStatus m_rack_status = DevNozzleRack::RACK_STATUS_UNKNOWN;
|
||||
|
||||
// GUI
|
||||
wxSimplebook* m_simple_book{ nullptr };
|
||||
wxPanel* m_panel_content{ nullptr };
|
||||
wxPanel* m_panel_refresh{ nullptr };
|
||||
|
||||
wgtDeviceNozzleRackTitle* m_title_nozzle_rack;
|
||||
wxBoxSizer* m_hotends_sizer;
|
||||
StaticBox* m_arow_nozzles_box;
|
||||
StaticBox* m_brow_nozzles_box;
|
||||
std::unordered_map<int, wgtDeviceNozzleRackNozzleItem*> m_nozzle_items;
|
||||
|
||||
wgtDeviceNozzleRackPos* m_rack_pos_panel;
|
||||
|
||||
Button* m_btn_hotends_infos;
|
||||
Button* m_btn_read_all;
|
||||
|
||||
/* refresh book */
|
||||
Label* m_progress_refresh{ nullptr };
|
||||
AnimaIcon* m_refresh_icon{ nullptr };
|
||||
|
||||
// "Hotends Info" upgrade dialog. Owned for its ShowModal lifetime by
|
||||
// OnBtnHotendsInfos; UpdateRackInfo forwards live device pushes to it while it is shown.
|
||||
wgtDeviceNozzleRackUpgradeDlg* m_rack_upgrade_dlg = nullptr;
|
||||
};
|
||||
|
||||
class wgtDeviceNozzleRackPos : public wxPanel
|
||||
{
|
||||
public:
|
||||
explicit wgtDeviceNozzleRackPos(wxWindow* parent) : wxPanel(parent) { CreateGui();}
|
||||
|
||||
public:
|
||||
void UpdateRackPos(const std::shared_ptr<DevNozzleRack>& rack);
|
||||
void Rescale();
|
||||
|
||||
private:
|
||||
void CreateGui();
|
||||
|
||||
void UpdateRackPos(DevNozzleRack::RackPos new_pos,
|
||||
DevNozzleRack::RackStatus new_status,
|
||||
bool is_reading);
|
||||
|
||||
// events
|
||||
void OnMoveRackUp(wxCommandEvent& evt);
|
||||
void OnMoveRackDown(wxCommandEvent& evt);
|
||||
void OnBtnHomingRack(wxCommandEvent& evt);
|
||||
|
||||
private:
|
||||
std::weak_ptr<DevNozzleRack> m_rack;
|
||||
DevNozzleRack::RackPos m_rack_pos = DevNozzleRack::RACK_POS_UNKNOWN;
|
||||
DevNozzleRack::RackStatus m_rack_status = DevNozzleRack::RACK_STATUS_UNKNOWN;
|
||||
|
||||
// GUI
|
||||
StaticBox* m_rowup_panel;
|
||||
ScalableButton* m_btn_rowup;
|
||||
Label* m_label_rowup_status{ nullptr };
|
||||
Label* m_label_rowup{ nullptr };
|
||||
|
||||
StaticBox* m_rowbottom_panel;
|
||||
ScalableButton* m_btn_rowbottom_up;
|
||||
Label* m_label_rowbottom_status{ nullptr };
|
||||
Label* m_label_rowbottom{ nullptr };
|
||||
|
||||
ScalableButton* m_btn_homing{ nullptr };
|
||||
};
|
||||
|
||||
};// end of namespace Slic3r::GUI
|
||||
@@ -0,0 +1,347 @@
|
||||
//**********************************************************/
|
||||
/* File: wgtDeviceNozzleRackNozzleItem.cpp
|
||||
* Description: One nozzle cell of the H2C hotend rack view. Holds the
|
||||
* wgtDeviceNozzleRackNozzleItem widget, the SetNozzleBmpColor helper and the layout
|
||||
* constants it needs.
|
||||
//**********************************************************/
|
||||
|
||||
#include "wgtDeviceNozzleRackNozzleItem.h"
|
||||
|
||||
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
|
||||
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/wxExtensions.hpp"
|
||||
|
||||
#include "slic3r/GUI/Widgets/Label.hpp"
|
||||
|
||||
#define WX_DIP_SIZE_46 wxSize(FromDIP(46), FromDIP(46))
|
||||
#define WX_DIP_SIZE(x, y) wxSize(FromDIP(x), FromDIP(y))
|
||||
|
||||
#define WGT_RACK_NOZZLE_SIZE WX_DIP_SIZE(88, 100)
|
||||
|
||||
static wxColour s_gray_clr("#B0B0B0");
|
||||
static wxColour s_hgreen_clr("#009688");
|
||||
static wxColour s_red_clr("#D01B1B");
|
||||
|
||||
wxDEFINE_EVENT(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, wxCommandEvent);
|
||||
|
||||
namespace Slic3r::GUI
|
||||
{
|
||||
|
||||
static wxBitmap SetNozzleBmpColor(const wxBitmap& bmp, const std::string& color_str) {
|
||||
if(color_str.empty()) return bmp;
|
||||
|
||||
wxImage img = bmp.ConvertToImage();
|
||||
wxColour color("#" + color_str);
|
||||
|
||||
for (int y = 0; y < img.GetHeight(); ++y) {
|
||||
for (int x = 0; x < img.GetWidth(); ++x) {
|
||||
unsigned char r = img.GetRed(x, y);
|
||||
unsigned char g = img.GetGreen(x, y);
|
||||
unsigned char b = img.GetBlue(x, y);
|
||||
|
||||
/*replace yellow with color*/
|
||||
if ( r >= 180 && g >= 180 && b <= 150) {
|
||||
img.SetRGB(x, y, color.Red(), color.Green(), color.Blue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return wxBitmap(img, -1, bmp.GetScaleFactor());
|
||||
}
|
||||
|
||||
wgtDeviceNozzleRackNozzleItem::wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id)
|
||||
: StaticBox(parent, wxID_ANY), m_nozzle_id(nozzle_id)
|
||||
{
|
||||
CreateGui();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackNozzleItem::CreateGui()
|
||||
{
|
||||
// Background
|
||||
SetCornerRadius(FromDIP(5));
|
||||
SetBackgroundColor(*wxWHITE);
|
||||
|
||||
// Top H
|
||||
wxSizer *top_h_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_nozzle_label_id = new Label(this);
|
||||
m_nozzle_label_id->SetFont(Label::Body_12);
|
||||
m_nozzle_label_id->SetBackgroundColour(*wxWHITE);
|
||||
m_nozzle_label_id->SetLabel(wxString::Format("%d", m_nozzle_id + 1));
|
||||
|
||||
m_status = NOZZLE_STATUS::NOZZLE_EMPTY;
|
||||
m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);
|
||||
m_nozzle_icon = new wxStaticBitmap(this, wxID_ANY, m_nozzle_empty_image->bmp(), wxDefaultPosition, WX_DIP_SIZE_46);
|
||||
m_nozzle_icon->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
m_nozzle_selected_bitmap = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, WX_DIP_SIZE(20, 20));
|
||||
m_nozzle_selected_bitmap->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
top_h_sizer->Add(m_nozzle_label_id, 0, wxTOP | wxLEFT, FromDIP(6));
|
||||
top_h_sizer->AddStretchSpacer(1);
|
||||
top_h_sizer->Add(m_nozzle_icon, 0, wxTOP, FromDIP(10));
|
||||
top_h_sizer->AddStretchSpacer(1);
|
||||
top_h_sizer->Add(m_nozzle_selected_bitmap, 0, wxTOP | wxRIGHT, FromDIP(2));
|
||||
|
||||
// Bottom V
|
||||
wxBoxSizer* bottom_v = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
wxSizer* label_h_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_nozzle_label_1 = new Label(this);
|
||||
m_nozzle_label_1->SetFont(Label::Body_12);
|
||||
m_nozzle_label_1->SetBackgroundColour(*wxWHITE);
|
||||
m_nozzle_label_1->SetLabel(_L("Empty"));
|
||||
|
||||
label_h_sizer->Add(m_nozzle_label_1, 0, wxALIGN_LEFT);
|
||||
|
||||
auto status_icon = create_scaled_bitmap("dev_rack_nozzle_error_icon", this, 14);
|
||||
m_nozzle_status_icon = new wxStaticBitmap(this, wxID_ANY, status_icon, wxDefaultPosition, WX_DIP_SIZE(14, 14));
|
||||
m_nozzle_status_icon->Bind(wxEVT_LEFT_DOWN, &wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus, this);
|
||||
m_nozzle_status_icon->Bind(wxEVT_ENTER_WINDOW, [this](auto&) { SetCursor(wxCURSOR_HAND); });
|
||||
m_nozzle_status_icon->Bind(wxEVT_LEAVE_WINDOW, [this](auto&) { SetCursor(wxCURSOR_ARROW); });
|
||||
m_nozzle_status_icon->SetBackgroundColour(*wxWHITE);
|
||||
m_nozzle_status_icon->Show(false);
|
||||
|
||||
label_h_sizer->Add(m_nozzle_status_icon, 0, wxALIGN_CENTER | wxLEFT, FromDIP(2));
|
||||
bottom_v->Add(label_h_sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, FromDIP(2));
|
||||
|
||||
m_nozzle_label_2 = new Label(this);
|
||||
m_nozzle_label_2->SetFont(Label::Body_12);
|
||||
m_nozzle_label_2->SetBackgroundColour(*wxWHITE);
|
||||
bottom_v->Add(m_nozzle_label_2, 0, wxALIGN_CENTER_HORIZONTAL);
|
||||
|
||||
// Main sizer
|
||||
wxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(top_h_sizer, 0, wxEXPAND);
|
||||
main_sizer->Add(bottom_v, 0, wxALIGN_CENTER_HORIZONTAL);
|
||||
SetSizer(main_sizer);
|
||||
|
||||
SetMinSize(WGT_RACK_NOZZLE_SIZE);
|
||||
SetMaxSize(WGT_RACK_NOZZLE_SIZE);
|
||||
SetSize(WGT_RACK_NOZZLE_SIZE);
|
||||
Layout();
|
||||
};
|
||||
|
||||
void wgtDeviceNozzleRackNozzleItem::SetSelected(bool selected)
|
||||
{
|
||||
if (!m_enable_select){
|
||||
assert(false && "not support select");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_is_selected != selected) {
|
||||
m_is_selected = selected;
|
||||
if (selected) {
|
||||
if (!m_nozzle_selected_image) {
|
||||
m_nozzle_selected_image = new ScalableBitmap(this, "dev_rack_nozzle_selected", 20);
|
||||
}
|
||||
|
||||
m_nozzle_selected_bitmap->SetBitmap(m_nozzle_selected_image->bmp());
|
||||
SetBorderColor(StateColor::darkModeColorFor(s_hgreen_clr));
|
||||
} else {
|
||||
m_nozzle_selected_bitmap->SetBitmap(wxNullBitmap);
|
||||
SetBorderColor(StateColor::darkModeColorFor(s_gray_clr));
|
||||
}
|
||||
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackNozzleItem::Update(const std::shared_ptr<DevNozzleRack> rack, bool on_rack /*= true*/)
|
||||
{
|
||||
m_rack = rack;
|
||||
|
||||
if (rack) {
|
||||
const auto &nozzle_info = on_rack ? rack->GetNozzle(m_nozzle_id) : rack->GetNozzleSystem()->GetExtNozzle(m_nozzle_id);
|
||||
const wxString &diameter_str = nozzle_info.GetNozzleDiameterStr();
|
||||
const wxString &flowtype_str = nozzle_info.GetNozzleFlowTypeStr();
|
||||
const std::string &color = nozzle_info.GetFilamentColor();
|
||||
|
||||
/*check empty first*/
|
||||
if (nozzle_info.IsEmpty()) {
|
||||
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_EMPTY, _L("Empty"), wxEmptyString, color);
|
||||
} else if (nozzle_info.IsNormal()) {
|
||||
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_NORMAL, diameter_str, flowtype_str, color);
|
||||
} else if (nozzle_info.IsAbnormal()) {
|
||||
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_ERROR, _L("Error"), wxEmptyString, color);
|
||||
} else if (nozzle_info.IsUnknown()) {
|
||||
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_UNKNOWN, _L("Unknown"), wxEmptyString, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackNozzleItem::SetNozzleStatus(NOZZLE_STATUS status, const wxString& str1, const wxString& str2, const std::string& color)
|
||||
{
|
||||
if (m_status != status || m_filament_color != color)
|
||||
{
|
||||
m_status = status;
|
||||
m_filament_color = color;
|
||||
switch (status)
|
||||
{
|
||||
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_EMPTY:
|
||||
{
|
||||
if (!m_nozzle_empty_image) { m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);}
|
||||
m_nozzle_icon->SetBitmap(m_nozzle_empty_image->bmp());
|
||||
break;
|
||||
}
|
||||
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_NORMAL:
|
||||
{
|
||||
if (!m_nozzle_normal_image) { m_nozzle_normal_image = new ScalableBitmap(this, "dev_rack_nozzle_normal", 46);}
|
||||
m_nozzle_icon->SetBitmap(SetNozzleBmpColor(m_nozzle_normal_image->bmp(), m_filament_color));
|
||||
break;
|
||||
}
|
||||
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_UNKNOWN:
|
||||
{
|
||||
if (!m_nozzle_unknown_image) { m_nozzle_unknown_image = new ScalableBitmap(this, "dev_rack_nozzle_unknown", 46);}
|
||||
m_nozzle_icon->SetBitmap(m_nozzle_unknown_image->bmp());
|
||||
break;
|
||||
}
|
||||
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR:
|
||||
{
|
||||
if (!m_nozzle_error_image) { m_nozzle_error_image = new ScalableBitmap(this, "dev_rack_nozzle_error", 46);}
|
||||
m_nozzle_icon->SetBitmap(m_nozzle_error_image->bmp());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (status == wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR)
|
||||
{
|
||||
m_nozzle_label_1->SetForegroundColour(StateColor::darkModeColorFor(s_red_clr));
|
||||
m_nozzle_status_icon->Show(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nozzle_label_1->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
|
||||
m_nozzle_status_icon->Show(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool update_layout = (m_nozzle_label_1->GetLabel() != str1 || m_nozzle_label_2->GetLabel() != str2);
|
||||
m_nozzle_label_1->SetLabel(str1);
|
||||
m_nozzle_label_2->SetLabel(str2);
|
||||
|
||||
if (update_layout) {
|
||||
Layout();
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus(wxMouseEvent& evt)
|
||||
{
|
||||
if (m_is_disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto rack = m_rack.lock();
|
||||
if (rack && m_status == wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR)
|
||||
{
|
||||
// Orca: show the abnormal-hotend warning as an informational dialog only, with no
|
||||
// "Jump to the upgrade page" button, since Orca's MessageDialog and device Upgrade UI
|
||||
// have no such entry point. Fires when tapping an error-state rack nozzle's status icon.
|
||||
MessageDialog dlg(nullptr, _L("The hotend is in an abnormal state and currently unavailable. "
|
||||
"Please go to 'Device -> Upgrade' to upgrade firmware."), _L("Abnormal Hotend"), wxICON_WARNING | wxOK);
|
||||
dlg.ShowModal();
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackNozzleItem::Rescale()
|
||||
{
|
||||
if (m_nozzle_normal_image) { m_nozzle_normal_image->msw_rescale(); }
|
||||
if (m_nozzle_empty_image) { m_nozzle_empty_image->msw_rescale(); }
|
||||
if (m_nozzle_unknown_image) { m_nozzle_unknown_image->msw_rescale(); }
|
||||
if (m_nozzle_error_image) { m_nozzle_error_image->msw_rescale(); }
|
||||
|
||||
auto status_icon = create_scaled_bitmap("dev_rack_nozzle_error_icon", this, 14);
|
||||
m_nozzle_status_icon->SetBitmap(status_icon);
|
||||
m_nozzle_status_icon->Refresh();
|
||||
|
||||
if (m_nozzle_selected_image) {
|
||||
m_nozzle_selected_image->msw_rescale();
|
||||
if (m_is_selected) {
|
||||
m_nozzle_selected_bitmap->SetBitmap(m_nozzle_selected_image->bmp());
|
||||
}
|
||||
};
|
||||
|
||||
switch (m_status)
|
||||
{
|
||||
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_EMPTY:
|
||||
{
|
||||
m_nozzle_icon->SetBitmap(m_nozzle_empty_image->bmp());
|
||||
break;
|
||||
}
|
||||
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_NORMAL:
|
||||
{
|
||||
m_nozzle_icon->SetBitmap(SetNozzleBmpColor(m_nozzle_normal_image->bmp(), m_filament_color));
|
||||
break;
|
||||
}
|
||||
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_UNKNOWN:
|
||||
{
|
||||
m_nozzle_icon->SetBitmap(m_nozzle_unknown_image->bmp());
|
||||
break;
|
||||
}
|
||||
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR:
|
||||
{
|
||||
m_nozzle_icon->SetBitmap(m_nozzle_error_image->bmp());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
void wgtDeviceNozzleRackNozzleItem::EnableSelect()
|
||||
{
|
||||
if (m_enable_select == true) {
|
||||
return;
|
||||
};
|
||||
|
||||
m_enable_select = true;
|
||||
m_nozzle_icon->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
|
||||
m_nozzle_label_id->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
|
||||
m_nozzle_label_1->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
|
||||
m_nozzle_label_2->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
|
||||
Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackNozzleItem::OnItemSelected(wxMouseEvent& evt)
|
||||
{
|
||||
if (m_enable_select && !m_is_disabled){
|
||||
SetSelected(true);
|
||||
wxCommandEvent command_evt(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, GetId());
|
||||
command_evt.SetEventObject(this);
|
||||
ProcessEvent(command_evt);
|
||||
}
|
||||
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
|
||||
void wgtDeviceNozzleRackNozzleItem::SetDisable(bool disabled)
|
||||
{
|
||||
if (m_is_disabled == disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_is_disabled = disabled;
|
||||
|
||||
auto bg_clr = disabled ? StateColor::darkModeColorFor("#E5E7EB") : StateColor::darkModeColorFor(*wxWHITE);
|
||||
m_nozzle_icon->SetBackgroundColour(bg_clr);
|
||||
m_nozzle_label_id->SetBackgroundColour(bg_clr);
|
||||
m_nozzle_label_1->SetBackgroundColour(bg_clr);
|
||||
m_nozzle_status_icon->SetBackgroundColour(bg_clr);
|
||||
m_nozzle_label_2->SetBackgroundColour(bg_clr);
|
||||
m_nozzle_selected_bitmap->SetBackgroundColour(bg_clr);
|
||||
|
||||
SetBackgroundColor(bg_clr);
|
||||
Refresh();
|
||||
};
|
||||
|
||||
};// end of namespace Slic3r::GUI
|
||||
@@ -0,0 +1,99 @@
|
||||
//**********************************************************/
|
||||
/* File: wgtDeviceNozzleRackNozzleItem.h
|
||||
* Description: One nozzle cell of the H2C hotend rack view.
|
||||
*
|
||||
* This header defines only the single-nozzle-cell widget (and the selection event it emits), which
|
||||
* is the one dependency MultiNozzleSync's HotEndTable needs. The rest of the Device-tab rack panel
|
||||
* (wgtDeviceNozzleRack / ...Area / ...ToolHead / ...Pos) is not provided here.
|
||||
//**********************************************************/
|
||||
|
||||
#pragma once
|
||||
#include "slic3r/GUI/DeviceCore/DevNozzleRack.h"
|
||||
|
||||
#include "slic3r/GUI/Widgets/StaticBox.hpp"
|
||||
#include "slic3r/GUI/Widgets/Label.hpp" // complete type required by the inline SetDisplayIdText below
|
||||
|
||||
#include <wx/panel.h>
|
||||
#include <memory>
|
||||
|
||||
// Previous definitions
|
||||
class ScalableBitmap;
|
||||
namespace Slic3r
|
||||
{
|
||||
struct DevNozzle;
|
||||
class DevNozzleRack;
|
||||
};
|
||||
|
||||
// Events
|
||||
wxDECLARE_EVENT(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, wxCommandEvent);
|
||||
|
||||
namespace Slic3r::GUI
|
||||
{
|
||||
class wgtDeviceNozzleRackNozzleItem : public StaticBox
|
||||
{
|
||||
public:
|
||||
enum NOZZLE_STATUS
|
||||
{
|
||||
NOZZLE_EMPTY,
|
||||
NOZZLE_NORMAL,
|
||||
NOZZLE_UNKNOWN,
|
||||
NOZZLE_ERROR
|
||||
};
|
||||
|
||||
public:
|
||||
wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id);
|
||||
|
||||
public:
|
||||
void Update(const std::shared_ptr<DevNozzleRack> rack, bool on_rack = true); // on_rack is false means extruder nozzle
|
||||
|
||||
int GetNozzleId() const { return m_nozzle_id; }
|
||||
void SetDisplayIdText(const wxString& text) { m_nozzle_label_id->SetLabel(text);};
|
||||
|
||||
void EnableSelect();;
|
||||
void SetSelected(bool selected);
|
||||
bool IsSelected() const { return m_is_selected; }
|
||||
|
||||
bool IsDisabled() const { return m_is_disabled; }
|
||||
void SetDisable(bool disabled);
|
||||
|
||||
void Rescale();
|
||||
|
||||
private:
|
||||
void CreateGui();
|
||||
|
||||
void SetNozzleStatus(NOZZLE_STATUS status, const wxString& str1, const wxString& str2, const std::string& color);
|
||||
|
||||
void OnBtnNozzleStatus(wxMouseEvent& evt);
|
||||
void OnItemSelected(wxMouseEvent& evt);
|
||||
|
||||
private:
|
||||
std::weak_ptr<DevNozzleRack> m_rack;
|
||||
|
||||
int m_nozzle_id; // internal id, from 0 to 5
|
||||
std::string m_filament_color;
|
||||
NOZZLE_STATUS m_status = NOZZLE_STATUS::NOZZLE_EMPTY;
|
||||
|
||||
// select
|
||||
bool m_is_selected = false;
|
||||
bool m_enable_select = false;
|
||||
ScalableBitmap* m_nozzle_selected_image{ nullptr };
|
||||
wxStaticBitmap* m_nozzle_selected_bitmap{ nullptr };
|
||||
|
||||
// enable or disable
|
||||
bool m_is_disabled = false;
|
||||
|
||||
// Images
|
||||
ScalableBitmap* m_nozzle_normal_image{ nullptr };
|
||||
ScalableBitmap* m_nozzle_empty_image{ nullptr };
|
||||
ScalableBitmap* m_nozzle_unknown_image{ nullptr };
|
||||
ScalableBitmap* m_nozzle_error_image{ nullptr };
|
||||
|
||||
// GUI
|
||||
wxStaticBitmap* m_nozzle_icon{ nullptr };
|
||||
Label* m_nozzle_label_id { nullptr };
|
||||
Label* m_nozzle_label_1{ nullptr };
|
||||
wxStaticBitmap* m_nozzle_status_icon = nullptr;
|
||||
Label* m_nozzle_label_2{ nullptr };
|
||||
};
|
||||
|
||||
};// end of namespace Slic3r::GUI
|
||||
@@ -0,0 +1,696 @@
|
||||
//**********************************************************/
|
||||
/* File: wgtDeviceNozzleRackUpdate.cpp
|
||||
* Description: The dialog for reading/upgrading the H2C induction-hotend-rack firmware.
|
||||
*
|
||||
* \n class wgtDeviceNozzleRackUpdate
|
||||
//**********************************************************/
|
||||
|
||||
#include "wgtDeviceNozzleRackUpdate.h"
|
||||
|
||||
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
|
||||
|
||||
#include "slic3r/GUI/MainFrame.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "slic3r/GUI/wxExtensions.hpp"
|
||||
|
||||
#include "slic3r/GUI/Widgets/Button.hpp"
|
||||
#include "slic3r/GUI/Widgets/Label.hpp"
|
||||
|
||||
#define WX_DIP_SIZE(x, y) wxSize(FromDIP(x), FromDIP(y))
|
||||
|
||||
static wxColour s_red_clr("#D01B1B");
|
||||
|
||||
namespace Slic3r::GUI
|
||||
{
|
||||
|
||||
wxDEFINE_EVENT(wxEVT_NOZZLE_JUMP_UPGRADE, wxCommandEvent);
|
||||
|
||||
wgtDeviceNozzleRackUpgradeDlg::wgtDeviceNozzleRackUpgradeDlg(wxWindow* parent, const std::shared_ptr<DevNozzleRack> rack)
|
||||
: DPIDialog(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
|
||||
{
|
||||
m_rack_upgrade_panel = new wgtDeviceNozzleRackUprade(this);
|
||||
m_rack_upgrade_panel->UpdateRackInfo(rack);
|
||||
|
||||
Bind(wxEVT_NOZZLE_JUMP_UPGRADE, [this](wxCommandEvent&) {
|
||||
EndModal(wxID_OK);
|
||||
});
|
||||
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(m_rack_upgrade_panel, 0, wxEXPAND);
|
||||
SetSizer(main_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackUpgradeDlg::UpdateRackInfo(const std::shared_ptr<DevNozzleRack> rack)
|
||||
{
|
||||
m_rack_upgrade_panel->UpdateRackInfo(rack);
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackUpgradeDlg::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
m_rack_upgrade_panel->Rescale();
|
||||
}
|
||||
|
||||
wgtDeviceNozzleRackUprade::wgtDeviceNozzleRackUprade(wxWindow* parent,
|
||||
wxWindowID id,
|
||||
const wxPoint& pos,
|
||||
const wxSize& size,
|
||||
long style)
|
||||
: wxPanel(parent, id, pos, size, style)
|
||||
{
|
||||
CreateGui();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackUprade::CreateGui()
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
|
||||
// Main vertical sizer
|
||||
auto* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// Header: title + buttons
|
||||
auto* header_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
// Title label
|
||||
auto* title_label = new Label(this, _L("Hotends Info"));
|
||||
title_label->SetFont(Label::Head_14);
|
||||
header_sizer->Add(title_label, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(20));
|
||||
|
||||
// Spacer
|
||||
header_sizer->AddStretchSpacer();
|
||||
main_sizer->Add(header_sizer, 0, wxEXPAND | wxTOP | wxRIGHT, FromDIP(10));
|
||||
|
||||
// "Nozzles"
|
||||
m_extruder_nozzle_item = new wgtDeviceNozzleRackHotendUpdate(this, "R");
|
||||
m_extruder_nozzle_item->UpdateColourStyle(wxColour("#F8F8F8"));
|
||||
m_extruder_nozzle_item->SetExtruderNozzleId(MAIN_EXTRUDER_ID);
|
||||
|
||||
main_sizer->Add(m_extruder_nozzle_item, 0, wxEXPAND | wxALL, FromDIP(12));
|
||||
for (int id = 0; id < 6; id ++)
|
||||
{
|
||||
auto item = new wgtDeviceNozzleRackHotendUpdate(this, wxString::Format("%d", id + 1));
|
||||
item->SetRackNozzleId(id);
|
||||
m_nozzle_items[id] = item;
|
||||
|
||||
main_sizer->Add(item, 0, wxEXPAND | wxALL, FromDIP(12));
|
||||
if (id < 5)
|
||||
{
|
||||
wxPanel* separator = new wxPanel(this);
|
||||
separator->SetMaxSize(wxSize(-1, FromDIP(1)));
|
||||
separator->SetMinSize(wxSize(-1, FromDIP(1)));
|
||||
separator->SetBackgroundColour(wxColour(238, 238, 238));// Orca: grey300 spelled out as a literal RGB, since Orca has no equivalent grey-300 colour macro
|
||||
main_sizer->Add(separator, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(12));
|
||||
}
|
||||
}
|
||||
|
||||
main_sizer->AddSpacer(FromDIP(20));
|
||||
|
||||
// Set sizer
|
||||
this->SetSizer(main_sizer);
|
||||
this->Layout();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackUprade::UpdateRackInfo(const std::shared_ptr<DevNozzleRack> rack)
|
||||
{
|
||||
m_nozzle_rack = rack;
|
||||
if (!rack) { return;}
|
||||
|
||||
// update the nozzles
|
||||
m_extruder_nozzle_item->UpdateExtruderNozzleInfo(rack);
|
||||
for (auto iter : m_nozzle_items)
|
||||
{
|
||||
iter.second->UpdateRackNozzleInfo(rack);
|
||||
}
|
||||
|
||||
// update layout
|
||||
Layout();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackUprade::OnBtnReadAll(wxCommandEvent& e)
|
||||
{
|
||||
if (auto rack = m_nozzle_rack.lock())
|
||||
{
|
||||
rack->CtrlRackReadAll(true);
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackUprade::Rescale()
|
||||
{
|
||||
m_extruder_nozzle_item->Rescale();
|
||||
for (auto& iter : m_nozzle_items)
|
||||
{
|
||||
iter.second->Rescale();
|
||||
}
|
||||
}
|
||||
|
||||
#define WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG *wxWHITE
|
||||
wgtDeviceNozzleRackHotendUpdate::wgtDeviceNozzleRackHotendUpdate(wxWindow* parent, const wxString& idx_text)
|
||||
: StaticBox(parent, wxID_ANY)
|
||||
{
|
||||
CreateGui();
|
||||
|
||||
m_idx_label->SetLabel(idx_text);
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackHotendUpdate::CreateGui()
|
||||
{
|
||||
SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
|
||||
SetBorderColor(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
|
||||
SetCornerRadius(0);
|
||||
|
||||
//load nozzle hs image
|
||||
for (int i = 1; i <= 4; i++)
|
||||
{
|
||||
auto normalImage = new ScalableBitmap(this, "Nozzle_HS_01_0" + std::to_string(i * 2), 46);
|
||||
auto bigImage = new ScalableBitmap(this, "Big_Nozzle_HS_01_0" + std::to_string(i * 2), 216);
|
||||
nozzle_hs.push_back({normalImage, bigImage});
|
||||
}
|
||||
for (int i = 2; i <= 4; i++)
|
||||
{
|
||||
auto normalImage = new ScalableBitmap(this, "Nozzle_HH_01_0" + std::to_string(i * 2), 46);
|
||||
auto bigImage = new ScalableBitmap(this, "Big_Nozzle_HH_01_0" + std::to_string(i * 2), 216);
|
||||
nozzle_hh.push_back({normalImage, bigImage});
|
||||
}
|
||||
|
||||
auto* content_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
// Index
|
||||
m_idx_label = new Label(this);
|
||||
m_idx_label->SetFont(Label::Head_14);
|
||||
m_idx_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
|
||||
content_sizer->Add(m_idx_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(25));
|
||||
|
||||
// Icon
|
||||
wxPanel* imagePanel = new wxPanel(this);
|
||||
imagePanel->SetMaxSize(WX_DIP_SIZE(46, -1));
|
||||
imagePanel->SetMinSize(WX_DIP_SIZE(46, -1));
|
||||
imagePanel->SetSize(wxSize(FromDIP(46), FromDIP(-1)));
|
||||
wxBoxSizer* panelSizer = new wxBoxSizer(wxVERTICAL);
|
||||
imagePanel->SetSizer(panelSizer);
|
||||
|
||||
m_nozzle_empty_image = new ScalableBitmap(imagePanel, "dev_rack_nozzle_empty", 46);
|
||||
m_icon_bitmap = new wxStaticBitmap(imagePanel, wxID_ANY, m_nozzle_empty_image->bmp());
|
||||
panelSizer->Add(m_icon_bitmap, 0, wxALIGN_CENTER);
|
||||
content_sizer->Add(imagePanel, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(25));
|
||||
m_icon_bitmap->Bind(wxEVT_ENTER_WINDOW, &wgtDeviceNozzleRackHotendUpdate::OnBitmapHoverEnter, this);
|
||||
m_icon_bitmap->Bind(wxEVT_LEAVE_WINDOW, &wgtDeviceNozzleRackHotendUpdate::OnBitmapHoverLeave, this);
|
||||
|
||||
// Diameter/type (vertical)
|
||||
wxPanel* type_panel = new wxPanel(this);
|
||||
auto* main_type_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
auto* type_sizer_row_1 = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto* type_sizer_row_2 = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_material_label = new Label(type_panel);
|
||||
m_material_label->SetFont(Label::Body_12);
|
||||
m_material_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
|
||||
|
||||
m_colour_box = new StaticBox(type_panel);
|
||||
m_colour_box->SetMaxSize(WX_DIP_SIZE(16, 16));
|
||||
m_colour_box->SetMinSize(WX_DIP_SIZE(16, 16));
|
||||
m_colour_box->SetCornerRadius(FromDIP(2));
|
||||
m_colour_box->SetSize(wxSize(FromDIP(16), FromDIP(16)));
|
||||
|
||||
type_sizer_row_1->Add(m_colour_box, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
|
||||
type_sizer_row_1->AddStretchSpacer(1);
|
||||
type_sizer_row_1->Add(m_material_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2));
|
||||
|
||||
m_diameter_label = new Label(type_panel);
|
||||
m_diameter_label->SetFont(Label::Body_12);
|
||||
m_diameter_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
|
||||
|
||||
m_flowtype_label = new Label(type_panel);
|
||||
m_flowtype_label->SetFont(Label::Body_12);
|
||||
m_flowtype_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
|
||||
|
||||
m_type_label = new Label(type_panel);
|
||||
m_type_label->SetFont(Label::Body_12);
|
||||
m_type_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
|
||||
|
||||
type_sizer_row_2->Add(m_diameter_label, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT );
|
||||
type_sizer_row_2->Add(m_flowtype_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2));
|
||||
type_sizer_row_2->Add(m_type_label, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2));
|
||||
|
||||
main_type_sizer->Add(type_sizer_row_1, 0, wxALIGN_LEFT);
|
||||
main_type_sizer->Add(type_sizer_row_2, 1, wxALIGN_LEFT | wxEXPAND | wxTOP, FromDIP(4));
|
||||
type_panel->SetSizer(main_type_sizer);
|
||||
type_panel->SetMaxSize(WX_DIP_SIZE(160, 40));
|
||||
type_panel->SetMinSize(WX_DIP_SIZE(160, 40));
|
||||
type_panel->SetSize(WX_DIP_SIZE(160, 40));
|
||||
|
||||
content_sizer->Add(type_panel, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(12));
|
||||
|
||||
// SN and version (vertical)
|
||||
wxPanel* info_panel = new wxPanel(this);
|
||||
auto* info_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_sn_label = new Label(info_panel);
|
||||
m_sn_label->SetFont(Label::Body_12);
|
||||
m_sn_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
|
||||
|
||||
auto* version_h_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_version_label = new Label(info_panel);
|
||||
m_version_label->SetFont(Label::Body_12);
|
||||
m_version_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
|
||||
|
||||
m_version_new_label = new Label(info_panel);
|
||||
m_version_new_label->SetFont(Label::Body_12);
|
||||
m_version_new_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
|
||||
m_version_new_label->SetForegroundColour(wxColour(0, 168, 84)); // Green
|
||||
|
||||
version_h_sizer->Add(m_version_label, 0, wxALIGN_CENTER_VERTICAL);
|
||||
version_h_sizer->Add(m_version_new_label, 0, wxALIGN_CENTER_VERTICAL);
|
||||
info_sizer->Add(m_sn_label, 0, wxALIGN_LEFT);
|
||||
info_sizer->Add(version_h_sizer, 0, wxALIGN_LEFT | wxTOP, FromDIP(4));
|
||||
info_panel->SetSizer(info_sizer);
|
||||
info_panel->SetMaxSize(WX_DIP_SIZE(183, 40));
|
||||
info_panel->SetMinSize(WX_DIP_SIZE(183, 40));
|
||||
info_panel->SetSize(WX_DIP_SIZE(183, 40));
|
||||
|
||||
//Used Time
|
||||
m_used_time = new Label(this);
|
||||
m_used_time->SetFont(Label::Body_12);
|
||||
m_used_time->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
|
||||
|
||||
m_refresh_icon = new ScalableBitmap(this, "refresh_printer", 12);
|
||||
m_error_icon = new ScalableBitmap(this, "error", 14);
|
||||
m_status_bitmap = new wxStaticBitmap(this, wxID_ANY, m_refresh_icon->bmp());
|
||||
m_status_bitmap->Bind(wxEVT_LEFT_UP, &wgtDeviceNozzleRackHotendUpdate::OnStatusIconClick, this);
|
||||
|
||||
std::vector<std::string> list{"refresh_nozzle_1", "refresh_nozzle_2", "refresh_nozzle_3", "refresh_nozzle_4"};
|
||||
// Orca: AnimaIcon has no per-instance size argument (it fixes 25px internally), so no size is
|
||||
// passed here.
|
||||
m_refreshing_icon = new AnimaIcon(this, wxID_ANY, list, "refresh_nozzle", 100);
|
||||
m_refreshing_icon->Show(false);
|
||||
|
||||
|
||||
m_status_label = new Label(this);
|
||||
m_status_label->SetFont(Label::Body_12);
|
||||
|
||||
content_sizer->Add(info_panel, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(20));
|
||||
content_sizer->Add(m_used_time, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(20));
|
||||
content_sizer->Add(m_status_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(20));
|
||||
content_sizer->Add(m_status_bitmap, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2));
|
||||
content_sizer->Add(m_refreshing_icon, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2));
|
||||
content_sizer->AddSpacer(FromDIP(25));
|
||||
|
||||
auto* main_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
main_sizer->Add(content_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(10));
|
||||
|
||||
SetSizer(main_sizer);
|
||||
Layout();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackHotendUpdate::OnStatusIconClick(wxMouseEvent& event)
|
||||
{
|
||||
if (m_status_label->GetLabel() == _L("Refresh"))
|
||||
{
|
||||
m_status_label->SetForegroundColour(wxColour("#A3A3A3"));
|
||||
m_status_label->SetLabel(_L("Refreshing"));
|
||||
m_status_bitmap->Show(false);
|
||||
if(!m_refreshing_icon->IsPlaying())
|
||||
{
|
||||
m_refreshing_icon->Play();
|
||||
m_refreshing_icon->Show();
|
||||
}
|
||||
if (auto shared = m_nozzle_rack.lock())
|
||||
{
|
||||
shared->CrtlRackReadNozzle(m_rack_nozzle_id);
|
||||
}
|
||||
Layout();
|
||||
}
|
||||
|
||||
if (m_status_label->GetLabel() == _L("Error") /*&& m_nozzle_status == NOZZLE_STATUS_ABNORMAL*/)
|
||||
{
|
||||
if (auto shared = m_nozzle_rack.lock())
|
||||
{
|
||||
MessageDialog dlg(nullptr, _L("Hotend status abnormal, unavailable at present. Please upgrade the firmware"
|
||||
" and try again."), _L("Update"), wxICON_WARNING);
|
||||
dlg.AddButton(wxID_CANCEL, _L("Cancel"), false);
|
||||
dlg.AddButton(wxID_OK,_L("Jump to the upgrade page"), true);
|
||||
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
{
|
||||
wxGetApp().mainframe->m_monitor->jump_to_Upgrade();
|
||||
|
||||
wxCommandEvent evt(wxEVT_NOZZLE_JUMP_UPGRADE, GetId());
|
||||
evt.SetEventObject(this);
|
||||
wxWindow* target = GetParent();
|
||||
if (target)
|
||||
{
|
||||
target = target->GetParent();
|
||||
if (target)
|
||||
{
|
||||
wxPostEvent(target, evt);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackHotendUpdate::OnBitmapHoverEnter(wxMouseEvent& event)
|
||||
{
|
||||
int scaledW = FromDIP(240);
|
||||
int scaledH = FromDIP(240);
|
||||
|
||||
ScalableBitmap* scaledBmp{nullptr};
|
||||
if (m_nozzle_status == NOZZLE_STATUS_EMPTY || m_nozzle_status == NOZZLE_STATUS_UNKNOWN || !findNozzleImage)
|
||||
{
|
||||
if (!m_scaled_nozzle_empty_image) {m_scaled_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 216);}
|
||||
|
||||
scaledBmp = m_scaled_nozzle_empty_image;
|
||||
}
|
||||
else
|
||||
{
|
||||
scaledBmp = m_scaled_nozzle_image;
|
||||
}
|
||||
|
||||
m_hoverFrame = new wxFrame(nullptr, wxID_ANY, "",
|
||||
wxDefaultPosition, wxDefaultSize,
|
||||
wxFRAME_NO_TASKBAR | wxBORDER_NONE | wxTRANSPARENT_WINDOW);
|
||||
m_hoverFrame->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
|
||||
m_hoverFrame->SetSize(scaledW, scaledH);
|
||||
wxBoxSizer* frameSizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_hoverFrame->SetSizer(frameSizer);
|
||||
|
||||
wxStaticBitmap* hoverBmp = new wxStaticBitmap(m_hoverFrame, wxID_ANY, scaledBmp->bmp());
|
||||
frameSizer->Add(hoverBmp, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP | wxBOTTOM, FromDIP(12));
|
||||
|
||||
wxPoint mousePos = wxGetMousePosition();
|
||||
m_hoverFrame->SetPosition(wxPoint(mousePos.x + 10, mousePos.y));
|
||||
m_hoverFrame->Layout();
|
||||
m_hoverFrame->Show(true);
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackHotendUpdate::OnBitmapHoverLeave(wxMouseEvent& event)
|
||||
{
|
||||
if (m_hoverFrame)
|
||||
{
|
||||
m_hoverFrame->Destroy();
|
||||
m_hoverFrame = nullptr;
|
||||
}
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackHotendUpdate::updateNozzleImage(const DevNozzle& nozzle)
|
||||
{
|
||||
if (m_nozzle_status == NOZZLE_STATUS_UNKNOWN || m_nozzle_status == NOZZLE_STATUS_EMPTY)
|
||||
{
|
||||
if (!m_nozzle_empty_image) {m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);}
|
||||
m_icon_bitmap->SetBitmap(m_nozzle_empty_image->bmp());
|
||||
m_icon_bitmap->Refresh();
|
||||
return;
|
||||
}
|
||||
findNozzleImage = false;
|
||||
int index = -1;
|
||||
switch (nozzle.GetNozzleDiameterType()) {
|
||||
case NOZZLE_DIAMETER_0_2: index = 0; break;
|
||||
case NOZZLE_DIAMETER_0_4: index = 1; break;
|
||||
case NOZZLE_DIAMETER_0_6: index = 2; break;
|
||||
case NOZZLE_DIAMETER_0_8: index = 3; break;
|
||||
default: index = -1; break;
|
||||
}
|
||||
NozzleType nozzleType = nozzle.GetNozzleType();
|
||||
if (nozzleType == ntHardenedSteel || nozzleType == ntStainlessSteel)
|
||||
{
|
||||
if (nozzle.GetNozzleFlowType() == H_FLOW && index >= 1)
|
||||
{
|
||||
m_nozzle_image = nozzle_hh[index - 1][0];
|
||||
m_icon_bitmap->SetBitmap(m_nozzle_image->bmp());
|
||||
m_scaled_nozzle_image = nozzle_hh[index - 1][1];
|
||||
findNozzleImage = true;
|
||||
}
|
||||
else if (nozzle.GetNozzleFlowType() == S_FLOW && index >= 0)
|
||||
{
|
||||
m_nozzle_image = nozzle_hs[index][0];
|
||||
m_icon_bitmap->SetBitmap(m_nozzle_image->bmp());
|
||||
m_scaled_nozzle_image = nozzle_hs[index][1];
|
||||
findNozzleImage = true;
|
||||
}
|
||||
}
|
||||
if (!findNozzleImage)
|
||||
{
|
||||
if (!m_nozzle_empty_image) {m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);}
|
||||
m_icon_bitmap->SetBitmap(m_nozzle_empty_image->bmp());
|
||||
}
|
||||
m_icon_bitmap->Refresh();
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackHotendUpdate::UpdateColourStyle(const wxColour& clr)
|
||||
{
|
||||
SetBackgroundColour(clr);
|
||||
SetBorderColor(clr);
|
||||
|
||||
// BFS: Update all children background color
|
||||
auto children = GetChildren();
|
||||
while (!children.IsEmpty())
|
||||
{
|
||||
auto win = children.front();
|
||||
children.pop_front();
|
||||
win->SetBackgroundColour(clr);
|
||||
|
||||
for (auto child : win->GetChildren())
|
||||
{
|
||||
children.push_back(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackHotendUpdate::UpdateExtruderNozzleInfo(const std::shared_ptr<DevNozzleRack> rack)
|
||||
{
|
||||
m_nozzle_rack = rack;
|
||||
if (rack)
|
||||
{
|
||||
DevNozzleSystem* nozzle_system = rack->GetNozzleSystem();
|
||||
if (nozzle_system)
|
||||
{
|
||||
UpdateInfo(nozzle_system->GetExtNozzle(m_ext_nozzle_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackHotendUpdate::UpdateRackNozzleInfo(const std::shared_ptr<DevNozzleRack> rack)
|
||||
{
|
||||
m_nozzle_rack = rack;
|
||||
if (rack)
|
||||
{
|
||||
UpdateInfo(rack->GetNozzle(m_rack_nozzle_id));
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackHotendUpdate::UpdateInfo(const DevNozzle& nozzle)
|
||||
{
|
||||
/*update nozzle possition and background*/
|
||||
if (auto share = m_nozzle_rack.lock())
|
||||
{
|
||||
// if (share->GetReadingCount() > 0 && m_status_label->IsShown())
|
||||
if (share->GetReadingCount() > 0 && m_status_label->IsShown() && m_status_label->GetLabel() == _L("Refreshing"))
|
||||
{
|
||||
m_refreshing_icon->Play();
|
||||
m_refreshing_icon->Show();
|
||||
m_nozzle_status = NOZZLE_STATUS_DC;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_refreshing_icon->Stop();
|
||||
m_refreshing_icon->Show(false);
|
||||
}
|
||||
}
|
||||
|
||||
wxString filamentDisplayName{};
|
||||
for (auto iter = GUI::wxGetApp().preset_bundle->filaments.begin(); iter != GUI::wxGetApp().preset_bundle->filaments.end(); ++iter)
|
||||
{
|
||||
const Preset& filament_preset = *iter;
|
||||
// const auto& config = filament_preset.config;
|
||||
if (filament_preset.filament_id == nozzle.GetFilamentId())
|
||||
{
|
||||
filamentDisplayName = wxString(filament_preset.alias);
|
||||
}
|
||||
}
|
||||
|
||||
if (nozzle.IsEmpty() && m_nozzle_status != NOZZLE_STATUS_EMPTY)
|
||||
{
|
||||
m_nozzle_status = NOZZLE_STATUS_EMPTY;
|
||||
|
||||
m_material_label->Show(false);
|
||||
m_colour_box->Show(false);
|
||||
|
||||
m_diameter_label->Show(false);
|
||||
m_flowtype_label->Show(false);
|
||||
m_type_label->SetLabel(_L("Empty"));
|
||||
m_type_label->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
|
||||
|
||||
m_sn_label->Show(false);
|
||||
m_version_label->Show(false);
|
||||
m_version_new_label->Show(false);
|
||||
|
||||
updateNozzleImage(nozzle);
|
||||
|
||||
m_used_time->Show(false);
|
||||
m_status_label->Show(false);
|
||||
m_status_bitmap->Show(false);
|
||||
|
||||
}
|
||||
else if (nozzle.IsNormal() && m_nozzle_status != NOZZLE_STATUS_NORMAL)
|
||||
{
|
||||
m_nozzle_status = NOZZLE_STATUS_NORMAL;
|
||||
|
||||
m_material_label->SetLabel(filamentDisplayName);
|
||||
m_colour_box->SetBackgroundColour(wxColour("#" + nozzle.GetFilamentColor()));
|
||||
m_colour_box->SetBorderColor(wxColour("#" + nozzle.GetFilamentColor()));
|
||||
m_material_label->Show(true);
|
||||
m_colour_box->Show(true);
|
||||
|
||||
m_diameter_label->Show(true);
|
||||
m_flowtype_label->Show(true);
|
||||
m_diameter_label->SetLabel(nozzle.GetNozzleDiameterStr());
|
||||
m_flowtype_label->SetLabel(nozzle.GetNozzleFlowTypeStr());
|
||||
m_type_label->SetLabel(nozzle.GetNozzleTypeStr());
|
||||
m_type_label->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
|
||||
|
||||
m_sn_label->Show(true);
|
||||
m_version_label->Show(true);
|
||||
|
||||
updateNozzleImage(nozzle);
|
||||
|
||||
m_used_time->Show(true);
|
||||
m_status_label->Show(false);
|
||||
m_status_bitmap->Show(false);
|
||||
}
|
||||
else if (nozzle.IsAbnormal() && m_nozzle_status != NOZZLE_STATUS_ABNORMAL)
|
||||
{
|
||||
m_nozzle_status = NOZZLE_STATUS_ABNORMAL;
|
||||
|
||||
m_material_label->SetLabel(filamentDisplayName);
|
||||
m_colour_box->SetBackgroundColour(wxColour("#" + nozzle.GetFilamentColor()));
|
||||
m_colour_box->SetBorderColor(wxColour("#" + nozzle.GetFilamentColor()));
|
||||
m_material_label->Show(true);
|
||||
m_colour_box->Show(true);
|
||||
|
||||
m_diameter_label->Show(true);
|
||||
m_flowtype_label->Show(true);
|
||||
m_diameter_label->SetLabel(nozzle.GetNozzleDiameterStr());
|
||||
m_flowtype_label->SetLabel(nozzle.GetNozzleFlowTypeStr());
|
||||
m_type_label->SetLabel(nozzle.GetNozzleTypeStr());
|
||||
m_type_label->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
|
||||
|
||||
updateNozzleImage(nozzle);
|
||||
|
||||
m_status_label->Show(true);
|
||||
m_status_bitmap->Show(true);
|
||||
m_status_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#E14747")));
|
||||
m_status_label->SetLabel(_L("Error"));
|
||||
m_status_bitmap->SetBitmap(m_error_icon->bmp());
|
||||
m_status_bitmap->Refresh();
|
||||
}
|
||||
else if (nozzle.IsUnknown() && m_nozzle_status != NOZZLE_STATUS_UNKNOWN)
|
||||
{
|
||||
m_nozzle_status = NOZZLE_STATUS_UNKNOWN;
|
||||
|
||||
m_colour_box->Show(false);
|
||||
m_material_label->SetLabel(wxString("--"));
|
||||
m_material_label->Show(true);
|
||||
|
||||
m_diameter_label->Show(false);
|
||||
m_flowtype_label->Show(false);
|
||||
m_type_label->SetLabel(_L("Unknown"));
|
||||
m_type_label->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
|
||||
|
||||
m_sn_label->Show(true);
|
||||
m_version_label->Show(true);
|
||||
|
||||
updateNozzleImage(nozzle);
|
||||
|
||||
m_used_time->Show(true);
|
||||
m_status_label->Show(true);
|
||||
m_status_bitmap->Show(true);
|
||||
m_status_label->SetForegroundColour(wxColour("#009688"));
|
||||
m_status_label->SetLabel(_L("Refresh"));
|
||||
m_status_bitmap->SetBitmap(m_refresh_icon->bmp());
|
||||
m_status_bitmap->Refresh();
|
||||
}
|
||||
|
||||
// Update firmware info
|
||||
const DevFirmwareVersionInfo& firmware = nozzle.GetFirmwareInfo();
|
||||
if (nozzle.IsUnknown())
|
||||
{
|
||||
m_sn_label->SetLabel(wxString::Format("%s: --", _L("SN")));
|
||||
m_version_label->SetLabel(wxString::Format("%s: --", _L("Version")));
|
||||
m_version_new_label->Show(false);
|
||||
}
|
||||
if (nozzle.IsNormal() || nozzle.IsAbnormal())
|
||||
{
|
||||
if (firmware.isValid())
|
||||
{
|
||||
m_sn_label->SetLabel(wxString::Format("%s: %s", _L("SN"), firmware.sn));
|
||||
|
||||
if (!firmware.sw_new_ver.empty() && firmware.sw_new_ver != firmware.sw_ver)
|
||||
{
|
||||
m_version_label->SetLabel(wxString::Format("%s: %s > ", _L("Version"), firmware.sw_ver));
|
||||
m_version_new_label->SetLabel(wxString::Format("%s", firmware.sw_new_ver));
|
||||
m_version_new_label->Show(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_version_label->SetLabel(wxString::Format("%s:%s", _L("Version"), firmware.sw_ver));
|
||||
m_version_new_label->Show(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sn_label->SetLabel(wxString::Format("%s: --", _L("SN")));
|
||||
m_version_label->SetLabel(wxString::Format("%s: --", _L("Version")));
|
||||
m_version_new_label->Show(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!nozzle.IsEmpty())
|
||||
{
|
||||
if (nozzle.IsUnknown())
|
||||
{
|
||||
m_used_time->SetLabel(wxString::Format(_L("Used Time: %s"), "--h"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update used time
|
||||
int usedSeconds = nozzle.GetNozzlePrintTime();
|
||||
if (usedSeconds < 60)
|
||||
{
|
||||
m_used_time->SetLabel(wxString::Format(_L("Used Time: %s"), "0 h"));
|
||||
}
|
||||
else
|
||||
{
|
||||
int printTime = (usedSeconds >= 3600 ? usedSeconds / 3600 : usedSeconds / 60);
|
||||
std::string printTimeStr = (usedSeconds >= 3600 ? std::to_string(printTime) + " h" : std::to_string(printTime) + " min");
|
||||
m_used_time->SetLabel(wxString::Format(_L("Used Time: %s"), printTimeStr.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackHotendUpdate::Rescale()
|
||||
{
|
||||
// update images
|
||||
if (m_nozzle_image) { m_nozzle_image->msw_rescale(); }
|
||||
if (m_nozzle_empty_image) { m_nozzle_empty_image->msw_rescale(); }
|
||||
if (m_nozzle_status == NOZZLE_STATUS_EMPTY && m_nozzle_empty_image)
|
||||
{
|
||||
m_icon_bitmap->SetBitmap(m_nozzle_empty_image->bmp());
|
||||
}
|
||||
else if (m_nozzle_image != nullptr)
|
||||
{
|
||||
m_icon_bitmap->SetBitmap(m_nozzle_image->bmp());
|
||||
}
|
||||
m_icon_bitmap->Refresh();
|
||||
}
|
||||
|
||||
};// namespace Slic3r::GUI
|
||||
@@ -0,0 +1,175 @@
|
||||
//**********************************************************/
|
||||
/* File: wgtDeviceNozzleRackUpdate.h
|
||||
* Description: The dialog for reading/upgrading the H2C induction-hotend-rack firmware.
|
||||
*
|
||||
* \n class wgtDeviceNozzleRackUpgradeDlg; // modal "Hotends Info" dialog
|
||||
* \n class wgtDeviceNozzleRackUprade; // the dialog's content panel (per-nozzle rows)
|
||||
* \n class wgtDeviceNozzleRackHotendUpdate; // one hotend row (toolhead "R" + rack 1..6)
|
||||
*
|
||||
* Opened from the Device-tab rack panel's "Hotends Info" button (wgtDeviceNozzleRack.cpp) and
|
||||
* reused by the Device->Upgrade page "Hotends on Rack" section
|
||||
* (UpgradePanel::createNozzleRackWidgets). All device data + MQTT commands live in
|
||||
* DevNozzleRack / DevNozzleSystem, so this is additive GUI gated behind
|
||||
* DevNozzleRack::IsSupported() — dormant for every non-rack printer.
|
||||
//**********************************************************/
|
||||
|
||||
#pragma once
|
||||
#include "slic3r/GUI/DeviceCore/DevNozzleRack.h"
|
||||
|
||||
#include "slic3r/GUI/GUI_Utils.hpp"
|
||||
#include "slic3r/GUI/Widgets/StaticBox.hpp"
|
||||
#include "slic3r/GUI/Widgets/AnimaController.hpp"
|
||||
|
||||
#include <wx/panel.h>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
// Previous definitions
|
||||
class Button;
|
||||
class Label;
|
||||
class ScalableBitmap;
|
||||
class ScalableButton;
|
||||
namespace Slic3r
|
||||
{
|
||||
struct DevNozzle;
|
||||
class DevNozzleRack;
|
||||
namespace GUI
|
||||
{
|
||||
class wgtDeviceNozzleRackUprade;
|
||||
class wgtDeviceNozzleRackHotendUpdate;
|
||||
}
|
||||
};
|
||||
|
||||
namespace Slic3r::GUI
|
||||
{
|
||||
class wgtDeviceNozzleRackUpgradeDlg : public DPIDialog
|
||||
{
|
||||
public:
|
||||
wgtDeviceNozzleRackUpgradeDlg(wxWindow* parent, const std::shared_ptr<DevNozzleRack> rack);
|
||||
|
||||
public:
|
||||
void UpdateRackInfo(const std::shared_ptr<DevNozzleRack> rack);
|
||||
|
||||
public:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
|
||||
private:
|
||||
wgtDeviceNozzleRackUprade* m_rack_upgrade_panel;
|
||||
};
|
||||
|
||||
|
||||
class wgtDeviceNozzleRackUprade : public wxPanel
|
||||
{
|
||||
public:
|
||||
wgtDeviceNozzleRackUprade(wxWindow* parent,
|
||||
wxWindowID id = wxID_ANY,
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize,
|
||||
long style = wxTAB_TRAVERSAL);
|
||||
~wgtDeviceNozzleRackUprade() = default;
|
||||
|
||||
public:
|
||||
void UpdateRackInfo(const std::shared_ptr<DevNozzleRack> rack);
|
||||
void Rescale();
|
||||
|
||||
private:
|
||||
void CreateGui();
|
||||
|
||||
void OnBtnReadAll(wxCommandEvent& e);
|
||||
|
||||
private:
|
||||
std::weak_ptr<DevNozzleRack> m_nozzle_rack;
|
||||
|
||||
// GUI
|
||||
wgtDeviceNozzleRackHotendUpdate* m_extruder_nozzle_item;
|
||||
std::unordered_map<int, wgtDeviceNozzleRackHotendUpdate*> m_nozzle_items;
|
||||
};
|
||||
|
||||
// Using for rack nozzle id or extruder nozzle id
|
||||
class wgtDeviceNozzleRackHotendUpdate : public StaticBox
|
||||
{
|
||||
public:
|
||||
wgtDeviceNozzleRackHotendUpdate(wxWindow* parent, const wxString& idx_text);
|
||||
|
||||
public:
|
||||
// Color
|
||||
void UpdateColourStyle(const wxColour& clr);
|
||||
|
||||
// extruder nozzle
|
||||
int GetExtruderNozzleId() const { return m_ext_nozzle_id; }
|
||||
void SetExtruderNozzleId(int ext_nozzle_id) { m_ext_nozzle_id = ext_nozzle_id; }
|
||||
void UpdateExtruderNozzleInfo(const std::shared_ptr<DevNozzleRack> rack);
|
||||
|
||||
// rack nozzle
|
||||
void SetRackNozzleId(int rack_nozzle_id) { m_rack_nozzle_id = rack_nozzle_id; }
|
||||
int GetRackNozzleId() const { return m_rack_nozzle_id; }
|
||||
void UpdateRackNozzleInfo(const std::shared_ptr<DevNozzleRack> rack);
|
||||
|
||||
// gui
|
||||
void Rescale();
|
||||
|
||||
private:
|
||||
void CreateGui();
|
||||
|
||||
void UpdateInfo(const DevNozzle& nozzle);
|
||||
|
||||
void OnBitmapHoverEnter(wxMouseEvent& event);
|
||||
void OnBitmapHoverLeave(wxMouseEvent& event);
|
||||
void OnStatusIconClick(wxMouseEvent& event);
|
||||
void updateNozzleImage(const DevNozzle& nozzle);
|
||||
|
||||
private:
|
||||
enum NozzleStatus : int
|
||||
{
|
||||
NOZZLE_STATUS_DC = -1,
|
||||
NOZZLE_STATUS_EMPTY,
|
||||
NOZZLE_STATUS_NORMAL,
|
||||
NOZZLE_STATUS_ABNORMAL,
|
||||
NOZZLE_STATUS_UNKNOWN,
|
||||
};
|
||||
|
||||
private:
|
||||
int m_ext_nozzle_id = -1;
|
||||
int m_rack_nozzle_id = -1;
|
||||
bool m_isRefreshFinish = false;
|
||||
bool findNozzleImage = false;
|
||||
|
||||
NozzleStatus m_nozzle_status = NOZZLE_STATUS_DC;
|
||||
std::weak_ptr<DevNozzleRack> m_nozzle_rack;
|
||||
|
||||
std::vector<std::vector<ScalableBitmap*>> nozzle_hs;
|
||||
std::vector<std::vector<ScalableBitmap*>> nozzle_hh;
|
||||
// GUI
|
||||
ScalableBitmap* m_nozzle_image = nullptr;
|
||||
ScalableBitmap* m_nozzle_empty_image = nullptr;
|
||||
ScalableBitmap* m_scaled_nozzle_image = nullptr;
|
||||
ScalableBitmap* m_scaled_nozzle_empty_image = nullptr;
|
||||
ScalableBitmap* m_refresh_icon = nullptr;
|
||||
AnimaIcon* m_refreshing_icon = nullptr;
|
||||
ScalableBitmap* m_error_icon = nullptr;
|
||||
|
||||
Label* m_idx_label;
|
||||
wxStaticBitmap* m_icon_bitmap{ nullptr };
|
||||
wxStaticBitmap* m_status_bitmap{ nullptr };
|
||||
|
||||
Label* m_material_label{ nullptr };
|
||||
StaticBox* m_colour_box{ nullptr };
|
||||
wxFrame* m_hoverFrame{ nullptr };
|
||||
|
||||
Label* m_status_label{ nullptr };
|
||||
Label* m_used_time{ nullptr };
|
||||
|
||||
Label* m_diameter_label;
|
||||
Label* m_flowtype_label;
|
||||
Label* m_type_label;
|
||||
ScalableButton* m_error_button{ nullptr };
|
||||
|
||||
Label* m_sn_label;
|
||||
Label* m_version_label;
|
||||
Label* m_version_new_label;
|
||||
};
|
||||
|
||||
wxDECLARE_EVENT(wxEVT_NOZZLE_JUMP_UPGRADE, wxCommandEvent);
|
||||
|
||||
};// end of namespace Slic3r::GUI
|
||||
@@ -0,0 +1,289 @@
|
||||
//**********************************************************/
|
||||
/* File: wgtDeviceNozzleSelect.cpp
|
||||
* Description: The panel to select nozzle
|
||||
*
|
||||
* \n class wgtDeviceNozzleSelect;
|
||||
//**********************************************************/
|
||||
|
||||
#include "wgtDeviceNozzleSelect.h"
|
||||
#include "wgtDeviceNozzleRackNozzleItem.h" // the nozzle-item widget lives in its own header
|
||||
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/DeviceTab/wgtMsgBox.h"
|
||||
#include "slic3r/GUI/Widgets/Label.hpp"
|
||||
|
||||
static wxColour s_gray_clr("#B0B0B0");
|
||||
static wxColour s_hgreen_clr("#009688");
|
||||
static wxColour s_red_clr("#D01B1B");
|
||||
|
||||
static std::vector<int> a_nozzle_seq = {16, 18, 20, 17, 19, 21};
|
||||
|
||||
wxDEFINE_EVENT(EVT_NOZZLE_SELECT_CHANGED, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_NOZZLE_SELECT_CLICKED, wxCommandEvent);
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
wgtDeviceNozzleRackSelect::wgtDeviceNozzleRackSelect(wxWindow *parent) : wxPanel(parent, wxID_ANY) { CreateGui(); }
|
||||
|
||||
static wxPanel* s_create_title(wxWindow *parent, const wxString& text)
|
||||
{
|
||||
wxPanel *panel = new wxPanel(parent, wxID_ANY);
|
||||
|
||||
auto title = new Label(panel, text);
|
||||
title->SetFont(::Label::Body_13);
|
||||
title->SetBackgroundColour(*wxWHITE);
|
||||
title->SetForegroundColour(0x909090);
|
||||
|
||||
auto split_line = new wxPanel(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
split_line->SetBackgroundColour(0xeeeeee);
|
||||
split_line->SetMinSize(wxSize(-1, 1));
|
||||
split_line->SetMaxSize(wxSize(-1, 1));
|
||||
|
||||
wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(0, 0, 0, wxEXPAND, 0);
|
||||
sizer->Add(title, 0, wxALIGN_CENTER, 0);
|
||||
sizer->Add(split_line, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND, 0);
|
||||
panel->SetSizer(sizer);
|
||||
panel->Layout();
|
||||
return panel;
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackSelect::CreateGui()
|
||||
{
|
||||
wxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
wxColour tip_bg_clr("#FFF0E0");
|
||||
m_title_tips_dynamic = new wgtMsgBox(this);
|
||||
m_title_tips_dynamic->SetBackgroundColour(tip_bg_clr);
|
||||
m_title_tips_dynamic->SetBorderColor(wxColour("#FF6F00"));
|
||||
m_title_tips_dynamic->SetCornerRadius(1);
|
||||
m_title_tips_dynamic->SetBorderWidth(1);
|
||||
auto text_label = m_title_tips_dynamic->GetTextLabel();
|
||||
text_label->SetFont(::Label::Body_12);
|
||||
text_label->SetForegroundColour("#FF6F00");
|
||||
text_label->SetBackgroundColour(tip_bg_clr);
|
||||
text_label->SetLabel(_L("Dynamic nozzles are allocated on the current plate. Picking hotend is not supported."));
|
||||
text_label->Wrap(FromDIP(300));
|
||||
text_label->Fit();
|
||||
m_title_tips_dynamic->Layout();
|
||||
m_title_tips_dynamic->Refresh();
|
||||
|
||||
// nozzles
|
||||
wxGridSizer *nozzle_sizer = new wxGridSizer(2, 3, FromDIP(10), FromDIP(10));
|
||||
for (auto idx : a_nozzle_seq) {
|
||||
wgtDeviceNozzleRackNozzleItem *nozzle_item = new wgtDeviceNozzleRackNozzleItem(this, idx - 16);
|
||||
nozzle_item->EnableSelect();
|
||||
nozzle_item->Bind(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, &wgtDeviceNozzleRackSelect::OnNozzleItemSelected, this);
|
||||
m_nozzle_items[idx] = nozzle_item;
|
||||
nozzle_sizer->Add(nozzle_item, 0);
|
||||
}
|
||||
|
||||
// toolhead area
|
||||
m_toolhead_nozzle_l = new wgtDeviceNozzleRackNozzleItem(this, 1);
|
||||
m_toolhead_nozzle_l->EnableSelect();
|
||||
m_toolhead_nozzle_l->SetDisplayIdText("L");
|
||||
m_toolhead_nozzle_l->Bind(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, &wgtDeviceNozzleRackSelect::OnNozzleItemSelected, this);
|
||||
|
||||
m_toolhead_nozzle_r = new wgtDeviceNozzleRackNozzleItem(this, 0);
|
||||
m_toolhead_nozzle_r->EnableSelect();
|
||||
m_toolhead_nozzle_r->SetDisplayIdText("R");
|
||||
m_toolhead_nozzle_r->Bind(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, &wgtDeviceNozzleRackSelect::OnNozzleItemSelected, this);
|
||||
|
||||
wxSizer* toolhead_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
toolhead_sizer->Add(m_toolhead_nozzle_l, 0, wxRIGHT, FromDIP(5));
|
||||
toolhead_sizer->Add(m_toolhead_nozzle_r, 0, wxLEFT, FromDIP(5));
|
||||
|
||||
main_sizer->Add(m_title_tips_dynamic, 0, wxALIGN_CENTRE_VERTICAL | wxBOTTOM, FromDIP(10));
|
||||
main_sizer->Add(s_create_title(this, _L("Hotend Rack")), 0, wxEXPAND);
|
||||
main_sizer->Add(nozzle_sizer, 0, wxTOP | wxBOTTOM | wxALIGN_LEFT, FromDIP(10));
|
||||
main_sizer->Add(s_create_title(this, _L("ToolHead")), 0, wxEXPAND);
|
||||
main_sizer->AddSpacer(FromDIP(10));
|
||||
main_sizer->Add(toolhead_sizer, 0, wxALIGN_LEFT);
|
||||
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
SetSizer(main_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
static void s_update_nozzle_info(wgtDeviceNozzleRackNozzleItem* item,
|
||||
std::shared_ptr<DevNozzleRack> rack,
|
||||
const DevNozzle& nozzle_info)
|
||||
{
|
||||
item->Update(rack, nozzle_info.IsOnRack());
|
||||
if (nozzle_info.IsUnknown()) {
|
||||
if (item->GetToolTipText() != _L("Nozzle information needs to be read")) {
|
||||
item->SetToolTip(_L("Nozzle information needs to be read"));
|
||||
}
|
||||
} else {
|
||||
item->SetToolTip(wxEmptyString);
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackSelect::UpdateNozzleInfos(std::shared_ptr<DevNozzleRack> rack)
|
||||
{
|
||||
m_nozzle_rack = rack;
|
||||
if (rack) {
|
||||
s_update_nozzle_info(m_toolhead_nozzle_l, rack, rack->GetNozzleSystem()->GetNozzleByPosId(DEPUTY_EXTRUDER_ID));
|
||||
s_update_nozzle_info(m_toolhead_nozzle_r, rack, rack->GetNozzleSystem()->GetNozzleByPosId(MAIN_EXTRUDER_ID));
|
||||
for (const auto& item : m_nozzle_items) {
|
||||
s_update_nozzle_info(item.second, rack, rack->GetNozzleSystem()->GetNozzleByPosId(item.first));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void s_enable_item_if_match(wgtDeviceNozzleRackNozzleItem* item,
|
||||
const DevNozzle& nozzle_info,
|
||||
const DevNozzle& selected_nozzle)
|
||||
{
|
||||
if (item) {
|
||||
if (nozzle_info.GetLogicExtruderId() != selected_nozzle.GetLogicExtruderId()) {
|
||||
item->SetDisable(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nozzle_info.IsEmpty() && !nozzle_info.IsAbnormal() && !nozzle_info.IsUnknown() &&
|
||||
nozzle_info.GetNozzleType() == selected_nozzle.GetNozzleType() &&
|
||||
nozzle_info.GetNozzleDiameter() == selected_nozzle.GetNozzleDiameter() &&
|
||||
nozzle_info.GetNozzleFlowType() == selected_nozzle.GetNozzleFlowType()) {
|
||||
item->SetDisable(false);
|
||||
} else {
|
||||
item->SetDisable(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackSelect::UpdatSelectedNozzle(std::shared_ptr<DevNozzleRack> rack, int selected_nozzle_pos_id)
|
||||
{
|
||||
m_nozzle_rack = rack;
|
||||
if (rack) {
|
||||
SetSelectedNozzle(rack->GetNozzleSystem()->GetNozzleByPosId(selected_nozzle_pos_id));
|
||||
if (m_enable_manual_nozzle_pick) {
|
||||
s_enable_item_if_match(m_toolhead_nozzle_r, rack->GetNozzleSystem()->GetNozzleByPosId(MAIN_EXTRUDER_ID), m_selected_nozzle);
|
||||
s_enable_item_if_match(m_toolhead_nozzle_l, rack->GetNozzleSystem()->GetNozzleByPosId(DEPUTY_EXTRUDER_ID), m_selected_nozzle);
|
||||
for (auto& item : m_nozzle_items) {
|
||||
s_enable_item_if_match(item.second, rack->GetNozzleSystem()->GetNozzleByPosId(item.first), m_selected_nozzle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackSelect::UpdatSelectedNozzles(std::shared_ptr<DevNozzleRack> rack,
|
||||
std::vector<int> selected_nozzle_pos_vec,
|
||||
bool use_dynamic_switch,
|
||||
std::optional<PrintFromType> /*print_from_type*/)
|
||||
{
|
||||
m_nozzle_rack = rack;
|
||||
m_enable_manual_nozzle_pick = !use_dynamic_switch;
|
||||
m_title_tips_dynamic->Show(use_dynamic_switch);
|
||||
|
||||
UpdateNozzleInfos(rack);
|
||||
if (!use_dynamic_switch) {
|
||||
if (selected_nozzle_pos_vec.size() > 0) {
|
||||
return UpdatSelectedNozzle(rack, selected_nozzle_pos_vec.at(0));
|
||||
} else {
|
||||
return ClearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
if (rack) {
|
||||
ClearSelection();
|
||||
for (const auto& pos_id : selected_nozzle_pos_vec) {
|
||||
if (pos_id == MAIN_EXTRUDER_ID) {
|
||||
m_toolhead_nozzle_r->SetSelected(true);
|
||||
} else if (pos_id == DEPUTY_EXTRUDER_ID) {
|
||||
m_toolhead_nozzle_l->SetSelected(true);
|
||||
} else if (auto it = m_nozzle_items.find(pos_id); it != m_nozzle_items.end()) {
|
||||
it->second->SetSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_toolhead_nozzle_l->IsSelected()) {
|
||||
m_toolhead_nozzle_l->SetDisable(true);
|
||||
}
|
||||
|
||||
if (!m_toolhead_nozzle_r->IsSelected()) {
|
||||
m_toolhead_nozzle_r->SetDisable(true);
|
||||
}
|
||||
|
||||
for (const auto& item : m_nozzle_items) {
|
||||
if (!item.second->IsSelected()) {
|
||||
item.second->SetDisable(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackSelect::ClearSelection()
|
||||
{
|
||||
m_selected_nozzle = DevNozzle();
|
||||
m_toolhead_nozzle_l->SetSelected(false);
|
||||
m_toolhead_nozzle_r->SetSelected(false);
|
||||
for (auto &item : m_nozzle_items) { item.second->SetSelected(false); }
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackSelect::SetSelectedNozzle(const DevNozzle &nozzle)
|
||||
{
|
||||
int new_selected_pos_id = nozzle.GetNozzlePosId();
|
||||
if (m_selected_nozzle.GetNozzlePosId() != new_selected_pos_id) {
|
||||
ClearSelection();
|
||||
|
||||
m_selected_nozzle = nozzle;
|
||||
if (new_selected_pos_id == MAIN_EXTRUDER_ID) {
|
||||
m_toolhead_nozzle_r->SetSelected(true);
|
||||
} else if (new_selected_pos_id == DEPUTY_EXTRUDER_ID) {
|
||||
m_toolhead_nozzle_l->SetSelected(true);
|
||||
} else if (auto it = m_nozzle_items.find(m_selected_nozzle.GetNozzlePosId()); it != m_nozzle_items.end()) {
|
||||
it->second->SetSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
volatile int sGetNozzlePosId(wgtDeviceNozzleRackNozzleItem* item,
|
||||
wgtDeviceNozzleRackNozzleItem* l_item,
|
||||
wgtDeviceNozzleRackNozzleItem* r_item)
|
||||
{
|
||||
int to_select_pos_id = -1;
|
||||
if (item == l_item) {
|
||||
to_select_pos_id = DEPUTY_EXTRUDER_ID;
|
||||
} else if (item == r_item) {
|
||||
to_select_pos_id = MAIN_EXTRUDER_ID;
|
||||
} else {
|
||||
to_select_pos_id = item->GetNozzleId() + 0x10;
|
||||
}
|
||||
|
||||
return to_select_pos_id;
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackSelect::OnNozzleItemSelected(wxCommandEvent &evt)
|
||||
{
|
||||
if (!m_enable_manual_nozzle_pick) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto *item = dynamic_cast<wgtDeviceNozzleRackNozzleItem *>(evt.GetEventObject());
|
||||
if (item; auto ptr = m_nozzle_rack.lock()) {
|
||||
int to_select_pos_id = sGetNozzlePosId(item, m_toolhead_nozzle_l, m_toolhead_nozzle_r);
|
||||
if (to_select_pos_id > -1 && to_select_pos_id != GetSelectedNozzlePosID()) {
|
||||
SetSelectedNozzle(ptr->GetNozzleSystem()->GetNozzleByPosId(to_select_pos_id));
|
||||
wxCommandEvent change_evt(EVT_NOZZLE_SELECT_CHANGED, GetId());
|
||||
change_evt.SetEventObject(this);
|
||||
ProcessEvent(change_evt);
|
||||
evt.Skip();
|
||||
} else {
|
||||
wxCommandEvent change_evt(EVT_NOZZLE_SELECT_CLICKED, GetId());
|
||||
change_evt.SetEventObject(this);
|
||||
ProcessEvent(change_evt);
|
||||
evt.Skip();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wgtDeviceNozzleRackSelect::Rescale()
|
||||
{
|
||||
m_toolhead_nozzle_l->Rescale();
|
||||
m_toolhead_nozzle_r->Rescale();
|
||||
for (auto &item : m_nozzle_items) { item.second->Rescale(); }
|
||||
}
|
||||
|
||||
}; // namespace Slic3r::GUI
|
||||
@@ -0,0 +1,74 @@
|
||||
//**********************************************************/
|
||||
/* File: wgtDeviceNozzleSelect.h
|
||||
* Description: The panel to select nozzle
|
||||
*
|
||||
* \n class wgtDeviceNozzleSelect;
|
||||
//**********************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h" // DevNozzle (value member) + DevDefs.h (PrintFromType, MAIN/DEPUTY_EXTRUDER_ID)
|
||||
|
||||
#include <wx/panel.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
// Previous definitions
|
||||
class Label;
|
||||
namespace Slic3r
|
||||
{
|
||||
struct DevNozzle;
|
||||
class DevNozzleRack;
|
||||
namespace GUI
|
||||
{
|
||||
class wgtDeviceNozzleRackNozzleItem;
|
||||
class wgtMsgBox;
|
||||
}
|
||||
};
|
||||
|
||||
wxDECLARE_EVENT(EVT_NOZZLE_SELECT_CHANGED, wxCommandEvent);
|
||||
wxDECLARE_EVENT(EVT_NOZZLE_SELECT_CLICKED, wxCommandEvent);
|
||||
namespace Slic3r::GUI
|
||||
{
|
||||
class wgtDeviceNozzleRackSelect : public wxPanel
|
||||
{
|
||||
public:
|
||||
wgtDeviceNozzleRackSelect(wxWindow* parent);
|
||||
~wgtDeviceNozzleRackSelect() = default;
|
||||
|
||||
public:
|
||||
int GetSelectedNozzlePosID() const { return m_selected_nozzle.GetNozzlePosId();}
|
||||
void UpdatSelectedNozzles(std::shared_ptr<DevNozzleRack> rack, std::vector<int> selected_nozzle_pos_vec, bool use_dynamic_switch, std::optional<PrintFromType> print_from_type);// for slicing with dynamic switch
|
||||
void Rescale();
|
||||
|
||||
private:
|
||||
void CreateGui();
|
||||
|
||||
void ClearSelection();
|
||||
void SetSelectedNozzle(const DevNozzle &nozzle);
|
||||
void UpdatSelectedNozzle(std::shared_ptr<DevNozzleRack> rack, int selected_nozzle_pos_id = -1); // for slicing without dynamic switch
|
||||
|
||||
void UpdateNozzleInfos(std::shared_ptr<DevNozzleRack> rack);
|
||||
|
||||
void OnNozzleItemSelected(wxCommandEvent& evt);
|
||||
|
||||
private:
|
||||
DevNozzle m_selected_nozzle;
|
||||
std::weak_ptr<DevNozzleRack> m_nozzle_rack;
|
||||
|
||||
// pick
|
||||
bool m_enable_manual_nozzle_pick = true;
|
||||
|
||||
// Label
|
||||
wgtMsgBox* m_title_tips_dynamic;
|
||||
|
||||
// GUI
|
||||
wgtDeviceNozzleRackNozzleItem * m_toolhead_nozzle_l{nullptr};
|
||||
wgtDeviceNozzleRackNozzleItem * m_toolhead_nozzle_r{nullptr};
|
||||
std::unordered_map<int, wgtDeviceNozzleRackNozzleItem *> m_nozzle_items; // from 16 to 21
|
||||
};
|
||||
|
||||
};// end of namespace Slic3r::GUI
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "wgtMsgBox.h"
|
||||
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Widgets/Label.hpp"
|
||||
#include "slic3r/GUI/wxExtensions.hpp"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
|
||||
namespace Slic3r::GUI
|
||||
{
|
||||
|
||||
wgtMsgBox::wgtMsgBox(wxWindow* parent)
|
||||
: StaticBox(parent, wxID_ANY)
|
||||
{
|
||||
CreateGUI();
|
||||
}
|
||||
|
||||
void wgtMsgBox::CreateGUI()
|
||||
{
|
||||
wxBoxSizer* mainSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_txt_label = new Label(this);
|
||||
m_txt_label->SetFont(::Label::Body_13);
|
||||
m_txt_label->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
auto padding_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
padding_sizer->Add(m_txt_label, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||
mainSizer->Add(padding_sizer, 0, wxALIGN_CENTER_VERTICAL | wxALL, 1);
|
||||
|
||||
//Button* btnClose = CreateCloseButton();
|
||||
//wxButton* btnClose = CreateCloseButton();
|
||||
//mainSizer->Add(btnClose, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
SetSizer(mainSizer);
|
||||
mainSizer->Fit(this);
|
||||
}
|
||||
|
||||
void wgtMsgBox::OnCloseClicked(wxCommandEvent& evt)
|
||||
{
|
||||
this->Hide();
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef WGTMSGBOX_H
|
||||
#define WGTMSGBOX_H
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/button.h>
|
||||
|
||||
#include "slic3r/GUI/Widgets/StaticBox.hpp"
|
||||
|
||||
class Label;
|
||||
namespace Slic3r::GUI
|
||||
{
|
||||
class wgtMsgBox : public StaticBox
|
||||
{
|
||||
public:
|
||||
wgtMsgBox(wxWindow* parent);
|
||||
|
||||
public:
|
||||
Label* GetTextLabel() const { return m_txt_label; }
|
||||
|
||||
private:
|
||||
Label* m_txt_label;
|
||||
|
||||
private:
|
||||
void CreateGUI();
|
||||
void OnCloseClicked(wxCommandEvent& evt);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // WGTMSGBOX_H
|
||||
@@ -1,9 +1,14 @@
|
||||
#include "DragDropPanel.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
#include "Widgets/StateColor.hpp"
|
||||
#include <slic3r/GUI/wxExtensions.hpp>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
wxDEFINE_EVENT(wxEVT_DRAG_DROP_COMPLETED, wxCommandEvent);
|
||||
|
||||
struct CustomData
|
||||
{
|
||||
int filament_id;
|
||||
@@ -201,7 +206,7 @@ wxDragResult ColorDropTarget::OnData(wxCoord x, wxCoord y, wxDragResult def)
|
||||
/////////////// ColorDropTarget end ////////////////////////
|
||||
|
||||
|
||||
DragDropPanel::DragDropPanel(wxWindow *parent, const wxString &label, bool is_auto)
|
||||
DragDropPanel::DragDropPanel(wxWindow *parent, const wxString &label, bool is_auto, bool has_title, bool is_sub)
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE)
|
||||
, m_is_auto(is_auto)
|
||||
{
|
||||
@@ -209,22 +214,31 @@ DragDropPanel::DragDropPanel(wxWindow *parent, const wxString &label, bool is_au
|
||||
|
||||
m_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto title_panel = new wxPanel(this);
|
||||
title_panel->SetBackgroundColour(0xEEEEEE);
|
||||
auto title_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
title_panel->SetSizer(title_sizer);
|
||||
if (has_title) {
|
||||
auto title_panel = new wxPanel(this);
|
||||
title_panel->SetBackgroundColour(is_sub ? wxColour(0xF8F8F8) : wxColour(0xEEEEEE));
|
||||
auto title_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
title_panel->SetSizer(title_sizer);
|
||||
|
||||
Label* static_text = new Label(this, label);
|
||||
static_text->SetFont(Label::Head_13);
|
||||
static_text->SetBackgroundColour(0xEEEEEE);
|
||||
m_title_label = new Label(this, label);
|
||||
m_title_label->SetFont(is_sub ? Label::Body_12 : Label::Head_13);
|
||||
if (is_sub)
|
||||
m_title_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#6B6B6B")));
|
||||
m_title_label->SetBackgroundColour(is_sub ? wxColour(0xF8F8F8) : wxColour(0xEEEEEE));
|
||||
|
||||
title_sizer->Add(static_text, 0, wxALIGN_CENTER | wxALL, FromDIP(5));
|
||||
title_sizer->Add(m_title_label, 0, wxALIGN_CENTER | wxALL, FromDIP(5));
|
||||
|
||||
m_sizer->Add(title_panel, 0, wxEXPAND);
|
||||
m_sizer->AddSpacer(10);
|
||||
m_sizer->Add(title_panel, 0, wxEXPAND);
|
||||
m_sizer->AddSpacer(10);
|
||||
}
|
||||
|
||||
m_grid_item_sizer = new wxGridSizer(0, 6, FromDIP(8), FromDIP(8)); // row = 0, col = 3, 10 10 is space
|
||||
m_sizer->Add(m_grid_item_sizer, 1, wxEXPAND | wxALL, FromDIP(8));
|
||||
if (is_sub) {
|
||||
m_grid_item_sizer = new wxGridSizer(0, 3, FromDIP(6), FromDIP(6));
|
||||
m_sizer->Add(m_grid_item_sizer, 0, wxEXPAND);
|
||||
} else {
|
||||
m_grid_item_sizer = new wxGridSizer(0, 6, FromDIP(8), FromDIP(8)); // row = 0, col = 3, 10 10 is space
|
||||
m_sizer->Add(m_grid_item_sizer, 1, wxEXPAND | wxALL, FromDIP(8));
|
||||
}
|
||||
|
||||
// set droptarget
|
||||
auto drop_target = new ColorDropTarget(this);
|
||||
@@ -242,20 +256,35 @@ void DragDropPanel::AddColorBlock(const wxColour &color, const std::string &type
|
||||
m_grid_item_sizer->Add(panel, 0);
|
||||
m_filament_blocks.push_back(panel);
|
||||
if (update_ui) {
|
||||
m_filament_blocks.front()->Refresh(); // FIX BUG: STUDIO-8467
|
||||
Freeze();
|
||||
|
||||
m_grid_item_sizer->Layout();
|
||||
Layout();
|
||||
Fit();
|
||||
GetParent()->GetParent()->Layout();
|
||||
GetParent()->GetParent()->Fit();
|
||||
m_filament_blocks.front()->Refresh(); // FIX BUG: STUDIO-8467
|
||||
|
||||
Thaw();
|
||||
NotifyDragDropCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
void DragDropPanel::RemoveColorBlock(ColorPanel *panel, bool update_ui)
|
||||
{
|
||||
m_sizer->Detach(panel);
|
||||
m_grid_item_sizer->Detach(panel);
|
||||
panel->Destroy();
|
||||
m_filament_blocks.erase(std::remove(m_filament_blocks.begin(), m_filament_blocks.end(), panel), m_filament_blocks.end());
|
||||
if (update_ui) {
|
||||
Freeze();
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
GetParent()->GetParent()->Layout();
|
||||
GetParent()->GetParent()->Fit();
|
||||
|
||||
Thaw();
|
||||
NotifyDragDropCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,6 +299,15 @@ void DragDropPanel::DoDragDrop(ColorPanel *panel, const wxColour &color, const s
|
||||
}
|
||||
}
|
||||
|
||||
void DragDropPanel::UpdateLabel(const wxString &label)
|
||||
{
|
||||
if (m_title_label) {
|
||||
m_title_label->SetLabel(label);
|
||||
m_title_label->Refresh();
|
||||
Layout();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> DragDropPanel::GetAllFilaments() const
|
||||
{
|
||||
std::vector<int> filaments;
|
||||
@@ -287,4 +325,327 @@ std::vector<int> DragDropPanel::GetAllFilaments() const
|
||||
return filaments;
|
||||
}
|
||||
|
||||
void DragDropPanel::NotifyDragDropCompleted()
|
||||
{
|
||||
wxCommandEvent event(wxEVT_DRAG_DROP_COMPLETED);
|
||||
event.SetEventObject(this);
|
||||
|
||||
wxWindow *parent = GetParent();
|
||||
while (parent) {
|
||||
auto name = parent->GetName();
|
||||
if (name == wxT("FilamentMapManualPanel")) {
|
||||
parent->GetEventHandler()->ProcessEvent(event);
|
||||
break;
|
||||
}
|
||||
parent = parent->GetParent();
|
||||
}
|
||||
}
|
||||
|
||||
class SeparatedColorDropTarget : public wxDropTarget
|
||||
{
|
||||
public:
|
||||
SeparatedColorDropTarget(SeparatedDragDropPanel *panel) : wxDropTarget(), m_panel(panel)
|
||||
{
|
||||
m_data = new ColorDataObject();
|
||||
SetDataObject(m_data);
|
||||
}
|
||||
|
||||
virtual wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def) override;
|
||||
virtual bool OnDrop(wxCoord x, wxCoord y) override { return true; }
|
||||
|
||||
private:
|
||||
SeparatedDragDropPanel *m_panel;
|
||||
ColorDataObject *m_data;
|
||||
};
|
||||
|
||||
wxDragResult SeparatedColorDropTarget::OnData(wxCoord x, wxCoord y, wxDragResult def)
|
||||
{
|
||||
if (!GetData()) return wxDragNone;
|
||||
|
||||
m_panel->AddColorBlock(m_data->GetColor(), m_data->GetType(), m_data->GetFilament(), false);
|
||||
return wxDragCopy;
|
||||
}
|
||||
|
||||
SeparatedDragDropPanel::SeparatedDragDropPanel(wxWindow *parent, const wxString &label, bool use_separation) : wxPanel(parent), m_use_separation(use_separation)
|
||||
{
|
||||
SetBackgroundColour(0xF8F8F8);
|
||||
|
||||
m_main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto title_panel = new wxPanel(this);
|
||||
title_panel->SetBackgroundColour(0xEEEEEE);
|
||||
auto title_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
title_panel->SetSizer(title_sizer);
|
||||
|
||||
m_label = new Label(title_panel, label);
|
||||
m_label->SetFont(Label::Head_13);
|
||||
m_label->SetBackgroundColour(0xEEEEEE);
|
||||
|
||||
title_sizer->Add(m_label, 0, wxALIGN_CENTER | wxALL, FromDIP(5));
|
||||
|
||||
m_main_sizer->Add(title_panel, 0, wxEXPAND);
|
||||
m_main_sizer->AddSpacer(10);
|
||||
|
||||
m_content_panel = new wxPanel(this);
|
||||
m_content_panel->SetBackgroundColour(0xF8F8F8);
|
||||
m_content_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_content_panel->SetSizer(m_content_sizer);
|
||||
|
||||
m_high_flow_panel = new DragDropPanel(m_content_panel, _L("High Flow"), false, true, true);
|
||||
m_standard_panel = new DragDropPanel(m_content_panel, _L("Standard"), false, true, true);
|
||||
m_unified_panel = new DragDropPanel(m_content_panel, wxEmptyString, false, false);
|
||||
|
||||
m_high_flow_panel->SetBackgroundColour(0xF8F8F8);
|
||||
m_standard_panel->SetBackgroundColour(0xF8F8F8);
|
||||
m_unified_panel->SetBackgroundColour(0xF8F8F8);
|
||||
m_unified_panel->SetMinSize({FromDIP(260), -1});
|
||||
|
||||
m_main_sizer->Add(m_content_panel, 1, wxEXPAND);
|
||||
|
||||
UpdateLayout();
|
||||
|
||||
auto drop_target = new SeparatedColorDropTarget(this);
|
||||
SetDropTarget(drop_target);
|
||||
|
||||
SetSizer(m_main_sizer);
|
||||
Layout();
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
}
|
||||
|
||||
void SeparatedDragDropPanel::UpdateLayout()
|
||||
{
|
||||
m_content_sizer->Clear(false);
|
||||
|
||||
if (m_use_separation) {
|
||||
m_unified_panel->Hide();
|
||||
m_high_flow_panel->Show();
|
||||
m_standard_panel->Show();
|
||||
|
||||
wxSize content_size = m_content_panel->GetSize();
|
||||
int panel_width = (content_size.GetWidth() - FromDIP(1) - FromDIP(8)) / 2;
|
||||
if (panel_width > 0) {
|
||||
m_high_flow_panel->SetMinSize(wxSize(panel_width, -1));
|
||||
m_standard_panel->SetMinSize(wxSize(panel_width, -1));
|
||||
}
|
||||
|
||||
m_content_sizer->Add(m_high_flow_panel, 1, wxEXPAND | wxLEFT, FromDIP(8));
|
||||
|
||||
// Create the separator once and re-attach it on relayout; Clear(false) above only
|
||||
// detaches, so allocating here would leak a window per relayout.
|
||||
if (!m_separator) {
|
||||
m_separator = new wxPanel(m_content_panel, wxID_ANY);
|
||||
m_separator->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(0xCE, 0xCE, 0xCE)));
|
||||
m_separator->SetMinSize(wxSize(FromDIP(1), -1));
|
||||
}
|
||||
m_separator->Show();
|
||||
m_content_sizer->Add(m_separator, 0, wxEXPAND | wxALL, FromDIP(4));
|
||||
|
||||
m_content_sizer->Add(m_standard_panel, 1, wxEXPAND | wxRIGHT, FromDIP(8));
|
||||
} else {
|
||||
m_high_flow_panel->Hide();
|
||||
m_standard_panel->Hide();
|
||||
if (m_separator)
|
||||
m_separator->Hide();
|
||||
m_unified_panel->Show();
|
||||
|
||||
m_content_sizer->Add(m_unified_panel, 1, wxEXPAND);
|
||||
}
|
||||
m_content_sizer->Layout();
|
||||
Layout();
|
||||
Fit();
|
||||
if (GetParent()) {
|
||||
GetParent()->Layout();
|
||||
if (GetParent()->GetParent()) {
|
||||
GetParent()->GetParent()->Layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SeparatedDragDropPanel::UpdateLabel(const wxString &label)
|
||||
{
|
||||
if (m_label) {
|
||||
m_label->SetLabel(label);
|
||||
m_label->Refresh();
|
||||
Layout();
|
||||
}
|
||||
}
|
||||
|
||||
void SeparatedDragDropPanel::SetUseSeparation(bool use_separation)
|
||||
{
|
||||
if (m_use_separation != use_separation) {
|
||||
m_use_separation = use_separation;
|
||||
|
||||
if (use_separation) {
|
||||
auto blocks = m_unified_panel->get_filament_blocks();
|
||||
for (auto &block : blocks) {
|
||||
m_standard_panel->AddColorBlock(block->GetColor(), block->GetType(), block->GetFilamentId(), false);
|
||||
m_unified_panel->RemoveColorBlock(block, false);
|
||||
}
|
||||
} else {
|
||||
auto high_flow_blocks = m_high_flow_panel->get_filament_blocks();
|
||||
auto standard_blocks = m_standard_panel->get_filament_blocks();
|
||||
|
||||
for (auto &block : high_flow_blocks) {
|
||||
m_unified_panel->AddColorBlock(block->GetColor(), block->GetType(), block->GetFilamentId(), false);
|
||||
m_high_flow_panel->RemoveColorBlock(block, false);
|
||||
}
|
||||
|
||||
for (auto &block : standard_blocks) {
|
||||
m_unified_panel->AddColorBlock(block->GetColor(), block->GetType(), block->GetFilamentId(), false);
|
||||
m_standard_panel->RemoveColorBlock(block, false);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
void SeparatedDragDropPanel::AddColorBlock(const wxColour &color, const std::string &type, int filament_id, bool is_high_flow, bool update_ui)
|
||||
{
|
||||
if (m_use_separation) {
|
||||
if (is_high_flow) {
|
||||
m_high_flow_panel->AddColorBlock(color, type, filament_id, update_ui);
|
||||
} else {
|
||||
m_standard_panel->AddColorBlock(color, type, filament_id, update_ui);
|
||||
}
|
||||
|
||||
if (update_ui) {
|
||||
CallAfter([this]() {
|
||||
Layout();
|
||||
GetParent()->Layout();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
m_unified_panel->AddColorBlock(color, type, filament_id, update_ui);
|
||||
}
|
||||
}
|
||||
|
||||
void SeparatedDragDropPanel::RemoveColorBlock(ColorPanel *panel, bool update_ui)
|
||||
{
|
||||
auto high_flow_blocks = m_high_flow_panel->get_filament_blocks();
|
||||
auto standard_blocks = m_standard_panel->get_filament_blocks();
|
||||
auto unified_blocks = m_unified_panel->get_filament_blocks();
|
||||
|
||||
if (std::find(high_flow_blocks.begin(), high_flow_blocks.end(), panel) != high_flow_blocks.end()) {
|
||||
m_high_flow_panel->RemoveColorBlock(panel, update_ui);
|
||||
} else if (std::find(standard_blocks.begin(), standard_blocks.end(), panel) != standard_blocks.end()) {
|
||||
m_standard_panel->RemoveColorBlock(panel, update_ui);
|
||||
} else if (std::find(unified_blocks.begin(), unified_blocks.end(), panel) != unified_blocks.end()) {
|
||||
m_unified_panel->RemoveColorBlock(panel, update_ui);
|
||||
}
|
||||
|
||||
if (update_ui && m_use_separation) {
|
||||
CallAfter([this]() {
|
||||
Layout();
|
||||
GetParent()->Layout();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> SeparatedDragDropPanel::GetAllFilaments() const
|
||||
{
|
||||
if (m_use_separation) {
|
||||
auto high_flow = m_high_flow_panel->GetAllFilaments();
|
||||
auto standard = m_standard_panel->GetAllFilaments();
|
||||
|
||||
std::vector<int> result;
|
||||
result.insert(result.end(), high_flow.begin(), high_flow.end());
|
||||
result.insert(result.end(), standard.begin(), standard.end());
|
||||
return result;
|
||||
} else {
|
||||
return m_unified_panel->GetAllFilaments();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> SeparatedDragDropPanel::GetHighFlowFilaments() const
|
||||
{
|
||||
if (m_use_separation) {
|
||||
return m_high_flow_panel->GetAllFilaments();
|
||||
}
|
||||
auto nozzle_volumes = wxGetApp().preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
|
||||
const int right_eid = 1;
|
||||
if (nozzle_volumes->values.size() > right_eid) {
|
||||
int volume_type = nozzle_volumes->values[right_eid];
|
||||
if (volume_type == static_cast<int>(NozzleVolumeType::nvtHighFlow)) {
|
||||
return m_unified_panel->GetAllFilaments();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<int> SeparatedDragDropPanel::GetStandardFilaments() const
|
||||
{
|
||||
if (m_use_separation) {
|
||||
return m_standard_panel->GetAllFilaments();
|
||||
}
|
||||
auto nozzle_volumes = wxGetApp().preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
|
||||
const int right_eid = 1;
|
||||
if (nozzle_volumes->values.size() > right_eid) {
|
||||
int volume_type = nozzle_volumes->values[right_eid];
|
||||
if (volume_type == static_cast<int>(NozzleVolumeType::nvtStandard)) {
|
||||
return m_unified_panel->GetAllFilaments();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<int> SeparatedDragDropPanel::GetTPUHighFlowFilaments() const
|
||||
{
|
||||
if (m_use_separation) {
|
||||
return {};
|
||||
}
|
||||
auto nozzle_volumes = wxGetApp().preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
|
||||
const int right_eid = 1;
|
||||
if (nozzle_volumes->values.size() > right_eid) {
|
||||
int volume_type = nozzle_volumes->values[right_eid];
|
||||
if (volume_type == static_cast<int>(NozzleVolumeType::nvtTPUHighFlow)) {
|
||||
return m_unified_panel->GetAllFilaments();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<ColorPanel *> SeparatedDragDropPanel::get_filament_blocks() const
|
||||
{
|
||||
if (m_use_separation) {
|
||||
auto high_flow = m_high_flow_panel->get_filament_blocks();
|
||||
auto standard = m_standard_panel->get_filament_blocks();
|
||||
|
||||
std::vector<ColorPanel *> result;
|
||||
result.insert(result.end(), high_flow.begin(), high_flow.end());
|
||||
result.insert(result.end(), standard.begin(), standard.end());
|
||||
return result;
|
||||
} else {
|
||||
return m_unified_panel->get_filament_blocks();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ColorPanel *> SeparatedDragDropPanel::get_high_flow_blocks() const
|
||||
{
|
||||
return m_high_flow_panel->get_filament_blocks();
|
||||
}
|
||||
|
||||
std::vector<ColorPanel *> SeparatedDragDropPanel::get_standard_blocks() const
|
||||
{
|
||||
return m_standard_panel->get_filament_blocks();
|
||||
}
|
||||
|
||||
void SeparatedDragDropPanel::ClearAllBlocks()
|
||||
{
|
||||
auto high_flow_blocks = m_high_flow_panel->get_filament_blocks();
|
||||
for (auto &block : high_flow_blocks) {
|
||||
m_high_flow_panel->RemoveColorBlock(block, false);
|
||||
}
|
||||
|
||||
auto standard_blocks = m_standard_panel->get_filament_blocks();
|
||||
for (auto &block : standard_blocks) {
|
||||
m_standard_panel->RemoveColorBlock(block, false);
|
||||
}
|
||||
|
||||
auto unified_blocks = m_unified_panel->get_filament_blocks();
|
||||
for (auto &block : unified_blocks) {
|
||||
m_unified_panel->RemoveColorBlock(block, false);
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
|
||||
#include <wx/simplebook.h>
|
||||
#include <wx/dialog.h>
|
||||
@@ -13,17 +14,20 @@
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
wxDECLARE_EVENT(wxEVT_DRAG_DROP_COMPLETED, wxCommandEvent);
|
||||
|
||||
wxColor Hex2Color(const std::string& str);
|
||||
|
||||
class ColorPanel;
|
||||
class DragDropPanel : public wxPanel
|
||||
{
|
||||
public:
|
||||
DragDropPanel(wxWindow *parent, const wxString &label, bool is_auto);
|
||||
DragDropPanel(wxWindow *parent, const wxString &label, bool is_auto, bool has_title = true, bool is_sub = false);
|
||||
|
||||
void AddColorBlock(const wxColour &color, const std::string &type, int filament_id, bool update_ui = true);
|
||||
void RemoveColorBlock(ColorPanel *panel, bool update_ui = true);
|
||||
void DoDragDrop(ColorPanel *panel, const wxColour &color, const std::string &type, int filament_id);
|
||||
void UpdateLabel(const wxString &label);
|
||||
|
||||
std::vector<int> GetAllFilaments() const;
|
||||
|
||||
@@ -35,9 +39,12 @@ public:
|
||||
private:
|
||||
wxBoxSizer *m_sizer;
|
||||
wxGridSizer *m_grid_item_sizer;
|
||||
Label *m_title_label = nullptr;
|
||||
bool m_is_auto;
|
||||
|
||||
std::vector<ColorPanel *> m_filament_blocks;
|
||||
|
||||
void NotifyDragDropCompleted();
|
||||
private:
|
||||
bool m_is_draging = false;
|
||||
};
|
||||
@@ -64,6 +71,49 @@ private:
|
||||
int m_filament_id;
|
||||
|
||||
};
|
||||
|
||||
// Drop zone for one extruder that can split into two sub-zones (High Flow / Standard)
|
||||
// when the extruder mixes nozzle volume types (Hybrid flow), and collapses back to a
|
||||
// single unified zone otherwise.
|
||||
class SeparatedDragDropPanel : public wxPanel
|
||||
{
|
||||
public:
|
||||
SeparatedDragDropPanel(wxWindow *parent, const wxString &label, bool use_separation = false);
|
||||
|
||||
void AddColorBlock(const wxColour &color, const std::string &type, int filament_id, bool is_high_flow = false, bool update_ui = true);
|
||||
void RemoveColorBlock(ColorPanel *panel, bool update_ui = true);
|
||||
|
||||
std::vector<int> GetAllFilaments() const;
|
||||
std::vector<int> GetHighFlowFilaments() const;
|
||||
std::vector<int> GetStandardFilaments() const;
|
||||
std::vector<int> GetTPUHighFlowFilaments() const;
|
||||
|
||||
std::vector<ColorPanel *> get_filament_blocks() const;
|
||||
std::vector<ColorPanel *> get_high_flow_blocks() const;
|
||||
std::vector<ColorPanel *> get_standard_blocks() const;
|
||||
|
||||
void SetUseSeparation(bool use_separation);
|
||||
bool IsUseSeparation() const { return m_use_separation; }
|
||||
void ClearAllBlocks();
|
||||
void UpdateLabel(const wxString &label);
|
||||
|
||||
private:
|
||||
void UpdateLayout();
|
||||
|
||||
wxBoxSizer *m_main_sizer;
|
||||
wxPanel *m_content_panel;
|
||||
wxBoxSizer *m_content_sizer;
|
||||
wxPanel *m_separator = nullptr;
|
||||
Label *m_label;
|
||||
|
||||
DragDropPanel *m_high_flow_panel;
|
||||
DragDropPanel *m_standard_panel;
|
||||
|
||||
DragDropPanel *m_unified_panel;
|
||||
|
||||
bool m_use_separation;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif /* slic3r_DragDropPanel_hpp_ */
|
||||
@@ -174,6 +174,9 @@ FilamentGroupPopup::FilamentGroupPopup(wxWindow *parent) : PopupWindow(parent, w
|
||||
button_labels[idx]->Bind(wxEVT_LEAVE_WINDOW, [this](auto &) { UpdateButtonStatus(); });
|
||||
}
|
||||
|
||||
// Smart filament assign section
|
||||
MakeSmartFilamentSection(top_sizer, horizontal_margin, vertical_padding);
|
||||
|
||||
{
|
||||
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
// ORCA Unified hyperlinks
|
||||
@@ -257,6 +260,7 @@ void FilamentGroupPopup::Init()
|
||||
SetFilamentMapMode(m_mode);
|
||||
}
|
||||
|
||||
UpdateSmartFilamentSection();
|
||||
UpdateButtonStatus();
|
||||
GUI::wxGetApp().UpdateDarkUIWin(this);
|
||||
}
|
||||
@@ -408,4 +412,65 @@ void FilamentGroupPopup::UpdateButtonStatus(int hover_idx)
|
||||
Fit();
|
||||
}
|
||||
|
||||
void FilamentGroupPopup::MakeSmartFilamentSection(wxSizer *top_sizer, int horizontal_margin, int vertical_padding)
|
||||
{
|
||||
m_smart_filament_panel = new StaticBox(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_smart_filament_panel->SetCornerRadius(FromDIP(4));
|
||||
m_smart_filament_panel->SetBorderWidth(FromDIP(1));
|
||||
m_smart_filament_panel->SetBorderColor(wxColour("#CECECE"));
|
||||
m_smart_filament_panel->SetBackgroundColor(StateColor(std::pair<wxColour, int>(wxColour("#F8F8F8"), StateColor::Normal)));
|
||||
|
||||
auto *label = new Label(m_smart_filament_panel, _L("Enable smart filament assign: Assign one filament to multiple nozzles to maximize savings"));
|
||||
label->SetFont(Label::Body_12);
|
||||
label->SetForegroundColour(GreyColor);
|
||||
label->SetBackgroundColour(wxColour("#F8F8F8"));
|
||||
label->Wrap(FromDIP(240));
|
||||
|
||||
m_smart_filament_switch = new SwitchButton(m_smart_filament_panel);
|
||||
m_smart_filament_switch->Bind(wxEVT_TOGGLEBUTTON, &FilamentGroupPopup::OnSmartFilamentToggle, this);
|
||||
#ifdef __WXOSX__
|
||||
// wxEVT_TOGGLEBUTTON event not handled well by PopupWindow on MacOS
|
||||
// we bind a wxEVT_LEFT_DOWN event as a workaround
|
||||
m_smart_filament_switch->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &) {
|
||||
wxCommandEvent evt(wxEVT_TOGGLEBUTTON);
|
||||
evt.SetInt(!m_smart_filament_switch->GetValue());
|
||||
m_smart_filament_switch->Command(evt);
|
||||
});
|
||||
#endif
|
||||
|
||||
auto *panel_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
panel_sizer->Add(label, 1, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(10));
|
||||
panel_sizer->Add(m_smart_filament_switch, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(10));
|
||||
m_smart_filament_panel->SetSizer(panel_sizer);
|
||||
|
||||
top_sizer->Add(m_smart_filament_panel, 0, wxEXPAND | wxLEFT | wxRIGHT, horizontal_margin);
|
||||
m_smart_filament_spacer = top_sizer->AddSpacer(vertical_padding);
|
||||
|
||||
// Hidden by default; shown in Init() when the filament track switch is ready
|
||||
m_smart_filament_panel->Show(false);
|
||||
m_smart_filament_spacer->Show(false);
|
||||
}
|
||||
|
||||
void FilamentGroupPopup::OnSmartFilamentToggle(wxCommandEvent &event)
|
||||
{
|
||||
auto &config = wxGetApp().preset_bundle->project_config;
|
||||
auto *dynamic_filament = dynamic_cast<ConfigOptionBool *>(config.option("enable_filament_dynamic_map"));
|
||||
if (dynamic_filament) { dynamic_filament->value = m_smart_filament_switch->GetValue(); }
|
||||
plater_ref->update();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void FilamentGroupPopup::UpdateSmartFilamentSection()
|
||||
{
|
||||
bool show = wxGetApp().sidebar().is_fila_switch_ready();
|
||||
m_smart_filament_panel->Show(show);
|
||||
m_smart_filament_spacer->Show(show);
|
||||
|
||||
if (show) {
|
||||
auto &config = wxGetApp().preset_bundle->project_config;
|
||||
auto *dynamic_filament = dynamic_cast<ConfigOptionBool *>(config.option("enable_filament_dynamic_map"));
|
||||
if (dynamic_filament) { m_smart_filament_switch->SetValue(dynamic_filament->value); }
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -3,10 +3,13 @@
|
||||
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/bmpbuttn.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/timer.h>
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "Widgets/PopupWindow.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
#include "Widgets/SwitchButton.hpp"
|
||||
#include "Widgets/StaticBox.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
@@ -42,6 +45,10 @@ private:
|
||||
void Init();
|
||||
void UpdateButtonStatus(int hover_idx = -1);
|
||||
void DrawRoundedCorner(int radius);
|
||||
|
||||
void MakeSmartFilamentSection(wxSizer *top_sizer, int horizontal_margin, int vertical_padding);
|
||||
void UpdateSmartFilamentSection();
|
||||
void OnSmartFilamentToggle(wxCommandEvent &event);
|
||||
private:
|
||||
FilamentMapMode GetFilamentMapMode() const;
|
||||
void SetFilamentMapMode(const FilamentMapMode mode);
|
||||
@@ -75,6 +82,10 @@ private:
|
||||
wxStaticText *wiki_link;
|
||||
wxStaticText *video_link;
|
||||
|
||||
StaticBox *m_smart_filament_panel{nullptr};
|
||||
wxSizerItem *m_smart_filament_spacer{nullptr};
|
||||
SwitchButton *m_smart_filament_switch{nullptr};
|
||||
|
||||
PartPlate* partplate_ref{ nullptr };
|
||||
Plater* plater_ref{ nullptr };
|
||||
};
|
||||
|
||||
@@ -36,9 +36,78 @@ static std::vector<int> get_applied_map(DynamicConfig& proj_config, const Plater
|
||||
return plater_ref->get_global_filament_map();
|
||||
}
|
||||
|
||||
static std::vector<int> get_applied_volume_map(DynamicConfig& proj_config, const Plater* plater_ref, const PartPlate* partplate_ref, const bool sync_plate)
|
||||
{
|
||||
if (sync_plate)
|
||||
return partplate_ref->get_real_filament_volume_maps(proj_config);
|
||||
return plater_ref->get_global_filament_volume_map();
|
||||
}
|
||||
|
||||
extern std::string& get_left_extruder_unprintable_text();
|
||||
extern std::string& get_right_extruder_unprintable_text();
|
||||
|
||||
// Orca: minimal smart-filament toggle. When a filament track switch is ready every AMS filament is
|
||||
// reachable from both nozzles, so one filament can be assigned to multiple nozzles to maximize
|
||||
// savings. The checkbox drives the enable_filament_dynamic_map project flag.
|
||||
class SmartFilamentPanel : public wxPanel
|
||||
{
|
||||
static constexpr int spacing = 20;
|
||||
|
||||
public:
|
||||
SmartFilamentPanel(wxWindow *parent) : wxPanel(parent)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
wxBoxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
main_sizer->AddSpacer(FromDIP(spacing));
|
||||
|
||||
auto *separator = new wxPanel(this);
|
||||
separator->SetBackgroundColour(wxColour("#EEEEEE"));
|
||||
main_sizer->Add(separator, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(15));
|
||||
|
||||
main_sizer->AddSpacer(FromDIP(spacing));
|
||||
|
||||
m_smart_filament_checkbox = new CheckBox(this);
|
||||
if (auto *opt = enable_filament_dynamic_map())
|
||||
m_smart_filament_checkbox->SetValue(opt->value);
|
||||
m_smart_filament_checkbox->Bind(wxEVT_TOGGLEBUTTON, &SmartFilamentPanel::on_smart_filament_checkbox, this);
|
||||
|
||||
auto *label = new Label(this, _L("Enable smart filament assign: Assign one filament to multiple nozzles to maximize savings"));
|
||||
label->SetFont(Label::Body_12);
|
||||
|
||||
// Orca: dropped the vendor "Learn more" tracking link (no Orca help page for this feature).
|
||||
|
||||
auto *smart_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
smart_sizer->Add(m_smart_filament_checkbox, 0, wxALIGN_CENTER_VERTICAL);
|
||||
smart_sizer->Add(label, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(3));
|
||||
|
||||
main_sizer->Add(smart_sizer, 0, wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(15));
|
||||
|
||||
SetSizer(main_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
}
|
||||
|
||||
private:
|
||||
static ConfigOptionBool *enable_filament_dynamic_map()
|
||||
{
|
||||
auto &config = wxGetApp().preset_bundle->project_config;
|
||||
return dynamic_cast<ConfigOptionBool *>(config.option("enable_filament_dynamic_map"));
|
||||
}
|
||||
|
||||
void on_smart_filament_checkbox(wxCommandEvent &event)
|
||||
{
|
||||
if (auto *opt = enable_filament_dynamic_map())
|
||||
opt->value = m_smart_filament_checkbox->GetValue();
|
||||
wxGetApp().plater()->update();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
private:
|
||||
CheckBox *m_smart_filament_checkbox{nullptr};
|
||||
};
|
||||
|
||||
|
||||
bool try_pop_up_before_slice(bool is_slice_all, Plater* plater_ref, PartPlate* partplate_ref, bool force_pop_up)
|
||||
{
|
||||
@@ -61,7 +130,9 @@ bool try_pop_up_before_slice(bool is_slice_all, Plater* plater_ref, PartPlate* p
|
||||
std::vector<std::string> filament_types = full_config.option<ConfigOptionStrings>("filament_type")->values;
|
||||
FilamentMapMode applied_mode = get_applied_map_mode(full_config, plater_ref,partplate_ref, sync_plate);
|
||||
std::vector<int> applied_maps = get_applied_map(full_config, plater_ref, partplate_ref, sync_plate);
|
||||
std::vector<int> applied_volume_maps = get_applied_volume_map(full_config, plater_ref, partplate_ref, sync_plate);
|
||||
applied_maps.resize(filament_colors.size(), 1);
|
||||
applied_volume_maps.resize(filament_colors.size(), 0);
|
||||
|
||||
if (!force_pop_up && applied_mode != fmmManual)
|
||||
return true;
|
||||
@@ -79,6 +150,7 @@ bool try_pop_up_before_slice(bool is_slice_all, Plater* plater_ref, PartPlate* p
|
||||
filament_colors,
|
||||
filament_types,
|
||||
applied_maps,
|
||||
applied_volume_maps,
|
||||
filament_lists,
|
||||
applied_mode,
|
||||
plater_ref->get_machine_sync_status(),
|
||||
@@ -90,25 +162,32 @@ bool try_pop_up_before_slice(bool is_slice_all, Plater* plater_ref, PartPlate* p
|
||||
if (ret == wxID_OK) {
|
||||
FilamentMapMode new_mode = map_dlg.get_mode();
|
||||
std::vector<int> new_maps = map_dlg.get_filament_maps();
|
||||
std::vector<int> new_volume_maps = map_dlg.get_filament_volume_maps();
|
||||
if (sync_plate) {
|
||||
if (is_slice_all) {
|
||||
auto plate_list = plater_ref->get_partplate_list().get_plate_list();
|
||||
for (int i = 0; i < plate_list.size(); ++i) {
|
||||
plate_list[i]->set_filament_map_mode(new_mode);
|
||||
if(new_mode == fmmManual)
|
||||
if (new_mode == fmmManual) {
|
||||
plate_list[i]->set_filament_maps(new_maps);
|
||||
plate_list[i]->set_filament_volume_maps(new_volume_maps);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
partplate_ref->set_filament_map_mode(new_mode);
|
||||
if (new_mode == fmmManual)
|
||||
if (new_mode == fmmManual) {
|
||||
partplate_ref->set_filament_maps(new_maps);
|
||||
partplate_ref->set_filament_volume_maps(new_volume_maps);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
plater_ref->set_global_filament_map_mode(new_mode);
|
||||
if (new_mode == fmmManual)
|
||||
if (new_mode == fmmManual) {
|
||||
plater_ref->set_global_filament_map(new_maps);
|
||||
plater_ref->set_global_filament_volume_map(new_volume_maps);
|
||||
}
|
||||
}
|
||||
plater_ref->update();
|
||||
// check whether able to slice, if not, return false
|
||||
@@ -124,21 +203,35 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
|
||||
const std::vector<std::string> &filament_color,
|
||||
const std::vector<std::string> &filament_type,
|
||||
const std::vector<int> &filament_map,
|
||||
const std::vector<int> &filament_volume_map,
|
||||
const std::vector<int> &filaments,
|
||||
const FilamentMapMode mode,
|
||||
bool machine_synced,
|
||||
bool show_default,
|
||||
bool with_checkbox)
|
||||
: wxDialog(parent, wxID_ANY, _L("Filament grouping"), wxDefaultPosition, wxDefaultSize,wxDEFAULT_DIALOG_STYLE), m_filament_color(filament_color), m_filament_type(filament_type), m_filament_map(filament_map)
|
||||
: wxDialog(parent, wxID_ANY, _L("Filament grouping"), wxDefaultPosition, wxDefaultSize,wxDEFAULT_DIALOG_STYLE)
|
||||
, m_filament_color(filament_color)
|
||||
, m_filament_type(filament_type)
|
||||
, m_filament_map(filament_map)
|
||||
, m_filament_volume_map(filament_volume_map)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
|
||||
SetMinSize(wxSize(FromDIP(580), -1));
|
||||
SetMaxSize(wxSize(FromDIP(580), -1));
|
||||
|
||||
// Orca: when a filament track switch is ready, every AMS filament reaches both nozzles, so the
|
||||
// Match/Convenience sub-mode is dropped and Auto is presented purely as a filament-saving mode.
|
||||
// Orca has no fmmAutoForQuality, so "only saving" collapses to "switch ready" and the remaining
|
||||
// auto mode set is { fmmAutoForFlush }.
|
||||
m_fila_switch_ready = wxGetApp().sidebar().is_fila_switch_ready();
|
||||
const bool only_saving_mode = m_fila_switch_ready;
|
||||
const bool auto_match_available = machine_synced && !m_fila_switch_ready;
|
||||
|
||||
if (mode < fmmManual)
|
||||
m_page_type = PageType::ptAuto;
|
||||
else if (mode == fmmManual)
|
||||
else if (mode == fmmManual || mode == fmmNozzleManual)
|
||||
// Orca: there is no dedicated nozzle-manual page, so treat an fmmNozzleManual plate as a manual variant and show the Custom page instead of misfiling it as "Same as Global".
|
||||
m_page_type = PageType::ptManual;
|
||||
else
|
||||
m_page_type = PageType::ptDefault;
|
||||
@@ -148,7 +241,7 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
|
||||
|
||||
wxBoxSizer *mode_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_auto_btn = new CapsuleButton(this, PageType::ptAuto, _L("Auto"), false);
|
||||
m_auto_btn = new CapsuleButton(this, PageType::ptAuto, only_saving_mode ? _L("Fila Saving") : _L("Auto"), false);
|
||||
m_manual_btn = new CapsuleButton(this, PageType::ptManual, _L("Custom"), false);
|
||||
if (show_default)
|
||||
m_default_btn = new CapsuleButton(this, PageType::ptDefault, _L("Same as Global"), true);
|
||||
@@ -167,12 +260,27 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
|
||||
|
||||
auto panel_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
// Orca: fall back to the saving mode whenever Match is unavailable, which now also covers the
|
||||
// filament-track-switch-ready case (auto_match_available folds in !m_fila_switch_ready).
|
||||
FilamentMapMode default_auto_mode = mode >= fmmManual ? fmmAutoForFlush :
|
||||
mode == fmmAutoForMatch && !machine_synced ? fmmAutoForFlush :
|
||||
mode == fmmAutoForMatch && !auto_match_available ? fmmAutoForFlush :
|
||||
mode;
|
||||
|
||||
m_manual_map_panel = new FilamentMapManualPanel(this, m_filament_color, m_filament_type, filaments, filament_map);
|
||||
m_auto_map_panel = new FilamentMapAutoPanel(this, default_auto_mode, machine_synced);
|
||||
m_manual_map_panel = new FilamentMapManualPanel(this, m_filament_color, m_filament_type, filaments, filament_map, filament_volume_map);
|
||||
// A manual grouping can point filaments at a nozzle volume the extruder does not physically
|
||||
// carry; the panel's validation timer reports that here so OK is gated on a printable map.
|
||||
m_manual_map_panel->Bind(wxEVT_INVALID_MANUAL_MAP, [this](wxCommandEvent &event) {
|
||||
if (m_page_type != PageType::ptManual) {
|
||||
if (!m_ok_btn->IsEnabled()) { m_ok_btn->Enable(); }
|
||||
return;
|
||||
}
|
||||
if (event.GetInt()) {
|
||||
if (!m_ok_btn->IsEnabled()) { m_ok_btn->Enable(); }
|
||||
} else {
|
||||
if (m_ok_btn->IsEnabled()) { m_ok_btn->Disable(); }
|
||||
}
|
||||
});
|
||||
m_auto_map_panel = new FilamentMapAutoPanel(this, default_auto_mode, auto_match_available);
|
||||
if (show_default)
|
||||
m_default_map_panel = new FilamentMapDefaultPanel(this);
|
||||
else
|
||||
@@ -183,6 +291,13 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
|
||||
if (show_default) panel_sizer->Add(m_default_map_panel, 0, wxEXPAND);
|
||||
main_sizer->Add(panel_sizer, 0, wxEXPAND);
|
||||
|
||||
// Smart filament section, shown only in filament-saving (flush) mode when the switch is ready.
|
||||
if (m_fila_switch_ready) {
|
||||
m_smart_filament = new SmartFilamentPanel(this);
|
||||
m_smart_filament->Show(get_mode() == fmmAutoForFlush);
|
||||
main_sizer->Add(m_smart_filament, 0, wxEXPAND);
|
||||
}
|
||||
|
||||
wxPanel* bottom_panel = new wxPanel(this);
|
||||
bottom_panel->SetBackgroundColour(*wxWHITE);
|
||||
wxBoxSizer *bottom_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
@@ -271,16 +386,8 @@ void FilamentMapDialog::on_checkbox(wxCommandEvent &event)
|
||||
void FilamentMapDialog::on_ok(wxCommandEvent &event)
|
||||
{
|
||||
if (m_page_type == PageType::ptManual) {
|
||||
std::vector<int> left_filaments = m_manual_map_panel->GetLeftFilaments();
|
||||
std::vector<int> right_filaments = m_manual_map_panel->GetRightFilaments();
|
||||
|
||||
for (int i = 0; i < m_filament_map.size(); ++i) {
|
||||
if (std::find(left_filaments.begin(), left_filaments.end(), i + 1) != left_filaments.end()) {
|
||||
m_filament_map[i] = 1;
|
||||
} else if (std::find(right_filaments.begin(), right_filaments.end(), i + 1) != right_filaments.end()) {
|
||||
m_filament_map[i] = 2;
|
||||
}
|
||||
}
|
||||
m_filament_map = m_manual_map_panel->GetFilamentMaps();
|
||||
m_filament_volume_map = m_manual_map_panel->GetFilamentVolumeMaps();
|
||||
}
|
||||
|
||||
EndModal(wxID_OK);
|
||||
@@ -318,6 +425,14 @@ void FilamentMapDialog::update_panel_status(PageType page)
|
||||
m_auto_map_panel->Show();
|
||||
}
|
||||
|
||||
// The nozzle-availability gate only constrains manual grouping; every other page must
|
||||
// leave OK usable even if the manual page had disabled it.
|
||||
if (page != PageType::ptManual && m_ok_btn && !m_ok_btn->IsEnabled())
|
||||
m_ok_btn->Enable();
|
||||
|
||||
if (m_smart_filament)
|
||||
m_smart_filament->Show(get_mode() == fmmAutoForFlush);
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace GUI {
|
||||
class DragDropPanel;
|
||||
class Plater;
|
||||
class PartPlate;
|
||||
class SmartFilamentPanel;
|
||||
|
||||
/**
|
||||
* @brief Try to pop up the filament map dialog before slicing.
|
||||
@@ -42,6 +43,7 @@ public:
|
||||
const std::vector<std::string>& filament_color,
|
||||
const std::vector<std::string>& filament_type,
|
||||
const std::vector<int> &filament_map,
|
||||
const std::vector<int> &filament_volume_map,
|
||||
const std::vector<int> &filaments,
|
||||
const FilamentMapMode mode,
|
||||
bool machine_synced,
|
||||
@@ -56,6 +58,12 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<int> get_filament_volume_maps() const {
|
||||
if (m_page_type == PageType::ptManual)
|
||||
return m_filament_volume_map;
|
||||
return {};
|
||||
}
|
||||
|
||||
int ShowModal();
|
||||
void set_modal_btn_labels(const wxString& left_label, const wxString& right_label);
|
||||
private:
|
||||
@@ -78,11 +86,15 @@ private:
|
||||
Button* m_ok_btn;
|
||||
Button* m_cancel_btn;
|
||||
CheckBox* m_checkbox;
|
||||
SmartFilamentPanel* m_smart_filament{nullptr};
|
||||
|
||||
bool m_fila_switch_ready{false};
|
||||
|
||||
PageType m_page_type;
|
||||
|
||||
private:
|
||||
std::vector<int> m_filament_map;
|
||||
std::vector<int> m_filament_volume_map;
|
||||
std::vector<std::string> m_filament_color;
|
||||
std::vector<std::string> m_filament_type;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "FilamentMapPanel.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "Widgets/MultiNozzleSync.hpp" // manuallySetNozzleCount producer for extruder_nozzle_stats
|
||||
#include <algorithm>
|
||||
#include <wx/dcbuffer.h>
|
||||
#include <wx/utils.h>
|
||||
#include "wx/graphics.h"
|
||||
@@ -17,28 +20,278 @@ static const wxColour BorderDisableColor = wxColour("#EEEEEE");
|
||||
static const wxColour TextNormalBlackColor = wxColour("#262E30");
|
||||
static const wxColour TextNormalGreyColor = wxColour("#6B6B6B");
|
||||
static const wxColour TextDisableColor = wxColour("#CECECE");
|
||||
static const wxColour TextErrorColor = wxColour("#E14747");
|
||||
|
||||
wxDEFINE_EVENT(wxEVT_INVALID_MANUAL_MAP, wxCommandEvent);
|
||||
|
||||
// Orca: whether the edited printer has an extruder that can physically carry several nozzles
|
||||
// (only such extruders track a per-volume-type nozzle inventory worth validating against).
|
||||
static bool printer_has_multi_nozzle_extruder()
|
||||
{
|
||||
auto *max_nozzle_counts_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionIntsNullable>("extruder_max_nozzle_count");
|
||||
// Skip nil entries: a nullable-int nil is INT_MAX (> 1) and would otherwise falsely pass the gate.
|
||||
return max_nozzle_counts_opt &&
|
||||
std::any_of(max_nozzle_counts_opt->values.begin(), max_nozzle_counts_opt->values.end(),
|
||||
[](int v) { return v > 1 && v != ConfigOptionIntsNullable::nil_value(); });
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::OnTimer(wxTimerEvent &)
|
||||
{
|
||||
bool valid = true;
|
||||
int invalid_eid = -1;
|
||||
NozzleVolumeType invalid_nozzle = NozzleVolumeType::nvtStandard;
|
||||
auto preset_bundle = wxGetApp().preset_bundle;
|
||||
auto proj_config = preset_bundle->project_config;
|
||||
auto nozzle_volume_values = proj_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values;
|
||||
std::vector<int> filament_map = GetFilamentMaps();
|
||||
std::vector<int> filament_volume_map = GetFilamentVolumeMaps();
|
||||
// Orca: only multi-nozzle extruders carry a meaningful nozzle inventory; validating a plain
|
||||
// dual-extruder printer against it would flag every grouping whenever the stats are stale.
|
||||
if (printer_has_multi_nozzle_extruder()) {
|
||||
for (size_t eid = 0; eid < nozzle_volume_values.size(); ++eid) {
|
||||
NozzleVolumeType extruder_volume_type = NozzleVolumeType(nozzle_volume_values[eid]);
|
||||
bool extruder_used = std::find_if(m_filament_list.begin(), m_filament_list.end(),
|
||||
[&filament_map, eid](int fid) {
|
||||
return fid - 1 < (int) filament_map.size() && (filament_map[fid - 1] - 1) == (int) eid;
|
||||
}) != m_filament_list.end();
|
||||
|
||||
if (!extruder_used) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (extruder_volume_type == nvtHybrid) {
|
||||
int standard_count = getExtruderNozzleCount(preset_bundle, eid, NozzleVolumeType::nvtStandard);
|
||||
int highflow_count = getExtruderNozzleCount(preset_bundle, eid, NozzleVolumeType::nvtHighFlow);
|
||||
|
||||
auto has_material_of_type = [this, eid, &filament_map, &filament_volume_map](NozzleVolumeType volume_type) {
|
||||
return std::find_if(m_filament_list.begin(), m_filament_list.end(),
|
||||
[eid, &filament_map, &filament_volume_map, volume_type](int fid) {
|
||||
return fid - 1 < (int) filament_map.size() && (filament_map[fid - 1] - 1) == (int) eid &&
|
||||
fid - 1 < (int) filament_volume_map.size() &&
|
||||
filament_volume_map[fid - 1] == static_cast<int>(volume_type);
|
||||
}) != m_filament_list.end();
|
||||
};
|
||||
|
||||
bool has_standard = has_material_of_type(NozzleVolumeType::nvtStandard);
|
||||
bool has_highflow = has_material_of_type(NozzleVolumeType::nvtHighFlow);
|
||||
|
||||
if ((has_standard && standard_count == 0) ||
|
||||
(has_highflow && highflow_count == 0)) {
|
||||
valid = false;
|
||||
invalid_eid = eid;
|
||||
invalid_nozzle = (has_standard && standard_count == 0) ? NozzleVolumeType::nvtStandard : NozzleVolumeType::nvtHighFlow;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
int count = getExtruderNozzleCount(preset_bundle, eid, extruder_volume_type);
|
||||
if (count == 0) {
|
||||
valid = false;
|
||||
invalid_eid = eid;
|
||||
invalid_nozzle = extruder_volume_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool update_ui = m_invalid_id != invalid_eid;
|
||||
bool send_event = update_ui || m_force_validation;
|
||||
|
||||
m_invalid_id = invalid_eid;
|
||||
|
||||
if (update_ui) {
|
||||
if (valid) {
|
||||
m_errors->Hide();
|
||||
m_suggestion_panel->Hide();
|
||||
} else {
|
||||
m_errors->SetLabel(wxString::Format(_L("Error: %s extruder has no available %s nozzle, current group result is invalid."),
|
||||
invalid_eid == 0 ? _L("Left") : _L("Right"),
|
||||
invalid_nozzle == NozzleVolumeType::nvtStandard ? _L("Standard") : _L("High Flow")));
|
||||
// Re-wrap: wrapping only applies to the label text present when Wrap is called,
|
||||
// and the label was empty at construction time.
|
||||
m_errors->Wrap(FromDIP(520));
|
||||
m_errors->Show();
|
||||
m_suggestion_panel->Show();
|
||||
}
|
||||
m_left_panel->Freeze();
|
||||
m_right_panel->Freeze();
|
||||
m_tips->Freeze();
|
||||
m_description->Freeze();
|
||||
Layout();
|
||||
Fit();
|
||||
this->GetParent()->Layout();
|
||||
this->GetParent()->Fit();
|
||||
m_left_panel->Thaw();
|
||||
m_right_panel->Thaw();
|
||||
m_tips->Thaw();
|
||||
m_description->Thaw();
|
||||
}
|
||||
|
||||
if (send_event) {
|
||||
wxCommandEvent event(wxEVT_INVALID_MANUAL_MAP);
|
||||
event.SetInt(valid);
|
||||
ProcessEvent(event);
|
||||
m_force_validation = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::OnSuggestionClicked(wxCommandEvent &event)
|
||||
{
|
||||
wxWindow *current = this;
|
||||
while (current && !wxDynamicCast(current, wxDialog)) {
|
||||
current = current->GetParent();
|
||||
}
|
||||
|
||||
if (current) {
|
||||
wxDialog *dlg = wxDynamicCast(current, wxDialog);
|
||||
if (dlg) {
|
||||
int invalid_eid = m_invalid_id;
|
||||
dlg->EndModal(wxID_CANCEL);
|
||||
|
||||
if (invalid_eid >= 0) {
|
||||
manuallySetNozzleCount(invalid_eid);
|
||||
}
|
||||
wxGetApp().plater()->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> FilamentMapManualPanel::GetFilamentMaps() const
|
||||
{
|
||||
std::vector<int> new_filament_map = m_filament_map;
|
||||
std::vector<int> left_filaments = this->GetLeftFilaments();
|
||||
std::vector<int> right_filaments = this->GetRightFilaments();
|
||||
|
||||
for (int i = 0; i < (int) new_filament_map.size(); ++i) {
|
||||
if (std::find(left_filaments.begin(), left_filaments.end(), i + 1) != left_filaments.end()) {
|
||||
new_filament_map[i] = 1;
|
||||
} else if (std::find(right_filaments.begin(), right_filaments.end(), i + 1) != right_filaments.end()) {
|
||||
new_filament_map[i] = 2;
|
||||
}
|
||||
}
|
||||
return new_filament_map;
|
||||
}
|
||||
|
||||
std::vector<int> FilamentMapManualPanel::GetFilamentVolumeMaps() const
|
||||
{
|
||||
std::vector<int> volume_map(m_filament_map.size(), 0);
|
||||
|
||||
std::vector<int> left_filaments = this->GetLeftFilaments();
|
||||
std::vector<int> right_high_flow_filaments = this->GetRightHighFlowFilaments();
|
||||
std::vector<int> right_standard_filaments = this->GetRightStandardFilaments();
|
||||
std::vector<int> right_tpu_high_flow_filaments = this->GetRightTPUHighFlowFilaments();
|
||||
|
||||
auto preset_bundle = wxGetApp().preset_bundle;
|
||||
auto proj_config = preset_bundle->project_config;
|
||||
auto nozzle_volume_values = proj_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values;
|
||||
|
||||
for (int i = 0; i < (int) volume_map.size(); ++i) {
|
||||
int filament_id = i + 1;
|
||||
|
||||
if (std::find(left_filaments.begin(), left_filaments.end(), filament_id) != left_filaments.end()) {
|
||||
if (nozzle_volume_values.size() > 0) {
|
||||
// Orca: never emit the Hybrid marker as a per-filament value; on a Hybrid
|
||||
// extruder each filament prints with a concrete flow, defaulting to Standard.
|
||||
volume_map[i] = nozzle_volume_values[0] == static_cast<int>(NozzleVolumeType::nvtHybrid) ?
|
||||
static_cast<int>(NozzleVolumeType::nvtStandard) :
|
||||
nozzle_volume_values[0];
|
||||
}
|
||||
}
|
||||
else if (std::find(right_high_flow_filaments.begin(), right_high_flow_filaments.end(), filament_id) != right_high_flow_filaments.end()) {
|
||||
volume_map[i] = static_cast<int>(NozzleVolumeType::nvtHighFlow);
|
||||
}
|
||||
else if (std::find(right_standard_filaments.begin(), right_standard_filaments.end(), filament_id) != right_standard_filaments.end()) {
|
||||
volume_map[i] = static_cast<int>(NozzleVolumeType::nvtStandard);
|
||||
}
|
||||
else if (std::find(right_tpu_high_flow_filaments.begin(), right_tpu_high_flow_filaments.end(), filament_id) != right_tpu_high_flow_filaments.end()) {
|
||||
volume_map[i] = static_cast<int>(NozzleVolumeType::nvtTPUHighFlow);
|
||||
}
|
||||
}
|
||||
|
||||
return volume_map;
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::SyncPanelHeights()
|
||||
{
|
||||
if (!m_left_panel || !m_right_panel) return;
|
||||
|
||||
auto curr_left = m_left_panel->GetMinSize();
|
||||
auto curr_right = m_right_panel->GetMinSize();
|
||||
|
||||
m_left_panel->SetMinSize(wxSize(FromDIP(260), FromDIP(110)));
|
||||
m_right_panel->SetMinSize(wxSize(FromDIP(260), FromDIP(110)));
|
||||
|
||||
m_left_panel->Layout();
|
||||
m_left_panel->Fit();
|
||||
m_right_panel->Layout();
|
||||
m_right_panel->Fit();
|
||||
|
||||
wxSize left_best_size = m_left_panel->GetBestSize();
|
||||
wxSize right_best_size = m_right_panel->GetBestSize();
|
||||
|
||||
int max_height = std::max(left_best_size.GetHeight(), right_best_size.GetHeight());
|
||||
bool height_changed = curr_left.GetHeight() != max_height || curr_right.GetHeight() != max_height;
|
||||
if (!height_changed) {
|
||||
if (curr_left.GetHeight() > 0)
|
||||
m_left_panel->SetMinSize(curr_left);
|
||||
if (curr_right.GetHeight() > 0)
|
||||
m_right_panel->SetMinSize(curr_right);
|
||||
if (GetParent()) {
|
||||
GetParent()->Layout();
|
||||
GetParent()->Fit();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
m_left_panel->SetMinSize(wxSize(FromDIP(260), max_height));
|
||||
m_right_panel->SetMinSize(wxSize(FromDIP(260), max_height));
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
if (GetParent()) {
|
||||
GetParent()->Layout();
|
||||
GetParent()->Fit();
|
||||
}
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::OnDragDropCompleted(wxCommandEvent &event)
|
||||
{
|
||||
SyncPanelHeights();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
FilamentMapManualPanel::FilamentMapManualPanel(wxWindow *parent,
|
||||
const std::vector<std::string> &color,
|
||||
const std::vector<std::string> &type,
|
||||
const std::vector<int> &filament_list,
|
||||
const std::vector<int> &filament_map)
|
||||
: wxPanel(parent), m_filament_map(filament_map), m_filament_color(color), m_filament_type(type), m_filament_list(filament_list)
|
||||
const std::vector<int> &filament_map,
|
||||
const std::vector<int> &filament_volume_map)
|
||||
: wxPanel(parent)
|
||||
, m_filament_map(filament_map)
|
||||
, m_filament_volume_map(filament_volume_map)
|
||||
, m_filament_list(filament_list)
|
||||
, m_filament_color(color)
|
||||
, m_filament_type(type)
|
||||
{
|
||||
SetName(wxT("FilamentMapManualPanel"));
|
||||
SetBackgroundColour(BgNormalColor);
|
||||
|
||||
auto top_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_description = new Label(this, _L("We will slice according to this grouping method:"));
|
||||
top_sizer->Add(m_description, 0, wxALIGN_LEFT | wxLEFT, FromDIP(15));
|
||||
m_description->Wrap(FromDIP(520));
|
||||
top_sizer->AddSpacer(FromDIP(8));
|
||||
|
||||
auto drag_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_left_panel = new DragDropPanel(this, _L("Left Nozzle"), false);
|
||||
m_right_panel = new DragDropPanel(this, _L("Right Nozzle"), false);
|
||||
m_right_panel = new SeparatedDragDropPanel(this, _L("Right Nozzle"), false);
|
||||
m_switch_btn = new ScalableButton(this, wxID_ANY, "switch_filament_maps");
|
||||
|
||||
UpdateNozzleVolumeType();
|
||||
|
||||
for (size_t idx = 0; idx < m_filament_map.size(); ++idx) {
|
||||
auto iter = std::find(m_filament_list.begin(), m_filament_list.end(), idx + 1);
|
||||
if (iter == m_filament_list.end()) continue;
|
||||
@@ -48,43 +301,155 @@ FilamentMapManualPanel::FilamentMapManualPanel(wxWindow *p
|
||||
m_left_panel->AddColorBlock(color, type, idx + 1);
|
||||
} else {
|
||||
assert(m_filament_map[idx] == 2);
|
||||
m_right_panel->AddColorBlock(color, type, idx + 1);
|
||||
bool is_high_flow = (idx < m_filament_volume_map.size()) && (m_filament_volume_map[idx] == static_cast<int>(NozzleVolumeType::nvtHighFlow));
|
||||
m_right_panel->AddColorBlock(color, type, idx + 1, is_high_flow);
|
||||
}
|
||||
}
|
||||
m_left_panel->SetMinSize({ FromDIP(260),-1 });
|
||||
m_right_panel->SetMinSize({ FromDIP(260),-1 });
|
||||
m_left_panel->SetMinSize({FromDIP(260), FromDIP(110)});
|
||||
m_right_panel->SetMinSize({FromDIP(260), FromDIP(110)});
|
||||
|
||||
drag_sizer->AddStretchSpacer();
|
||||
drag_sizer->Add(m_left_panel, 1, wxEXPAND);
|
||||
drag_sizer->AddSpacer(FromDIP(7));
|
||||
drag_sizer->Add(m_switch_btn, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(1));
|
||||
drag_sizer->AddSpacer(FromDIP(7));
|
||||
drag_sizer->Add(m_switch_btn, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(8));
|
||||
drag_sizer->Add(m_right_panel, 1, wxEXPAND);
|
||||
drag_sizer->AddStretchSpacer();
|
||||
|
||||
top_sizer->Add(drag_sizer, 0, wxEXPAND);
|
||||
|
||||
m_tips = new Label(this, _L("Tip: You can drag the filaments to reassign them to different nozzles."));
|
||||
m_tips->SetFont(Label::Body_14);
|
||||
m_tips->SetForegroundColour(TextNormalGreyColor);
|
||||
m_tips->Wrap(FromDIP(520));
|
||||
top_sizer->AddSpacer(FromDIP(20));
|
||||
top_sizer->Add(m_tips, 0, wxALIGN_LEFT | wxLEFT, FromDIP(15));
|
||||
|
||||
m_errors = new Label(this, "");
|
||||
m_errors->SetFont(Label::Body_13);
|
||||
m_errors->SetForegroundColour(TextErrorColor);
|
||||
m_errors->Wrap(FromDIP(520));
|
||||
top_sizer->AddSpacer(FromDIP(10));
|
||||
top_sizer->Add(m_errors, 0, wxALIGN_LEFT | wxLEFT, FromDIP(15));
|
||||
|
||||
m_errors->Hide();
|
||||
|
||||
m_suggestion_panel = new wxPanel(this, wxID_ANY);
|
||||
m_suggestion_panel->SetBackgroundColour(*wxWHITE);
|
||||
auto suggestion_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto suggestion_text = new Label(m_suggestion_panel, _L("Please adjust your grouping or click "));
|
||||
suggestion_text->SetFont(Label::Body_13);
|
||||
suggestion_text->SetForegroundColour(TextErrorColor);
|
||||
suggestion_text->SetBackgroundColour(*wxWHITE);
|
||||
auto suggestion_btn = new ScalableButton(m_suggestion_panel, wxID_ANY, "edit", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true, 14);
|
||||
suggestion_btn->SetBackgroundColour(*wxWHITE);
|
||||
auto suggestion_text2 = new Label(m_suggestion_panel, _L(" to set nozzle count"));
|
||||
suggestion_text2->SetFont(Label::Body_13);
|
||||
suggestion_text2->SetForegroundColour(TextErrorColor);
|
||||
suggestion_text2->SetBackgroundColour(*wxWHITE);
|
||||
suggestion_sizer->Add(suggestion_text, 0, wxALIGN_CENTER_VERTICAL);
|
||||
suggestion_sizer->Add(suggestion_btn, 0, wxALIGN_CENTER_VERTICAL);
|
||||
suggestion_sizer->Add(suggestion_text2, 0, wxALIGN_CENTER_VERTICAL);
|
||||
m_suggestion_panel->SetSizer(suggestion_sizer);
|
||||
top_sizer->Add(m_suggestion_panel, 0, wxALIGN_LEFT | wxLEFT, FromDIP(15));
|
||||
m_suggestion_panel->Hide();
|
||||
suggestion_btn->Bind(wxEVT_BUTTON, &FilamentMapManualPanel::OnSuggestionClicked, this);
|
||||
|
||||
// Multi-nozzle: give the user a reachable way to declare, per extruder, how many
|
||||
// physical nozzles of each volume type a multi-nozzle extruder carries. This is the
|
||||
// fallback "manual" producer of the extruder_nozzle_stats config (the full device nozzle-rack
|
||||
// auto-sync is deferred). Gated on the edited printer preset having an extruder with
|
||||
// extruder_max_nozzle_count > 1, so the trigger is not even created for any single-nozzle or
|
||||
// dual-extruder ({1,1}, H2D) printer - zero UI change for every existing profile.
|
||||
if (printer_has_multi_nozzle_extruder()) {
|
||||
auto *max_nozzle_counts_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionIntsNullable>("extruder_max_nozzle_count");
|
||||
auto *set_count_link = new Label(this, _L("Set the physical nozzle count..."));
|
||||
set_count_link->SetFont(Label::Body_14);
|
||||
set_count_link->SetForegroundColour(BorderSelectedColor);
|
||||
set_count_link->SetCursor(wxCursor(wxCURSOR_HAND));
|
||||
top_sizer->AddSpacer(FromDIP(8));
|
||||
top_sizer->Add(set_count_link, 0, wxALIGN_LEFT | wxLEFT, FromDIP(15));
|
||||
const std::vector<int> max_counts = max_nozzle_counts_opt->values;
|
||||
set_count_link->Bind(wxEVT_LEFT_DOWN, [max_counts](wxMouseEvent &evt) {
|
||||
for (int extruder_id = 0; extruder_id < (int) max_counts.size(); ++extruder_id) {
|
||||
if (max_counts[extruder_id] > 1 && max_counts[extruder_id] != ConfigOptionIntsNullable::nil_value())
|
||||
GUI::manuallySetNozzleCount(extruder_id);
|
||||
}
|
||||
evt.Skip();
|
||||
});
|
||||
}
|
||||
|
||||
m_timer = new wxTimer(this);
|
||||
Bind(wxEVT_TIMER, &FilamentMapManualPanel::OnTimer, this);
|
||||
Bind(wxEVT_DRAG_DROP_COMPLETED, &FilamentMapManualPanel::OnDragDropCompleted, this);
|
||||
|
||||
m_switch_btn->Bind(wxEVT_BUTTON, &FilamentMapManualPanel::OnSwitchFilament, this);
|
||||
|
||||
SetSizer(top_sizer);
|
||||
SetMinSize(wxSize(FromDIP(580), -1));
|
||||
Layout();
|
||||
Fit();
|
||||
GUI::wxGetApp().UpdateDarkUIWin(this);
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::UpdateNozzleVolumeType()
|
||||
{
|
||||
auto check_separation = []() {
|
||||
auto preset_bundle = wxGetApp().preset_bundle;
|
||||
auto nozzle_volume_values = preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values;
|
||||
if (nozzle_volume_values.size() <= 1)
|
||||
return false;
|
||||
|
||||
return nozzle_volume_values[1] == static_cast<int>(NozzleVolumeType::nvtHybrid);
|
||||
};
|
||||
bool should_separate = check_separation();
|
||||
m_right_panel->SetUseSeparation(should_separate);
|
||||
|
||||
UpdateNozzleCountDisplay();
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::UpdateNozzleCountDisplay()
|
||||
{
|
||||
auto preset_bundle = wxGetApp().preset_bundle;
|
||||
|
||||
// Orca: nozzle counts are only tracked (and only meaningful) for multi-nozzle extruders;
|
||||
// plain dual-extruder printers keep their unadorned zone titles.
|
||||
if (!printer_has_multi_nozzle_extruder()) {
|
||||
m_left_panel->UpdateLabel(_L("Left Nozzle"));
|
||||
m_right_panel->UpdateLabel(_L("Right Nozzle"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Format the count suffix separately so a translation containing '%' cannot
|
||||
// corrupt the wxString::Format output.
|
||||
int left_count = getExtruderNozzleCountTotal(preset_bundle, 0);
|
||||
wxString left_title = _L("Left Nozzle") + wxString::Format("(%d)", left_count);
|
||||
m_left_panel->UpdateLabel(left_title);
|
||||
|
||||
if (m_right_panel->IsUseSeparation()) {
|
||||
int standard_count = getExtruderNozzleCount(preset_bundle, 1, NozzleVolumeType::nvtStandard);
|
||||
int highflow_count = getExtruderNozzleCount(preset_bundle, 1, NozzleVolumeType::nvtHighFlow);
|
||||
wxString right_title = _L("Right Nozzle") + wxString::Format("(Std: %d, HF: %d)", standard_count, highflow_count);
|
||||
m_right_panel->UpdateLabel(right_title);
|
||||
} else {
|
||||
int right_count = getExtruderNozzleCountTotal(preset_bundle, 1);
|
||||
wxString right_title = _L("Right Nozzle") + wxString::Format("(%d)", right_count);
|
||||
m_right_panel->UpdateLabel(right_title);
|
||||
}
|
||||
}
|
||||
|
||||
FilamentMapManualPanel::~FilamentMapManualPanel()
|
||||
{
|
||||
m_timer->Stop();
|
||||
delete m_timer;
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::OnSwitchFilament(wxCommandEvent &)
|
||||
{
|
||||
auto left_blocks = m_left_panel->get_filament_blocks();
|
||||
auto right_blocks = m_right_panel->get_filament_blocks();
|
||||
|
||||
for (auto &block : left_blocks) {
|
||||
m_right_panel->AddColorBlock(block->GetColor(), block->GetType(), block->GetFilamentId(), false);
|
||||
m_right_panel->AddColorBlock(block->GetColor(), block->GetType(), block->GetFilamentId(), false, false);
|
||||
m_left_panel->RemoveColorBlock(block, false);
|
||||
}
|
||||
|
||||
@@ -94,22 +459,27 @@ void FilamentMapManualPanel::OnSwitchFilament(wxCommandEvent &)
|
||||
}
|
||||
this->GetParent()->Layout();
|
||||
this->GetParent()->Fit();
|
||||
|
||||
if (m_right_panel->IsUseSeparation()) {
|
||||
m_left_panel->Layout();
|
||||
m_left_panel->Fit();
|
||||
m_right_panel->Layout();
|
||||
m_right_panel->Fit();
|
||||
SyncPanelHeights();
|
||||
}
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::Hide()
|
||||
bool FilamentMapManualPanel::Show(bool show)
|
||||
{
|
||||
m_left_panel->Hide();
|
||||
m_right_panel->Hide();
|
||||
m_switch_btn->Hide();
|
||||
wxPanel::Hide();
|
||||
}
|
||||
m_force_validation = show;
|
||||
if (show) {
|
||||
m_timer->Start(500);
|
||||
SyncPanelHeights();
|
||||
} else {
|
||||
m_timer->Stop();
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::Show()
|
||||
{
|
||||
m_left_panel->Show();
|
||||
m_right_panel->Show();
|
||||
m_switch_btn->Show();
|
||||
wxPanel::Show();
|
||||
return wxPanel::Show(show);
|
||||
}
|
||||
|
||||
GUI::FilamentMapBtnPanel::FilamentMapBtnPanel(wxWindow *parent, const wxString &label, const wxString &detail, const std::string &icon) : wxPanel(parent)
|
||||
|
||||
Loaded 100 of 702 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user