mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
Merge branch 'main' of https://github.com/OrcaSlicer/OrcaSlicer_priv into feat/printer-agent-impl
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/string.h>
|
||||
@@ -30,6 +31,8 @@
|
||||
#include <wx/busyinfo.h>
|
||||
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
#include <sstream>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
|
||||
@@ -349,7 +349,7 @@ void* BBLNetworkPlugin::get_function(const char* name)
|
||||
return function;
|
||||
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
function = GetProcAddress(m_networking_module, name);
|
||||
function = reinterpret_cast<void*>(GetProcAddress(m_networking_module, name));
|
||||
#else
|
||||
function = dlsym(m_networking_module, name);
|
||||
#endif
|
||||
|
||||
@@ -2,10 +2,17 @@
|
||||
#include "BBLNetworkPlugin.hpp"
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include "NetworkAgentFactory.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "NetworkAgent.hpp"
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <cmath>
|
||||
@@ -13,6 +20,120 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
// Bambu's own catalog ids for every filament this app ships, Bambu's own included, since Orca
|
||||
// content-addresses those too. Keyed both ways so each of the four translation entry points
|
||||
// below is a single lookup. Loaded once per process, on first use. A missing or malformed file
|
||||
// logs once and leaves both maps empty, so every translation degrades to identity. Same shape
|
||||
// as DevFilaBlacklist::load_filaments_blacklist_config.
|
||||
struct BambuFilamentIdMap { std::unordered_map<std::string, std::string> to_bambu, to_orca; };
|
||||
|
||||
const BambuFilamentIdMap& bambu_filament_id_map()
|
||||
{
|
||||
static const BambuFilamentIdMap map = [] {
|
||||
BambuFilamentIdMap m;
|
||||
const std::string path = resources_dir() + "/printers/bambu_filament_ids.json";
|
||||
try {
|
||||
boost::nowide::ifstream file(path);
|
||||
if (!file.is_open()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Bambu filament id map not found, ids pass through untranslated: " << path;
|
||||
return m;
|
||||
}
|
||||
json doc;
|
||||
file >> doc;
|
||||
for (const auto& [orca_filament_id, row] : doc.at("filaments").items()) {
|
||||
const std::string bambu_id = row.at("bambu_id").get<std::string>();
|
||||
m.to_bambu.emplace(orca_filament_id, bambu_id);
|
||||
m.to_orca.emplace(bambu_id, orca_filament_id);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Bambu filament id map unreadable, ids pass through untranslated: " << e.what();
|
||||
m = {};
|
||||
}
|
||||
return m;
|
||||
}();
|
||||
return map;
|
||||
}
|
||||
|
||||
// Rewrites every string under "tray_info_idx", "filament_id" or "filamentId", at any depth, in place.
|
||||
void rewrite_filament_ids(json& j, const std::unordered_map<std::string, std::string>& map)
|
||||
{
|
||||
if (j.is_object()) {
|
||||
for (auto& [key, value] : j.items()) {
|
||||
if (value.is_string() && (key == "tray_info_idx" || key == "filament_id" || key == "filamentId")) {
|
||||
auto it = map.find(value.get_ref<const std::string&>());
|
||||
if (it != map.end())
|
||||
value = it->second;
|
||||
} else
|
||||
rewrite_filament_ids(value, map);
|
||||
}
|
||||
} else if (j.is_array())
|
||||
for (auto& element : j)
|
||||
rewrite_filament_ids(element, map);
|
||||
}
|
||||
|
||||
// Text that does not parse as JSON, or that mentions none of the id keys, comes back byte-identical.
|
||||
std::string rewrite_filament_ids(std::string text, const std::unordered_map<std::string, std::string>& map)
|
||||
{
|
||||
if (map.empty() || (text.find("tray_info_idx") == std::string::npos && text.find("filament_id") == std::string::npos &&
|
||||
text.find("filamentId") == std::string::npos))
|
||||
return text; // nothing to map, skip the parse (moved, not copied)
|
||||
try {
|
||||
json j = json::parse(text);
|
||||
rewrite_filament_ids(j, map);
|
||||
return j.dump();
|
||||
} catch (const std::exception&) {
|
||||
return text; // not JSON: forward as received
|
||||
}
|
||||
}
|
||||
|
||||
// Wraps an inbound message callback so every Bambu id it delivers arrives already translated.
|
||||
// A null fn is a deregistration (see GUI_App.cpp's shutdown phase 1 and NetworkAgent::apply_printer_callbacks
|
||||
// clearing callbacks with {}), and must stay null rather than become a live wrapper around an empty target.
|
||||
OnMessageFn to_orca_messages(OnMessageFn fn)
|
||||
{
|
||||
if (!fn)
|
||||
return fn;
|
||||
return [fn = std::move(fn)](std::string dev_id, std::string msg) { fn(std::move(dev_id), BBLPrinterAgent::to_orca_payload(std::move(msg))); };
|
||||
}
|
||||
|
||||
// Retypes a plug-in entry point for an older plug-in generation. The detour through the
|
||||
// generic function pointer marks the signature change as deliberate, which a direct cast
|
||||
// between two signatures does not.
|
||||
template <typename To, typename From>
|
||||
To as_abi(From fn)
|
||||
{
|
||||
static_assert(std::is_function_v<std::remove_pointer_t<From>>, "as_abi retypes a function pointer");
|
||||
return reinterpret_cast<To>(reinterpret_cast<void (*)()>(fn));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string BBLPrinterAgent::to_orca_filament_id(const std::string& printer_filament_id) const
|
||||
{
|
||||
const auto& map = bambu_filament_id_map().to_orca;
|
||||
auto it = map.find(printer_filament_id);
|
||||
return it != map.end() ? it->second : printer_filament_id;
|
||||
}
|
||||
|
||||
std::string BBLPrinterAgent::from_orca_filament_id(const std::string& orca_filament_id) const
|
||||
{
|
||||
const auto& map = bambu_filament_id_map().to_bambu;
|
||||
auto it = map.find(orca_filament_id);
|
||||
return it != map.end() ? it->second : orca_filament_id;
|
||||
}
|
||||
|
||||
std::string BBLPrinterAgent::to_orca_payload(std::string json_text)
|
||||
{
|
||||
return rewrite_filament_ids(std::move(json_text), bambu_filament_id_map().to_orca);
|
||||
}
|
||||
|
||||
std::string BBLPrinterAgent::from_orca_payload(std::string json_text)
|
||||
{
|
||||
return rewrite_filament_ids(std::move(json_text), bambu_filament_id_map().to_bambu);
|
||||
}
|
||||
|
||||
BBLPrinterAgent::BBLPrinterAgent() = default;
|
||||
|
||||
BBLPrinterAgent::~BBLPrinterAgent() = default;
|
||||
@@ -186,6 +307,7 @@ int BBLPrinterAgent::publish(const std::string& dev_id, const nlohmann::json& j,
|
||||
|
||||
int BBLPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag)
|
||||
{
|
||||
json_str = from_orca_payload(std::move(json_str));
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_send_message();
|
||||
@@ -194,7 +316,7 @@ int BBLPrinterAgent::send_message(std::string dev_id, std::string json_str, int
|
||||
// series through the legacy form would silently drop MessageFlag sign/encrypt.
|
||||
switch (plugin.network_abi()) {
|
||||
case NetworkAbi::Legacy: {
|
||||
auto legacy_func = reinterpret_cast<func_send_message_legacy>(func);
|
||||
auto legacy_func = as_abi<func_send_message_legacy>(func);
|
||||
return legacy_func(agent, std::move(dev_id), std::move(json_str), qos);
|
||||
}
|
||||
case NetworkAbi::V0203:
|
||||
@@ -231,13 +353,14 @@ int BBLPrinterAgent::disconnect_printer()
|
||||
|
||||
int BBLPrinterAgent::send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag)
|
||||
{
|
||||
json_str = from_orca_payload(std::move(json_str));
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_send_message_to_printer();
|
||||
if (func && agent) {
|
||||
switch (plugin.network_abi()) {
|
||||
case NetworkAbi::Legacy: {
|
||||
auto legacy_func = reinterpret_cast<func_send_message_to_printer_legacy>(func);
|
||||
auto legacy_func = as_abi<func_send_message_to_printer_legacy>(func);
|
||||
return legacy_func(agent, std::move(dev_id), std::move(json_str), qos);
|
||||
}
|
||||
case NetworkAbi::V0203:
|
||||
@@ -327,7 +450,7 @@ int BBLPrinterAgent::bind(std::string dev_ip, std::string dev_id, std::string de
|
||||
switch (plugin.network_abi()) {
|
||||
case NetworkAbi::Legacy:
|
||||
case NetworkAbi::V0203: {
|
||||
auto older_func = reinterpret_cast<func_bind_pre0208>(func);
|
||||
auto older_func = as_abi<func_bind_pre0208>(func);
|
||||
return older_func(agent, dev_ip, dev_id, sec_link, timezone, improved, update_fn);
|
||||
}
|
||||
case NetworkAbi::Current:
|
||||
@@ -485,11 +608,12 @@ int dispatch_start(CurrentFn func, PrintParams& params, const CallbackFns&... ca
|
||||
auto agent = plugin.get_agent();
|
||||
if (!func || !agent)
|
||||
return -1;
|
||||
params.ams_mapping_info = BBLPrinterAgent::from_orca_payload(std::move(params.ams_mapping_info));
|
||||
switch (plugin.network_abi()) {
|
||||
case NetworkAbi::Legacy:
|
||||
return reinterpret_cast<LegacyFn>(func)(agent, BBLNetworkPlugin::as_legacy(params), callbacks...);
|
||||
return as_abi<LegacyFn>(func)(agent, BBLNetworkPlugin::as_legacy(params), callbacks...);
|
||||
case NetworkAbi::V0203:
|
||||
return reinterpret_cast<Fn0203>(func)(agent, BBLNetworkPlugin::as_0203(params), callbacks...);
|
||||
return as_abi<Fn0203>(func)(agent, BBLNetworkPlugin::as_0203(params), callbacks...);
|
||||
case NetworkAbi::Current:
|
||||
return func(agent, std::move(params), callbacks...);
|
||||
default:
|
||||
@@ -579,7 +703,7 @@ int BBLPrinterAgent::set_on_message_fn(OnMessageFn fn)
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_on_message_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
return func(agent, to_orca_messages(std::move(fn)));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -612,7 +736,7 @@ int BBLPrinterAgent::set_on_local_message_fn(OnMessageFn fn)
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_on_local_message_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
return func(agent, to_orca_messages(std::move(fn)));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -99,6 +99,20 @@ public:
|
||||
int set_queue_on_main_fn(QueueOnMainFn fn) override;
|
||||
FilamentSyncMode get_filament_sync_mode() const override;
|
||||
|
||||
// Bambu's own catalog ids. Orca content-addresses every system filament, Bambu's included;
|
||||
// the printer, the AMS and Bambu's cloud know only Bambu's ids, so this agent translates at
|
||||
// the boundary through resources/printers/bambu_filament_ids.json (generated by
|
||||
// scripts/update_bambu_filament_ids.py). An id without a map row is returned as it is.
|
||||
std::string to_orca_filament_id(const std::string& printer_filament_id) const override;
|
||||
std::string from_orca_filament_id(const std::string& orca_filament_id) const override;
|
||||
|
||||
// Rewrite every string under "tray_info_idx", "filament_id" or "filamentId", at any depth,
|
||||
// in a JSON document (an MQTT payload or PrintParams::ams_mapping_info). Text that does not
|
||||
// parse, or contains none of the keys, is returned unchanged.
|
||||
// Taken by value: most outbound traffic carries no filament id and is moved straight back out.
|
||||
static std::string to_orca_payload(std::string json_text);
|
||||
static std::string from_orca_payload(std::string json_text);
|
||||
|
||||
private:
|
||||
// why: the lan/cloud DECISION stays machine-side; keep this mechanical branch in sync with publish_json.
|
||||
int publish(const std::string& dev_id, const nlohmann::json& j, bool lan_mode);
|
||||
|
||||
@@ -115,6 +115,7 @@ class UdpSession
|
||||
{
|
||||
public:
|
||||
UdpSession(Bonjour::ReplyFn rfn);
|
||||
virtual ~UdpSession() = default;
|
||||
virtual void handle_receive(const boost::system::error_code& error, size_t bytes) = 0;
|
||||
std::vector<char> buffer;
|
||||
boost::asio::ip::udp::endpoint remote_endpoint;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../GUI/GUI_App.hpp"
|
||||
#include "../GUI/DeviceCore/DevStorage.h"
|
||||
#include "../GUI/DeviceManager.hpp"
|
||||
#include "NetworkAgent.hpp"
|
||||
#include "../GUI/Jobs/ProgressIndicator.hpp"
|
||||
#include "../GUI/PartPlate.hpp"
|
||||
#include "libslic3r/CutUtils.hpp"
|
||||
@@ -62,22 +63,29 @@ std::vector<std::string> not_support_auto_pa_cali_filaments = {
|
||||
|
||||
void get_default_k_n_value(const std::string &filament_id, float &k, float &n)
|
||||
{
|
||||
if (filament_id.compare("GFU01") == 0) {
|
||||
// filament_id is our OF id; the literals below are the printer's own. An id the agent has
|
||||
// no mapping for (e.g. a caller still on the old id) passes through unchanged.
|
||||
auto* agent = wxGetApp().getAgent();
|
||||
const std::string printer_filament_id = agent ? agent->from_orca_filament_id(filament_id) : filament_id;
|
||||
if (printer_filament_id.compare("GFU01") == 0) {
|
||||
/* TPU 95A */
|
||||
k = 0.25;
|
||||
n = 1.0;
|
||||
} else if (filament_id.compare("GFU03") == 0) {
|
||||
} else if (printer_filament_id.compare("GFU03") == 0) {
|
||||
/* TPU 90A */
|
||||
k = 0.35;
|
||||
n = 1.0;
|
||||
} else if (filament_id.compare("GFU04") == 0) {
|
||||
} else if (printer_filament_id.compare("GFU04") == 0) {
|
||||
/* TPU 85A */
|
||||
k = 0.65;
|
||||
n = 1.0;
|
||||
} else if (filament_id.compare("GFG00") == 0 || filament_id.compare("GFG01") == 0 || filament_id.compare("GFG60") == 0 || filament_id.compare("GFL06") == 0 ||
|
||||
filament_id.compare("GFL55") == 0 || filament_id.compare("GFG99") == 0 || filament_id.compare("GFG98") == 0 || filament_id.compare("GFG97") == 0 ||
|
||||
filament_id.compare("GFG50") == 0 || filament_id.compare("GFU02") == 0 || filament_id.compare("GFU98") == 0 || filament_id.compare("GFS00") == 0 ||
|
||||
filament_id.compare("GFS02") == 0) {
|
||||
} else if (printer_filament_id.compare("GFG00") == 0 || printer_filament_id.compare("GFG01") == 0 ||
|
||||
printer_filament_id.compare("GFG60") == 0 || printer_filament_id.compare("GFL06") == 0 ||
|
||||
printer_filament_id.compare("GFL55") == 0 || printer_filament_id.compare("GFG99") == 0 ||
|
||||
printer_filament_id.compare("GFG98") == 0 || printer_filament_id.compare("GFG97") == 0 ||
|
||||
printer_filament_id.compare("GFG50") == 0 || printer_filament_id.compare("GFU02") == 0 ||
|
||||
printer_filament_id.compare("GFU98") == 0 || printer_filament_id.compare("GFS00") == 0 ||
|
||||
printer_filament_id.compare("GFS02") == 0) {
|
||||
/* 0.04 filaments */
|
||||
k = 0.04;
|
||||
n = 1.0;
|
||||
@@ -1393,7 +1401,10 @@ void CalibUtils::calib_retraction(const CalibInfo &calib_info, wxString &error_m
|
||||
|
||||
bool CalibUtils::is_support_auto_pa_cali(std::string filament_id)
|
||||
{
|
||||
auto iter = std::find(not_support_auto_pa_cali_filaments.begin(), not_support_auto_pa_cali_filaments.end(), filament_id);
|
||||
// filament_id is our OF id; not_support_auto_pa_cali_filaments holds the printer's own ids.
|
||||
auto* agent = wxGetApp().getAgent();
|
||||
const std::string printer_filament_id = agent ? agent->from_orca_filament_id(filament_id) : filament_id;
|
||||
auto iter = std::find(not_support_auto_pa_cali_filaments.begin(), not_support_auto_pa_cali_filaments.end(), printer_filament_id);
|
||||
if (iter != not_support_auto_pa_cali_filaments.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Identifiers of the cloud services an ICloudServiceAgent can stand for.
|
||||
static const std::string ORCA_CLOUD_PROVIDER("orca");
|
||||
static const std::string BBL_CLOUD_PROVIDER("bbl");
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -8,7 +8,11 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -16,6 +20,12 @@ namespace {
|
||||
|
||||
constexpr const char* CrealityPrintAgent_VERSION = "0.1.0";
|
||||
|
||||
std::string to_lower(std::string s)
|
||||
{
|
||||
for (auto& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
return s;
|
||||
}
|
||||
|
||||
bool has_visible_base_preset(const PresetCollection& filaments, const std::string& filament_id)
|
||||
{
|
||||
for (const auto& p : filaments.get_presets()) {
|
||||
@@ -27,19 +37,47 @@ bool has_visible_base_preset(const PresetCollection& filaments, const std::strin
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lower-case words of a preset name with the "@scope" suffix dropped:
|
||||
// "Generic PLA Matte @Creality K2-all" -> {"generic", "pla", "matte"}.
|
||||
std::vector<std::string> name_words(const std::string& name)
|
||||
{
|
||||
std::vector<std::string> words;
|
||||
std::string word;
|
||||
for (char c : name.substr(0, name.find('@'))) {
|
||||
if (std::isalnum(static_cast<unsigned char>(c))) {
|
||||
word += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
} else if (!word.empty()) {
|
||||
words.push_back(word);
|
||||
word.clear();
|
||||
}
|
||||
}
|
||||
if (!word.empty())
|
||||
words.push_back(word);
|
||||
return words;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Score visible compatible filament presets against the CFS spool metadata and
|
||||
// return the best-matching filament_id. Scoring:
|
||||
// +20 preset name contains brand_name as a substring
|
||||
// (e.g. "Hyper PLA" in "Hyper PLA @Creality K2 0.4 nozzle")
|
||||
// +10 preset name contains the vendor substring (e.g. "Creality")
|
||||
// Tiebreak: prefer the SYSTEM (shipped) preset over user copies. Brand-
|
||||
// specific system presets carry their own filament_id; user copies of
|
||||
// generic presets inherit a generic filament_id from their parent, so
|
||||
// preferring the user copy can collapse a brand-specific match back to
|
||||
// "Generic PLA" via the inherited id. Plus: this code targets upstream
|
||||
// OrcaSlicer where shipping the user's local tuning would be wrong.
|
||||
// +10 the preset belongs to the spool's vendor - by its owning VendorProfile OR by
|
||||
// its name. The profile test is what finds the vendor's own generics, which are
|
||||
// named "Generic <material> @<scope>" and do not repeat the vendor; the name test
|
||||
// still finds a third party filament shipped inside that vendor's bundle, which
|
||||
// carries the bundle owner's profile but names its real brand.
|
||||
// -5 per word of the preset name the spool never mentioned ("generic" excepted -
|
||||
// it marks the unbranded base product rather than a qualifier), so the least
|
||||
// specific preset that still explains the spool wins. Without it a spool
|
||||
// reporting only "PLA" scores "Generic PLA High Speed" and "Generic PLA Matte"
|
||||
// exactly as high as "Generic PLA"; those are three products with three
|
||||
// filament_ids, so whichever sorted first won and the printer got the wrong
|
||||
// one. Applied after the score gate, so it only reorders genuine matches.
|
||||
// Tiebreak: prefer the SYSTEM (shipped) preset over user copies, then by name so the
|
||||
// winner never depends on how std::sort leaves equal elements. User copies of generic
|
||||
// presets inherit a generic filament_id from their parent, so preferring the user copy
|
||||
// can collapse a brand-specific match back to "Generic PLA" via the inherited id.
|
||||
// Requires the preset's declared filament_type to equal the spool's base type
|
||||
// (PLA/PETG/ABS/...) so we never auto-pick a PETG preset for a PLA spool.
|
||||
// Falls back to filaments.filament_id_by_type(base_type) when nothing scores.
|
||||
@@ -48,15 +86,17 @@ std::string CrealityPrintAgent::match_filament_preset(const PresetCollection& fi
|
||||
const std::string& brand_name,
|
||||
const std::string& base_type)
|
||||
{
|
||||
auto to_lower = [](std::string s) {
|
||||
for (auto& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
return s;
|
||||
};
|
||||
|
||||
const std::string vendor_lower = to_lower(vendor);
|
||||
const std::string brand_lower = to_lower(brand_name);
|
||||
const std::string type_lower = to_lower(base_type);
|
||||
|
||||
// Everything the spool told us about itself, as words. A word in a preset's name
|
||||
// that is not in here is a qualifier the spool never claimed.
|
||||
std::set<std::string> spool_words{"generic"};
|
||||
for (const std::string& src : {brand_lower, vendor_lower, type_lower})
|
||||
for (auto& w : name_words(src))
|
||||
spool_words.insert(std::move(w));
|
||||
|
||||
struct Match {
|
||||
const Preset* preset;
|
||||
int score;
|
||||
@@ -83,11 +123,20 @@ std::string CrealityPrintAgent::match_filament_preset(const PresetCollection& fi
|
||||
int score = 0;
|
||||
if (!brand_lower.empty() && name_lower.find(brand_lower) != std::string::npos)
|
||||
score += 20;
|
||||
if (!vendor_lower.empty() && name_lower.find(vendor_lower) != std::string::npos)
|
||||
// Profile OR name - neither alone covers both the vendor's own generics and the
|
||||
// third party filaments shipped inside its bundle. See the header comment.
|
||||
if (!vendor_lower.empty()
|
||||
&& ((p.vendor != nullptr && to_lower(p.vendor->name) == vendor_lower)
|
||||
|| name_lower.find(vendor_lower) != std::string::npos))
|
||||
score += 10;
|
||||
|
||||
if (score > 0)
|
||||
matches.push_back({&p, score, !p.is_system && !p.is_default});
|
||||
if (score == 0) continue;
|
||||
|
||||
for (const auto& w : name_words(p.name))
|
||||
if (spool_words.count(w) == 0)
|
||||
score -= 5;
|
||||
|
||||
matches.push_back({&p, score, !p.is_system && !p.is_default});
|
||||
}
|
||||
|
||||
if (matches.empty()) {
|
||||
@@ -105,7 +154,7 @@ std::string CrealityPrintAgent::match_filament_preset(const PresetCollection& fi
|
||||
[](const Match& a, const Match& b) {
|
||||
if (a.score != b.score) return a.score > b.score;
|
||||
if (a.is_user != b.is_user) return !a.is_user; // prefer system over user
|
||||
return false;
|
||||
return a.preset->name < b.preset->name; // keep the winner deterministic
|
||||
});
|
||||
|
||||
BOOST_LOG_TRIVIAL(info)
|
||||
|
||||
@@ -254,10 +254,8 @@ int Http::priv::xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_o
|
||||
bool cb_cancel = false;
|
||||
|
||||
if (self->progressfn) {
|
||||
double speed;
|
||||
double speed = 0.;
|
||||
curl_easy_getinfo(self->curl, CURLINFO_SPEED_UPLOAD, &speed);
|
||||
if (speed > 0.01)
|
||||
speed = speed;
|
||||
Progress progress(dltotal, dlnow, ultotal, ulnow, self->buffer, speed);
|
||||
self->progressfn(progress, cb_cancel);
|
||||
}
|
||||
@@ -323,8 +321,10 @@ void Http::priv::form_add_file(const char *name, const fs::path &path, const cha
|
||||
// We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows
|
||||
// and so we use CURLFORM_STREAM with boost ifstream to read the file.
|
||||
|
||||
std::string filename_str;
|
||||
if (filename == nullptr) {
|
||||
filename = path.string().c_str();
|
||||
filename_str = path.string();
|
||||
filename = filename_str.c_str();
|
||||
}
|
||||
|
||||
form_files.emplace_back(path, offset, length);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define __I_CLOUD_SERVICE_AGENT_HPP__
|
||||
|
||||
#include "bambu_networking.hpp"
|
||||
#include "CloudProvider.hpp"
|
||||
#include "../../libslic3r/ProjectTask.hpp"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -37,9 +38,6 @@ namespace Slic3r {
|
||||
* implementation.
|
||||
*/
|
||||
|
||||
static const std::string ORCA_CLOUD_PROVIDER("orca");
|
||||
static const std::string BBL_CLOUD_PROVIDER("bbl");
|
||||
|
||||
struct CloudEvent {
|
||||
std::string provider; // ORCA_CLOUD_PROVIDER or BBL_CLOUD_PROVIDER
|
||||
};
|
||||
|
||||
@@ -16,41 +16,6 @@
|
||||
#include <cstdint>
|
||||
#include "ICameraSignalingChannel.hpp"
|
||||
|
||||
#if 1
|
||||
|
||||
struct OrcaProtocol
|
||||
{
|
||||
enum CameraStreamMode { http, http_snapshot, rtsp, webrtc };
|
||||
struct Capabilities {
|
||||
bool has_ams;
|
||||
struct CameraInfo {
|
||||
CameraStreamMode available_modes;
|
||||
std::string url;
|
||||
};
|
||||
|
||||
std::vector<CameraInfo> cameras;
|
||||
|
||||
bool toolchanger;
|
||||
int nozzle_count;
|
||||
};
|
||||
|
||||
struct AMSInfo {
|
||||
int slot_count;
|
||||
std::vector<std::string> color_info;
|
||||
std::vector<std::string> filament_id;
|
||||
};
|
||||
|
||||
AMSInfo ams_info;
|
||||
|
||||
struct Status {
|
||||
std::vector<int> nozzle_temps;
|
||||
int bed_temp;
|
||||
int chamber_temp;
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ICloudServiceAgent;
|
||||
@@ -378,6 +343,16 @@ public:
|
||||
*/
|
||||
virtual bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) { return false; }
|
||||
|
||||
/**
|
||||
* Translate one filament id across the printer boundary.
|
||||
*
|
||||
* Orca content-addresses every system filament; a printer, its AMS and its vendor cloud
|
||||
* know only that vendor's own catalog ids. An agent whose printers already speak Orca's
|
||||
* ids leaves them alone, and so does an id with no mapping.
|
||||
*/
|
||||
virtual std::string to_orca_filament_id(const std::string& printer_filament_id) const { return printer_filament_id; }
|
||||
virtual std::string from_orca_filament_id(const std::string& orca_filament_id) const { return orca_filament_id; }
|
||||
|
||||
/**
|
||||
* Get the current camera stream URL for this agent's active machine.
|
||||
* Only meaningful when get_camera_stream_mode() returns an HTTP or RTSP mode.
|
||||
|
||||
@@ -57,7 +57,7 @@ void set_miniaturizable(void * window)
|
||||
while(viewObject = (NSView *)[viewEnum nextObject]) {
|
||||
if([viewObject class] == [NSTextField self]) {
|
||||
//[(NSTextField*)viewObject setTextColor : NSColor.whiteColor];
|
||||
mainframe_text_field = viewObject;
|
||||
mainframe_text_field = (NSTextField*)viewObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ void set_title_colour_after_set_title(void * window)
|
||||
while(viewObject = (NSView *)[viewEnum nextObject]) {
|
||||
if([viewObject class] == [NSTextField self]) {
|
||||
[(NSTextField*)viewObject setTextColor : NSColor.whiteColor];
|
||||
mainframe_text_field = viewObject;
|
||||
mainframe_text_field = (NSTextField*)viewObject;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
|
||||
#include "slic3r/GUI/DeviceCore/DevManager.h"
|
||||
#include "../GUI/DeviceCore/DevStorage.h"
|
||||
@@ -21,6 +22,7 @@
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cctype>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
@@ -815,51 +817,70 @@ std::string MoonrakerPrinterAgent::map_filament_type_to_generic_id(const std::st
|
||||
{
|
||||
const std::string upper = trim_and_upper(filament_type);
|
||||
|
||||
// Map to OrcaFilamentLibrary preset IDs (compatible with all printers)
|
||||
// Source: resources/profiles/OrcaFilamentLibrary/filament/
|
||||
// Normalize reported material names (trimmed, uppercased) to an OrcaFilamentLibrary
|
||||
// generic family. The family's filament_id is resolved from the loaded system presets
|
||||
// below rather than hardcoded, so profile id re-mints never require touching this table.
|
||||
// scripts/test_moonraker_lane_data.py parses this initializer; keep the {"A", "B"} format.
|
||||
static const std::map<std::string, std::string> type_to_ofl_family = {
|
||||
// PLA variants
|
||||
{"PLA", "PLA"},
|
||||
{"PLA-CF", "PLA-CF"},
|
||||
{"PLA SILK", "PLA Silk"},
|
||||
{"PLA-SILK", "PLA Silk"},
|
||||
{"PLA HIGH SPEED", "PLA High Speed"},
|
||||
{"PLA-HS", "PLA High Speed"},
|
||||
{"PLA HS", "PLA High Speed"},
|
||||
|
||||
// PLA variants
|
||||
if (upper == "PLA") return "OGFL99";
|
||||
if (upper == "PLA-CF") return "OGFL98";
|
||||
if (upper == "PLA SILK" || upper == "PLA-SILK") return "OGFL96";
|
||||
if (upper == "PLA HIGH SPEED" || upper == "PLA-HS" || upper == "PLA HS") return "OGFL95";
|
||||
// ABS/ASA variants
|
||||
{"ABS", "ABS"},
|
||||
{"ASA", "ASA"},
|
||||
|
||||
// ABS/ASA variants
|
||||
if (upper == "ABS") return "OGFB99";
|
||||
if (upper == "ASA") return "OGFB98";
|
||||
// PETG/PET variants
|
||||
{"PETG", "PETG"},
|
||||
{"PET", "PETG"},
|
||||
{"PCTG", "PCTG"},
|
||||
|
||||
// PETG/PET variants
|
||||
if (upper == "PETG" || upper == "PET") return "OGFG99";
|
||||
if (upper == "PCTG") return "OGFG97";
|
||||
// PA/Nylon variants
|
||||
{"PA", "PA"},
|
||||
{"NYLON", "PA"},
|
||||
{"PA-CF", "PA-CF"},
|
||||
{"PPA", "PPA-CF"},
|
||||
{"PPA-CF", "PPA-CF"},
|
||||
{"PPA-GF", "PPA-GF"},
|
||||
|
||||
// PA/Nylon variants
|
||||
if (upper == "PA" || upper == "NYLON") return "OGFN99";
|
||||
if (upper == "PA-CF") return "OGFN98";
|
||||
if (upper == "PPA" || upper == "PPA-CF") return "OGFN97";
|
||||
if (upper == "PPA-GF") return "OGFN96";
|
||||
// PC variants
|
||||
{"PC", "PC"},
|
||||
|
||||
// PC variants
|
||||
if (upper == "PC") return "OGFC99";
|
||||
// PP/PE variants
|
||||
{"PE", "PE"},
|
||||
{"PP", "PP"},
|
||||
|
||||
// PP/PE variants
|
||||
if (upper == "PE") return "OGFP99";
|
||||
if (upper == "PP") return "OGFP97";
|
||||
// Support materials
|
||||
{"PVA", "PVA"},
|
||||
{"HIPS", "HIPS"},
|
||||
{"BVOH", "BVOH"},
|
||||
|
||||
// Support materials
|
||||
if (upper == "PVA") return "OGFS99";
|
||||
if (upper == "HIPS") return "OGFS98";
|
||||
if (upper == "BVOH") return "OGFS97";
|
||||
// TPU variants
|
||||
{"TPU", "TPU"},
|
||||
|
||||
// TPU variants
|
||||
if (upper == "TPU") return "OGFU99";
|
||||
// Other materials
|
||||
{"EVA", "EVA"},
|
||||
{"PHA", "PHA"},
|
||||
{"COPE", "CoPE"},
|
||||
{"SBS", "SBS"},
|
||||
};
|
||||
|
||||
// Other materials
|
||||
if (upper == "EVA") return "OGFR99";
|
||||
if (upper == "PHA") return "OGFR98";
|
||||
if (upper == "COPE") return "OGFLC99";
|
||||
if (upper == "SBS") return "OFLSBS99";
|
||||
auto it = type_to_ofl_family.find(upper);
|
||||
if (it == type_to_ofl_family.end())
|
||||
return UNKNOWN_FILAMENT_ID;
|
||||
|
||||
// Unknown material
|
||||
if (auto* bundle = GUI::wxGetApp().preset_bundle) {
|
||||
const Preset* preset = bundle->filaments.find_preset("Generic " + it->second + " @System");
|
||||
if (preset != nullptr && preset->is_system && !preset->filament_id.empty())
|
||||
return preset->filament_id;
|
||||
}
|
||||
|
||||
// Unknown material, or no loaded preset data to resolve against
|
||||
return UNKNOWN_FILAMENT_ID;
|
||||
}
|
||||
|
||||
|
||||
@@ -1026,6 +1026,20 @@ bool NetworkAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode sync
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string NetworkAgent::to_orca_filament_id(const std::string& printer_filament_id) const
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->to_orca_filament_id(printer_filament_id);
|
||||
return printer_filament_id;
|
||||
}
|
||||
|
||||
std::string NetworkAgent::from_orca_filament_id(const std::string& orca_filament_id) const
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->from_orca_filament_id(orca_filament_id);
|
||||
return orca_filament_id;
|
||||
}
|
||||
|
||||
CameraStreamMode NetworkAgent::get_camera_stream_mode() const
|
||||
{
|
||||
if (m_printer_agent)
|
||||
|
||||
@@ -184,6 +184,8 @@ public:
|
||||
CameraStreamMode get_camera_stream_mode() const;
|
||||
std::string get_local_camera_stream_url() const;
|
||||
std::unique_ptr<ICameraSignalingChannel> create_camera_signaling_channel(const std::string& dev_id);
|
||||
std::string to_orca_filament_id(const std::string& printer_filament_id) const;
|
||||
std::string from_orca_filament_id(const std::string& orca_filament_id) const;
|
||||
int request_bind_ticket(std::string* ticket);
|
||||
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <iostream>
|
||||
#include <libslic3r/Platform.hpp>
|
||||
#include <memory>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/rand.h>
|
||||
@@ -869,9 +870,11 @@ std::string OrcaCloudServiceAgent::build_login_cmd()
|
||||
display_name = "unknown name";
|
||||
}
|
||||
json cmd;
|
||||
cmd["command"] = "orca_userlogin";
|
||||
cmd["data"]["name"] = display_name;
|
||||
cmd["data"]["avatar"] = get_user_avatar();
|
||||
cmd["command"] = "orca_userlogin";
|
||||
cmd["data"]["name"] = display_name;
|
||||
cmd["data"]["avatar"] = get_user_avatar();
|
||||
// The unique handle, shown under the display name in the homepage account menu.
|
||||
cmd["data"]["account"] = get_user_name();
|
||||
return cmd.dump();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <ostream>
|
||||
#include <utility>
|
||||
@@ -29,6 +30,7 @@
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/PresetCacheFormat.hpp"
|
||||
#include "libslic3r_version.h"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
@@ -40,6 +42,7 @@
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
#include "slic3r/Utils/Http.hpp"
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
#include "slic3r/Config/Version.hpp"
|
||||
#include "slic3r/Config/Snapshot.hpp"
|
||||
#include "slic3r/GUI/MarkdownTip.hpp"
|
||||
@@ -96,6 +99,8 @@ struct Update
|
||||
bool forced_update;
|
||||
//BBS: add directory support
|
||||
bool is_directory {false};
|
||||
// Orca: a vendor update may be the cache-only form.
|
||||
bool is_opc {false};
|
||||
|
||||
Update() {}
|
||||
//BBS: add directory support
|
||||
@@ -126,13 +131,24 @@ struct Update
|
||||
//BBS: add directory support
|
||||
void install() const
|
||||
{
|
||||
if (is_directory) {
|
||||
if (is_directory) {
|
||||
copy_directory_recursively(source, target, file_filter);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
copy_file_fix(source, target);
|
||||
|
||||
// A vendor must be installed in exactly one form. Remove the
|
||||
// representation that would otherwise be stale or shadow this one.
|
||||
boost::system::error_code ec;
|
||||
if (is_opc) {
|
||||
fs::remove(target.parent_path() / (vendor + ".json"), ec);
|
||||
ec.clear();
|
||||
fs::remove_all(target.parent_path() / vendor, ec);
|
||||
}
|
||||
else {
|
||||
fs::remove(target.parent_path() / (vendor + ".opc"), ec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const Update &self)
|
||||
{
|
||||
@@ -180,6 +196,8 @@ struct Updates
|
||||
std::vector<Update> updates;
|
||||
};
|
||||
|
||||
static bool reload_configs_update_gui();
|
||||
|
||||
|
||||
wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
|
||||
@@ -207,6 +225,10 @@ struct PresetUpdater::priv
|
||||
|
||||
// Per-vendor update checking
|
||||
std::set<std::string> checked_vendors;
|
||||
// Orca (PR #130): changelog text for each vendor, captured in memory during
|
||||
// sync_vendor_config()/check_new_vendors() instead of written beside the cache.
|
||||
std::unordered_map<std::string, std::string> vendor_changelogs;
|
||||
mutable std::mutex vendor_changelogs_mutex;
|
||||
std::vector<std::thread> vendor_check_threads;
|
||||
std::atomic<bool> vendor_check_cancel{false};
|
||||
|
||||
@@ -231,6 +253,8 @@ struct PresetUpdater::priv
|
||||
void parse_version_string(const std::string& body) const;
|
||||
void sync_resources(std::string http_url, std::map<std::string, Resource> &resources, bool check_patch = false, std::string current_version="", std::string changelog_file="");
|
||||
void sync_vendor_config(const std::string& vendor_id);
|
||||
void check_new_vendors(const std::set<std::string>& system_vendors,
|
||||
std::function<void(std::vector<std::string>, bool)> callback);
|
||||
void sync_tooltip(std::string http_url, std::string language);
|
||||
void sync_plugins(std::string http_url, std::string plugin_version);
|
||||
void sync_printer_config(std::string http_url);
|
||||
@@ -268,7 +292,7 @@ void PresetUpdater::priv::set_download_prefs(AppConfig *app_config)
|
||||
version_check_url = app_config->version_check_url();
|
||||
|
||||
auto profile_update_url = app_config->profile_update_url();
|
||||
if (!profile_update_url.empty())
|
||||
if (!profile_update_url.empty() && app_config->get_bool("enable_ota"))
|
||||
enabled_config_update = true;
|
||||
else
|
||||
enabled_config_update = false;
|
||||
@@ -671,6 +695,9 @@ void PresetUpdater::priv::sync_vendor_config(const std::string& vendor_id)
|
||||
|
||||
std::string online_version_str; // this represents the PROFILE VERSION, not ORCA VERSION
|
||||
std::string download_url_str;
|
||||
std::string changelog;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] fetching vendor update status from " << url;
|
||||
|
||||
Http::get(url)
|
||||
.timeout_connect(5)
|
||||
@@ -683,9 +710,12 @@ void PresetUpdater::priv::sync_vendor_config(const std::string& vendor_id)
|
||||
if (http_status != 200) return;
|
||||
try {
|
||||
json j = json::parse(body);
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] url: " << url << " returned:" << body;
|
||||
|
||||
if (j.contains("vendor_version") && j.contains("download_url")) {
|
||||
online_version_str = j["vendor_version"].get<std::string>();
|
||||
download_url_str = j["download_url"].get<std::string>();
|
||||
changelog = j.value("changelog", std::string());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] vendor check JSON parse failed: " << e.what();
|
||||
@@ -707,6 +737,10 @@ void PresetUpdater::priv::sync_vendor_config(const std::string& vendor_id)
|
||||
boost::system::error_code ec;
|
||||
fs::remove_all(cache_profile_path / vendor_id, ec);
|
||||
fs::remove(cache_profile_path / (vendor_id + ".json"), ec);
|
||||
// Orca: the OPC cache is the vendor's whole installation in one file; clear it too.
|
||||
fs::remove(cache_profile_path / (vendor_id + ".opc"), ec);
|
||||
// Best-effort cleanup of the legacy on-disk changelog written by older builds
|
||||
// (changelogs are now kept in memory - see vendor_changelogs).
|
||||
fs::remove(cache_profile_path / (vendor_id + ".changelog"), ec);
|
||||
|
||||
// Download the zip
|
||||
@@ -735,7 +769,7 @@ void PresetUpdater::priv::sync_vendor_config(const std::string& vendor_id)
|
||||
if (!download_ok || cancel || vendor_check_cancel) return;
|
||||
|
||||
// Extract vendor profile bundles under ota/profiles. The downloaded zip contains
|
||||
// the vendor json/folder at its root.
|
||||
// either the vendor json/folder or the vendor cache at its root.
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] extracting update for " << vendor_id;
|
||||
if (!extract_file(download_file, cache_profile_path)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] extraction failed for " << vendor_id;
|
||||
@@ -745,6 +779,27 @@ void PresetUpdater::priv::sync_vendor_config(const std::string& vendor_id)
|
||||
|
||||
if (cancel || vendor_check_cancel) return;
|
||||
|
||||
const fs::path cached_vendor_json = cache_profile_path / (vendor_id + ".json");
|
||||
const fs::path cached_vendor_folder = cache_profile_path / vendor_id;
|
||||
|
||||
const fs::path cached_vendor_opc = cache_profile_path / (vendor_id + ".opc");
|
||||
|
||||
bool is_json_update = fs::is_regular_file(cached_vendor_json) && fs::is_directory(cached_vendor_folder) && !fs::is_empty(cached_vendor_folder);
|
||||
bool is_opc_update = fs::is_regular_file(cached_vendor_opc);
|
||||
if (!is_json_update && !is_opc_update) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] rejected update for " << vendor_id
|
||||
<< ": expected " << vendor_id << ".json and a non-empty "
|
||||
<< vendor_id << " directory, or OPC update format.";
|
||||
fs::remove_all(cached_vendor_folder, ec);
|
||||
fs::remove(cached_vendor_json, ec);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(vendor_changelogs_mutex);
|
||||
vendor_changelogs[vendor_id] = std::move(changelog);
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] vendor " << vendor_id << " update cached, notifying UI";
|
||||
GUI::wxGetApp().CallAfter([] {
|
||||
GUI::wxGetApp().check_config_updates_from_updater();
|
||||
@@ -1039,6 +1094,7 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:Checking whether the profile from resource is newer";
|
||||
|
||||
AppConfig *app_config = GUI::wxGetApp().app_config;
|
||||
|
||||
const auto enabled_vendors = app_config->vendors();
|
||||
|
||||
std::set<std::string> bundles;
|
||||
@@ -1051,8 +1107,8 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const
|
||||
|
||||
const auto is_vendor_enabled = (vendor_name == PresetBundle::ORCA_DEFAULT_BUNDLE) // always update configs from resource to vendor for ORCA_DEFAULT_BUNDLE
|
||||
|| (enabled_vendors.find(vendor_name) != enabled_vendors.end());
|
||||
if (enabled_config_update) {
|
||||
if (is_vendor_installed(vendor_name)) {
|
||||
if (is_vendor_installed(vendor_name)) {
|
||||
if (enabled_config_update) {
|
||||
if (is_vendor_enabled) {
|
||||
// Orca: whichever form of the vendor resources ships at the newer
|
||||
// version is the one installing lays down, and the one to judge
|
||||
@@ -1067,17 +1123,12 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const
|
||||
<< resource_ver.to_string() << " from resource, old version " << vendor_ver.to_string();
|
||||
bundles.insert(vendor_name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
//need to be removed because not installed
|
||||
} else {
|
||||
// need to be removed because not installed
|
||||
remove_installed_vendor(vendor_name);
|
||||
}
|
||||
}
|
||||
else if (is_vendor_enabled) {
|
||||
bundles.insert(vendor_name);
|
||||
}
|
||||
}
|
||||
else if (is_vendor_enabled) {
|
||||
} else if (is_vendor_enabled) {
|
||||
bundles.insert(vendor_name);
|
||||
}
|
||||
}
|
||||
@@ -1147,68 +1198,110 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
|
||||
if (!fs::exists(cache_profile_path))
|
||||
return updates;
|
||||
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(cache_profile_path)) {
|
||||
const auto &path = dir_entry.path();
|
||||
std::string file_path = path.string();
|
||||
if (is_json_file(file_path)) {
|
||||
const auto path_in_vendor = vendor_path / path.filename();
|
||||
std::string vendor_name = path.filename().string();
|
||||
// Remove the .json suffix.
|
||||
vendor_name.erase(vendor_name.size() - 5);
|
||||
auto print_in_cache = (cache_profile_path / vendor_name / PRESET_PRINT_NAME);
|
||||
auto filament_in_cache = (cache_profile_path / vendor_name / PRESET_FILAMENT_NAME);
|
||||
auto machine_in_cache = (cache_profile_path / vendor_name / PRESET_PRINTER_NAME);
|
||||
// Orca (PR #130): vendor changelogs are captured in memory during
|
||||
// sync_vendor_config()/check_new_vendors(), not written beside the cache.
|
||||
std::unordered_map<std::string, std::string> changelogs;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(vendor_changelogs_mutex);
|
||||
changelogs = vendor_changelogs;
|
||||
}
|
||||
|
||||
if (is_vendor_installed(vendor_name)
|
||||
|| fs::exists(print_in_cache)
|
||||
|| fs::exists(filament_in_cache)
|
||||
|| fs::exists(machine_in_cache)) {
|
||||
// Orca: a vendor installed as a preset cache carries its version there.
|
||||
Semver vendor_ver = installed_vendor_version(vendor_name);
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(cache_profile_path)) {
|
||||
const auto &path = dir_entry.path();
|
||||
std::string file_path = path.string();
|
||||
const bool is_opc_file = boost::iequals(path.extension().string(), ".opc");
|
||||
if (!is_json_file(file_path) && !is_opc_file)
|
||||
continue;
|
||||
|
||||
std::map<std::string, std::string> key_values;
|
||||
std::vector<std::string> keys(3);
|
||||
Semver cache_ver;
|
||||
keys[0] = BBL_JSON_KEY_VERSION;
|
||||
keys[1] = BBL_JSON_KEY_DESCRIPTION;
|
||||
keys[2] = BBL_JSON_KEY_FORCE_UPDATE;
|
||||
get_values_from_json(file_path, keys, key_values);
|
||||
std::string description = key_values[BBL_JSON_KEY_DESCRIPTION];
|
||||
bool force_update = false;
|
||||
if (key_values.find(BBL_JSON_KEY_FORCE_UPDATE) != key_values.end())
|
||||
force_update = (key_values[BBL_JSON_KEY_FORCE_UPDATE] == "1")?true:false;
|
||||
auto config_version = Semver::parse(key_values[BBL_JSON_KEY_VERSION]);
|
||||
if (config_version)
|
||||
cache_ver = *config_version;
|
||||
const std::string vendor_name = path.stem().string();
|
||||
auto print_in_cache = (cache_profile_path / vendor_name / PRESET_PRINT_NAME);
|
||||
auto filament_in_cache = (cache_profile_path / vendor_name / PRESET_FILAMENT_NAME);
|
||||
auto machine_in_cache = (cache_profile_path / vendor_name / PRESET_PRINTER_NAME);
|
||||
|
||||
std::string changelog;
|
||||
std::string changelog_file = (cache_profile_path / (vendor_name + ".changelog")).string();
|
||||
boost::nowide::ifstream ifs(changelog_file);
|
||||
if (ifs) {
|
||||
std::ostringstream oss;
|
||||
oss<< ifs.rdbuf();
|
||||
changelog = oss.str();
|
||||
ifs.close();
|
||||
}
|
||||
// Orca (PR #130): a JSON cache entry is only meaningful next to a non-empty
|
||||
// <vendor>/ preset directory; a stray or half-downloaded <vendor>.json is
|
||||
// skipped. An .opc cache is a single self-contained file (validated below),
|
||||
// so this check does not apply to it.
|
||||
if (!is_opc_file) {
|
||||
const auto vendor_folder_in_cache = cache_profile_path / vendor_name;
|
||||
if (!fs::is_regular_file(path) || !fs::is_directory(vendor_folder_in_cache) ||
|
||||
fs::is_empty(vendor_folder_in_cache)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]:ignoring invalid cached update for "
|
||||
<< vendor_name << ": expected " << vendor_name
|
||||
<< ".json and a non-empty " << vendor_name << " directory";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (vendor_ver < cache_ver) {
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:need to update settings from " << vendor_ver.to_string()
|
||||
<< " to newer version " << cache_ver.to_string() << ", app version " << SLIC3R_VERSION;
|
||||
Version version;
|
||||
version.config_version = cache_ver;
|
||||
version.comment = description;
|
||||
// Orca: update vendor.json
|
||||
updates.updates.emplace_back(std::move(file_path), path_in_vendor.string(), std::move(version), vendor_name, changelog, "", force_update, false);
|
||||
//Orca: update vendor folder
|
||||
updates.updates.emplace_back(cache_profile_path / vendor_name, vendor_path / vendor_name, Version(), vendor_name, "", "", force_update, true);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:cached settings for " << vendor_name
|
||||
<< " are not newer than installed version, installed " << vendor_ver.to_string()
|
||||
<< ", cached " << cache_ver.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_vendor_installed(vendor_name)
|
||||
|| is_opc_file
|
||||
|| fs::exists(print_in_cache)
|
||||
|| fs::exists(filament_in_cache)
|
||||
|| fs::exists(machine_in_cache)) {
|
||||
// Orca: a vendor installed as a preset cache carries its version there.
|
||||
Semver vendor_ver = installed_vendor_version(vendor_name);
|
||||
|
||||
Semver cache_ver;
|
||||
std::string description;
|
||||
bool force_update = false;
|
||||
if (is_opc_file) {
|
||||
cache_ver = VendorCacheFile::usable_version(file_path, vendor_name);
|
||||
if (!cache_ver.valid()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]:ignoring unreadable vendor cache " << file_path;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::map<std::string, std::string> key_values;
|
||||
std::vector<std::string> keys(3);
|
||||
keys[0] = BBL_JSON_KEY_VERSION;
|
||||
keys[1] = BBL_JSON_KEY_DESCRIPTION;
|
||||
keys[2] = BBL_JSON_KEY_FORCE_UPDATE;
|
||||
get_values_from_json(file_path, keys, key_values);
|
||||
description = key_values[BBL_JSON_KEY_DESCRIPTION];
|
||||
if (key_values.find(BBL_JSON_KEY_FORCE_UPDATE) != key_values.end())
|
||||
force_update = (key_values[BBL_JSON_KEY_FORCE_UPDATE] == "1")?true:false;
|
||||
auto config_version = Semver::parse(key_values[BBL_JSON_KEY_VERSION]);
|
||||
if (config_version)
|
||||
cache_ver = *config_version;
|
||||
}
|
||||
|
||||
// Orca (PR #130): changelog for this vendor was captured in memory at sync time.
|
||||
const auto changelog_it = changelogs.find(vendor_name);
|
||||
std::string changelog = changelog_it != changelogs.end() ? changelog_it->second : std::string();
|
||||
|
||||
if (vendor_ver < cache_ver) {
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:need to update settings from " << vendor_ver.to_string()
|
||||
<< " to newer version " << cache_ver.to_string() << ", app version " << SLIC3R_VERSION;
|
||||
Version version;
|
||||
version.config_version = cache_ver;
|
||||
version.comment = description;
|
||||
if (is_opc_file) {
|
||||
// A cache contains the vendor profile and all presets.
|
||||
// Install it directly; Update::install removes any
|
||||
// superseded JSON representation.
|
||||
auto &update = updates.updates.emplace_back(
|
||||
std::move(file_path), vendor_path / (vendor_name + ".opc"),
|
||||
std::move(version), vendor_name, changelog, "", force_update, false);
|
||||
update.is_opc = true;
|
||||
}
|
||||
else {
|
||||
// JSON profile and its preset directory are installed
|
||||
// separately, as before.
|
||||
updates.updates.emplace_back(std::move(file_path),
|
||||
vendor_path / (vendor_name + ".json"), std::move(version),
|
||||
vendor_name, changelog, "", force_update, false);
|
||||
updates.updates.emplace_back(cache_profile_path / vendor_name,
|
||||
vendor_path / vendor_name, Version(), vendor_name,
|
||||
"", "", force_update, true);
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:cached settings for " << vendor_name
|
||||
<< " are not newer than installed version, installed " << vendor_ver.to_string()
|
||||
<< ", cached " << cache_ver.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updates;
|
||||
}
|
||||
@@ -1317,6 +1410,9 @@ void PresetUpdater::sync(std::string http_url, std::string language, std::string
|
||||
// after the startup printer preset has been restored.
|
||||
this->p->sync_plugins(http_url, plugin_version);
|
||||
this->p->sync_printer_config(http_url);
|
||||
// Orca (PR #130): the filament library is always installed, so refresh it
|
||||
// from the updater on every startup sync rather than deferring to check_vendor_update().
|
||||
this->p->sync_vendor_config(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
||||
//if (p->cancel)
|
||||
// return;
|
||||
//remove the tooltip currently
|
||||
@@ -1349,6 +1445,302 @@ void PresetUpdater::check_vendor_update(const std::string& vendor_id)
|
||||
});
|
||||
}
|
||||
|
||||
// Orca: ask the server which vendors from `system_vendors` have a profile bundle available that
|
||||
// isn't installed yet (or is newer than what's installed). Request body maps vendor id -> currently
|
||||
// installed profile version (unknown/not-yet-installed vendors report "0.0.0"). Response maps
|
||||
// vendor id -> {version, download_url, changelog} for each vendor the server has an update for.
|
||||
// Any such vendor is downloaded and cached under ota/profiles the same way sync_vendor_config()
|
||||
// does; the caller's check_config_updates_from_updater() -> get_config_updates()/perform_updates()
|
||||
// flow then installs the cached profiles into data_dir()/system.
|
||||
//
|
||||
// Mirrors check_vendor_update()/sync_vendor_config(): the network query and the download/extract
|
||||
// work run on a background thread (vendor_check_threads), never on the calling (UI) thread. Only
|
||||
// the confirmation dialog (which must run on the UI thread) and the final callback are marshaled
|
||||
// back via CallAfter().
|
||||
void PresetUpdater::priv::check_new_vendors(const std::set<std::string>& system_vendors,
|
||||
std::function<void(std::vector<std::string>, bool)> callback)
|
||||
{
|
||||
vendor_check_threads.emplace_back([this, system_vendors, callback]() {
|
||||
AppConfig* app_config = GUI::wxGetApp().app_config;
|
||||
std::string url = app_config->profile_update_url() + "/new?orcaslicer_version=" + Http::url_encode(SoftFever_VERSION);
|
||||
|
||||
auto check_cancel = [this](Http::Progress, bool& cancel_http) {
|
||||
if (cancel || vendor_check_cancel)
|
||||
cancel_http = true;
|
||||
};
|
||||
|
||||
json request_body = json::object();
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] checking new vendors for:";
|
||||
for (const auto& vendor_id : system_vendors) {
|
||||
// Orca: installed_vendor_version() reads whichever form the vendor is
|
||||
// installed as - the .json profile or the .opc preset cache stamp -
|
||||
// so a cache-only vendor is not reported as version 0.0.0 and then
|
||||
// endlessly re-offered by the server.
|
||||
Semver installed_ver = installed_vendor_version(vendor_id);
|
||||
request_body[vendor_id] = installed_ver.to_string();
|
||||
BOOST_LOG_TRIVIAL(info) << vendor_id << " (installed version " << installed_ver.to_string() << ")";
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] new vendor check request url: " << url;
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] new vendor check request body: " << request_body.dump(2);
|
||||
|
||||
json response_json;
|
||||
bool got_response = false;
|
||||
|
||||
auto post = Http::post(url);
|
||||
|
||||
post.timeout_connect(5);
|
||||
post.on_progress(check_cancel);
|
||||
post.header("Content-Type", "application/json");
|
||||
post.on_error([](std::string body, std::string error, unsigned http_status) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] new vendor check HTTP error: " << error;
|
||||
})
|
||||
.on_complete([&response_json, &got_response](std::string body, unsigned http_status) {
|
||||
if (http_status != 200)
|
||||
return;
|
||||
try {
|
||||
json j = json::parse(body);
|
||||
if (j.is_object()) {
|
||||
response_json = std::move(j);
|
||||
got_response = true;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] new vendor check JSON parse failed: " << e.what();
|
||||
}
|
||||
});
|
||||
|
||||
post.set_post_body(request_body.dump());
|
||||
post.perform_sync();
|
||||
|
||||
if (cancel || vendor_check_cancel)
|
||||
return;
|
||||
|
||||
if (!got_response) {
|
||||
GUI::wxGetApp().CallAfter([callback]() { callback({}, false); });
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect candidates before touching the filesystem or network again.
|
||||
struct NewVendorCandidate
|
||||
{
|
||||
std::string vendor_id;
|
||||
Semver version;
|
||||
std::string download_url;
|
||||
std::string changelog;
|
||||
};
|
||||
std::vector<NewVendorCandidate> candidates;
|
||||
for (auto it = response_json.begin(); it != response_json.end(); ++it) {
|
||||
const json& entry = it.value();
|
||||
if (!entry.is_object())
|
||||
continue;
|
||||
std::string download_url_str = entry.value("download_url", std::string());
|
||||
if (download_url_str.empty())
|
||||
continue;
|
||||
|
||||
NewVendorCandidate candidate;
|
||||
candidate.vendor_id = it.key();
|
||||
auto parsed_ver = Semver::parse(entry.value("version", std::string()));
|
||||
candidate.version = parsed_ver ? *parsed_ver : Semver();
|
||||
candidate.download_url = std::move(download_url_str);
|
||||
candidate.changelog = entry.value("changelog", std::string());
|
||||
candidates.push_back(std::move(candidate));
|
||||
}
|
||||
|
||||
if (candidates.empty()) {
|
||||
GUI::wxGetApp().CallAfter([callback]() { callback({}, false); });
|
||||
return;
|
||||
}
|
||||
|
||||
// Orca: the confirmation dialog must run on the UI thread; if confirmed, the actual
|
||||
// download/install work is dispatched back onto a new background thread from there,
|
||||
// same as check_vendor_update() does for a single vendor.
|
||||
GUI::wxGetApp().CallAfter([this, candidates, callback]() {
|
||||
std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
|
||||
for (const auto& candidate : candidates)
|
||||
updates_msg.emplace_back(candidate.vendor_id, candidate.version, std::string(), candidate.changelog);
|
||||
|
||||
GUI::MsgUpdateConfig dlg(updates_msg);
|
||||
if (dlg.ShowModal() != wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] user declined installing new vendors";
|
||||
callback({}, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Orca: the actual download runs on a background thread below (so it doesn't block the
|
||||
// UI), but that also means nothing visibly happens for the several seconds it can take
|
||||
// (longer still if a retry kicks in) — push a notification so it's clear work is
|
||||
// ongoing rather than looking hung.
|
||||
{
|
||||
std::string vendor_list;
|
||||
for (const auto& candidate : candidates) {
|
||||
if (!vendor_list.empty())
|
||||
vendor_list += ", ";
|
||||
vendor_list += candidate.vendor_id;
|
||||
}
|
||||
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(
|
||||
_u8L("Downloading new vendor profile(s): ") + vendor_list + _u8L("..."));
|
||||
}
|
||||
|
||||
vendor_check_threads.emplace_back([this, candidates, callback]() {
|
||||
auto check_cancel = [this](Http::Progress, bool& cancel_http) {
|
||||
if (cancel || vendor_check_cancel)
|
||||
cancel_http = true;
|
||||
};
|
||||
|
||||
std::vector<std::string> new_vendor_ids;
|
||||
std::vector<std::string> failed_vendor_ids;
|
||||
auto cache_profile_path = cache_path / "profiles";
|
||||
fs::create_directories(cache_profile_path);
|
||||
boost::system::error_code ec;
|
||||
|
||||
for (const auto& candidate : candidates) {
|
||||
if (cancel || vendor_check_cancel)
|
||||
break;
|
||||
|
||||
const std::string& vendor_id = candidate.vendor_id;
|
||||
const std::string& download_url_str = candidate.download_url;
|
||||
std::string changelog = candidate.changelog;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] downloading new vendor " << vendor_id << " version " << candidate.version.to_string();
|
||||
|
||||
// Clear only this vendor's cached data, same as sync_vendor_config().
|
||||
fs::remove_all(cache_profile_path / vendor_id, ec);
|
||||
fs::remove(cache_profile_path / (vendor_id + ".json"), ec);
|
||||
|
||||
fs::path download_file = cache_path / (vendor_id + TMP_EXTENSION);
|
||||
bool download_ok = false;
|
||||
|
||||
// Orca: same retry pattern as Plater.cpp's project download — a single-shot
|
||||
// 5s connect timeout against GitHub's redirect chain is prone to transient
|
||||
// failures (DNS/connect hiccups) that succeed a moment later, so retry a few
|
||||
// times before giving up rather than failing the whole vendor on one blip.
|
||||
int retry_count = 0;
|
||||
const int max_retries = 3;
|
||||
bool keep_trying = true;
|
||||
while (keep_trying && retry_count < max_retries) {
|
||||
retry_count++;
|
||||
Http::get(download_url_str)
|
||||
.timeout_connect(5)
|
||||
.on_progress(check_cancel)
|
||||
.on_error([&vendor_id, &retry_count](std::string body, std::string error, unsigned http_status) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] download failed for new vendor " << vendor_id
|
||||
<< " (attempt " << retry_count << "/" << max_retries << "): " << error;
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned http_status) {
|
||||
if (http_status != 200)
|
||||
return;
|
||||
fs::fstream file(download_file, std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
if (!file.good())
|
||||
return;
|
||||
file.write(body.c_str(), body.size());
|
||||
file.close();
|
||||
if (file.good())
|
||||
download_ok = true;
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
keep_trying = !download_ok && !(cancel || vendor_check_cancel);
|
||||
}
|
||||
|
||||
if (!download_ok || cancel || vendor_check_cancel) {
|
||||
if (!download_ok)
|
||||
failed_vendor_ids.push_back(vendor_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] extracting new vendor " << vendor_id;
|
||||
if (!extract_file(download_file, cache_profile_path)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] extraction failed for new vendor " << vendor_id;
|
||||
fs::remove(download_file, ec);
|
||||
failed_vendor_ids.push_back(vendor_id);
|
||||
continue;
|
||||
}
|
||||
fs::remove(download_file, ec);
|
||||
|
||||
const fs::path cached_vendor_json = cache_profile_path / (vendor_id + ".json");
|
||||
const fs::path cached_vendor_folder = cache_profile_path / vendor_id;
|
||||
if (!fs::is_regular_file(cached_vendor_json) || !fs::is_directory(cached_vendor_folder) ||
|
||||
fs::is_empty(cached_vendor_folder)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] rejected new vendor " << vendor_id << ": expected " << vendor_id
|
||||
<< ".json and a non-empty " << vendor_id << " directory";
|
||||
fs::remove_all(cached_vendor_folder, ec);
|
||||
fs::remove(cached_vendor_json, ec);
|
||||
failed_vendor_ids.push_back(vendor_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(vendor_changelogs_mutex);
|
||||
vendor_changelogs[vendor_id] = std::move(changelog);
|
||||
}
|
||||
|
||||
new_vendor_ids.push_back(vendor_id);
|
||||
}
|
||||
|
||||
if (!new_vendor_ids.empty()) {
|
||||
// Orca: the user already confirmed via the dialog above, so install right away
|
||||
// instead of routing through check_config_updates_from_updater(), which only
|
||||
// queues a passive notification (meant for the silent background per-vendor
|
||||
// check) requiring yet another click + confirmation before anything is copied
|
||||
// into data_dir()/system.
|
||||
GUI::wxGetApp().CallAfter([this, new_vendor_ids] {
|
||||
AppConfig* app_config = GUI::wxGetApp().app_config;
|
||||
Updates updates = get_config_updates(app_config->orig_version());
|
||||
|
||||
// Only install the vendors just confirmed; leave any other unrelated
|
||||
// pending cached update (from a background sync_vendor_config()) alone,
|
||||
// still gated behind its own notification/confirmation.
|
||||
std::set<std::string> confirmed(new_vendor_ids.begin(), new_vendor_ids.end());
|
||||
Updates filtered;
|
||||
for (auto& update : updates.updates)
|
||||
if (confirmed.count(update.vendor))
|
||||
filtered.updates.push_back(std::move(update));
|
||||
|
||||
if (filtered.updates.empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] new vendors cached but no updates detected";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!perform_updates(std::move(filtered)) || !reload_configs_update_gui()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] failed to install new vendors";
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] new vendors installed";
|
||||
for (const auto& vendor_id : new_vendor_ids) {
|
||||
Semver cur_ver = GUI::wxGetApp().preset_bundle->get_vendor_profile_version(vendor_id);
|
||||
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(
|
||||
GUI::NotificationType::PresetUpdateFinished,
|
||||
GUI::NotificationManager::NotificationLevel::ImportantNotificationLevel,
|
||||
_u8L("Configuration package: ") + vendor_id + _u8L(" updated to ") + cur_ver.to_string());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!failed_vendor_ids.empty()) {
|
||||
GUI::wxGetApp().CallAfter([failed_vendor_ids] {
|
||||
std::string vendor_list;
|
||||
for (const auto& vendor_id : failed_vendor_ids) {
|
||||
if (!vendor_list.empty())
|
||||
vendor_list += ", ";
|
||||
vendor_list += vendor_id;
|
||||
}
|
||||
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(
|
||||
_u8L("Failed to download vendor profile(s): ") + vendor_list);
|
||||
});
|
||||
}
|
||||
|
||||
GUI::wxGetApp().CallAfter([callback, new_vendor_ids]() { callback(new_vendor_ids, false); });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void PresetUpdater::check_new_vendors(const std::set<std::string>& system_vendors,
|
||||
std::function<void(std::vector<std::string>, bool)> callback)
|
||||
{
|
||||
p->check_new_vendors(system_vendors, std::move(callback));
|
||||
}
|
||||
|
||||
void PresetUpdater::slic3r_update_notify()
|
||||
{
|
||||
if (! p->enabled_version_check)
|
||||
@@ -1370,10 +1762,9 @@ static bool reload_configs_update_gui()
|
||||
GUI::wxGetApp().load_current_presets();
|
||||
GUI::wxGetApp().plater()->set_bed_shape();
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, UpdateParams params) const
|
||||
{
|
||||
if (! p->enabled_config_update) { return R_NOOP; }
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#ifndef slic3r_PresetUpdate_hpp_
|
||||
#define slic3r_PresetUpdate_hpp_
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <wx/event.h>
|
||||
@@ -59,6 +61,12 @@ public:
|
||||
void on_update_notification_confirm();
|
||||
void do_printer_config_update();
|
||||
void check_vendor_update(const std::string& vendor_id);
|
||||
// Orca: async, mirrors check_vendor_update()/sync_vendor_config() — the network query and any
|
||||
// download/install work happen on a background thread; only the confirmation dialog runs on
|
||||
// the UI thread. `callback` is invoked on the UI thread with the ids of vendors that were
|
||||
// installed (empty if none were found, or the user declined) and whether the user declined.
|
||||
void check_new_vendors(const std::set<std::string>& system_vendors,
|
||||
std::function<void(std::vector<std::string> installed_vendors, bool declined)> callback);
|
||||
|
||||
bool version_check_enabled() const;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <boost/process/args.hpp>
|
||||
#endif
|
||||
|
||||
#include <wx/filename.h>
|
||||
#include <wx/stdpaths.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -331,7 +331,9 @@ void Serial::set_baud_rate(unsigned baud_rate)
|
||||
speed_t c_ispeed;
|
||||
speed_t c_ospeed;
|
||||
};
|
||||
#ifndef BOTHER
|
||||
#define BOTHER CBAUDEX
|
||||
#endif
|
||||
|
||||
termios2 ios;
|
||||
handle_errno(::ioctl(handle, TCGETS2, &ios));
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
@@ -50,7 +52,7 @@ std::string find_closest_color_preset_by_vendor_and_type(const PresetCollection&
|
||||
std::string p_color = p.config.opt_string("default_filament_colour", 0u);
|
||||
unsigned int p_color_value;
|
||||
if (!p_color.empty()) {
|
||||
unsigned int hash_pos = p_color.find("#");
|
||||
size_t hash_pos = p_color.find("#");
|
||||
p_color_value = std::stoul(p_color.substr(hash_pos != std::string::npos ? hash_pos + 1 : 0), nullptr, 16);
|
||||
} else {
|
||||
// Default to black if no color specified in profile. Assume other profiles might be a closer color match.
|
||||
|
||||
Reference in New Issue
Block a user