From a2a4ca20e424b3d860a2890f908e93664c8b2929 Mon Sep 17 00:00:00 2001 From: Andrew <159703254+andrewsoonqn@users.noreply.github.com> Date: Thu, 16 Jul 2026 16:27:53 +0800 Subject: [PATCH] Reset device selection on agent swap or unload (#124) set_live_printer_agent centralizes the swap: deselect the machine, clear stale sidebar state and the previous agent's Other Devices, then install the new agent (or null when its provider vanished). Plugin load/unload callbacks refresh the dropdown and re-run agent selection. load_last_machine no longer falls back to the first available machine. --- src/slic3r/GUI/DeviceCore/DevManager.cpp | 66 ++++++++-------- src/slic3r/GUI/DeviceCore/DevManager.h | 8 +- src/slic3r/GUI/GUI_App.cpp | 95 +++++++++++++++++++++--- src/slic3r/GUI/GUI_App.hpp | 5 ++ src/slic3r/GUI/Tab.cpp | 18 +++++ src/slic3r/GUI/Tab.hpp | 1 + 6 files changed, 147 insertions(+), 46 deletions(-) diff --git a/src/slic3r/GUI/DeviceCore/DevManager.cpp b/src/slic3r/GUI/DeviceCore/DevManager.cpp index aa4d360b97..3c664facfd 100644 --- a/src/slic3r/GUI/DeviceCore/DevManager.cpp +++ b/src/slic3r/GUI/DeviceCore/DevManager.cpp @@ -496,6 +496,26 @@ namespace Slic3r OnSelectedMachineChanged(previous_selected_machine, selected_machine); } + void DeviceManager::clear_other_devices() + { + // why: on agent swap, keep "My Devices" but drop the transient "Other Devices" + // Those belong to the previous agent's network scan; the new agent's start_discovery re-populates its own. + const auto my = get_my_machine_list(); + for (auto it = localMachineList.begin(); it != localMachineList.end();) + { + if (my.find(it->first) == my.end()) + { + // not a "My Device" -> an "Other Device" + delete it->second; + it = localMachineList.erase(it); + } + else + { + ++it; + } + } + } + bool DeviceManager::set_selected_machine(std::string dev_id) { BOOST_LOG_TRIVIAL(info) << "set_selected_machine=" << dev_id @@ -558,7 +578,6 @@ namespace Slic3r } else { - Slic3r::GUI::wxGetApp().reset_unsigned_plugin_warning(); if (m_agent) { if (it->second->connection_type() != "lan" || it->second->connection_type().empty()) @@ -592,7 +611,6 @@ namespace Slic3r } selected_machine = dev_id; - record_user_last_machine(selected_machine); return true; } @@ -857,40 +875,20 @@ namespace Slic3r } } - void DeviceManager::record_user_last_machine(const std::string& dev_id) - { - if (Slic3r::GUI::wxGetApp().app_config) { - Slic3r::GUI::wxGetApp().app_config->set("user_last_selected_machine", dev_id); - } - } - - std::string DeviceManager::get_user_last_machine() const - { - if (Slic3r::GUI::wxGetApp().app_config) { - const auto& user_last_machine = Slic3r::GUI::wxGetApp().app_config->get("user_last_selected_machine"); - if (!user_last_machine.empty()) { - return user_last_machine; - } else if (m_agent) { - return m_agent->get_user_selected_machine(); - } - } - - return ""; - } - void DeviceManager::load_last_machine() { - if (userMachineList.empty()) return; - else if (userMachineList.size() == 1) { - this->set_selected_machine(userMachineList.begin()->second->get_dev_id()); - } else { - const auto& last_monitor_machine = get_user_last_machine(); - if (userMachineList.find(last_monitor_machine) != userMachineList.end()) { - set_selected_machine(last_monitor_machine); - } else { - this->set_selected_machine(userMachineList.begin()->second->get_dev_id()); - } - } + // Get all available machines, include cloud machines and lan machines that have access right + auto all_machines = get_my_machine_list(); + if (all_machines.empty()) + return; + + // Reconnect the machine the user last selected, if it's still available. + // why: no first-available fallback - auto-connecting an arbitrary machine + // fights the agent-swap reset, which intentionally leaves nothing selected. + const std::string last_monitor_machine = m_agent ? m_agent->get_user_selected_machine() : ""; + const auto last_machine = all_machines.find(last_monitor_machine); + if (last_machine != all_machines.end()) + this->set_selected_machine(last_machine->second->get_dev_id()); } void DeviceManager::OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state) diff --git a/src/slic3r/GUI/DeviceCore/DevManager.h b/src/slic3r/GUI/DeviceCore/DevManager.h index 1f7f87b7fb..70bee613a8 100644 --- a/src/slic3r/GUI/DeviceCore/DevManager.h +++ b/src/slic3r/GUI/DeviceCore/DevManager.h @@ -48,8 +48,9 @@ public: MachineObject* get_selected_machine(); bool set_selected_machine(std::string dev_id); - void record_user_last_machine(const std::string& dev_id); - std::string get_user_last_machine() const; + // why: clears stale sidebar sync-status / AMS visuals. Public so the printer-agent + // swap path can reuse it instead of duplicating the two sidebar calls. + void OnSelectedMachineLost(); // local machine void set_local_selected_machine(std::string dev_id) { local_selected_machine = dev_id; }; @@ -70,6 +71,8 @@ public: void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); } void clean_user_info(bool keep_local_selection = false); + void clear_other_devices(); + void load_last_machine(); void update_user_machine_list_info(const std::string& provider); void parse_user_print_info(std::string body); @@ -110,7 +113,6 @@ private: void check_pushing(); void OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state); - void OnSelectedMachineLost(); void OnSelectedMachineChanged(const std::string& pre_dev_id, const std::string& new_dev_id); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 747d4df572..2f3b5b8903 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2814,16 +2814,58 @@ void GUI_App::init_plugin_gui_wiring() }); }; + // why: a newly loaded plugin only adds a selectable agent + // refresh the dropdown and leave the live agent alone + auto refresh_printer_agent_dropdown_after_load = [](const std::string&) + { + if (!wxTheApp) + return; + + GUI_App* app = &GUI::wxGetApp(); + if (app->is_closing()) + return; + + app->CallAfter([app] + { + if (!app->is_closing()) + app->refresh_printer_agent_dropdown(); + }); + }; + + // why: the unloaded plugin may have been the provider of the live agent + // re-run selection, where a now-missing agent will be cleared + // refresh dropdown after + auto switch_printer_agent_after_unload = [](const std::string&) + { + if (!wxTheApp) + return; + + GUI_App* app = &GUI::wxGetApp(); + if (app->is_closing()) + return; + + app->CallAfter([app] { + if (app->is_closing()) + return; + + app->switch_printer_agent(); + app->refresh_printer_agent_dropdown(); + }); + }; + plugin_mgr.subscribe_on_unload_callback(PluginHostUi::close_windows_for_plugin); plugin_mgr.subscribe_on_load_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); }); plugin_mgr.subscribe_on_unload_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); }); plugin_mgr.subscribe_on_load_callback(NetworkAgentFactory::register_python_plugin); plugin_mgr.subscribe_on_unload_callback(NetworkAgentFactory::deregister_python_plugin); + plugin_mgr.subscribe_on_load_callback(refresh_printer_agent_dropdown_after_load); + plugin_mgr.subscribe_on_unload_callback(switch_printer_agent_after_unload); plugin_mgr.subscribe_on_capability_load_callback( - [refresh_plugins_dialog](const PluginCapabilityId& capability) { + [refresh_plugins_dialog, refresh_printer_agent_dropdown_after_load](const PluginCapabilityId& capability) { if (capability.type == PluginCapabilityType::PrinterConnection) NetworkAgentFactory::register_python_printer_agent(capability.plugin_key, capability.name); refresh_plugins_dialog(); + refresh_printer_agent_dropdown_after_load(capability.plugin_key); // A newly loaded capability may satisfy a missing-plugin notification; re-validate the // current plate (on the UI thread) so the notification clears once its plugin is available. if (wxTheApp && !wxGetApp().is_closing()) @@ -2833,10 +2875,11 @@ void GUI_App::init_plugin_gui_wiring() }); }); plugin_mgr.subscribe_on_capability_unload_callback( - [refresh_plugins_dialog](const PluginCapabilityId& capability) { + [refresh_plugins_dialog, switch_printer_agent_after_unload](const PluginCapabilityId& capability) { if (capability.type == PluginCapabilityType::PrinterConnection) NetworkAgentFactory::deregister_python_printer_agent(capability.plugin_key, capability.name); refresh_plugins_dialog(); + switch_printer_agent_after_unload(capability.plugin_key); }); } @@ -3878,6 +3921,36 @@ unsigned GUI_App::get_colour_approx_luma(const wxColour &colour) )); } +void GUI_App::refresh_printer_agent_dropdown() +{ + if (Tab* tab = get_tab(Preset::TYPE_PRINTER)) + { + if (auto* printer_tab = dynamic_cast(tab)) + printer_tab->refresh_printer_agent_dropdown(); + } +} + +void GUI_App::set_live_printer_agent(std::shared_ptr agent) +{ + if (!m_agent) + return; + + // why: tearing down the old machine selection is only ever the prefix of setting the live + // agent (to a new one, or to null when the selection is missing) - so it lives here, not as + // a standalone helper. Pass nullptr to clear the selection. + if (DeviceManager* dev = getDeviceManager()) + { + dev->set_selected_machine(""); // why: empty id disconnects and deselects the current machine + m_agent->set_user_selected_machine(""); + // note: belt-and-suspenders (precedent: DeviceManagerRefresher::on_timer) + dev->OnSelectedMachineLost(); // why: clear stale sidebar sync-status / AMS + dev->clear_other_devices(); // why: drop stale LAN discoveries; keep My Devices + } + + m_agent->set_printer_agent(agent); + sidebar().update_all_preset_comboboxes(); +} + std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id) { if (!stored_id.empty()) @@ -3903,9 +3976,11 @@ void GUI_App::switch_printer_agent() // Check if agent is registered const PrinterAgentInfo* agent_info_ptr = NetworkAgentFactory::get_printer_agent_info(effective_agent_id); if (!agent_info_ptr) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": unregistered agent ID '" << effective_agent_id - << "', keeping current agent"; - // Keep current agent, don't switch + // why: the selected agent's provider is gone (e.g. plugin unloaded); leaving the old + // live agent up would keep talking to a machine the user can no longer select. + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": agent ID '" << effective_agent_id + << "' is unregistered; clearing live printer agent"; + set_live_printer_agent(nullptr); return; } const PrinterAgentInfo agent_info = *agent_info_ptr; @@ -3919,7 +3994,9 @@ void GUI_App::switch_printer_agent() NetworkAgentFactory::create_printer_agent_by_id(effective_agent_id, cloud_agent, log_dir); if (!new_printer_agent) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": failed to create agent '" << effective_agent_id << "', keeping current agent"; + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": failed to create agent '" << effective_agent_id + << "'; clearing live printer agent"; + set_live_printer_agent(nullptr); return; } @@ -3942,9 +4019,9 @@ void GUI_App::switch_printer_agent() return; } - // Swap the agent - m_agent->set_printer_agent(new_printer_agent); - sidebar().update_all_preset_comboboxes(); + // Swap the agent; set_live_printer_agent resets the device selection so the new + // agent starts clean (#124). + set_live_printer_agent(new_printer_agent); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": printer agent switched to " << effective_agent_id; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 6a977d37fc..8bf32df64c 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -803,6 +803,11 @@ private: void window_pos_center(wxTopLevelWindow *window); bool select_language(); + // Dynamic printer agent selection - internal helpers for switch_printer_agent + // and the plugin load/unload callbacks (init_plugin_gui_wiring). + void refresh_printer_agent_dropdown(); + void set_live_printer_agent(std::shared_ptr agent); // null clears the selection + bool config_wizard_startup(); void check_updates(const bool verbose); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 857abdce67..bade7fee98 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -7907,6 +7907,24 @@ bool TabPrinter::apply_extruder_cnt_from_cache() return false; } +void TabPrinter::refresh_printer_agent_dropdown() const +{ + auto* choice = dynamic_cast(get_field("printer_agent")); + if (!choice || !choice->getWindow()) + return; + + const auto agents = NetworkAgentFactory::get_registered_printer_agents(); + if (agents.empty()) + return; + + // why: rows live on PrinterAgentChoice now; rebuild them from the live registry and re-select the stored id. + const std::string selected_agent = wxGetApp().preset_bundle->printers.get_edited_preset() + .config.opt_string("printer_agent"); + choice->reload_rows(); + choice->set_value(selected_agent, false); + this->GetParent()->Layout(); +} + bool Tab::validate_custom_gcodes() { if (m_type != Preset::TYPE_FILAMENT && diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 7187aff467..19eb0b849d 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -675,6 +675,7 @@ public: wxSizer* create_bed_shape_widget(wxWindow* parent); void cache_extruder_cnt(const DynamicPrintConfig* config = nullptr); bool apply_extruder_cnt_from_cache(); + void refresh_printer_agent_dropdown() const; }; class TabSLAMaterial : public Tab