Merge branch 'dk_notifications'

This commit is contained in:
David Kocik 2021-11-02 10:52:11 +01:00
commit d2b17f138a
11 changed files with 249 additions and 18 deletions

View file

@ -34,8 +34,9 @@
#
# Open preferences (might add item to highlight)
# hypertext_type = preferences
# hypertext_preferences_page = 0 (values 0-2 according to prefernces tab to be opened)
#
# hypertext_preferences_page = 2 (values 0-2 according to prefernces tab to be opened)
# hypertext_preferences_item = show_collapse_button (name of variable saved in prusaslicer.ini connected to the setting in preferences)
#
# Open gallery (no aditional var)
# hypertext_type = gallery
#
@ -97,6 +98,7 @@ documentation_link = https://help.prusa3d.com/en/article/reload-from-disk_120427
text = Hiding sidebar\nDid you know that you can hide the right sidebar using the shortcut <b>Shift+Tab</b>? You can also enable the icon for this from the<a>Preferences.</a>
hypertext_type = preferences
hypertext_preferences_page = 2
hypertext_preferences_item = show_collapse_button
[hint:Perspective camera]
text = Perspective camera\nDid you know that you can use the <b>K</b> key to quickly switch between an orthographic and perspective camera?
@ -213,6 +215,7 @@ disabled_tags = SLA
text = Settings in non-modal window\nDid you know that you can open the Settings in a new non-modal window? This means you can have settings open on one screen and the G-code Preview on the other. Go to the<a>Preferences</a>and select Settings in non-modal window.
hypertext_type = preferences
hypertext_preferences_page = 2
hypertext_preferences_item = dlg_settings_layout_mode
[hint:Adaptive infills]
text = Adaptive infills\nDid you know that you can use the Adaptive cubic and Support cubic infills to decrease the print time and lower the filament consumption? Read more in the documentation.

View file

@ -248,11 +248,11 @@ std::string AppConfig::load()
bool recovered = false;
try {
ifs.open(AppConfig::config_path());
ifs.open(AppConfig::loading_path());
#ifdef WIN32
// Verify the checksum of the config file without taking just for debugging purpose.
if (!verify_config_file_checksum(ifs))
BOOST_LOG_TRIVIAL(info) << "The configuration file " << AppConfig::config_path() <<
BOOST_LOG_TRIVIAL(info) << "The configuration file " << AppConfig::loading_path() <<
" has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit.";
ifs.seekg(0, boost::nowide::ifstream::beg);
@ -262,32 +262,32 @@ std::string AppConfig::load()
#ifdef WIN32
// The configuration file is corrupted, try replacing it with the backup configuration.
ifs.close();
std::string backup_path = (boost::format("%1%.bak") % AppConfig::config_path()).str();
std::string backup_path = (boost::format("%1%.bak") % AppConfig::loading_path()).str();
if (boost::filesystem::exists(backup_path)) {
// Compute checksum of the configuration backup file and try to load configuration from it when the checksum is correct.
boost::nowide::ifstream backup_ifs(backup_path);
if (!verify_config_file_checksum(backup_ifs)) {
BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", AppConfig::config_path(), backup_path);
BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", AppConfig::loading_path(), backup_path);
backup_ifs.close();
boost::filesystem::remove(backup_path);
} else if (std::string error_message; copy_file(backup_path, AppConfig::config_path(), error_message, false) != SUCCESS) {
BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", AppConfig::config_path(), backup_path, error_message);
} else if (std::string error_message; copy_file(backup_path, AppConfig::loading_path(), error_message, false) != SUCCESS) {
BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", AppConfig::loading_path(), backup_path, error_message);
backup_ifs.close();
boost::filesystem::remove(backup_path);
} else {
BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", AppConfig::config_path(), backup_path);
BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", AppConfig::loading_path(), backup_path);
// Try parse configuration file after restore from backup.
try {
ifs.open(AppConfig::config_path());
ifs.open(AppConfig::loading_path());
pt::read_ini(ifs, tree);
recovered = true;
} catch (pt::ptree_error& ex) {
BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", AppConfig::config_path(), ex.what());
BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", AppConfig::loading_path(), ex.what());
}
}
} else
#endif // WIN32
BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", AppConfig::config_path(), ex.what());
BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", AppConfig::loading_path(), ex.what());
if (! recovered) {
// Report the initial error of parsing PrusaSlicer.ini.
// Error while parsing config file. We'll customize the error message and rethrow to be displayed.

View file

@ -148,6 +148,9 @@ public:
// Does the config file exist?
bool exists();
void set_loading_path(const std::string& path) { m_loading_path = path; }
std::string loading_path() { return (m_loading_path.empty() ? config_path() : m_loading_path); }
std::vector<std::string> get_recent_projects() const;
void set_recent_projects(const std::vector<std::string>& recent_projects);
@ -196,6 +199,8 @@ private:
Semver m_orig_version;
// Whether the existing version is before system profiles & configuration updating
bool m_legacy_datadir;
std::string m_loading_path;
};
} // namespace Slic3r

View file

@ -188,6 +188,61 @@ void PresetBundle::setup_directories()
}
}
// recursively copy all files and dirs in from_dir to to_dir
static void copy_dir(const boost::filesystem::path& from_dir, const boost::filesystem::path& to_dir)
{
if(!boost::filesystem::is_directory(from_dir))
return;
// i assume to_dir.parent surely exists
if (!boost::filesystem::is_directory(to_dir))
boost::filesystem::create_directory(to_dir);
for (auto& dir_entry : boost::filesystem::directory_iterator(from_dir)) {
if (!boost::filesystem::is_directory(dir_entry.path())) {
std::string em;
CopyFileResult cfr = copy_file(dir_entry.path().string(), (to_dir / dir_entry.path().filename()).string(), em, false);
if (cfr != SUCCESS) {
BOOST_LOG_TRIVIAL(error) << "Error when copying files from " << from_dir << " to " << to_dir << ": " << em;
}
} else {
copy_dir(dir_entry.path(), to_dir / dir_entry.path().filename());
}
}
}
void PresetBundle::copy_files(const std::string& from)
{
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
// list of searched paths based on current directory system in setup_directories()
// do not copy cache and snapshots
boost::filesystem::path from_data_dir = boost::filesystem::path(from);
std::initializer_list<boost::filesystem::path> from_dirs= {
from_data_dir / "vendor",
from_data_dir / "shapes",
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
// Store the print/filament/printer presets into a "presets" directory.
data_dir / "presets",
data_dir / "presets" / "print",
data_dir / "presets" / "filament",
data_dir / "presets" / "sla_print",
data_dir / "presets" / "sla_material",
data_dir / "presets" / "printer",
data_dir / "presets" / "physical_printer"
#else
// Store the print/filament/printer presets at the same location as the upstream Slic3r.
from_data_dir / "print",
from_data_dir / "filament",
from_data_dir / "sla_print",
from_data_dir / "sla_material",
from_data_dir / "printer",
from_data_dir / "physical_printer"
#endif
};
// copy recursively all files
for (const boost::filesystem::path& from_dir : from_dirs) {
copy_dir(from_dir, data_dir / from_dir.filename());
}
}
PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule,
const PresetPreferences& preferred_selection/* = PresetPreferences()*/)
{

View file

@ -24,6 +24,7 @@ public:
void reset(bool delete_files);
void setup_directories();
void copy_files(const std::string& from);
struct PresetPreferences {
std::string printer_model_id;// name of a preferred printer model

View file

@ -743,6 +743,25 @@ bool GUI_App::init_opengl()
#endif
}
// gets path to PrusaSlicer.ini, returns semver from first line comment
static boost::optional<Semver> parse_semver_from_ini(std::string path)
{
std::ifstream stream(path);
std::stringstream buffer;
buffer << stream.rdbuf();
std::string body = buffer.str();
size_t end_line = body.find_first_of("\n\r");
body.resize(end_line);
size_t start = body.find("PrusaSlicer ");
if (start == std::string::npos)
return boost::none;
body = body.substr(start + 12);
size_t end = body.find_first_of(" \n\r");
if (end < body.size())
body.resize(end);
return Semver::parse(body);
}
void GUI_App::init_app_config()
{
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
@ -791,9 +810,110 @@ void GUI_App::init_app_config()
"\n\n" + app_config->config_path() + "\n\n" + error);
}
}
// Save orig_version here, so its empty if no app_config existed before this run.
m_last_config_version = app_config->orig_version();//parse_semver_from_ini(app_config->config_path());
}
}
// returns true if found newer version and user agreed to use it
bool GUI_App::check_older_app_config(Semver current_version, bool backup)
{
// find other version app config (alpha / beta / release)
std::string config_path = app_config->config_path();
boost::filesystem::path parent_file_path(config_path);
std::string filename = parent_file_path.filename().string();
parent_file_path.remove_filename().remove_filename();
std::vector<boost::filesystem::path> candidates;
if (SLIC3R_APP_KEY "-alpha" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-alpha" / filename);
if (SLIC3R_APP_KEY "-beta" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-beta" / filename);
if (SLIC3R_APP_KEY != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY / filename);
Semver last_semver = current_version;
for (const auto& candidate : candidates) {
if (boost::filesystem::exists(candidate)) {
// parse
boost::optional<Semver>other_semver = parse_semver_from_ini(candidate.string());
if (other_semver && *other_semver > last_semver) {
last_semver = *other_semver;
m_older_data_dir_path = candidate.parent_path().string();
}
}
}
if (m_older_data_dir_path.empty())
return false;
BOOST_LOG_TRIVIAL(info) << "last app config file used: " << m_older_data_dir_path;
// ask about using older data folder
wxRichMessageDialog msg(nullptr, backup ?
wxString::Format(_L("PrusaSlicer detected another configuration folder at %s."
"\nIts version is %s."
"\nLast version you used in current configuration folder is %s."
"\nPlease note that PrusaSlicer uses different folders to save configuration of alpha, beta and full release versions."
"\nWould you like to copy found configuration to your current configuration folder?"
"\n\nIf you select yes, PrusaSlicer will copy all profiles and other files from found folder to the current one. Overwriting any existing file with matching name."
"\nIf you select no, you will continue with current configuration.")
, m_older_data_dir_path, last_semver.to_string(), current_version.to_string())
: wxString::Format(_L("PrusaSlicer detected another configuration folder at %s."
"\nIts version is %s."
"\nThere is no configuration file in current configuration folder."
"\nPlease note that PrusaSlicer uses different folders to save configuration of alpha, beta and full release versions."
"\nWould you like to copy found configuration to your current configuration folder?"
"\n\nIf you select yes, PrusaSlicer will copy all profiles and other files from found folder to the current one."
"\nIf you select no, you will start with clean installation with configuration wizard.")
, m_older_data_dir_path, last_semver.to_string())
, _L("PrusaSlicer"), wxICON_QUESTION | wxYES_NO);
if (msg.ShowModal() == wxID_YES) {
std::string snapshot_id;
if (backup) {
// configuration snapshot
std::string comment;
if (const Config::Snapshot* snapshot = Config::take_config_snapshot_report_error(
*app_config,
Config::Snapshot::SNAPSHOT_USER,
comment);
snapshot != nullptr)
// Is thos correct? Save snapshot id for later, when new app config is loaded.
snapshot_id = snapshot->id;
else
BOOST_LOG_TRIVIAL(error) << "Failed to take congiguration snapshot: ";
}
// This will tell later (when config folder structure is sure to exists) to copy files from m_older_data_dir_path
m_init_app_config_from_older = true;
// load app config from older file
app_config->set_loading_path((boost::filesystem::path(m_older_data_dir_path) / filename).string());
std::string error = app_config->load();
if (!error.empty()) {
// Error while parsing config file. We'll customize the error message and rethrow to be displayed.
if (is_editor()) {
throw Slic3r::RuntimeError(
_u8L("Error parsing PrusaSlicer config file, it is probably corrupted. "
"Try to manually delete the file to recover from the error. Your user profiles will not be affected.") +
"\n\n" + app_config->config_path() + "\n\n" + error);
}
else {
throw Slic3r::RuntimeError(
_u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. "
"Try to manually delete the file to recover from the error.") +
"\n\n" + app_config->config_path() + "\n\n" + error);
}
}
if (!snapshot_id.empty())
app_config->set("on_snapshot", snapshot_id);
m_app_conf_exists = true;
return true;
}
return false;
}
void GUI_App::copy_older_config()
{
preset_bundle->copy_files(m_older_data_dir_path);
}
void GUI_App::init_single_instance_checker(const std::string &name, const std::string &path)
{
BOOST_LOG_TRIVIAL(debug) << "init wx instance checker " << name << " "<< path;
@ -812,6 +932,29 @@ bool GUI_App::OnInit()
bool GUI_App::on_init_inner()
{
// win32 build on win64 and viceversa
#ifdef _WIN64
if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "") {
wxRichMessageDialog dlg(nullptr,
_L("You have started PrusaSlicer for 64-bit architecture on 32-bit system."
"\nPlease download and install correct version at https://www.prusa3d.cz/prusaslicer/."
"\nDo you wish to continue?"),
"PrusaSlicer", wxICON_QUESTION | wxYES_NO);
if (dlg.ShowModal() != wxID_YES)
return false;
}
#elif _WIN32
if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") {
wxRichMessageDialog dlg(nullptr,
_L("You have started PrusaSlicer for 32-bit architecture on 64-bit system."
"\nPlease download and install correct version at https://www.prusa3d.cz/prusaslicer/."
"\nDo you wish to continue?"),
"PrusaSlicer", wxICON_QUESTION | wxYES_NO);
if (dlg.ShowModal() != wxID_YES)
return false;
}
#endif // _WIN64
// Forcing back menu icons under gtk2 and gtk3. Solution is based on:
// https://docs.gtk.org/gtk3/class.Settings.html
// see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb
@ -861,6 +1004,13 @@ bool GUI_App::on_init_inner()
}
}
if (m_last_config_version) {
if (*m_last_config_version < *Semver::parse(SLIC3R_VERSION))
check_older_app_config(*m_last_config_version, true);
} else {
check_older_app_config(Semver(), false);
}
app_config->set("version", SLIC3R_VERSION);
app_config->save();
@ -899,12 +1049,18 @@ bool GUI_App::on_init_inner()
scrn->SetText(_L("Loading configuration")+ dots);
}
preset_bundle = new PresetBundle();
// just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory
// supplied as argument to --datadir; in that case we should still run the wizard
preset_bundle->setup_directories();
if (m_init_app_config_from_older)
copy_older_config();
if (is_editor()) {
#ifdef __WXMSW__
if (app_config->get("associate_3mf") == "1")

View file

@ -337,6 +337,8 @@ public:
private:
bool on_init_inner();
void init_app_config();
bool check_older_app_config(Semver current_version, bool backup);
void copy_older_config();
void window_pos_save(wxTopLevelWindow* window, const std::string &name);
void window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false);
void window_pos_sanitize(wxTopLevelWindow* window);
@ -344,6 +346,10 @@ private:
bool config_wizard_startup();
void check_updates(const bool verbose);
bool m_init_app_config_from_older { false };
std::string m_older_data_dir_path;
boost::optional<Semver> m_last_config_version;
};
DECLARE_APP(GUI_App)

View file

@ -413,9 +413,9 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
// open preferences
} else if(dict["hypertext_type"] == "preferences") {
int page = static_cast<Preset::Type>(std::atoi(dict["hypertext_preferences_page"].c_str()));
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page]() { wxGetApp().open_preferences(page); } };
std::string item = dict["hypertext_preferences_item"];
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page, item]() { wxGetApp().open_preferences(page, item); } };
m_loaded_hints.emplace_back(hint_data);
} else if (dict["hypertext_type"] == "plater") {
std::string item = dict["hypertext_plater_item"];
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_toolbar_item(item); } };

View file

@ -2068,9 +2068,11 @@ bool NotificationManager::update_notifications(GLCanvas3D& canvas)
if ((*it).remaining_time > 0)
(*it).remaining_time -= time_since_render;
if ((*it).remaining_time <= 0) {
if ((*it).condition_callback()) { // push notification, erase it from waiting list (frame is scheduled by push)
if ((*it).notification && (*it).condition_callback()) { // push notification, erase it from waiting list (frame is scheduled by push)
(*it).notification->reset_timer();
if (push_notification_data(std::move((*it).notification), 0)) {
// if activate_existing returns false, we expect push to return true.
if(!this->activate_existing((*it).notification.get()) || (*it).delay_interval == 0) {
push_notification_data(std::move((*it).notification), 0);
it = m_waiting_notifications.erase(it);
continue;
}
@ -2107,11 +2109,13 @@ bool NotificationManager::activate_existing(const NotificationManager::PopNotifi
const std::string &new_text = notification->get_data().text1;
for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) {
if ((*it)->get_type() == new_type && !(*it)->is_finished()) {
// multiple of one type allowed, but must have different text
if (std::find(m_multiple_types.begin(), m_multiple_types.end(), new_type) != m_multiple_types.end()) {
// If found same type and same text, return true - update will be performed on the old notif
if ((*it)->compare_text(new_text) == false) {
continue;
}
// multiple of one type allowed, but must have different text nad ObjectID
} else if (new_type == NotificationType::SlicingWarning) {
auto w1 = dynamic_cast<const ObjectIDNotification*>(notification);
auto w2 = dynamic_cast<const ObjectIDNotification*>(it->get());

View file

@ -112,7 +112,7 @@ enum class NotificationType
// information about netfabb is finished repairing model (blocking proccess)
NetfabbFinished,
// Short meesage to fill space between start and finish of export
ExportOngoing
ExportOngoing,
};
class NotificationManager
@ -706,6 +706,7 @@ private:
// Otherwise another delay interval waiting. Timestamp is 0.
// Note that notification object is constructed when being added to the waiting list, but there are no updates called on it and its timer is reset at regular push.
// Also note that no control of same notification is done during push_delayed_notification_data but if waiting notif fails to push, it continues waiting.
// If delay_interval is 0, notification is pushed only after initial_delay no matter the result.
void push_delayed_notification_data(std::unique_ptr<NotificationManager::PopNotification> notification, std::function<bool(void)> condition_callback, int64_t initial_delay, int64_t delay_interval);
//finds older notification of same type and moves it to the end of queue. returns true if found
bool activate_existing(const NotificationManager::PopNotification* notification);

View file

@ -3294,6 +3294,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova
show_warning_dialog = true;
if (! output_path.empty()) {
background_process.schedule_export(output_path.string(), output_path_on_removable_media);
notification_manager->push_delayed_notification(NotificationType::ExportOngoing, []() {return true; }, 1000, 0);
} else {
background_process.schedule_upload(std::move(upload_job));
}
@ -4030,7 +4031,6 @@ void Plater::priv::on_export_began(wxCommandEvent& evt)
{
if (show_warning_dialog)
warnings_dialog();
notification_manager->push_delayed_notification(NotificationType::ExportOngoing, [](){return true;}, 1000, 1000);
}
void Plater::priv::on_slicing_began()
{