Files
OrcaSlicer/tests/libslic3r/test_preset_bundle_ams_sync.cpp
T
Lam Wei Lun ccbb73baaf ## 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.
2026-10-06 12:09:23 +08:00

196 lines
9.5 KiB
C++

#include <catch2/catch_all.hpp>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "libslic3r/Preset.hpp"
#include "libslic3r/PresetBundle.hpp"
using namespace Slic3r;
namespace {
// In-memory system filament preset. Mirrors the helper in test_preset_bundle_loading.cpp.
Preset &add_system_filament(PresetBundle &bundle, const std::string &name, const std::string &filament_id, const std::string &filament_type)
{
DynamicPrintConfig config(bundle.filaments.default_preset().config);
config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value = "";
config.option<ConfigOptionStrings>("filament_type", true)->values = {filament_type};
Preset &preset = bundle.filaments.load_preset(std::string(), name, config, /*select=*/false);
preset.is_system = true;
preset.filament_id = filament_id;
return preset;
}
// One AMS tray entry as build_filament_ams_list produces it.
DynamicPrintConfig make_tray(const std::string &filament_id, const std::string &filament_type, const std::string &color, const std::string &ams_id,
const std::string &slot_id)
{
DynamicPrintConfig tray;
tray.set_key_value("filament_id", new ConfigOptionStrings{filament_id});
tray.set_key_value("filament_type", new ConfigOptionStrings{filament_type});
tray.set_key_value("filament_colour", new ConfigOptionStrings{color});
tray.set_key_value("filament_colour_type", new ConfigOptionStrings{"1"});
tray.set_key_value("filament_multi_colour", new ConfigOptionStrings{});
tray.set_key_value("ams_id", new ConfigOptionStrings{ams_id});
tray.set_key_value("slot_id", new ConfigOptionStrings{slot_id});
return tray;
}
} // namespace
// A tray the printer UI wrote carries a material type but no OrcaSlicer preset id. It must still
// sync, resolved to Generic <type> with the tray's own color.
TEST_CASE("AMS sync resolves a printer-set tray to Generic by material type", "[Preset][AMS]")
{
PresetBundle bundle;
add_system_filament(bundle, "Generic PETG @Q2", "OFYPdQJh", "PETG");
add_system_filament(bundle, "Generic PLA @Q2", "GFL99", "PLA");
bundle.filament_ams_list[0] = make_tray("OFYPdQJh", "PETG", "#FE717A", "0", "0"); // preset-backed
bundle.filament_ams_list[1] = make_tray("", "PETG", "#898F9B", "0", "1"); // printer-set, no id
bundle.filament_ams_list[2] = make_tray("", "PLA", "#FAFAFA", "0", "2"); // printer-set, no id
bundle.filament_ams_list[3] = make_tray("", "", "#000000", "0", "3"); // empty slot
std::vector<std::pair<DynamicPrintConfig *, std::string>> unknowns;
std::map<int, AMSMapInfo> maps;
MergeFilamentInfo merge;
const unsigned int count = bundle.sync_ams_list(unknowns, /*use_map=*/false, maps, /*enable_append=*/false, merge);
CHECK(count == 3);
CHECK(unknowns.empty());
REQUIRE(bundle.filament_presets.size() == 3);
CHECK(bundle.filament_presets[0] == "Generic PETG @Q2");
CHECK(bundle.filament_presets[1] == "Generic PETG @Q2");
CHECK(bundle.filament_presets[2] == "Generic PLA @Q2");
const ConfigOptionStrings *colors = bundle.project_config.option<ConfigOptionStrings>("filament_colour");
REQUIRE(colors != nullptr);
REQUIRE(colors->values.size() == 3);
CHECK(colors->values[0] == "#FE717A");
CHECK(colors->values[1] == "#898F9B");
CHECK(colors->values[2] == "#FAFAFA");
}
// Without a preset id and without a material type there is nothing to resolve; the tray stays
// skipped in a direct sync.
TEST_CASE("AMS sync skips a tray without a preset id or a material type", "[Preset][AMS]")
{
PresetBundle bundle;
add_system_filament(bundle, "Generic PETG @Q2", "OFYPdQJh", "PETG");
bundle.filament_ams_list[0] = make_tray("", "", "#000000", "0", "0");
std::vector<std::pair<DynamicPrintConfig *, std::string>> unknowns;
std::map<int, AMSMapInfo> maps;
MergeFilamentInfo merge;
CHECK(bundle.sync_ams_list(unknowns, /*use_map=*/false, maps, /*enable_append=*/false, merge) == 0);
CHECK(bundle.filament_presets.empty());
}
// Guard the pre-existing behavior: a non-empty id that matches no preset still resolves by type.
TEST_CASE("AMS sync still resolves an unmatched preset id by material type", "[Preset][AMS]")
{
PresetBundle bundle;
add_system_filament(bundle, "Generic PETG @Q2", "OFYPdQJh", "PETG");
bundle.filament_ams_list[0] = make_tray("REMOVED_ID", "PETG", "#123456", "0", "0");
std::vector<std::pair<DynamicPrintConfig *, std::string>> unknowns;
std::map<int, AMSMapInfo> maps;
MergeFilamentInfo merge;
CHECK(bundle.sync_ams_list(unknowns, /*use_map=*/false, maps, /*enable_append=*/false, merge) == 1);
CHECK(unknowns.size() == 1);
REQUIRE(bundle.filament_presets.size() == 1);
CHECK(bundle.filament_presets[0] == "Generic PETG @Q2");
}
// Mapping-mode sync used to force "Generic PLA" for an id-less tray; it must use the type instead.
TEST_CASE("AMS sync in mapping mode resolves a printer-set tray to Generic, not Generic PLA", "[Preset][AMS]")
{
PresetBundle bundle;
add_system_filament(bundle, "Generic PETG @Q2", "OFYPdQJh", "PETG");
add_system_filament(bundle, "Generic PLA @Q2", "GFL99", "PLA");
bundle.filament_presets = {"Generic PLA @Q2"};
bundle.project_config.option<ConfigOptionStrings>("filament_colour")->values = {"#000000"};
bundle.project_config.option<ConfigOptionStrings>("filament_colour_type")->values = {"1"};
bundle.filament_ams_list[0] = make_tray("", "PETG", "#898F9B", "0", "1");
std::map<int, AMSMapInfo> maps;
maps[0] = AMSMapInfo{"0", "1"};
std::vector<std::pair<DynamicPrintConfig *, std::string>> unknowns;
MergeFilamentInfo merge;
const unsigned int count = bundle.sync_ams_list(unknowns, /*use_map=*/true, maps, /*enable_append=*/false, merge);
CHECK(count == 1);
CHECK(unknowns.empty());
REQUIRE(bundle.filament_presets.size() == 1);
CHECK(bundle.filament_presets[0] == "Generic PETG @Q2");
CHECK(bundle.project_config.option<ConfigOptionStrings>("filament_colour")->values[0] == "#898F9B");
}
// The dialog builds ams_mapping2 one entry per filament_presets index, while the generated
// G-code's toolchange names the filament's index in the slice's filament config arrays. Those
// are the same index only if full_config() collects the filament arrays in filament_presets
// order; if a refactor reorders one side, every mapped print aims at the wrong lane.
TEST_CASE("Full config filament arrays follow the selected filament preset order", "[Preset][AMS]")
{
PresetBundle bundle;
add_system_filament(bundle, "Generic PETG @Q2", "OFYPdQJh", "PETG");
add_system_filament(bundle, "Generic PLA @Q2", "GFL99", "PLA");
// Deliberately not the load order: the arrays must follow this selection.
bundle.filament_presets = {"Generic PLA @Q2", "Generic PETG @Q2"};
const DynamicPrintConfig full = bundle.full_config(/*apply_extruder=*/false);
REQUIRE(full.option<ConfigOptionStrings>("filament_type") != nullptr);
CHECK(full.option<ConfigOptionStrings>("filament_settings_id")->values == std::vector<std::string>{"Generic PLA @Q2", "Generic PETG @Q2"});
CHECK(full.option<ConfigOptionStrings>("filament_type")->values == std::vector<std::string>{"PLA", "PETG"});
// The AMS sync matches trays by filament_id, so this array must follow the same order.
CHECK(full.option<ConfigOptionStrings>("filament_ids")->values == std::vector<std::string>{"GFL99", "OFYPdQJh"});
}
// get_ams_cobox_infos builds the combo arrays that SyncAmsInfoDialog pairs with AMS colors
// positionally. A printer-set tray must appear so the array stays aligned with the sync output.
TEST_CASE("AMS combo info keeps a printer-set tray and stays aligned", "[Preset][AMS]")
{
PresetBundle bundle;
add_system_filament(bundle, "Generic PETG @Q2", "OFYPdQJh", "PETG");
add_system_filament(bundle, "Generic PLA @Q2", "GFL99", "PLA");
auto tray_a = make_tray("OFYPdQJh", "PETG", "#FE717A", "0", "0");
tray_a.set_key_value("tray_name", new ConfigOptionStrings{"A"});
auto tray_b = make_tray("", "PETG", "#898F9B", "0", "1");
tray_b.set_key_value("tray_name", new ConfigOptionStrings{"B"});
auto tray_c = make_tray("GFL99", "PLA", "#FAFAFA", "0", "2");
tray_c.set_key_value("tray_name", new ConfigOptionStrings{"C"});
bundle.filament_ams_list[0] = tray_a;
bundle.filament_ams_list[1] = tray_b;
bundle.filament_ams_list[2] = tray_c;
AMSComboInfo combo_info;
bundle.get_ams_cobox_infos(combo_info);
// The id-less PETG tray resolves to a generic preset instead of being dropped, so C keeps
// its index and its color.
REQUIRE(combo_info.ams_filament_presets.size() == 3);
CHECK(combo_info.ams_filament_presets[0] == "Generic PETG @Q2");
CHECK(combo_info.ams_filament_presets[1] == "Generic PETG @Q2");
CHECK(combo_info.ams_filament_presets[2] == "Generic PLA @Q2");
REQUIRE(combo_info.ams_filament_colors.size() == 3);
CHECK(combo_info.ams_filament_colors[0] == "#FE717A");
CHECK(combo_info.ams_filament_colors[1] == "#898F9B");
CHECK(combo_info.ams_filament_colors[2] == "#FAFAFA");
REQUIRE(combo_info.ams_names.size() == 3);
CHECK(combo_info.ams_names[0] == "A");
CHECK(combo_info.ams_names[1] == "B");
CHECK(combo_info.ams_names[2] == "C");
}