diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index fab7a70582..830fff8f98 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -413,14 +413,11 @@ namespace Slic3r { std::string WipeTowerIntegration::prime(GCode& gcodegen) { - assert(m_layer_idx == 0); std::string gcode; - for (const WipeTower::ToolChangeResult& tcr : m_priming) { if (! tcr.extrusions.empty()) gcode += append_tcr(gcodegen, tcr, tcr.new_tool); } - return gcode; } @@ -1243,18 +1240,30 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz)); BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print)); bbox_prime.offset(0.5f); - // Beep for 500ms, tone 800Hz. Yet better, play some Morse. - _write(file, this->retract()); - _write(file, "M300 S800 P500\n"); - if (bbox_prime.overlap(bbox_print)) { - // Wait for the user to remove the priming extrusions, otherwise they would - // get covered by the print. - _write(file, "M1 Remove priming towers and click button.\n"); - } - else { - // Just wait for a bit to let the user check, that the priming succeeded. - //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. - _write(file, "M1 S10\n"); + bool overlap = bbox_prime.overlap(bbox_print); + + if (print.config().gcode_flavor == gcfMarlin) { + _write(file, this->retract()); + _write(file, "M300 S800 P500\n"); // Beep for 500ms, tone 800Hz. + if (overlap) { + // Wait for the user to remove the priming extrusions. + _write(file, "M1 Remove priming towers and click button.\n"); + } else { + // Just wait for a bit to let the user check, that the priming succeeded. + //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. + _write(file, "M1 S10\n"); + } + } else { + // This is not Marlin, M1 command is probably not supported. + // (See https://github.com/prusa3d/PrusaSlicer/issues/5441.) + if (overlap) { + print.active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, + L("Your print is very close to the priming regions. " + "Make sure there is no collision.")); + } else { + // Just continue printing, no action necessary. + } + } } print.throw_if_canceled(); diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 7a1cdf8d38..c25026cc49 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1042,8 +1042,6 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial int vol_idx = 0; for (ModelVolume* volume : volumes) { - volume->supported_facets.clear(); - volume->seam_facets.clear(); if (!volume->mesh().empty()) { TriangleMesh mesh(volume->mesh()); mesh.require_shared_vertices(); @@ -1060,6 +1058,9 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial vol->source.object_idx = (int)new_objects.size(); vol->source.volume_idx = vol_idx; + vol->supported_facets.assign(volume->supported_facets); + vol->seam_facets.assign(volume->seam_facets); + // Perform conversion only if the target "imperial" state is different from the current one. // This check supports conversion of "mixed" set of volumes, each with different "imperial" state. if (//vol->source.is_converted_from_inches != from_imperial && diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 406f57de02..9bf0a23b53 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -1272,7 +1273,7 @@ bool GUI_App::switch_language() } } -#ifdef __linux +#ifdef __linux__ static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguageInfo* language, const wxLanguageInfo* system_language) { @@ -1281,6 +1282,9 @@ static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguage std::vector locales; const std::string lang_prefix = into_u8(language->CanonicalName.BeforeFirst('_')); + // Call locale -a so we can parse the output to get the list of available locales + // We expect lines such as "en_US.utf8". Pick ones starting with the language code + // we are switching to. Lines with different formatting will be removed later. FILE* fp = popen("locale -a", "r"); if (fp != NULL) { while (fgets(path, max_len, fp) != NULL) { @@ -1293,34 +1297,51 @@ static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguage } // locales now contain all candidates for this language. - // Sort them so ones containing anything about UTF-8 are at the beginning. + // Sort them so ones containing anything about UTF-8 are at the end. std::sort(locales.begin(), locales.end(), [](const std::string& a, const std::string& b) { auto has_utf8 = [](const std::string & s) { - return boost::to_upper_copy(s).find("UTF") != std::string::npos - && s.find("8") != std::string::npos; + auto S = boost::to_upper_copy(s); + return S.find("UTF8") != std::string::npos || S.find("UTF-8") != std::string::npos; }; - return (has_utf8(a) && ! has_utf8(b)); + return ! has_utf8(a) && has_utf8(b); }); - // Remove the suffix. + // Remove the suffix behind a dot, if there is one. for (std::string& s : locales) s = s.substr(0, s.find(".")); - // Is there a candidate matching a country code of a system language? Put it at the beginning - // (duplicates do not matter). Check backwards so the utf8 one ends up first if there are more. + // We just hope that dear Linux "locale -a" returns country codes + // in ISO 3166-1 alpha-2 code (two letter) format. + // https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes + // To be sure, remove anything not looking as expected + // (any number of lowercase letters, underscore, two uppercase letters). + locales.erase(std::remove_if(locales.begin(), + locales.end(), + [](const std::string& s) { + return ! std::regex_match(s, + std::regex("^[a-z]+_[A-Z]{2}$")); + }), + locales.end()); + + // Is there a candidate matching a country code of a system language? Move it to the end, + // while maintaining the order of matches, so that the best match ends up at the very end. std::string system_country = "_" + into_u8(system_language->CanonicalName.AfterFirst('_')).substr(0, 2); int cnt = locales.size(); for (int i=0; iLanguage)) - return lang; - } + for (auto it = locales.rbegin(); it != locales.rend(); ++ it) + if (! it->empty()) { + const std::string &locale = *it; + const wxLanguageInfo* lang = wxLocale::FindLanguageInfo(from_u8(locale)); + if (wxLocale::IsAvailable(lang->Language)) + return lang; + } return language; } #endif diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index b531968440..7706e7c243 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1011,7 +1011,13 @@ void NotificationManager::push_notification(const std::string& text, int timesta { push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, timestamp); } -void NotificationManager::push_notification(const std::string& text, NotificationManager::NotificationLevel level, int timestamp) + +void NotificationManager::push_notification(NotificationType type, + NotificationLevel level, + const std::string& text, + const std::string& hypertext, + std::function callback, + int timestamp) { int duration = 0; switch (level) { @@ -1022,7 +1028,7 @@ void NotificationManager::push_notification(const std::string& text, Notificatio assert(false); return; } - push_notification_data({ NotificationType::CustomNotification, level, duration, text }, timestamp); + push_notification_data({ type, level, duration, text, hypertext, callback }, timestamp); } void NotificationManager::push_slicing_error_notification(const std::string& text) { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 9452a8da0c..3e278a3908 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -67,7 +67,9 @@ enum class NotificationType // Progress bar instead of text. ProgressBar, // Notification, when Color Change G-code is empty and user try to add color change on DoubleSlider. - EmptyColorChangeCode + EmptyColorChangeCode, + // Notification that custom supports/seams were deleted after mesh repair. + CustomSupportsAndSeamRemovedAfterRepair }; class NotificationManager @@ -97,7 +99,8 @@ public: void push_notification(const std::string& text, int timestamp = 0); // Push a NotificationType::CustomNotification with provided notification level and 10s for RegularNotification. // ErrorNotification and ImportantNotification are never faded out. - void push_notification(const std::string& text, NotificationLevel level, int timestamp = 0); + void push_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext = "", + std::function callback = std::function(), int timestamp = 0); // Creates Slicing Error notification with a custom text and no fade out. void push_slicing_error_notification(const std::string& text); // Creates Slicing Warning notification with a custom text and no fade out. diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index beb503ef30..35c03db267 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2124,11 +2124,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) if (evt.data.second) { this->show_action_buttons(this->ready_to_slice); notification_manager->close_notification_of_type(NotificationType::ExportFinished); - notification_manager->push_notification(format(_L("Successfully unmounted. The device %s(%s) can now be safely removed from the computer."), evt.data.first.name, evt.data.first.path), - NotificationManager::NotificationLevel::RegularNotification); + notification_manager->push_notification(NotificationType::CustomNotification, + NotificationManager::NotificationLevel::RegularNotification, + format(_L("Successfully unmounted. The device %s(%s) can now be safely removed from the computer."), evt.data.first.name, evt.data.first.path) + ); } else { - notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path), - NotificationManager::NotificationLevel::ErrorNotification); + notification_manager->push_notification(NotificationType::CustomNotification, + NotificationManager::NotificationLevel::ErrorNotification, + format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path) + ); } }); this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) { @@ -3361,10 +3365,38 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = if (obj_idx < 0) return; - Plater::TakeSnapshot snapshot(q, _L("Fix Throught NetFabb")); + size_t snapshot_time = undo_redo_stack().active_snapshot_time(); + Plater::TakeSnapshot snapshot(q, _L("Fix through NetFabb")); - fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx); - sla::reproject_points_and_holes(model.objects[obj_idx]); + ModelObject* mo = model.objects[obj_idx]; + + // If there are custom supports/seams, remove them. Fixed mesh + // may be different and they would make no sense. + bool paint_removed = false; + for (ModelVolume* mv : mo->volumes) { + paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty(); + mv->supported_facets.clear(); + mv->seam_facets.clear(); + } + if (paint_removed) { + // snapshot_time is captured by copy so the lambda knows where to undo/redo to. + notification_manager->push_notification( + NotificationType::CustomSupportsAndSeamRemovedAfterRepair, + NotificationManager::NotificationLevel::RegularNotification, + _u8L("Custom supports and seams were removed after repairing the mesh."), + _u8L("Undo the repair"), + [this, snapshot_time](wxEvtHandler*){ + if (undo_redo_stack().has_undo_snapshot(snapshot_time)) + undo_redo_to(snapshot_time); + else + notification_manager->push_notification( + _u8L("Cannot undo to before the mesh repair!")); + return true; + }); + } + + fix_model_by_win10_sdk_gui(*mo, vol_idx); + sla::reproject_points_and_holes(mo); this->update(); this->object_list_changed(); this->schedule_background_process(); diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index edf6b917a1..d82d9e31db 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -36,6 +36,15 @@ namespace Slic3r { namespace UndoRedo { +#ifdef SLIC3R_UNDOREDO_DEBUG +static inline std::string ptr_to_string(const void* ptr) +{ + char buf[64]; + sprintf(buf, "%p", ptr); + return buf; +} +#endif + SnapshotData::SnapshotData() : printer_technology(ptUnknown), flags(0), layer_range_idx(-1) { } @@ -368,15 +377,6 @@ private: MutableHistoryInterval& operator=(const MutableHistoryInterval &rhs); }; -#ifdef SLIC3R_UNDOREDO_DEBUG -static inline std::string ptr_to_string(const void* ptr) -{ - char buf[64]; - sprintf(buf, "%p", ptr); - return buf; -} -#endif - // Smaller objects (Model, ModelObject, ModelInstance, ModelVolume, DynamicPrintConfig) // are mutable and there is not tracking of the changes, therefore a snapshot needs to be // taken every time and compared to the previous data at the Undo / Redo stack.