mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-09 00:31:19 +00:00
## Summary
Follow-up to the feature/OrcaSonar review. Scopes the new behavior to the OrcaSonar agent, fixes several AMS-sync and lifecycle bugs, and makes the MQTT tests run in CI. Non-OrcaSonar agents keep their existing behavior. ## Changes Discovery - Gate the background SSDP scan to a configured OrcaSonar machine instead of running for every non-BBL profile; drive discovery on demand from the machine-select popup and back off idle rounds from 5s to 60s. - Add NSLocalNetworkUsageDescription and NSBonjourServices to both macOS Info.plist templates. AMS sync - Resolve id-less printer-set trays by material type in get_ams_cobox_infos and the sidebar badge loop, matching sync_ams_list, so combo arrays and badges stay aligned. - Keep a printer-set tray that resolved by type out of the "unknown or incompatible filaments" dialog. AMS device UI - Gate the "Empty" tray classification, and the editable temperature fields, to the OrcaSonar agent; Bambu keeps "?" and read-only presets. - Validate AMS material temps on confirm: reject out-of-range values and min > max before the command is sent. Storage tab - Bind the show/hide handler on the panel's ancestors (StoragePanel and MonitorPanel) and unbind on destruction, so the Bambu file system stops when the tab is left. Transport - Load /etc/ssl/cert.pem on macOS in add_platform_root_certificates, after set_default_verify_paths. - Release lifecycle_mutex before start()'s connect wait so stop() can cancel the attempt and signal completion instead of stalling the UI thread. Localization - Add OrcaFilesPanel.cpp to list.txt and regenerate the catalogs. ## Tests - Un-hide the loopback MockBroker integration tests (ephemeral port, no network) so ctest registers and runs them. - Mock broker tracks SUBSCRIBE/UNSUBSCRIBE and delivers push_report only to a subscribed topic; the reconnect test asserts the re-SUBSCRIBE. - Add bbl empty-tray cases, an AMS combo-alignment case, and unknowns assertions to the AMS sync tests.
This commit is contained in:
@@ -126,6 +126,13 @@
|
||||
</array>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.10</string>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>OrcaSlicer uses your local network to discover and connect to printers.</string>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_octoprint._tcp</string>
|
||||
<string>_http._tcp</string>
|
||||
</array>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
|
||||
@@ -3813,7 +3813,11 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info)
|
||||
auto ams_name = ams.opt_string("tray_name", 0u);
|
||||
auto filament_changed = !ams.has("filament_changed") || ams.opt_bool("filament_changed");
|
||||
auto filament_multi_color = ams.opt<ConfigOptionStrings>("filament_multi_colour")->values;
|
||||
if (filament_id.empty()) {
|
||||
auto filament_type = ams.opt_string("filament_type", 0u);
|
||||
auto is_placeholder = ams.has("filament_slot_placeholder") && ams.opt_bool("filament_slot_placeholder", 0u);
|
||||
// A printer-set tray carries a material type but no preset id; resolve it to the
|
||||
// matching Generic preset so it is not dropped and the combo array keeps aligning.
|
||||
if (filament_id.empty() && (is_placeholder || filament_type.empty())) {
|
||||
continue;
|
||||
}
|
||||
if (!filament_changed && this->filament_presets.size() > combox_info.ams_filament_presets.size()) {
|
||||
@@ -3823,16 +3827,48 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info)
|
||||
combox_info.ams_names.push_back(ams_name);
|
||||
continue;
|
||||
}
|
||||
auto iter = std::find_if(filaments.begin(), filaments.end(),
|
||||
[this, &filament_id](auto &f) { return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
|
||||
warn_ambiguous_filament_id_match(filaments, iter, filament_id);
|
||||
auto iter = filaments.end();
|
||||
if (!filament_id.empty()) {
|
||||
iter = std::find_if(filaments.begin(), filaments.end(),
|
||||
[this, &filament_id](auto &f) { return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
|
||||
warn_ambiguous_filament_id_match(filaments, iter, filament_id);
|
||||
}
|
||||
if (iter == filaments.end()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id;
|
||||
auto filament_type = ams.opt_string("filament_type", 0u);
|
||||
if (!filament_type.empty()) {
|
||||
auto original_type = filament_type;
|
||||
filament_type = "Generic " + filament_type;
|
||||
iter = std::find_if(filaments.begin(), filaments.end(),
|
||||
[&filament_type](auto &f) { return f.is_compatible && f.is_system && boost::algorithm::starts_with(f.name, filament_type); });
|
||||
if (iter == filaments.end()) {
|
||||
// Similarity fallback: find a generic preset whose filament_type
|
||||
// appears as a whole word in the AMS type (e.g. "ASA" in "ASA Sparkle").
|
||||
auto upper_type = boost::to_upper_copy(original_type);
|
||||
auto contains_word = [](const std::string& haystack, const std::string& needle) {
|
||||
auto pos = haystack.find(needle);
|
||||
while (pos != std::string::npos) {
|
||||
bool start_ok = (pos == 0 || !std::isalnum(static_cast<unsigned char>(haystack[pos - 1])));
|
||||
bool end_ok = (pos + needle.size() >= haystack.size() ||
|
||||
!std::isalnum(static_cast<unsigned char>(haystack[pos + needle.size()])));
|
||||
if (start_ok && end_ok)
|
||||
return true;
|
||||
pos = haystack.find(needle, pos + 1);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// Find the longest-matching preset type to prefer e.g. "PA-CF" over "PA".
|
||||
size_t best_len = 0;
|
||||
for (auto it = filaments.begin(); it != filaments.end(); ++it) {
|
||||
if (!it->is_compatible || !it->is_system || !boost::algorithm::starts_with(it->name, "Generic "))
|
||||
continue;
|
||||
auto preset_type = boost::to_upper_copy(it->config.opt_string("filament_type", 0u));
|
||||
if (preset_type.size() > best_len && contains_word(upper_type, preset_type)) {
|
||||
iter = it;
|
||||
best_len = preset_type.size();
|
||||
filament_type = "Generic " + it->config.opt_string("filament_type", 0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (iter == filaments.end()) {
|
||||
// Prefer old selection
|
||||
@@ -3843,7 +3879,11 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info)
|
||||
combox_info.ams_names.push_back(ams_name);
|
||||
continue;
|
||||
}
|
||||
iter = std::find_if(filaments.begin(), filaments.end(), [](auto &f) { return f.is_compatible && f.is_system; });
|
||||
iter = std::find_if(filaments.begin(), filaments.end(), [](auto &f) {
|
||||
return f.is_compatible && f.is_system && boost::algorithm::starts_with(f.name, "Generic ");
|
||||
});
|
||||
if (iter == filaments.end())
|
||||
iter = std::find_if(filaments.begin(), filaments.end(), [](auto &f) { return f.is_compatible && f.is_system; });
|
||||
if (iter == filaments.end())
|
||||
continue;
|
||||
}
|
||||
@@ -3881,6 +3921,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
for (auto &entry : filament_ams_list) {
|
||||
auto & ams = entry.second;
|
||||
auto filament_id = ams.opt_string("filament_id", 0u);
|
||||
const bool printer_set_tray = filament_id.empty();
|
||||
auto filament_color = ams.opt_string("filament_colour", 0u);
|
||||
auto filament_color_type = ams.opt_string("filament_colour_type", 0u);
|
||||
auto filament_changed = !ams.has("filament_changed") || ams.opt_bool("filament_changed");
|
||||
@@ -3984,6 +4025,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
}
|
||||
}
|
||||
}
|
||||
const bool resolved_by_material_type = printer_set_tray && iter != filaments.end();
|
||||
if (iter == filaments.end()) {
|
||||
// Prefer old selection
|
||||
if (ams_filament_presets.size() < this->filament_presets.size()) {
|
||||
@@ -4007,7 +4049,8 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
if (iter == filaments.end())
|
||||
continue;
|
||||
}
|
||||
unknowns.emplace_back(&ams, boost::algorithm::starts_with(iter->name, filament_type) ?
|
||||
if (!resolved_by_material_type)
|
||||
unknowns.emplace_back(&ams, boost::algorithm::starts_with(iter->name, filament_type) ?
|
||||
(has_type ? L("The filament may not be compatible with the current machine settings. Generic filament presets will be used.") :
|
||||
L("The filament model is unknown. Generic filament presets will be used.")) :
|
||||
(has_type ? L("The filament may not be compatible with the current machine settings. A random filament preset will be used.") :
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include <algorithm>
|
||||
@@ -473,9 +474,11 @@ void AMSMaterialsSetting::update_filament_editing(bool is_printing)
|
||||
}
|
||||
|
||||
// A third-party tray owns its temp range; BBL RFID trays keep the read-only preset values.
|
||||
// Only the OrcaSonar agent accepts the temp fields, so other agents stay read-only.
|
||||
const bool is_orca_agent = obj && obj->printer_agent_id == ORCA_PRINTER_AGENT_ID;
|
||||
const bool can_edit = !is_printing || obj->is_support_filament_setting_inprinting;
|
||||
m_input_nozzle_min->Enable(m_is_third && can_edit);
|
||||
m_input_nozzle_max->Enable(m_is_third && can_edit);
|
||||
m_input_nozzle_min->Enable(m_is_third && can_edit && is_orca_agent);
|
||||
m_input_nozzle_max->Enable(m_is_third && can_edit && is_orca_agent);
|
||||
|
||||
if (!m_is_third) {
|
||||
m_tip_readonly->SetLabelText(wxEmptyString);
|
||||
@@ -730,6 +733,21 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
|
||||
return;
|
||||
}
|
||||
|
||||
if (nozzle_temp_min_int < FILAMENT_MIN_TEMP || nozzle_temp_min_int > FILAMENT_MAX_TEMP ||
|
||||
nozzle_temp_max_int < FILAMENT_MIN_TEMP || nozzle_temp_max_int > FILAMENT_MAX_TEMP) {
|
||||
MessageDialog msg_dlg(nullptr,
|
||||
wxString::Format(_L("The input value should be greater than %1% and less than %2%"), FILAMENT_MIN_TEMP, FILAMENT_MAX_TEMP),
|
||||
wxEmptyString, wxICON_WARNING | wxOK);
|
||||
msg_dlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
if (nozzle_temp_min_int > nozzle_temp_max_int) {
|
||||
MessageDialog msg_dlg(nullptr, _L("The minimum temperature cannot be greater than the maximum temperature."),
|
||||
wxEmptyString, wxICON_WARNING | wxOK);
|
||||
msg_dlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Orca: log the tray payload this dialog hands the printer, so the filament_id resolved from the
|
||||
// dropdown selection can be checked against the tray_info_idx the AMS actually receives. A
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "DevFilaSystem.h"
|
||||
#include "json_diff.hpp"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp" // ORCA_PRINTER_AGENT_ID
|
||||
#include "DevNozzleSystem.h" // DevNozzle / DevNozzleSystem for GetNozzleFlowStringByAmsId
|
||||
|
||||
// TODO: remove this include
|
||||
@@ -63,12 +64,12 @@ void DevAmsTray::UpdateColorFromStr(const std::string& color)
|
||||
}
|
||||
}
|
||||
|
||||
void DevAmsTray::UpdateEmptyState(bool material_fields_present)
|
||||
void DevAmsTray::UpdateEmptyState(bool material_fields_present, bool classify_empty)
|
||||
{
|
||||
const auto is_zero_or_empty = [](const std::string& value) {
|
||||
return value.empty() || std::all_of(value.begin(), value.end(), [](char c) { return c == '0'; });
|
||||
};
|
||||
is_empty = material_fields_present && setting_id.empty() && m_fila_type.empty() &&
|
||||
is_empty = material_fields_present && classify_empty && setting_id.empty() && m_fila_type.empty() &&
|
||||
is_zero_or_empty(color) && is_zero_or_empty(tag_uid);
|
||||
}
|
||||
|
||||
@@ -793,7 +794,8 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
{
|
||||
curr_tray->remain = -1;
|
||||
}
|
||||
curr_tray->UpdateEmptyState(tray_it->contains("tray_info_idx") && tray_it->contains("tray_type"));
|
||||
curr_tray->UpdateEmptyState(tray_it->contains("tray_info_idx") && tray_it->contains("tray_type"),
|
||||
obj->printer_agent_id == ORCA_PRINTER_AGENT_ID);
|
||||
// The tray objects are reused across status updates. Reset this
|
||||
// state when a previously empty slot receives a filament again.
|
||||
curr_tray->is_slot_placeholder = tray_it->contains("tray_slot_placeholder");
|
||||
|
||||
@@ -100,7 +100,7 @@ public:
|
||||
// setters
|
||||
void reset();
|
||||
void UpdateColorFromStr(const std::string& color);
|
||||
void UpdateEmptyState(bool material_fields_present);
|
||||
void UpdateEmptyState(bool material_fields_present, bool classify_empty);
|
||||
void set_hold_count() { hold_count = HOLD_COUNT_MAX; }
|
||||
|
||||
// getter
|
||||
|
||||
@@ -4290,7 +4290,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
vt_slot[0].m_fila_type = setting_id_to_type(vt_slot[0].setting_id, jj["tray_type"].get<std::string>());
|
||||
// The ack carries the whole slot; re-derive empty so the panel flips off
|
||||
// "Empty" without waiting out the hold.
|
||||
vt_slot[0].UpdateEmptyState(true);
|
||||
vt_slot[0].UpdateEmptyState(true, printer_agent_id == ORCA_PRINTER_AGENT_ID);
|
||||
// delay update
|
||||
vt_slot[0].set_hold_count();
|
||||
} else {
|
||||
@@ -4318,7 +4318,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
tray_it->second->m_fila_type = setting_id_to_type(tray_it->second->setting_id, jj["tray_type"].get<std::string>());
|
||||
// The ack carries the whole slot; re-derive empty so the panel flips off
|
||||
// "Empty" without waiting out the hold.
|
||||
tray_it->second->UpdateEmptyState(true);
|
||||
tray_it->second->UpdateEmptyState(true, printer_agent_id == ORCA_PRINTER_AGENT_ID);
|
||||
// delay update
|
||||
tray_it->second->set_hold_count();
|
||||
} else {
|
||||
@@ -5352,7 +5352,8 @@ DevAmsTray MachineObject::parse_vt_tray(json vtray)
|
||||
else {
|
||||
vt_tray.remain = -1;
|
||||
}
|
||||
vt_tray.UpdateEmptyState(vtray.contains("tray_info_idx") && vtray.contains("tray_type"));
|
||||
vt_tray.UpdateEmptyState(vtray.contains("tray_info_idx") && vtray.contains("tray_type"),
|
||||
printer_agent_id == ORCA_PRINTER_AGENT_ID);
|
||||
}
|
||||
|
||||
return vt_tray;
|
||||
|
||||
@@ -1137,7 +1137,8 @@ void GUI_App::post_init()
|
||||
m_agent->set_on_http_error_fn([this](CloudEvent event, unsigned int status, std::string body) {
|
||||
this->handle_http_error(status, body, event.provider);
|
||||
});
|
||||
m_agent->start_discovery(true, false);
|
||||
if (should_start_ssdp_discovery())
|
||||
m_agent->start_discovery(true, false);
|
||||
}
|
||||
|
||||
//update the plugin tips
|
||||
@@ -4109,6 +4110,18 @@ std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id) cons
|
||||
return (preset_bundle && preset_bundle->is_bbl_vendor()) ? BBL_PRINTER_AGENT_ID : ORCA_PRINTER_AGENT_ID;
|
||||
}
|
||||
|
||||
bool GUI_App::should_start_ssdp_discovery()
|
||||
{
|
||||
// Non-Orca agents (BBL, plugins) keep their own discovery; OrcaSonar only scans in the
|
||||
// background when one of its machines is already configured, otherwise the popup drives it.
|
||||
const std::string agent_id = resolve_printer_agent_id(
|
||||
preset_bundle ? preset_bundle->printers.get_edited_preset().config.opt_string("printer_agent")
|
||||
: std::string());
|
||||
if (agent_id != ORCA_PRINTER_AGENT_ID)
|
||||
return true;
|
||||
return m_device_manager && !m_device_manager->get_my_machine_list(ORCA_PRINTER_AGENT_ID).empty();
|
||||
}
|
||||
|
||||
std::string GUI_App::canonical_printer_agent_id(const std::string& picked_id)
|
||||
{
|
||||
return picked_id == resolve_printer_agent_id("") ? std::string() : picked_id;
|
||||
@@ -4180,7 +4193,8 @@ void GUI_App::switch_printer_agent()
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": printer agent switched to " << effective_agent_id;
|
||||
|
||||
// Start discovery so Python agents can populate the device list via SSDP callback
|
||||
m_agent->start_discovery(true, false);
|
||||
if (should_start_ssdp_discovery())
|
||||
m_agent->start_discovery(true, false);
|
||||
|
||||
// Auto-switch MachineObject (new agent has empty device_info, so always re-select)
|
||||
select_machine(effective_agent_id);
|
||||
|
||||
@@ -399,6 +399,9 @@ public:
|
||||
// Reconcile the live printer agent with the stored preset selection.
|
||||
void switch_printer_agent();
|
||||
|
||||
// Whether the background SSDP scan should run for the effective printer agent.
|
||||
bool should_start_ssdp_discovery();
|
||||
|
||||
std::string resolve_printer_agent_id(const std::string& stored_id) const;
|
||||
// ORCA TODO: in the future, bbl presets should specify "bbl" printer agent id
|
||||
// then, all resolve and canonical would just be ORCA<->""
|
||||
|
||||
@@ -220,32 +220,40 @@ MediaFilePanel::MediaFilePanel(wxWindow * parent)
|
||||
SetSelecting(false);
|
||||
});
|
||||
|
||||
auto onShowHide = [this](auto &e) {
|
||||
e.Skip();
|
||||
if (auto w = dynamic_cast<wxWindow *>(e.GetEventObject()); !w || w->IsBeingDeleted())
|
||||
return;
|
||||
CallAfter([this] {
|
||||
auto fs = m_image_grid ? m_image_grid->GetFileSystem() : nullptr;
|
||||
if (fs) IsShownOnScreen() ? fs->Start() : fs->Stop();
|
||||
});
|
||||
};
|
||||
Bind(wxEVT_SHOW, onShowHide);
|
||||
// Watch the page host's show/hide. Walk up past any wrapper containers (e.g. a
|
||||
// StoragePanel) so the target stays the same regardless of nesting depth.
|
||||
wxWindow* host = parent;
|
||||
while (host && !host->IsTopLevel() && host->GetParent() && !host->GetParent()->IsTopLevel())
|
||||
host = host->GetParent();
|
||||
if (host && host != this)
|
||||
host->Bind(wxEVT_SHOW, onShowHide);
|
||||
Bind(wxEVT_SHOW, &MediaFilePanel::on_show_hide, this);
|
||||
// A Device sub-tab switch hides the page container (StoragePanel); a main tab
|
||||
// switch hides MonitorPanel inside LazyPage. Neither event reaches the other,
|
||||
// so watch every ancestor below the top-level frame.
|
||||
for (wxWindow* w = GetParent(); w && !w->IsTopLevel(); w = w->GetParent()) {
|
||||
w->Bind(wxEVT_SHOW, &MediaFilePanel::on_show_hide, this);
|
||||
m_show_hosts.push_back(w);
|
||||
}
|
||||
|
||||
m_lan_user = "bblp";
|
||||
}
|
||||
|
||||
MediaFilePanel::~MediaFilePanel()
|
||||
{
|
||||
Unbind(wxEVT_SHOW, &MediaFilePanel::on_show_hide, this);
|
||||
for (wxWindow* w : m_show_hosts)
|
||||
if (w && !w->IsBeingDeleted())
|
||||
w->Unbind(wxEVT_SHOW, &MediaFilePanel::on_show_hide, this);
|
||||
m_show_hosts.clear();
|
||||
|
||||
UpdateByObj(nullptr);
|
||||
}
|
||||
|
||||
void MediaFilePanel::on_show_hide(wxShowEvent& e)
|
||||
{
|
||||
e.Skip();
|
||||
if (auto w = dynamic_cast<wxWindow*>(e.GetEventObject()); !w || w->IsBeingDeleted())
|
||||
return;
|
||||
CallAfter([this] {
|
||||
auto fs = m_image_grid ? m_image_grid->GetFileSystem() : nullptr;
|
||||
if (fs) IsShownOnScreen() ? fs->Start() : fs->Stop();
|
||||
});
|
||||
}
|
||||
|
||||
void MediaFilePanel::UpdateByObj(MachineObject* obj)
|
||||
{
|
||||
bool sdcard_state_changed = false;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <boost/smart_ptr/shared_ptr.hpp>
|
||||
#include <cstddef>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
@@ -53,6 +54,8 @@ public:
|
||||
private:
|
||||
void modeChanged(wxCommandEvent & e);
|
||||
|
||||
void on_show_hide(wxShowEvent & e);
|
||||
|
||||
void fetchUrl(boost::weak_ptr<PrinterFileSystem> fs);
|
||||
|
||||
void doAction(size_t index, int action);
|
||||
@@ -99,6 +102,8 @@ private:
|
||||
int m_last_mode = 0;
|
||||
int m_last_type = 0;
|
||||
std::set<int> m_last_errors;
|
||||
|
||||
std::vector<wxWindow*> m_show_hosts;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -6420,9 +6420,10 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
|
||||
for (const auto &entry : wxGetApp().preset_bundle->filament_ams_list) {
|
||||
const auto &tray = entry.second;
|
||||
const bool has_filament = !tray.opt_string("filament_id", 0u).empty();
|
||||
const bool has_type = !tray.opt_string("filament_type", 0u).empty();
|
||||
const bool is_placeholder = tray.has("filament_slot_placeholder") &&
|
||||
tray.opt_bool("filament_slot_placeholder", 0u);
|
||||
if (!has_filament && !is_placeholder) {
|
||||
if (!has_filament && !is_placeholder && !has_type) {
|
||||
continue;
|
||||
}
|
||||
if (combo_index >= p->combos_filament.size()) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
#include "GUI_Preview.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "format.hpp"
|
||||
@@ -848,8 +849,8 @@ void SelectMachinePopup::update_machine_list(wxCommandEvent &event)
|
||||
|
||||
void SelectMachinePopup::start_ssdp(bool start)
|
||||
{
|
||||
return;
|
||||
//if (wxGetApp().getAgent()) { wxGetApp().getAgent()->start_discovery(true, start); }
|
||||
if (auto* agent = wxGetApp().getAgent())
|
||||
agent->start_discovery(true, start);
|
||||
}
|
||||
|
||||
void SelectMachinePopup::OnLeftUp(wxMouseEvent &event)
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509err.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
# ifndef NOMINMAX
|
||||
# define NOMINMAX
|
||||
@@ -1001,6 +1005,11 @@ void Http::add_platform_root_certificates(SSL_CTX* ssl_context)
|
||||
|
||||
load_store(CERT_SYSTEM_STORE_CURRENT_USER);
|
||||
load_store(CERT_SYSTEM_STORE_LOCAL_MACHINE);
|
||||
#elif defined(__APPLE__)
|
||||
// macOS ships its system trust roots as a PEM bundle; the bundled OpenSSL's
|
||||
// compiled-in default path is the build directory, so load this explicitly.
|
||||
if (ssl_context && ::access("/etc/ssl/cert.pem", R_OK) == 0)
|
||||
SSL_CTX_load_verify_locations(ssl_context, "/etc/ssl/cert.pem", nullptr);
|
||||
#else
|
||||
(void)ssl_context;
|
||||
#endif
|
||||
|
||||
@@ -58,21 +58,25 @@ template<class Conn> void expires_never(Conn& conn) {
|
||||
OrcaMqttConnection::~OrcaMqttConnection() { stop(); }
|
||||
|
||||
bool OrcaMqttConnection::start(const Config& config, MessageHandler on_message, StateHandler on_state) {
|
||||
std::lock_guard<std::recursive_mutex> lifecycle_lock(lifecycle_mutex);
|
||||
stop();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
current_config = config;
|
||||
this->on_message = std::move(on_message);
|
||||
this->on_state = std::move(on_state);
|
||||
initial_result = false;
|
||||
initial_completed = false;
|
||||
connected = false;
|
||||
m_last_connack_rc.store(-1);
|
||||
std::lock_guard<std::recursive_mutex> lifecycle_lock(lifecycle_mutex);
|
||||
stop();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
current_config = config;
|
||||
this->on_message = std::move(on_message);
|
||||
this->on_state = std::move(on_state);
|
||||
initial_result = false;
|
||||
initial_completed = false;
|
||||
connected = false;
|
||||
m_last_connack_rc.store(-1);
|
||||
}
|
||||
stopping.store(false);
|
||||
worker = std::thread(&OrcaMqttConnection::run, this);
|
||||
}
|
||||
stopping.store(false);
|
||||
worker = std::thread(&OrcaMqttConnection::run, this);
|
||||
|
||||
// Wait without holding lifecycle_mutex so a concurrent stop() can cancel the
|
||||
// attempt and signal completion instead of blocking until the timeout.
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
if (!initial_cv.wait_for(lock, std::chrono::seconds(10), [this] { return initial_completed; })) {
|
||||
initial_completed = true;
|
||||
|
||||
@@ -397,8 +397,9 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
void ssdp_round()
|
||||
bool ssdp_round()
|
||||
{
|
||||
bool emitted = false;
|
||||
namespace asio = boost::asio;
|
||||
using asio::ip::udp;
|
||||
try {
|
||||
@@ -436,8 +437,10 @@ private:
|
||||
if (make_machine_alive_json(usn, sender.address().to_string(), location, machine_alive)) {
|
||||
nlohmann::json machine = nlohmann::json::parse(machine_alive);
|
||||
const std::string device_id = machine["dev_id"].get<std::string>();
|
||||
if (seen_ids.insert(device_id).second && m_emit)
|
||||
if (seen_ids.insert(device_id).second && m_emit) {
|
||||
m_emit(machine_alive);
|
||||
emitted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (error != asio::error::would_block && error != asio::error::try_again) {
|
||||
@@ -450,14 +453,19 @@ private:
|
||||
} catch (const std::exception& error) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "OrcaSonarDiscovery: SSDP round failed: " << error.what();
|
||||
}
|
||||
return emitted;
|
||||
}
|
||||
|
||||
void browse_loop()
|
||||
{
|
||||
// Idle rounds back off to avoid a permanent 8s M-SEARCH when no OrcaSonar is present.
|
||||
std::chrono::seconds interval(5);
|
||||
while (m_running.load()) {
|
||||
ssdp_round();
|
||||
const bool found = ssdp_round();
|
||||
std::unique_lock<std::mutex> lock(m_wait_mutex);
|
||||
m_wait_cv.wait_for(lock, std::chrono::seconds(5), [this] { return !m_running.load(); });
|
||||
m_wait_cv.wait_for(lock, found ? std::chrono::seconds(5) : interval, [this] { return !m_running.load(); });
|
||||
interval = found ? std::chrono::seconds(5)
|
||||
: std::chrono::seconds(std::min<long long>(interval.count() * 2, 60));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user