ed8430bc9b
on first start of Slic3r. Updated help menu (removed reference to the upstream manual) Fixed some OpenGL assert due to glOrtho being called with zero Z span.
424 lines
13 KiB
C++
424 lines
13 KiB
C++
#include "GUI.hpp"
|
|
#include "GUI_App.hpp"
|
|
#include "I18N.hpp"
|
|
#include "WipeTowerDialog.hpp"
|
|
|
|
#include <assert.h>
|
|
#include <string>
|
|
|
|
#include <boost/lexical_cast.hpp>
|
|
#include <boost/algorithm/string.hpp>
|
|
|
|
#if __APPLE__
|
|
#import <IOKit/pwr_mgt/IOPMLib.h>
|
|
#elif _WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#define NOMINMAX
|
|
#include <Windows.h>
|
|
#include "boost/nowide/convert.hpp"
|
|
#endif
|
|
|
|
#include <wx/display.h>
|
|
|
|
#include "wxExtensions.hpp"
|
|
#include "GUI_Preview.hpp"
|
|
#include "AboutDialog.hpp"
|
|
#include "AppConfig.hpp"
|
|
#include "ConfigWizard.hpp"
|
|
#include "PresetBundle.hpp"
|
|
#include "UpdateDialogs.hpp"
|
|
|
|
#include "libslic3r/Utils.hpp"
|
|
#include "libslic3r/Print.hpp"
|
|
#include "Tab.hpp"
|
|
#include "GUI_ObjectList.hpp"
|
|
|
|
namespace Slic3r { namespace GUI {
|
|
|
|
#if __APPLE__
|
|
IOPMAssertionID assertionID;
|
|
#endif
|
|
|
|
void disable_screensaver()
|
|
{
|
|
#if __APPLE__
|
|
CFStringRef reasonForActivity = CFSTR("Slic3r");
|
|
IOReturn success = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
|
|
kIOPMAssertionLevelOn, reasonForActivity, &assertionID);
|
|
// ignore result: success == kIOReturnSuccess
|
|
#elif _WIN32
|
|
SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS);
|
|
#endif
|
|
}
|
|
|
|
void enable_screensaver()
|
|
{
|
|
#if __APPLE__
|
|
IOReturn success = IOPMAssertionRelease(assertionID);
|
|
#elif _WIN32
|
|
SetThreadExecutionState(ES_CONTINUOUS);
|
|
#endif
|
|
}
|
|
|
|
bool debugged()
|
|
{
|
|
#ifdef _WIN32
|
|
return IsDebuggerPresent() == TRUE;
|
|
#else
|
|
return false;
|
|
#endif /* _WIN32 */
|
|
}
|
|
|
|
void break_to_debugger()
|
|
{
|
|
#ifdef _WIN32
|
|
if (IsDebuggerPresent())
|
|
DebugBreak();
|
|
#endif /* _WIN32 */
|
|
}
|
|
|
|
const std::string& shortkey_ctrl_prefix()
|
|
{
|
|
static const std::string str =
|
|
#ifdef __APPLE__
|
|
"⌘"
|
|
#else
|
|
"Ctrl+"
|
|
#endif
|
|
;
|
|
return str;
|
|
}
|
|
|
|
const std::string& shortkey_alt_prefix()
|
|
{
|
|
static const std::string str =
|
|
#ifdef __APPLE__
|
|
"⌥"
|
|
#else
|
|
"Alt+"
|
|
#endif
|
|
;
|
|
return str;
|
|
}
|
|
|
|
bool config_wizard_startup(bool app_config_exists)
|
|
{
|
|
if (!app_config_exists || wxGetApp().preset_bundle->printers.size() <= 1) {
|
|
config_wizard(ConfigWizard::RR_DATA_EMPTY);
|
|
return true;
|
|
} else if (get_app_config()->legacy_datadir()) {
|
|
// Looks like user has legacy pre-vendorbundle data directory,
|
|
// explain what this is and run the wizard
|
|
|
|
MsgDataLegacy dlg;
|
|
dlg.ShowModal();
|
|
|
|
config_wizard(ConfigWizard::RR_DATA_LEGACY);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void config_wizard(int reason)
|
|
{
|
|
// Exit wizard if there are unsaved changes and the user cancels the action.
|
|
if (! wxGetApp().check_unsaved_changes())
|
|
return;
|
|
|
|
try {
|
|
ConfigWizard wizard(nullptr, static_cast<ConfigWizard::RunReason>(reason));
|
|
wizard.run(wxGetApp().preset_bundle, wxGetApp().preset_updater);
|
|
}
|
|
catch (const std::exception &e) {
|
|
show_error(nullptr, e.what());
|
|
}
|
|
|
|
wxGetApp().load_current_presets();
|
|
|
|
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA &&
|
|
wxGetApp().obj_list()->has_multi_part_objects())
|
|
{
|
|
show_info(nullptr,
|
|
_(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" +
|
|
_(L("Please check and fix your object list.")),
|
|
_(L("Attention!")));
|
|
}
|
|
}
|
|
|
|
// opt_index = 0, by the reason of zero-index in ConfigOptionVector by default (in case only one element)
|
|
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/)
|
|
{
|
|
try{
|
|
switch (config.def()->get(opt_key)->type) {
|
|
case coFloatOrPercent:{
|
|
std::string str = boost::any_cast<std::string>(value);
|
|
bool percent = false;
|
|
if (str.back() == '%') {
|
|
str.pop_back();
|
|
percent = true;
|
|
}
|
|
double val = stod(str);
|
|
config.set_key_value(opt_key, new ConfigOptionFloatOrPercent(val, percent));
|
|
break;}
|
|
case coPercent:
|
|
config.set_key_value(opt_key, new ConfigOptionPercent(boost::any_cast<double>(value)));
|
|
break;
|
|
case coFloat:{
|
|
double& val = config.opt_float(opt_key);
|
|
val = boost::any_cast<double>(value);
|
|
break;
|
|
}
|
|
case coPercents:{
|
|
ConfigOptionPercents* vec_new = new ConfigOptionPercents{ boost::any_cast<double>(value) };
|
|
config.option<ConfigOptionPercents>(opt_key)->set_at(vec_new, opt_index, opt_index);
|
|
break;
|
|
}
|
|
case coFloats:{
|
|
ConfigOptionFloats* vec_new = new ConfigOptionFloats{ boost::any_cast<double>(value) };
|
|
config.option<ConfigOptionFloats>(opt_key)->set_at(vec_new, opt_index, opt_index);
|
|
break;
|
|
}
|
|
case coString:
|
|
config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast<std::string>(value)));
|
|
break;
|
|
case coStrings:{
|
|
if (opt_key == "compatible_prints" || opt_key == "compatible_printers") {
|
|
config.option<ConfigOptionStrings>(opt_key)->values =
|
|
boost::any_cast<std::vector<std::string>>(value);
|
|
}
|
|
else if (config.def()->get(opt_key)->gui_flags.compare("serialized") == 0) {
|
|
std::string str = boost::any_cast<std::string>(value);
|
|
std::vector<std::string> values {};
|
|
if (!str.empty()) {
|
|
if (str.back() == ';') str.pop_back();
|
|
// Split a string to multiple strings by a semi - colon.This is the old way of storing multi - string values.
|
|
// Currently used for the post_process config value only.
|
|
boost::split(values, str, boost::is_any_of(";"));
|
|
if (values.size() == 1 && values[0] == "")
|
|
values.resize(0);
|
|
}
|
|
config.option<ConfigOptionStrings>(opt_key)->values = values;
|
|
}
|
|
else{
|
|
ConfigOptionStrings* vec_new = new ConfigOptionStrings{ boost::any_cast<std::string>(value) };
|
|
config.option<ConfigOptionStrings>(opt_key)->set_at(vec_new, opt_index, 0);
|
|
}
|
|
}
|
|
break;
|
|
case coBool:
|
|
config.set_key_value(opt_key, new ConfigOptionBool(boost::any_cast<bool>(value)));
|
|
break;
|
|
case coBools:{
|
|
ConfigOptionBools* vec_new = new ConfigOptionBools{ boost::any_cast<unsigned char>(value) != 0 };
|
|
config.option<ConfigOptionBools>(opt_key)->set_at(vec_new, opt_index, 0);
|
|
break;}
|
|
case coInt:
|
|
config.set_key_value(opt_key, new ConfigOptionInt(boost::any_cast<int>(value)));
|
|
break;
|
|
case coInts:{
|
|
ConfigOptionInts* vec_new = new ConfigOptionInts{ boost::any_cast<int>(value) };
|
|
config.option<ConfigOptionInts>(opt_key)->set_at(vec_new, opt_index, 0);
|
|
}
|
|
break;
|
|
case coEnum:{
|
|
if (opt_key == "top_fill_pattern" ||
|
|
opt_key == "bottom_fill_pattern" ||
|
|
opt_key == "fill_pattern")
|
|
config.set_key_value(opt_key, new ConfigOptionEnum<InfillPattern>(boost::any_cast<InfillPattern>(value)));
|
|
else if (opt_key.compare("gcode_flavor") == 0)
|
|
config.set_key_value(opt_key, new ConfigOptionEnum<GCodeFlavor>(boost::any_cast<GCodeFlavor>(value)));
|
|
else if (opt_key.compare("support_material_pattern") == 0)
|
|
config.set_key_value(opt_key, new ConfigOptionEnum<SupportMaterialPattern>(boost::any_cast<SupportMaterialPattern>(value)));
|
|
else if (opt_key.compare("seam_position") == 0)
|
|
config.set_key_value(opt_key, new ConfigOptionEnum<SeamPosition>(boost::any_cast<SeamPosition>(value)));
|
|
else if (opt_key.compare("host_type") == 0)
|
|
config.set_key_value(opt_key, new ConfigOptionEnum<PrintHostType>(boost::any_cast<PrintHostType>(value)));
|
|
else if (opt_key.compare("display_orientation") == 0)
|
|
config.set_key_value(opt_key, new ConfigOptionEnum<SLADisplayOrientation>(boost::any_cast<SLADisplayOrientation>(value)));
|
|
else if(opt_key.compare("support_pillar_connection_mode") == 0)
|
|
config.set_key_value(opt_key, new ConfigOptionEnum<SLAPillarConnectionMode>(boost::any_cast<SLAPillarConnectionMode>(value)));
|
|
}
|
|
break;
|
|
case coPoints:{
|
|
if (opt_key.compare("bed_shape") == 0) {
|
|
config.option<ConfigOptionPoints>(opt_key)->values = boost::any_cast<std::vector<Vec2d>>(value);
|
|
break;
|
|
}
|
|
ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast<Vec2d>(value) };
|
|
config.option<ConfigOptionPoints>(opt_key)->set_at(vec_new, opt_index, 0);
|
|
}
|
|
break;
|
|
case coNone:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
catch (const std::exception & /* e */)
|
|
{
|
|
int i = 0;//no reason, just experiment
|
|
}
|
|
}
|
|
|
|
void show_error(wxWindow* parent, const wxString& message)
|
|
{
|
|
ErrorDialog msg(parent, message);
|
|
msg.ShowModal();
|
|
}
|
|
|
|
void show_error_id(int id, const std::string& message)
|
|
{
|
|
auto *parent = id != 0 ? wxWindow::FindWindowById(id) : nullptr;
|
|
show_error(parent, from_u8(message));
|
|
}
|
|
|
|
void show_info(wxWindow* parent, const wxString& message, const wxString& title)
|
|
{
|
|
wxMessageDialog msg_wingow(parent, message, title.empty() ? _(L("Notice")) : title, wxOK | wxICON_INFORMATION);
|
|
msg_wingow.ShowModal();
|
|
}
|
|
|
|
void warning_catcher(wxWindow* parent, const wxString& message)
|
|
{
|
|
wxMessageDialog msg(parent, message, _(L("Warning")), wxOK | wxICON_WARNING);
|
|
msg.ShowModal();
|
|
}
|
|
|
|
void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value)
|
|
{
|
|
if (comboCtrl == nullptr)
|
|
return;
|
|
|
|
wxCheckListBoxComboPopup* popup = new wxCheckListBoxComboPopup;
|
|
if (popup != nullptr)
|
|
{
|
|
// FIXME If the following line is removed, the combo box popup list will not react to mouse clicks.
|
|
// On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10.
|
|
comboCtrl->UseAltPopupWindow();
|
|
|
|
comboCtrl->EnablePopupAnimation(false);
|
|
comboCtrl->SetPopupControl(popup);
|
|
popup->SetStringValue(from_u8(text));
|
|
popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); });
|
|
popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); });
|
|
popup->Bind(wxEVT_KEY_DOWN, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
|
|
popup->Bind(wxEVT_KEY_UP, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
|
|
|
|
std::vector<std::string> items_str;
|
|
boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off);
|
|
|
|
for (const std::string& item : items_str)
|
|
{
|
|
popup->Append(from_u8(item));
|
|
}
|
|
|
|
for (unsigned int i = 0; i < popup->GetCount(); ++i)
|
|
{
|
|
popup->Check(i, initial_value);
|
|
}
|
|
}
|
|
}
|
|
|
|
int combochecklist_get_flags(wxComboCtrl* comboCtrl)
|
|
{
|
|
int flags = 0;
|
|
|
|
wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup);
|
|
if (popup != nullptr)
|
|
{
|
|
for (unsigned int i = 0; i < popup->GetCount(); ++i)
|
|
{
|
|
if (popup->IsChecked(i))
|
|
flags |= 1 << i;
|
|
}
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
AppConfig* get_app_config()
|
|
{
|
|
return wxGetApp().app_config;
|
|
}
|
|
|
|
wxString from_u8(const std::string &str)
|
|
{
|
|
return wxString::FromUTF8(str.c_str());
|
|
}
|
|
|
|
std::string into_u8(const wxString &str)
|
|
{
|
|
auto buffer_utf8 = str.utf8_str();
|
|
return std::string(buffer_utf8.data());
|
|
}
|
|
|
|
wxString from_path(const boost::filesystem::path &path)
|
|
{
|
|
#ifdef _WIN32
|
|
return wxString(path.string<std::wstring>());
|
|
#else
|
|
return from_u8(path.string<std::string>());
|
|
#endif
|
|
}
|
|
|
|
boost::filesystem::path into_path(const wxString &str)
|
|
{
|
|
return boost::filesystem::path(str.wx_str());
|
|
}
|
|
|
|
void about()
|
|
{
|
|
AboutDialog dlg;
|
|
dlg.ShowModal();
|
|
}
|
|
|
|
void desktop_open_datadir_folder()
|
|
{
|
|
// Execute command to open a file explorer, platform dependent.
|
|
// FIXME: The const_casts aren't needed in wxWidgets 3.1, remove them when we upgrade.
|
|
|
|
const auto path = data_dir();
|
|
#ifdef _WIN32
|
|
const wxString widepath = from_u8(path);
|
|
const wchar_t *argv[] = { L"explorer", widepath.GetData(), nullptr };
|
|
::wxExecute(const_cast<wchar_t**>(argv), wxEXEC_ASYNC, nullptr);
|
|
#elif __APPLE__
|
|
const char *argv[] = { "open", path.data(), nullptr };
|
|
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr);
|
|
#else
|
|
const char *argv[] = { "xdg-open", path.data(), nullptr };
|
|
|
|
// Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
|
|
// because they may mess up the environment expected by the file manager.
|
|
// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
|
|
if (wxGetEnv("APPIMAGE", nullptr)) {
|
|
// We're running from AppImage
|
|
wxEnvVariableHashMap env_vars;
|
|
wxGetEnvMap(&env_vars);
|
|
|
|
env_vars.erase("APPIMAGE");
|
|
env_vars.erase("APPDIR");
|
|
env_vars.erase("LD_LIBRARY_PATH");
|
|
env_vars.erase("LD_PRELOAD");
|
|
env_vars.erase("UNION_PRELOAD");
|
|
|
|
wxExecuteEnv exec_env;
|
|
exec_env.env = std::move(env_vars);
|
|
|
|
wxString owd;
|
|
if (wxGetEnv("OWD", &owd)) {
|
|
// This is the original work directory from which the AppImage image was run,
|
|
// set it as CWD for the child process:
|
|
exec_env.cwd = std::move(owd);
|
|
}
|
|
|
|
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, &exec_env);
|
|
} else {
|
|
// Looks like we're NOT running from AppImage, we'll make no changes to the environment.
|
|
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, nullptr);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
} }
|