Update translations. Code dedup and cleanup

This commit is contained in:
Lam Wei Lun
2026-09-11 13:05:39 +08:00
parent 42bee12481
commit 7c2991d00c
47 changed files with 2559 additions and 1180 deletions
+50 -82
View File
@@ -141,9 +141,6 @@ std::unique_ptr<AppAction> make_action(const std::string& plugin_key, const std:
// ---- built-in command actions (the speed dial "commands" section) ------
constexpr const char* kCommandPrefix = "orca_command";
constexpr const char* kOrcaSourceKey = "orca";
constexpr const char* kOrcaSourceName = "OrcaSlicer";
constexpr const char* kSettingPrefix = "orca_setting";
constexpr const char* kPlateGotoPrefix = "orca_plate_goto";
constexpr const char* kRecentProjectPrefix = "orca_recent_project";
@@ -212,29 +209,28 @@ struct SettingAction : AppAction
}
};
// A built-in command action. Thin value: identity + presentation come from the NativeCommands
// catalog, and run() routes back to it - the catalog is the single source of truth for its
// behaviour. The id is keyed by the stable catalog key (NOT the display title), so a rename or a
// UI-language switch never re-keys the action; the title is display-only.
struct CommandAction : AppAction
// Seed one action's persisted state (favourite flag + frecency counters) from an already-parsed
// stats blob and capped favourite list. Shared by the dynamic materialisers.
void seed_from(const nlohmann::json& stats, const std::vector<std::string>& favs, const std::string& id, AppAction& a)
{
static std::unique_ptr<CommandAction> make(const NativeCommand& c) { return std::unique_ptr<CommandAction>(new CommandAction(c)); }
std::string command_key;
AppActionRunResult run(const std::string& param) const override { return NativeCommands::run(command_key, param); }
private:
explicit CommandAction(const NativeCommand& c)
: AppAction(AppActionId{AppAction::compose_id(kCommandPrefix, c.key, kOrcaSourceKey)}, c.title, kOrcaSourceKey, kOrcaSourceName)
, command_key(c.key)
{
this->kind = AppActionKind::Command;
this->group = c.group;
this->input = c.input;
this->icon = c.icon;
a.favourite = std::find(favs.begin(), favs.end(), id) != favs.end();
if (auto it = stats.find(id); it != stats.end() && it->is_object()) {
a.count = it->value("count", 0);
a.last = it->value("last", 0LL);
}
};
}
// Drop actions whose id starts with `prefix` but that were not seen in this pass (a stale materialisation).
void drop_stale(std::unordered_map<std::string, std::shared_ptr<AppAction>>& actions, const char* prefix,
const std::unordered_set<std::string>& seen)
{
for (auto it = actions.begin(); it != actions.end();) {
if (it->first.rfind(prefix, 0) == 0 && !seen.count(it->first))
it = actions.erase(it);
else
++it;
}
}
// A dynamic "Go to Plate N" action, one per live plate, rebuilt on every snapshot() (so a
// rename/move immediately shows up). id is keyed by plate index, NOT the display title, so
@@ -343,10 +339,10 @@ void ActionRegistry::init()
// Built-in palette commands (Save/Load, Preferences, Mode switch, Slice/Preview, Go to layer).
// Register after plugins so the plugin ids win on any (unlikely) id collision - ids are distinct
// by prefix, so this is order-independent. The catalog lives in NativeCommands - the registry
// only materialises thin CommandAction values from it.
// by prefix, so this is order-independent. The catalog (and its thin AppAction adapter) lives in
// NativeCommands; the registry only stores and dispatches the result.
for (const NativeCommand& c : NativeCommands::catalog())
upsert(CommandAction::make(c));
upsert(NativeCommands::make_action(c));
}
void ActionRegistry::refresh_source(const std::string& plugin_key, ActionChange change)
@@ -429,6 +425,14 @@ void ActionRegistry::seed_state(AppAction& a) const
}
}
void ActionRegistry::load_persisted(nlohmann::json& stats, std::vector<std::string>& favs) const
{
stats = read_section("stats", nlohmann::json::object());
if (!stats.is_object())
stats = nlohmann::json::object();
favs = favourite_ids();
}
// ---- read surface -----------------------------------------------------------
const AppAction* ActionRegistry::by_id(const std::string& id) const
@@ -529,10 +533,9 @@ void ActionRegistry::materialize_setting_actions()
// Load the persisted per-action state ONCE (not per-option) so a re-materialised setting keeps
// its recency/favourite; mirroring seed_state but amortised over the whole option set.
nlohmann::json stats = read_section("stats", nlohmann::json::object());
if (!stats.is_object())
stats = nlohmann::json::object();
const std::vector<std::string> favs = favourite_ids();
nlohmann::json stats;
std::vector<std::string> favs;
load_persisted(stats, favs);
std::unordered_set<std::string> seen;
for (const Search::Option& opt : options) {
@@ -568,11 +571,7 @@ void ActionRegistry::materialize_setting_actions()
}
}
action->favourite = std::find(favs.begin(), favs.end(), id) != favs.end();
if (auto it = stats.find(id); it != stats.end() && it->is_object()) {
action->count = it->value("count", 0);
action->last = it->value("last", 0LL);
}
seed_from(stats, favs, id, *action);
auto const action_id = action->id();
auto const app_action = std::shared_ptr<AppAction>(std::move(action));
m_actions.insert_or_assign(action_id, app_action);
@@ -580,12 +579,7 @@ void ActionRegistry::materialize_setting_actions()
// Drop SettingActions whose option no longer exists in the current configs (e.g. the printer
// technology / UI mode changed). Non-setting actions are untouched.
for (auto it = m_actions.begin(); it != m_actions.end();) {
if (it->first.rfind(kSettingPrefix, 0) == 0 && !seen.count(it->first))
it = m_actions.erase(it);
else
++it;
}
drop_stale(m_actions, kSettingPrefix, seen);
}
void ActionRegistry::materialize_plate_actions()
@@ -597,21 +591,15 @@ void ActionRegistry::materialize_plate_actions()
Plater* plater = wxTheApp ? wxGetApp().plater() : nullptr;
if (!plater || plater->printer_technology() != ptFFF || plater->only_gcode_mode()) {
// Drop any stale plate actions (e.g. the printer technology switched to SLA).
for (auto it = m_actions.begin(); it != m_actions.end();) {
if (it->first.rfind(kPlateGotoPrefix, 0) == 0)
it = m_actions.erase(it);
else
++it;
}
drop_stale(m_actions, kPlateGotoPrefix, {});
return;
}
// Persisted per-action state, read ONCE (mirrors materialize_setting_actions) so a relisted
// "Go to Plate N" keeps its recency/favourite when the plate is renamed - the id is index-keyed.
nlohmann::json stats = read_section("stats", nlohmann::json::object());
if (!stats.is_object())
stats = nlohmann::json::object();
const std::vector<std::string> favs = favourite_ids();
nlohmann::json stats;
std::vector<std::string> favs;
load_persisted(stats, favs);
const std::vector<PartPlate*>& list = plater->get_partplate_list().get_plate_list();
std::unordered_set<std::string> seen;
@@ -629,24 +617,15 @@ void ActionRegistry::materialize_plate_actions()
if (!name.empty())
title += " (" + name + ")";
auto action = std::make_unique<PlateAction>(int(i), title, kOrcaSourceName);
action->favourite = std::find(favs.begin(), favs.end(), id) != favs.end();
if (auto it = stats.find(id); it != stats.end() && it->is_object()) {
action->count = it->value("count", 0);
action->last = it->value("last", 0LL);
}
auto action = std::make_unique<PlateAction>(int(i), title, kOrcaSourceName);
seed_from(stats, favs, id, *action);
auto const action_id = action->id();
auto const app_action = std::shared_ptr<AppAction>(std::move(action));
m_actions.insert_or_assign(action_id, app_action);
}
// Drop plate actions whose index no longer exists (a plate was deleted / moved to the front).
for (auto it = m_actions.begin(); it != m_actions.end();) {
if (it->first.rfind(kPlateGotoPrefix, 0) == 0 && !seen.count(it->first))
it = m_actions.erase(it);
else
++it;
}
drop_stale(m_actions, kPlateGotoPrefix, seen);
}
void ActionRegistry::materialize_recent_project_actions()
@@ -655,10 +634,9 @@ void ActionRegistry::materialize_recent_project_actions()
// Persisted per-action state, read ONCE (mirrors materialize_plate_actions) so a relisted recent
// project keeps its recency/favourite when the recents list reorders - the id is path-keyed.
nlohmann::json stats = read_section("stats", nlohmann::json::object());
if (!stats.is_object())
stats = nlohmann::json::object();
const std::vector<std::string> favs = favourite_ids();
nlohmann::json stats;
std::vector<std::string> favs;
load_persisted(stats, favs);
// app_config stores recents oldest-first; the palette shows newest-first.
std::vector<std::string> recents = wxGetApp().app_config->get_recent_projects();
@@ -680,24 +658,15 @@ void ActionRegistry::materialize_recent_project_actions()
if (title.empty())
title = path;
auto action = std::make_unique<RecentProjectAction>(path, std::move(title), path);
action->favourite = std::find(favs.begin(), favs.end(), id) != favs.end();
if (auto it = stats.find(id); it != stats.end() && it->is_object()) {
action->count = it->value("count", 0);
action->last = it->value("last", 0LL);
}
auto action = std::make_unique<RecentProjectAction>(path, std::move(title), path);
seed_from(stats, favs, id, *action);
auto const action_id = action->id();
auto const app_action = std::shared_ptr<AppAction>(std::move(action));
m_actions.insert_or_assign(action_id, app_action);
}
// Drop recent-project actions whose file no longer exists / was removed from the recents list.
for (auto it = m_actions.begin(); it != m_actions.end();) {
if (it->first.rfind(kRecentProjectPrefix, 0) == 0 && !seen.count(it->first))
it = m_actions.erase(it);
else
++it;
}
drop_stale(m_actions, kRecentProjectPrefix, seen);
}
bool ActionRegistry::should_ask(const std::string& id) const
@@ -753,7 +722,6 @@ nlohmann::json ActionRegistry::snapshot()
{"group", a->group},
{"input", a->input},
{"icon", a->icon},
{"shortcut", ""},
{"mode", mode_key(a->required_mode)}});
};
@@ -816,7 +784,7 @@ nlohmann::json ActionRegistry::tab_options() const
if (id.empty())
continue;
out.push_back({{"id", id.ToStdString()},
{"title", notebook->GetPageText(i).ToStdString()},
{"title", notebook->GetPageLabel(i).ToStdString()},
{"icon", notebook->GetPageIcon(i)}});
}
return out;
+21 -9
View File
@@ -114,6 +114,12 @@ private:
std::string m_source_name; // display name of the action's source
};
// Stable identity/display name of the built-in ("OrcaSlicer") action source. Shared by the native
// command catalog and the dynamically materialised setting/plate/recent actions so every built-in
// action re-keys together.
inline constexpr const char* kOrcaSourceKey = "orca";
inline constexpr const char* kOrcaSourceName = "OrcaSlicer";
// True when a setting at `setting_mode` cannot be edited in `current_mode` and the UI must switch
// first. Developer settings are handled as a separate prompt by the Speed Dial.
inline bool requires_mode_switch(ConfigOptionMode setting_mode, ConfigOptionMode current_mode)
@@ -128,15 +134,17 @@ std::vector<std::string> cap_favourites(const std::vector<std::string>& ids, siz
// Self-contained sink and single owner of runnable actions for the app session.
//
// Workflow:
// 1. init() (once, UI thread) subscribes to the plugin loader and enumerates the
// current script capabilities into actions.
// 1. init() (once, UI thread) subscribes to the plugin loader and enumerates the current script
// capabilities into actions, then materialises the static built-ins from the NativeCommands catalog.
// 2. Loader load/unload callbacks route through refresh_source()/refresh_capability(),
// which upsert()/remove() actions. The registry keeps the only action list and
// restores persisted user state as actions arrive.
// 3. Consumers use by_id(), snapshot(), and run() without knowing the source.
// 3. Dynamic built-in families (settings, plates, recent projects) are re-materialised at the top of
// snapshot(), because their membership follows live state (the current configs, plate list, recents).
// 4. Consumers use by_id(), snapshot(), and run() without knowing the source.
//
// note: there is exactly one source (script plugins), so it lives inline here rather
// than behind a polymorphic source interface.
// note: the static catalog lives in NativeCommands; the registry owns the pool, persistence and
// dispatch, and materialises the dynamic families inline rather than behind a source interface.
class ActionRegistry
{
public:
@@ -180,16 +188,20 @@ public:
nlohmann::json snapshot();
// "Go to tab..." Speed Dial helper: enumerate the MainFrame notebook's current pages
// as [{id,title},...]. Live by construction - built-in tabs (Home/Prepare/Preview/Device/
// Project/Calibration) and plugin tabs (plugin.<key>.<name>) are all Notebook pages, so a
// page appears/disappears with the notebook. Plugin tabs hidden in the overflow menu (many
// plugins) aren't separate pages and are not listed. Call on the UI thread; null-safe.
// as [{id,title,icon},...], using the page's real label (not the compact-blanked button text).
// Live by construction - built-in tabs (Home/Prepare/Preview/Device/Project/Calibration) and
// plugin tabs (plugin.<key>.<name>) are all Notebook pages, so a page appears/disappears with
// the notebook. Plugin tabs hidden in the overflow menu (many plugins) aren't separate pages and
// are not listed. Call on the UI thread; null-safe.
nlohmann::json tab_options() const;
private:
void seed_state(AppAction& a) const; // favourite/stats from config
AppAction* find(const std::string& id);
// Read the persisted stats blob + capped favourite list once for a materialisation pass.
void load_persisted(nlohmann::json& stats, std::vector<std::string>& favs) const;
// (Re)materialise the current visible config settings as SettingActions from the live
// searcher (respecting printer-tech + user-mode + visibility filtering), removing stale ones.
// Called at the top of snapshot() so the palette always reflects the current configs.
+41 -6
View File
@@ -2616,6 +2616,10 @@ void GUI_App::init_app_config()
}
#endif // _WIN32
}
// Speed Dial opens on a bare Space from any page by default. Seed the flag so Preferences and the
// MainFrame shortcut read the same value; an existing config (true or false) is left untouched.
if (app_config->get("enable_speed_dial").empty())
app_config->set_bool("enable_speed_dial", true);
set_logging_level(Slic3r::level_string_to_boost(app_config->get("log_severity_level")));
}
@@ -8163,6 +8167,22 @@ void GUI_App::save_mode(const /*ConfigOptionMode*/int mode)
update_mode();
}
void GUI_App::set_mode(ConfigOptionMode mode)
{
const bool was_developer = app_config->get_bool("developer_mode");
if (was_developer)
app_config->set_bool("developer_mode", false);
save_mode(mode);
if (was_developer)
app_config->save();
}
void GUI_App::enable_developer_mode()
{
app_config->set_bool("developer_mode", true);
update_mode();
}
// Update view mode according to selected menu
void GUI_App::update_mode()
{
@@ -8316,17 +8336,32 @@ void GUI_App::refresh_plugins()
// The metadata refresh blocks on disc discovery and a cloud round-trip, so run it on a worker
// and report completion through the notification manager -- the speed dial needs no dialog.
std::thread([]() {
refresh_plugin_metadata_blocking(/*fetch_cloud=*/true);
wxTheApp->CallAfter([]() {
wxString error;
try {
refresh_plugin_metadata_blocking(/*fetch_cloud=*/true);
} catch (const std::exception& ex) {
error = from_u8(ex.what());
} catch (...) {
error = "Unknown error"; // plain literal: wx translation isn't safe off the UI thread
}
if (!wxTheApp)
return;
wxTheApp->CallAfter([error]() {
if (wxGetApp().is_closing())
return;
Plater* plater = wxGetApp().plater();
if (plater == nullptr)
return;
plater->get_notification_manager()->push_notification(
NotificationType::CustomNotification,
NotificationManager::NotificationLevel::RegularNotificationLevel,
into_u8(_L("Plugins refreshed.")));
if (error.IsEmpty())
plater->get_notification_manager()->push_notification(
NotificationType::CustomNotification,
NotificationManager::NotificationLevel::RegularNotificationLevel,
into_u8(_L("Plugins refreshed.")));
else
plater->get_notification_manager()->push_notification(
NotificationType::CustomNotification,
NotificationManager::NotificationLevel::ErrorNotificationLevel,
into_u8(wxString::Format(_L("Failed to refresh plugins: %s"), error)));
});
}).detach();
}
+5
View File
@@ -593,6 +593,11 @@ public:
std::string get_saved_mode_str();
std::string get_mode_str();
void save_mode(const /*ConfigOptionMode*/int mode) ;
// Switch to `mode` from the Speed Dial: a developer-mode override hides the saved mode
// (get_mode returns comDevelop), so clear it first and persist the choice.
void set_mode(ConfigOptionMode mode);
// Turn the developer-mode override on and refresh the UI (used before jumping to a Developer setting).
void enable_developer_mode();
void update_mode();
void update_internal_development();
void show_ip_address_enter_dialog(wxString title = wxEmptyString);
+2 -2
View File
@@ -199,8 +199,8 @@ void KBShortcutsDialog::fill_shortcuts()
// Switch table page
{ ctrl + L("Tab"), L("Switch table page")},
// Open speed dial
{ "Space", L("Open speed dial") },
{ alt + "1..9,0", L("Run a Speed Dial favourite") },
{ L_CONTEXT("Space", "Keyboard Shortcut"), L("Open speed dial") },
{ alt + "1..9,0", L("Run a Speed Dial favourite (while the Speed Dial is open)") },
//DEL
#ifdef __APPLE__
{"fn+⌫", L("Delete Selected")},
+13 -6
View File
@@ -45,6 +45,7 @@
#include "Preferences.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/ProgressDialog.hpp"
#include "Widgets/StaticBox.hpp"
#include "BindDialog.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "../Utils/NetworkAgentFactory.hpp"
@@ -109,17 +110,21 @@ namespace {
// Space opens the speed dial, but it is the activation key for buttons, checkboxes and other
// controls. CHAR_HOOK runs before the focused child, so only take Space when the focused window has
// no keyboard-activation meaning of its own. Canvases (GLCanvas3D) and panels are not wxControls and
// fall through to "open"; Notebook and wxWebView are wxControls that don't use Space, so allow them.
// no keyboard-activation meaning of its own. Canvases (GLCanvas3D) and panels are not controls and
// fall through to "open"; the Notebook itself does too, so Space still opens the dial on any page.
bool focus_keeps_space(wxWindow* focus)
{
if (!focus)
return false;
if (dynamic_cast<wxTextEntryBase*>(focus))
return true; // typing a space into a text field
if (dynamic_cast<wxWebView*>(focus))
return true; // web content scrolls and hosts its own text fields
if (dynamic_cast<::Button*>(focus))
return true; // custom button: Space clicks it (it is a wxWindow, not a wxControl)
if (dynamic_cast<wxControl*>(focus) && !dynamic_cast<Notebook*>(focus) && !dynamic_cast<wxWebView*>(focus))
if (dynamic_cast<StaticBox*>(focus))
return true; // custom composites (ComboBox, SpinInput, ...) activate with Space and are wxWindow
if (dynamic_cast<wxControl*>(focus) && !dynamic_cast<Notebook*>(focus))
return true; // stock button/checkbox/choice/list/etc. keep Space
return false;
}
@@ -724,8 +729,10 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
// Orca: open the speed dial from any page with a bare Space. Only when no modifier is held (so
// editing shortcuts like Ctrl+Shift+Space in the canvas still reach it) and the focused window
// doesn't use Space to activate itself (buttons, checkboxes, list/choice controls, text fields),
// so a bare Space there still clicks/toggles instead of being hijacked.
if (!evt.CmdDown() && !evt.ShiftDown() && !evt.AltDown() && evt.GetKeyCode() == WXK_SPACE) {
// so a bare Space there still clicks/toggles instead of being hijacked. Gated by a preference
// (default on) so users can hand Space back to the focused control entirely.
if (wxGetApp().app_config->get_bool("enable_speed_dial") && !evt.CmdDown() && !evt.ShiftDown() &&
!evt.AltDown() && evt.GetKeyCode() == WXK_SPACE) {
if (focus_keeps_space(wxWindow::FindFocus())) {
evt.Skip(); // let the focused control keep Space
return;
@@ -4325,7 +4332,7 @@ void MainFrame::technology_changed()
// update menu titles
PrinterTechnology pt = plater()->printer_technology();
if (int id = m_menubar->FindMenu(pt == ptFFF ? _omitL("Material Settings") : _L("Filament settings")); id != wxNOT_FOUND)
m_menubar->SetMenuLabel(id, pt == ptFFF ? _omitL("Material Settings") : _L("Filament settings"));
m_menubar->SetMenuLabel(id, pt == ptSLA ? _omitL("Material Settings") : _L("Filament settings"));
}
// Opens the calibration wizard for `calib_kind`. Single source of truth for the wizard lifecycle:
+112 -179
View File
@@ -25,7 +25,7 @@
#include <cmath>
#include <cstdlib>
#include <exception>
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
@@ -120,120 +120,43 @@ AppActionRunResult calib_command(CalibKind kind)
return {AppActionRunResult::Level::Success};
}
// Palette-only: developer mode overrides the saved mode (get_mode returns comDevelop), so choosing
// Simple/Advanced/Expert must clear it first. Mirrors Preferences: persist the flag, then update.
void select_mode(ConfigOptionMode mode)
{
GUI_App& app = wxGetApp();
const bool was_developer = app.app_config->get_bool("developer_mode");
if (was_developer)
app.app_config->set_bool("developer_mode", false);
app.save_mode(mode);
if (was_developer)
app.app_config->save();
}
constexpr const char* kCommandPrefix = "orca_command";
// Tile pictogram per command: the SVG base name of the icon the matching GUI control already uses
// (menu/toolbar/sidebar). Absent key => blank tile. Keeping this as one table makes the curation
// reviewable and lets a test check every value resolves to a real file.
const std::map<std::string, std::string>& command_icons()
// Thin AppAction wrapper for one catalog entry: identity and presentation come from the catalog,
// run() routes back to it. The id is keyed by the stable catalog key (not the display title), so a
// rename or a UI-language switch never re-keys the action.
struct CommandAction : AppAction
{
static const std::map<std::string, std::string> icons = {
// Slice & Export
{"slice_and_preview", "media_play"},
{"export_gcode", "menu_export_gcode"},
{"export_stl", "menu_export_stl"},
{"export_stl_multi", "menu_export_stl"},
{"export_sliced_file", "menu_export_sliced_file"},
{"export_all_sliced_file", "menu_export_sliced_file"},
{"export_toolpaths_obj", "menu_export_toolpaths"},
{"export_config", "menu_export_config"},
{"export_3mf", "menu_save"},
{"export_drc_single", "menu_export_stl"},
{"export_drc_multi", "menu_export_stl"},
// Commands
{"load_project", "menu_open"},
{"save_project", "menu_save"},
{"save_project_as", "menu_save"},
{"open_preferences", "cog"},
{"go_to_layer", "height_range_layer"},
// Mode: the sidebar mode toggle's own icon (ParamsPanel).
{"mode_simple", "advanced"},
{"mode_advanced", "advanced"},
{"mode_expert", "advanced"},
{"toggle_developer_mode", "advanced"},
// Calibration
{"calib_temperature", "calib_sf"},
{"calib_max_volumetric", "calib_sf"},
{"calib_pressure_advance", "calib_sf"},
{"calib_flow_ratio", "calib_sf"},
{"calib_retraction", "calib_sf"},
{"calib_cornering", "calib_sf"},
{"calib_input_shaping_freq", "calib_sf"},
{"calib_input_shaping_damp", "calib_sf"},
{"calib_vfa", "calib_sf"},
// View
{"reset_window_layout", "toolbar_reset"},
// Object
{"obj_delete", "menu_delete"},
{"obj_delete_all", "menu_remove"},
{"obj_mirror_x", "menu_mirror_x"},
{"obj_mirror_y", "menu_mirror_y"},
{"obj_mirror_z", "menu_mirror_z"},
{"obj_split_objects", "menu_split_objects"},
{"obj_split_parts", "menu_split_parts"},
{"obj_drop", "toolbar_flatten"},
{"obj_instances_up", "instance_add"},
{"obj_instances_down", "instance_remove"},
{"obj_arrange", "toolbar_arrange"},
{"obj_orient", "toolbar_orient"},
// Add Primitive
{"add_primitive_cube", "menu_obj_cube"},
{"add_primitive_cylinder", "menu_obj_cylinder"},
{"add_primitive_sphere", "menu_obj_sphere"},
{"add_primitive_cone", "menu_obj_cone"},
{"add_primitive_disc", "menu_obj_disc"},
{"add_primitive_torus", "menu_obj_torus"},
{"add_primitive_text", "menu_obj_text"},
{"add_primitive_svg", "menu_obj_svg"},
// Plate
{"plate_add", "toolbar_add_plate"},
{"plate_duplicate", "menu_copy"},
{"plate_delete", "menu_delete"},
{"plate_rename", "plate_name_edit"},
{"plate_toggle_lock", "lock_normal"},
{"plate_goto", "go_next_plate"},
// Printer / Presets
{"sync_ams", "ams_fila_sync"},
{"sync_presets", "printer_sync_ok"},
{"preset_bundle", "menu_edit_preset"},
// Import
{"import_file", "menu_import"},
{"import_zip_archive", "menu_import"},
{"import_configs", "menu_import"},
// Help
{"help_open_config_folder", "folder-closed"},
{"help_tip_of_the_day", "info"},
{"help_check_updates", "ams_refresh_normal"},
{"help_about", "OrcaSlicer_about"},
{"open_wiki", "link_wiki_img"},
};
return icons;
}
std::string command_key;
AppActionRunResult run(const std::string& param) const override { return NativeCommands::run(command_key, param); }
explicit CommandAction(const NativeCommand& c)
: AppAction(AppActionId{AppAction::compose_id(kCommandPrefix, c.key, kOrcaSourceKey)}, c.title, kOrcaSourceKey, kOrcaSourceName)
, command_key(c.key)
{
this->kind = AppActionKind::Command;
this->group = c.group;
this->input = c.input;
this->icon = c.icon;
}
};
std::vector<NativeCommand> build_command_catalog()
{
std::vector<NativeCommand> out;
auto add = [&](std::string key, std::string title, std::string group, std::function<AppActionRunResult(const std::string&)> runner,
std::string input = {}) {
std::string icon;
if (auto it = command_icons().find(key); it != command_icons().end())
icon = it->second;
std::string input = {}, std::string icon = {}) {
out.push_back({std::move(key), std::move(title), std::move(group), std::move(input), std::move(icon), std::move(runner)});
};
// Presentation-first overload: keeps the tile icon next to the title/group it belongs to.
auto add_with_icon = [&](std::string key, std::string title, std::string group, std::string icon,
std::function<AppActionRunResult(const std::string&)> runner, std::string input = {}) {
add(std::move(key), std::move(title), std::move(group), std::move(runner), std::move(input), std::move(icon));
};
// ---- Slice & Export ----
add("slice_and_preview", _u8L("Slice and Preview"), _u8L("Slice & Export"), [](const std::string&) {
add_with_icon("slice_and_preview", _u8L("Slice and Preview"), _u8L("Slice & Export"), "media_play", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (plater) {
plater->reslice();
@@ -244,8 +167,8 @@ std::vector<NativeCommand> build_command_catalog()
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add(
"go_to_layer", _u8L("Go to layer (percent)"), _u8L("Commands"),
add_with_icon(
"go_to_layer", _u8L("Go to layer (percent)"), _u8L("Commands"), "height_range_layer",
[](const std::string& param) {
Plater* plater = wxGetApp().plater();
if (plater) {
@@ -258,47 +181,52 @@ std::vector<NativeCommand> build_command_catalog()
},
"percent");
// "go_to_tab" is two-phase: the palette collects the tab after activating it, so dispatch here
// is a no-op (the jump goes through the go_to_tab web command).
// "go_to_tab" is two-phase: the palette collects the tab after activating it, then hands the tab
// id back as `param` (same contract as go_to_layer's percent).
add(
"go_to_tab", _u8L("Go to tab..."), _u8L("Commands"),
[](const std::string&) { return AppActionRunResult{AppActionRunResult::Level::Success}; }, "tab");
[](const std::string& param) {
if (MainFrame* mf = wxGetApp().mainframe; mf && !param.empty())
mf->select_tab(from_u8(param));
return AppActionRunResult{AppActionRunResult::Level::Success};
},
"tab");
add("load_project", _u8L("Load Project"), _u8L("Commands"), [](const std::string&) {
add_with_icon("load_project", _u8L("Load Project"), _u8L("Commands"), "menu_open", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->load_project();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("save_project", _u8L("Save Project"), _u8L("Commands"), [](const std::string&) {
add_with_icon("save_project", _u8L("Save Project"), _u8L("Commands"), "menu_save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->save_project(false);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("save_project_as", _u8L("Save Project As"), _u8L("Commands"), [](const std::string&) {
add_with_icon("save_project_as", _u8L("Save Project As"), _u8L("Commands"), "menu_save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->save_project(true);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("open_preferences", _u8L("Preferences"), _u8L("Commands"), [](const std::string&) {
add_with_icon("open_preferences", _u8L("Preferences"), _u8L("Commands"), "cog", [](const std::string&) {
wxGetApp().open_preferences();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
// ---- Mode ----
add("mode_simple", _u8L("Mode: Simple"), _u8L("Mode"), [](const std::string&) {
select_mode(comSimple);
add_with_icon("mode_simple", _u8L("Mode: Simple"), _u8L("Mode"), "advanced", [](const std::string&) {
wxGetApp().set_mode(comSimple);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("mode_advanced", _u8L("Mode: Advanced"), _u8L("Mode"), [](const std::string&) {
select_mode(comAdvanced);
add_with_icon("mode_advanced", _u8L("Mode: Advanced"), _u8L("Mode"), "advanced", [](const std::string&) {
wxGetApp().set_mode(comAdvanced);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("mode_expert", _u8L("Mode: Expert"), _u8L("Mode"), [](const std::string&) {
select_mode(comExpert);
add_with_icon("mode_expert", _u8L("Mode: Expert"), _u8L("Mode"), "advanced", [](const std::string&) {
wxGetApp().set_mode(comExpert);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
// Mirrors Preferences > Developer > Developer mode: flip the flag, persist, refresh the UI.
add("toggle_developer_mode", _u8L("Toggle Developer Mode"), _u8L("Mode"), [](const std::string&) {
add_with_icon("toggle_developer_mode", _u8L("Toggle Developer Mode"), _u8L("Mode"), "advanced", [](const std::string&) {
GUI_App& app = wxGetApp();
const bool on = !app.app_config->get_bool("developer_mode");
app.app_config->set_bool("developer_mode", on);
@@ -308,50 +236,50 @@ std::vector<NativeCommand> build_command_catalog()
});
// ---- Export pipeline ----
add("export_gcode", _u8L("Export G-code"), _u8L("Slice & Export"), [](const std::string&) {
add_with_icon("export_gcode", _u8L("Export G-code"), _u8L("Slice & Export"), "menu_export_gcode", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_gcode(false);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("export_stl", _u8L("Export STL"), _u8L("Slice & Export"), [](const std::string&) {
add_with_icon("export_stl", _u8L("Export STL"), _u8L("Slice & Export"), "menu_export_stl", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_stl();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("export_3mf", _u8L("Export 3MF"), _u8L("Slice & Export"), [](const std::string&) {
add_with_icon("export_3mf", _u8L("Export 3MF"), _u8L("Slice & Export"), "menu_save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_core_3mf();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("export_sliced_file", _u8L("Export Sliced File"), _u8L("Slice & Export"), [](const std::string&) {
add_with_icon("export_sliced_file", _u8L("Export Sliced File"), _u8L("Slice & Export"), "menu_export_sliced_file", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_gcode_3mf(false);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("export_all_sliced_file", _u8L("Export All Sliced Files"), _u8L("Slice & Export"), [](const std::string&) {
add_with_icon("export_all_sliced_file", _u8L("Export All Sliced Files"), _u8L("Slice & Export"), "menu_export_sliced_file", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_gcode_3mf(true);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
// ---- Calibration ----
add("calib_temperature", _u8L("Temperature Calibration"), _u8L("Calibration"),
add_with_icon("calib_temperature", _u8L("Temperature Calibration"), _u8L("Calibration"), "calib_sf",
[](const std::string&) { return calib_command(CalibKind::Temperature); });
add("calib_max_volumetric", _u8L("Max Volumetric Speed Calibration"), _u8L("Calibration"),
add_with_icon("calib_max_volumetric", _u8L("Max Volumetric Speed Calibration"), _u8L("Calibration"), "calib_sf",
[](const std::string&) { return calib_command(CalibKind::MaxVolumetric); });
add("calib_pressure_advance", _u8L("Pressure Advance Calibration"), _u8L("Calibration"),
add_with_icon("calib_pressure_advance", _u8L("Pressure Advance Calibration"), _u8L("Calibration"), "calib_sf",
[](const std::string&) { return calib_command(CalibKind::PressureAdvance); });
add("calib_flow_ratio", _u8L("Flow Ratio Calibration"), _u8L("Calibration"),
add_with_icon("calib_flow_ratio", _u8L("Flow Ratio Calibration"), _u8L("Calibration"), "calib_sf",
[](const std::string&) { return calib_command(CalibKind::FlowRatio); });
add("calib_retraction", _u8L("Retraction Calibration"), _u8L("Calibration"),
add_with_icon("calib_retraction", _u8L("Retraction Calibration"), _u8L("Calibration"), "calib_sf",
[](const std::string&) { return calib_command(CalibKind::Retraction); });
add("calib_cornering", _u8L("Cornering Calibration"), _u8L("Calibration"),
add_with_icon("calib_cornering", _u8L("Cornering Calibration"), _u8L("Calibration"), "calib_sf",
[](const std::string&) { return calib_command(CalibKind::Cornering); });
add("calib_input_shaping_freq", _u8L("Input Shaping Frequency Calibration"), _u8L("Calibration"),
add_with_icon("calib_input_shaping_freq", _u8L("Input Shaping Frequency Calibration"), _u8L("Calibration"), "calib_sf",
[](const std::string&) { return calib_command(CalibKind::InputShapingFreq); });
add("calib_input_shaping_damp", _u8L("Input Shaping Damping Calibration"), _u8L("Calibration"),
add_with_icon("calib_input_shaping_damp", _u8L("Input Shaping Damping Calibration"), _u8L("Calibration"), "calib_sf",
[](const std::string&) { return calib_command(CalibKind::InputShapingDamp); });
add("calib_vfa", _u8L("VFA Calibration"), _u8L("Calibration"), [](const std::string&) { return calib_command(CalibKind::VFA); });
add_with_icon("calib_vfa", _u8L("VFA Calibration"), _u8L("Calibration"), "calib_sf", [](const std::string&) { return calib_command(CalibKind::VFA); });
// ---- View ----
for (auto [key, dir, title] :
@@ -386,39 +314,39 @@ std::vector<NativeCommand> build_command_catalog()
plater->get_camera().select_next_type();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("reset_window_layout", _u8L("Reset Window Layout"), _u8L("View"), [](const std::string&) {
add_with_icon("reset_window_layout", _u8L("Reset Window Layout"), _u8L("View"), "toolbar_reset", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->reset_window_layout();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
// ---- Object ----
add("obj_delete", _u8L("Delete Selected"), _u8L("Object"), [](const std::string&) {
add_with_icon("obj_delete", _u8L("Delete Selected"), _u8L("Object"), "menu_delete", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return !p->is_selection_empty(); }, [](Plater* p) { p->remove_selected(); });
});
add("obj_delete_all", _u8L("Delete All Objects"), _u8L("Object"), [](const std::string&) {
add_with_icon("obj_delete_all", _u8L("Delete All Objects"), _u8L("Object"), "menu_remove", [](const std::string&) {
return object_op(
wxGetApp().plater(), [](Plater* p) { return p->can_delete_all(); }, [](Plater* p) { p->delete_all_objects_from_model(); });
});
add("obj_mirror_x", _u8L("Mirror X"), _u8L("Object"), [](const std::string&) {
add_with_icon("obj_mirror_x", _u8L("Mirror X"), _u8L("Object"), "menu_mirror_x", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return p->can_mirror(); }, [](Plater* p) { p->mirror(Axis::X); });
});
add("obj_mirror_y", _u8L("Mirror Y"), _u8L("Object"), [](const std::string&) {
add_with_icon("obj_mirror_y", _u8L("Mirror Y"), _u8L("Object"), "menu_mirror_y", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return p->can_mirror(); }, [](Plater* p) { p->mirror(Axis::Y); });
});
add("obj_mirror_z", _u8L("Mirror Z"), _u8L("Object"), [](const std::string&) {
add_with_icon("obj_mirror_z", _u8L("Mirror Z"), _u8L("Object"), "menu_mirror_z", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return p->can_mirror(); }, [](Plater* p) { p->mirror(Axis::Z); });
});
add("obj_split_objects", _u8L("Split to Objects"), _u8L("Object"), [](const std::string&) {
add_with_icon("obj_split_objects", _u8L("Split to Objects"), _u8L("Object"), "menu_split_objects", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return p->can_split_to_objects(); }, [](Plater* p) { p->split_object(true); });
});
add("obj_split_parts", _u8L("Split to Parts"), _u8L("Object"), [](const std::string&) {
add_with_icon("obj_split_parts", _u8L("Split to Parts"), _u8L("Object"), "menu_split_parts", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return p->can_split_to_volumes(); }, [](Plater* p) { p->split_volume(); });
});
add("obj_center", _u8L("Center Selected on Plate"), _u8L("Object"), [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return !p->is_selection_empty(); }, [](Plater* p) { p->center_selection(); });
});
add("obj_drop", _u8L("Drop to Bed"), _u8L("Object"), [](const std::string&) {
add_with_icon("obj_drop", _u8L("Drop to Bed"), _u8L("Object"), "toolbar_flatten", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return !p->is_selection_empty(); }, [](Plater* p) { p->drop_selection(); });
});
add("obj_fit_volume", _u8L("Scale to Fit Print Volume"), _u8L("Object"), [](const std::string&) {
@@ -426,24 +354,24 @@ std::vector<NativeCommand> build_command_catalog()
wxGetApp().plater(), [](Plater* p) { return p->can_scale_to_print_volume(); },
[](Plater* p) { p->scale_selection_to_fit_print_volume(); });
});
add("obj_instances_up", _u8L("Increase Instances"), _u8L("Object"), [](const std::string&) {
add_with_icon("obj_instances_up", _u8L("Increase Instances"), _u8L("Object"), "instance_add", [](const std::string&) {
return object_op(
wxGetApp().plater(), [](Plater* p) { return p->can_increase_instances(); }, [](Plater* p) { p->increase_instances(); });
});
add("obj_instances_down", _u8L("Decrease Instances"), _u8L("Object"), [](const std::string&) {
add_with_icon("obj_instances_down", _u8L("Decrease Instances"), _u8L("Object"), "instance_remove", [](const std::string&) {
return object_op(
wxGetApp().plater(), [](Plater* p) { return p->can_decrease_instances(); }, [](Plater* p) { p->decrease_instances(); });
});
add("obj_arrange", _u8L("Auto-Arrange"), _u8L("Object"), [](const std::string&) {
add_with_icon("obj_arrange", _u8L("Auto-Arrange"), _u8L("Object"), "toolbar_arrange", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return p->can_arrange(); }, [](Plater* p) { p->arrange(); });
});
add("obj_orient", _u8L("Auto-Orient"), _u8L("Object"), [](const std::string&) {
add_with_icon("obj_orient", _u8L("Auto-Orient"), _u8L("Object"), "toolbar_orient", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return p->can_arrange(); }, [](Plater* p) { p->orient(); });
});
// ---- Add Primitive ---- (the Add > Add Primitive submenu; creates a new object)
auto add_primitive = [&](std::string key, std::string title, const char* type_name) {
add(std::move(key), std::move(title), _u8L("Add Primitive"), [type_name](const std::string&) {
auto add_primitive = [&](std::string key, std::string title, std::string icon, const char* type_name) {
add_with_icon(std::move(key), std::move(title), _u8L("Add Primitive"), std::move(icon), [type_name](const std::string&) {
Plater* plater = wxGetApp().plater();
if (plater) {
ensure_3d_view(plater);
@@ -453,13 +381,13 @@ std::vector<NativeCommand> build_command_catalog()
return AppActionRunResult{AppActionRunResult::Level::Success};
});
};
add_primitive("add_primitive_cube", _u8L("Cube"), "Cube");
add_primitive("add_primitive_cylinder", _u8L("Cylinder"), "Cylinder");
add_primitive("add_primitive_sphere", _u8L("Sphere"), "Sphere");
add_primitive("add_primitive_cone", _u8L("Cone"), "Cone");
add_primitive("add_primitive_disc", _u8L("Disc"), "Disc");
add_primitive("add_primitive_torus", _u8L("Torus"), "Torus");
add("add_primitive_text", _u8L("Text"), _u8L("Add Primitive"), [](const std::string&) {
add_primitive("add_primitive_cube", _u8L("Cube"), "menu_obj_cube", "Cube");
add_primitive("add_primitive_cylinder", _u8L("Cylinder"), "menu_obj_cylinder", "Cylinder");
add_primitive("add_primitive_sphere", _u8L("Sphere"), "menu_obj_sphere", "Sphere");
add_primitive("add_primitive_cone", _u8L("Cone"), "menu_obj_cone", "Cone");
add_primitive("add_primitive_disc", _u8L("Disc"), "menu_obj_disc", "Disc");
add_primitive("add_primitive_torus", _u8L("Torus"), "menu_obj_torus", "Torus");
add_with_icon("add_primitive_text", _u8L("Text"), _u8L("Add Primitive"), "menu_obj_text", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (plater) {
ensure_3d_view(plater);
@@ -469,7 +397,7 @@ std::vector<NativeCommand> build_command_catalog()
}
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("add_primitive_svg", _u8L("SVG"), _u8L("Add Primitive"), [](const std::string&) {
add_with_icon("add_primitive_svg", _u8L("SVG"), _u8L("Add Primitive"), "menu_obj_svg", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (plater) {
ensure_3d_view(plater);
@@ -493,7 +421,7 @@ std::vector<NativeCommand> build_command_catalog()
}
// ---- Plate ----
add("plate_add", _u8L("Add Plate"), _u8L("Plate"), [](const std::string&) {
add_with_icon("plate_add", _u8L("Add Plate"), _u8L("Plate"), "toolbar_add_plate", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (!is_fff_plater(plater))
return plate_unavailable();
@@ -502,7 +430,7 @@ std::vector<NativeCommand> build_command_catalog()
plater->add_plate();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("plate_duplicate", _u8L("Duplicate Plate"), _u8L("Plate"), [](const std::string&) {
add_with_icon("plate_duplicate", _u8L("Duplicate Plate"), _u8L("Plate"), "menu_copy", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (!is_fff_plater(plater))
return plate_unavailable();
@@ -511,7 +439,7 @@ std::vector<NativeCommand> build_command_catalog()
plater->duplicate_plate();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("plate_delete", _u8L("Delete Plate"), _u8L("Plate"), [](const std::string&) {
add_with_icon("plate_delete", _u8L("Delete Plate"), _u8L("Plate"), "menu_delete", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (!is_fff_plater(plater))
return plate_unavailable();
@@ -520,7 +448,7 @@ std::vector<NativeCommand> build_command_catalog()
plater->delete_plate();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("plate_rename", _u8L("Rename Plate"), _u8L("Plate"), [](const std::string&) {
add_with_icon("plate_rename", _u8L("Rename Plate"), _u8L("Plate"), "plate_name_edit", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (!is_fff_plater(plater))
return plate_unavailable();
@@ -531,7 +459,7 @@ std::vector<NativeCommand> build_command_catalog()
curr->set_plate_name(dlg.get_plate_name().ToUTF8().data());
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("plate_toggle_lock", _u8L("Toggle Plate Lock"), _u8L("Plate"), [](const std::string&) {
add_with_icon("plate_toggle_lock", _u8L("Toggle Plate Lock"), _u8L("Plate"), "lock_normal", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (!is_fff_plater(plater))
return plate_unavailable();
@@ -541,7 +469,7 @@ std::vector<NativeCommand> build_command_catalog()
plates.lock_plate(index, !plates.is_locked(index));
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("plate_goto", _u8L("Go to Plate"), _u8L("Plate"), [](const std::string& param) {
add_with_icon("plate_goto", _u8L("Go to Plate"), _u8L("Plate"), "go_next_plate", [](const std::string& param) {
Plater* plater = wxGetApp().plater();
if (!is_fff_plater(plater))
return plate_unavailable();
@@ -559,7 +487,7 @@ std::vector<NativeCommand> build_command_catalog()
});
// ---- Printer / device connection ----
add("sync_ams", _u8L("Synchronize Filament List from AMS"), _u8L("Printer"), [](const std::string&) {
add_with_icon("sync_ams", _u8L("Synchronize Filament List from AMS"), _u8L("Printer"), "ams_fila_sync", [](const std::string&) {
Plater* plater = wxGetApp().plater();
DeviceManager* dev = wxGetApp().getDeviceManager();
if (dev && dev->get_selected_machine() && plater) {
@@ -570,11 +498,11 @@ std::vector<NativeCommand> build_command_catalog()
});
// ---- Presets / cloud ----
add("preset_bundle", _u8L("Open Preset Bundle"), _u8L("Presets"), [](const std::string&) {
add_with_icon("preset_bundle", _u8L("Open Preset Bundle"), _u8L("Presets"), "menu_edit_preset", [](const std::string&) {
wxGetApp().open_presetbundledialog();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("sync_presets", _u8L("Sync Presets"), _u8L("Presets"), [](const std::string&) {
add_with_icon("sync_presets", _u8L("Sync Presets"), _u8L("Presets"), "printer_sync_ok", [](const std::string&) {
if (!wxGetApp().is_user_login())
return AppActionRunResult{AppActionRunResult::Level::Info, _L("Sign in to sync presets.")};
wxGetApp().restart_sync_user_preset();
@@ -582,7 +510,7 @@ std::vector<NativeCommand> build_command_catalog()
});
// ---- Import ----
add("import_file", _u8L("Import 3MF/STL/STEP/SVG/OBJ/AMF"), _u8L("Import"), [](const std::string&) {
add_with_icon("import_file", _u8L("Import 3MF/STL/STEP/SVG/OBJ/AMF"), _u8L("Import"), "menu_import", [](const std::string&) {
if (Plater* plater = wxGetApp().plater()) {
#ifdef __APPLE__
plater->add_model();
@@ -592,39 +520,39 @@ std::vector<NativeCommand> build_command_catalog()
}
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("import_zip_archive", _u8L("Import ZIP Archive"), _u8L("Import"), [](const std::string&) {
add_with_icon("import_zip_archive", _u8L("Import ZIP Archive"), _u8L("Import"), "menu_import", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->import_zip_archive();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("import_configs", _u8L("Import Configs"), _u8L("Import"), [](const std::string&) {
add_with_icon("import_configs", _u8L("Import Configs"), _u8L("Import"), "menu_import", [](const std::string&) {
if (MainFrame* mf = wxGetApp().mainframe)
mf->load_config_file();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
// ---- Export extras ----
add("export_stl_multi", _u8L("Export All Objects as STLs"), _u8L("Export"), [](const std::string&) {
add_with_icon("export_stl_multi", _u8L("Export All Objects as STLs"), _u8L("Export"), "menu_export_stl", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_stl(false, false, true);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("export_drc_single", _u8L("Export All Objects as DRC (one file)"), _u8L("Export"), [](const std::string&) {
add_with_icon("export_drc_single", _u8L("Export All Objects as DRC (one file)"), _u8L("Export"), "menu_export_stl", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_stl(false, false, false, FT_DRC);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("export_drc_multi", _u8L("Export All Objects as DRCs"), _u8L("Export"), [](const std::string&) {
add_with_icon("export_drc_multi", _u8L("Export All Objects as DRCs"), _u8L("Export"), "menu_export_stl", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_stl(false, false, true, FT_DRC);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("export_toolpaths_obj", _u8L("Export Toolpaths as OBJ"), _u8L("Export"), [](const std::string&) {
add_with_icon("export_toolpaths_obj", _u8L("Export Toolpaths as OBJ"), _u8L("Export"), "menu_export_toolpaths", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_toolpaths_to_obj();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("export_config", _u8L("Export Preset Bundle"), _u8L("Export"), [](const std::string&) {
add_with_icon("export_config", _u8L("Export Preset Bundle"), _u8L("Export"), "menu_export_config", [](const std::string&) {
if (MainFrame* mf = wxGetApp().mainframe)
mf->export_config();
return AppActionRunResult{AppActionRunResult::Level::Success};
@@ -639,7 +567,7 @@ std::vector<NativeCommand> build_command_catalog()
wxGetApp().ShowUserGuide();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("help_open_config_folder", _u8L("Show Configuration Folder"), _u8L("Help"), [](const std::string&) {
add_with_icon("help_open_config_folder", _u8L("Show Configuration Folder"), _u8L("Help"), "folder-closed", [](const std::string&) {
Slic3r::GUI::desktop_open_datadir_folder();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
@@ -652,7 +580,7 @@ std::vector<NativeCommand> build_command_catalog()
dlg.ShowModal();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("help_tip_of_the_day", _u8L("Show Tip of the Day"), _u8L("Help"), [](const std::string&) {
add_with_icon("help_tip_of_the_day", _u8L("Show Tip of the Day"), _u8L("Help"), "info", [](const std::string&) {
if (Plater* plater = wxGetApp().plater()) {
plater->get_dailytips()->open();
if (GLCanvas3D* canvas = plater->get_current_canvas3D())
@@ -660,15 +588,15 @@ std::vector<NativeCommand> build_command_catalog()
}
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("help_check_updates", _u8L("Check for Updates"), _u8L("Help"), [](const std::string&) {
add_with_icon("help_check_updates", _u8L("Check for Updates"), _u8L("Help"), "ams_refresh_normal", [](const std::string&) {
wxGetApp().check_new_version_sf(true, 1);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("help_about", _u8L("About OrcaSlicer"), _u8L("Help"), [](const std::string&) {
add_with_icon("help_about", _u8L("About OrcaSlicer"), _u8L("Help"), "OrcaSlicer_about", [](const std::string&) {
Slic3r::GUI::about();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("open_wiki", _u8L("Open Wiki"), _u8L("Help"), [](const std::string&) {
add_with_icon("open_wiki", _u8L("Open Wiki"), _u8L("Help"), "link_wiki_img", [](const std::string&) {
wxLaunchDefaultBrowser("https://www.orcaslicer.com/wiki/", wxBROWSER_NEW_WINDOW);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
@@ -706,6 +634,11 @@ const std::vector<NativeCommand>& NativeCommands::catalog()
return commands;
}
std::unique_ptr<AppAction> NativeCommands::make_action(const NativeCommand& command)
{
return std::make_unique<CommandAction>(command);
}
AppActionRunResult NativeCommands::run(const std::string& key, const std::string& param)
{
GUI_App& app = wxGetApp();
+11 -5
View File
@@ -1,17 +1,18 @@
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "ActionRegistry.hpp" // for AppActionRunResult
#include "ActionRegistry.hpp" // for AppAction / AppActionRunResult
namespace Slic3r { namespace GUI {
// A built-in speed-dial command: identity + how to run it. The registry keeps commands as thin
// values (CommandAction) and routes run() here, so this catalog is the single source of truth for
// the behaviour (runner => an owner method), the presentation (title/group/input), and the tile
// pictogram (icon = an SVG base name under resources/images, "" for no icon).
// A built-in speed-dial command: identity + how to run it. make_action() wraps a value as a thin
// AppAction for the registry, so this catalog is the single source of truth for the behaviour
// (runner => an owner method), the presentation (title/group/input), and the tile pictogram
// (icon = an SVG base name under resources/images, "" for no icon).
struct NativeCommand
{
std::string key;
@@ -28,6 +29,11 @@ const std::vector<NativeCommand>& catalog();
// Dispatches `key` to its runner (unknown keys return a quiet Info). UI thread only.
AppActionRunResult run(const std::string& key, const std::string& param = {});
// Materialises one catalog entry as a runnable AppAction. Keeps the catalog's identity,
// presentation and behaviour as the single source of truth; ActionRegistry only stores and
// dispatches the result. UI thread only.
std::unique_ptr<AppAction> make_action(const NativeCommand& command);
} // namespace NativeCommands
}} // namespace Slic3r::GUI
+6
View File
@@ -267,6 +267,12 @@ wxString ButtonsListCtrl::GetPageText(size_t n) const
return btn->GetLabel();
}
// ORCA
wxString ButtonsListCtrl::GetPageLabel(size_t n) const
{
return n < m_pageLabels.size() ? m_pageLabels[n] : wxString();
}
// ORCA
void ButtonsListCtrl::SetOverflowButton(wxWindow* button)
{
+14 -1
View File
@@ -33,8 +33,14 @@ public:
void SetPageText(size_t n, const wxString& strText);
void SetCompact(size_t n, bool compact); // ORCA
wxString GetPageText(size_t n) const;
// ORCA: the full page label, unaffected by SetCompact() blanking the button text.
wxString GetPageLabel(size_t n) const;
// Resource name the page was inserted with (empty for plugin pages, which pass a wxBitmap).
const std::string& GetPageIcon(size_t n) const { return m_pageIcons[n]; }
const std::string& GetPageIcon(size_t n) const
{
static const std::string empty;
return n < m_pageIcons.size() ? m_pageIcons[n] : empty;
}
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.
@@ -244,6 +250,13 @@ public:
return GetBtnsListCtrl()->GetPageText(n);
}
// ORCA: the real page label. GetPageText() returns the button label, which SetCompact() blanks.
wxString GetPageLabel(size_t n) const
{
wxCHECK_MSG(n < GetPageCount(), wxString(), wxS("Invalid page"));
return GetBtnsListCtrl()->GetPageLabel(n);
}
// Resource icon name the page was inserted with; empty for pages added with a wxBitmap.
std::string GetPageIcon(size_t n) const
{
+17 -10
View File
@@ -543,15 +543,16 @@ bool install_local_plugin_package(const boost::filesystem::path& package_file, w
});
timer->Start(100);
bool finished = false;
wxEventLoop loop;
auto on_finish = [&finished, &loop]() {
finished = true;
if (loop.IsRunning())
loop.Exit();
// finished/loop live on the heap: the worker's completion callback is posted to the UI loop
// and can still fire after this stack frame is gone, so it must not reference locals.
struct WaitState
{
bool finished = false;
wxEventLoop loop;
};
auto wait = std::make_shared<WaitState>();
std::thread([state, package_file, on_finish]() mutable {
std::thread([state, package_file, wait]() mutable {
std::string error;
bool ok = false;
try {
@@ -570,11 +571,17 @@ bool install_local_plugin_package(const boost::filesystem::path& package_file, w
state->ok = ok;
state->error = std::move(error);
}
wxTheApp->CallAfter(on_finish);
if (!wxTheApp)
return;
wxTheApp->CallAfter([wait]() {
wait->finished = true;
if (wait->loop.IsRunning())
wait->loop.Exit();
});
}).detach();
if (!finished)
loop.Run();
if (!wait->finished)
wait->loop.Run();
timer->Stop();
delete timer;
+5
View File
@@ -1723,6 +1723,11 @@ void PreferencesDialog::create_items()
auto item_multi_machine = create_item_checkbox(_L("Multi device management"), _L("With this option enabled, you can send a task to multiple devices at the same time and manage multiple devices."), "enable_multi_machine", _L("(Requires restart)"));
g_sizer->Add(item_multi_machine);
auto item_speed_dial = create_item_checkbox(_L("Open the Speed Dial with the Space key"),
_L("When enabled, pressing Space (with no other key held) opens the Speed Dial action search from any page."),
"enable_speed_dial");
g_sizer->Add(item_speed_dial);
#if 0
g_sizer->Add(create_item_title(_L("Filament Grouping")), 1, wxEXPAND);
//temporarily disable it
+3 -1
View File
@@ -322,7 +322,9 @@ void OptionsSearcher::init(std::vector<InputInfo> input_values)
void OptionsSearcher::apply(DynamicPrintConfig *config, Preset::Type type, ConfigOptionMode mode)
{
if (options.empty()) return;
// options_all_modes is a separate consumer (the Speed Dial), so "nothing initialised yet" means
// both views are empty - the mode-filtered options can be empty while the all-modes view is not.
if (options.empty() && options_all_modes.empty()) return;
options.erase(std::remove_if(options.begin(), options.end(), [type](Option opt) { return opt.type == type; }), options.end());
options_all_modes.erase(std::remove_if(options_all_modes.begin(), options_all_modes.end(), [type](Option opt) { return opt.type == type; }),
-4
View File
@@ -156,10 +156,6 @@ public:
void dlg_sys_color_changed();
void dlg_msw_rescale();
// The full gated option set built by init() (after visibility/mode/printer-tech filtering).
// Used by the Speed Dial to materialise config settings as first-class actions.
const std::vector<Option>& all_options() const { return options; }
// Every option across all UI modes (Developer included), regardless of the current mode.
// Used by the Speed Dial so it can list settings the user would have to switch mode to edit.
const std::vector<Option>& all_modes_options() const { return options_all_modes; }
+2 -13
View File
@@ -9,8 +9,6 @@
#include "Plater.hpp"
#include "Widgets/WebViewHostDialog.hpp"
#include <libslic3r/AppConfig.hpp>
#include <algorithm>
#include <wx/display.h>
@@ -156,15 +154,7 @@ void SpeedDialWebDialog::handle_web_command(const nlohmann::json& payload)
run_action(payload.value("id", ""), payload.value("title", ""), payload.value("param", ""));
else if (command == "search_tabs")
search_tabs();
else if (command == "go_to_tab") {
// "Go to tab..." second phase: the page hands back the tab id it matched.
const std::string tab_id = payload.value("id", "");
if (!tab_id.empty()) {
Hide();
if (wxGetApp().mainframe)
wxGetApp().mainframe->select_tab(from_u8(tab_id));
}
} else if (command == "resize")
else if (command == "resize")
resize_to_content(json_int_or(payload, "height", 0));
}
@@ -218,8 +208,7 @@ void SpeedDialWebDialog::run_action(const std::string& id, const std::string& ti
_L("Developer setting"), wxOK | wxCANCEL);
if (dlg.ShowModal() != wxID_OK)
return;
wxGetApp().app_config->set_bool("developer_mode", true);
wxGetApp().update_mode();
wxGetApp().enable_developer_mode();
} else {
RichMessageDialog dlg(wxGetApp().mainframe,
wxString::Format(_L("\"%s\" is a %s setting. Switch from %s mode to %s mode to edit it?"),