mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-28 03:11:47 +00:00
Merge branch 'main' into feat/plugin-storage-api
This commit is contained in:
@@ -620,6 +620,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
plugin/host/PluginHostSlicing.cpp
|
||||
plugin/host/PluginHostUi.cpp
|
||||
plugin/host/PluginHostUi.hpp
|
||||
plugin/host/PluginPages.cpp
|
||||
plugin/host/PluginPages.hpp
|
||||
plugin/CloudPluginService.cpp
|
||||
plugin/CloudPluginService.hpp
|
||||
plugin/PluginFsUtils.cpp
|
||||
@@ -640,6 +642,9 @@ set(SLIC3R_GUI_SOURCES
|
||||
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp
|
||||
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp
|
||||
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapabilityTrampoline.hpp
|
||||
plugin/pluginTypes/pages/PagesPluginCapability.hpp
|
||||
plugin/pluginTypes/pages/PagesPluginCapability.cpp
|
||||
plugin/pluginTypes/pages/PagesPluginCapabilityTrampoline.hpp
|
||||
plugin/pluginTypes/script/ScriptPluginCapability.hpp
|
||||
plugin/pluginTypes/script/ScriptPluginCapability.cpp
|
||||
plugin/pluginTypes/script/ScriptPluginCapabilityTrampoline.hpp
|
||||
|
||||
@@ -432,14 +432,9 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
|
||||
cfg.models_variants_installed.erase(it ++);
|
||||
else
|
||||
++ it;
|
||||
// Read the active config bundle, parse the config version.
|
||||
PresetBundle bundle;
|
||||
//BBS: change directoties by design
|
||||
//bundle.load_configbundle((data_dir / PRESET_SYSTEM_DIR / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
bundle.load_vendor_configs_from_json((data_dir/PRESET_SYSTEM_DIR).string(), cfg.name, PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
for (const auto &vp : bundle.vendors)
|
||||
if (vp.second.id == cfg.name)
|
||||
cfg.version.config_version = vp.second.config_version;
|
||||
// Orca: the version the vendor is installed at, read from its profile or —
|
||||
// where the cache is the whole installation — from the cache's own stamp.
|
||||
cfg.version.config_version = installed_vendor_version(cfg.name);
|
||||
snapshot.vendor_configs.emplace_back(std::move(cfg));
|
||||
}
|
||||
|
||||
|
||||
@@ -869,11 +869,11 @@ void AuxiliaryPanel::init_tabpanel()
|
||||
m_assembly_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::ASSEMBLY_GUIDE);
|
||||
m_others_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::OTHERS);
|
||||
|
||||
m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), "", true);
|
||||
m_tabpanel->AddPage(m_pictures_panel, _L("Pictures"), "", false);
|
||||
m_tabpanel->AddPage(m_bill_of_materials_panel, _L("Bill of Materials"), "", false);
|
||||
m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), "", false);
|
||||
m_tabpanel->AddPage(m_others_panel, _L("Others"), "", false);
|
||||
m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), true);
|
||||
m_tabpanel->AddPage(m_pictures_panel, _L("Pictures"), false);
|
||||
m_tabpanel->AddPage(m_bill_of_materials_panel, _L("Bill of Materials"), false);
|
||||
m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), false);
|
||||
m_tabpanel->AddPage(m_others_panel, _L("Others"), false);
|
||||
}
|
||||
|
||||
wxWindow *AuxiliaryPanel::create_side_tools()
|
||||
|
||||
@@ -488,7 +488,6 @@ void CalibrationPanel::init_tabpanel() {
|
||||
selected = true;
|
||||
m_tabpanel->AddPage(m_cali_panels[i],
|
||||
get_calibration_type_name(m_cali_panels[i]->get_calibration_mode()),
|
||||
"",
|
||||
selected);
|
||||
}
|
||||
|
||||
|
||||
@@ -752,7 +752,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
bool has_top_shell = has_top_shell_layers && config->option<ConfigOptionPercent>("top_surface_density")->value > 0;
|
||||
bool has_bottom_shell = config->opt_int("bottom_shell_layers") > 0;
|
||||
bool has_solid_infill = has_top_shell_layers || has_bottom_shell;
|
||||
toggle_line("sparse_infill_smooth_factor", pattern == ipHilbertCurve);
|
||||
toggle_line("sparse_infill_smooth_factor", is_smoothable_infill_pattern(pattern, config->opt_int("fill_multiline")));
|
||||
toggle_field("top_surface_pattern", has_top_shell);
|
||||
toggle_field("bottom_surface_pattern", has_bottom_shell);
|
||||
toggle_field("top_surface_density", has_top_shell_layers);
|
||||
|
||||
@@ -66,41 +66,41 @@ using Config::SnapshotDB;
|
||||
|
||||
// Configuration data structures extensions needed for the wizard
|
||||
//BBS: set BBL as default
|
||||
bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_bbl_bundle)
|
||||
bool Bundle::load(fs::path dir, const std::string &vendor_name, bool ais_in_resources, bool ais_bbl_bundle)
|
||||
{
|
||||
this->preset_bundle = std::make_unique<PresetBundle>();
|
||||
this->is_in_resources = ais_in_resources;
|
||||
this->is_bbl_bundle = ais_bbl_bundle;
|
||||
|
||||
std::string path_string = source_path.string();
|
||||
std::string parent_path = source_path.parent_path().string();
|
||||
//BBS: add json logic for vendor bundles
|
||||
std::string vendor_name = source_path.filename().string();
|
||||
if (Slic3r::is_json_file(path_string)) {
|
||||
// Remove the .json suffix.
|
||||
vendor_name.erase(vendor_name.size() - 5);
|
||||
}
|
||||
else
|
||||
// Orca: served from the vendor's preset cache where one covers it — which is
|
||||
// how a shipped build carries its vendors — and parsed from the JSONs otherwise.
|
||||
// A vendor that can be neither read nor parsed — a cache the build cannot use
|
||||
// with the preset JSONs behind it pruned, say — is one the wizard cannot offer.
|
||||
// Every other vendor still can be, so it is left out rather than thrown over.
|
||||
size_t presets_loaded = 0;
|
||||
try {
|
||||
auto [config_substitutions, loaded] = preset_bundle->load_vendor_configs_from_json(
|
||||
dir.string(), vendor_name, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
|
||||
UNUSED(config_substitutions);
|
||||
// No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
|
||||
assert(config_substitutions.empty());
|
||||
presets_loaded = loaded;
|
||||
} catch (const std::exception &e) {
|
||||
BOOST_LOG_TRIVIAL(fatal) << boost::format("Vendor bundle: `%1%`: cannot be loaded, leaving it out: %2%") % vendor_name % e.what();
|
||||
return false;
|
||||
|
||||
// Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
|
||||
//BBS: add json logic for vendor bundles
|
||||
auto [config_substitutions, presets_loaded] = preset_bundle->load_vendor_configs_from_json(
|
||||
parent_path, vendor_name, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
|
||||
UNUSED(config_substitutions);
|
||||
// No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
|
||||
assert(config_substitutions.empty());
|
||||
}
|
||||
auto first_vendor = preset_bundle->vendors.begin();
|
||||
if (first_vendor == preset_bundle->vendors.end()) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string;
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % vendor_name;
|
||||
return false;
|
||||
}
|
||||
if (presets_loaded == 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string;
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % vendor_name;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded;
|
||||
BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % vendor_name % presets_loaded;
|
||||
this->vendor_profile = &first_vendor->second;
|
||||
return true;
|
||||
}
|
||||
@@ -125,15 +125,10 @@ BundleMap BundleMap::load()
|
||||
|
||||
//Orca: add custom as default
|
||||
//Orca: add json logic for vendor bundle
|
||||
auto orca_bundle_path = (vendor_dir / PresetBundle::ORCA_DEFAULT_BUNDLE).replace_extension(".json");
|
||||
auto orca_bundle_rsrc = false;
|
||||
if (!boost::filesystem::exists(orca_bundle_path)) {
|
||||
orca_bundle_path = (rsrc_vendor_dir / PresetBundle::ORCA_DEFAULT_BUNDLE).replace_extension(".json");
|
||||
orca_bundle_rsrc = true;
|
||||
}
|
||||
{
|
||||
const bool from_rsrc = ! is_vendor_installed(PresetBundle::ORCA_DEFAULT_BUNDLE);
|
||||
Bundle bbl_bundle;
|
||||
if (bbl_bundle.load(std::move(orca_bundle_path), orca_bundle_rsrc, true))
|
||||
if (bbl_bundle.load(from_rsrc ? rsrc_vendor_dir : vendor_dir, PresetBundle::ORCA_DEFAULT_BUNDLE, from_rsrc, true))
|
||||
res.emplace(PresetBundle::ORCA_DEFAULT_BUNDLE, std::move(bbl_bundle));
|
||||
}
|
||||
|
||||
@@ -141,18 +136,13 @@ BundleMap BundleMap::load()
|
||||
// and then additionally from resources/profiles.
|
||||
bool is_in_resources = false;
|
||||
for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) {
|
||||
for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) {
|
||||
//BBS: add json logic for vendor bundle
|
||||
if (Slic3r::is_json_file(dir_entry.path().string())) {
|
||||
std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".json" part
|
||||
for (const std::string &id : vendor_names_in(*dir)) {
|
||||
// Don't load this bundle if we've already loaded it.
|
||||
if (res.find(id) != res.end()) { continue; }
|
||||
|
||||
// Don't load this bundle if we've already loaded it.
|
||||
if (res.find(id) != res.end()) { continue; }
|
||||
|
||||
Bundle bundle;
|
||||
if (bundle.load(dir_entry.path(), is_in_resources))
|
||||
res.emplace(std::move(id), std::move(bundle));
|
||||
}
|
||||
Bundle bundle;
|
||||
if (bundle.load(*dir, id, is_in_resources))
|
||||
res.emplace(id, std::move(bundle));
|
||||
}
|
||||
|
||||
is_in_resources = true;
|
||||
|
||||
@@ -71,9 +71,11 @@ struct Bundle
|
||||
Bundle() = default;
|
||||
Bundle(Bundle&& other);
|
||||
|
||||
// Load the vendor `vendor_name` as it is installed in `dir`, from its preset
|
||||
// cache or its profile JSONs, whichever is usable.
|
||||
// Returns false if not loaded. Reason for that is logged as boost::log error.
|
||||
//BBS: set BBL as default
|
||||
bool load(fs::path source_path, bool is_in_resources, bool is_bbl_bundle = false);
|
||||
bool load(fs::path dir, const std::string &vendor_name, bool is_in_resources, bool is_bbl_bundle = false);
|
||||
|
||||
const std::string& vendor_id() const { return vendor_profile->id; }
|
||||
};
|
||||
|
||||
@@ -2201,25 +2201,14 @@ bool CreatePrinterPresetDialog::load_system_and_user_presets_with_curr_model(Pre
|
||||
} else {
|
||||
selected_vendor_id = m_printer_preset_vendor_selected.id;
|
||||
|
||||
if (boost::filesystem::exists(boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR / selected_vendor_id)) {
|
||||
preset_path = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).string();
|
||||
} else if (boost::filesystem::exists(boost::filesystem::path(Slic3r::resources_dir()) / "profiles" / selected_vendor_id)) {
|
||||
preset_path = (boost::filesystem::path(Slic3r::resources_dir()) / "profiles").string();
|
||||
}
|
||||
|
||||
if (preset_path.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Preset path was not found";
|
||||
MessageDialog dlg(this, _L("Preset path was not found; please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Pass the app's preset bundle (which already holds OrcaFilamentLibrary) as the base
|
||||
// bundle so vendor filaments that inherit OFL bases resolve via the existing
|
||||
// cross-vendor inheritance path.
|
||||
temp_preset_bundle.load_vendor_configs_from_json(preset_path, selected_vendor_id,
|
||||
// Orca: served from the vendor's preset cache where one covers it — a shipped
|
||||
// build carries that instead of the raw preset JSONs — and parsed otherwise.
|
||||
temp_preset_bundle.load_vendor_configs_from_json((boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).string(),
|
||||
selected_vendor_id,
|
||||
PresetBundle::LoadConfigBundleAttribute::LoadSystem,
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent,
|
||||
wxGetApp().preset_bundle);
|
||||
|
||||
@@ -10,20 +10,36 @@
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
|
||||
#include "libslic3r/Time.hpp"
|
||||
|
||||
using namespace nlohmann;
|
||||
|
||||
namespace {
|
||||
// Orca: access_code and user_access_code used to be separate AppConfig keys before the two
|
||||
// fields were merged; fall back to the legacy key so existing users' saved codes aren't lost.
|
||||
std::string get_access_code_with_legacy_fallback(Slic3r::AppConfig* config, const std::string& dev_id)
|
||||
// Orca: access_code lives on BBLocalMachine::access_code (keyed by dev_id via
|
||||
// get_local_machines(), scoped by the record's own printer_agent_id field) - so binding a
|
||||
// printer under one agent doesn't silently appear as already-bound under a different,
|
||||
// independent agent. This only covers LAN devices (BBLocalMachine's own scope); access_code
|
||||
// and user_access_code used to be the only, flat dev_id-only AppConfig keys before
|
||||
// BBLocalMachine::access_code existed, and codes saved back then are still stored flat (no
|
||||
// agent association at all). Since BBL was the only agent that existed at the time, honor
|
||||
// those flat legacy keys as implicitly BBL's - but only for the BBL agent, so they aren't
|
||||
// leaked to other agents that never bound the device themselves.
|
||||
std::string get_access_code_with_legacy_fallback(Slic3r::AppConfig* config, const std::string& dev_id, const std::string& agent_id)
|
||||
{
|
||||
std::string code = config->get("access_code", dev_id);
|
||||
if (code.empty())
|
||||
code = config->get("user_access_code", dev_id);
|
||||
return code;
|
||||
const auto& machines = config->get_local_machines();
|
||||
auto it = machines.find(dev_id);
|
||||
if (it != machines.end() && it->second.printer_agent_id == agent_id && !it->second.access_code.empty())
|
||||
return it->second.access_code;
|
||||
|
||||
if (agent_id == Slic3r::BBL_PRINTER_AGENT_ID || agent_id.empty()) {
|
||||
std::string code = config->get("access_code", dev_id);
|
||||
if (code.empty())
|
||||
code = config->get("user_access_code", dev_id);
|
||||
return code;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,12 +71,13 @@ namespace Slic3r
|
||||
continue;
|
||||
MachineObject* obj = new MachineObject(this, m_agent, m.dev_name, m.dev_id, m.dev_ip);
|
||||
obj->printer_type = m.printer_type;
|
||||
obj->printer_agent_id = m.printer_agent_id;
|
||||
obj->dev_connection_type = "lan";
|
||||
obj->bind_state = "free";
|
||||
obj->bind_sec_link = "secure";
|
||||
obj->m_is_online = true;
|
||||
obj->last_alive = Slic3r::Utils::get_current_time_utc();
|
||||
obj->set_access_code(get_access_code_with_legacy_fallback(config, m.dev_id), false);
|
||||
obj->set_access_code(get_access_code_with_legacy_fallback(config, m.dev_id, obj->printer_agent_id), false);
|
||||
if (obj->has_access_right()) {
|
||||
localMachineList.insert(std::make_pair(m.dev_id, obj));
|
||||
} else {
|
||||
@@ -77,10 +94,12 @@ namespace Slic3r
|
||||
if (m.is_lan_mode_printer()) {
|
||||
if (m.has_access_right()) {
|
||||
BBLocalMachine local_machine;
|
||||
local_machine.dev_id = m.get_dev_id();
|
||||
local_machine.dev_name = m.get_dev_name();
|
||||
local_machine.dev_ip = m.get_dev_ip();
|
||||
local_machine.printer_type = m.printer_type;
|
||||
local_machine.dev_id = m.get_dev_id();
|
||||
local_machine.dev_name = m.get_dev_name();
|
||||
local_machine.dev_ip = m.get_dev_ip();
|
||||
local_machine.printer_type = m.printer_type;
|
||||
local_machine.printer_agent_id = m.printer_agent_id;
|
||||
local_machine.access_code = m.get_access_code();
|
||||
config->update_local_machine(local_machine);
|
||||
}
|
||||
} else {
|
||||
@@ -143,6 +162,14 @@ namespace Slic3r
|
||||
}
|
||||
}
|
||||
|
||||
std::string DeviceManager::get_current_printer_agent_id() const
|
||||
{
|
||||
if (!m_agent)
|
||||
return "";
|
||||
auto printer_agent = m_agent->get_printer_agent();
|
||||
return printer_agent ? printer_agent->get_agent_info().id : "";
|
||||
}
|
||||
|
||||
void DeviceManager::EnableMultiMachine(bool enable)
|
||||
{
|
||||
m_agent->enable_multi_machine(enable);
|
||||
@@ -339,6 +366,7 @@ namespace Slic3r
|
||||
/* insert a new machine */
|
||||
obj = new MachineObject(this, m_agent, dev_name, dev_id, dev_ip);
|
||||
obj->printer_type = _parse_printer_type(printer_type_str);
|
||||
obj->printer_agent_id = get_current_printer_agent_id();
|
||||
obj->wifi_signal = printer_signal;
|
||||
obj->dev_connection_type = connect_type;
|
||||
obj->bind_state = bind_state;
|
||||
@@ -350,7 +378,7 @@ namespace Slic3r
|
||||
//load access code
|
||||
AppConfig* config = Slic3r::GUI::wxGetApp().app_config;
|
||||
if (config) {
|
||||
obj->set_access_code(get_access_code_with_legacy_fallback(config, dev_id), false);
|
||||
obj->set_access_code(get_access_code_with_legacy_fallback(config, dev_id, obj->printer_agent_id), false);
|
||||
}
|
||||
localMachineList.insert(std::make_pair(dev_id, obj));
|
||||
|
||||
@@ -379,6 +407,7 @@ namespace Slic3r
|
||||
obj = it->second;
|
||||
} else {
|
||||
obj = new MachineObject(this, m_agent, machine.dev_name, machine.dev_id, machine.dev_ip);
|
||||
obj->printer_agent_id = get_current_printer_agent_id();
|
||||
localMachineList.insert(std::make_pair(machine.dev_id, obj));
|
||||
}
|
||||
if (machine.printer_type.empty())
|
||||
@@ -505,16 +534,26 @@ namespace Slic3r
|
||||
OnSelectedMachineChanged(previous_selected_machine, selected_machine);
|
||||
}
|
||||
|
||||
void DeviceManager::clear_other_devices()
|
||||
void DeviceManager::clear_other_devices(const std::string& target_agent_id)
|
||||
{
|
||||
// why: on agent swap, keep "My Devices" but drop the transient "Other Devices"
|
||||
// Those belong to the previous agent's network scan; the new agent's start_discovery re-populates its own.
|
||||
//
|
||||
// Also drop "My Devices" stamped by a different agent than the one we're swapping to
|
||||
// (target_agent_id, passed by the caller since the live agent hasn't been repointed yet
|
||||
// at this point): otherwise a device first discovered under agent A survives every swap
|
||||
// with a stale printer_agent_id, stays hidden from every agent's filtered list, and only
|
||||
// gets re-tagged if something happens to delete and re-create it (e.g. account logout).
|
||||
// Dropping it here instead lets the new agent's start_discovery re-insert and re-stamp it
|
||||
// like any other fresh device.
|
||||
const auto my = get_my_machine_list();
|
||||
for (auto it = localMachineList.begin(); it != localMachineList.end();)
|
||||
{
|
||||
if (my.find(it->first) == my.end())
|
||||
const bool is_my_device = my.find(it->first) != my.end();
|
||||
const bool agent_mismatch = !target_agent_id.empty() && it->second &&
|
||||
it->second->printer_agent_id != target_agent_id;
|
||||
if (!is_my_device || agent_mismatch)
|
||||
{
|
||||
// not a "My Device" -> an "Other Device"
|
||||
delete it->second;
|
||||
it = localMachineList.erase(it);
|
||||
}
|
||||
@@ -697,13 +736,16 @@ namespace Slic3r
|
||||
m_agent->add_subscribe(subscribe_list_cache);
|
||||
}
|
||||
|
||||
std::map<std::string, MachineObject*> DeviceManager::get_my_machine_list()
|
||||
std::map<std::string, MachineObject*> DeviceManager::get_my_machine_list(const std::string& agent_id)
|
||||
{
|
||||
std::map<std::string, MachineObject*> result;
|
||||
|
||||
for (auto it = userMachineList.begin(); it != userMachineList.end(); it++)
|
||||
{
|
||||
if (it->second && !it->second->is_lan_mode_printer())
|
||||
if (!it->second || (!agent_id.empty() && it->second->printer_agent_id != agent_id))
|
||||
continue;
|
||||
|
||||
if (!it->second->is_lan_mode_printer())
|
||||
{
|
||||
result.insert(std::make_pair(it->first, it->second));
|
||||
}
|
||||
@@ -711,7 +753,10 @@ namespace Slic3r
|
||||
|
||||
for (auto it = localMachineList.begin(); it != localMachineList.end(); it++)
|
||||
{
|
||||
if (it->second && it->second->has_access_right() && it->second->is_avaliable() && it->second->is_lan_mode_printer())
|
||||
if (!it->second || (!agent_id.empty() && it->second->printer_agent_id != agent_id))
|
||||
continue;
|
||||
|
||||
if (it->second->has_access_right() && it->second->is_avaliable() && it->second->is_lan_mode_printer())
|
||||
{
|
||||
// remove redundant in userMachineList
|
||||
if (result.find(it->first) == result.end())
|
||||
@@ -723,12 +768,15 @@ namespace Slic3r
|
||||
return result;
|
||||
}
|
||||
|
||||
std::map<std::string, MachineObject*> DeviceManager::get_my_cloud_machine_list()
|
||||
std::map<std::string, MachineObject*> DeviceManager::get_my_cloud_machine_list(const std::string& agent_id)
|
||||
{
|
||||
std::map<std::string, MachineObject*> result;
|
||||
for (auto it = userMachineList.begin(); it != userMachineList.end(); it++)
|
||||
{
|
||||
if (it->second && !it->second->is_lan_mode_printer()) { result.emplace(*it); }
|
||||
if (!it->second || (!agent_id.empty() && it->second->printer_agent_id != agent_id))
|
||||
continue;
|
||||
|
||||
if (!it->second->is_lan_mode_printer()) { result.emplace(*it); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -801,6 +849,7 @@ namespace Slic3r
|
||||
else
|
||||
{
|
||||
obj = new MachineObject(this, m_agent, "", "", "");
|
||||
obj->printer_agent_id = get_current_printer_agent_id();
|
||||
if (m_agent)
|
||||
{
|
||||
obj->set_bind_status(m_agent->get_user_name(provider));
|
||||
|
||||
@@ -74,7 +74,10 @@ public:
|
||||
void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); }
|
||||
void clean_user_info(bool keep_local_selection = false);
|
||||
|
||||
void clear_other_devices();
|
||||
// target_agent_id: id of the agent being swapped to (empty = no agent-mismatch check,
|
||||
// just the original "drop Other Devices" behavior). Pass the incoming agent's id, not the
|
||||
// live one - this runs before the live agent is repointed.
|
||||
void clear_other_devices(const std::string& target_agent_id = "");
|
||||
|
||||
void load_last_machine();
|
||||
void update_user_machine_list_info(const std::string& provider);
|
||||
@@ -90,10 +93,15 @@ public:
|
||||
|
||||
/* my machine*/
|
||||
MachineObject* get_my_machine(std::string dev_id);
|
||||
std::map<std::string, MachineObject*> get_my_machine_list();
|
||||
std::map<std::string, MachineObject*> get_my_cloud_machine_list();
|
||||
std::map<std::string, MachineObject*> get_my_machine_list(const std::string& agent_id = "");
|
||||
std::map<std::string, MachineObject*> get_my_cloud_machine_list(const std::string& agent_id = "");
|
||||
void modify_device_name(std::string dev_id, std::string dev_name, const std::string& provider);
|
||||
|
||||
// id of the currently live IPrinterAgent (IPrinterAgent::get_agent_info().id), or empty if
|
||||
// m_agent has no printer agent set yet. Pass to get_my_machine_list()/get_my_cloud_machine_list()
|
||||
// to scope results to the active agent.
|
||||
std::string get_current_printer_agent_id() const;
|
||||
|
||||
/* create machine or update machine properties */
|
||||
void on_machine_alive(std::string json_str);
|
||||
int query_bind_status(std::string& msg, const std::string& provider);
|
||||
|
||||
@@ -27,6 +27,7 @@ void DevStatus::ParseStatus(const nlohmann::json& print_jj)
|
||||
#else
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": get exception=" << e.what();
|
||||
#endif
|
||||
(void)e; // suppress C4101 when BBL_RELEASE_TO_PUBLIC
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "libslic3r/Time.hpp"
|
||||
#include "libslic3r/Thread.hpp"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
#include "GuiColor.hpp"
|
||||
|
||||
#include "GUI_App.hpp"
|
||||
@@ -458,11 +459,41 @@ void MachineObject::set_access_code(std::string code, bool only_refresh)
|
||||
if (only_refresh) {
|
||||
AppConfig* config = GUI::wxGetApp().app_config;
|
||||
if (config) {
|
||||
if (!code.empty()) {
|
||||
GUI::wxGetApp().app_config->set_str("access_code", get_dev_id(), code);
|
||||
DeviceManager::update_local_machine(*this);
|
||||
if (is_lan_mode_printer()) {
|
||||
// why: LAN codes are scoped via BBLocalMachine::access_code, keyed by dev_id and
|
||||
// scoped by that record's own printer_agent_id field - see the matching comment
|
||||
// on get_access_code_with_legacy_fallback() in DevManager.cpp - so binding this
|
||||
// device under one printer agent doesn't silently read as already-bound under a
|
||||
// different, independent one. Cloud devices (the else branch below) aren't
|
||||
// scoped this way: they're never recalled from a stale local cache across a
|
||||
// session boundary, since parse_user_print_info() always overwrites their code
|
||||
// fresh from the cloud API's current response, so there's no cross-agent leakage
|
||||
// risk to guard against there.
|
||||
if (!code.empty()) {
|
||||
DeviceManager::update_local_machine(*this);
|
||||
} else {
|
||||
// Only patch an existing record's code - don't persist a brand-new
|
||||
// never-bound entry just because set_access_code("") was called on it.
|
||||
const auto& machines = config->get_local_machines();
|
||||
auto it = machines.find(get_dev_id());
|
||||
if (it != machines.end()) {
|
||||
BBLocalMachine local_machine = it->second;
|
||||
local_machine.access_code = "";
|
||||
config->update_local_machine(local_machine);
|
||||
}
|
||||
// Also clear the pre-scoping flat legacy key when unbinding under BBL, so an
|
||||
// old BBL-era code can't silently "re-bind" this device again via
|
||||
// get_access_code_with_legacy_fallback()'s legacy fallback.
|
||||
if (printer_agent_id == BBL_PRINTER_AGENT_ID || printer_agent_id.empty()) {
|
||||
config->erase("access_code", get_dev_id());
|
||||
config->erase("user_access_code", get_dev_id());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GUI::wxGetApp().app_config->erase("access_code", get_dev_id());
|
||||
if (!code.empty())
|
||||
config->set_str("access_code", get_dev_id(), code);
|
||||
else
|
||||
config->erase("access_code", get_dev_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,6 +229,16 @@ public:
|
||||
|
||||
//PRINTER_TYPE printer_type = PRINTER_3DPrinter_UKNOWN;
|
||||
std::string printer_type; /* model_id */
|
||||
|
||||
// id of the IPrinterAgent that was used to discover or bind this device (IPrinterAgent::get_agent_info().id,
|
||||
// e.g. "bbl"), stamped at creation time — not derived from get_agent(), since m_agent is a single
|
||||
// process-wide NetworkAgent shared by every MachineObject and gets repointed on agent swap
|
||||
// (see DeviceManager::set_agent()), so it can't tell which agent originally found this device.
|
||||
// We persist this as well so that when the printer agent is swapped, we don't show unrelated devices,
|
||||
// e.g. if the current printer agent is elegoo, we shouldn't show printers connected by BBL printer agent
|
||||
// under local machines.
|
||||
std::string printer_agent_id;
|
||||
|
||||
std::string get_show_printer_type() const;
|
||||
PrinterSeries get_printer_series() const;
|
||||
PrinterArch get_printer_arch() const;
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
|
||||
#include "Widgets/HyperLink.hpp" // ORCA
|
||||
|
||||
#define DESIGN_INPUT_SIZE wxSize(FromDIP(100), -1)
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ void Downloader::start_download(const std::string& full_url)
|
||||
Plater* plater = wxGetApp().plater();
|
||||
|
||||
mainframe->Freeze();
|
||||
mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
plater->select_view_3D("3D");
|
||||
plater->select_view("plate");
|
||||
plater->get_current_canvas3D()->zoom_to_bed();
|
||||
|
||||
@@ -331,8 +331,10 @@ void Field::PostInitialize()
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
if (tab_id >= 0)
|
||||
wxGetApp().mainframe->select_tab(tab_id);
|
||||
if (tab_id >= 0) {
|
||||
static constexpr const char* kShortcutTabIds[] = {TAB_ID_HOME, TAB_ID_PREPARE, TAB_ID_PREVIEW, TAB_ID_MONITOR};
|
||||
wxGetApp().mainframe->select_tab(kShortcutTabIds[tab_id]);
|
||||
}
|
||||
if (tab_id > 0)
|
||||
// tab panel should be focused for correct navigation between tabs
|
||||
wxGetApp().tab_panel()->SetFocus();
|
||||
|
||||
@@ -420,7 +420,7 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
|
||||
if (properties_shown) {
|
||||
float label_w = 0.0f;
|
||||
float value_w = 0.0f;
|
||||
properties_rows.reserve(13);
|
||||
properties_rows.reserve(14);
|
||||
auto add_row = [&properties_rows, &label_w, &value_w](std::string label, std::string value) {
|
||||
label_w = std::max(label_w, ImGui::CalcTextSize(label.c_str()).x);
|
||||
value_w = std::max(value_w, ImGui::CalcTextSize(value.c_str()).x);
|
||||
@@ -433,6 +433,27 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
|
||||
add_row(_u8L("Width"), buff);
|
||||
if (is_extrusion) sprintf(buff, ("%.3f " + _u8L("mm")).c_str(), vertex.height); else strcpy(buff, NA_CSTR);
|
||||
add_row(_u8L("Height"), buff);
|
||||
// ORCA: Length of the move ending at the current vertex. Arc moves (G2/G3) are discretized
|
||||
// into several vertices sharing the same gcode line id, so accumulate the whole run to report
|
||||
// the arc length instead of the length of a single chord.
|
||||
if (vertex_id > 0 && (is_extrusion || vertex.is_travel() || vertex.is_wipe())) {
|
||||
const size_t vertices_count = viewer->get_vertices_count();
|
||||
size_t first_id = vertex_id;
|
||||
while (first_id > 0 && viewer->get_vertex_at(first_id - 1).gcode_id == vertex.gcode_id)
|
||||
--first_id;
|
||||
size_t last_id = vertex_id;
|
||||
while (last_id + 1 < vertices_count && viewer->get_vertex_at(last_id + 1).gcode_id == vertex.gcode_id)
|
||||
++last_id;
|
||||
float length = 0.0f;
|
||||
for (size_t i = std::max<size_t>(first_id, 1); i <= last_id; ++i) {
|
||||
length += (libvgcode::convert(viewer->get_vertex_at(i).position) -
|
||||
libvgcode::convert(viewer->get_vertex_at(i - 1).position)).norm();
|
||||
}
|
||||
sprintf(buff, ("%.3f " + _u8L("mm")).c_str(), length);
|
||||
}
|
||||
else
|
||||
strcpy(buff, NA_CSTR);
|
||||
add_row(_u8L("Length"), buff);
|
||||
sprintf(buff, "%d", vertex.layer_id + 1);
|
||||
add_row(_u8L("Layer"), buff);
|
||||
sprintf(buff, ("%.1f " + _u8L("mm/s")).c_str(), vertex.feedrate);
|
||||
|
||||
@@ -2887,7 +2887,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
float x = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_x"))->get_at(plate_id);
|
||||
float y = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_y"))->get_at(plate_id);
|
||||
float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_tower_width"))->value;
|
||||
float a = dynamic_cast<const ConfigOptionFloat*>(proj_cfg.option("wipe_tower_rotation_angle"))->value;
|
||||
float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
|
||||
// BBS
|
||||
float v = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_volume"))->value;
|
||||
Vec3d plate_origin = ppl.get_plate(plate_id)->get_origin();
|
||||
@@ -9196,7 +9196,7 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar()
|
||||
view3d_canvas->get_gizmos_manager().reset_all_states(); // close all gizmos
|
||||
view3d_canvas->reload_scene(true);
|
||||
}
|
||||
app.mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
|
||||
app.mainframe->select_tab(TAB_ID_PREPARE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#elif _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
#include "boost/nowide/convert.hpp"
|
||||
#endif
|
||||
|
||||
+44
-18
@@ -813,12 +813,12 @@ void GUI_App::post_init()
|
||||
m_open_method = "url";
|
||||
} else {
|
||||
if (this->init_params->input_gcode) {
|
||||
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
plater_->select_view_3D("3D");
|
||||
this->plater()->load_gcode(from_u8(this->init_params->input_files.front()));
|
||||
m_open_method = "gcode";
|
||||
} else {
|
||||
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
plater_->select_view_3D("3D");
|
||||
wxArrayString input_files;
|
||||
for (auto& file : this->init_params->input_files) {
|
||||
@@ -852,7 +852,7 @@ void GUI_App::post_init()
|
||||
mainframe->Freeze();
|
||||
#endif
|
||||
plater_->canvas3D()->enable_render(false);
|
||||
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
plater_->select_view_3D("3D");
|
||||
//BBS init the opengl resource here
|
||||
if (!plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen() ||
|
||||
@@ -890,9 +890,9 @@ void GUI_App::post_init()
|
||||
}
|
||||
}
|
||||
if (is_editor())
|
||||
mainframe->select_tab(size_t(0));
|
||||
mainframe->select_tab(TAB_ID_HOME);
|
||||
if (app_config->get("default_page") == "1")
|
||||
mainframe->select_tab(size_t(1));
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
#ifndef __linux__
|
||||
mainframe->Thaw();
|
||||
#endif
|
||||
@@ -1829,10 +1829,10 @@ bool GUI_App::hot_reload_network_plugin()
|
||||
wxWindowDisabler disabler;
|
||||
|
||||
if (mainframe) {
|
||||
int current_tab = mainframe->m_tabpanel->GetSelection();
|
||||
if (current_tab == MainFrame::TabPosition::tpMonitor) {
|
||||
wxString current_tab = mainframe->m_tabpanel->GetSelectedPageName();
|
||||
if (current_tab == TAB_ID_MONITOR) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": navigating away from Monitor tab before unload";
|
||||
mainframe->m_tabpanel->SetSelection(MainFrame::TabPosition::tp3DEditor);
|
||||
mainframe->m_tabpanel->SelectPageByName(TAB_ID_PREPARE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2851,6 +2851,16 @@ void GUI_App::init_plugin_gui_wiring()
|
||||
plugin_mgr.subscribe_on_unload_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
|
||||
plugin_mgr.subscribe_on_load_callback(NetworkAgentFactory::register_python_plugin);
|
||||
plugin_mgr.subscribe_on_unload_callback(NetworkAgentFactory::deregister_python_plugin);
|
||||
plugin_mgr.subscribe_on_load_callback([](const std::string& plugin_key) {
|
||||
if (wxTheApp == nullptr || wxGetApp().is_closing() || wxGetApp().mainframe == nullptr)
|
||||
return;
|
||||
wxGetApp().mainframe->plugin_pages().on_plugin_register(plugin_key);
|
||||
});
|
||||
plugin_mgr.subscribe_on_unload_callback([](const std::string& plugin_key) {
|
||||
if (wxTheApp == nullptr || wxGetApp().is_closing() || wxGetApp().mainframe == nullptr)
|
||||
return;
|
||||
wxGetApp().mainframe->plugin_pages().on_plugin_deregister(plugin_key);
|
||||
});
|
||||
plugin_mgr.subscribe_on_load_callback(refresh_printer_agent_dropdown_after_load);
|
||||
plugin_mgr.subscribe_on_unload_callback(switch_printer_agent_after_unload);
|
||||
plugin_mgr.subscribe_on_capability_load_callback(
|
||||
@@ -2866,11 +2876,15 @@ void GUI_App::init_plugin_gui_wiring()
|
||||
if (Plater* plater = wxGetApp().plater())
|
||||
plater->revalidate_current_plate_if_plugins_missing();
|
||||
});
|
||||
if (capability.type == PluginCapabilityType::Pages && wxTheApp && !wxGetApp().is_closing() && wxGetApp().mainframe)
|
||||
wxGetApp().mainframe->plugin_pages().on_cap_register(capability);
|
||||
});
|
||||
plugin_mgr.subscribe_on_capability_unload_callback(
|
||||
[refresh_plugins_dialog, switch_printer_agent_after_unload](const PluginCapabilityId& capability) {
|
||||
if (capability.type == PluginCapabilityType::PrinterConnection)
|
||||
NetworkAgentFactory::deregister_python_printer_agent(capability.plugin_key, capability.name);
|
||||
if (capability.type == PluginCapabilityType::Pages && wxTheApp && !wxGetApp().is_closing() && wxGetApp().mainframe)
|
||||
wxGetApp().mainframe->plugin_pages().on_cap_deregister(capability);
|
||||
refresh_plugins_dialog();
|
||||
switch_printer_agent_after_unload(capability.plugin_key);
|
||||
});
|
||||
@@ -3273,15 +3287,12 @@ bool GUI_App::on_init_inner()
|
||||
}
|
||||
} */
|
||||
|
||||
copy_network_if_available();
|
||||
|
||||
if (scrn) {
|
||||
scrn->SetText(_L("Loading Plugins") + dots, 20);
|
||||
wxYield();
|
||||
}
|
||||
|
||||
on_init_network();
|
||||
|
||||
// Initialize plugins after network then register on_load callbacks so once the plugin loads finish, it gets registered automatically.
|
||||
// initialize() also installs the libslic3r hooks (capability resolver,
|
||||
// slicing-pipeline dispatcher) via plugin_hooks::install() -- no
|
||||
@@ -3310,6 +3321,9 @@ bool GUI_App::on_init_inner()
|
||||
}
|
||||
}
|
||||
|
||||
copy_network_if_available();
|
||||
on_init_network();
|
||||
|
||||
if (m_agent)
|
||||
plugin_mgr.set_cloud_agent(std::dynamic_pointer_cast<OrcaCloudServiceAgent>(m_agent->get_cloud_agent()));
|
||||
|
||||
@@ -3382,7 +3396,7 @@ bool GUI_App::on_init_inner()
|
||||
mainframe = new MainFrame();
|
||||
// hide settings tabs after first Layout
|
||||
if (is_editor()) {
|
||||
mainframe->select_tab(size_t(0));
|
||||
mainframe->select_tab(TAB_ID_HOME);
|
||||
}
|
||||
|
||||
sidebar().obj_list()->init();
|
||||
@@ -3937,7 +3951,13 @@ void GUI_App::set_live_printer_agent(std::shared_ptr<IPrinterAgent> agent)
|
||||
m_agent->set_user_selected_machine("");
|
||||
// note: belt-and-suspenders (precedent: DeviceManagerRefresher::on_timer)
|
||||
dev->OnSelectedMachineLost(); // why: clear stale sidebar sync-status / AMS
|
||||
dev->clear_other_devices(); // why: drop stale LAN discoveries; keep My Devices
|
||||
// why: drop stale LAN discoveries; keep My Devices, but only those belonging to the
|
||||
// agent we're about to swap to, so a device stamped by the outgoing agent doesn't
|
||||
// linger hidden - the new agent's start_discovery re-inserts and re-stamps it fresh.
|
||||
// agent is null when clearing the live agent entirely (e.g. plugin unload); there's no
|
||||
// target to filter against then, so fall back to the original "keep all My Devices"
|
||||
// behavior rather than guessing.
|
||||
dev->clear_other_devices(agent ? agent->get_agent_info().id : std::string());
|
||||
}
|
||||
|
||||
m_agent->set_printer_agent(agent);
|
||||
@@ -4590,7 +4610,7 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
|
||||
mainframe = new MainFrame();
|
||||
if (is_editor())
|
||||
// hide settings tabs after first Layout
|
||||
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
// Propagate model objects to object list.
|
||||
sidebar().obj_list()->init();
|
||||
//sidebar().aux_list()->init_auxiliary();
|
||||
@@ -6797,6 +6817,12 @@ void GUI_App::add_pending_vendor_preset(const std::pair<std::string, std::map<st
|
||||
|
||||
// Add the corresponding vendor
|
||||
std::string vendor_name = PresetBundle::find_preset_vendor(inherits_name, type);
|
||||
if (vendor_name.empty()) {
|
||||
// No vendor ships this preset's parent. An unnamed entry here becomes an
|
||||
// unnamed bundle at install time, which nothing can install.
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": no vendor carries " << inherits_name << ", skipping";
|
||||
return;
|
||||
}
|
||||
if (need_add_vendors.find(vendor_name) == need_add_vendors.end())
|
||||
need_add_vendors[vendor_name] = std::map<std::string, std::set<std::string>>();
|
||||
|
||||
@@ -9849,7 +9875,7 @@ bool GUI_App::check_url_association(std::wstring url_prefix, std::wstring& reg_b
|
||||
{
|
||||
reg_bin = L"";
|
||||
#ifdef WIN32
|
||||
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
|
||||
wxRegKey key_full(wxRegKey::HKCU, L"Software\\Classes\\" + url_prefix + L"\\shell\\open\\command");
|
||||
if (!key_full.Exists()) {
|
||||
return false;
|
||||
}
|
||||
@@ -9875,8 +9901,8 @@ void GUI_App::associate_url(std::wstring url_prefix)
|
||||
|
||||
wxString key_string = "\"" + wbinary + "\" \"%1\"";
|
||||
|
||||
wxRegKey key_first(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix);
|
||||
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
|
||||
wxRegKey key_first(wxRegKey::HKCU, L"Software\\Classes\\" + url_prefix);
|
||||
wxRegKey key_full(wxRegKey::HKCU, L"Software\\Classes\\" + url_prefix + L"\\shell\\open\\command");
|
||||
if (!key_first.Exists()) {
|
||||
key_first.Create(false);
|
||||
}
|
||||
@@ -9896,7 +9922,7 @@ void GUI_App::disassociate_url(std::wstring url_prefix)
|
||||
#ifdef WIN32
|
||||
if (is_running_in_msix())
|
||||
return;
|
||||
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
|
||||
wxRegKey key_full(wxRegKey::HKCU, L"Software\\Classes\\" + url_prefix + L"\\shell\\open\\command");
|
||||
if (!key_full.Exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -597,7 +597,6 @@ void GLGizmoMeasure::on_render()
|
||||
}
|
||||
}
|
||||
Vec3d position_on_model;
|
||||
Vec3d direction_on_model;
|
||||
size_t model_facet_idx = -1;
|
||||
double closest_hit_distance = std::numeric_limits<double>::max();
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "HMS.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "DeviceManager.hpp"
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevUtil.h"
|
||||
|
||||
+16
-13
@@ -1,7 +1,6 @@
|
||||
#ifndef slic3r_HMS_hpp_
|
||||
#define slic3r_HMS_hpp_
|
||||
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
@@ -11,7 +10,11 @@
|
||||
#include "slic3r/Utils/Http.hpp"
|
||||
#include "libslic3r/Thread.hpp"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include <ctime>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -26,12 +29,12 @@ namespace GUI {
|
||||
class HMSQuery {
|
||||
|
||||
protected:
|
||||
std::unordered_map<string, json> m_hms_info_jsons; // key-> device id type, the first three digits of SN number
|
||||
std::unordered_map<string, json> m_hms_action_jsons;// key-> device id type
|
||||
std::unordered_map<std::string, nlohmann::json> m_hms_info_jsons; // key-> device id type, the first three digits of SN number
|
||||
std::unordered_map<std::string, nlohmann::json> m_hms_action_jsons;// key-> device id type
|
||||
std::unordered_map<wxString, wxImage> m_hms_local_images; // key-> image name
|
||||
mutable std::mutex m_hms_mutex;
|
||||
|
||||
std::unordered_map<string, time_t> m_cloud_hms_last_update_time;
|
||||
std::unordered_map<std::string, std::time_t> m_cloud_hms_last_update_time;
|
||||
|
||||
public:
|
||||
HMSQuery() { }
|
||||
@@ -61,18 +64,18 @@ private:
|
||||
// load hms
|
||||
void init_hms_info(const std::string& dev_type_id);
|
||||
void copy_from_data_dir_to_local();
|
||||
int download_hms_related(const std::string& hms_type, const std::string& dev_id_type, json* receive_json);
|
||||
int load_from_local(const std::string& hms_type, const std::string& dev_id_type, json* receive_json, std::string& version_info);
|
||||
int save_to_local(std::string lang, std::string hms_type, std::string dev_id_type, json save_json);
|
||||
int download_hms_related(const std::string& hms_type, const std::string& dev_id_type, nlohmann::json* receive_json);
|
||||
int load_from_local(const std::string& hms_type, const std::string& dev_id_type, nlohmann::json* receive_json, std::string& version_info);
|
||||
int save_to_local(std::string lang, std::string hms_type, std::string dev_id_type, nlohmann::json save_json);
|
||||
std::string get_hms_file(std::string hms_type, std::string lang = std::string("en"), std::string dev_id_type = "");
|
||||
|
||||
// internal query
|
||||
string get_dev_id_type(const MachineObject* obj) const;
|
||||
wxString _query_hms_msg(const string& dev_id_type, const string& long_error_code, const string& lang_code = std::string("en"));
|
||||
std::string get_dev_id_type(const MachineObject* obj) const;
|
||||
wxString _query_hms_msg(const std::string& dev_id_type, const std::string& long_error_code, const std::string& lang_code = std::string("en"));
|
||||
|
||||
bool _is_internal_error(const string &dev_id_type, const string &long_error_code, const string &lang_code = std::string("en"));
|
||||
wxString _query_error_msg(const string& dev_id_type, const std::string& long_error_code, const std::string& lang_code = std::string("en"));
|
||||
wxString _query_error_image_action(const string& dev_id_type, const std::string& long_error_code, std::vector<int>& button_action);
|
||||
bool _is_internal_error(const std::string &dev_id_type, const std::string &long_error_code, const std::string &lang_code = std::string("en"));
|
||||
wxString _query_error_msg(const std::string& dev_id_type, const std::string& long_error_code, const std::string& lang_code = std::string("en"));
|
||||
wxString _query_error_image_action(const std::string& dev_id_type, const std::string& long_error_code, std::vector<int>& button_action);
|
||||
};
|
||||
|
||||
int get_hms_info_version(std::string &version);
|
||||
@@ -85,4 +88,4 @@ std::string get_error_message(int error_code);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -3332,8 +3332,9 @@ const char* ImGuiWrapper::clipboard_get(void* user_data)
|
||||
wxTextDataObject data;
|
||||
wxTheClipboard->GetData(data);
|
||||
|
||||
if (data.GetTextLength() > 0) {
|
||||
self->m_clipboard_text = into_u8(data.GetText());
|
||||
const wxString text = data.GetText();
|
||||
if (text.Length() > 0) {
|
||||
self->m_clipboard_text = into_u8(text);
|
||||
res = self->m_clipboard_text.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
+106
-94
@@ -493,9 +493,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
});
|
||||
|
||||
//BBS
|
||||
Bind(EVT_SELECT_TAB, [this](wxCommandEvent&evt) {
|
||||
TabPosition pos = (TabPosition)evt.GetInt();
|
||||
m_tabpanel->SetSelection(pos);
|
||||
Bind(EVT_SELECT_TAB, [this](wxCommandEvent& evt) {
|
||||
m_tabpanel->SelectPageByName(evt.GetString());
|
||||
});
|
||||
|
||||
Bind(EVT_SYNC_CLOUD_PRESET, &MainFrame::on_select_default_preset, this);
|
||||
@@ -702,7 +701,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
}
|
||||
return;}
|
||||
#endif
|
||||
if (evt.CmdDown() && evt.GetKeyCode() == 'R') { if (m_slice_enable) { wxGetApp().plater()->update(true, true); wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE)); this->m_tabpanel->SetSelection(tpPreview); } return; }
|
||||
if (evt.CmdDown() && evt.GetKeyCode() == 'R') { if (m_slice_enable) { wxGetApp().plater()->update(true, true); wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE)); this->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW); } return; }
|
||||
if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'G') {
|
||||
m_plater->apply_background_progress();
|
||||
m_print_enable = get_enable_print_status();
|
||||
@@ -723,7 +722,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'S') { if (can_save_as()) m_plater->save_project(true); return;}
|
||||
else if (evt.CmdDown() && evt.GetKeyCode() == 'S') { if (can_save()) m_plater->save_project(); return;}
|
||||
if (evt.CmdDown() && evt.GetKeyCode() == 'F') {
|
||||
if (m_plater && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview)) {
|
||||
if (m_plater && is_prepare_or_preview_tab()) {
|
||||
m_plater->sidebar().can_search();
|
||||
}
|
||||
}
|
||||
@@ -1007,8 +1006,8 @@ void MainFrame::update_layout()
|
||||
m_layout = layout;
|
||||
|
||||
// From the very beginning the Print settings should be selected
|
||||
//m_last_selected_tab = m_layout == ESettingsLayout::Dlg ? 0 : 1;
|
||||
m_last_selected_tab = 1;
|
||||
//m_last_selected_tab = m_layout == ESettingsLayout::Dlg ? TAB_ID_HOME : TAB_ID_PREPARE;
|
||||
m_last_selected_tab = TAB_ID_PREPARE;
|
||||
|
||||
// Set new settings
|
||||
switch (m_layout)
|
||||
@@ -1016,14 +1015,18 @@ void MainFrame::update_layout()
|
||||
case ESettingsLayout::Old:
|
||||
{
|
||||
m_plater->Reparent(m_tabpanel);
|
||||
m_tabpanel->InsertPage(tp3DEditor, m_plater, _L("Prepare"), std::string("tab_3d_active"), std::string("tab_3d_active"), false);
|
||||
m_tabpanel->InsertPage(tpPreview, m_plater, _L("Preview"), std::string("tab_preview_active"), std::string("tab_preview_active"), false);
|
||||
// Right after Home — or first, when there is no Home tab (PositionAfter() would
|
||||
// append instead, and by now the other built-in tabs are already in place).
|
||||
const int home_idx = m_tabpanel->FindPageByName(TAB_ID_HOME);
|
||||
const size_t prepare_pos = (home_idx == wxNOT_FOUND) ? 0 : static_cast<size_t>(home_idx) + 1;
|
||||
m_tabpanel->InsertPage(prepare_pos, TAB_ID_PREPARE, m_plater, _L("Prepare"), "tab_3d_active");
|
||||
m_tabpanel->InsertPage(prepare_pos + 1, TAB_ID_PREVIEW, m_plater, _L("Preview"), "tab_preview_active");
|
||||
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 0);
|
||||
|
||||
m_tabpanel->Bind(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, [this](wxCommandEvent& evt)
|
||||
{
|
||||
// jump to 3deditor under preview_only mode
|
||||
if (evt.GetId() == tp3DEditor){
|
||||
if (evt.GetId() == m_tabpanel->FindPageByName(TAB_ID_PREPARE)) {
|
||||
Sidebar& sidebar = GUI::wxGetApp().sidebar();
|
||||
if (sidebar.need_auto_sync_after_connect_printer()) {
|
||||
sidebar.set_need_auto_sync_after_connect_printer(false);
|
||||
@@ -1107,6 +1110,9 @@ void MainFrame::update_edge_panels()
|
||||
void MainFrame::shutdown()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "MainFrame::shutdown enter";
|
||||
if (m_project != nullptr)
|
||||
m_project->shutdown();
|
||||
m_plugin_pages.shutdown();
|
||||
#ifdef __WXGTK__
|
||||
// Edge panels are child windows — wxWidgets destroys them automatically.
|
||||
m_edge_bottom = nullptr;
|
||||
@@ -1252,15 +1258,14 @@ void MainFrame::init_tabpanel() {
|
||||
#endif
|
||||
//BBS
|
||||
wxWindow* panel = m_tabpanel->GetCurrentPage();
|
||||
int sel = m_tabpanel->GetSelection();
|
||||
//wxString page_text = m_tabpanel->GetPageText(sel);
|
||||
m_last_selected_tab = m_tabpanel->GetSelection();
|
||||
m_last_selected_tab = m_tabpanel->GetSelectedPageName();
|
||||
if (panel == m_plater) {
|
||||
if (sel == tp3DEditor) {
|
||||
if (m_last_selected_tab == TAB_ID_PREPARE) {
|
||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLVIEWTOOLBAR_3D));
|
||||
m_param_panel->OnActivate();
|
||||
}
|
||||
else if (sel == tpPreview) {
|
||||
else if (m_last_selected_tab == TAB_ID_PREVIEW) {
|
||||
m_plater->reset_check_status();
|
||||
if (!m_plater->check_ams_status(m_slice_select == eSliceAll))
|
||||
return;
|
||||
@@ -1275,7 +1280,7 @@ void MainFrame::init_tabpanel() {
|
||||
//monitor
|
||||
}
|
||||
#ifndef __APPLE__
|
||||
if (sel == tp3DEditor) {
|
||||
if (m_last_selected_tab == TAB_ID_PREPARE) {
|
||||
m_topbar->EnableUndoRedoItems();
|
||||
}
|
||||
else {
|
||||
@@ -1285,34 +1290,16 @@ void MainFrame::init_tabpanel() {
|
||||
|
||||
if (panel)
|
||||
panel->SetFocus();
|
||||
|
||||
/*switch (sel) {
|
||||
case TabPosition::tpHome:
|
||||
show_option(false);
|
||||
break;
|
||||
case TabPosition::tp3DEditor:
|
||||
show_option(true);
|
||||
break;
|
||||
case TabPosition::tpPreview:
|
||||
show_option(true);
|
||||
break;
|
||||
case TabPosition::tpMonitor:
|
||||
show_option(false);
|
||||
break;
|
||||
default:
|
||||
show_option(false);
|
||||
break;
|
||||
}*/
|
||||
});
|
||||
|
||||
if (wxGetApp().is_editor()) {
|
||||
m_webview = new WebViewPanel(m_tabpanel);
|
||||
Bind(EVT_LOAD_URL, [this](wxCommandEvent &evt) {
|
||||
wxString url = evt.GetString();
|
||||
select_tab(MainFrame::tpHome);
|
||||
select_tab(TAB_ID_HOME);
|
||||
m_webview->load_url(url);
|
||||
});
|
||||
m_tabpanel->AddPage(m_webview, "", "tab_home_active", "tab_home_active", false);
|
||||
m_tabpanel->AddPage(TAB_ID_HOME, m_webview, "", "tab_home_active");
|
||||
m_param_panel = new ParamsPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
|
||||
}
|
||||
|
||||
@@ -1327,7 +1314,7 @@ void MainFrame::init_tabpanel() {
|
||||
//BBS add pages
|
||||
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_monitor->SetBackgroundColour(*wxWHITE);
|
||||
m_tabpanel->AddPage(m_monitor, _L("Device"), std::string("tab_monitor_active"), std::string("tab_monitor_active"), false);
|
||||
m_tabpanel->AddPage(TAB_ID_MONITOR, m_monitor, _L("Device"), "tab_monitor_active");
|
||||
|
||||
m_printer_view = new PrinterWebView(m_tabpanel);
|
||||
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent &evt) {
|
||||
@@ -1342,16 +1329,20 @@ void MainFrame::init_tabpanel() {
|
||||
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_multi_machine->SetBackgroundColour(*wxWHITE);
|
||||
// TODO: change the bitmap
|
||||
m_tabpanel->AddPage(m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"), std::string("tab_multi_active"), false);
|
||||
m_tabpanel->AddPage(TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active");
|
||||
}
|
||||
|
||||
m_project = new ProjectPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_project->SetBackgroundColour(*wxWHITE);
|
||||
m_tabpanel->AddPage(m_project, _L("Project"), std::string("tab_auxiliary_active"), std::string("tab_auxiliary_active"), false);
|
||||
m_tabpanel->AddPage(TAB_ID_PROJECT, m_project, _L("Project"), "tab_auxiliary_active");
|
||||
|
||||
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_calibration->SetBackgroundColour(*wxWHITE);
|
||||
m_tabpanel->AddPage(m_calibration, _L("Calibration"), std::string("tab_calibration_active"), std::string("tab_calibration_active"), false);
|
||||
m_tabpanel->AddPage(TAB_ID_CALIBRATION, m_calibration, _L("Calibration"), "tab_calibration_active");
|
||||
|
||||
// Plugin pages are appended after the built-in tabs; their ids are namespaced
|
||||
// (plugin.<plugin_key>.<name>) so they can't collide with the built-in TAB_ID_* constants.
|
||||
m_plugin_pages.initialize(m_tabpanel);
|
||||
|
||||
if (m_plater) {
|
||||
// load initial config
|
||||
@@ -1373,10 +1364,15 @@ void MainFrame::show_device(bool should_use_native) {
|
||||
|
||||
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
|
||||
|
||||
// The web page is appended when printer agents are enabled. Remove that
|
||||
// extra page before switching back to the normal native/Web layout.
|
||||
if (!use_printer_agents) {
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND && idx != tpMonitor) {
|
||||
// The web Device page is the extra tab printer-agents mode shows alongside the native one.
|
||||
// Printers that drive the native Bambu device tab have nothing to put in it, so they don't
|
||||
// get it — otherwise a Bambu user sees two Device tabs, one of them permanently empty.
|
||||
const bool want_web_device_tab = use_printer_agents && wxGetApp().preset_bundle != nullptr &&
|
||||
!wxGetApp().preset_bundle->use_bbl_device_tab();
|
||||
|
||||
// Remove the extra page before switching to any layout that shouldn't have it.
|
||||
if (!want_web_device_tab) {
|
||||
if ((idx = m_tabpanel->FindPageByName(TAB_ID_MONITOR_WEB)) != wxNOT_FOUND) {
|
||||
m_printer_view->Show(false);
|
||||
m_tabpanel->RemovePage(idx);
|
||||
}
|
||||
@@ -1394,8 +1390,8 @@ void MainFrame::show_device(bool should_use_native) {
|
||||
m_tabpanel->RemovePage(idx);
|
||||
}
|
||||
m_monitor->Show(false);
|
||||
m_tabpanel->InsertPage(tpMonitor, m_monitor, _L("Device"), std::string("tab_monitor_active"),
|
||||
std::string("tab_monitor_active"));
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
|
||||
_L("Device"), "tab_monitor_active");
|
||||
}
|
||||
|
||||
if (m_printer_view == nullptr) {
|
||||
@@ -1416,28 +1412,31 @@ void MainFrame::show_device(bool should_use_native) {
|
||||
// TODO: change the bitmap
|
||||
if (m_tabpanel->FindPage(m_multi_machine) == wxNOT_FOUND) {
|
||||
m_multi_machine->Show(false);
|
||||
m_tabpanel->InsertPage(tpMultiDevice, m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"),
|
||||
std::string("tab_multi_active"), false);
|
||||
// Past the web Device tab when it is already there, so enabling multi-machine
|
||||
// later can't wedge this page between the two Device tabs.
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR_WEB, TAB_ID_MONITOR}),
|
||||
TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active");
|
||||
}
|
||||
}
|
||||
if (!m_calibration) {
|
||||
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_calibration->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
// Calibration is always the last page, so don't use InsertPage here. Otherwise, if multi_machine page is not enabled,
|
||||
// the calibration tab won't be properly added as well, due to the TabPosition::tpCalibration no longer matches the real tab position.
|
||||
if (m_tabpanel->FindPage(m_calibration) == wxNOT_FOUND) {
|
||||
m_calibration->Show(false);
|
||||
m_tabpanel->AddPage(m_calibration, _L("Calibration"), std::string("tab_calibration_active"),
|
||||
std::string("tab_calibration_active"), false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
|
||||
_L("Calibration"), "tab_calibration_active");
|
||||
}
|
||||
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) {
|
||||
m_printer_view->Show(false);
|
||||
m_tabpanel->AddPage(m_printer_view, _L("Device (Web)"), std::string("tab_monitor_active"),
|
||||
std::string("tab_monitor_active"), false);
|
||||
} else {
|
||||
m_tabpanel->SetPageText(idx, _L("Device (Web)"));
|
||||
if (want_web_device_tab) {
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) {
|
||||
m_printer_view->Show(false);
|
||||
// Immediately right of the native Device tab, not at the end of the tab bar.
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MONITOR_WEB,
|
||||
m_printer_view, _L("Device (Web)"), "tab_monitor_active");
|
||||
} else {
|
||||
m_tabpanel->SetPageText(idx, _L("Device (Web)"));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
@@ -1445,6 +1444,7 @@ void MainFrame::show_device(bool should_use_native) {
|
||||
#endif // _MSW_DARK_MODE
|
||||
|
||||
fit_tab_labels(); // ORCA on printer change
|
||||
m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -1466,7 +1466,8 @@ void MainFrame::show_device(bool should_use_native) {
|
||||
m_monitor->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
m_monitor->Show(false);
|
||||
m_tabpanel->InsertPage(tpMonitor, m_monitor, _L("Device"), std::string("tab_monitor_active"), std::string("tab_monitor_active"));
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
|
||||
_L("Device"), "tab_monitor_active");
|
||||
|
||||
if (wxGetApp().is_enable_multi_machine()) {
|
||||
if (!m_multi_machine) {
|
||||
@@ -1475,18 +1476,18 @@ void MainFrame::show_device(bool should_use_native) {
|
||||
}
|
||||
// TODO: change the bitmap
|
||||
m_multi_machine->Show(false);
|
||||
m_tabpanel->InsertPage(tpMultiDevice, m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"),
|
||||
std::string("tab_multi_active"), false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MULTI_DEVICE, m_multi_machine,
|
||||
_L("Multi-device"), "tab_multi_active");
|
||||
}
|
||||
if (!m_calibration) {
|
||||
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_calibration->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
m_calibration->Show(false);
|
||||
// Calibration is always the last page, so don't use InsertPage here. Otherwise, if multi_machine page is not enabled,
|
||||
// the calibration tab won't be properly added as well, due to the TabPosition::tpCalibration no longer matches the real tab position.
|
||||
m_tabpanel->AddPage(m_calibration, _L("Calibration"), std::string("tab_calibration_active"),
|
||||
std::string("tab_calibration_active"), false);
|
||||
// Last of the built-in tabs, but plugin tabs already sit past it — anchor rather than
|
||||
// append, so its position doesn't depend on the relayout() below running afterwards.
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
|
||||
_L("Calibration"), "tab_calibration_active");
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
@@ -1519,10 +1520,17 @@ void MainFrame::show_device(bool should_use_native) {
|
||||
});
|
||||
}
|
||||
m_printer_view->Show(false);
|
||||
m_tabpanel->InsertPage(tpMonitor, m_printer_view, _L("Device"), std::string("tab_monitor_active"),
|
||||
std::string("tab_monitor_active"));
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_printer_view,
|
||||
_L("Device"), "tab_monitor_active");
|
||||
}
|
||||
fit_tab_labels(); // ORCA on printer change
|
||||
m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
|
||||
}
|
||||
|
||||
bool MainFrame::is_prepare_or_preview_tab() const
|
||||
{
|
||||
const wxString tab = m_tabpanel->GetSelectedPageName();
|
||||
return tab == TAB_ID_PREPARE || tab == TAB_ID_PREVIEW;
|
||||
}
|
||||
|
||||
void MainFrame::fit_tab_labels()
|
||||
@@ -1554,7 +1562,7 @@ void MainFrame::fit_tab_labels()
|
||||
bool MainFrame::preview_only_hint()
|
||||
{
|
||||
if (m_plater && (m_plater->only_gcode_mode() || (m_plater->using_exported_file()))) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2% in preview mode")%m_tabpanel->GetSelection() %tp3DEditor;
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2% in preview mode")%m_tabpanel->GetSelectedPageName() %wxString(TAB_ID_PREPARE);
|
||||
|
||||
ConfirmBeforeSendDialog confirm_dlg(this, wxID_ANY, _L("Warning"));
|
||||
confirm_dlg.Bind(EVT_SECONDARY_CHECK_CONFIRM, [this](wxCommandEvent& e) {
|
||||
@@ -1872,22 +1880,22 @@ bool MainFrame::can_clone() const {
|
||||
|
||||
bool MainFrame::can_select() const
|
||||
{
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->model().objects.empty();
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->model().objects.empty();
|
||||
}
|
||||
|
||||
bool MainFrame::can_deselect() const
|
||||
{
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->is_selection_empty();
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->is_selection_empty();
|
||||
}
|
||||
|
||||
bool MainFrame::can_delete() const
|
||||
{
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->is_selection_empty();
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->is_selection_empty();
|
||||
}
|
||||
|
||||
bool MainFrame::can_delete_all() const
|
||||
{
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->model().objects.empty();
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->model().objects.empty();
|
||||
}
|
||||
|
||||
bool MainFrame::can_reslice() const
|
||||
@@ -1996,7 +2004,7 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_ALL));
|
||||
else
|
||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE));
|
||||
this->m_tabpanel->SetSelection(tpPreview);
|
||||
this->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3143,7 +3151,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
wxGetApp().app_config->set_bool("auto_perspective", !wxGetApp().app_config->get_bool("auto_perspective"));
|
||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
||||
},
|
||||
this, [this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview; },
|
||||
this, [this]() { return is_prepare_or_preview_tab(); },
|
||||
[this]() { return wxGetApp().app_config->get_bool("auto_perspective"); }, this);
|
||||
|
||||
viewMenu->AppendSeparator();
|
||||
@@ -3152,7 +3160,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
wxGetApp().toggle_show_gcode_window();
|
||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
||||
},
|
||||
this, [this]() { return m_tabpanel->GetSelection() == tpPreview; },
|
||||
this, [this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW; },
|
||||
[this]() { return wxGetApp().show_gcode_window(); }, this);
|
||||
|
||||
append_menu_check_item(
|
||||
@@ -3161,7 +3169,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
wxGetApp().toggle_show_3d_navigator();
|
||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
||||
},
|
||||
this, [this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview; },
|
||||
this, [this]() { return is_prepare_or_preview_tab(); },
|
||||
[this]() { return wxGetApp().show_3d_navigator(); }, this);
|
||||
|
||||
append_menu_check_item(viewMenu, wxID_ANY, _L("Show Gridlines"), _L("Show Gridlines on plate"),
|
||||
@@ -3169,15 +3177,14 @@ void MainFrame::init_menubar_as_editor()
|
||||
wxGetApp().toggle_show_plate_gridlines();
|
||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
||||
}, this,
|
||||
[this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview; },
|
||||
[this]() { return is_prepare_or_preview_tab(); },
|
||||
[this]() { return wxGetApp().show_plate_gridlines(); }, this);
|
||||
|
||||
append_menu_item(
|
||||
viewMenu, wxID_ANY, _L("Reset Window Layout"), _L("Reset to default window layout"),
|
||||
[this](wxCommandEvent&) { m_plater->reset_window_layout(); }, "", this,
|
||||
[this]() {
|
||||
return (m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview) &&
|
||||
m_plater->is_sidebar_enabled();
|
||||
return is_prepare_or_preview_tab() && m_plater->is_sidebar_enabled();
|
||||
},
|
||||
this);
|
||||
|
||||
@@ -3199,7 +3206,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
wxGetApp().toggle_show_outline();
|
||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
||||
},
|
||||
this, [this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor; },
|
||||
this, [this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE; },
|
||||
[this]() { return wxGetApp().show_outline(); }, this);
|
||||
|
||||
/*viewMenu->AppendSeparator();
|
||||
@@ -4000,13 +4007,16 @@ void MainFrame::select_tab(wxPanel* panel)
|
||||
wxGetApp().params_dialog()->Popup();
|
||||
return;
|
||||
}
|
||||
// Not panel->GetName(): Prepare and Preview share the single m_plater window, so the
|
||||
// window has no one correct name. The slot -> id lookup is the only correct resolution.
|
||||
int page_idx = m_tabpanel->FindPage(panel);
|
||||
if (page_idx == tp3DEditor && m_tabpanel->GetSelection() == tpPreview)
|
||||
wxString page_name = (page_idx == wxNOT_FOUND) ? wxString() : m_tabpanel->GetPageName(static_cast<size_t>(page_idx));
|
||||
if (page_name == TAB_ID_PREPARE && m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW)
|
||||
return;
|
||||
//BBS GUI refactor: remove unused layout new/dlg
|
||||
/*if (page_idx != wxNOT_FOUND && m_layout == ESettingsLayout::Dlg)
|
||||
page_idx++;*/
|
||||
select_tab(size_t(page_idx));
|
||||
select_tab(page_name);
|
||||
}
|
||||
|
||||
//BBS
|
||||
@@ -4014,7 +4024,7 @@ void MainFrame::jump_to_monitor(std::string dev_id)
|
||||
{
|
||||
if(!m_monitor)
|
||||
return;
|
||||
m_tabpanel->SetSelection(tpMonitor);
|
||||
m_tabpanel->SelectPageByName(TAB_ID_MONITOR);
|
||||
if (!dev_id.empty()) {
|
||||
((MonitorPanel*)m_monitor)->select_machine(dev_id);
|
||||
}
|
||||
@@ -4024,26 +4034,26 @@ void MainFrame::jump_to_multipage()
|
||||
{
|
||||
if(!m_multi_machine)
|
||||
return;
|
||||
m_tabpanel->SetSelection(tpMultiDevice);
|
||||
m_tabpanel->SelectPageByName(TAB_ID_MULTI_DEVICE);
|
||||
((MultiMachinePage*)m_multi_machine)->jump_to_send_page();
|
||||
}
|
||||
|
||||
|
||||
//BBS GUI refactor: remove unused layout new/dlg
|
||||
void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
|
||||
void MainFrame::select_tab(const wxString& id/* = wxString()*/)
|
||||
{
|
||||
//bool tabpanel_was_hidden = false;
|
||||
|
||||
// Controls on page are created on active page of active tab now.
|
||||
// We should select/activate tab before its showing to avoid an UI-flickering
|
||||
auto select = [this, tab](bool was_hidden) {
|
||||
// when tab == -1, it means we should show the last selected tab
|
||||
auto select = [this, id](bool was_hidden) {
|
||||
// when id is empty, it means we should show the last selected tab
|
||||
//BBS GUI refactor: remove unused layout new/dlg
|
||||
//size_t new_selection = tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab;
|
||||
size_t new_selection = tab == (size_t)(-1) ? m_last_selected_tab : tab;
|
||||
wxString new_selection = id.empty() ? m_last_selected_tab : id;
|
||||
|
||||
if (m_tabpanel->GetSelection() != (int)new_selection)
|
||||
m_tabpanel->SetSelection(new_selection);
|
||||
if (m_tabpanel->GetSelectedPageName() != new_selection)
|
||||
m_tabpanel->SelectPageByName(new_selection);
|
||||
#ifdef _MSW_DARK_MODE
|
||||
/*if (wxGetApp().tabs_as_menu()) {
|
||||
if (Tab* cur_tab = dynamic_cast<Tab*>(m_tabpanel->GetPage(new_selection)))
|
||||
@@ -4052,10 +4062,12 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
|
||||
m_plater->get_current_canvas3D()->render();
|
||||
}*/
|
||||
#endif
|
||||
if (tab == MainFrame::tp3DEditor && m_layout == ESettingsLayout::Old)
|
||||
// Intentionally `id`, not `new_selection`: the fallback-to-last-tab path must not
|
||||
// trigger this render even when the last selected tab was Prepare.
|
||||
if (id == TAB_ID_PREPARE && m_layout == ESettingsLayout::Old)
|
||||
m_plater->canvas3D()->render();
|
||||
else if (was_hidden) {
|
||||
Tab* cur_tab = dynamic_cast<Tab*>(m_tabpanel->GetPage(new_selection));
|
||||
Tab* cur_tab = dynamic_cast<Tab*>(m_tabpanel->GetPageByName(new_selection));
|
||||
if (cur_tab)
|
||||
cur_tab->OnActivate();
|
||||
}
|
||||
@@ -4064,10 +4076,10 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
|
||||
select(false);
|
||||
}
|
||||
|
||||
void MainFrame::request_select_tab(TabPosition pos)
|
||||
void MainFrame::request_select_tab(const wxString& id)
|
||||
{
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_SELECT_TAB);
|
||||
evt->SetInt(pos);
|
||||
evt->SetString(id);
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
@@ -4359,7 +4371,7 @@ void MainFrame::load_printer_url()
|
||||
}
|
||||
}
|
||||
|
||||
bool MainFrame::is_printer_view() const { return m_tabpanel->GetSelection() == TabPosition::tpMonitor; }
|
||||
bool MainFrame::is_printer_view() const { return m_tabpanel->GetSelectedPageName() == TAB_ID_MONITOR; }
|
||||
|
||||
|
||||
void MainFrame::refresh_plugin_tips()
|
||||
|
||||
@@ -35,6 +35,21 @@
|
||||
#include "PrinterWebView.hpp"
|
||||
#include "calib_dlg.hpp"
|
||||
#include "MultiMachinePage.hpp"
|
||||
#include "slic3r/plugin/host/PluginPages.hpp"
|
||||
|
||||
// Stable identifiers for MainFrame::m_tabpanel's built-in pages. These are
|
||||
// names rather than positional indices so optional pages cannot shift them.
|
||||
#define TAB_ID_HOME "home"
|
||||
#define TAB_ID_PREPARE "prepare"
|
||||
#define TAB_ID_PREVIEW "preview"
|
||||
#define TAB_ID_MONITOR "monitor"
|
||||
// Printer-agents mode shows the legacy web page alongside the native Device tab, so it needs an
|
||||
// id of its own: sharing TAB_ID_MONITOR makes every name lookup resolve to whichever of the two
|
||||
// comes first, which silently defeats PluginPages' selection round-trip across a tab relayout.
|
||||
#define TAB_ID_MONITOR_WEB "monitor_web"
|
||||
#define TAB_ID_MULTI_DEVICE "multi_device"
|
||||
#define TAB_ID_PROJECT "project"
|
||||
#define TAB_ID_CALIBRATION "calibration"
|
||||
|
||||
#define ENABEL_PRINT_ALL 0
|
||||
|
||||
@@ -115,7 +130,7 @@ class MainFrame : public DPIFrame
|
||||
wxMenuItem* m_menu_item_reslice_now { nullptr };
|
||||
wxSizer* m_main_sizer{ nullptr };
|
||||
|
||||
size_t m_last_selected_tab;
|
||||
wxString m_last_selected_tab;
|
||||
|
||||
std::string get_base_name(const wxString &full_name, const char *extension = nullptr) const;
|
||||
std::string get_dir_name(const wxString &full_name) const;
|
||||
@@ -214,19 +229,6 @@ public:
|
||||
#ifdef __APPLE__
|
||||
bool get_mac_full_screen() { return m_mac_fullscreen; }
|
||||
#endif
|
||||
//BBS GUI refactor
|
||||
enum TabPosition
|
||||
{
|
||||
tpHome = 0,
|
||||
tp3DEditor = 1,
|
||||
tpPreview = 2,
|
||||
tpMonitor = 3,
|
||||
tpMultiDevice = 4,
|
||||
tpProject = 5,
|
||||
tpCalibration = 6,
|
||||
tpAuxiliary = 7,
|
||||
toDebugTool = 8,
|
||||
};
|
||||
|
||||
//BBS: add slice&&print status update logic
|
||||
enum SlicePrintEventType
|
||||
@@ -326,8 +328,8 @@ public:
|
||||
// When tab == -1, will be selected last selected tab
|
||||
//BBS: GUI refactor
|
||||
void select_tab(wxPanel* panel);
|
||||
void select_tab(size_t tab = size_t(-1));
|
||||
void request_select_tab(TabPosition pos);
|
||||
void select_tab(const wxString& id = wxString());
|
||||
void request_select_tab(const wxString& id);
|
||||
int get_calibration_curr_tab();
|
||||
void select_view(const std::string& direction);
|
||||
// Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig
|
||||
@@ -360,6 +362,9 @@ public:
|
||||
//SoftFever
|
||||
void show_device(bool should_use_native);
|
||||
void fit_tab_labels(); // ORCA
|
||||
// True while either of the two tabs backed by m_plater is selected.
|
||||
bool is_prepare_or_preview_tab() const;
|
||||
PluginPages& plugin_pages() { return m_plugin_pages; }
|
||||
|
||||
PA_Calibration_Dlg* m_pa_calib_dlg{ nullptr };
|
||||
FlowRateCalibrationDialog* m_flow_rate_calib_dlg{ nullptr };
|
||||
@@ -385,7 +390,8 @@ public:
|
||||
CalibrationPanel* m_calibration{ nullptr };
|
||||
WebViewPanel* m_webview { nullptr };
|
||||
PrinterWebView* m_printer_view{nullptr};
|
||||
wxLogWindow* m_log_window { nullptr };
|
||||
PluginPages m_plugin_pages;
|
||||
wxLogWindow* m_log_window { nullptr };
|
||||
// BBS
|
||||
//wxBookCtrlBase* m_tabpanel { nullptr };
|
||||
Notebook* m_tabpanel{ nullptr };
|
||||
|
||||
@@ -186,17 +186,17 @@ void MonitorPanel::init_tabpanel()
|
||||
|
||||
//m_status_add_machine_panel = new AddMachinePanel(m_tabpanel);
|
||||
m_status_info_panel = new StatusPanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_status_info_panel, _L("Status"), "", true);
|
||||
m_tabpanel->AddPage(m_status_info_panel, _L("Status"), true);
|
||||
|
||||
m_media_file_panel = new MediaFilePanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_media_file_panel, _L("Storage"), "", false);
|
||||
//m_tabpanel->AddPage(m_media_file_panel, _L("Internal Storage"), "", false);
|
||||
m_tabpanel->AddPage(m_media_file_panel, _L("Storage"), false);
|
||||
//m_tabpanel->AddPage(m_media_file_panel, _L("Internal Storage"), false);
|
||||
|
||||
m_upgrade_panel = new UpgradePanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_upgrade_panel, _L_CONTEXT(L_CONTEXT("Update", "Firmware"), "Firmware"), "", false);
|
||||
m_tabpanel->AddPage(m_upgrade_panel, _L_CONTEXT(L_CONTEXT("Update", "Firmware"), "Firmware"), false);
|
||||
|
||||
m_hms_panel = new HMSPanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), "", false);
|
||||
m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), false);
|
||||
|
||||
std::string network_ver = Slic3r::NetworkAgent::get_version();
|
||||
if (!network_ver.empty()) {
|
||||
@@ -413,7 +413,10 @@ void MonitorPanel::update_hms_tag()
|
||||
bool MonitorPanel::Show(bool show)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
wxGetApp().mainframe->SetMinSize(wxGetApp().plater()->GetMinSize());
|
||||
// Notebook::InsertPage() hides every page it appends, so this also runs while MainFrame is
|
||||
// still constructing, before GUI_App::mainframe is assigned. Same guard as Plater::Show().
|
||||
if (wxGetApp().mainframe)
|
||||
wxGetApp().mainframe->SetMinSize(wxGetApp().plater()->GetMinSize());
|
||||
#endif
|
||||
|
||||
NetworkAgent* m_agent = wxGetApp().getAgent();
|
||||
|
||||
@@ -86,9 +86,9 @@ void MultiMachinePage::init_tabpanel()
|
||||
m_cloud_task_manager = new CloudTaskManagerPage(m_tabpanel);
|
||||
m_machine_manager = new MultiMachineManagerPage(m_tabpanel);
|
||||
|
||||
m_tabpanel->AddPage(m_machine_manager, _L("Device"), "", true);
|
||||
m_tabpanel->AddPage(m_local_task_manager, _L("Task Sending"), "", false);
|
||||
m_tabpanel->AddPage(m_cloud_task_manager, _L("Task Sent"), "", false);
|
||||
m_tabpanel->AddPage(m_machine_manager, _L("Device"), true);
|
||||
m_tabpanel->AddPage(m_local_task_manager, _L("Task Sending"), false);
|
||||
m_tabpanel->AddPage(m_cloud_task_manager, _L("Task Sent"), false);
|
||||
}
|
||||
|
||||
void MultiMachinePage::init_timer()
|
||||
|
||||
@@ -120,11 +120,11 @@ void ButtonsListCtrl::Rescale()
|
||||
|
||||
void ButtonsListCtrl::SetSelection(int sel)
|
||||
{
|
||||
if (m_selection == sel)
|
||||
if (m_selection == sel && sel >= 0 && sel < static_cast<int>(m_pageButtons.size()))
|
||||
return;
|
||||
// BBS: change button color
|
||||
wxColour selected_btn_bg("#009688"); // Gradient #009688
|
||||
if (m_selection >= 0) {
|
||||
if (m_selection >= 0 && m_selection < static_cast<int>(m_pageButtons.size())) {
|
||||
StateColor bg_color = StateColor(
|
||||
std::pair{wxColour(107, 107, 107), (int) StateColor::Hovered},
|
||||
std::pair{wxColour(59, 68, 70), (int) StateColor::Normal});
|
||||
@@ -132,9 +132,15 @@ void ButtonsListCtrl::SetSelection(int sel)
|
||||
StateColor text_color = StateColor(
|
||||
std::pair{wxColour(254,254, 254), (int) StateColor::Normal}
|
||||
);
|
||||
m_pageButtons[m_selection]->SetSelected(false);
|
||||
m_pageButtons[m_selection]->SetTextColor(text_color);
|
||||
}
|
||||
|
||||
if (sel < 0 || sel >= static_cast<int>(m_pageButtons.size())) {
|
||||
m_selection = -1;
|
||||
Refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
m_selection = sel;
|
||||
|
||||
StateColor bg_color = StateColor(
|
||||
@@ -145,17 +151,19 @@ void ButtonsListCtrl::SetSelection(int sel)
|
||||
StateColor text_color = StateColor(
|
||||
std::pair{wxColour(254, 254, 254), (int) StateColor::Normal}
|
||||
);
|
||||
m_pageButtons[m_selection]->SetSelected(true);
|
||||
m_pageButtons[m_selection]->SetTextColor(text_color);
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /* = false*/, const std::string &bmp_name /* = ""*/, const std::string &inactive_bmp_name)
|
||||
bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /* = false*/, const std::string &bmp_name /* = ""*/, const wxBitmap &bmp /* = wxNullBitmap */)
|
||||
{
|
||||
Button * btn = new Button(this, text.empty() ? text : " " + text, bmp_name, wxNO_BORDER);
|
||||
btn->SetCornerRadius(0);
|
||||
|
||||
if (bmp_name.empty() && bmp.IsOk())
|
||||
btn->SetIcon(bmp);
|
||||
|
||||
int em = em_unit(this);
|
||||
//BBS set size for button
|
||||
btn->SetMinSize({(text.empty() ? 40 : 136) * em / 10, 36 * em / 10});
|
||||
@@ -168,8 +176,6 @@ bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /*
|
||||
StateColor text_color = StateColor(
|
||||
std::pair{wxColour(254,254, 254), (int) StateColor::Normal});
|
||||
btn->SetTextColor(text_color);
|
||||
btn->SetInactiveIcon(inactive_bmp_name);
|
||||
btn->SetSelected(false);
|
||||
btn->Bind(wxEVT_BUTTON, [this, btn](wxCommandEvent& event) {
|
||||
if (auto it = std::find(m_pageButtons.begin(), m_pageButtons.end(), btn); it != m_pageButtons.end()) {
|
||||
auto sel = it - m_pageButtons.begin();
|
||||
@@ -192,6 +198,14 @@ bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /*
|
||||
|
||||
void ButtonsListCtrl::RemovePage(size_t n)
|
||||
{
|
||||
if (n >= m_pageButtons.size())
|
||||
return;
|
||||
|
||||
if (m_selection == static_cast<int>(n))
|
||||
m_selection = -1;
|
||||
else if (m_selection > static_cast<int>(n))
|
||||
--m_selection;
|
||||
|
||||
Button* btn = m_pageButtons[n];
|
||||
m_pageButtons.erase(m_pageButtons.begin() + n);
|
||||
m_pageLabels.erase(m_pageLabels.begin() + n); // ORCA
|
||||
@@ -240,6 +254,24 @@ wxString ButtonsListCtrl::GetPageText(size_t n) const
|
||||
return btn->GetLabel();
|
||||
}
|
||||
|
||||
// ORCA
|
||||
void ButtonsListCtrl::SetOverflowButton(wxWindow* button)
|
||||
{
|
||||
if (m_overflow_button == button)
|
||||
return;
|
||||
|
||||
if (m_overflow_button != nullptr)
|
||||
m_sizer->Detach(m_overflow_button);
|
||||
|
||||
m_overflow_button = button;
|
||||
|
||||
if (m_overflow_button != nullptr)
|
||||
// Right after the tab buttons (index 0), ahead of any stretch spacer / side_tools.
|
||||
m_sizer->Insert(1, m_overflow_button, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxBOTTOM, m_btn_margin);
|
||||
|
||||
m_sizer->Layout();
|
||||
}
|
||||
|
||||
//#endif // _WIN32
|
||||
|
||||
void Notebook::Init()
|
||||
@@ -253,6 +285,8 @@ void Notebook::Init()
|
||||
|
||||
m_showTimeout = m_hideTimeout = 0;
|
||||
|
||||
m_pageNames.clear();
|
||||
|
||||
/* On Linux, Gstreamer wxMediaCtrl does not seem to get along well with
|
||||
* 32-bit X11 visuals (the overlay does not work). Is this a wxWindows
|
||||
* bug? Is this a Gstreamer bug? No idea, but it is our problem ...
|
||||
|
||||
+106
-33
@@ -3,7 +3,11 @@
|
||||
|
||||
//#ifdef _WIN32
|
||||
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <wx/bookctrl.h>
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/sizer.h>
|
||||
|
||||
class ScalableButton;
|
||||
@@ -23,13 +27,16 @@ public:
|
||||
void SetSelection(int sel);
|
||||
void UpdateMode();
|
||||
void Rescale();
|
||||
bool InsertPage(size_t n, const wxString &text, bool bSelect = false, const std::string &bmp_name = "", const std::string &inactive_bmp_name = "");
|
||||
bool InsertPage(size_t n, const wxString &text, bool bSelect = false, const std::string &bmp_name = "", const wxBitmap &bmp = wxNullBitmap);
|
||||
void RemovePage(size_t n);
|
||||
bool SetPageImage(size_t n, const std::string& bmp_name) const;
|
||||
void SetPageText(size_t n, const wxString& strText);
|
||||
void SetCompact(size_t n, bool compact); // ORCA
|
||||
wxString GetPageText(size_t n) const;
|
||||
wxFlexGridSizer* GetBtnsSizer(){return m_buttons_sizer;}; // ORCA
|
||||
// ORCA: a companion widget shown right after the tab buttons (before any side_tools), e.g.
|
||||
// an overflow indicator. Pass nullptr to remove it; ownership stays with the caller.
|
||||
void SetOverflowButton(wxWindow* button);
|
||||
|
||||
private:
|
||||
wxFlexGridSizer* m_buttons_sizer;
|
||||
@@ -40,9 +47,10 @@ private:
|
||||
int m_btn_margin;
|
||||
int m_line_margin;
|
||||
std::vector<wxString> m_pageLabels; // ORCA
|
||||
wxWindow* m_overflow_button{nullptr}; // ORCA
|
||||
};
|
||||
|
||||
class Notebook: public wxBookCtrlBase
|
||||
class Notebook : public wxBookCtrlBase
|
||||
{
|
||||
public:
|
||||
Notebook(wxWindow * parent,
|
||||
@@ -103,7 +111,7 @@ public:
|
||||
// by this control) and show it immediately.
|
||||
bool ShowNewPage(wxWindow * page)
|
||||
{
|
||||
return AddPage(page, wxString(), "", "");
|
||||
return AddPage(page, wxString(), false, NO_IMAGE);
|
||||
}
|
||||
|
||||
|
||||
@@ -135,51 +143,56 @@ public:
|
||||
|
||||
// Implement base class pure virtual methods.
|
||||
|
||||
// adds a new page to the control
|
||||
bool AddPage(wxWindow* page,
|
||||
// Page management. Every insertion funnels through the InsertPage() below; `id` is the
|
||||
// stable page name FindPageByName() resolves. Built-in tabs name a resource bitmap,
|
||||
// plugin pages hand over a ready wxBitmap; wx's own imageId overloads carry neither.
|
||||
bool AddPage(const wxString& id,
|
||||
wxWindow* page,
|
||||
const wxString& text,
|
||||
const std::string& bmp_name,
|
||||
const std::string& inactive_bmp_name,
|
||||
const std::string& bmp_name = "",
|
||||
bool bSelect = false)
|
||||
{
|
||||
DoInvalidateBestSize();
|
||||
return InsertPage(GetPageCount(), page, text, bmp_name, inactive_bmp_name, bSelect);
|
||||
return InsertPage(GetPageCount(), id, page, text, bmp_name, bSelect);
|
||||
}
|
||||
|
||||
// Page management
|
||||
virtual bool InsertPage(size_t n,
|
||||
wxWindow * page,
|
||||
const wxString & text,
|
||||
bool bSelect = false,
|
||||
int imageId = NO_IMAGE) override
|
||||
bool AddPage(wxWindow* page, const wxString& text, bool bSelect = false, int imageId = NO_IMAGE) override
|
||||
{
|
||||
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect, imageId))
|
||||
DoInvalidateBestSize();
|
||||
return InsertPage(GetPageCount(), page, text, bSelect, imageId);
|
||||
}
|
||||
|
||||
bool InsertPage(size_t n,
|
||||
const wxString& id,
|
||||
wxWindow * page,
|
||||
const wxString & text,
|
||||
const std::string& bmp_name = "",
|
||||
bool bSelect = false,
|
||||
const wxBitmap& bmp = wxNullBitmap)
|
||||
{
|
||||
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect))
|
||||
return false;
|
||||
|
||||
GetBtnsListCtrl()->InsertPage(n, text, bSelect);
|
||||
m_pageNames.insert(m_pageNames.begin() + n, id);
|
||||
GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name, bmp);
|
||||
|
||||
// wxBookCtrlBase::InsertPage() only inserts into the page list and sizes the new
|
||||
// page to the current page's rect — it never touches visibility, and a freshly
|
||||
// constructed page defaults to shown. Without this it renders on top of whatever
|
||||
// page is currently selected until the next SetSelection() call hides it.
|
||||
if (!DoSetSelectionAfterInsertion(n, bSelect))
|
||||
page->Hide();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InsertPage(size_t n,
|
||||
wxWindow * page,
|
||||
const wxString & text,
|
||||
const std::string& bmp_name = "",
|
||||
const std::string& inactive_bmp_name = "",
|
||||
bool bSelect = false)
|
||||
virtual bool InsertPage(size_t n,
|
||||
wxWindow * page,
|
||||
const wxString & text,
|
||||
bool bSelect = false,
|
||||
int WXUNUSED(imageId) = NO_IMAGE) override
|
||||
{
|
||||
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect))
|
||||
return false;
|
||||
|
||||
GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name, inactive_bmp_name);
|
||||
|
||||
if (bSelect)
|
||||
SetSelection(n);
|
||||
|
||||
return true;
|
||||
return InsertPage(n, wxString(), page, text, "", bSelect);
|
||||
}
|
||||
|
||||
virtual int SetSelection(size_t n) override
|
||||
@@ -211,8 +224,8 @@ public:
|
||||
return DoSetSelection(n);
|
||||
}
|
||||
|
||||
// Neither labels nor images are supported but we still store the labels
|
||||
// just in case the user code attaches some importance to them.
|
||||
// Labels are stored by the custom button list; wx's image-list API is unused — tab icons
|
||||
// are set directly on the buttons, either from a resource name or a ready wxBitmap.
|
||||
virtual bool SetPageText(size_t n, const wxString & strText) override
|
||||
{
|
||||
wxCHECK_MSG(n < GetPageCount(), false, wxS("Invalid page"));
|
||||
@@ -251,7 +264,64 @@ public:
|
||||
page->SetFocus();
|
||||
}
|
||||
|
||||
// The base clears its page list directly instead of calling DoRemovePage() per page,
|
||||
// which would leave m_pageNames behind. No caller today; kept in sync regardless.
|
||||
virtual bool DeleteAllPages() override
|
||||
{
|
||||
m_pageNames.clear();
|
||||
return wxBookCtrlBase::DeleteAllPages();
|
||||
}
|
||||
|
||||
ButtonsListCtrl* GetBtnsListCtrl() const { return static_cast<ButtonsListCtrl*>(m_bookctrl); }
|
||||
void SetOverflowButton(wxWindow* button) { GetBtnsListCtrl()->SetOverflowButton(button); }
|
||||
|
||||
// Insertion index just past the first of `ids` that is present, or the end of the bar
|
||||
// if none is — lets call sites state tab order as "after X" instead of re-deriving it.
|
||||
size_t PositionAfter(std::initializer_list<const char*> ids) const
|
||||
{
|
||||
for (const char* id : ids)
|
||||
if (const int idx = FindPageByName(id); idx != wxNOT_FOUND)
|
||||
return static_cast<size_t>(idx) + 1;
|
||||
return GetPageCount();
|
||||
}
|
||||
|
||||
int FindPageByName(const wxString& id) const
|
||||
{
|
||||
if (id.empty())
|
||||
return wxNOT_FOUND;
|
||||
for (size_t i = 0; i < m_pageNames.size(); ++i)
|
||||
if (m_pageNames[i] == id)
|
||||
return static_cast<int>(i);
|
||||
return wxNOT_FOUND;
|
||||
}
|
||||
|
||||
wxWindow* GetPageByName(const wxString& id) const
|
||||
{
|
||||
const int idx = FindPageByName(id);
|
||||
return idx == wxNOT_FOUND ? nullptr : GetPage(static_cast<size_t>(idx));
|
||||
}
|
||||
|
||||
bool SelectPageByName(const wxString& id)
|
||||
{
|
||||
const int idx = FindPageByName(id);
|
||||
if (idx == wxNOT_FOUND)
|
||||
return false;
|
||||
SetSelection(static_cast<size_t>(idx));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Inverse of FindPageByName: index -> id. Empty string for an out-of-range
|
||||
// index or a page that was never given an id (e.g. settings Tab pages).
|
||||
wxString GetPageName(size_t n) const
|
||||
{
|
||||
return n < m_pageNames.size() ? m_pageNames[n] : wxString();
|
||||
}
|
||||
|
||||
wxString GetSelectedPageName() const
|
||||
{
|
||||
const int sel = GetSelection();
|
||||
return sel < 0 ? wxString() : GetPageName(static_cast<size_t>(sel));
|
||||
}
|
||||
|
||||
void UpdateMode()
|
||||
{
|
||||
@@ -369,6 +439,7 @@ protected:
|
||||
wxWindow* const win = wxBookCtrlBase::DoRemovePage(page);
|
||||
if (win)
|
||||
{
|
||||
m_pageNames.erase(m_pageNames.begin() + page);
|
||||
GetBtnsListCtrl()->RemovePage(page);
|
||||
DoSetSelectionAfterRemoval(page);
|
||||
}
|
||||
@@ -394,6 +465,8 @@ protected:
|
||||
private:
|
||||
void Init();
|
||||
|
||||
std::vector<wxString> m_pageNames; // index-parallel to wxBookCtrlBase::m_pages
|
||||
|
||||
wxShowEffect m_showEffect,
|
||||
m_hideEffect;
|
||||
|
||||
|
||||
@@ -1918,7 +1918,7 @@ void NotificationManager::push_validate_error_notification(StringObjectException
|
||||
wxGetApp().sidebar().jump_to_option(opt, Preset::TYPE_PRINT, L"");
|
||||
}
|
||||
else {
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -1985,7 +1985,7 @@ void NotificationManager::push_validate_error_notification(StringObjectException
|
||||
wxGetApp().sidebar().jump_to_option(opt, opt_type, L"");
|
||||
}
|
||||
else {
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -2015,7 +2015,7 @@ void NotificationManager::push_slicing_error_notification(const std::string &tex
|
||||
if (iter != objects.end()) { ovs.push_back({ *iter, nullptr }); }
|
||||
}
|
||||
if (!ovs.empty()) {
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
wxGetApp().obj_list()->select_items(ovs);
|
||||
}
|
||||
return false;
|
||||
@@ -2046,7 +2046,7 @@ void NotificationManager::push_slicing_warning_notification(const std::string& t
|
||||
auto& objects = wxGetApp().model().objects;
|
||||
auto iter = std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; });
|
||||
if (iter != objects.end()) {
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
wxGetApp().obj_list()->select_items({ {*iter, nullptr} });
|
||||
}
|
||||
return false;
|
||||
@@ -2693,7 +2693,7 @@ void NotificationManager::push_slicing_serious_warning_notification(const std::s
|
||||
if (iter != objects.end()) { ovs.push_back({ *iter, nullptr }); }
|
||||
}
|
||||
if (!ovs.empty()) {
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
wxGetApp().obj_list()->select_items(ovs);
|
||||
wxGetApp().obj_list()->update_selections_on_canvas();
|
||||
}
|
||||
@@ -2777,7 +2777,7 @@ void NotificationManager::push_slicing_serious_warning_notification(const std::s
|
||||
}
|
||||
}
|
||||
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
|
||||
if (!sel_items.empty()) {
|
||||
obj_list->select_items(sel_items);
|
||||
|
||||
@@ -4445,8 +4445,6 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini
|
||||
//this may be happened after machine changed
|
||||
void PartPlateList::reset_size(int width, int depth, int height, bool reload_objects, bool update_shapes)
|
||||
{
|
||||
Vec3d origin1, origin2;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":before size: plate_width %1%, plate_depth %2%, plate_height %3%") % m_plate_width % m_plate_depth % m_plate_height;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":after size: plate_width %1%, plate_depth %2%, plate_height %3%") % width % depth % height;
|
||||
if ((m_plate_width != width) || (m_plate_depth != depth) || (m_plate_height != height))
|
||||
|
||||
+43
-32
@@ -5783,6 +5783,8 @@ private:
|
||||
bool show_warning_dialog { false };
|
||||
};
|
||||
|
||||
Plater::~Plater() = default;
|
||||
|
||||
const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf)", std::regex::icase);
|
||||
const std::regex Plater::priv::pattern_3mf(".*3mf", std::regex::icase);
|
||||
const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::icase);
|
||||
@@ -5797,7 +5799,7 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi
|
||||
#endif // WIN32
|
||||
|
||||
m_mainframe.Raise();
|
||||
m_mainframe.select_tab(size_t(MainFrame::tp3DEditor));
|
||||
m_mainframe.select_tab(TAB_ID_PREPARE);
|
||||
if (wxGetApp().is_editor())
|
||||
m_plater.select_view_3D("3D");
|
||||
|
||||
@@ -6579,9 +6581,9 @@ void Plater::priv::select_next_view_3D()
|
||||
{
|
||||
|
||||
if (current_panel == view3D)
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tpPreview));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
|
||||
else if (current_panel == preview)
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
// else if (current_panel == assemble_view)
|
||||
// set_current_panel(view3D);
|
||||
}
|
||||
@@ -7880,7 +7882,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
q->select_plate(first_plate_index);
|
||||
//set to 3d tab
|
||||
q->select_view_3D("Preview");
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tpPreview);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
|
||||
}
|
||||
else {
|
||||
//set to 3d tab
|
||||
@@ -7899,7 +7901,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
else {
|
||||
//always set to 3D after loading files
|
||||
q->select_view_3D("3D");
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
}
|
||||
|
||||
if (load_model) {
|
||||
@@ -8807,7 +8809,7 @@ void Plater::priv::process_validation_warning(StringObjectException const &warni
|
||||
}
|
||||
}
|
||||
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
|
||||
if (inst_idx != -1) {
|
||||
auto* model = wxGetApp().obj_list()->GetModel();
|
||||
@@ -8836,7 +8838,7 @@ void Plater::priv::process_validation_warning(StringObjectException const &warni
|
||||
} else {
|
||||
auto iter = id.id ? std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; }) : objects.end();
|
||||
if (iter != objects.end()) {
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
wxGetApp().obj_list()->select_items({{*iter, nullptr}});
|
||||
wxGetApp().obj_list()->update_selections_on_canvas();
|
||||
}
|
||||
@@ -11220,13 +11222,19 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
|
||||
}
|
||||
|
||||
const int new_sel = e.GetSelection();
|
||||
sidebar_layout.show = new_sel == MainFrame::tp3DEditor || new_sel == MainFrame::tpPreview;
|
||||
if (new_sel == wxNOT_FOUND) {
|
||||
// GetPage(new_sel) below needs a valid index.
|
||||
e.Skip();
|
||||
return;
|
||||
}
|
||||
const wxString new_name = main_frame->m_tabpanel->GetPageName(new_sel);
|
||||
sidebar_layout.show = new_name == TAB_ID_PREPARE || new_name == TAB_ID_PREVIEW;
|
||||
update_sidebar();
|
||||
int old_sel = e.GetOldSelection();
|
||||
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
|
||||
const bool use_native_device_tab = wxGetApp().preset_bundle &&
|
||||
(wxGetApp().preset_bundle->use_bbl_device_tab() || use_printer_agents);
|
||||
if (use_native_device_tab && new_sel == MainFrame::tpMonitor) {
|
||||
if (use_native_device_tab && new_name == TAB_ID_MONITOR) {
|
||||
// BBL network module is only required for BBL-vendor printers.
|
||||
// Non-BBL Python plugins (e.g. moonraker) drive the Device tab without it.
|
||||
if (!use_printer_agents && wxGetApp().preset_bundle->is_bbl_vendor() && !Slic3r::NetworkAgent::is_network_module_loaded()) {
|
||||
@@ -11238,12 +11246,15 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Pointer test, not a name lookup: in printer-agents mode this page is TAB_ID_MONITOR_WEB
|
||||
// while the native Device tab holds TAB_ID_MONITOR, and in legacy-web mode it holds
|
||||
// TAB_ID_MONITOR itself.
|
||||
const bool selecting_web_device_tab = main_frame->m_printer_view &&
|
||||
main_frame->m_tabpanel->GetPage(new_sel) == main_frame->m_printer_view;
|
||||
if (selecting_web_device_tab) {
|
||||
// Use the selected discovered machine when the preset has no host.
|
||||
main_frame->load_printer_url();
|
||||
} else if (new_sel == MainFrame::tpMonitor && wxGetApp().preset_bundle != nullptr) {
|
||||
} else if (new_name == TAB_ID_MONITOR && wxGetApp().preset_bundle != nullptr) {
|
||||
auto cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
||||
if (main_frame->m_printer_view && url.empty()) {
|
||||
@@ -12106,7 +12117,7 @@ bool Plater::priv::check_ams_status_impl(bool is_slice_all)
|
||||
wxPostEvent(q, SimpleEvent(EVT_GLTOOLBAR_SLICE_ALL));
|
||||
else
|
||||
wxPostEvent(q, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE));
|
||||
wxGetApp().mainframe->m_tabpanel->SetSelection(MainFrame::TabPosition::tpPreview);
|
||||
wxGetApp().mainframe->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -12674,7 +12685,7 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name, const UndoRed
|
||||
ModelWipeTower& tower = model.wipe_tower;
|
||||
|
||||
tower.positions[plate_idx] = Vec2d(tower_x_opt->get_at(plate_idx), tower_y_opt->get_at(plate_idx));
|
||||
tower.rotation = proj_cfg.opt_float("wipe_tower_rotation_angle");
|
||||
tower.rotation = config.opt_float("wipe_tower_rotation_angle");
|
||||
}
|
||||
}
|
||||
const GLGizmosManager& gizmos = get_current_canvas3D()->get_canvas_type() == GLCanvas3D::CanvasAssembleView ? assemble_view->get_canvas3d()->get_gizmos_manager() : view3D->get_canvas3d()->get_gizmos_manager();
|
||||
@@ -12784,7 +12795,7 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
|
||||
ModelWipeTower& tower = model.wipe_tower;
|
||||
|
||||
tower.positions[plate_idx] = Vec2d(tower_x_opt->get_at(plate_idx), tower_y_opt->get_at(plate_idx));
|
||||
tower.rotation = proj_cfg.opt_float("wipe_tower_rotation_angle");
|
||||
tower.rotation = config.opt_float("wipe_tower_rotation_angle");
|
||||
}
|
||||
}
|
||||
const int layer_range_idx = it_snapshot->snapshot_data.layer_range_idx;
|
||||
@@ -13063,7 +13074,7 @@ int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_
|
||||
get_notification_manager()->clear_all();
|
||||
|
||||
if (!silent)
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
|
||||
//get_partplate_list().reinit();
|
||||
//get_partplate_list().update_slice_context_to_current_plate(p->background_process);
|
||||
@@ -13212,7 +13223,7 @@ void Plater::load_project(wxString const& filename2,
|
||||
if (!m_exported_file) {
|
||||
p->select_view("topfront");
|
||||
p->camera.requires_zoom_to_plate = REQUIRES_ZOOM_TO_ALL_PLATE;
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
}
|
||||
else {
|
||||
p->partplate_list.select_plate_view();
|
||||
@@ -13326,7 +13337,7 @@ void Plater::import_model_id(wxString download_info)
|
||||
const int max_retries = 3;
|
||||
|
||||
/* jump to 3D eidtor */
|
||||
wxGetApp().mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
|
||||
/* prepare progress dialog */
|
||||
bool cont = true;
|
||||
@@ -13635,7 +13646,7 @@ void Plater::calib_pa(const Calib_Params& params)
|
||||
{
|
||||
const auto calib_pa_name = wxString::Format(L"Pressure Advance Test");
|
||||
new_project(false, false, calib_pa_name);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
print_config->set_key_value("overhang_reverse", new ConfigOptionBool(false));
|
||||
@@ -14116,7 +14127,7 @@ void Plater::calib_flowrate(bool is_linear, int pass, InfillPattern pattern) {
|
||||
if (new_project(false, false, calib_name) == wxID_CANCEL)
|
||||
return;
|
||||
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
|
||||
if (is_linear) {
|
||||
if (pass == 1)
|
||||
@@ -14153,7 +14164,7 @@ void Plater::calib_temp(const Calib_Params& params) {
|
||||
|
||||
const auto calib_temp_name = wxString::Format(L"Nozzle temperature test");
|
||||
new_project(false, false, calib_temp_name);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
if (params.mode != CalibMode::Calib_Temp_Tower) return;
|
||||
|
||||
if (!add_model(false, Slic3r::resources_dir() + "/calib/temperature_tower/temperature_tower.drc"))
|
||||
@@ -14233,7 +14244,7 @@ void Plater::calib_max_vol_speed(const Calib_Params& params)
|
||||
{
|
||||
const auto calib_vol_speed_name = wxString::Format(L"Max volumetric speed test");
|
||||
new_project(false, false, calib_vol_speed_name);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
if (params.mode != CalibMode::Calib_Vol_speed_Tower)
|
||||
return;
|
||||
if (!add_model(false, Slic3r::resources_dir() + "/calib/volumetric_speed/SpeedTestStructure.drc"))
|
||||
@@ -14312,7 +14323,7 @@ void Plater::calib_retraction(const Calib_Params& params)
|
||||
{
|
||||
const auto calib_retraction_name = wxString::Format(L"Retraction");
|
||||
new_project(false, false, calib_retraction_name);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
if (params.mode != CalibMode::Calib_Retraction_tower)
|
||||
return;
|
||||
|
||||
@@ -14372,7 +14383,7 @@ void Plater::calib_VFA(const Calib_Params& params)
|
||||
{
|
||||
const auto calib_vfa_name = wxString::Format(L"VFA test");
|
||||
new_project(false, false, calib_vfa_name);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
if (params.mode != CalibMode::Calib_VFA_Tower)
|
||||
return;
|
||||
|
||||
@@ -14455,7 +14466,7 @@ void Plater::calib_input_shaping_freq(const Calib_Params& params)
|
||||
{
|
||||
const auto calib_input_shaping_name = wxString::Format(L"Input shaping Frequency test");
|
||||
new_project(false, false, calib_input_shaping_name);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
if (params.mode != CalibMode::Calib_Input_shaping_freq)
|
||||
return;
|
||||
|
||||
@@ -14521,7 +14532,7 @@ void Plater::calib_input_shaping_damp(const Calib_Params& params)
|
||||
{
|
||||
const auto calib_input_shaping_name = wxString::Format(L"Input shaping Damping test");
|
||||
new_project(false, false, calib_input_shaping_name);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
if (params.mode != CalibMode::Calib_Input_shaping_damp)
|
||||
return;
|
||||
|
||||
@@ -14586,7 +14597,7 @@ void Plater::Calib_Cornering(const Calib_Params& params)
|
||||
{
|
||||
const auto Calib_Cornering = wxString::Format(L"Cornering test");
|
||||
new_project(false, false, Calib_Cornering);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
if (params.mode != CalibMode::Calib_Cornering)
|
||||
return;
|
||||
|
||||
@@ -14719,7 +14730,7 @@ void Plater::load_gcode(const wxString& filename)
|
||||
//p->gcode_result.reset();
|
||||
//reset_gcode_toolpaths();
|
||||
p->preview->reload_print(m_only_gcode);
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tpPreview);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
|
||||
p->set_current_panel(p->preview, true);
|
||||
p->get_current_canvas3D()->render();
|
||||
//p->notification_manager->bbl_show_plateinfo_notification(into_u8(_L("Preview only mode for gcode file.")));
|
||||
@@ -15392,7 +15403,7 @@ LoadType determine_load_type(std::string filename, std::string override_setting)
|
||||
wxGetApp().app_config->set("import_project_action", std::to_string(choice));
|
||||
|
||||
// BBS: jump to plater panel
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
return load_type;
|
||||
}
|
||||
|
||||
@@ -15621,7 +15632,7 @@ void Plater::reset_with_confirm()
|
||||
.ShowModal() == wxID_YES) {
|
||||
reset();
|
||||
// BBS: jump to plater panel
|
||||
wxGetApp().mainframe->select_tab(size_t(0));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_HOME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17435,7 +17446,7 @@ int Plater::export_config_3mf(int plate_idx, Export3mfProgressFn proFn)
|
||||
//BBS
|
||||
void Plater::send_calibration_job_finished(wxCommandEvent & evt)
|
||||
{
|
||||
p->main_frame->request_select_tab(MainFrame::TabPosition::tpCalibration);
|
||||
p->main_frame->request_select_tab(TAB_ID_CALIBRATION);
|
||||
auto calibration_panel = p->main_frame->m_calibration;
|
||||
if (calibration_panel) {
|
||||
auto curr_wizard = static_cast<CalibrationWizard*>(calibration_panel->get_tabpanel()->GetPage(evt.GetInt()));
|
||||
@@ -17467,7 +17478,7 @@ void Plater::print_job_finished(wxCommandEvent &evt)
|
||||
if (!dev) return;
|
||||
|
||||
dev->set_selected_machine(evt.GetString().ToStdString());
|
||||
p->main_frame->request_select_tab(MainFrame::TabPosition::tpMonitor);
|
||||
p->main_frame->request_select_tab(TAB_ID_MONITOR);
|
||||
//jump to monitor and select device status panel
|
||||
MonitorPanel* curr_monitor = p->main_frame->m_monitor;
|
||||
if(curr_monitor)
|
||||
@@ -17482,7 +17493,7 @@ void Plater::send_job_finished(wxCommandEvent& evt)
|
||||
|
||||
send_gcode_finish(evt.GetString());
|
||||
p->hide_send_to_printer_dlg();
|
||||
//p->main_frame->request_select_tab(MainFrame::TabPosition::tpMonitor);
|
||||
//p->main_frame->request_select_tab(TAB_ID_MONITOR);
|
||||
////jump to monitor and select device status panel
|
||||
//MonitorPanel* curr_monitor = p->main_frame->m_monitor;
|
||||
//if (curr_monitor)
|
||||
@@ -18376,7 +18387,7 @@ void Plater::pop_warning_and_go_to_device_page(wxString printer_name, PrinterWar
|
||||
MessageDialog dlg(this, content, title, wxOK | wxFORWARD | wxICON_WARNING, _L("Device Page"));
|
||||
auto result = dlg.ShowModal();
|
||||
if (result == wxFORWARD) {
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tpMonitor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_MONITOR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ public:
|
||||
Plater(const Plater &) = delete;
|
||||
Plater &operator=(Plater &&) = delete;
|
||||
Plater &operator=(const Plater &) = delete;
|
||||
~Plater() = default;
|
||||
~Plater();
|
||||
|
||||
bool Show(bool show = true);
|
||||
|
||||
@@ -1020,4 +1020,4 @@ wxArrayString get_all_camera_view_type();
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -15,39 +15,6 @@ namespace Slic3r { namespace GUI {
|
||||
|
||||
namespace {
|
||||
|
||||
// Low-specificity element defaults (no !important) for UNSTYLED plugin HTML, so a bare
|
||||
// plugin page looks native while any CSS the plugin ships still wins. Built on the
|
||||
// --orca-* variables the host injects (see WebViewHostDialog); document-start injected
|
||||
// AFTER the host contract so the variables are defined (shares the base injector's
|
||||
// WebView2 timing guard).
|
||||
std::string plugin_defaults_user_script()
|
||||
{
|
||||
std::string css;
|
||||
css += "<style id=\"orca-plugin-defaults\">";
|
||||
css += "html,body{background:var(--orca-bg);color:var(--orca-fg);"
|
||||
"font-family:var(--orca-font);font-size:13px;}";
|
||||
css += "body{margin:0;}";
|
||||
css += "h1,h2,h3,h4,h5,h6{color:var(--orca-fg);font-weight:600;}";
|
||||
css += "a{color:var(--orca-accent);}";
|
||||
css += "hr{border:0;border-top:1px solid var(--orca-border);}";
|
||||
css += "button{font:inherit;color:var(--orca-accent-fg);background:var(--orca-accent);"
|
||||
"border:1px solid var(--orca-accent);border-radius:4px;padding:5px 14px;cursor:pointer;}";
|
||||
css += "button:hover{filter:brightness(1.1);}";
|
||||
css += "button:disabled{opacity:.5;cursor:default;}";
|
||||
css += "input,select,textarea{font:inherit;color:var(--orca-fg);"
|
||||
"background:var(--orca-bg);border:1px solid var(--orca-border);"
|
||||
"border-radius:4px;padding:4px 8px;}";
|
||||
css += "input:focus,select:focus,textarea:focus{outline:none;border-color:var(--orca-accent);}";
|
||||
css += "table{border-collapse:collapse;}";
|
||||
css += "th,td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--orca-border);}";
|
||||
css += "th{color:var(--orca-muted);font-weight:600;}";
|
||||
css += "::-webkit-scrollbar{width:12px;height:12px;}";
|
||||
css += "::-webkit-scrollbar-thumb{background:var(--orca-border);border-radius:6px;}";
|
||||
css += "::-webkit-scrollbar-track{background:transparent;}";
|
||||
css += "</style>";
|
||||
return WebViewHostDialog::document_start_injector(css, "orca-plugin-defaults", "beforeend");
|
||||
}
|
||||
|
||||
// Injected into the top-level page at document start (before the plugin's own
|
||||
// scripts). Defines window.orca as the only host surface the page may use. It
|
||||
// references window.wx lazily (at call time) so it never races the backend's
|
||||
@@ -129,7 +96,7 @@ PluginWebDialog::PluginWebDialog(wxWindow* parent,
|
||||
void PluginWebDialog::add_user_scripts()
|
||||
{
|
||||
if (wxWebView* wv = browser()) {
|
||||
wv->AddUserScript(wxString::FromUTF8(plugin_defaults_user_script()));
|
||||
wv->AddUserScript(wxString::FromUTF8(WebViewHostDialog::plugin_defaults_user_script()));
|
||||
wv->AddUserScript(ORCA_BRIDGE_JS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1748,11 +1748,26 @@ void PreferencesDialog::create_items()
|
||||
g_sizer->Add(item_pop_up_filament_map_dialog);
|
||||
#endif
|
||||
|
||||
//// GENERAL > Plugins
|
||||
g_sizer->Add(create_item_title(_L("Plugins")), 1, wxEXPAND);
|
||||
|
||||
auto item_plugin_pages_visible_count = create_item_spinctrl(
|
||||
_L("Visible plugin pages"),
|
||||
"",
|
||||
_L("pages"),
|
||||
_L("Number of plugin pages shown as fixed tabs before the remaining pages collapse into a dropdown on the last tab."),
|
||||
SETTING_PLUGIN_PAGES_VISIBLE_COUNT,
|
||||
PLUGIN_PAGES_VISIBLE_COUNT_MIN,
|
||||
PLUGIN_PAGES_VISIBLE_COUNT_MAX,
|
||||
[](int value) { wxGetApp().mainframe->plugin_pages().set_visible_page_count(value); }
|
||||
);
|
||||
g_sizer->Add(item_plugin_pages_visible_count);
|
||||
|
||||
g_sizer->AddSpacer(FromDIP(10));
|
||||
sizer_page->Add(g_sizer, 0, wxEXPAND);
|
||||
|
||||
//////////////////////////
|
||||
//// CONTROL TAB
|
||||
//// CONTROL TAB
|
||||
/////////////////////////////////////
|
||||
m_pref_tabs->AppendItem(_L("Control"));
|
||||
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
|
||||
|
||||
@@ -1042,7 +1042,7 @@ bool PlaterPresetComboBox::switch_to_tab()
|
||||
|
||||
//BBS Select NoteBook Tab params
|
||||
if (tab->GetParent() == wxGetApp().params_panel())
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
else {
|
||||
wxGetApp().params_dialog()->Popup();
|
||||
tab->OnActivate();
|
||||
|
||||
@@ -39,7 +39,7 @@ public:
|
||||
PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size = wxDefaultSize, PresetBundle* preset_bundle = nullptr);
|
||||
~PresetComboBox();
|
||||
|
||||
enum LabelItemType {
|
||||
enum LabelItemType : std::size_t {
|
||||
LABEL_ITEM_PHYSICAL_PRINTER = 0xffffff01,
|
||||
LABEL_ITEM_PRINTER_MODELS,
|
||||
LABEL_ITEM_DISABLED,
|
||||
|
||||
+51
-10
@@ -74,7 +74,18 @@ ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos,
|
||||
Fit();
|
||||
}
|
||||
|
||||
ProjectPanel::~ProjectPanel() {}
|
||||
ProjectPanel::~ProjectPanel()
|
||||
{
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void ProjectPanel::shutdown()
|
||||
{
|
||||
m_reload_cancel_token->store(true, std::memory_order_release);
|
||||
if (m_reload_task && m_reload_task->joinable())
|
||||
m_reload_task->join();
|
||||
m_reload_task.reset();
|
||||
}
|
||||
|
||||
// Helper to convert newlines to <br>
|
||||
static std::string convert_newlines_to_br(const std::string& text) {
|
||||
@@ -101,7 +112,17 @@ void ProjectPanel::onWebNavigating(wxWebViewEvent& evt)
|
||||
|
||||
void ProjectPanel::on_reload(wxCommandEvent& evt)
|
||||
{
|
||||
boost::thread reload = boost::thread([this] {
|
||||
if (wxTheApp == nullptr || wxGetApp().is_closing() ||
|
||||
m_reload_cancel_token->load(std::memory_order_acquire))
|
||||
return;
|
||||
|
||||
if (m_reload_task && m_reload_task->joinable())
|
||||
m_reload_task->join();
|
||||
|
||||
const auto cancel_token = m_reload_cancel_token;
|
||||
m_reload_task = std::make_unique<boost::thread>([this, cancel_token] {
|
||||
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
|
||||
return;
|
||||
std::string update_type;
|
||||
std::string license;
|
||||
std::string model_name;
|
||||
@@ -115,6 +136,9 @@ void ProjectPanel::on_reload(wxCommandEvent& evt)
|
||||
|
||||
std::map<std::string, std::vector<json>> files;
|
||||
|
||||
if (wxGetApp().plater() == nullptr)
|
||||
return;
|
||||
|
||||
Model model = wxGetApp().plater()->model();
|
||||
|
||||
auto model_info = model.model_info;
|
||||
@@ -156,7 +180,14 @@ void ProjectPanel::on_reload(wxCommandEvent& evt)
|
||||
std::string file_path = encode_path(wxGetApp().plater()->model().get_auxiliary_file_temp_path().c_str());
|
||||
if (!file_path.empty()) {
|
||||
files = Reload(file_path);
|
||||
wxGetApp().CallAfter([this, file_path, files] { m_auxiliary->Reload(file_path, files); });
|
||||
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
|
||||
return;
|
||||
|
||||
wxGetApp().CallAfter([this, cancel_token, file_path, files] {
|
||||
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
|
||||
return;
|
||||
m_auxiliary->Reload(file_path, files);
|
||||
});
|
||||
} else {
|
||||
clear_model_info();
|
||||
return;
|
||||
@@ -215,15 +246,18 @@ void ProjectPanel::on_reload(wxCommandEvent& evt)
|
||||
|
||||
json m_Res = json::object();
|
||||
m_Res["command"] = "show_3mf_info";
|
||||
m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id++);
|
||||
m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id.fetch_add(1, std::memory_order_relaxed));
|
||||
m_Res["model"] = j;
|
||||
|
||||
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));
|
||||
|
||||
if (m_web_init_completed) {
|
||||
wxGetApp().CallAfter([this, strJS] {
|
||||
if (m_web_init_completed.load(std::memory_order_acquire) &&
|
||||
!cancel_token->load(std::memory_order_acquire) && wxTheApp != nullptr && !wxGetApp().is_closing()) {
|
||||
wxGetApp().CallAfter([this, cancel_token, strJS] {
|
||||
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
|
||||
return;
|
||||
RunScript(strJS.ToStdString());
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -264,7 +298,7 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt)
|
||||
}
|
||||
}
|
||||
else if (strCmd == "request_3mf_info") {
|
||||
m_web_init_completed = true;
|
||||
m_web_init_completed.store(true, std::memory_order_release);
|
||||
}
|
||||
else if (strCmd == "edit_project_info") {
|
||||
show_info_editor(true);
|
||||
@@ -307,13 +341,20 @@ void ProjectPanel::update_model_data()
|
||||
|
||||
void ProjectPanel::clear_model_info()
|
||||
{
|
||||
if (wxTheApp == nullptr || wxGetApp().is_closing() ||
|
||||
m_reload_cancel_token->load(std::memory_order_acquire))
|
||||
return;
|
||||
|
||||
json m_Res = json::object();
|
||||
m_Res["command"] = "clear_3mf_info";
|
||||
m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id++);
|
||||
m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id.fetch_add(1, std::memory_order_relaxed));
|
||||
|
||||
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));
|
||||
|
||||
wxGetApp().CallAfter([this, strJS] {
|
||||
const auto cancel_token = m_reload_cancel_token;
|
||||
wxGetApp().CallAfter([this, cancel_token, strJS] {
|
||||
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
|
||||
return;
|
||||
RunScript(strJS.ToStdString());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,9 +26,11 @@
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "slic3r/Utils/json_diff.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <boost/thread.hpp>
|
||||
#include "Event.hpp"
|
||||
#include "libslic3r/ProjectTask.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
@@ -60,14 +62,17 @@ struct project_file{
|
||||
class ProjectPanel : public wxPanel
|
||||
{
|
||||
private:
|
||||
bool m_web_init_completed = {false};
|
||||
std::atomic<bool> m_web_init_completed{false};
|
||||
bool m_reload_already = {false};
|
||||
|
||||
std::shared_ptr<std::atomic<bool>> m_reload_cancel_token{std::make_shared<std::atomic<bool>>(false)};
|
||||
std::unique_ptr<boost::thread> m_reload_task;
|
||||
|
||||
wxWebView* m_browser = {nullptr};
|
||||
AuxiliaryPanel* m_auxiliary{nullptr};
|
||||
wxString m_project_home_url;
|
||||
wxString m_root_dir;
|
||||
static inline int m_sequence_id = 8000;
|
||||
static inline std::atomic<int> m_sequence_id{8000};
|
||||
|
||||
void show_info_editor(bool show);
|
||||
|
||||
@@ -75,6 +80,7 @@ private:
|
||||
public:
|
||||
ProjectPanel(wxWindow *parent, wxWindowID id = wxID_ANY, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
|
||||
~ProjectPanel();
|
||||
void shutdown();
|
||||
|
||||
|
||||
void onWebNavigating(wxWebViewEvent& evt);
|
||||
|
||||
@@ -1088,8 +1088,8 @@ void SelectMachineDialog::sync_ams_mapping_result(std::vector<FilamentInfo> &res
|
||||
}
|
||||
}
|
||||
relayout_nozzle_cards();
|
||||
auto tab_index = (MainFrame::TabPosition) dynamic_cast<Notebook *>(wxGetApp().tab_panel())->GetSelection();
|
||||
if (tab_index == MainFrame::TabPosition::tp3DEditor || tab_index == MainFrame::TabPosition::tpPreview) {
|
||||
wxString tab_name = wxGetApp().tab_panel()->GetSelectedPageName();
|
||||
if (tab_name == TAB_ID_PREPARE || tab_name == TAB_ID_PREVIEW) {
|
||||
updata_thumbnail_data_after_connected_printer();
|
||||
}
|
||||
}
|
||||
@@ -3912,7 +3912,7 @@ _collect_sorted_machines(Slic3r::DeviceManager* dev_manager,
|
||||
};
|
||||
|
||||
// collect from user machine list
|
||||
const auto& user_machine_list = dev_manager->get_my_machine_list();// user machine list
|
||||
const auto& user_machine_list = dev_manager->get_my_machine_list(dev_manager->get_current_printer_agent_id());// user machine list
|
||||
for (const auto& elem : user_machine_list)
|
||||
{
|
||||
MachineObject* mobj = elem.second;
|
||||
|
||||
@@ -501,6 +501,7 @@ void SelectMachinePopup::update_other_devices()
|
||||
DeviceManager* dev = wxGetApp().getDeviceManager();
|
||||
if (!dev) return;
|
||||
m_free_machine_list = dev->get_local_machinelist();
|
||||
const std::string current_agent_id = dev->get_current_printer_agent_id();
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "SelectMachinePopup update_other_devices start";
|
||||
this->Freeze();
|
||||
@@ -512,6 +513,10 @@ void SelectMachinePopup::update_other_devices()
|
||||
/* do not show printer bind state is empty */
|
||||
if (!mobj->is_avaliable()) continue;
|
||||
|
||||
/* do not show devices discovered/bound by a different printer agent */
|
||||
if (mobj->printer_agent_id != current_agent_id)
|
||||
continue;
|
||||
|
||||
if (!wxGetApp().is_user_login(wxGetApp().get_printer_cloud_provider()) && !mobj->is_lan_mode_printer())
|
||||
continue;
|
||||
|
||||
@@ -634,7 +639,7 @@ void SelectMachinePopup::update_user_devices()
|
||||
}
|
||||
|
||||
m_bind_machine_list.clear();
|
||||
m_bind_machine_list = dev->get_my_machine_list();
|
||||
m_bind_machine_list = dev->get_my_machine_list(dev->get_current_printer_agent_id());
|
||||
|
||||
//sort list
|
||||
std::vector<std::pair<std::string, MachineObject*>> user_machine_list;
|
||||
|
||||
@@ -1218,8 +1218,8 @@ void SyncAmsInfoDialog::sync_ams_mapping_result(std::vector<FilamentInfo> &resul
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
auto tab_index = (MainFrame::TabPosition) dynamic_cast<Notebook *>(wxGetApp().tab_panel())->GetSelection();
|
||||
if (tab_index == MainFrame::TabPosition::tp3DEditor || tab_index == MainFrame::TabPosition::tpPreview) {
|
||||
wxString tab_name = wxGetApp().tab_panel()->GetSelectedPageName();
|
||||
if (tab_name == TAB_ID_PREPARE || tab_name == TAB_ID_PREVIEW) {
|
||||
updata_thumbnail_data_after_connected_printer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
#ifdef _WIN32
|
||||
// The standard Windows includes.
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
#include <psapi.h>
|
||||
#endif /* _WIN32 */
|
||||
|
||||
@@ -2790,7 +2790,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline");
|
||||
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
|
||||
optgroup->append_single_option_line("gyroid_optimized", "strength_settings_patterns#gyroid-optimized");
|
||||
optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_patterns#sparse-infill-smooth-factor");
|
||||
optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_infill#sparse-infill-smooth-factor");
|
||||
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
|
||||
optgroup->append_single_option_line("sparse_infill_rotate_template", "strength_settings_infill_rotation_template_metalanguage");
|
||||
optgroup->append_single_option_line("skin_infill_density", "strength_settings_patterns#locked-zag");
|
||||
@@ -6458,7 +6458,7 @@ void Tab::load_current_preset()
|
||||
std::string bmp_name = tab->type() == Slic3r::Preset::TYPE_FILAMENT ? "spool" :
|
||||
tab->type() == Slic3r::Preset::TYPE_SLA_MATERIAL ? "" : "cog";
|
||||
tab->Hide(); // #ys_WORKAROUND : Hide tab before inserting to avoid unwanted rendering of the tab
|
||||
dynamic_cast<Notebook*>(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(), bmp_name);
|
||||
dynamic_cast<Notebook*>(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), wxString(), tab, tab->title(), bmp_name);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
@@ -8537,8 +8537,12 @@ void Page::activate(ConfigOptionMode mode, std::function<void()> throw_if_cancel
|
||||
|
||||
#ifdef __WXMSW__
|
||||
// BBS: fix field control position
|
||||
wxTheApp->CallAfter([this]() {
|
||||
for (auto group : m_optgroups) {
|
||||
wxTheApp->CallAfter([wp = std::weak_ptr<Page>(shared_from_this())]() {
|
||||
auto page = wp.lock();
|
||||
if (!page)
|
||||
return;
|
||||
|
||||
for (auto group : page->m_optgroups) {
|
||||
if (group->custom_ctrl)
|
||||
group->custom_ctrl->fixup_items_positions();
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ public:
|
||||
// by this control) and show it immediately.
|
||||
bool ShowNewPage(wxWindow * page)
|
||||
{
|
||||
return AddPage(page, wxString(), ""/*true *//* select it */);
|
||||
return AddPage(page, wxString());
|
||||
}
|
||||
|
||||
// Set effect to use for showing/hiding pages.
|
||||
@@ -139,14 +139,13 @@ public:
|
||||
|
||||
// Implement base class pure virtual methods.
|
||||
|
||||
// adds a new page to the control
|
||||
bool AddPage(wxWindow* page,
|
||||
const wxString& text,
|
||||
const std::string& bmp_name,
|
||||
bool bSelect = false)
|
||||
bool bSelect = false,
|
||||
int imageId = NO_IMAGE) override
|
||||
{
|
||||
DoInvalidateBestSize();
|
||||
return InsertNewPage(GetPageCount(), page, text, bmp_name, bSelect);
|
||||
return InsertPage(GetPageCount(), page, text, bSelect, imageId);
|
||||
}
|
||||
|
||||
//// Page management
|
||||
@@ -167,23 +166,6 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InsertNewPage(size_t n,
|
||||
wxWindow * page,
|
||||
const wxString & text,
|
||||
const std::string& bmp_name = "",
|
||||
bool bSelect = false)
|
||||
{
|
||||
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect))
|
||||
return false;
|
||||
|
||||
GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name);
|
||||
|
||||
if (bSelect)
|
||||
SetSelection(n);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RemovePage(size_t n)
|
||||
{
|
||||
if (!wxBookCtrlBase::RemovePage(n))
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "WebGuideDialog.hpp"
|
||||
#include "ConfigWizard.hpp"
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/iostreams/detail/select.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
@@ -9,7 +11,9 @@
|
||||
#include "I18N.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/PresetCacheFormat.hpp"
|
||||
#include "slic3r/GUI/wxExtensions.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "libslic3r_version.h"
|
||||
@@ -41,8 +45,6 @@ using namespace nlohmann;
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
json m_ProfileJson;
|
||||
|
||||
static wxString update_custom_filaments()
|
||||
{
|
||||
json m_Res = json::object();
|
||||
@@ -190,12 +192,10 @@ GuideFrame::GuideFrame(GUI_App *pGUI, long style)
|
||||
|
||||
GuideFrame::~GuideFrame()
|
||||
{
|
||||
m_destroy = true;
|
||||
if (m_load_task && m_load_task->joinable()) {
|
||||
*m_cancel_token = true; // stop the loading thread and any queued CallAfter lambdas before join
|
||||
if (m_load_task && m_load_task->joinable())
|
||||
m_load_task->join();
|
||||
delete m_load_task;
|
||||
m_load_task = nullptr;
|
||||
}
|
||||
m_load_task.reset();
|
||||
if (m_browser) {
|
||||
delete m_browser;
|
||||
m_browser = nullptr;
|
||||
@@ -301,15 +301,71 @@ void GuideFrame::OnNavigationRequest(wxWebViewEvent &evt)
|
||||
/**
|
||||
* Callback invoked when a navigation request was accepted
|
||||
*/
|
||||
// The empty shape every profile-loading path starts from or falls back to.
|
||||
void GuideFrame::reset_profile_json()
|
||||
{
|
||||
m_ProfileJson["model"] = json::array();
|
||||
m_ProfileJson["machine"] = json::object();
|
||||
m_ProfileJson["filament"] = json::object();
|
||||
m_ProfileJson["process"] = json::array();
|
||||
}
|
||||
|
||||
void GuideFrame::init_guide_paths()
|
||||
{
|
||||
m_ProfileJson = json::parse("{}");
|
||||
reset_profile_json();
|
||||
|
||||
vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
|
||||
rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
|
||||
orca_bundle_rsrc = true;
|
||||
|
||||
if (boost::filesystem::exists(vendor_dir)) {
|
||||
for (const auto& entry : boost::filesystem::directory_iterator(vendor_dir)) {
|
||||
if (!boost::filesystem::is_directory(entry) &&
|
||||
boost::iequals(entry.path().extension().string(), ".json") &&
|
||||
!boost::iequals(entry.path().stem().string(), PresetBundle::ORCA_FILAMENT_LIBRARY)) {
|
||||
orca_bundle_rsrc = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto lib_json = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
|
||||
m_OrcaFilaLibPath = boost::filesystem::exists(vendor_dir / lib_json)
|
||||
? (vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string()
|
||||
: (rsrc_vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
|
||||
}
|
||||
|
||||
void GuideFrame::on_profile_loaded()
|
||||
{
|
||||
// Must be called on the main thread.
|
||||
SaveProfileData();
|
||||
const std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished, json contents:\n" << strAll;
|
||||
json res;
|
||||
res["command"] = "userguide_profile_load_finish";
|
||||
res["sequence_id"] = "10001";
|
||||
RunScript(wxString::Format("HandleStudio(%s)", res.dump(-1, ' ', true)));
|
||||
}
|
||||
|
||||
void GuideFrame::OnNavigationComplete(wxWebViewEvent &evt)
|
||||
{
|
||||
//wxLogMessage("%s", "Navigation complete; url='" + evt.GetURL() + "'");
|
||||
if (!bFirstComplete) {
|
||||
m_load_task = new boost::thread(boost::bind(&GuideFrame::LoadProfileData, this));
|
||||
// boost::thread LoadProfileThread(boost::bind(&GuideFrame::LoadProfileData, this));
|
||||
//LoadProfileThread.detach();
|
||||
|
||||
bFirstComplete = true;
|
||||
try {
|
||||
init_guide_paths();
|
||||
if (BuildProfileDataFromPresetBundle()) {
|
||||
if (!*m_cancel_token)
|
||||
on_profile_loaded();
|
||||
} else {
|
||||
// Presets not yet in memory — delegate to background thread.
|
||||
m_load_task = std::make_unique<boost::thread>(boost::bind(&GuideFrame::LoadProfileData, this));
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", init error: " << e.what();
|
||||
m_load_task = std::make_unique<boost::thread>(boost::bind(&GuideFrame::LoadProfileData, this));
|
||||
}
|
||||
}
|
||||
|
||||
m_browser->Show();
|
||||
@@ -762,11 +818,9 @@ bool GuideFrame::apply_config(AppConfig *app_config, PresetBundle *preset_bundle
|
||||
bool check_unsaved_preset_changes = false;
|
||||
std::vector<std::string> install_bundles;
|
||||
std::vector<std::string> remove_bundles;
|
||||
const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
|
||||
for (const auto &it : enabled_vendors) {
|
||||
if (it.second.size() > 0) {
|
||||
auto vendor_file = vendor_dir/(it.first + ".json");
|
||||
if (!fs::exists(vendor_file)) {
|
||||
if (!is_vendor_installed(it.first)) {
|
||||
install_bundles.emplace_back(it.first);
|
||||
}
|
||||
}
|
||||
@@ -777,8 +831,7 @@ bool GuideFrame::apply_config(AppConfig *app_config, PresetBundle *preset_bundle
|
||||
if (it.second.size() > 0) {
|
||||
if (enabled_vendors.find(it.first) != enabled_vendors.end())
|
||||
continue;
|
||||
auto vendor_file = vendor_dir/(it.first + ".json");
|
||||
if (fs::exists(vendor_file)) {
|
||||
if (is_vendor_installed(it.first)) {
|
||||
remove_bundles.emplace_back(it.first);
|
||||
}
|
||||
}
|
||||
@@ -1127,99 +1180,324 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList,
|
||||
return status;
|
||||
}
|
||||
|
||||
int GuideFrame::LoadProfileData()
|
||||
bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_resource_vendors)
|
||||
{
|
||||
try {
|
||||
m_ProfileJson = json::parse("{}");
|
||||
m_ProfileJson["model"] = json::array();
|
||||
m_ProfileJson["machine"] = json::object();
|
||||
m_ProfileJson["filament"] = json::object();
|
||||
m_ProfileJson["process"] = json::array();
|
||||
// Models from vendor profiles
|
||||
for (const auto& [vendor_id, vp] : bundle.vendors) {
|
||||
for (const auto& model : vp.models) {
|
||||
std::string nozzle_str;
|
||||
for (const auto& v : model.variants) {
|
||||
if (!nozzle_str.empty()) nozzle_str += ";";
|
||||
nozzle_str += v.name;
|
||||
}
|
||||
const std::string materials_str = boost::algorithm::join(model.default_materials, ";");
|
||||
boost::filesystem::path cover_path =
|
||||
(boost::filesystem::path(resources_dir()) / "profiles" / vp.id / (model.id + "_cover.png"))
|
||||
.make_preferred();
|
||||
if (!boost::filesystem::exists(cover_path))
|
||||
cover_path =
|
||||
(boost::filesystem::path(resources_dir()) / "web/image/printer" / (model.id + "_cover.png"))
|
||||
.make_preferred();
|
||||
|
||||
vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
|
||||
rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
|
||||
|
||||
// Orca: add custom as default
|
||||
// Orca: add json logic for vendor bundle
|
||||
orca_bundle_rsrc = true;
|
||||
|
||||
// search if there exists a .json file in vendor_dir folder, if exists, set orca_bundle_rsrc to false
|
||||
for (const auto& entry : boost::filesystem::directory_iterator(vendor_dir)) {
|
||||
if (!boost::filesystem::is_directory(entry) && boost::iequals(entry.path().extension().string(), ".json") && !boost::iequals(entry.path().stem().string(), PresetBundle::ORCA_FILAMENT_LIBRARY)) {
|
||||
orca_bundle_rsrc = false;
|
||||
break;
|
||||
json entry;
|
||||
entry["model"] = model.id;
|
||||
entry["name"] = model.name;
|
||||
entry["vendor"] = vp.id;
|
||||
entry["nozzle_diameter"] = nozzle_str;
|
||||
entry["materials"] = materials_str;
|
||||
entry["cover"] = cover_path.string();
|
||||
entry["nozzle_selected"] = "";
|
||||
entry["sub_path"] = "";
|
||||
m_ProfileJson["model"].push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// load the default filament library first
|
||||
std::set<std::string> loaded_vendors;
|
||||
auto filament_library_name = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
|
||||
if (boost::filesystem::exists(vendor_dir / filament_library_name)) {
|
||||
m_OrcaFilaLibPath = (vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
|
||||
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (vendor_dir / filament_library_name).string());
|
||||
} else {
|
||||
m_OrcaFilaLibPath = (rsrc_vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
|
||||
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (rsrc_vendor_dir / filament_library_name).string());
|
||||
}
|
||||
loaded_vendors.insert(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
||||
// Machine map: preset name -> {model, nozzle variant}
|
||||
for (const Preset& p : bundle.printers()) {
|
||||
if (!p.is_system || !p.vendor) continue;
|
||||
const auto* printer_model = p.config.option<ConfigOptionString>("printer_model");
|
||||
const auto* printer_variant = p.config.option<ConfigOptionString>("printer_variant");
|
||||
if (!printer_model || printer_model->value.empty() || !printer_variant) continue;
|
||||
|
||||
//load custom bundle from user data path
|
||||
boost::filesystem::directory_iterator endIter;
|
||||
for (boost::filesystem::directory_iterator iter(vendor_dir); iter != endIter; iter++) {
|
||||
if (!boost::filesystem::is_directory(*iter)) {
|
||||
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
||||
strVendor = strVendor.AfterLast('\\');
|
||||
strVendor = strVendor.AfterLast('/');
|
||||
|
||||
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
||||
if(strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
||||
continue;
|
||||
|
||||
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
||||
loaded_vendors.insert(w2s(strVendor));
|
||||
}
|
||||
if (m_destroy)
|
||||
return 0;
|
||||
json mach;
|
||||
mach["model"] = printer_model->value;
|
||||
mach["nozzle"] = printer_variant->value;
|
||||
m_ProfileJson["machine"][p.name] = mach;
|
||||
}
|
||||
|
||||
boost::filesystem::directory_iterator others_endIter;
|
||||
for (boost::filesystem::directory_iterator iter(rsrc_vendor_dir); iter != others_endIter; iter++) {
|
||||
if (!boost::filesystem::is_directory(*iter)) {
|
||||
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
||||
strVendor = strVendor.AfterLast('\\');
|
||||
strVendor = strVendor.AfterLast('/');
|
||||
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
||||
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
||||
continue;
|
||||
// Filament map from system filament presets (vendor/type already resolved in config)
|
||||
const json& machines = m_ProfileJson["machine"];
|
||||
for (const Preset& p : bundle.filaments()) {
|
||||
if (!p.is_system || !p.vendor) continue;
|
||||
const auto* fila_vendor = p.config.option<ConfigOptionStrings>("filament_vendor");
|
||||
const auto* fila_type = p.config.option<ConfigOptionStrings>("filament_type");
|
||||
const auto* compat_printers = p.config.option<ConfigOptionStrings>("compatible_printers");
|
||||
|
||||
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
||||
loaded_vendors.insert(w2s(strVendor));
|
||||
std::string vendor = (fila_vendor && !fila_vendor->values.empty()) ? fila_vendor->values[0] : "";
|
||||
std::string type = (fila_type && !fila_type->values.empty()) ? fila_type->values[0] : "";
|
||||
|
||||
std::string model_list;
|
||||
if (compat_printers) {
|
||||
for (const std::string& pname : compat_printers->values) {
|
||||
auto it = machines.find(pname);
|
||||
if (it != machines.end()) {
|
||||
const std::string m = (*it)["model"];
|
||||
const std::string n = (*it)["nozzle"];
|
||||
model_list += "[" + m + "++" + n + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_destroy)
|
||||
return 0;
|
||||
|
||||
json ff;
|
||||
ff["name"] = p.name;
|
||||
ff["sub_path"] = p.file;
|
||||
ff["vendor"] = vendor;
|
||||
ff["type"] = type;
|
||||
ff["models"] = model_list;
|
||||
ff["selected"] = 0;
|
||||
m_ProfileJson["filament"][p.name] = ff;
|
||||
}
|
||||
|
||||
wxGetApp().CallAfter([this] {
|
||||
if (!m_destroy) {
|
||||
//sync to appconfig first to populate current selections
|
||||
SaveProfileData();
|
||||
// Process list from visible system print presets
|
||||
for (const Preset& p : bundle.prints()) {
|
||||
if (!p.is_system || !p.vendor || !p.is_visible) continue;
|
||||
json entry;
|
||||
entry["name"] = p.name;
|
||||
entry["sub_path"] = p.file;
|
||||
m_ProfileJson["process"].push_back(entry);
|
||||
}
|
||||
|
||||
//sync to web after selections are populated
|
||||
std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
if (require_all_resource_vendors) {
|
||||
// If rsrc_vendor_dir has vendors (profile JSONs, or the preset caches a
|
||||
// packaged build ships instead) not covered by the current bundle, the
|
||||
// bundle is incomplete (e.g. dev env where data_dir/system only has
|
||||
// OrcaFilamentLibrary+Custom). Fall back so the slow path reads both dirs.
|
||||
try {
|
||||
for (const std::string& name : vendor_names_in(rsrc_vendor_dir)) {
|
||||
if (bundle.vendors.find(name) == bundle.vendors.end()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "GuideFrame: vendor '" << name
|
||||
<< "' in resources but not in preset_bundle — falling back to JSON loading";
|
||||
reset_profile_json();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (const std::exception&) {}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished, json contents: " << std::endl << strAll;
|
||||
json m_Res = json::object();
|
||||
m_Res["command"] = "userguide_profile_load_finish";
|
||||
m_Res["sequence_id"] = "10001";
|
||||
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', true));
|
||||
BOOST_LOG_TRIVIAL(info) << "GuideFrame: built profile data ("
|
||||
<< m_ProfileJson["model"].size() << " models, "
|
||||
<< m_ProfileJson["machine"].size() << " machines, "
|
||||
<< m_ProfileJson["filament"].size() << " filaments)";
|
||||
return !m_ProfileJson["machine"].empty();
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "GuideFrame::BuildProfileJson failed: " << e.what()
|
||||
<< " — falling back to JSON loading";
|
||||
reset_profile_json();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RunScript(strJS);
|
||||
bool GuideFrame::BuildProfileDataFromPresetBundle()
|
||||
{
|
||||
PresetBundle* pb = wxGetApp().preset_bundle;
|
||||
if (!pb || pb->vendors.empty())
|
||||
return false;
|
||||
return BuildProfileJson(*pb, /*require_all_resource_vendors=*/true);
|
||||
}
|
||||
|
||||
bool GuideFrame::BuildProfileDataFromVendors()
|
||||
{
|
||||
try {
|
||||
// Same vendor set and precedence as the JSON scan in LoadProfileData: a
|
||||
// vendor in the user's system dir shadows the bundled one of that name.
|
||||
// vendor_names_in names a vendor by its profile or, where a build ships
|
||||
// preset caches instead, by its cache alone.
|
||||
std::map<std::string, boost::filesystem::path> vendor_sources;
|
||||
for (const boost::filesystem::path& dir : { vendor_dir, rsrc_vendor_dir }) {
|
||||
boost::system::error_code ec;
|
||||
if (boost::filesystem::exists(dir, ec))
|
||||
for (const std::string& name : vendor_names_in(dir))
|
||||
vendor_sources.emplace(name, dir); // first dir wins
|
||||
}
|
||||
|
||||
// The load order: the filament library first, because the others'
|
||||
// filaments inherit from it, then every versioned vendor — each loaded
|
||||
// from the directory it was found in, so a vendor that is not installed
|
||||
// is served from the shipped profiles. Each is stamped by name and
|
||||
// version alone: a profile change requires a version bump, so those two
|
||||
// determine content wherever the vendor's copy sits.
|
||||
struct VendorSource { std::string name; boost::filesystem::path dir; std::string version; };
|
||||
std::vector<VendorSource> ordered;
|
||||
auto add_vendor = [&ordered](const std::string& name, const boost::filesystem::path& dir) {
|
||||
// The version a load from `dir` would serve: the profile's where one
|
||||
// exists (a cache is only served while it covers the profile beside
|
||||
// it), the cache's own stamp where the cache is the whole vendor.
|
||||
// A profile without a version (blacklist.json) carries no presets
|
||||
// and is passed over.
|
||||
const boost::filesystem::path profile = dir / (name + ".json");
|
||||
if (boost::filesystem::exists(profile)) {
|
||||
const Semver v = get_version_from_json(profile.string());
|
||||
if (v.valid())
|
||||
ordered.push_back({name, dir, v.to_string()});
|
||||
} else {
|
||||
ordered.push_back({name, dir,
|
||||
VendorCacheFile::peek_version((dir / (name + ".opc")).string(), name)});
|
||||
}
|
||||
};
|
||||
const std::string filament_library(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
||||
if (auto it = vendor_sources.find(filament_library); it != vendor_sources.end())
|
||||
add_vendor(filament_library, it->second);
|
||||
for (const auto& [name, dir] : vendor_sources)
|
||||
if (name != filament_library)
|
||||
add_vendor(name, dir);
|
||||
if (ordered.empty())
|
||||
return false;
|
||||
json stamps = json::array();
|
||||
for (const VendorSource& v : ordered)
|
||||
stamps.push_back({v.name, v.version});
|
||||
|
||||
// What this function derives is a pure function of that stamped set, so
|
||||
// the derived JSON is cached whole: a fresh cache makes an open one
|
||||
// file read, with no bundle built and no preset installed. Stale or
|
||||
// absent, the bundle is rebuilt below and the result written back.
|
||||
const boost::filesystem::path cache_file =
|
||||
boost::filesystem::path(Slic3r::data_dir()) / "cache" / "wizard_profile_data.json";
|
||||
try {
|
||||
// Slurped whole and parsed from the buffer — nlohmann's fastest
|
||||
// input path; a stream adapter costs real time on a multi-MB file.
|
||||
boost::nowide::ifstream ifs(cache_file.string(), std::ios::binary);
|
||||
if (ifs.is_open()) {
|
||||
const std::string text{std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>()};
|
||||
json cached = json::parse(text);
|
||||
if (cached.value("format", 0) == 1 && cached["vendors"] == stamps &&
|
||||
! cached["profile"]["machine"].empty()) {
|
||||
for (const char* key : { "model", "machine", "filament", "process" })
|
||||
m_ProfileJson[key] = std::move(cached["profile"][key]);
|
||||
BOOST_LOG_TRIVIAL(info) << "GuideFrame: profile data served from " << cache_file;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(info) << "GuideFrame: rejecting cached profile data: " << e.what();
|
||||
}
|
||||
|
||||
// Each vendor comes from its preset cache where one covers it, which is
|
||||
// what makes this worth doing instead of the scan below; loading into a
|
||||
// bundle per vendor keeps the install order the startup path has.
|
||||
PresetBundle bundle;
|
||||
auto load_vendor = [](PresetBundle& into, const std::string& vendor,
|
||||
const boost::filesystem::path& dir, const PresetBundle* base) {
|
||||
into.load_vendor_configs_from_json(dir.string(), vendor, PresetBundle::LoadSystem,
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, base);
|
||||
};
|
||||
for (const VendorSource& v : ordered) {
|
||||
if (*m_cancel_token)
|
||||
return false; // as in the scan below: a vendor without a cache is parsed, and that takes time
|
||||
if (v.name == filament_library) {
|
||||
load_vendor(bundle, v.name, v.dir, nullptr);
|
||||
} else {
|
||||
PresetBundle tmp;
|
||||
load_vendor(tmp, v.name, v.dir, &bundle);
|
||||
bundle.merge_presets(std::move(tmp));
|
||||
}
|
||||
}
|
||||
if (bundle.vendors.empty())
|
||||
return false;
|
||||
if (! BuildProfileJson(bundle, /*require_all_resource_vendors=*/false))
|
||||
return false;
|
||||
|
||||
// Written through a temp file and moved into place, as the preset caches
|
||||
// are: half a cache must never be readable, and the PID suffix keeps two
|
||||
// instances from interleaving on one temp file.
|
||||
const std::string tmp_path = cache_file.string() + "." + std::to_string(get_current_pid()) + ".tmp";
|
||||
try {
|
||||
json out;
|
||||
out["format"] = 1;
|
||||
out["vendors"] = std::move(stamps);
|
||||
json& profile = out["profile"];
|
||||
for (const char* key : { "model", "machine", "filament", "process" })
|
||||
profile[key] = m_ProfileJson[key];
|
||||
boost::filesystem::create_directories(cache_file.parent_path());
|
||||
{
|
||||
boost::nowide::ofstream ofs(tmp_path, std::ios::binary | std::ios::trunc);
|
||||
ofs << out.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
ofs.close();
|
||||
if (! ofs.good())
|
||||
throw std::runtime_error("write failed");
|
||||
}
|
||||
if (const std::error_code ec = rename_file(tmp_path, cache_file.string()))
|
||||
throw std::runtime_error(ec.message());
|
||||
} catch (const std::exception& e) {
|
||||
boost::system::error_code rm;
|
||||
boost::filesystem::remove(tmp_path, rm);
|
||||
BOOST_LOG_TRIVIAL(warning) << "GuideFrame: could not write the profile data cache: " << e.what();
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed: " << e.what();
|
||||
reset_profile_json();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int GuideFrame::LoadProfileData()
|
||||
{
|
||||
// Background thread: the fast path in OnNavigationComplete failed (presets not yet loaded).
|
||||
// Loading order (fastest to slowest):
|
||||
// 1. Load every vendor, from its preset cache wherever one covers it
|
||||
// 2. Read all vendor JSONs by hand
|
||||
try {
|
||||
if (!BuildProfileDataFromVendors()) {
|
||||
// Last resort — read all vendor JSONs
|
||||
std::set<std::string> loaded_vendors;
|
||||
auto filament_library_name = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
|
||||
if (boost::filesystem::exists(vendor_dir / filament_library_name))
|
||||
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (vendor_dir / filament_library_name).string());
|
||||
else
|
||||
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (rsrc_vendor_dir / filament_library_name).string());
|
||||
loaded_vendors.insert(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
||||
|
||||
boost::filesystem::directory_iterator endIter;
|
||||
for (boost::filesystem::directory_iterator iter(vendor_dir); iter != endIter; iter++) {
|
||||
if (!boost::filesystem::is_directory(*iter)) {
|
||||
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
||||
strVendor = strVendor.AfterLast('\\');
|
||||
strVendor = strVendor.AfterLast('/');
|
||||
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
||||
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
||||
continue;
|
||||
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
||||
loaded_vendors.insert(w2s(strVendor));
|
||||
}
|
||||
if (*m_cancel_token) return 0;
|
||||
}
|
||||
|
||||
boost::filesystem::directory_iterator others_endIter;
|
||||
for (boost::filesystem::directory_iterator iter(rsrc_vendor_dir); iter != others_endIter; iter++) {
|
||||
if (!boost::filesystem::is_directory(*iter)) {
|
||||
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
||||
strVendor = strVendor.AfterLast('\\');
|
||||
strVendor = strVendor.AfterLast('/');
|
||||
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
||||
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
||||
continue;
|
||||
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
||||
loaded_vendors.insert(w2s(strVendor));
|
||||
}
|
||||
if (*m_cancel_token) return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Capture the cancel token by value (shared_ptr) so the lambda doesn't
|
||||
// touch `this` if GuideFrame is destroyed before the event fires.
|
||||
auto tok = m_cancel_token;
|
||||
wxGetApp().CallAfter([this, tok] {
|
||||
if (!*tok)
|
||||
on_profile_loaded();
|
||||
});
|
||||
} catch (std::exception& e) {
|
||||
// wxLogMessage("GUIDE: load_profile_error %s ", e.what());
|
||||
// wxMessageBox(e.what(), "", MB_OK);
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", error: " << e.what() << std::endl;
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", error: " << e.what();
|
||||
}
|
||||
|
||||
filament_info_cache.clear();
|
||||
|
||||
@@ -30,10 +30,14 @@
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class GuideFrame : public DPIDialog
|
||||
@@ -78,6 +82,12 @@ public:
|
||||
int LoadProfileData();
|
||||
int SaveProfileData();
|
||||
int LoadProfileFamily(std::string strVendor, std::string strFilePath);
|
||||
void init_guide_paths();
|
||||
void on_profile_loaded();
|
||||
bool BuildProfileJson(const PresetBundle& bundle, bool require_all_resource_vendors);
|
||||
bool BuildProfileDataFromPresetBundle();
|
||||
bool BuildProfileDataFromVendors();
|
||||
void reset_profile_json();
|
||||
int SaveProfile();
|
||||
int GetFilamentInfo( std::string VendorDirectory,json & pFilaList, std::string filepath, std::string &sVendor, std::string &sType);
|
||||
|
||||
@@ -112,8 +122,11 @@ private:
|
||||
|
||||
//First Load
|
||||
bool bFirstComplete{false};
|
||||
bool m_destroy{false};
|
||||
boost::thread* m_load_task{ nullptr };
|
||||
// Set once in the destructor. Read through `this` by the loading thread
|
||||
// (joined before `this` dies) and captured as the shared_ptr by CallAfter
|
||||
// lambdas so they don't touch `this` after the object is freed.
|
||||
std::shared_ptr<std::atomic<bool>> m_cancel_token{std::make_shared<std::atomic<bool>>(false)};
|
||||
std::unique_ptr<boost::thread> m_load_task;
|
||||
|
||||
// User Config
|
||||
bool PrivacyUse;
|
||||
@@ -123,6 +136,7 @@ private:
|
||||
bool InstallNetplugin;
|
||||
bool network_plugin_ready {false};
|
||||
|
||||
json m_ProfileJson;
|
||||
json m_OrcaFilaList;
|
||||
std::string m_OrcaFilaLibPath;
|
||||
|
||||
|
||||
@@ -95,14 +95,11 @@ void Button::SetIcon(const wxString& icon)
|
||||
}
|
||||
}
|
||||
|
||||
void Button::SetInactiveIcon(const wxString &icon)
|
||||
void Button::SetIcon(const wxBitmap& icon)
|
||||
{
|
||||
if (!icon.IsEmpty()) {
|
||||
// BBS set button icon default size to 20
|
||||
this->inactive_icon = ScalableBitmap(this, icon.ToStdString(), this->active_icon.px_cnt());
|
||||
} else {
|
||||
this->inactive_icon = ScalableBitmap();
|
||||
}
|
||||
this->active_icon = ScalableBitmap();
|
||||
this->active_icon.bmp() = icon;
|
||||
messureSize();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
@@ -257,12 +254,10 @@ void Button::SetStyle(const ButtonStyle style, const ButtonType type)
|
||||
|
||||
void Button::Rescale()
|
||||
{
|
||||
if (this->active_icon.bmp().IsOk())
|
||||
// Only a named icon can be re-rasterized; one set from a wxBitmap has no source file,
|
||||
if (!this->active_icon.name().empty())
|
||||
this->active_icon.msw_rescale();
|
||||
|
||||
if (this->inactive_icon.bmp().IsOk())
|
||||
this->inactive_icon.msw_rescale();
|
||||
|
||||
messureSize();
|
||||
|
||||
if(m_has_style)
|
||||
@@ -293,11 +288,7 @@ void Button::render(wxDC& dc)
|
||||
wxSize szIcon;
|
||||
wxSize textSize = this->textSize.GetSize();
|
||||
|
||||
ScalableBitmap icon;
|
||||
if (m_selected || ((states & (int)StateColor::State::Hovered) != 0))
|
||||
icon = active_icon;
|
||||
else
|
||||
icon = inactive_icon;
|
||||
const ScalableBitmap& icon = active_icon;
|
||||
wxSize padding = this->paddingSize;
|
||||
int spacing = 5;
|
||||
// Wrap text
|
||||
@@ -512,8 +503,8 @@ void Button::OnParentMotion(wxMouseEvent& event)
|
||||
{
|
||||
if (!tipWindow)
|
||||
{
|
||||
tipWindow = new wxTipWindow(this, tip);
|
||||
tipWindow->Bind(wxEVT_DESTROY, [this](wxEvent& event) { this->tipWindow = nullptr;});
|
||||
tipWindow = wxTipWindow::New(this, tip);
|
||||
if (!tipWindow) return event.Skip();
|
||||
tipWindow->Enable(false);
|
||||
}
|
||||
|
||||
@@ -531,7 +522,8 @@ void Button::OnParentMotion(wxMouseEvent& event)
|
||||
{
|
||||
if (tipWindow)
|
||||
{
|
||||
delete tipWindow;
|
||||
tipWindow->Dismiss();
|
||||
tipWindow->Destroy();
|
||||
tipWindow = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -552,7 +544,7 @@ void Button::OnParentLeave(wxMouseEvent& event)
|
||||
if (!screen_rect.Contains(pos))
|
||||
{
|
||||
tipWindow->Dismiss();
|
||||
delete tipWindow;
|
||||
tipWindow->Destroy();
|
||||
tipWindow = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "../wxExtensions.hpp"
|
||||
#include "StaticBox.hpp"
|
||||
#include <wx/tipwin.h>
|
||||
|
||||
class ButtonProps
|
||||
{
|
||||
@@ -27,14 +28,13 @@ enum class ButtonType{
|
||||
Expanded , // Font14 Semi-Rounded For full length buttons. ex. buttons in static box
|
||||
};
|
||||
|
||||
class wxTipWindow;
|
||||
class Button : public StaticBox
|
||||
{
|
||||
wxTipWindow::Ref tipWindow;
|
||||
wxRect textSize;
|
||||
wxSize minSize; // set by outer
|
||||
wxSize paddingSize;
|
||||
ScalableBitmap active_icon;
|
||||
ScalableBitmap inactive_icon;
|
||||
|
||||
StateColor text_color;
|
||||
|
||||
@@ -44,8 +44,6 @@ class Button : public StaticBox
|
||||
bool isCenter = true;
|
||||
bool vertical = false;
|
||||
|
||||
wxTipWindow* tipWindow = nullptr;
|
||||
|
||||
static const int buttonWidth = 200;
|
||||
static const int buttonHeight = 50;
|
||||
|
||||
@@ -61,8 +59,7 @@ public:
|
||||
bool SetFont(const wxFont& font) override;
|
||||
|
||||
void SetIcon(const wxString& icon);
|
||||
|
||||
void SetInactiveIcon(const wxString& icon);
|
||||
void SetIcon(const wxBitmap& icon);
|
||||
|
||||
void SetMinSize(const wxSize& size) override;
|
||||
void SetMaxSize(const wxSize& size) override;
|
||||
|
||||
@@ -57,18 +57,6 @@ std::string host_theme_vars_css()
|
||||
return s;
|
||||
}
|
||||
|
||||
// Document-start user script: injects the contract <style>, stamps data-orca-theme before
|
||||
// first paint, and raises a JS flag so the legacy globalapi.js dark.css poll stands down for
|
||||
// host-themed pages. The WebView2 timing guard lives in document_start_injector().
|
||||
std::string host_theme_user_script()
|
||||
{
|
||||
const std::string style = "<style id=\"orca-host-theme-vars\">" + host_theme_vars_css() + "</style>";
|
||||
return WebViewHostDialog::document_start_injector(
|
||||
style, "orca-host-theme-vars", "afterbegin",
|
||||
"window.__orcaHostThemed=true;var theme=\"" + host_theme_name() + "\";",
|
||||
"if(document.documentElement)document.documentElement.setAttribute('data-orca-theme',theme);");
|
||||
}
|
||||
|
||||
// JS to re-theme an already-loaded document live (no reload): replace the injected
|
||||
// style's contents and update data-orca-theme. Everything downstream (theme.css
|
||||
// tokens, plugin element defaults, page layout) re-cascades from these values.
|
||||
@@ -87,6 +75,46 @@ if(document.documentElement)
|
||||
|
||||
} // namespace
|
||||
|
||||
// Document-start user script: injects the contract <style>, stamps data-orca-theme before
|
||||
// first paint, and raises a JS flag so the legacy globalapi.js dark.css poll stands down for
|
||||
// host-themed pages. The WebView2 timing guard lives in document_start_injector().
|
||||
std::string WebViewHostDialog::theme_user_script()
|
||||
{
|
||||
const std::string style = "<style id=\"orca-host-theme-vars\">" + host_theme_vars_css() + "</style>";
|
||||
return document_start_injector(
|
||||
style, "orca-host-theme-vars", "afterbegin",
|
||||
"window.__orcaHostThemed=true;var theme=\"" + host_theme_name() + "\";",
|
||||
"if(document.documentElement)document.documentElement.setAttribute('data-orca-theme',theme);");
|
||||
}
|
||||
|
||||
std::string WebViewHostDialog::plugin_defaults_user_script()
|
||||
{
|
||||
std::string css;
|
||||
css += "<style id=\"orca-plugin-defaults\">";
|
||||
css += "html,body{background:var(--orca-bg);color:var(--orca-fg);"
|
||||
"font-family:var(--orca-font);font-size:13px;}";
|
||||
css += "body{margin:0;}";
|
||||
css += "h1,h2,h3,h4,h5,h6{color:var(--orca-fg);font-weight:600;}";
|
||||
css += "a{color:var(--orca-accent);}";
|
||||
css += "hr{border:0;border-top:1px solid var(--orca-border);}";
|
||||
css += "button{font:inherit;color:var(--orca-accent-fg);background:var(--orca-accent);"
|
||||
"border:1px solid var(--orca-accent);border-radius:4px;padding:5px 14px;cursor:pointer;}";
|
||||
css += "button:hover{filter:brightness(1.1);}";
|
||||
css += "button:disabled{opacity:.5;cursor:default;}";
|
||||
css += "input,select,textarea{font:inherit;color:var(--orca-fg);"
|
||||
"background:var(--orca-bg);border:1px solid var(--orca-border);"
|
||||
"border-radius:4px;padding:4px 8px;}";
|
||||
css += "input:focus,select:focus,textarea:focus{outline:none;border-color:var(--orca-accent);}";
|
||||
css += "table{border-collapse:collapse;}";
|
||||
css += "th,td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--orca-border);}";
|
||||
css += "th{color:var(--orca-muted);font-weight:600;}";
|
||||
css += "::-webkit-scrollbar{width:12px;height:12px;}";
|
||||
css += "::-webkit-scrollbar-thumb{background:var(--orca-border);border-radius:6px;}";
|
||||
css += "::-webkit-scrollbar-track{background:transparent;}";
|
||||
css += "</style>";
|
||||
return document_start_injector(css, "orca-plugin-defaults", "beforeend");
|
||||
}
|
||||
|
||||
std::string WebViewHostDialog::document_start_injector(const std::string& markup,
|
||||
const char* dom_id,
|
||||
const char* position,
|
||||
@@ -244,7 +272,7 @@ void WebViewHostDialog::register_theme_user_scripts()
|
||||
// script message handler is registered separately (AddScriptMessageHandler), but on
|
||||
// some backends RemoveAllUserScripts() drops it too, which would break
|
||||
// window.wx.postMessage / HandleStudio. Live re-theme goes through apply_theme_live().
|
||||
m_browser->AddUserScript(wxString::FromUTF8(host_theme_user_script()));
|
||||
m_browser->AddUserScript(wxString::FromUTF8(theme_user_script()));
|
||||
add_user_scripts();
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,10 @@ public:
|
||||
const std::string& prelude = {},
|
||||
const std::string& on_inject = {});
|
||||
|
||||
// Shared by modeless Pages tabs and PluginWebDialog.
|
||||
static std::string theme_user_script();
|
||||
static std::string plugin_defaults_user_script();
|
||||
|
||||
protected:
|
||||
wxWebView* browser() const { return m_browser; }
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <string.h>
|
||||
#include <locale>
|
||||
#include <boost/locale/encoding_utf.hpp>
|
||||
#include <codecvt>
|
||||
#include <regex>
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -1953,8 +1952,7 @@ std::string fold_utf8_to_ascii(const std::string &src, bool is_convert_for_filen
|
||||
for (wchar_t c : wstr)
|
||||
fold_to_ascii(c, out);
|
||||
if (is_convert_for_filename) {
|
||||
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
|
||||
auto dstStr = converter.to_bytes(dst);
|
||||
auto dstStr = boost::locale::conv::utf_to_utf<char>(dst.c_str(), dst.c_str() + dst.size());
|
||||
|
||||
std::size_t found = dstStr.find_last_of("/\\");
|
||||
if (found != std::string::npos) {
|
||||
@@ -1964,7 +1962,7 @@ std::string fold_utf8_to_ascii(const std::string &src, bool is_convert_for_filen
|
||||
std::string newFileName = regex_replace(filename, reg, "");
|
||||
dstStr = dir + "\\" + newFileName;
|
||||
}
|
||||
dst = converter.from_bytes(dstStr);
|
||||
dst = boost::locale::conv::utf_to_utf<wchar_t>(dstStr.c_str(), dstStr.c_str() + dstStr.size());
|
||||
}
|
||||
|
||||
return boost::locale::conv::utf_to_utf<char>(dst.c_str(), dst.c_str() + dst.size());
|
||||
|
||||
@@ -572,7 +572,7 @@ int OrcaCloudServiceAgent::set_config_dir(std::string cfg_dir)
|
||||
{
|
||||
config_dir = cfg_dir;
|
||||
wxFileName fallback(wxString::FromUTF8(cfg_dir.c_str()), secret_constants::USER_SECRET_FILENAME);
|
||||
fallback.Normalize();
|
||||
fallback.MakeAbsolute();
|
||||
secret_fallback_path = fallback.GetFullPath().ToStdString();
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
@@ -1564,7 +1564,7 @@ void OrcaCloudServiceAgent::persist_user_secret(const std::string& secret)
|
||||
return;
|
||||
}
|
||||
wxFileName path(wxString::FromUTF8(secret_fallback_path.c_str()));
|
||||
path.Normalize();
|
||||
path.MakeAbsolute();
|
||||
if (!wxFileName::DirExists(path.GetPath())) {
|
||||
wxFileName::Mkdir(path.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
|
||||
}
|
||||
@@ -2487,7 +2487,7 @@ void OrcaCloudServiceAgent::compute_fallback_path()
|
||||
if (wxTheApp == nullptr)
|
||||
return;
|
||||
wxFileName fallback(wxStandardPaths::Get().GetUserDataDir(), "orca_refresh_token.sec");
|
||||
fallback.Normalize();
|
||||
fallback.MakeAbsolute();
|
||||
secret_fallback_path = fallback.GetFullPath().ToStdString();
|
||||
}
|
||||
|
||||
@@ -3581,7 +3581,7 @@ std::string OrcaCloudServiceAgent::token_lock_path() const
|
||||
if (config_dir.empty())
|
||||
return {};
|
||||
wxFileName lock(wxString::FromUTF8(config_dir.c_str()), "orca_refresh_token.lock");
|
||||
lock.Normalize();
|
||||
lock.MakeAbsolute();
|
||||
return lock.GetFullPath().ToStdString();
|
||||
}
|
||||
|
||||
|
||||
@@ -1044,46 +1044,42 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const
|
||||
std::set<std::string> bundles;
|
||||
// Orca: always install filament library
|
||||
bundles.insert(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(rsrc_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);
|
||||
if (bundles.find(vendor_name) != bundles.end())continue;
|
||||
// A vendor is named by its profile or, where the build ships preset caches
|
||||
// instead of the raw profile JSONs, by its cache alone.
|
||||
for (const std::string &vendor_name : vendor_names_in(rsrc_path)) {
|
||||
if (bundles.find(vendor_name) != bundles.end())continue;
|
||||
|
||||
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 ( fs::exists(path_in_vendor)) {
|
||||
if (is_vendor_enabled) {
|
||||
Semver resource_ver = get_version_from_json(file_path);
|
||||
Semver vendor_ver = get_version_from_json(path_in_vendor.string());
|
||||
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_enabled) {
|
||||
// Orca: whichever form of the vendor resources ships at the newer
|
||||
// version is the one installing lays down, and the one to judge
|
||||
// what is installed against.
|
||||
Semver resource_ver = resource_vendor_version(vendor_name);
|
||||
// Orca: a vendor installed as a preset cache has no profile
|
||||
// beside it; the version it was installed at is in the cache.
|
||||
Semver vendor_ver = installed_vendor_version(vendor_name);
|
||||
|
||||
if (vendor_ver < resource_ver) {
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:found vendor " << vendor_name << " newer version "
|
||||
<< resource_ver.to_string() << " from resource, old version " << vendor_ver.to_string();
|
||||
bundles.insert(vendor_name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
//need to be removed because not installed
|
||||
fs::remove(path_in_vendor);
|
||||
const auto path_of_vendor = vendor_path / vendor_name;
|
||||
if (fs::exists(path_of_vendor))
|
||||
fs::remove_all(path_of_vendor);
|
||||
if (vendor_ver < resource_ver) {
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:found vendor " << vendor_name << " newer version "
|
||||
<< resource_ver.to_string() << " from resource, old version " << vendor_ver.to_string();
|
||||
bundles.insert(vendor_name);
|
||||
}
|
||||
}
|
||||
else if (is_vendor_enabled) {
|
||||
bundles.insert(vendor_name);
|
||||
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) {
|
||||
bundles.insert(vendor_name);
|
||||
}
|
||||
}
|
||||
|
||||
if (bundles.size() > 0) {
|
||||
@@ -1163,11 +1159,12 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
|
||||
auto filament_in_cache = (cache_profile_path / vendor_name / PRESET_FILAMENT_NAME);
|
||||
auto machine_in_cache = (cache_profile_path / vendor_name / PRESET_PRINTER_NAME);
|
||||
|
||||
if (( fs::exists(path_in_vendor))
|
||||
if (is_vendor_installed(vendor_name)
|
||||
|| fs::exists(print_in_cache)
|
||||
|| fs::exists(filament_in_cache)
|
||||
|| fs::exists(machine_in_cache)) {
|
||||
Semver vendor_ver = get_version_from_json(path_in_vendor.string());
|
||||
// Orca: a vendor installed as a preset cache carries its version there.
|
||||
Semver vendor_ver = installed_vendor_version(vendor_name);
|
||||
|
||||
std::map<std::string, std::string> key_values;
|
||||
std::vector<std::string> keys(3);
|
||||
|
||||
@@ -368,7 +368,7 @@ void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
|
||||
emit_progress(100);
|
||||
if (the_job.switch_to_device_tab) {
|
||||
const auto mainframe = GUI::wxGetApp().mainframe;
|
||||
mainframe->request_select_tab(MainFrame::TabPosition::tpMonitor);
|
||||
mainframe->request_select_tab(TAB_ID_MONITOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ bool SimplyPrint::do_temp_upload(const boost::filesystem::path& file_path,
|
||||
wxLaunchDefaultBrowser(url);
|
||||
} else {
|
||||
const auto mainframe = GUI::wxGetApp().mainframe;
|
||||
mainframe->request_select_tab(MainFrame::TabPosition::tpMonitor);
|
||||
mainframe->request_select_tab(TAB_ID_MONITOR);
|
||||
mainframe->load_printer_url(url);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "PyPluginPackage.hpp"
|
||||
#include "PyPluginTrampoline.hpp"
|
||||
#include "pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp"
|
||||
#include "pluginTypes/pages/PagesPluginCapability.hpp"
|
||||
#include "pluginTypes/script/ScriptPluginCapability.hpp"
|
||||
#include "pluginTypes/slicingPipeline/SlicingPipelinePluginCapability.hpp"
|
||||
|
||||
@@ -319,17 +320,17 @@ void bind_python_api(pybind11::module_& m)
|
||||
{
|
||||
m.doc() = "OrcaSlicer plugin API";
|
||||
|
||||
auto pluginTypes = py::enum_<PluginCapabilityType>(m, "PluginType", "Available plugin capability groups")
|
||||
.value("PrinterConnection", PluginCapabilityType::PrinterConnection)
|
||||
.value("Automation", PluginCapabilityType::Automation)
|
||||
.value("Analysis", PluginCapabilityType::Analysis)
|
||||
.value("Importer", PluginCapabilityType::Importer)
|
||||
.value("Exporter", PluginCapabilityType::Exporter)
|
||||
.value("Visualization", PluginCapabilityType::Visualization)
|
||||
.value("Script", PluginCapabilityType::Script)
|
||||
.value("SlicingPipeline", PluginCapabilityType::SlicingPipeline)
|
||||
.value("Unknown", PluginCapabilityType::Unknown)
|
||||
.export_values();
|
||||
py::enum_<PluginCapabilityType>(m, "PluginType", "Available plugin capability groups")
|
||||
.value("PrinterConnection", PluginCapabilityType::PrinterConnection)
|
||||
.value("Pages", PluginCapabilityType::Pages)
|
||||
.value("Analysis", PluginCapabilityType::Analysis)
|
||||
.value("Importer", PluginCapabilityType::Importer)
|
||||
.value("Exporter", PluginCapabilityType::Exporter)
|
||||
.value("Visualization", PluginCapabilityType::Visualization)
|
||||
.value("Script", PluginCapabilityType::Script)
|
||||
.value("SlicingPipeline", PluginCapabilityType::SlicingPipeline)
|
||||
.value("Unknown", PluginCapabilityType::Unknown)
|
||||
.export_values();
|
||||
|
||||
py::enum_<PluginResult>(m, "PluginResult", "Execution summary code")
|
||||
.value("Success", PluginResult::Success)
|
||||
@@ -419,9 +420,10 @@ void bind_python_api(pybind11::module_& m)
|
||||
BOOST_LOG_TRIVIAL(debug) << "Registering embedded Python plugin type bindings";
|
||||
|
||||
// Make sure you register your bindings here
|
||||
PrinterAgentPluginCapability::RegisterBindings(m, pluginTypes);
|
||||
ScriptPluginCapability::RegisterBindings(m, pluginTypes);
|
||||
SlicingPipelinePluginCapability::RegisterBindings(m, pluginTypes);
|
||||
PrinterAgentPluginCapability::RegisterBindings(m);
|
||||
PagesPluginCapability::RegisterBindings(m);
|
||||
ScriptPluginCapability::RegisterBindings(m);
|
||||
SlicingPipelinePluginCapability::RegisterBindings(m);
|
||||
PluginHost::RegisterBindings(m);
|
||||
BOOST_LOG_TRIVIAL(debug) << "Registered ScriptPluginCapability Python bindings";
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class PluginCapabilityType { PrinterConnection = 0, Automation, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown };
|
||||
enum class PluginCapabilityType { PrinterConnection = 0, Pages, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown };
|
||||
|
||||
struct PluginCapabilityId
|
||||
{
|
||||
@@ -39,7 +39,7 @@ inline std::string plugin_capability_type_to_string(PluginCapabilityType type)
|
||||
{
|
||||
switch (type) {
|
||||
case PluginCapabilityType::PrinterConnection: return "printer-connection";
|
||||
case PluginCapabilityType::Automation: return "automation";
|
||||
case PluginCapabilityType::Pages: return "pages";
|
||||
case PluginCapabilityType::Analysis: return "analysis";
|
||||
case PluginCapabilityType::Importer: return "importer";
|
||||
case PluginCapabilityType::Exporter: return "exporter";
|
||||
@@ -54,7 +54,7 @@ inline std::string plugin_capability_type_display_name(PluginCapabilityType type
|
||||
{
|
||||
switch (type) {
|
||||
case PluginCapabilityType::PrinterConnection: return "Printer connection";
|
||||
case PluginCapabilityType::Automation: return "Automation";
|
||||
case PluginCapabilityType::Pages: return "Pages";
|
||||
case PluginCapabilityType::Analysis: return "Analysis";
|
||||
case PluginCapabilityType::Importer: return "Importer";
|
||||
case PluginCapabilityType::Exporter: return "Exporter";
|
||||
@@ -76,8 +76,8 @@ inline PluginCapabilityType plugin_capability_type_from_string(std::string_view
|
||||
|
||||
if (lowered == "printer-connection")
|
||||
return PluginCapabilityType::PrinterConnection;
|
||||
if (lowered == "automation")
|
||||
return PluginCapabilityType::Automation;
|
||||
if (lowered == "pages")
|
||||
return PluginCapabilityType::Pages;
|
||||
if (lowered == "analysis")
|
||||
return PluginCapabilityType::Analysis;
|
||||
if (lowered == "importer")
|
||||
|
||||
@@ -0,0 +1,469 @@
|
||||
#include "PluginPages.hpp"
|
||||
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/Notebook.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Widgets/Button.hpp"
|
||||
#include "slic3r/GUI/Widgets/WebView.hpp"
|
||||
#include "slic3r/GUI/Widgets/WebViewHostDialog.hpp"
|
||||
#include "slic3r/GUI/wxExtensions.hpp"
|
||||
#include "slic3r/plugin/PluginManager.hpp"
|
||||
|
||||
#include <libslic3r/Utils.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <wx/bookctrl.h>
|
||||
#include <wx/menu.h>
|
||||
#include <wx/sizer.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace {
|
||||
|
||||
constexpr char PLUGIN_PAGE_BRIDGE_JS[] = R"JS(
|
||||
(function () {
|
||||
if (window.top !== window.self) return;
|
||||
if (window.orca) return;
|
||||
var handlers = [];
|
||||
function deliver(payload, attempts) {
|
||||
try {
|
||||
if (window.wx && typeof window.wx.postMessage === 'function') {
|
||||
window.wx.postMessage(payload);
|
||||
return;
|
||||
}
|
||||
} catch (e) { /* retry while the native handler is being registered */ }
|
||||
if (attempts < 100)
|
||||
window.setTimeout(function () { deliver(payload, attempts + 1); }, 25);
|
||||
}
|
||||
function send(data) {
|
||||
deliver(JSON.stringify({
|
||||
channel: 'orca', kind: 'message', data: (data === undefined ? null : data)
|
||||
}), 0);
|
||||
}
|
||||
window.orca = {
|
||||
postMessage: function (data) { send(data); },
|
||||
onMessage: function (callback) {
|
||||
if (typeof callback === 'function') handlers.push(callback);
|
||||
}
|
||||
};
|
||||
window.__orcaDispatch = function (payload) {
|
||||
var data = payload ? payload.data : null;
|
||||
for (var i = 0; i < handlers.length; i++) {
|
||||
try { handlers[i](data); } catch (e) {}
|
||||
}
|
||||
};
|
||||
})();
|
||||
)JS";
|
||||
|
||||
} // namespace
|
||||
|
||||
PluginPage::PluginPage(wxWindow* parent, std::shared_ptr<PagesPluginCapability> capability)
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
||||
, m_cap(std::move(capability))
|
||||
, m_lifetime(std::make_shared<std::atomic<PluginPage*>>(this))
|
||||
{
|
||||
auto* topsizer = new wxBoxSizer(wxVERTICAL);
|
||||
SetSizer(topsizer);
|
||||
|
||||
m_browser = WebView::CreateWebView(this, bootstrap_url());
|
||||
if (m_browser == nullptr) {
|
||||
wxLogError("Could not initialize plugin page web view");
|
||||
return;
|
||||
}
|
||||
|
||||
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||
m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PluginPage::on_bootstrap_event, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_ERROR, &PluginPage::on_bootstrap_event, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_NEWWINDOW, &PluginPage::on_new_window, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PluginPage::on_script_message, this);
|
||||
m_browser->AddUserScript(wxString::FromUTF8(GUI::WebViewHostDialog::theme_user_script()));
|
||||
m_browser->AddUserScript(wxString::FromUTF8(GUI::WebViewHostDialog::plugin_defaults_user_script()));
|
||||
m_browser->AddUserScript(PLUGIN_PAGE_BRIDGE_JS);
|
||||
|
||||
const std::shared_ptr<std::atomic<PluginPage*>> lifetime = m_lifetime;
|
||||
m_cap->set_message_sender([lifetime](const std::string& message) {
|
||||
if (wxTheApp == nullptr)
|
||||
return;
|
||||
|
||||
GUI::wxGetApp().CallAfter([lifetime, message] {
|
||||
if (PluginPage* page = lifetime->load(std::memory_order_acquire))
|
||||
page->push_message(message);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
PluginPage::~PluginPage()
|
||||
{
|
||||
detach_capability();
|
||||
if (m_lifetime)
|
||||
m_lifetime->store(nullptr, std::memory_order_release);
|
||||
}
|
||||
|
||||
void PluginPage::detach_capability()
|
||||
{
|
||||
if (m_lifetime)
|
||||
m_lifetime->store(nullptr, std::memory_order_release);
|
||||
if (m_cap)
|
||||
m_cap->clear_message_sender();
|
||||
m_cap.reset();
|
||||
}
|
||||
|
||||
wxString PluginPage::web_base_url() const
|
||||
{
|
||||
const auto path = (boost::filesystem::path(resources_dir()) / "web").make_preferred().string();
|
||||
return wxString("file://") + GUI::from_u8(path) + "/";
|
||||
}
|
||||
|
||||
wxString PluginPage::bootstrap_url() const
|
||||
{
|
||||
const auto path = (boost::filesystem::path(resources_dir()) / "web/dialog/PluginWebDialog/blank.html").make_preferred().string();
|
||||
return wxString("file://") + GUI::from_u8(path);
|
||||
}
|
||||
|
||||
void PluginPage::on_bootstrap_event(wxWebViewEvent& event)
|
||||
{
|
||||
load_plugin_content();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void PluginPage::load_plugin_content()
|
||||
{
|
||||
if (m_content_loaded || m_browser == nullptr || m_cap == nullptr)
|
||||
return;
|
||||
|
||||
m_content_loaded = true;
|
||||
try {
|
||||
m_browser->SetPage(wxString::FromUTF8(m_cap->get_ui()), web_base_url());
|
||||
} catch (const std::exception& error) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to load plugin page '" << m_cap->name() << "': " << error.what();
|
||||
detach_capability();
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to load plugin page '" << m_cap->name() << "'";
|
||||
detach_capability();
|
||||
}
|
||||
}
|
||||
|
||||
void PluginPage::on_new_window(wxWebViewEvent& event)
|
||||
{
|
||||
const wxString url = event.GetURL();
|
||||
if (!url.empty() && m_browser != nullptr)
|
||||
m_browser->LoadURL(url);
|
||||
event.Veto();
|
||||
}
|
||||
|
||||
void PluginPage::on_script_message(wxWebViewEvent& event)
|
||||
{
|
||||
if (!m_cap)
|
||||
return;
|
||||
|
||||
const wxString payload = event.GetString();
|
||||
nlohmann::json root = nlohmann::json::parse(payload.utf8_string(), nullptr, false);
|
||||
if (root.is_discarded() || root.value("channel", std::string()) != "orca" ||
|
||||
root.value("kind", std::string()) != "message")
|
||||
return;
|
||||
|
||||
const auto data = root.find("data");
|
||||
try {
|
||||
m_cap->on_message(data == root.end()
|
||||
? "null"
|
||||
: data->dump(-1, ' ', false, nlohmann::json::error_handler_t::replace));
|
||||
} catch (const std::exception& error) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Plugin page message handler failed for '" << m_cap->name() << "': " << error.what();
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Plugin page message handler failed for '" << m_cap->name() << "'";
|
||||
}
|
||||
}
|
||||
|
||||
void PluginPage::push_message(const std::string& message)
|
||||
{
|
||||
if (m_browser == nullptr)
|
||||
return;
|
||||
|
||||
// PagesPluginCapability::post_message() already dumps JSON, so accept it as-is; only a
|
||||
// non-JSON payload needs wrapping as a string literal.
|
||||
const std::string payload = nlohmann::json::accept(message)
|
||||
? message
|
||||
: nlohmann::json(message).dump(-1, ' ', false, nlohmann::json::error_handler_t::replace);
|
||||
|
||||
WebView::RunScript(m_browser, wxString::Format(
|
||||
"(function dispatch(payload, attempts) {\n"
|
||||
" if (typeof window.__orcaDispatch === 'function') { window.__orcaDispatch(payload); return; }\n"
|
||||
" if (attempts < 100) window.setTimeout(function() { dispatch(payload, attempts + 1); }, 25);\n"
|
||||
"})({data: %s}, 0);",
|
||||
wxString::FromUTF8(payload)));
|
||||
}
|
||||
|
||||
PluginPages::~PluginPages()
|
||||
{
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void PluginPages::initialize(Notebook* parent)
|
||||
{
|
||||
shutdown();
|
||||
m_parent = parent;
|
||||
if (m_parent == nullptr)
|
||||
return;
|
||||
|
||||
m_visible_page_count = GUI::wxGetApp().app_config->get_plugin_pages_visible_count();
|
||||
|
||||
for (const auto& capability : PluginManager::instance().get_plugin_capabilities("", PluginCapabilityType::Pages)) {
|
||||
if (capability)
|
||||
create_page(capability->identity());
|
||||
}
|
||||
relayout();
|
||||
}
|
||||
|
||||
void PluginPages::shutdown()
|
||||
{
|
||||
while (!m_pages.empty())
|
||||
remove_page(m_pages.begin()->first);
|
||||
m_parent = nullptr;
|
||||
}
|
||||
|
||||
void PluginPages::set_visible_page_count(int count)
|
||||
{
|
||||
const int clamped = std::clamp(count, PLUGIN_PAGES_VISIBLE_COUNT_MIN, PLUGIN_PAGES_VISIBLE_COUNT_MAX);
|
||||
if (clamped == m_visible_page_count)
|
||||
return;
|
||||
|
||||
m_visible_page_count = clamped;
|
||||
relayout();
|
||||
}
|
||||
|
||||
std::shared_ptr<PagesPluginCapability> PluginPages::get_pages_cap(const PluginCapabilityId& id, bool is_enabled) const
|
||||
{
|
||||
auto capability = PluginManager::instance().get_plugin_capability(id, /*only_enabled=*/false);
|
||||
if (!capability || capability->is_enabled() != is_enabled || capability->type() != PluginCapabilityType::Pages)
|
||||
return nullptr;
|
||||
|
||||
return std::dynamic_pointer_cast<PagesPluginCapability>(capability);
|
||||
}
|
||||
|
||||
bool PluginPages::create_page(const PluginCapabilityId& id)
|
||||
{
|
||||
if (m_pages.find(id) != m_pages.end())
|
||||
return false;
|
||||
|
||||
auto capability = get_pages_cap(id, true);
|
||||
if (!capability)
|
||||
return false;
|
||||
|
||||
std::string icon;
|
||||
try {
|
||||
icon = capability->get_icon();
|
||||
} catch (const std::exception& error) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to get icon for plugin " << id.plugin_key << ": " << error.what();
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to get icon for plugin " << id.plugin_key;
|
||||
}
|
||||
|
||||
auto* page = new PluginPage(m_parent, std::move(capability));
|
||||
if (!page->is_valid()) {
|
||||
page->Destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!icon.empty()) {
|
||||
try {
|
||||
boost::filesystem::path icon_path(icon);
|
||||
const std::string extension = icon_path.extension().string();
|
||||
if (extension == ".svg" || extension == ".png")
|
||||
icon_path.replace_extension();
|
||||
|
||||
page->set_icon(create_scaled_bitmap(icon_path.string(), m_parent, 20));
|
||||
} catch (const std::exception& error) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to load icon for plugin " << id.plugin_key << ": " << error.what();
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to load icon for plugin " << id.plugin_key;
|
||||
}
|
||||
}
|
||||
|
||||
m_pages.emplace(id, page);
|
||||
m_order.push_back(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PluginPages::on_cap_register(const PluginCapabilityId& id)
|
||||
{
|
||||
if (m_parent == nullptr)
|
||||
return;
|
||||
|
||||
if (create_page(id))
|
||||
relayout();
|
||||
}
|
||||
|
||||
void PluginPages::on_cap_deregister(const PluginCapabilityId& id)
|
||||
{
|
||||
remove_page(id);
|
||||
}
|
||||
|
||||
void PluginPages::on_plugin_register(const std::string& plugin_key)
|
||||
{
|
||||
for (const auto& capability : PluginManager::instance().get_plugin_capabilities(plugin_key, PluginCapabilityType::Pages)) {
|
||||
if (capability)
|
||||
on_cap_register(capability->identity());
|
||||
}
|
||||
}
|
||||
|
||||
void PluginPages::on_plugin_deregister(const std::string& plugin_key)
|
||||
{
|
||||
for (auto it = m_pages.begin(); it != m_pages.end();) {
|
||||
if (it->first.plugin_key != plugin_key) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
const PluginCapabilityId id = it->first;
|
||||
++it;
|
||||
remove_page(id);
|
||||
}
|
||||
}
|
||||
|
||||
void PluginPages::remove_page(const PluginCapabilityId& id)
|
||||
{
|
||||
auto it = m_pages.find(id);
|
||||
if (it == m_pages.end())
|
||||
return;
|
||||
|
||||
PluginPage* page = it->second;
|
||||
page->detach_capability();
|
||||
|
||||
m_pages.erase(it);
|
||||
m_order.erase(std::remove(m_order.begin(), m_order.end(), id), m_order.end());
|
||||
|
||||
const int idx = m_parent != nullptr ? m_parent->FindPage(page) : wxNOT_FOUND;
|
||||
if (idx != wxNOT_FOUND)
|
||||
m_parent->RemovePage(idx);
|
||||
|
||||
relayout();
|
||||
page->Destroy();
|
||||
}
|
||||
|
||||
wxString PluginPages::page_tab_id(const PluginCapabilityId& id)
|
||||
{
|
||||
return wxString::FromUTF8("plugin." + id.plugin_key + "." + id.name);
|
||||
}
|
||||
|
||||
void PluginPages::relayout()
|
||||
{
|
||||
if (m_parent == nullptr)
|
||||
return;
|
||||
|
||||
m_order.erase(std::remove_if(m_order.begin(), m_order.end(),
|
||||
[this](const PluginCapabilityId& id) {
|
||||
const bool orphaned = m_pages.find(id) == m_pages.end();
|
||||
if (orphaned)
|
||||
BOOST_LOG_TRIVIAL(error) << "PluginPages::relayout: '" << id.name << "' was in m_order but not m_pages, dropping";
|
||||
return orphaned;
|
||||
}),
|
||||
m_order.end());
|
||||
|
||||
const int visible_slots = std::max(1, m_visible_page_count);
|
||||
const bool need_overflow = static_cast<int>(m_order.size()) > visible_slots;
|
||||
|
||||
// Every visible slot is a normal, individual tab hosting its own page. When there's
|
||||
// overflow, the last slot's page is swappable via m_overflow_button/show_overflow_menu()
|
||||
// rather than being a fixed page — m_swapped_in_id tracks which one currently sits there.
|
||||
std::vector<PluginCapabilityId> tab_ids;
|
||||
if (!need_overflow) {
|
||||
tab_ids = m_order;
|
||||
m_swapped_in_id.reset();
|
||||
} else {
|
||||
const auto overflow_begin = m_order.begin() + (visible_slots - 1);
|
||||
tab_ids.assign(m_order.begin(), overflow_begin);
|
||||
|
||||
if (!m_swapped_in_id || std::find(overflow_begin, m_order.end(), *m_swapped_in_id) == m_order.end())
|
||||
m_swapped_in_id = *overflow_begin;
|
||||
tab_ids.push_back(*m_swapped_in_id);
|
||||
}
|
||||
|
||||
// MainFrame::show_device() relayouts on every printer change and most of those change
|
||||
// nothing, so only touch the notebook when the trailing slots don't already spell out
|
||||
// tab_ids — a rebuild destroys and recreates every tab button and rasterizes every icon.
|
||||
const size_t page_count = m_parent->GetPageCount();
|
||||
bool up_to_date = page_count >= tab_ids.size();
|
||||
for (size_t i = 0; up_to_date && i < tab_ids.size(); ++i)
|
||||
up_to_date = m_parent->GetPageName(page_count - tab_ids.size() + i) == page_tab_id(tab_ids[i]);
|
||||
for (const auto& [id, page] : m_pages) {
|
||||
if (!up_to_date)
|
||||
break;
|
||||
const bool wanted = std::find(tab_ids.begin(), tab_ids.end(), id) != tab_ids.end();
|
||||
up_to_date = (m_parent->FindPage(page) != wxNOT_FOUND) == wanted;
|
||||
}
|
||||
|
||||
if (!up_to_date) {
|
||||
const wxString id_to_reselect = m_parent->GetSelectedPageName();
|
||||
|
||||
for (const auto& [id, page] : m_pages) {
|
||||
const int idx = m_parent->FindPage(page);
|
||||
if (idx != wxNOT_FOUND)
|
||||
m_parent->RemovePage(idx);
|
||||
}
|
||||
|
||||
for (const auto& id : tab_ids) {
|
||||
PluginPage* page = m_pages.at(id);
|
||||
m_parent->InsertPage(m_parent->GetPageCount(), page_tab_id(id), page, wxString::FromUTF8(id.name), "",
|
||||
false, page->icon());
|
||||
}
|
||||
|
||||
if (!id_to_reselect.empty())
|
||||
m_parent->SelectPageByName(id_to_reselect);
|
||||
}
|
||||
|
||||
if (need_overflow) {
|
||||
if (m_overflow_button == nullptr) {
|
||||
auto* btn = new Button(m_parent->GetBtnsListCtrl(), wxString(L"\u25BE"), wxString(), wxNO_BORDER);
|
||||
btn->SetCornerRadius(0);
|
||||
const int em = em_unit(m_parent);
|
||||
btn->SetMinSize({40 * em / 10, 36 * em / 10});
|
||||
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { show_overflow_menu(); });
|
||||
GUI::wxGetApp().UpdateDarkUI(btn);
|
||||
m_overflow_button = btn;
|
||||
}
|
||||
m_parent->SetOverflowButton(m_overflow_button);
|
||||
} else if (m_overflow_button != nullptr) {
|
||||
m_parent->SetOverflowButton(nullptr);
|
||||
m_overflow_button->Destroy();
|
||||
m_overflow_button = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PluginPages::show_overflow_menu()
|
||||
{
|
||||
const int visible_slots = std::max(1, m_visible_page_count);
|
||||
if (m_overflow_button == nullptr || static_cast<int>(m_order.size()) <= visible_slots)
|
||||
return;
|
||||
|
||||
const std::vector<PluginCapabilityId> overflow_ids(m_order.begin() + (visible_slots - 1), m_order.end());
|
||||
|
||||
wxMenu menu;
|
||||
for (size_t i = 0; i < overflow_ids.size(); ++i)
|
||||
menu.AppendRadioItem(static_cast<int>(wxID_HIGHEST + 1 + i), wxString::FromUTF8(overflow_ids[i].name));
|
||||
if (m_swapped_in_id) {
|
||||
const auto it = std::find(overflow_ids.begin(), overflow_ids.end(), *m_swapped_in_id);
|
||||
if (it != overflow_ids.end())
|
||||
menu.Check(static_cast<int>(wxID_HIGHEST + 1 + (it - overflow_ids.begin())), true);
|
||||
}
|
||||
|
||||
menu.Bind(wxEVT_MENU, [this, overflow_ids](wxCommandEvent& evt) {
|
||||
const size_t index = static_cast<size_t>(evt.GetId() - (wxID_HIGHEST + 1));
|
||||
if (index >= overflow_ids.size())
|
||||
return;
|
||||
m_swapped_in_id = overflow_ids[index];
|
||||
relayout();
|
||||
m_parent->SelectPageByName(page_tab_id(*m_swapped_in_id));
|
||||
});
|
||||
m_overflow_button->PopupMenu(&menu);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||
#include <slic3r/plugin/pluginTypes/pages/PagesPluginCapability.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/webview.h>
|
||||
|
||||
class Notebook;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PluginPage : public wxPanel
|
||||
{
|
||||
public:
|
||||
PluginPage(wxWindow* parent, std::shared_ptr<PagesPluginCapability> capability);
|
||||
~PluginPage() override;
|
||||
|
||||
PluginPage() = delete;
|
||||
|
||||
bool is_valid() const { return m_browser != nullptr && m_cap != nullptr; }
|
||||
void detach_capability();
|
||||
void on_bootstrap_event(wxWebViewEvent& event);
|
||||
void on_new_window(wxWebViewEvent& event);
|
||||
void on_script_message(wxWebViewEvent& event);
|
||||
void push_message(const std::string& message);
|
||||
void set_icon(const wxBitmap& icon) { m_icon = icon; }
|
||||
const wxBitmap& icon() const { return m_icon; }
|
||||
|
||||
private:
|
||||
void load_plugin_content();
|
||||
wxString bootstrap_url() const;
|
||||
wxString web_base_url() const;
|
||||
|
||||
wxWebView* m_browser{nullptr};
|
||||
std::shared_ptr<PagesPluginCapability> m_cap;
|
||||
std::shared_ptr<std::atomic<PluginPage*>> m_lifetime;
|
||||
bool m_content_loaded{false};
|
||||
wxBitmap m_icon;
|
||||
};
|
||||
|
||||
class PluginPages
|
||||
{
|
||||
public:
|
||||
PluginPages() = default;
|
||||
~PluginPages();
|
||||
|
||||
PluginPages(const PluginPages&) = delete;
|
||||
PluginPages& operator=(const PluginPages&) = delete;
|
||||
|
||||
void initialize(Notebook* parent);
|
||||
void shutdown();
|
||||
|
||||
void on_cap_register(const PluginCapabilityId& id);
|
||||
void on_cap_deregister(const PluginCapabilityId& id);
|
||||
void on_plugin_register(const std::string& plugin_key);
|
||||
void on_plugin_deregister(const std::string& plugin_key);
|
||||
|
||||
void set_visible_page_count(int count);
|
||||
|
||||
void relayout();
|
||||
|
||||
private:
|
||||
std::shared_ptr<PagesPluginCapability> get_pages_cap(const PluginCapabilityId& id, bool is_enabled) const;
|
||||
bool create_page(const PluginCapabilityId& id);
|
||||
void remove_page(const PluginCapabilityId& id);
|
||||
|
||||
void show_overflow_menu();
|
||||
static wxString page_tab_id(const PluginCapabilityId& id);
|
||||
|
||||
std::map<PluginCapabilityId, PluginPage*> m_pages;
|
||||
std::vector<PluginCapabilityId> m_order;
|
||||
Notebook* m_parent{nullptr};
|
||||
|
||||
int m_visible_page_count{0};
|
||||
|
||||
std::optional<PluginCapabilityId> m_swapped_in_id;
|
||||
wxWindow* m_overflow_button{nullptr};
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,60 @@
|
||||
#include "PagesPluginCapability.hpp"
|
||||
#include "PagesPluginCapabilityTrampoline.hpp"
|
||||
|
||||
#include "../../PluginFsUtils.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void PagesPluginCapability::RegisterBindings(pybind11::module_& module)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "Registering orca.pages bindings";
|
||||
|
||||
auto pages = module.def_submodule("pages", "Plugin page API");
|
||||
|
||||
py::class_<PagesPluginCapability, PluginCapabilityInterface, PyPagesPluginCapabilityTrampoline,
|
||||
std::shared_ptr<PagesPluginCapability>>(pages, "PagesPluginCapabilityBase")
|
||||
.def(py::init<>())
|
||||
.def("get_type", &PagesPluginCapability::get_type)
|
||||
.def("get_ui", &PagesPluginCapability::get_ui)
|
||||
.def("get_icon", &PagesPluginCapability::get_icon)
|
||||
.def("on_message", &PagesPluginCapability::on_message)
|
||||
.def(
|
||||
"post_message",
|
||||
[](PagesPluginCapability& capability, py::object data) {
|
||||
capability.post_message(py_to_json(data).dump());
|
||||
},
|
||||
py::arg("data"), "Send a JSON-compatible value to the page's window.orca.onMessage handlers.");
|
||||
}
|
||||
|
||||
void PagesPluginCapability::post_message(std::string message)
|
||||
{
|
||||
std::function<void(const std::string&)> sender;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_message_mutex);
|
||||
sender = m_message_sender;
|
||||
}
|
||||
|
||||
if (sender)
|
||||
sender(message);
|
||||
}
|
||||
|
||||
void PagesPluginCapability::set_message_sender(std::function<void(const std::string&)> sender)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_message_mutex);
|
||||
m_message_sender = std::move(sender);
|
||||
}
|
||||
|
||||
void PagesPluginCapability::clear_message_sender()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_message_mutex);
|
||||
m_message_sender = nullptr;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef slic3r_PagesPluginCapability_hpp_
|
||||
#define slic3r_PagesPluginCapability_hpp_
|
||||
|
||||
#include "../../PythonPluginInterface.hpp"
|
||||
#include "pybind11/pybind11.h"
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
class PagesPluginCapability : public PluginCapabilityInterface
|
||||
{
|
||||
public:
|
||||
static void RegisterBindings(pybind11::module_& module);
|
||||
|
||||
PluginCapabilityType get_type() const override { return PluginCapabilityType::Pages; }
|
||||
|
||||
virtual std::string get_ui() = 0;
|
||||
virtual void on_message(std::string message) { (void) message; }
|
||||
virtual std::string get_icon() { return {}; }
|
||||
|
||||
void post_message(std::string message);
|
||||
void set_message_sender(std::function<void(const std::string&)> sender);
|
||||
void clear_message_sender();
|
||||
|
||||
private:
|
||||
mutable std::mutex m_message_mutex;
|
||||
std::function<void(const std::string&)> m_message_sender;
|
||||
};
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "PagesPluginCapability.hpp"
|
||||
#include "../../PluginFsUtils.hpp"
|
||||
#include "../../PyPluginTrampoline.hpp"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PyPagesPluginCapabilityTrampoline : public PyPluginCommonTrampoline<PagesPluginCapability>
|
||||
{
|
||||
public:
|
||||
using PyPluginCommonTrampoline<PagesPluginCapability>::PyPluginCommonTrampoline;
|
||||
|
||||
std::string get_icon() override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
||||
[] {},
|
||||
PYBIND11_OVERRIDE,
|
||||
std::string,
|
||||
PagesPluginCapability,
|
||||
get_icon);
|
||||
}
|
||||
|
||||
std::string get_ui() override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
||||
[] {},
|
||||
PYBIND11_OVERRIDE_PURE,
|
||||
std::string,
|
||||
PagesPluginCapability,
|
||||
get_ui);
|
||||
}
|
||||
|
||||
void on_message(std::string message) override
|
||||
{
|
||||
PluginCapabilityInterface::RefCounter ref_counter(*this);
|
||||
PythonGILState gil;
|
||||
if (!gil)
|
||||
throw std::runtime_error("Python interpreter is shutting down");
|
||||
|
||||
ORCA_PY_AUDIT_SCOPE(::Slic3r::PluginAuditManager::AuditMode::Loading);
|
||||
|
||||
pybind11::function override = pybind11::get_override(static_cast<PagesPluginCapability*>(this), "on_message");
|
||||
if (!override)
|
||||
return;
|
||||
|
||||
nlohmann::json data = nlohmann::json::parse(message, nullptr, false);
|
||||
if (data.is_discarded())
|
||||
data = message;
|
||||
|
||||
ORCA_PY_LOGGED_OVERRIDE_BODY(override(::Slic3r::json_to_py(data)));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -13,10 +13,8 @@ namespace py = pybind11;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module, pybind11::enum_<PluginCapabilityType>& pluginTypes)
|
||||
void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module)
|
||||
{
|
||||
(void) pluginTypes;
|
||||
|
||||
auto printer_agent_module = module.def_submodule("printer_agent", "Printer Agent API");
|
||||
|
||||
py::enum_<FilamentSyncMode>(printer_agent_module, "FilamentSyncMode")
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Slic3r {
|
||||
class PrinterAgentPluginCapability : public PluginCapabilityInterface, public IPrinterAgent
|
||||
{
|
||||
public:
|
||||
static void RegisterBindings(pybind11::module_& module, pybind11::enum_<PluginCapabilityType>& pluginTypes);
|
||||
static void RegisterBindings(pybind11::module_& module);
|
||||
|
||||
PluginCapabilityType get_type() const override { return PluginCapabilityType::PrinterConnection; }
|
||||
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace Slic3r {
|
||||
void ScriptPluginCapability::RegisterBindings(pybind11::module_& module, pybind11::enum_<PluginCapabilityType>& pluginTypes)
|
||||
void ScriptPluginCapability::RegisterBindings(pybind11::module_& module)
|
||||
{
|
||||
(void) pluginTypes;
|
||||
BOOST_LOG_TRIVIAL(debug) << "Registering orca.script bindings";
|
||||
|
||||
auto script = module.def_submodule("script", "Script Plugins API");
|
||||
|
||||
@@ -11,8 +11,7 @@ public:
|
||||
|
||||
virtual ExecutionResult execute() = 0;
|
||||
|
||||
static void RegisterBindings(pybind11::module_ &module,
|
||||
pybind11::enum_<PluginCapabilityType> &pluginTypes);
|
||||
static void RegisterBindings(pybind11::module_ &module);
|
||||
};
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@ namespace Slic3r {
|
||||
|
||||
bool SlicingPipelineContext::cancelled() const { return print && print->canceled(); }
|
||||
|
||||
void SlicingPipelinePluginCapability::RegisterBindings(py::module_& module, py::enum_<PluginCapabilityType>& pluginTypes) {
|
||||
(void) pluginTypes; // unused: this capability defines its own Step enum (below) rather than extending the shared PluginCapabilityType enum.
|
||||
void SlicingPipelinePluginCapability::RegisterBindings(py::module_& module) {
|
||||
auto slicing = module.def_submodule("slicing", "Slicing pipeline API (research/experimental).");
|
||||
|
||||
py::enum_<SlicingPipelineStepPlugin>(slicing, "Step")
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
// Runs on the slicing worker thread. Do not call orca.host.ui.* here: the UI thread can be
|
||||
// blocked waiting on the slicing worker, so a marshaled UI call from this thread can deadlock.
|
||||
virtual ExecutionResult execute(SlicingPipelineContext& ctx) = 0;
|
||||
static void RegisterBindings(pybind11::module_& module, pybind11::enum_<PluginCapabilityType>& pluginTypes);
|
||||
static void RegisterBindings(pybind11::module_& module);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
Reference in New Issue
Block a user