Replace fake-enum printer agent dropdown (#121)

A dedicated PrinterAgentChoice field
reads rows straight from the live
agent registry and stores the agent
id string, replacing the fake-coEnum
index mapping. The field moves to
TabPrinter and registers with the
searcher so UnsavedChanges renders
it; the PhysicalPrinterDialog copy
and its update hook are removed
(#125). switch_printer_agent now
resolves ids via
resolve_printer_agent_id.
This commit is contained in:
Andrew
2026-07-16 16:21:05 +08:00
committed by Ian Chua
parent f3770f3106
commit 12075459d2
9 changed files with 312 additions and 196 deletions

View File

@@ -2273,6 +2273,8 @@ public:
plugin_picker,
// Raw JSON string value, edited through a dialog behind a button rather than in the row.
plugin_config,
// PrinterAgentChoice
printer_agent_select,
};
// Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map.

View File

@@ -35,6 +35,7 @@
#include "Widgets/TextCtrl.h"
#include "../Utils/ColorSpaceConvert.hpp"
#include "../Utils/NetworkAgentFactory.hpp"
#ifdef __WXOSX__
#define wxOSX true
#else
@@ -1403,39 +1404,6 @@ using choice_ctrl = ::ComboBox; // BBS
static std::map<std::string, DynamicList*> dynamic_lists;
static bool is_plugin_printer_agent_key(const std::string& value)
{
return value.rfind("plugin:", 0) == 0;
}
static int printer_agent_item_for_enum_index(const choice_ctrl* field, int enum_index)
{
if (!field)
return -1;
const unsigned int count = field->GetCount();
for (unsigned int idx = 0; idx < count; ++idx) {
if (void* data = field->GetClientData(idx)) {
const int stored = static_cast<int>(reinterpret_cast<uintptr_t>(data)) - 1;
if (stored == enum_index)
return static_cast<int>(idx);
}
}
return -1;
}
static int printer_agent_enum_index_for_item(const choice_ctrl* field, int item_index, int fallback)
{
if (!field || item_index < 0)
return fallback;
if (void* data = field->GetClientData(item_index))
return static_cast<int>(reinterpret_cast<uintptr_t>(data)) - 1;
return fallback;
}
void Choice::register_dynamic_list(std::string const &optname, DynamicList *list) { dynamic_lists.emplace(optname, list); }
void DynamicList::update()
@@ -1518,33 +1486,7 @@ void Choice::BUILD()
window = dynamic_cast<wxWindow*>(temp);
if (! m_opt.enum_labels.empty() || ! m_opt.enum_values.empty()) {
if (m_opt_id == "printer_agent") {
const bool has_builtin_agents = std::any_of(m_opt.enum_values.begin(), m_opt.enum_values.end(),
[](const std::string& value) { return !is_plugin_printer_agent_key(value); });
const bool has_plugin_agents = std::any_of(m_opt.enum_values.begin(), m_opt.enum_values.end(),
[](const std::string& value) { return is_plugin_printer_agent_key(value); });
auto append_agent_rows = [this, temp](bool plugins) {
for (size_t i = 0; i < m_opt.enum_values.size(); ++i) {
const bool is_plugin = is_plugin_printer_agent_key(m_opt.enum_values[i]);
if (is_plugin != plugins)
continue;
const wxString label = i < m_opt.enum_labels.size() ? _(m_opt.enum_labels[i]) : wxString(m_opt.enum_values[i]);
const int item = temp->Append(label);
temp->SetClientData(item, reinterpret_cast<void*>(static_cast<uintptr_t>(i + 1)));
}
};
if (has_builtin_agents) {
temp->Append(_L("System agents"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED);
append_agent_rows(false);
}
if (has_plugin_agents) {
temp->Append(_L("Plugins"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED);
append_agent_rows(true);
}
} else if (m_opt.enum_labels.empty()) {
if (m_opt.enum_labels.empty()) {
// Append non-localized enum_values
for (auto el : m_opt.enum_values)
temp->Append(el);
@@ -1651,7 +1593,7 @@ void Choice::set_selection()
switch (m_opt.type) {
case coEnum:{
const int val = m_opt.default_value->getInt();
field->SetSelection(m_opt_id == "printer_agent" ? printer_agent_item_for_enum_index(field, val) : val);
field->SetSelection(val);
break;
}
case coFloat:
@@ -1701,12 +1643,7 @@ void Choice::set_value(const std::string& value, bool change_event) //! Redunda
}
choice_ctrl* field = dynamic_cast<choice_ctrl*>(window);
if (m_opt_id == "printer_agent") {
const int enum_index = idx == m_opt.enum_values.size() ?
(m_opt.default_value ? m_opt.default_value->getInt() : 0) :
static_cast<int>(idx);
field->SetSelection(printer_agent_item_for_enum_index(field, enum_index));
} else if (idx == m_opt.enum_values.size())
if (idx == m_opt.enum_values.size())
field->SetValue(value);
else
field->SetSelection(idx);
@@ -1772,33 +1709,11 @@ void Choice::set_value(const boost::any& value, bool change_event)
case coEnum:
// BBS
case coEnums: {
auto printer_agent_index_from_key = [this](const std::string& key) {
auto it = std::find(m_opt.enum_values.begin(), m_opt.enum_values.end(), key);
if (it != m_opt.enum_values.end())
return static_cast<int>(it - m_opt.enum_values.begin());
return m_opt.default_value ? m_opt.default_value->getInt() : 0;
};
int val = 0;
if (m_opt_id == "printer_agent") {
if (const int* int_value = boost::any_cast<int>(&value))
val = *int_value;
else if (const wxString* wx_value = boost::any_cast<wxString>(&value))
val = printer_agent_index_from_key(into_u8(*wx_value));
else if (const std::string* string_value = boost::any_cast<std::string>(&value))
val = printer_agent_index_from_key(*string_value);
else {
m_disable_change_event = false;
return;
}
} else
val = boost::any_cast<int>(value);
int val = boost::any_cast<int>(value);
int selection = val;
if (m_opt_id == "printer_agent") {
selection = printer_agent_item_for_enum_index(field, val);
} else if (m_opt_id == "input_shaping_type") {
if (m_opt_id == "input_shaping_type") {
if (field != nullptr) {
const unsigned int count = field->GetCount();
int match_index = -1;
@@ -1920,12 +1835,6 @@ boost::any& Choice::get_value()
{
if (m_opt.nullable && field->GetSelection() == -1)
m_value = ConfigOptionEnumsGenericNullable::nil_value();
else if (m_opt_id == "printer_agent")
{
const int selection = field->GetSelection();
const int fallback = m_opt.default_value ? m_opt.default_value->getInt() : 0;
m_value = printer_agent_enum_index_for_item(field, selection, fallback);
}
else if (m_opt_id == "input_shaping_type")
{
int selection = field->GetSelection();
@@ -2067,6 +1976,171 @@ void Choice::msw_rescale()
}
// PrinterAgentChoice
void PrinterAgentChoice::reload_rows()
{
auto* combo = dynamic_cast<choice_ctrl*>(window); // wxWidgets ComboBox
if (!combo)
return;
// clear ComboBox
combo->Clear();
// helpers
const auto agents = NetworkAgentFactory::get_registered_printer_agents();
const bool has_builtin_agents = std::any_of(agents.begin(), agents.end(),
[](const PrinterAgentInfo& a) { return !a.is_plugin(); });
const bool has_plugin_agents = std::any_of(agents.begin(), agents.end(),
[](const PrinterAgentInfo& a) { return a.is_plugin(); });
auto append_agent_rows = [combo](bool is_plugin)
{
const auto agents = NetworkAgentFactory::get_registered_printer_agents();
for (size_t i = 0; i < agents.size(); ++i)
{
if (agents[i].is_plugin() != is_plugin)
continue;
const int item = combo->Append(_(agents[i].display_name));
// why: carry the agent-id string on the row. alias is an owned wxString (auto-freed, never rendered)
combo->SetItemAlias(item, from_u8(agents[i].id));
}
};
// append rows
if (has_builtin_agents)
{
combo->Append(_L("System agents"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED);
append_agent_rows(false); // append rows for agents that are not plugins
}
if (has_plugin_agents)
{
combo->Append(_L("Plugins"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED);
append_agent_rows(true); // append rows for agents that are plugins
}
}
void PrinterAgentChoice::BUILD()
{
wxSize size(def_width_wider() * m_em_unit, wxDefaultCoord);
if (m_opt.height >= 0) size.SetHeight(m_opt.height * m_em_unit);
if (m_opt.width >= 0) size.SetWidth(m_opt.width * m_em_unit);
static Builder<choice_ctrl> builder;
choice_ctrl* temp = builder.build(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr,
wxCB_READONLY);
temp->Clear();
temp->GetDropDown().SetUseContentWidth(true);
if (parent_is_custom_ctrl && m_opt.height < 0)
opt_height = (double)temp->GetTextCtrl()->GetSize().GetHeight() / m_em_unit;
temp->SetTextLabel(_L(m_opt.sidetext));
m_combine_side_text = true;
#ifdef __WXGTK3__
wxSize best_sz = temp->GetBestSize();
if (best_sz.x > size.x) temp->SetSize(best_sz);
#endif
if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
window = dynamic_cast<wxWindow*>(temp);
reload_rows();
temp->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) { on_change_field(); }, temp->GetId());
temp->SetToolTip(get_tooltip_text(temp->GetValue()));
}
// Resolve CONFIG id string to a matching row in the live REGISTRY. "" uses the vendor default.
// An unregistered id clears selection and shows "<id> (missing)" as free text.
void PrinterAgentChoice::set_value(const std::string& value, bool change_event)
{
m_disable_change_event = !change_event;
auto* field = dynamic_cast<choice_ctrl*>(window);
// check if any row's corresponding id matches the agent id we are attempting to set
const std::string effective_agent_id = wxGetApp().resolve_printer_agent_id(value);
const unsigned int count = field->GetCount();
int match = wxNOT_FOUND;
for (unsigned int i = 0; i < count; ++i)
{
if (into_u8(field->GetItemAlias(i)) == effective_agent_id) // if alias == id
{
match = static_cast<int>(i);
break;
}
}
// based on match or not, set selection and value
// - SetSelection and SetValue are UI to manipulate the display of the ComboBox
// - SetSelection automatically calls SetValue for the same value
// - we can also SetValue separately from SetSelection
if (match == wxNOT_FOUND)
{
field->SetSelection(wxNOT_FOUND); // nothing shows as selected in the dropdown
field->SetValue(from_u8(value + " (missing)")); // set a value not in the selection (upper display field)
}
else
{
// display name of agent shows both in upper display field and appears selected in dropdown
field->SetSelection(match);
}
m_disable_change_event = false;
}
// Accept boost::any values from callers (usually to OptionsGroup/Field parent classes) and normalize them to an agent id.
// Then use PrinterAgentChoice::set_value(std::string& value, ...)
void PrinterAgentChoice::set_value(const boost::any& value, bool change_event)
{
m_disable_change_event = !change_event;
auto* field = dynamic_cast<choice_ctrl*>(window);
if (value.empty())
{
field->SetValue("");
m_value = value;
m_disable_change_event = false;
return;
}
std::string id;
if (const std::string* s = boost::any_cast<std::string>(&value))
id = *s;
else if (const wxString* w = boost::any_cast<wxString>(&value))
id = into_u8(*w);
set_value(id, change_event);
}
// A real row returns its alias, which is the agent id. Header rows, missing rows,
// and no selection return empty boost::any so the custom writer leaves config unchanged.
boost::any& PrinterAgentChoice::get_value()
{
auto* field = dynamic_cast<choice_ctrl*>(window);
const int sel = field->GetSelection();
const std::string id = sel < 0 ? std::string{} : into_u8(field->GetItemAlias(sel));
if (id.empty())
m_value = boost::any{};
else
m_value = id;
return m_value;
}
void PrinterAgentChoice::enable() { dynamic_cast<choice_ctrl*>(window)->Enable(); }
void PrinterAgentChoice::disable() { dynamic_cast<choice_ctrl*>(window)->Disable(); }
void PrinterAgentChoice::msw_rescale()
{
Field::msw_rescale();
auto* field = dynamic_cast<choice_ctrl*>(window)->GetTextCtrl();
wxSize size(wxDefaultSize);
size.SetWidth((m_opt.width > 0 ? m_opt.width : def_width_wider()) * m_em_unit);
field->SetMinSize(wxSize(-1, int(1.5f * field->GetFont().GetPixelSize().y + 0.5f)));
field->SetSize(size);
dynamic_cast<choice_ctrl*>(window)->Rescale();
}
void PluginField::BUILD()
{
auto* panel = new wxPanel(m_parent, wxID_ANY);

View File

@@ -469,6 +469,44 @@ public:
void suppress_scroll();
};
// printer_agent is a coString whose choices come from the live agent registry.
// PrinterAgentChoice uses a ComboBox directly because Choice expects static config enums.
// Real rows carry the stored agent id in the row alias (SetItemAlias/GetItemAlias).
class PrinterAgentChoice : public Field
{
using Field::Field;
public:
PrinterAgentChoice(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id)
{
}
PrinterAgentChoice(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(
parent, opt, id)
{
}
~PrinterAgentChoice()
{
}
wxWindow* window{nullptr};
void BUILD() override;
// Clear and repopulate rows from the live registry (grouped System agents / Plugins).
// Does not change selection; the caller follows with set_value(stored id).
void reload_rows();
void set_value(const std::string& value, bool change_event = false);
void set_value(const boost::any& value, bool change_event = false) override;
boost::any& get_value() override;
void enable() override;
void disable() override;
void msw_rescale() override;
wxWindow* getWindow() override { return window; }
};
class PluginField : public Field {
using Field::Field;
public:

View File

@@ -3878,6 +3878,18 @@ unsigned GUI_App::get_colour_approx_luma(const wxColour &colour)
));
}
std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id)
{
if (!stored_id.empty())
return stored_id;
return (preset_bundle && preset_bundle->is_bbl_vendor()) ? BBL_PRINTER_AGENT_ID : ORCA_PRINTER_AGENT_ID;
}
std::string GUI_App::canonical_printer_agent_id(const std::string& picked_id)
{
return picked_id == resolve_printer_agent_id("") ? std::string() : picked_id;
}
void GUI_App::switch_printer_agent()
{
if (!m_agent) {
@@ -3885,17 +3897,8 @@ void GUI_App::switch_printer_agent()
return;
}
// Read printer_agent from config, falling back to default
std::string effective_agent_id = ORCA_PRINTER_AGENT_ID;
if (preset_bundle->is_bbl_vendor())
effective_agent_id = BBL_PRINTER_AGENT_ID;
const DynamicPrintConfig& config = preset_bundle->printers.get_edited_preset().config;
if (config.has("printer_agent")) {
const std::string& value = config.option<ConfigOptionString>("printer_agent")->value;
if (!value.empty())
effective_agent_id = value;
}
const std::string effective_agent_id = resolve_printer_agent_id(config.opt_string("printer_agent"));
// Check if agent is registered
const PrinterAgentInfo* agent_info_ptr = NetworkAgentFactory::get_printer_agent_info(effective_agent_id);

View File

@@ -365,9 +365,14 @@ public:
HMSQuery* get_hms_query() { return hms_query; }
NetworkAgent* getAgent() { return m_agent; }
// Dynamic printer agent switching
// Reconcile the live printer agent with the stored preset selection.
void switch_printer_agent();
std::string resolve_printer_agent_id(const std::string& stored_id);
// ORCA TODO: in the future, bbl presets should specify "bbl" printer agent id
// then, all resolve and canonical would just be ORCA<->""
std::string canonical_printer_agent_id(const std::string& picked_id);
FilamentColorCodeQuery* get_filament_color_code_query();
bool is_editor() const { return m_app_mode == EAppMode::Editor; }
bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; }

View File

@@ -54,6 +54,9 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
case ConfigOptionDef::GUIType::one_string: m_fields.emplace(id, TextCtrl::Create<TextCtrl>(this->ctrl_parent(), opt, id)); break;
case ConfigOptionDef::GUIType::plugin_picker: m_fields.emplace(id, PluginField::Create<PluginField>(this->ctrl_parent(), opt, id)); break;
case ConfigOptionDef::GUIType::plugin_config: m_fields.emplace(id, PluginConfigField::Create<PluginConfigField>(this->ctrl_parent(), opt, id)); break;
case ConfigOptionDef::GUIType::printer_agent_select: m_fields.emplace(
id, PrinterAgentChoice::Create<PrinterAgentChoice>(this->ctrl_parent(), opt, id));
break;
default:
switch (opt.type) {
case coFloatOrPercent:
@@ -654,6 +657,16 @@ Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index
void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value)
{
if (opt_id == "printer_agent") {
// TODO: Replace this option-specific branch with a generic value adapter if
// more fields need custom field-value to config-value conversion.
if (const std::string* id = boost::any_cast<std::string>(&value))
this->change_opt_value("printer_agent", wxGetApp().canonical_printer_agent_id(*id));
OptionsGroup::on_change_OG(opt_id, value);
return;
}
if (!m_opt_map.empty()) {
auto it = m_opt_map.find(opt_id);
if (it == m_opt_map.end()) {
@@ -772,6 +785,19 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config,
}
}
#endif
else if (opt_key == "printer_agent")
{
// why: printer_agent is a coString kept out of m_opt_map. The generic non-opt_map revert
// below restores the edited config from get_value(), but a deregistered/"(missing)" saved
// id has no selectable row, so the field yields no value and the edited config keeps the
// user's interim pick -> stuck dirty. Restore the SAVED id straight into the edited config
// (displayable or not; config is the saved or system baseline), then repaint and notify.
const std::string saved_id = config.opt_string("printer_agent");
set_value(opt_key, saved_id);
this->change_opt_value(opt_key, saved_id);
OptionsGroup::on_change_OG(opt_key, saved_id);
return;
}
else if (m_opt_map.find(opt_key) == m_opt_map.end() ||
// This option don't have corresponded field
opt_key == "printable_area" || opt_key == "compatible_printers" || opt_key == "compatible_prints" || opt_key == "thumbnails" ||

View File

@@ -25,7 +25,6 @@
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "MainFrame.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "format.hpp"
#include "Tab.hpp"
#include "wxExtensions.hpp"
@@ -128,22 +127,8 @@ PhysicalPrinterDialog::~PhysicalPrinterDialog()
void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup)
{
m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
// Special handling for printer_agent: convert fake enum index to string agent ID
if (opt_key == "printer_agent") {
try {
int selected_idx = boost::any_cast<int>(value);
auto agents = NetworkAgentFactory::get_registered_printer_agents();
if (selected_idx >= 0 && selected_idx < static_cast<int>(agents.size())) {
m_config->set_key_value("printer_agent",
new ConfigOptionString(agents[selected_idx].id));
}
} catch (const boost::bad_any_cast&) {
// If value is not an int, ignore
}
if (opt_key == "host_type" || opt_key == "printhost_authorization_type")
this->update();
} else if (opt_key == "host_type" || opt_key == "printhost_authorization_type") {
this->update();
}
if (opt_key == "print_host")
this->update_printhost_buttons();
if (opt_key == "printhost_port")
@@ -154,47 +139,6 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
m_optgroup->append_single_option_line("host_type");
// Build printer agent dropdown from registry (only if network agent is available)
if (wxGetApp().getAgent() != nullptr) {
auto agents = NetworkAgentFactory::get_registered_printer_agents();
if (!agents.empty()) {
// Create a fake enum option to force a Choice widget instead of TextCtrl
// (printer_agent is coString in config, but we need a dropdown)
ConfigOptionDef def;
def.type = coEnum;
def.width = Field::def_width_wider();
def.label = L("Printer Agent");
def.tooltip = L("Select the network agent implementation for printer communication. "
"Available agents are registered at startup.");
def.mode = comAdvanced;
// Populate enum values and labels from registered agents
for (const auto& agent : agents) {
def.enum_values.push_back(agent.id);
def.enum_labels.push_back(agent.display_name);
}
// Resolve selected agent: use config value if valid, otherwise fall back to default
std::string selected_agent = m_config->opt_string("printer_agent");
auto it = std::find_if(agents.begin(), agents.end(), [&selected_agent](const auto& a) { return a.id == selected_agent; });
if (it == agents.end()) {
selected_agent = ORCA_PRINTER_AGENT_ID;
it = std::find_if(agents.begin(), agents.end(), [&selected_agent](const auto& a) { return a.id == selected_agent; });
}
if (it != agents.end()) {
size_t default_idx = std::distance(agents.begin(), it);
def.set_default_value(new ConfigOptionInt(static_cast<int>(default_idx)));
}
// Create and append the option line
auto agent_option = Option(def, "printer_agent");
Line agent_line = m_optgroup->create_single_option_line(agent_option);
m_optgroup->append_line(agent_line);
}
}
auto create_sizer_with_btn = [](wxWindow* parent, Button** btn, const std::string& icon_name, const wxString& label) {
*btn = new Button(parent, label);
(*btn)->SetStyle(ButtonStyle::Regular, ButtonType::Parameter);
@@ -816,31 +760,6 @@ void PhysicalPrinterDialog::update_host_type(bool printer_change)
}
}
void PhysicalPrinterDialog::update_printer_agent_type()
{
if (m_config == nullptr)
return;
Field* agent_field = m_optgroup->get_field("printer_agent");
if (!agent_field)
return;
Choice* agent_choice = dynamic_cast<Choice*>(agent_field);
if (!agent_choice)
return;
// Sync selection with current config value
const std::string current_agent = m_config->opt_string("printer_agent");
auto agents = NetworkAgentFactory::get_registered_printer_agents();
for (size_t i = 0; i < agents.size(); ++i) {
if (agents[i].id == current_agent) {
agent_choice->set_value(i);
return;
}
}
}
void PhysicalPrinterDialog::update_printers()
{
wxBusyCursor wait;
@@ -894,11 +813,6 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event)
{
wxGetApp().get_tab(Preset::TYPE_PRINTER)->save_preset("", false, false, true, m_preset_name);
event.Skip();
// Defer printer agent switch to ensure preset save completes first
wxGetApp().CallAfter([] {
wxGetApp().switch_printer_agent();
});
}
}} // namespace Slic3r::GUI

View File

@@ -60,7 +60,6 @@ public:
void update(bool printer_change = false);
void update_host_type(bool printer_change);
void update_printer_agent_type();
void update_preset_input();
void update_printhost_buttons();
void update_printers();

View File

@@ -33,6 +33,7 @@
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "slic3r/plugin/PluginConfig.hpp"
#include "Plater.hpp"
@@ -5018,6 +5019,40 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("gcode_flavor", "printer_basic_information_advanced#g-code-flavor");
optgroup->append_single_option_line("pellet_modded_printer", "printer_basic_information_advanced#pellet-modded-printer");
optgroup->append_single_option_line("bbl_use_printhost", "printer_basic_information_advanced#use-3rd-party-print-host");
// "Printer Agent" dropdown - printer_agent is a coString; gui_type routes it to
// PrinterAgentChoice instead of a TextCtrl. Rows and values come from the live agent
// registry, and the value is stored as the agent-id string.
if (wxGetApp().getAgent() != nullptr)
{
auto registered_printer_agents = NetworkAgentFactory::get_registered_printer_agents();
if (!registered_printer_agents.empty())
{
ConfigOptionDef def;
def.type = coString;
def.gui_type = ConfigOptionDef::GUIType::printer_agent_select;
def.width = 3 * Field::def_width_wider() / 2;
def.label = L("Printer Agent");
def.tooltip = L("Select the network agent implementation for printer communication. "
"Available agents are registered at startup.");
def.mode = comAdvanced;
// Create the field without get_option() so it is not registered in m_opt_map.
// ConfigOptionsGroup handles printer_agent before the generic mapped write path.
Line agent_line = optgroup->create_single_option_line(Option(def, "printer_agent"));
optgroup->append_line(agent_line);
if (Field* agent_field = get_field("printer_agent"))
{
if (auto* choice = dynamic_cast<PrinterAgentChoice*>(agent_field); choice && choice->getWindow())
choice->set_value(m_config->opt_string("printer_agent"), false);
}
// Register by hand so the UnsavedChanges dialog can render a row for it.
wxGetApp().sidebar().get_searcher().add_key("printer_agent", m_type, optgroup->title,
optgroup->config_category());
}
}
optgroup->append_single_option_line("use_3mf");
optgroup->append_single_option_line("scan_first_layer" , "printer_basic_information_advanced#scan-first-layer");
optgroup->append_single_option_line("enable_power_loss_recovery", "printer_basic_information_advanced#power-loss-recovery");
@@ -5884,6 +5919,16 @@ void TabPrinter::reload_config()
// so update it implicitly
if (m_active_page && m_active_page->title() == "Multimaterial")
m_active_page->set_value("extruders_count", int(m_extruders_count));
// m_opt_map-driven reload does not cover printer_agent, so sync this custom field explicitly.
if (Field* agent_field = get_field("printer_agent"))
{
if (auto* choice = dynamic_cast<PrinterAgentChoice*>(agent_field); choice && choice->getWindow())
{
const std::string selected_agent = m_config->opt_string("printer_agent");
choice->set_value(selected_agent, false);
}
}
}
void TabPrinter::activate_selected_page(std::function<void()> throw_if_canceled)
@@ -5894,6 +5939,16 @@ void TabPrinter::activate_selected_page(std::function<void()> throw_if_canceled)
// so update it implicitly
if (m_active_page && m_active_page->title() == "Multimaterial")
m_active_page->set_value("extruders_count", int(m_extruders_count));
// m_opt_map-driven reload does not cover printer_agent, so sync this custom field explicitly.
if (Field* agent_field = get_field("printer_agent"))
{
if (auto* choice = dynamic_cast<PrinterAgentChoice*>(agent_field); choice && choice->getWindow())
{
const std::string selected_agent = m_config->opt_string("printer_agent");
choice->set_value(selected_agent, false);
}
}
}
void TabPrinter::clear_pages()