feat(gui): per-filament nozzle volume selection and persistence

- FilamentMapDialog's manual page understands volume types: a mixed
  (Hybrid) extruder shows separate Standard / High Flow drop zones
  with live sub-nozzle counts, a validation timer with an inline
  error + "set nozzle count" suggestion, and composes a per-filament
  volume map on OK (persisted to the plate or globally)
- switching an extruder's Flow type rewrites the affected plate map
  entries; plate maps stay sized across filament add/delete/count
  changes (values keep their filament, no index shift)
- CLI: manual mapping on multi-nozzle printers synthesizes the volume
  map from extruder flow types when absent; nozzle-manual mode
  requires explicit maps; computed maps land on the plate so exported
  projects carry filament_volume_maps
- filament_nozzle_map joins the project options (selection seeding +
  filament-count resizing)
- pot entries for the new dialog strings

All 19 reference fixtures byte-identical; slicing an exported Hybrid
project reproduces its g-code byte-for-byte with the volume map
round-tripped through model_settings.config.
This commit is contained in:
SoftFever
2026-07-11 16:37:50 +08:00
parent 21b46044d0
commit 75c8d0775a
14 changed files with 1131 additions and 83 deletions
+10
View File
@@ -18332,6 +18332,16 @@ msgstr ""
msgid "Set the physical nozzle count..." msgid "Set the physical nozzle count..."
msgstr "" msgstr ""
#, possible-c-format, possible-boost-format
msgid "Error: %s extruder has no available %s nozzle, current group result is invalid."
msgstr ""
msgid "Please adjust your grouping or click "
msgstr ""
msgid " to set nozzle count"
msgstr ""
msgid "Prime tower is required for nozzle changing. There may be flaws on the model without prime tower. Are you sure you want to disable prime tower?" msgid "Prime tower is required for nozzle changing. There may be flaws on the model without prime tower. Are you sure you want to disable prime tower?"
msgstr "" msgstr ""
+82
View File
@@ -5945,6 +5945,72 @@ int CLI::run(int argc, char **argv)
else else
filament_maps = part_plate->get_real_filament_maps(m_print_config); 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++) for (int index = 0; index < filament_maps.size(); index++)
{ {
int filament_extruder = filament_maps[index]; int filament_extruder = filament_maps[index];
@@ -6183,6 +6249,22 @@ int CLI::run(int argc, char **argv)
BOOST_LOG_TRIVIAL(info) << "print::process: first time_using_cache is " << time_using_cache << " secs."; BOOST_LOG_TRIVIAL(info) << "print::process: first time_using_cache is " << time_using_cache << " secs.";
} }
if (printer_technology == ptFFF) { 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(); std::string conflict_result = print_fff->get_conflict_string();
if (!conflict_result.empty()) { if (!conflict_result.empty()) {
BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": found slicing result conflict!"<< std::endl; BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": found slicing result conflict!"<< std::endl;
+18
View File
@@ -61,6 +61,9 @@ static std::vector<std::string> s_project_options {
// Per-filament nozzle-volume choice; project-level like filament_map so the per-filament // Per-filament nozzle-volume choice; project-level like filament_map so the per-filament
// slot resolution survives preset switches. // slot resolution survives preset switches.
"filament_volume_map", "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 // 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 // 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. // restored from a saved 3mf; reset to false on load and set true only by live device sync.
@@ -2717,6 +2720,9 @@ void PresetBundle::update_selections(AppConfig &config)
std::vector<int> filament_maps(filament_colors.size(), 1); std::vector<int> filament_maps(filament_colors.size(), 1);
project_config.option<ConfigOptionInts>("filament_map")->values = filament_maps; 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)); 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; project_config.option<ConfigOptionInts>("filament_volume_map")->values = filament_volume_maps;
@@ -2864,6 +2870,9 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
std::vector<int> filament_maps(filament_colors.size(), 1); std::vector<int> filament_maps(filament_colors.size(), 1);
project_config.option<ConfigOptionInts>("filament_map")->values = filament_maps; 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)); 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; project_config.option<ConfigOptionInts>("filament_volume_map")->values = filament_volume_maps;
@@ -3043,6 +3052,7 @@ 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_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type"); ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map"); 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"); ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
filament_color->resize(n); filament_color->resize(n);
@@ -3053,6 +3063,7 @@ void PresetBundle::set_num_filaments(unsigned int n, std::vector<std::string> ne
} }
filament_color_type->resize(n); filament_color_type->resize(n);
filament_map->values.resize(n, 1); filament_map->values.resize(n, 1);
filament_nozzle_map->values.resize(n, 0);
filament_volume_map->values.resize(n, static_cast<int>(NozzleVolumeType::nvtStandard)); filament_volume_map->values.resize(n, static_cast<int>(NozzleVolumeType::nvtStandard));
ams_multi_color_filment.resize(n); ams_multi_color_filment.resize(n);
@@ -3081,6 +3092,7 @@ 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_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type"); ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map"); 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"); ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
filament_color->resize(n); filament_color->resize(n);
@@ -3091,6 +3103,7 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
} }
filament_color_type->resize(n); filament_color_type->resize(n);
filament_map->values.resize(n, 1); filament_map->values.resize(n, 1);
filament_nozzle_map->values.resize(n, 0);
filament_volume_map->values.resize(n, static_cast<int>(NozzleVolumeType::nvtStandard)); filament_volume_map->values.resize(n, static_cast<int>(NozzleVolumeType::nvtStandard));
ams_multi_color_filment.resize(n); ams_multi_color_filment.resize(n);
@@ -3132,12 +3145,16 @@ void PresetBundle::update_num_filaments(unsigned int to_del_flament_id)
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour"); ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
ConfigOptionStrings *filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type"); ConfigOptionStrings *filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map"); 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"); ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
if (filament_color->values.size() > to_del_flament_id) { if (filament_color->values.size() > to_del_flament_id) {
filament_color->values.erase(filament_color->values.begin() + 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) { if (filament_map->values.size() > to_del_flament_id) {
filament_map->values.erase(filament_map->values.begin() + 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) { if (filament_volume_map->values.size() > to_del_flament_id) {
filament_volume_map->values.erase(filament_volume_map->values.begin() + to_del_flament_id); filament_volume_map->values.erase(filament_volume_map->values.begin() + to_del_flament_id);
} }
@@ -3145,6 +3162,7 @@ void PresetBundle::update_num_filaments(unsigned int to_del_flament_id)
else { else {
filament_color->values.resize(to_del_flament_id); filament_color->values.resize(to_del_flament_id);
filament_map->values.resize(to_del_flament_id, 1); 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)); filament_volume_map->values.resize(to_del_flament_id, static_cast<int>(NozzleVolumeType::nvtStandard));
} }
+376 -15
View File
@@ -1,9 +1,14 @@
#include "DragDropPanel.hpp" #include "DragDropPanel.hpp"
#include "GUI_App.hpp"
#include "I18N.hpp"
#include "Widgets/Label.hpp" #include "Widgets/Label.hpp"
#include "Widgets/StateColor.hpp"
#include <slic3r/GUI/wxExtensions.hpp> #include <slic3r/GUI/wxExtensions.hpp>
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
wxDEFINE_EVENT(wxEVT_DRAG_DROP_COMPLETED, wxCommandEvent);
struct CustomData struct CustomData
{ {
int filament_id; int filament_id;
@@ -201,7 +206,7 @@ wxDragResult ColorDropTarget::OnData(wxCoord x, wxCoord y, wxDragResult def)
/////////////// ColorDropTarget end //////////////////////// /////////////// 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) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE)
, m_is_auto(is_auto) , m_is_auto(is_auto)
{ {
@@ -209,22 +214,31 @@ DragDropPanel::DragDropPanel(wxWindow *parent, const wxString &label, bool is_au
m_sizer = new wxBoxSizer(wxVERTICAL); m_sizer = new wxBoxSizer(wxVERTICAL);
auto title_panel = new wxPanel(this); if (has_title) {
title_panel->SetBackgroundColour(0xEEEEEE); auto title_panel = new wxPanel(this);
auto title_sizer = new wxBoxSizer(wxHORIZONTAL); title_panel->SetBackgroundColour(is_sub ? wxColour(0xF8F8F8) : wxColour(0xEEEEEE));
title_panel->SetSizer(title_sizer); auto title_sizer = new wxBoxSizer(wxHORIZONTAL);
title_panel->SetSizer(title_sizer);
Label* static_text = new Label(this, label); m_title_label = new Label(this, label);
static_text->SetFont(Label::Head_13); m_title_label->SetFont(is_sub ? Label::Body_12 : Label::Head_13);
static_text->SetBackgroundColour(0xEEEEEE); 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->Add(title_panel, 0, wxEXPAND);
m_sizer->AddSpacer(10); m_sizer->AddSpacer(10);
}
m_grid_item_sizer = new wxGridSizer(0, 6, FromDIP(8), FromDIP(8)); // row = 0, col = 3, 10 10 is space if (is_sub) {
m_sizer->Add(m_grid_item_sizer, 1, wxEXPAND | wxALL, FromDIP(8)); 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 // set droptarget
auto drop_target = new ColorDropTarget(this); 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_grid_item_sizer->Add(panel, 0);
m_filament_blocks.push_back(panel); m_filament_blocks.push_back(panel);
if (update_ui) { 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()->Layout();
GetParent()->GetParent()->Fit(); GetParent()->GetParent()->Fit();
m_filament_blocks.front()->Refresh(); // FIX BUG: STUDIO-8467
Thaw();
NotifyDragDropCompleted();
} }
} }
void DragDropPanel::RemoveColorBlock(ColorPanel *panel, bool update_ui) void DragDropPanel::RemoveColorBlock(ColorPanel *panel, bool update_ui)
{ {
m_sizer->Detach(panel); m_grid_item_sizer->Detach(panel);
panel->Destroy(); panel->Destroy();
m_filament_blocks.erase(std::remove(m_filament_blocks.begin(), m_filament_blocks.end(), panel), m_filament_blocks.end()); m_filament_blocks.erase(std::remove(m_filament_blocks.begin(), m_filament_blocks.end(), panel), m_filament_blocks.end());
if (update_ui) { if (update_ui) {
Freeze();
Layout();
Fit();
GetParent()->GetParent()->Layout(); GetParent()->GetParent()->Layout();
GetParent()->GetParent()->Fit(); 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> DragDropPanel::GetAllFilaments() const
{ {
std::vector<int> filaments; std::vector<int> filaments;
@@ -287,4 +325,327 @@ std::vector<int> DragDropPanel::GetAllFilaments() const
return filaments; 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 }} // namespace Slic3r::GUI
+51 -1
View File
@@ -3,6 +3,7 @@
#include "GUI.hpp" #include "GUI.hpp"
#include "GUI_Utils.hpp" #include "GUI_Utils.hpp"
#include "Widgets/Label.hpp"
#include <wx/simplebook.h> #include <wx/simplebook.h>
#include <wx/dialog.h> #include <wx/dialog.h>
@@ -13,17 +14,20 @@
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
wxDECLARE_EVENT(wxEVT_DRAG_DROP_COMPLETED, wxCommandEvent);
wxColor Hex2Color(const std::string& str); wxColor Hex2Color(const std::string& str);
class ColorPanel; class ColorPanel;
class DragDropPanel : public wxPanel class DragDropPanel : public wxPanel
{ {
public: 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 AddColorBlock(const wxColour &color, const std::string &type, int filament_id, bool update_ui = true);
void RemoveColorBlock(ColorPanel *panel, 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 DoDragDrop(ColorPanel *panel, const wxColour &color, const std::string &type, int filament_id);
void UpdateLabel(const wxString &label);
std::vector<int> GetAllFilaments() const; std::vector<int> GetAllFilaments() const;
@@ -35,9 +39,12 @@ public:
private: private:
wxBoxSizer *m_sizer; wxBoxSizer *m_sizer;
wxGridSizer *m_grid_item_sizer; wxGridSizer *m_grid_item_sizer;
Label *m_title_label = nullptr;
bool m_is_auto; bool m_is_auto;
std::vector<ColorPanel *> m_filament_blocks; std::vector<ColorPanel *> m_filament_blocks;
void NotifyDragDropCompleted();
private: private:
bool m_is_draging = false; bool m_is_draging = false;
}; };
@@ -64,6 +71,49 @@ private:
int m_filament_id; 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 }} // namespace Slic3r::GUI
#endif /* slic3r_DragDropPanel_hpp_ */ #endif /* slic3r_DragDropPanel_hpp_ */
+47 -15
View File
@@ -36,6 +36,13 @@ static std::vector<int> get_applied_map(DynamicConfig& proj_config, const Plater
return plater_ref->get_global_filament_map(); 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_left_extruder_unprintable_text();
extern std::string& get_right_extruder_unprintable_text(); extern std::string& get_right_extruder_unprintable_text();
@@ -123,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; 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); 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_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_maps.resize(filament_colors.size(), 1);
applied_volume_maps.resize(filament_colors.size(), 0);
if (!force_pop_up && applied_mode != fmmManual) if (!force_pop_up && applied_mode != fmmManual)
return true; return true;
@@ -141,6 +150,7 @@ bool try_pop_up_before_slice(bool is_slice_all, Plater* plater_ref, PartPlate* p
filament_colors, filament_colors,
filament_types, filament_types,
applied_maps, applied_maps,
applied_volume_maps,
filament_lists, filament_lists,
applied_mode, applied_mode,
plater_ref->get_machine_sync_status(), plater_ref->get_machine_sync_status(),
@@ -152,25 +162,32 @@ bool try_pop_up_before_slice(bool is_slice_all, Plater* plater_ref, PartPlate* p
if (ret == wxID_OK) { if (ret == wxID_OK) {
FilamentMapMode new_mode = map_dlg.get_mode(); FilamentMapMode new_mode = map_dlg.get_mode();
std::vector<int> new_maps = map_dlg.get_filament_maps(); 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 (sync_plate) {
if (is_slice_all) { if (is_slice_all) {
auto plate_list = plater_ref->get_partplate_list().get_plate_list(); auto plate_list = plater_ref->get_partplate_list().get_plate_list();
for (int i = 0; i < plate_list.size(); ++i) { for (int i = 0; i < plate_list.size(); ++i) {
plate_list[i]->set_filament_map_mode(new_mode); 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_maps(new_maps);
plate_list[i]->set_filament_volume_maps(new_volume_maps);
}
} }
} }
else { else {
partplate_ref->set_filament_map_mode(new_mode); 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_maps(new_maps);
partplate_ref->set_filament_volume_maps(new_volume_maps);
}
} }
} }
else { else {
plater_ref->set_global_filament_map_mode(new_mode); 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_map(new_maps);
plater_ref->set_global_filament_volume_map(new_volume_maps);
}
} }
plater_ref->update(); plater_ref->update();
// check whether able to slice, if not, return false // check whether able to slice, if not, return false
@@ -186,12 +203,17 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
const std::vector<std::string> &filament_color, const std::vector<std::string> &filament_color,
const std::vector<std::string> &filament_type, const std::vector<std::string> &filament_type,
const std::vector<int> &filament_map, const std::vector<int> &filament_map,
const std::vector<int> &filament_volume_map,
const std::vector<int> &filaments, const std::vector<int> &filaments,
const FilamentMapMode mode, const FilamentMapMode mode,
bool machine_synced, bool machine_synced,
bool show_default, bool show_default,
bool with_checkbox) 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); SetBackgroundColour(*wxWHITE);
@@ -244,7 +266,20 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
mode == fmmAutoForMatch && !auto_match_available ? fmmAutoForFlush : mode == fmmAutoForMatch && !auto_match_available ? fmmAutoForFlush :
mode; mode;
m_manual_map_panel = new FilamentMapManualPanel(this, m_filament_color, m_filament_type, filaments, filament_map); 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); m_auto_map_panel = new FilamentMapAutoPanel(this, default_auto_mode, auto_match_available);
if (show_default) if (show_default)
m_default_map_panel = new FilamentMapDefaultPanel(this); m_default_map_panel = new FilamentMapDefaultPanel(this);
@@ -351,16 +386,8 @@ void FilamentMapDialog::on_checkbox(wxCommandEvent &event)
void FilamentMapDialog::on_ok(wxCommandEvent &event) void FilamentMapDialog::on_ok(wxCommandEvent &event)
{ {
if (m_page_type == PageType::ptManual) { if (m_page_type == PageType::ptManual) {
std::vector<int> left_filaments = m_manual_map_panel->GetLeftFilaments(); m_filament_map = m_manual_map_panel->GetFilamentMaps();
std::vector<int> right_filaments = m_manual_map_panel->GetRightFilaments(); m_filament_volume_map = m_manual_map_panel->GetFilamentVolumeMaps();
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;
}
}
} }
EndModal(wxID_OK); EndModal(wxID_OK);
@@ -398,6 +425,11 @@ void FilamentMapDialog::update_panel_status(PageType page)
m_auto_map_panel->Show(); 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) if (m_smart_filament)
m_smart_filament->Show(get_mode() == fmmAutoForFlush); m_smart_filament->Show(get_mode() == fmmAutoForFlush);
+8
View File
@@ -43,6 +43,7 @@ public:
const std::vector<std::string>& filament_color, const std::vector<std::string>& filament_color,
const std::vector<std::string>& filament_type, const std::vector<std::string>& filament_type,
const std::vector<int> &filament_map, const std::vector<int> &filament_map,
const std::vector<int> &filament_volume_map,
const std::vector<int> &filaments, const std::vector<int> &filaments,
const FilamentMapMode mode, const FilamentMapMode mode,
bool machine_synced, bool machine_synced,
@@ -57,6 +58,12 @@ public:
return {}; return {};
} }
std::vector<int> get_filament_volume_maps() const {
if (m_page_type == PageType::ptManual)
return m_filament_volume_map;
return {};
}
int ShowModal(); int ShowModal();
void set_modal_btn_labels(const wxString& left_label, const wxString& right_label); void set_modal_btn_labels(const wxString& left_label, const wxString& right_label);
private: private:
@@ -87,6 +94,7 @@ private:
private: private:
std::vector<int> m_filament_map; 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_color;
std::vector<std::string> m_filament_type; std::vector<std::string> m_filament_type;
}; };
+372 -31
View File
@@ -1,5 +1,6 @@
#include "FilamentMapPanel.hpp" #include "FilamentMapPanel.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
#include "Plater.hpp"
#include "Widgets/MultiNozzleSync.hpp" // manuallySetNozzleCount producer for extruder_nozzle_stats #include "Widgets/MultiNozzleSync.hpp" // manuallySetNozzleCount producer for extruder_nozzle_stats
#include <algorithm> #include <algorithm>
#include <wx/dcbuffer.h> #include <wx/dcbuffer.h>
@@ -19,28 +20,278 @@ static const wxColour BorderDisableColor = wxColour("#EEEEEE");
static const wxColour TextNormalBlackColor = wxColour("#262E30"); static const wxColour TextNormalBlackColor = wxColour("#262E30");
static const wxColour TextNormalGreyColor = wxColour("#6B6B6B"); static const wxColour TextNormalGreyColor = wxColour("#6B6B6B");
static const wxColour TextDisableColor = wxColour("#CECECE"); 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, FilamentMapManualPanel::FilamentMapManualPanel(wxWindow *parent,
const std::vector<std::string> &color, const std::vector<std::string> &color,
const std::vector<std::string> &type, const std::vector<std::string> &type,
const std::vector<int> &filament_list, const std::vector<int> &filament_list,
const std::vector<int> &filament_map) 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_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); SetBackgroundColour(BgNormalColor);
auto top_sizer = new wxBoxSizer(wxVERTICAL); auto top_sizer = new wxBoxSizer(wxVERTICAL);
m_description = new Label(this, _L("We will slice according to this grouping method:")); 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)); top_sizer->Add(m_description, 0, wxALIGN_LEFT | wxLEFT, FromDIP(15));
m_description->Wrap(FromDIP(520));
top_sizer->AddSpacer(FromDIP(8)); top_sizer->AddSpacer(FromDIP(8));
auto drag_sizer = new wxBoxSizer(wxHORIZONTAL); auto drag_sizer = new wxBoxSizer(wxHORIZONTAL);
m_left_panel = new DragDropPanel(this, _L("Left Nozzle"), false); 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"); m_switch_btn = new ScalableButton(this, wxID_ANY, "switch_filament_maps");
UpdateNozzleVolumeType();
for (size_t idx = 0; idx < m_filament_map.size(); ++idx) { 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); auto iter = std::find(m_filament_list.begin(), m_filament_list.end(), idx + 1);
if (iter == m_filament_list.end()) continue; if (iter == m_filament_list.end()) continue;
@@ -50,29 +301,55 @@ FilamentMapManualPanel::FilamentMapManualPanel(wxWindow *p
m_left_panel->AddColorBlock(color, type, idx + 1); m_left_panel->AddColorBlock(color, type, idx + 1);
} else { } else {
assert(m_filament_map[idx] == 2); 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_left_panel->SetMinSize({FromDIP(260), FromDIP(110)});
m_right_panel->SetMinSize({ FromDIP(260),-1 }); m_right_panel->SetMinSize({FromDIP(260), FromDIP(110)});
drag_sizer->AddStretchSpacer();
drag_sizer->Add(m_left_panel, 1, wxEXPAND); 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(8));
drag_sizer->Add(m_switch_btn, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(1));
drag_sizer->AddSpacer(FromDIP(7));
drag_sizer->Add(m_right_panel, 1, wxEXPAND); drag_sizer->Add(m_right_panel, 1, wxEXPAND);
drag_sizer->AddStretchSpacer();
top_sizer->Add(drag_sizer, 0, wxEXPAND); 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 = new Label(this, _L("Tip: You can drag the filaments to reassign them to different nozzles."));
m_tips->SetFont(Label::Body_14); m_tips->SetFont(Label::Body_14);
m_tips->SetForegroundColour(TextNormalGreyColor); m_tips->SetForegroundColour(TextNormalGreyColor);
m_tips->Wrap(FromDIP(520));
top_sizer->AddSpacer(FromDIP(20)); top_sizer->AddSpacer(FromDIP(20));
top_sizer->Add(m_tips, 0, wxALIGN_LEFT | wxLEFT, FromDIP(15)); top_sizer->Add(m_tips, 0, wxALIGN_LEFT | wxLEFT, FromDIP(15));
m_switch_btn->Bind(wxEVT_BUTTON, &FilamentMapManualPanel::OnSwitchFilament, this); 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 // 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 // physical nozzles of each volume type a multi-nozzle extruder carries. This is the
@@ -80,11 +357,8 @@ FilamentMapManualPanel::FilamentMapManualPanel(wxWindow *p
// auto-sync is deferred). Gated on the edited printer preset having an extruder with // 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 // 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. // dual-extruder ({1,1}, H2D) printer - zero UI change for every existing profile.
auto *max_nozzle_counts_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionIntsNullable>("extruder_max_nozzle_count"); if (printer_has_multi_nozzle_extruder()) {
// Skip nil entries: a nullable-int nil is INT_MAX (> 1) and would otherwise falsely pass the gate. auto *max_nozzle_counts_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionIntsNullable>("extruder_max_nozzle_count");
if (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(); })) {
auto *set_count_link = new Label(this, _L("Set the physical 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->SetFont(Label::Body_14);
set_count_link->SetForegroundColour(BorderSelectedColor); set_count_link->SetForegroundColour(BorderSelectedColor);
@@ -94,26 +368,88 @@ FilamentMapManualPanel::FilamentMapManualPanel(wxWindow *p
const std::vector<int> max_counts = max_nozzle_counts_opt->values; const std::vector<int> max_counts = max_nozzle_counts_opt->values;
set_count_link->Bind(wxEVT_LEFT_DOWN, [max_counts](wxMouseEvent &evt) { set_count_link->Bind(wxEVT_LEFT_DOWN, [max_counts](wxMouseEvent &evt) {
for (int extruder_id = 0; extruder_id < (int) max_counts.size(); ++extruder_id) { for (int extruder_id = 0; extruder_id < (int) max_counts.size(); ++extruder_id) {
if (max_counts[extruder_id] > 1) if (max_counts[extruder_id] > 1 && max_counts[extruder_id] != ConfigOptionIntsNullable::nil_value())
GUI::manuallySetNozzleCount(extruder_id); GUI::manuallySetNozzleCount(extruder_id);
} }
evt.Skip(); 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); SetSizer(top_sizer);
SetMinSize(wxSize(FromDIP(580), -1));
Layout(); Layout();
Fit(); Fit();
GUI::wxGetApp().UpdateDarkUIWin(this); 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 &) void FilamentMapManualPanel::OnSwitchFilament(wxCommandEvent &)
{ {
auto left_blocks = m_left_panel->get_filament_blocks(); auto left_blocks = m_left_panel->get_filament_blocks();
auto right_blocks = m_right_panel->get_filament_blocks(); auto right_blocks = m_right_panel->get_filament_blocks();
for (auto &block : left_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); m_left_panel->RemoveColorBlock(block, false);
} }
@@ -123,22 +459,27 @@ void FilamentMapManualPanel::OnSwitchFilament(wxCommandEvent &)
} }
this->GetParent()->Layout(); this->GetParent()->Layout();
this->GetParent()->Fit(); 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_force_validation = show;
m_right_panel->Hide(); if (show) {
m_switch_btn->Hide(); m_timer->Start(500);
wxPanel::Hide(); SyncPanelHeights();
} } else {
m_timer->Stop();
}
void FilamentMapManualPanel::Show() return wxPanel::Show(show);
{
m_left_panel->Show();
m_right_panel->Show();
m_switch_btn->Show();
wxPanel::Show();
} }
GUI::FilamentMapBtnPanel::FilamentMapBtnPanel(wxWindow *parent, const wxString &label, const wxString &detail, const std::string &icon) : wxPanel(parent) GUI::FilamentMapBtnPanel::FilamentMapBtnPanel(wxWindow *parent, const wxString &label, const wxString &detail, const std::string &icon) : wxPanel(parent)
+37 -9
View File
@@ -9,32 +9,60 @@
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
// Fired by the manual panel's validation timer: the event int is 1 when the current manual
// grouping is printable with the installed nozzles, 0 when some zone has no matching nozzle.
wxDECLARE_EVENT(wxEVT_INVALID_MANUAL_MAP, wxCommandEvent);
class FilamentMapManualPanel : public wxPanel class FilamentMapManualPanel : public wxPanel
{ {
public: public:
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); 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,
const std::vector<int> &filament_volume_map);
~FilamentMapManualPanel();
std::vector<int> GetFilamentMaps() const { return m_filament_map; } std::vector<int> GetFilamentMaps() const;
std::vector<int> GetFilamentVolumeMaps() const;
std::vector<int> GetLeftFilaments() const { return m_left_panel->GetAllFilaments(); } std::vector<int> GetLeftFilaments() const { return m_left_panel->GetAllFilaments(); }
std::vector<int> GetRightFilaments() const { return m_right_panel->GetAllFilaments(); } std::vector<int> GetRightFilaments() const { return m_right_panel->GetAllFilaments(); }
void Hide(); std::vector<int> GetRightHighFlowFilaments() const { return m_right_panel->GetHighFlowFilaments(); }
void Show(); std::vector<int> GetRightStandardFilaments() const { return m_right_panel->GetStandardFilaments(); }
std::vector<int> GetRightTPUHighFlowFilaments() const { return m_right_panel->GetTPUHighFlowFilaments(); }
void UpdateNozzleVolumeType();
void UpdateNozzleCountDisplay();
bool Show(bool show = true) override;
private: private:
void OnSwitchFilament(wxCommandEvent &); void OnTimer(wxTimerEvent &evt);
DragDropPanel *m_left_panel; void OnSwitchFilament(wxCommandEvent &);
DragDropPanel *m_right_panel; void SyncPanelHeights();
void OnDragDropCompleted(wxCommandEvent &evt);
void OnSuggestionClicked(wxCommandEvent &event);
Label *m_description; DragDropPanel *m_left_panel;
Label *m_tips; SeparatedDragDropPanel *m_right_panel;
Label *m_description;
Label *m_tips;
Label *m_errors;
wxPanel *m_suggestion_panel;
ScalableButton *m_switch_btn; ScalableButton *m_switch_btn;
std::vector<int> m_filament_map; std::vector<int> m_filament_map;
std::vector<int> m_filament_volume_map;
std::vector<int> m_filament_list; std::vector<int> m_filament_list;
std::vector<std::string> m_filament_color; std::vector<std::string> m_filament_color;
std::vector<std::string> m_filament_type; std::vector<std::string> m_filament_type;
wxTimer *m_timer;
int m_invalid_id{-1};
bool m_force_validation{false};
}; };
class FilamentMapBtnPanel : public wxPanel class FilamentMapBtnPanel : public wxPanel
+44
View File
@@ -3894,6 +3894,16 @@ void PartPlate::set_filament_count(int filament_count)
std::vector<int>& filament_maps = m_config.option<ConfigOptionInts>("filament_map")->values; std::vector<int>& filament_maps = m_config.option<ConfigOptionInts>("filament_map")->values;
filament_maps.resize(filament_count, 1); filament_maps.resize(filament_count, 1);
} }
if (m_config.has("filament_nozzle_map")) {
std::vector<int>& filament_nozzle_map = m_config.option<ConfigOptionInts>("filament_nozzle_map")->values;
filament_nozzle_map.resize(filament_count, 0);
}
if (m_config.has("filament_volume_map")) {
std::vector<int>& filament_volume_map = m_config.option<ConfigOptionInts>("filament_volume_map")->values;
filament_volume_map.resize(filament_count, static_cast<int>(NozzleVolumeType::nvtStandard));
}
} }
void PartPlate::on_filament_added() void PartPlate::on_filament_added()
@@ -3902,6 +3912,27 @@ void PartPlate::on_filament_added()
std::vector<int>& filament_maps = m_config.option<ConfigOptionInts>("filament_map")->values; std::vector<int>& filament_maps = m_config.option<ConfigOptionInts>("filament_map")->values;
filament_maps.push_back(1); filament_maps.push_back(1);
} }
if (m_config.has("filament_nozzle_map")) {
std::vector<int>& filament_nozzle_map = m_config.option<ConfigOptionInts>("filament_nozzle_map")->values;
filament_nozzle_map.push_back(0);
}
if (m_config.has("filament_volume_map")) {
std::vector<int>& filament_volume_map = m_config.option<ConfigOptionInts>("filament_volume_map")->values;
// A new filament defaults onto the first extruder, so seed its volume value from
// that extruder's flow type.
int volume_type = static_cast<int>(NozzleVolumeType::nvtStandard);
auto nozzle_volumes = wxGetApp().preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
if (nozzle_volumes && !nozzle_volumes->values.empty())
volume_type = nozzle_volumes->values[0];
// Orca: never store the Hybrid marker as a per-filament value; on a Hybrid extruder
// each filament still prints with a concrete flow, defaulting to Standard.
if (volume_type == static_cast<int>(NozzleVolumeType::nvtHybrid))
volume_type = static_cast<int>(NozzleVolumeType::nvtStandard);
filament_volume_map.push_back(volume_type);
}
} }
void PartPlate::on_filament_deleted(int filament_count, int filament_id) void PartPlate::on_filament_deleted(int filament_count, int filament_id)
@@ -3914,6 +3945,19 @@ void PartPlate::on_filament_deleted(int filament_count, int filament_id)
if (filament_id >= 0 && filament_id < (int) filament_maps.size()) if (filament_id >= 0 && filament_id < (int) filament_maps.size())
filament_maps.erase(filament_maps.begin() + filament_id); filament_maps.erase(filament_maps.begin() + filament_id);
} }
if (m_config.has("filament_nozzle_map")) {
std::vector<int>& filament_nozzle_map = m_config.option<ConfigOptionInts>("filament_nozzle_map")->values;
if (filament_id >= 0 && filament_id < (int) filament_nozzle_map.size())
filament_nozzle_map.erase(filament_nozzle_map.begin() + filament_id);
}
if (m_config.has("filament_volume_map")) {
std::vector<int>& filament_volume_map = m_config.option<ConfigOptionInts>("filament_volume_map")->values;
if (filament_id >= 0 && filament_id < (int) filament_volume_map.size())
filament_volume_map.erase(filament_volume_map.begin() + filament_id);
}
update_first_layer_print_sequence_when_delete_filament(filament_id); update_first_layer_print_sequence_when_delete_filament(filament_id);
} }
+66 -4
View File
@@ -1213,9 +1213,16 @@ ExtruderGroup::ExtruderGroup(wxWindow * parent, int index, wxString const &title
combo_flow->GetDropDown().SetUseContentWidth(true); combo_flow->GetDropDown().SetUseContentWidth(true);
combo_flow->Bind(wxEVT_COMBOBOX, [this, index, combo_flow](wxCommandEvent &evt) { combo_flow->Bind(wxEVT_COMBOBOX, [this, index, combo_flow](wxCommandEvent &evt) {
auto printer_tab = dynamic_cast<TabPrinter *>(wxGetApp().get_tab(Preset::TYPE_PRINTER)); auto printer_tab = dynamic_cast<TabPrinter *>(wxGetApp().get_tab(Preset::TYPE_PRINTER));
printer_tab->set_extruder_volume_type(index, NozzleVolumeType(intptr_t(combo_flow->GetClientData(evt.GetInt())))); NozzleVolumeType volume_type = NozzleVolumeType(intptr_t(combo_flow->GetClientData(evt.GetInt())));
if (GUI::wxGetApp().plater()) printer_tab->set_extruder_volume_type(index, volume_type);
GUI::wxGetApp().plater()->update_machine_sync_status(); auto plater = GUI::wxGetApp().plater();
if (plater) {
// A new Flow type invalidates the per-filament volume choices stored on the
// plates for this extruder; rewrite them so the next apply/grouping sees the
// selected volume instead of a stale one.
plater->update_filament_volume_map(index, static_cast<int>(volume_type));
plater->update_machine_sync_status();
}
}); });
this->combo_flow = combo_flow; this->combo_flow = combo_flow;
@@ -7328,6 +7335,16 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
filament_map->values.resize(filament_count, 1); filament_map->values.resize(filament_count, 1);
} }
ConfigOptionInts* filament_nozzle_map = proj_cfg.opt<ConfigOptionInts>("filament_nozzle_map", true);
if (filament_nozzle_map->size() != filament_count) {
filament_nozzle_map->values.resize(filament_count, 0);
}
ConfigOptionInts* filament_volume_map = proj_cfg.opt<ConfigOptionInts>("filament_volume_map", true);
if (filament_volume_map->size() != filament_count) {
filament_volume_map->values.resize(filament_count, static_cast<int>(NozzleVolumeType::nvtStandard));
}
// Sync filament multi colour // Sync filament multi colour
ConfigOptionStrings* filament_multi_color = proj_cfg.opt<ConfigOptionStrings>("filament_multi_colour", true); ConfigOptionStrings* filament_multi_color = proj_cfg.opt<ConfigOptionStrings>("filament_multi_colour", true);
if (filament_multi_color->size() != filament_count) { if (filament_multi_color->size() != filament_count) {
@@ -17799,12 +17816,24 @@ void Plater::set_global_filament_map(const std::vector<int>& filament_map)
project_config.option<ConfigOptionInts>("filament_map")->values = filament_map; project_config.option<ConfigOptionInts>("filament_map")->values = filament_map;
} }
void Plater::set_global_filament_volume_map(const std::vector<int>& filament_volume_map)
{
auto& project_config = wxGetApp().preset_bundle->project_config;
project_config.option<ConfigOptionInts>("filament_volume_map")->values = filament_volume_map;
}
std::vector<int> Plater::get_global_filament_map() const std::vector<int> Plater::get_global_filament_map() const
{ {
auto& project_config = wxGetApp().preset_bundle->project_config; auto& project_config = wxGetApp().preset_bundle->project_config;
return project_config.option<ConfigOptionInts>("filament_map")->values; return project_config.option<ConfigOptionInts>("filament_map")->values;
} }
std::vector<int> Plater::get_global_filament_volume_map() const
{
auto& project_config = wxGetApp().preset_bundle->project_config;
return project_config.option<ConfigOptionInts>("filament_volume_map")->values;
}
FilamentMapMode Plater::get_global_filament_map_mode() const FilamentMapMode Plater::get_global_filament_map_mode() const
{ {
@@ -18721,13 +18750,17 @@ void Plater::open_filament_map_setting_dialog(wxCommandEvent &evt)
auto plate_filament_maps = curr_plate->get_real_filament_maps(project_config); auto plate_filament_maps = curr_plate->get_real_filament_maps(project_config);
auto plate_filament_map_mode = curr_plate->get_filament_map_mode(); auto plate_filament_map_mode = curr_plate->get_filament_map_mode();
auto plate_filament_volume_maps = curr_plate->get_real_filament_volume_maps(project_config);
if (plate_filament_maps.size() != filament_colors.size()) // refine it later, save filament map to app config if (plate_filament_maps.size() != filament_colors.size()) // refine it later, save filament map to app config
plate_filament_maps.resize(filament_colors.size(), 1); plate_filament_maps.resize(filament_colors.size(), 1);
if (plate_filament_volume_maps.size() != filament_colors.size())
plate_filament_volume_maps.resize(filament_colors.size(), 0);
FilamentMapDialog filament_dlg(this, FilamentMapDialog filament_dlg(this,
filament_colors, filament_colors,
filament_types, filament_types,
plate_filament_maps, plate_filament_maps,
plate_filament_volume_maps,
curr_plate->get_extruders(true), curr_plate->get_extruders(true),
plate_filament_map_mode, plate_filament_map_mode,
this->get_machine_sync_status(), this->get_machine_sync_status(),
@@ -18741,16 +18774,21 @@ void Plater::open_filament_map_setting_dialog(wxCommandEvent &evt)
FilamentMapMode old_map_mode = curr_plate->get_filament_map_mode(); FilamentMapMode old_map_mode = curr_plate->get_filament_map_mode();
FilamentMapMode new_map_mode = filament_dlg.get_mode(); FilamentMapMode new_map_mode = filament_dlg.get_mode();
std::vector<int> new_filament_volume_maps = filament_dlg.get_filament_volume_maps();
std::vector<int> old_filament_volume_maps = curr_plate->get_real_filament_volume_maps(project_config);
if (new_map_mode != old_map_mode) { if (new_map_mode != old_map_mode) {
curr_plate->set_filament_map_mode(new_map_mode); curr_plate->set_filament_map_mode(new_map_mode);
} }
if (new_map_mode == fmmManual){ if (new_map_mode == fmmManual){
curr_plate->set_filament_maps(new_filament_maps); curr_plate->set_filament_maps(new_filament_maps);
curr_plate->set_filament_volume_maps(new_filament_volume_maps);
} }
bool need_invalidate = (old_map_mode != new_map_mode || bool need_invalidate = (old_map_mode != new_map_mode ||
old_filament_maps != new_filament_maps); old_filament_maps != new_filament_maps ||
old_filament_volume_maps != new_filament_volume_maps);
if (need_invalidate) { if (need_invalidate) {
if (need_slice) { if (need_slice) {
@@ -19216,6 +19254,30 @@ bool Plater::get_machine_sync_status()
return p->get_machine_sync_status(); return p->get_machine_sync_status();
} }
void Plater::update_filament_volume_map(int extruder_id, int volume_type)
{
// Hybrid is a per-extruder mix, not a per-filament volume; reset the affected filaments
// to Standard so the manual grouping dialog starts from a concrete assignment.
int selected_volume_type = volume_type == static_cast<int>(NozzleVolumeType::nvtHybrid) ? static_cast<int>(NozzleVolumeType::nvtStandard) : volume_type;
auto& partplate_list = get_partplate_list();
for (int idx = 0; idx < partplate_list.get_plate_count(); ++idx) {
auto plate = partplate_list.get_plate(idx);
if (!plate) continue;
auto filament_map = plate->get_filament_maps();
auto filament_volume_map = plate->get_filament_volume_maps();
if (filament_map.empty() || filament_volume_map.empty()) continue;
if (filament_volume_map.size() < filament_map.size()) {
filament_volume_map.resize(filament_map.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
}
for (size_t i = 0; i < filament_map.size(); ++i) {
if (filament_map[i] == extruder_id + 1) {
filament_volume_map[i] = selected_volume_type;
}
}
plate->set_filament_volume_maps(filament_volume_map);
}
}
#if ENABLE_ENVIRONMENT_MAP #if ENABLE_ENVIRONMENT_MAP
void Plater::init_environment_texture() void Plater::init_environment_texture()
{ {
+7
View File
@@ -586,7 +586,9 @@ public:
void set_global_filament_map_mode(FilamentMapMode mode); void set_global_filament_map_mode(FilamentMapMode mode);
void set_global_filament_map(const std::vector<int>& filament_map); void set_global_filament_map(const std::vector<int>& filament_map);
void set_global_filament_volume_map(const std::vector<int>& filament_volume_map);
std::vector<int> get_global_filament_map() const; std::vector<int> get_global_filament_map() const;
std::vector<int> get_global_filament_volume_map() const;
FilamentMapMode get_global_filament_map_mode() const; FilamentMapMode get_global_filament_map_mode() const;
void update_menus(); void update_menus();
@@ -748,6 +750,11 @@ public:
void update_machine_sync_status(); void update_machine_sync_status();
// Rewrite every plate's per-filament volume choice for the filaments grouped onto this
// extruder after its Flow type changed (Hybrid resets them to Standard so the user
// re-assigns concrete volumes in the grouping dialog).
void update_filament_volume_map(int extruder_id, int volume_type);
#if ENABLE_ENVIRONMENT_MAP #if ENABLE_ENVIRONMENT_MAP
void init_environment_texture(); void init_environment_texture();
unsigned int get_environment_texture_id() const; unsigned int get_environment_texture_id() const;
+8 -8
View File
@@ -35,7 +35,7 @@ static const int RightExtruderIdx = 1;
// config key (ConfigOptionStrings, one encoded map per extruder) via get_extruder_nozzle_stats / // config key (ConfigOptionStrings, one encoded map per extruder) via get_extruder_nozzle_stats /
// save_extruder_nozzle_stats_to_string. These helpers read and write that config. // save_extruder_nozzle_stats_to_string. These helpers read and write that config.
static int extruder_nozzle_count(PresetBundle *preset_bundle, int extruder_id, NozzleVolumeType volume_type) int getExtruderNozzleCount(PresetBundle *preset_bundle, int extruder_id, NozzleVolumeType volume_type)
{ {
if (!preset_bundle) if (!preset_bundle)
return 0; return 0;
@@ -49,7 +49,7 @@ static int extruder_nozzle_count(PresetBundle *preset_bundle, int extruder_id, N
return it == stats[extruder_id].end() ? 0 : it->second; return it == stats[extruder_id].end() ? 0 : it->second;
} }
static int extruder_nozzle_count_total(PresetBundle *preset_bundle, int extruder_id) int getExtruderNozzleCountTotal(PresetBundle *preset_bundle, int extruder_id)
{ {
if (!preset_bundle) if (!preset_bundle)
return 0; return 0;
@@ -230,8 +230,8 @@ void updateNozzleCountDisplay(PresetBundle *preset_bundle, int extruder_id, Nozz
int display_count = -1; // hides the badge int display_count = -1; // hides the badge
if (support_multi_nozzle) if (support_multi_nozzle)
display_count = volume_type == nvtHybrid ? extruder_nozzle_count_total(preset_bundle, extruder_id) display_count = volume_type == nvtHybrid ? getExtruderNozzleCountTotal(preset_bundle, extruder_id)
: extruder_nozzle_count(preset_bundle, extruder_id, volume_type); : getExtruderNozzleCount(preset_bundle, extruder_id, volume_type);
plater->sidebar().set_extruder_nozzle_count(extruder_id, display_count); plater->sidebar().set_extruder_nozzle_count(extruder_id, display_count);
} }
@@ -269,7 +269,7 @@ void onNozzleVolumeTypeSwitch(PresetBundle *preset_bundle, int extruder_id, Nozz
// Hybrid is a mix, not a type every nozzle shares, so there is nothing to carry over. // Hybrid is a mix, not a type every nozzle shares, so there is nothing to carry over.
if (type == nvtHybrid) if (type == nvtHybrid)
return; return;
const int total = extruder_nozzle_count_total(preset_bundle, extruder_id); const int total = getExtruderNozzleCountTotal(preset_bundle, extruder_id);
setExtruderNozzleCount(preset_bundle, extruder_id, type, total, true); setExtruderNozzleCount(preset_bundle, extruder_id, type, total, true);
} }
@@ -294,14 +294,14 @@ void manuallySetNozzleCount(int extruder_id)
return; return;
const NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volume_type_opt->values[extruder_id]); const NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volume_type_opt->values[extruder_id]);
const int standard_count = extruder_nozzle_count(preset_bundle, extruder_id, nvtStandard); const int standard_count = getExtruderNozzleCount(preset_bundle, extruder_id, nvtStandard);
const int highflow_count = extruder_nozzle_count(preset_bundle, extruder_id, nvtHighFlow); const int highflow_count = getExtruderNozzleCount(preset_bundle, extruder_id, nvtHighFlow);
// Require at least one nozzle for a Hybrid extruder (an empty mix is meaningless) and when the other // Require at least one nozzle for a Hybrid extruder (an empty mix is meaningless) and when the other
// extruder currently has none. // extruder currently has none.
bool force_no_zero = volume_type == nvtHybrid; bool force_no_zero = volume_type == nvtHybrid;
if (nozzle_volume_type_opt->values.size() > 1) if (nozzle_volume_type_opt->values.size() > 1)
force_no_zero |= extruder_nozzle_count_total(preset_bundle, 1 - extruder_id) == 0; force_no_zero |= getExtruderNozzleCountTotal(preset_bundle, 1 - extruder_id) == 0;
ManualNozzleCountDialog dialog(wxGetApp().plater(), volume_type, standard_count, highflow_count, max_nozzle_count->values[extruder_id], force_no_zero); ManualNozzleCountDialog dialog(wxGetApp().plater(), volume_type, standard_count, highflow_count, max_nozzle_count->values[extruder_id], force_no_zero);
if (dialog.ShowModal() == wxID_OK) { if (dialog.ShowModal() == wxID_OK) {
@@ -219,6 +219,11 @@ std::optional<NozzleOption> tryPopUpMultiNozzleDialog(MachineObject* obj);
// counts are reset first. // counts are reset first.
void setExtruderNozzleCount(PresetBundle *preset_bundle, int extruder_id, NozzleVolumeType type, int nozzle_count, bool clear_all); void setExtruderNozzleCount(PresetBundle *preset_bundle, int extruder_id, NozzleVolumeType type, int nozzle_count, bool clear_all);
// Read one extruder's nozzle count for a volume type (or its total) from the edited printer preset's
// `extruder_nozzle_stats` config key. Returns 0 when the stats are absent or the extruder is unknown.
int getExtruderNozzleCount(PresetBundle *preset_bundle, int extruder_id, NozzleVolumeType volume_type);
int getExtruderNozzleCountTotal(PresetBundle *preset_bundle, int extruder_id);
// Refresh the sidebar nozzle-count badge for one extruder: shows the extruder's physical nozzle count on // Refresh the sidebar nozzle-count badge for one extruder: shows the extruder's physical nozzle count on
// multi-nozzle printers, hides it (count -1) everywhere else. // multi-nozzle printers, hides it (count -1) everywhere else.
void updateNozzleCountDisplay(PresetBundle *preset_bundle, int extruder_id, NozzleVolumeType volume_type); void updateNozzleCountDisplay(PresetBundle *preset_bundle, int extruder_id, NozzleVolumeType volume_type);