Notifications & warning dialog

notifications
dialog with warnings produced by slicing is shown before exporting
This commit is contained in:
David Kocik 2020-08-03 15:36:55 +02:00
parent 4bfb69eb14
commit b3f8ae5ca7
21 changed files with 1791 additions and 89 deletions

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="resin">
<rect x="4" y="7" fill="#ED6B21" width="8" height="8"/>
<path fill="none" stroke="#808080" stroke-linecap="round" stroke-miterlimit="10" d="M4.5,15h6.99c0.28,0,0.5-0.23,0.5-0.5V6
c0-1-2-1-2-2s0-1,0-1h1V1.5C11,1.23,10.77,1,10.5,1H5.5C5.23,1,5,1.23,5,1.5V3h1v1c0,1-2,1-2,2v8.5C4,14.77,4.23,15,4.5,15z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 671 B

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="cross_megafocus.svg"
xml:space="preserve"
enable-background="new 0 0 16 16"
viewBox="0 0 16 16"
y="0px"
x="0px"
id="Layer_1"
version="1.0"><metadata
id="metadata16"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs14" /><sodipodi:namedview
inkscape:current-layer="Layer_1"
inkscape:window-maximized="1"
inkscape:window-y="-9"
inkscape:window-x="-9"
inkscape:cy="8"
inkscape:cx="8"
inkscape:zoom="47.0625"
showgrid="false"
id="namedview12"
inkscape:window-height="1721"
inkscape:window-width="3200"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<g
style="opacity:1;fill-opacity:1"
transform="matrix(1.1,0,0,1.1,-0.8,-0.8)"
id="cross">
<g
style="fill-opacity:1"
id="g4">
<line
style="fill-opacity:1"
id="line2"
y2="14"
x2="2"
y1="2"
x1="14"
stroke-miterlimit="10"
stroke-linecap="round"
stroke-width="3"
stroke="#ed6b21"
fill="none" />
</g>
<g
style="fill-opacity:1"
id="g8">
<line
style="fill-opacity:1"
id="line6"
y2="14"
x2="14"
y1="2"
x1="2"
stroke-miterlimit="10"
stroke-linecap="round"
stroke-width="3"
stroke="#ed6b21"
fill="none" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="timer_dot.svg"
xml:space="preserve"
enable-background="new 0 0 16 16"
viewBox="0 0 16 16"
y="0px"
x="0px"
id="Layer_1"
version="1.0"><metadata
id="metadata11"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs9"><linearGradient
id="linearGradient830"
inkscape:collect="always"><stop
id="stop826"
offset="0"
style="stop-color:#000000;stop-opacity:1;" /><stop
id="stop828"
offset="1"
style="stop-color:#000000;stop-opacity:0;" /></linearGradient><radialGradient
gradientUnits="userSpaceOnUse"
r="3.5"
fy="8"
fx="8"
cy="8"
cx="8"
id="radialGradient832"
xlink:href="#linearGradient830"
inkscape:collect="always" /></defs><sodipodi:namedview
inkscape:document-rotation="0"
inkscape:current-layer="Layer_1"
inkscape:window-maximized="1"
inkscape:window-y="-11"
inkscape:window-x="-11"
inkscape:cy="6.66147"
inkscape:cx="7.0304602"
inkscape:zoom="83.4386"
showgrid="false"
id="namedview7"
inkscape:window-height="2066"
inkscape:window-width="3840"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<g
transform="matrix(0.7,0,0,0.7,2.4,2.4)"
style="fill:#bf6637;fill-opacity:1;stroke:none;stroke-opacity:1"
id="g4">
<circle
style="fill:#bf6637;fill-opacity:1;stroke:none;stroke-opacity:1"
id="circle2"
r="3"
cy="8"
cx="8" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 16 16"
enable-background="new 0 0 16 16"
xml:space="preserve"
sodipodi:docname="timer_dot_empty.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
id="metadata11"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs9"><linearGradient
inkscape:collect="always"
id="linearGradient830"><stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop826" /><stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop828" /></linearGradient><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient830"
id="radialGradient832"
cx="8"
cy="8"
fx="8"
fy="8"
r="3.5"
gradientUnits="userSpaceOnUse" /></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="3840"
inkscape:window-height="2066"
id="namedview7"
showgrid="false"
inkscape:zoom="83.4386"
inkscape:cx="7.0304602"
inkscape:cy="6.66147"
inkscape:window-x="-11"
inkscape:window-y="-11"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<g
id="g4"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.7,0,0,0.7,2.4,2.4)">
<circle
cx="8"
cy="8"
r="3"
id="circle2"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -113,7 +113,12 @@ namespace ImGui
const char PrinterSlaIconMarker = 0x6; const char PrinterSlaIconMarker = 0x6;
const char FilamentIconMarker = 0x7; const char FilamentIconMarker = 0x7;
const char MaterialIconMarker = 0x8; const char MaterialIconMarker = 0x8;
const char CloseIconMarker = 0xB;
const char CloseIconHoverMarker = 0xC;
const char TimerDotMarker = 0xE;
const char TimerDotEmptyMarker = 0xF;
const char WarningMarker = 0x10;
const char ErrorMarker = 0x11;
// void MyFunction(const char* name, const MyMatrix44& v); // void MyFunction(const char* name, const MyMatrix44& v);
} }

View File

@ -686,6 +686,7 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) { return oi1.print_z < oi2.print_z; }); std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) { return oi1.print_z < oi2.print_z; });
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print; std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print;
// Merge numerically very close Z values. // Merge numerically very close Z values.
for (size_t i = 0; i < ordering.size();) { for (size_t i = 0; i < ordering.size();) {
// Find the last layer with roughly the same print_z. // Find the last layer with roughly the same print_z.

View File

@ -163,6 +163,8 @@ set(SLIC3R_GUI_SOURCES
GUI/InstanceCheck.hpp GUI/InstanceCheck.hpp
GUI/Search.cpp GUI/Search.cpp
GUI/Search.hpp GUI/Search.hpp
GUI/NotificationManager.cpp
GUI/NotificationManager.hpp
Utils/Http.cpp Utils/Http.cpp
Utils/Http.hpp Utils/Http.hpp
Utils/FixModelByWin10.cpp Utils/FixModelByWin10.cpp

View File

@ -89,11 +89,13 @@ void BackgroundSlicingProcess::process_fff()
{ {
assert(m_print == m_fff_print); assert(m_print == m_fff_print);
m_print->process(); m_print->process();
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id)); wxCommandEvent evt(m_event_slicing_completed_id);
evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psBrim).timestamp));
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb); m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb);
if (this->set_step_started(bspsGCodeFinalize)) { if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) { if (! m_export_path.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
//FIXME localize the messages //FIXME localize the messages
// Perform the final post-processing of the export path by applying the print statistics over the file name. // Perform the final post-processing of the export path by applying the print statistics over the file name.
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path); std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
@ -124,6 +126,7 @@ void BackgroundSlicingProcess::process_fff()
run_post_process_scripts(export_path, m_fff_print->config()); run_post_process_scripts(export_path, m_fff_print->config());
m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str()); m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
} else if (! m_upload_job.empty()) { } else if (! m_upload_job.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
prepare_upload(); prepare_upload();
} else { } else {
m_print->set_status(100, _utf8(L("Slicing complete"))); m_print->set_status(100, _utf8(L("Slicing complete")));
@ -149,6 +152,8 @@ void BackgroundSlicingProcess::process_sla()
m_print->process(); m_print->process();
if (this->set_step_started(bspsGCodeFinalize)) { if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) { if (! m_export_path.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path); const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
Zipper zipper(export_path); Zipper zipper(export_path);
@ -170,6 +175,7 @@ void BackgroundSlicingProcess::process_sla()
m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str()); m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str());
} else if (! m_upload_job.empty()) { } else if (! m_upload_job.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
prepare_upload(); prepare_upload();
} else { } else {
m_print->set_status(100, _utf8(L("Slicing complete"))); m_print->set_status(100, _utf8(L("Slicing complete")));

View File

@ -60,6 +60,10 @@ public:
// The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code export is finished. // The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code export is finished.
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed. // The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
void set_finished_event(int event_id) { m_event_finished_id = event_id; } void set_finished_event(int event_id) { m_event_finished_id = event_id; }
// The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code is being exported to
// specified path or uploaded.
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
void set_export_began_event(int event_id) { m_event_export_began_id = event_id; }
// Activate either m_fff_print or m_sla_print. // Activate either m_fff_print or m_sla_print.
// Return true if changed. // Return true if changed.
@ -190,6 +194,9 @@ private:
int m_event_slicing_completed_id = 0; int m_event_slicing_completed_id = 0;
// wxWidgets command ID to be sent to the plater to inform that the task finished. // wxWidgets command ID to be sent to the plater to inform that the task finished.
int m_event_finished_id = 0; int m_event_finished_id = 0;
// wxWidgets command ID to be sent to the plater to inform that the G-code is being exported.
int m_event_export_began_id = 0;
}; };
}; // namespace Slic3r }; // namespace Slic3r

View File

@ -31,6 +31,7 @@
#include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectManipulation.hpp"
#include "Mouse3DController.hpp" #include "Mouse3DController.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include "NotificationManager.hpp"
#if ENABLE_RETINA_GL #if ENABLE_RETINA_GL
#include "slic3r/Utils/RetinaHelper.hpp" #include "slic3r/Utils/RetinaHelper.hpp"
@ -651,19 +652,45 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
m_warnings.emplace_back(warning); m_warnings.emplace_back(warning);
std::sort(m_warnings.begin(), m_warnings.end()); std::sort(m_warnings.begin(), m_warnings.end());
std::string text;
switch (warning) {
case ObjectOutside: text = L("An object outside the print area was detected."); break;
case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break;
case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break;
case SomethingNotShown: text = L("Some objects are not visible."); break;
case ObjectClashed: wxGetApp().plater()->get_notification_manager()->push_plater_error_notification(L("An object outside the print area was detected.\n"
"Resolve the current problem to continue slicing."),
*(wxGetApp().plater()->get_current_canvas3D()));
break;
}
if (!text.empty())
wxGetApp().plater()->get_notification_manager()->push_plater_warning_notification(text, *(wxGetApp().plater()->get_current_canvas3D()));
} }
else { else {
if (it == m_warnings.end()) // deactivating something that is not active is an easy task if (it == m_warnings.end()) // deactivating something that is not active is an easy task
return; return;
m_warnings.erase(it); m_warnings.erase(it);
if (m_warnings.empty()) { // nothing remains to be shown
std::string text;
switch (warning) {
case ObjectOutside: text = L("An object outside the print area was detected."); break;
case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break;
case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break;
case SomethingNotShown: text = L("Some objects are not visibl.e"); break;
case ObjectClashed: wxGetApp().plater()->get_notification_manager()->close_plater_error_notification(); break;
}
if (!text.empty())
wxGetApp().plater()->get_notification_manager()->close_plater_warning_notification(text);
/*if (m_warnings.empty()) { // nothing remains to be shown
reset(); reset();
m_msg_text = "";// save information for rescaling m_msg_text = "";// save information for rescaling
return; return;
} }*/
} }
/*
// Look at the end of our vector and generate proper texture. // Look at the end of our vector and generate proper texture.
std::string text; std::string text;
bool red_colored = false; bool red_colored = false;
@ -685,6 +712,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
// save information for rescaling // save information for rescaling
m_msg_text = text; m_msg_text = text;
m_is_colored_red = red_colored; m_is_colored_red = red_colored;
*/
} }
@ -2074,6 +2102,8 @@ void GLCanvas3D::render()
std::string tooltip; std::string tooltip;
// Negative coordinate means out of the window, likely because the window was deactivated. // Negative coordinate means out of the window, likely because the window was deactivated.
// In that case the tooltip should be hidden. // In that case the tooltip should be hidden.
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.)
@ -2103,6 +2133,8 @@ void GLCanvas3D::render()
m_tooltip.render(m_mouse.position, *this); m_tooltip.render(m_mouse.position, *this);
wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this);
wxGetApp().plater()->get_notification_manager()->render_notifications(*this);
wxGetApp().imgui()->render(); wxGetApp().imgui()->render();
@ -3418,6 +3450,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS #ifdef SLIC3R_DEBUG_MOUSE_EVENTS
printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str()); printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ #endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
m_dirty = true;
// do not return if dragging or tooltip not empty to allow for tooltip update // do not return if dragging or tooltip not empty to allow for tooltip update
if (!m_mouse.dragging && m_tooltip.is_empty()) if (!m_mouse.dragging && m_tooltip.is_empty())
return; return;
@ -3811,7 +3844,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_gizmos.reset_all_states(); m_gizmos.reset_all_states();
// Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over. // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over.
if (m_picking_enabled) //if (m_picking_enabled)
m_dirty = true; m_dirty = true;
} }
else else

View File

@ -54,6 +54,7 @@
#include "Mouse3DController.hpp" #include "Mouse3DController.hpp"
#include "RemovableDriveManager.hpp" #include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp" #include "InstanceCheck.hpp"
#include "NotificationManager.hpp"
#ifdef __WXMSW__ #ifdef __WXMSW__
#include <dbt.h> #include <dbt.h>
@ -384,7 +385,7 @@ bool GUI_App::on_init_inner()
// supplied as argument to --datadir; in that case we should still run the wizard // supplied as argument to --datadir; in that case we should still run the wizard
preset_bundle->setup_directories(); preset_bundle->setup_directories();
#ifdef __WXMSW__ #ifdef __WXMSW__
associate_3mf_files(); associate_3mf_files();
#endif // __WXMSW__ #endif // __WXMSW__
@ -392,6 +393,11 @@ bool GUI_App::on_init_inner()
Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) { Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) {
app_config->set("version_online", into_u8(evt.GetString())); app_config->set("version_online", into_u8(evt.GetString()));
app_config->save(); app_config->save();
if(this->plater_ != nullptr) {
if (*Semver::parse(SLIC3R_VERSION) < * Semver::parse(into_u8(evt.GetString()))) {
this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAviable, *(this->plater_->get_current_canvas3D()));
}
}
}); });
// initialize label colors and fonts // initialize label colors and fonts
@ -1439,7 +1445,7 @@ void GUI_App::check_updates(const bool verbose)
PresetUpdater::UpdateResult updater_result; PresetUpdater::UpdateResult updater_result;
try { try {
updater_result = preset_updater->config_update(app_config->orig_version()); updater_result = preset_updater->config_update(app_config->orig_version(), verbose);
if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
mainframe->Close(); mainframe->Close();
} }

View File

@ -194,12 +194,15 @@ public:
Plater* plater(); Plater* plater();
Model& model(); Model& model();
AppConfig* app_config{ nullptr }; AppConfig* app_config{ nullptr };
PresetBundle* preset_bundle{ nullptr }; PresetBundle* preset_bundle{ nullptr };
PresetUpdater* preset_updater{ nullptr }; PresetUpdater* preset_updater{ nullptr };
MainFrame* mainframe{ nullptr }; MainFrame* mainframe{ nullptr };
Plater* plater_{ nullptr }; Plater* plater_{ nullptr };
PresetUpdater* get_preset_updater() { return preset_updater; }
wxNotebook* tab_panel() const ; wxNotebook* tab_panel() const ;
int extruders_cnt() const; int extruders_cnt() const;
int extruders_edited_cnt() const; int extruders_edited_cnt() const;

View File

@ -37,11 +37,17 @@ namespace GUI {
static const std::map<const char, std::string> font_icons = { static const std::map<const char, std::string> font_icons = {
{ImGui::PrintIconMarker , "cog" }, {ImGui::PrintIconMarker , "cog" },
{ImGui::PrinterIconMarker , "printer" }, {ImGui::PrinterIconMarker , "printer" },
{ImGui::PrinterSlaIconMarker, "sla_printer"}, {ImGui::PrinterSlaIconMarker, "sla_printer" },
{ImGui::FilamentIconMarker , "spool" }, {ImGui::FilamentIconMarker , "spool" },
{ImGui::MaterialIconMarker , "resin" } {ImGui::MaterialIconMarker , "resin" },
{ImGui::CloseIconMarker , "cross" },
{ImGui::CloseIconHoverMarker, "cross_focus_large" },
{ImGui::TimerDotMarker , "timer_dot" },
{ImGui::TimerDotEmptyMarker , "timer_dot_empty" },
{ImGui::WarningMarker , "flag_green" },
{ImGui::ErrorMarker , "flag_red" }
}; };
ImGuiWrapper::ImGuiWrapper() ImGuiWrapper::ImGuiWrapper()
@ -265,6 +271,11 @@ void ImGuiWrapper::set_next_window_bg_alpha(float alpha)
ImGui::SetNextWindowBgAlpha(alpha); ImGui::SetNextWindowBgAlpha(alpha);
} }
void ImGuiWrapper::set_next_window_size(float x, float y, ImGuiCond cond)
{
ImGui::SetNextWindowSize(ImVec2(x, y), cond);
}
bool ImGuiWrapper::begin(const std::string &name, int flags) bool ImGuiWrapper::begin(const std::string &name, int flags)
{ {
return ImGui::Begin(name.c_str(), nullptr, (ImGuiWindowFlags)flags); return ImGui::Begin(name.c_str(), nullptr, (ImGuiWindowFlags)flags);
@ -296,12 +307,23 @@ bool ImGuiWrapper::button(const wxString &label)
return ImGui::Button(label_utf8.c_str()); return ImGui::Button(label_utf8.c_str());
} }
bool ImGuiWrapper::button(const wxString& label, float width, float height)
{
auto label_utf8 = into_u8(label);
return ImGui::Button(label_utf8.c_str(), ImVec2(width, height));
}
bool ImGuiWrapper::radio_button(const wxString &label, bool active) bool ImGuiWrapper::radio_button(const wxString &label, bool active)
{ {
auto label_utf8 = into_u8(label); auto label_utf8 = into_u8(label);
return ImGui::RadioButton(label_utf8.c_str(), active); return ImGui::RadioButton(label_utf8.c_str(), active);
} }
bool ImGuiWrapper::image_button()
{
return false;
}
bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format)
{ {
return ImGui::InputDouble(label.c_str(), const_cast<double*>(&value), 0.0f, 0.0f, format.c_str()); return ImGui::InputDouble(label.c_str(), const_cast<double*>(&value), 0.0f, 0.0f, format.c_str());

View File

@ -57,6 +57,7 @@ public:
void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f);
void set_next_window_bg_alpha(float alpha); void set_next_window_bg_alpha(float alpha);
void set_next_window_size(float x, float y, ImGuiCond cond);
bool begin(const std::string &name, int flags = 0); bool begin(const std::string &name, int flags = 0);
bool begin(const wxString &name, int flags = 0); bool begin(const wxString &name, int flags = 0);
@ -65,7 +66,9 @@ public:
void end(); void end();
bool button(const wxString &label); bool button(const wxString &label);
bool button(const wxString& label, float width, float height);
bool radio_button(const wxString &label, bool active); bool radio_button(const wxString &label, bool active);
bool image_button();
bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f"); bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f");
bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f"); bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f");
bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f"); bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f");

View File

@ -7,6 +7,7 @@
#include "AppConfig.hpp" #include "AppConfig.hpp"
#include "GLCanvas3D.hpp" #include "GLCanvas3D.hpp"
#include "Plater.hpp" #include "Plater.hpp"
#include "NotificationManager.hpp"
#include <wx/glcanvas.h> #include <wx/glcanvas.h>
@ -403,6 +404,8 @@ void Mouse3DController::disconnected()
m_params_by_device[m_device_str] = m_params_ui; m_params_by_device[m_device_str] = m_params_ui;
m_device_str.clear(); m_device_str.clear();
m_connected = false; m_connected = false;
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::Mouse3dDisconnected, *(wxGetApp().plater()->get_current_canvas3D()));
wxGetApp().plater()->CallAfter([]() { wxGetApp().plater()->CallAfter([]() {
Plater *plater = wxGetApp().plater(); Plater *plater = wxGetApp().plater();
if (plater != nullptr) { if (plater != nullptr) {

View File

@ -0,0 +1,918 @@
#include "NotificationManager.hpp"
#include "GUI_App.hpp"
#include "Plater.hpp"
#include "GLCanvas3D.hpp"
#include "ImGuiWrapper.hpp"
#include "wxExtensions.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <wx/glcanvas.h>
#include <iostream>
#define NOTIFICATION_MAX_MOVE 3.0f
#define GAP_WIDTH 10.0f
#define SPACE_RIGHT_PANEL 10.0f
namespace Slic3r {
namespace GUI {
wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent);
wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent);
namespace Notifications_Internal{
void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity)
{
if (fading_out)
ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity));
else
ImGui::PushStyleColor(idx, col);
}
}
//ScalableBitmap bmp_icon;
//------PopNotification--------
NotificationManager::PopNotification::PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler) :
m_data (n)
, m_id (id)
, m_remaining_time (n.duration)
, m_last_remaining_time (n.duration)
, m_counting_down (n.duration != 0)
, m_text1 (n.text1)
, m_hypertext (n.hypertext)
, m_text2 (n.text2)
, m_evt_handler (evt_handler)
{
init();
}
NotificationManager::PopNotification::~PopNotification()
{
}
NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y)
{
if (m_finished)
return RenderResult::Finished;
if (m_close_pending) {
// request of extra frame will be done in caller function by ret val ClosePending
m_finished = true;
return RenderResult::ClosePending;
}
if (m_hidden) {
m_top_y = initial_y - GAP_WIDTH;
return RenderResult::Static;
}
RenderResult ret_val = m_counting_down ? RenderResult::Countdown : RenderResult::Static;
Size cnv_size = canvas.get_canvas_size();
ImGuiWrapper& imgui = *wxGetApp().imgui();
bool shown = true;
std::string name;
ImVec2 mouse_pos = ImGui::GetMousePos();
if (m_line_height != ImGui::CalcTextSize("A").y)
init();
set_next_window_size(imgui);
//top y of window
m_top_y = initial_y + m_window_height;
//top right position
ImVec2 win_pos(1.0f * (float)cnv_size.get_width() - SPACE_RIGHT_PANEL, 1.0f * (float)cnv_size.get_height() - m_top_y);
imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f);
imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always);
//find if hovered
if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y&& mouse_pos.y < win_pos.y + m_window_height)
{
ImGui::SetNextWindowFocus();
ret_val = RenderResult::Hovered;
//reset fading
m_fading_out = false;
m_current_fade_opacity = 1.f;
m_remaining_time = m_data.duration;
m_countdown_frame = 0;
}
if (m_counting_down && m_remaining_time < 0)
m_close_pending = true;
if (m_close_pending) {
// request of extra frame will be done in caller function by ret val ClosePending
m_finished = true;
return RenderResult::ClosePending;
}
// color change based on fading out
bool fading_pop = false;
if (m_fading_out) {
if (!m_paused)
m_current_fade_opacity -= 1.f / ((m_fading_time + 1.f) * 60.f);
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
fading_pop = true;
}
// background color
if (m_is_gray) {
ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f);
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
} else if (m_data.level == NotificationLevel::ErrorNotification) {
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
backcolor.x += 0.3f;
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
} else if (m_data.level == NotificationLevel::WarningNotification) {
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
backcolor.x += 0.3f;
backcolor.y += 0.15f;
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
}
//name of window - probably indentifies window and is shown so last_end add whitespaces according to id
for (size_t i = 0; i < m_id; i++)
name += " ";
if (imgui.begin(name, &shown, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar )) {
if (shown) {
ImVec2 win_size = ImGui::GetWindowSize();
//FIXME: dont forget to us this for texts
//GUI::format(_utf8(L()));
/*
//countdown numbers
ImGui::SetCursorPosX(15);
ImGui::SetCursorPosY(15);
imgui.text(std::to_string(m_remaining_time).c_str());
*/
if(m_counting_down)
render_countdown(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
render_left_sign(imgui);
render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
if (m_multiline && m_lines_count > 3)
render_minimize_button(imgui, win_pos.x, win_pos.y);
} else {
// the user clicked on the [X] button ( ImGuiWindowFlags_NoTitleBar means theres no [X] button)
m_close_pending = true;
canvas.set_as_dirty();
}
}
imgui.end();
if (fading_pop) {
ImGui::PopStyleColor();
ImGui::PopStyleColor();
}
if (m_is_gray)
ImGui::PopStyleColor();
else if (m_data.level == NotificationLevel::ErrorNotification)
ImGui::PopStyleColor();
else if (m_data.level == NotificationLevel::WarningNotification)
ImGui::PopStyleColor();
return ret_val;
}
void NotificationManager::PopNotification::init()
{
std::string text = m_text1 + " " + m_hypertext;
int last_end = 0;
m_lines_count = 0;
//determine line width
m_line_height = ImGui::CalcTextSize("A").y;
m_left_indentation = m_line_height;
if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) {
std::string text;
text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker);
float picture_width = ImGui::CalcTextSize(text.c_str()).x;
m_left_indentation = picture_width + m_line_height / 2;
}
m_window_width_offset = m_left_indentation + m_line_height * 2;
m_window_width = m_line_height * 25;
// count lines
m_endlines.clear();
while (last_end < text.length() - 1)
{
int next_hard_end = text.find_first_of('\n', last_end);
if (next_hard_end > 0 && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) {
//next line is ended by '/n'
m_endlines.push_back(next_hard_end);
last_end = next_hard_end + 1;
}
else {
// find next suitable endline
if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - 3.5f * m_line_height) {// m_window_width_offset) {
// more than one line till end
int next_space = text.find_first_of(' ', last_end);
if (next_space > 0) {
int next_space_candidate = text.find_first_of(' ', next_space + 1);
while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) {
next_space = next_space_candidate;
next_space_candidate = text.find_first_of(' ', next_space + 1);
}
m_endlines.push_back(next_space);
last_end = next_space + 1;
}
}
else {
m_endlines.push_back(text.length());
last_end = text.length();
}
}
m_lines_count++;
}
}
void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui)
{
if (m_multiline) {
m_window_height = m_lines_count * m_line_height;
}else
{
m_window_height = 2 * m_line_height;
}
m_window_height += 1 * m_line_height; // top and bottom
}
void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
ImVec2 win_size(win_size_x, win_size_y);
ImVec2 win_pos(win_pos_x, win_pos_y);
float x_offset = m_left_indentation;
std::string fulltext = m_text1 + m_hypertext; //+ m_text2;
ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str());
// text posistions are calculated by lines count
// large texts has "more" button or are displayed whole
// smaller texts are divided as one liners and two liners
if (m_lines_count > 2) {
if (m_multiline) {
int last_end = 0;
float starting_y = m_line_height/2;//10;
float shift_y = m_line_height;// -m_line_height / 20;
for (size_t i = 0; i < m_lines_count; i++) {
std::string line = m_text1.substr(last_end , m_endlines[i] - last_end);
last_end = m_endlines[i] + 1;
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(starting_y + i * shift_y);
imgui.text(line.c_str());
}
//hyperlink text
if (!m_hypertext.empty())
{
render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(m_endlines[m_lines_count - 2] + 1, m_endlines[m_lines_count - 1] - m_endlines[m_lines_count - 2] - 1).c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext);
}
} else {
// line1
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
// line2
std::string line = m_text1.substr(m_endlines[0] + 1, m_endlines[1] - m_endlines[0] - 1);
if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x)
{
line = line.substr(0, line.length() - 6);
line += "..";
}else
line += " ";
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2);
imgui.text(line.c_str());
// "More" hypertext
render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true);
}
} else {
//text 1
float cursor_y = win_size.y / 2 - text_size.y / 2;
float cursor_x = x_offset;
if(m_lines_count > 1) {
// line1
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
// line2
std::string line = m_text1.substr(m_endlines[0] + 1);
cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2;
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(cursor_y);
imgui.text(line.c_str());
cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x;
} else {
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(cursor_y);
imgui.text(m_text1.c_str());
cursor_x = x_offset + ImGui::CalcTextSize(m_text1.c_str()).x;
}
//hyperlink text
if (!m_hypertext.empty())
{
render_hypertext(imgui, cursor_x + 4, cursor_y, m_hypertext);
}
//notification text 2
//text 2 is suposed to be after the hyperlink - currently it is not used
/*
if (!m_text2.empty())
{
ImVec2 part_size = ImGui::CalcTextSize(m_hypertext.c_str());
ImGui::SetCursorPosX(win_size.x / 2 + text_size.x / 2 - part_size.x + 8 - x_offset);
ImGui::SetCursorPosY(cursor_y);
imgui.text(m_text2.c_str());
}
*/
}
}
void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, const float text_x, const float text_y, const std::string text, bool more)
{
//invisible button
ImVec2 part_size = ImGui::CalcTextSize(text.c_str());
ImGui::SetCursorPosX(text_x -4);
ImGui::SetCursorPosY(text_y -5);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
if (imgui.button(" ", part_size.x + 6, part_size.y + 10))
{
if (more)
{
m_multiline = true;
set_next_window_size(imgui);
}
else {
on_text_click();
m_close_pending = true;
}
}
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
//hover color
ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
orange_color.y += 0.2f;
//text
Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity);
ImGui::SetCursorPosX(text_x);
ImGui::SetCursorPosY(text_y);
imgui.text(text.c_str());
ImGui::PopStyleColor();
//underline
ImVec2 lineEnd = ImGui::GetItemRectMax();
lineEnd.y -= 2;
ImVec2 lineStart = lineEnd;
lineStart.x = ImGui::GetItemRectMin().x;
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))));
}
void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
ImVec2 win_size(win_size_x, win_size_y);
ImVec2 win_pos(win_pos_x, win_pos_y);
ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
orange_color.w = 0.8f;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
//button - if part if treggered
std::string button_text;
button_text = ImGui::CloseIconMarker;
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y),
ImVec2(win_pos.x, win_pos.y + win_size.y - (m_multiline? 2 * m_line_height : 0)),
true))
{
button_text = ImGui::CloseIconHoverMarker;
}
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.25f);
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y/2);
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
{
m_close_pending = true;
}
//invisible large button
ImGui::SetCursorPosX(win_size.x - win_size.x / 10.f);
ImGui::SetCursorPosY(0);
if (imgui.button(" ", win_size.x / 10.f, win_size.y - (m_multiline ? 2 * m_line_height : 0)))
{
m_close_pending = true;
}
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
}
void NotificationManager::PopNotification::render_countdown(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
/*
ImVec2 win_size(win_size_x, win_size_y);
ImVec2 win_pos(win_pos_x, win_pos_y);
//countdown dots
std::string dot_text;
dot_text = m_remaining_time <= (float)m_data.duration / 4 * 3 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
ImGui::SetCursorPosX(win_size.x - m_line_height);
//ImGui::SetCursorPosY(win_size.y / 2 - 24);
ImGui::SetCursorPosY(0);
imgui.text(dot_text.c_str());
dot_text = m_remaining_time < m_data.duration / 2 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
ImGui::SetCursorPosX(win_size.x - m_line_height);
//ImGui::SetCursorPosY(win_size.y / 2 - 9);
ImGui::SetCursorPosY(win_size.y / 2 - m_line_height / 2);
imgui.text(dot_text.c_str());
dot_text = m_remaining_time <= m_data.duration / 4 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
ImGui::SetCursorPosX(win_size.x - m_line_height);
//ImGui::SetCursorPosY(win_size.y / 2 + 6);
ImGui::SetCursorPosY(win_size.y - m_line_height);
imgui.text(dot_text.c_str());
*/
if (!m_fading_out && m_remaining_time <= m_data.duration / 4) {
m_fading_out = true;
m_fading_time = m_remaining_time;
}
if (m_last_remaining_time != m_remaining_time) {
m_last_remaining_time = m_remaining_time;
m_countdown_frame = 0;
}
/*
//countdown line
ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
float invisible_length = ((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x);
invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame);
ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length, win_pos_y + win_size_y - 5);
ImVec2 lineStart = ImVec2(win_pos_x - win_size_x, win_pos_y + win_size_y - 5);
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.picture_width * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))), 2.f);
if (!m_paused)
m_countdown_frame++;
*/
}
void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui)
{
if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) {
std::string text;
text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker);
ImGui::SetCursorPosX(m_line_height / 3);
ImGui::SetCursorPosY(m_window_height / 2 - m_line_height / 2);
imgui.text(text.c_str());
}
}
void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y)
{
ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
orange_color.w = 0.8f;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
//button - if part if treggered
std::string button_text;
button_text = ImGui::CloseIconMarker;
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 10.f, win_pos_y + m_window_height - 2 * m_line_height + 1),
ImVec2(win_pos_x, win_pos_y + m_window_height),
true))
{
button_text = ImGui::CloseIconHoverMarker;
}
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
ImGui::SetCursorPosX(m_window_width - m_line_height * 2.25f);
ImGui::SetCursorPosY(m_window_height - button_size.y - 5);
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
{
m_multiline = false;
}
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
}
void NotificationManager::PopNotification::on_text_click()
{
switch (m_data.type) {
case NotificationType::ExportToRemovableFinished :
assert(m_evt_handler != nullptr);
if (m_evt_handler != nullptr)
wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED));
break;
case NotificationType::SlicingComplete :
//wxGetApp().plater()->export_gcode(false);
assert(m_evt_handler != nullptr);
if (m_evt_handler != nullptr)
wxPostEvent(m_evt_handler, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED));
break;
case NotificationType::PresetUpdateAviable :
//wxGetApp().plater()->export_gcode(false);
assert(m_evt_handler != nullptr);
if (m_evt_handler != nullptr)
wxPostEvent(m_evt_handler, PresetUpdateAviableClickedEvent(EVT_PRESET_UPDATE_AVIABLE_CLICKED));
break;
case NotificationType::NewAppAviable:
wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases");
break;
default:
break;
}
}
void NotificationManager::PopNotification::update(const NotificationData& n)
{
m_text1 = n.text1;
m_hypertext = n.hypertext;
m_text2 = n.text2;
init();
}
bool NotificationManager::PopNotification::compare_text(const std::string& text)
{
std::string t1(m_text1);
std::string t2(text);
t1.erase(std::remove_if(t1.begin(), t1.end(), ::isspace), t1.end());
t2.erase(std::remove_if(t2.begin(), t2.end(), ::isspace), t2.end());
if (t1.compare(t2) == 0)
return true;
return false;
}
NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool large) :
NotificationManager::PopNotification(n, id, evt_handler)
{
set_large(large);
}
void NotificationManager::SlicingCompleteLargeNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
if (!m_is_large)
PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
else {
ImVec2 win_size(win_size_x, win_size_y);
ImVec2 win_pos(win_pos_x, win_pos_y);
ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str());
float x_offset = m_left_indentation;
std::string fulltext = m_text1 + m_hypertext + m_text2;
ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str());
float cursor_y = win_size.y / 2 - text_size.y / 2;
if (m_has_print_info) {
x_offset = 20;
cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2;
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(cursor_y);
imgui.text(m_print_info.c_str());
cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2;
}
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(cursor_y);
imgui.text(m_text1.c_str());
render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext);
}
}
void NotificationManager::SlicingCompleteLargeNotification::set_print_info(std::string info)
{
m_print_info = info;
m_has_print_info = true;
if(m_is_large)
m_lines_count = 2;
}
void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l)
{
m_is_large = l;
m_counting_down = !l;
m_hypertext = l ? _u8L("Export G-Code.") : std::string();
m_hidden = !l;
}
//------NotificationManager--------
NotificationManager::NotificationManager(wxEvtHandler* evt_handler) :
m_evt_handler(evt_handler)
{
}
NotificationManager::~NotificationManager()
{
for (PopNotification* notification : m_pop_notifications)
{
delete notification;
}
}
void NotificationManager::push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp)
{
auto it = std::find_if(basic_notifications.begin(), basic_notifications.end(),
boost::bind(&NotificationData::type, _1) == type);
if (it != basic_notifications.end())
push_notification_data( *it, canvas, timestamp);
}
void NotificationManager::push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp)
{
push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, canvas, timestamp );
}
void NotificationManager::push_notification(const std::string& text, NotificationManager::NotificationLevel level, GLCanvas3D& canvas, int timestamp)
{
switch (level)
{
case Slic3r::GUI::NotificationManager::NotificationLevel::RegularNotification:
push_notification_data({ NotificationType::CustomNotification, level, 10, text }, canvas, timestamp);
break;
case Slic3r::GUI::NotificationManager::NotificationLevel::ErrorNotification:
push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp);
break;
case Slic3r::GUI::NotificationManager::NotificationLevel::ImportantNotification:
push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp);
break;
default:
break;
}
}
void NotificationManager::push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas)
{
set_all_slicing_errors_gray(false);
push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, canvas, 0);
close_notification_of_type(NotificationType::SlicingComplete);
}
void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step)
{
NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text };
NotificationManager::SlicingWarningNotification* notification = new NotificationManager::SlicingWarningNotification(data, m_next_id++, m_evt_handler);
notification->set_object_id(oid);
notification->set_warning_step(warning_step);
if
(push_notification_data(notification, canvas, 0)) {
notification->set_gray(gray);
}
else {
delete notification;
}
}
void NotificationManager::push_plater_error_notification(const std::string& text, GLCanvas3D& canvas)
{
push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, canvas, 0);
}
void NotificationManager::push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas)
{
push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }, canvas, 0);
}
void NotificationManager::close_plater_error_notification()
{
for (PopNotification* notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::PlaterError) {
notification->close();
}
}
}
void NotificationManager::close_plater_warning_notification(const std::string& text)
{
for (PopNotification* notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) {
notification->close();
}
}
}
void NotificationManager::set_all_slicing_errors_gray(bool g)
{
for (PopNotification* notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::SlicingError) {
notification->set_gray(g);
}
}
}
void NotificationManager::set_all_slicing_warnings_gray(bool g)
{
for (PopNotification* notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::SlicingWarning) {
notification->set_gray(g);
}
}
}
void NotificationManager::set_slicing_warning_gray(const std::string& text, bool g)
{
for (PopNotification* notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::SlicingWarning && notification->compare_text(text)) {
notification->set_gray(g);
}
}
}
void NotificationManager::close_slicing_errors_and_warnings()
{
for (PopNotification* notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::SlicingError || notification->get_type() == NotificationType::SlicingWarning) {
notification->close();
}
}
}
void NotificationManager::push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large)
{
std::string hypertext;
int time = 10;
if(large)
{
hypertext = _u8L("Export G-Code.");
time = 0;
}
NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time, _u8L("Slicing finished."), hypertext };
NotificationManager::SlicingCompleteLargeNotification* notification = new NotificationManager::SlicingCompleteLargeNotification(data, m_next_id++, m_evt_handler, large);
if (push_notification_data(notification, canvas, timestamp)) {
} else {
delete notification;
}
}
void NotificationManager::set_slicing_complete_print_time(std::string info)
{
for (PopNotification* notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::SlicingComplete) {
dynamic_cast<SlicingCompleteLargeNotification*>(notification)->set_print_info(info);
break;
}
}
}
void NotificationManager::set_slicing_complete_large(bool large)
{
for (PopNotification* notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::SlicingComplete) {
dynamic_cast<SlicingCompleteLargeNotification*>(notification)->set_large(large);
break;
}
}
}
void NotificationManager::close_notification_of_type(const NotificationType type)
{
for (PopNotification* notification : m_pop_notifications) {
if (notification->get_type() == type) {
notification->close();
}
}
}
void NotificationManager::compare_warning_oids(const std::vector<size_t>& living_oids)
{
for (PopNotification* notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::SlicingWarning) {
auto w = dynamic_cast<SlicingWarningNotification*>(notification);
bool found = false;
for (size_t oid : living_oids) {
if (w->get_object_id() == oid) {
found = true;
break;
}
}
if (!found)
notification->close();
}
}
}
bool NotificationManager::push_notification_data(const NotificationData &notification_data, GLCanvas3D& canvas, int timestamp)
{
PopNotification* n = new PopNotification(notification_data, m_next_id++, m_evt_handler);
bool r = push_notification_data(n, canvas, timestamp);
if (!r)
delete n;
return r;
}
bool NotificationManager::push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp)
{
// if timestamped notif, push only new one
if (timestamp != 0) {
if (m_used_timestamps.find(timestamp) == m_used_timestamps.end()) {
m_used_timestamps.insert(timestamp);
} else {
return false;
}
}
if (!this->find_older(notification)) {
m_pop_notifications.emplace_back(notification);
canvas.request_extra_frame();
return true;
} else {
m_pop_notifications.back()->update(notification->get_data());
canvas.request_extra_frame();
return false;
}
}
void NotificationManager::render_notifications(GLCanvas3D& canvas)
{
float last_x = 0.0f;
float current_height = 0.0f;
bool request_next_frame = false;
bool render_main = false;
bool hovered = false;
sort_notifications();
// iterate thru notifications and render them / erease them
for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) {
if ((*it)->get_finished()) {
delete (*it);
it = m_pop_notifications.erase(it);
} else {
(*it)->set_paused(m_hovered);
PopNotification::RenderResult res = (*it)->render(canvas, last_x);
if (res != PopNotification::RenderResult::Finished) {
last_x = (*it)->get_top() + GAP_WIDTH;
current_height = std::max(current_height, (*it)->get_current_top());
render_main = true;
}
if (res == PopNotification::RenderResult::Countdown || res == PopNotification::RenderResult::ClosePending || res == PopNotification::RenderResult::Finished)
request_next_frame = true;
if (res == PopNotification::RenderResult::Hovered)
hovered = true;
++it;
}
}
m_hovered = hovered;
//actualizate timers and request frame if needed
wxWindow* p = dynamic_cast<wxWindow*> (wxGetApp().plater());
while (p->GetParent())
p = p->GetParent();
wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
if (!top_level_wnd->IsActive())
return;
if (!m_hovered && m_last_time < wxGetLocalTime())
{
if (wxGetLocalTime() - m_last_time == 1)
{
for(auto notification : m_pop_notifications)
{
notification->substract_remaining_time();
}
}
m_last_time = wxGetLocalTime();
}
if (request_next_frame)
canvas.request_extra_frame();
}
void NotificationManager::sort_notifications()
{
std::sort(m_pop_notifications.begin(), m_pop_notifications.end(), [](PopNotification* n1, PopNotification* n2) {
int n1l = (int)n1->get_data().level;
int n2l = (int)n2->get_data().level;
if (n1l == n2l && n1->get_is_gray() && !n2->get_is_gray())
return true;
return (n1l < n2l);
});
}
bool NotificationManager::find_older(NotificationManager::PopNotification* notification)
{
NotificationType type = notification->get_type();
std::string text = notification->get_data().text1;
for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) {
if((*it)->get_type() == type && !(*it)->get_finished()) {
if (type == NotificationType::CustomNotification || type == NotificationType::PlaterWarning) {
if (!(*it)->compare_text(text))
continue;
}else if (type == NotificationType::SlicingWarning) {
auto w1 = dynamic_cast<SlicingWarningNotification*>(notification);
auto w2 = dynamic_cast<SlicingWarningNotification*>(*it);
if (w1 != nullptr && w2 != nullptr) {
if (!(*it)->compare_text(text) || w1->get_object_id() != w2->get_object_id()) {
continue;
}
} else {
continue;
}
}
if (it != m_pop_notifications.end() - 1)
std::rotate(it, it + 1, m_pop_notifications.end());
return true;
}
}
return false;
}
void NotificationManager::dpi_changed()
{
}
}//namespace GUI
}//namespace Slic3r

View File

@ -0,0 +1,268 @@
#ifndef slic3r_GUI_NotificationManager_hpp_
#define slic3r_GUI_NotificationManager_hpp_
#include "Event.hpp"
#include "I18N.hpp"
#include <string>
#include <vector>
#include <deque>
#include <unordered_set>
namespace Slic3r {
namespace GUI {
using EjectDriveNotificationClickedEvent = SimpleEvent;
wxDECLARE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent);
using ExportGcodeNotificationClickedEvent = SimpleEvent;
wxDECLARE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
using PresetUpdateAviableClickedEvent = SimpleEvent;
wxDECLARE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent);
class GLCanvas3D;
class ImGuiWrapper;
enum class NotificationType
{
CustomNotification,
SlicingComplete,
SlicingNotPossible,
ExportToRemovableFinished,
Mouse3dDisconnected,
Mouse3dConnected,
NewPresetsAviable,
NewAppAviable,
PresetUpdateAviable,
LoadingFailed,
ValidateError, // currently not used - instead Slicing error is used for both slicing and validate errors
SlicingError,
SlicingWarning,
PlaterError,
PlaterWarning,
ApplyError
};
class NotificationManager
{
public:
enum class NotificationLevel : int
{
ErrorNotification = 4,
WarningNotification = 3,
ImportantNotification = 2,
RegularNotification = 1,
};
// duration 0 means not disapearing
struct NotificationData {
NotificationType type;
NotificationLevel level;
const int duration;
const std::string text1;
const std::string hypertext = std::string();
const std::string text2 = std::string();
};
//Pop notification - shows only once to user.
class PopNotification
{
public:
enum class RenderResult
{
Finished,
ClosePending,
Static,
Countdown,
Hovered
};
PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler);
virtual ~PopNotification();
RenderResult render(GLCanvas3D& canvas, const float& initial_y);
// close will dissapear notification on next render
void close() { m_close_pending = true; }
// data from newer notification of same type
void update(const NotificationData& n);
bool get_finished() const { return m_finished; }
// returns top after movement
float get_top() const { return m_top_y; }
//returns top in actual frame
float get_current_top() const { return m_top_y; }
const NotificationType get_type() const { return m_data.type; }
const NotificationData get_data() const { return m_data; }
const bool get_is_gray() const { return m_is_gray; }
// Call equals one second down
void substract_remaining_time() { m_remaining_time--; }
void set_gray(bool g) { m_is_gray = g; }
void set_paused(bool p) { m_paused = p; }
bool compare_text(const std::string& text);
protected:
// Call after every size change
void init();
// Calculetes correct size but not se it in imgui!
virtual void set_next_window_size(ImGuiWrapper& imgui);
virtual void render_text(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x , const float win_pos_y);
void render_close_button(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x , const float win_pos_y);
void render_countdown(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x , const float win_pos_y);
void render_hypertext(ImGuiWrapper& imgui,
const float text_x, const float text_y,
const std::string text,
bool more = false);
void render_left_sign(ImGuiWrapper& imgui);
void render_minimize_button(ImGuiWrapper& imgui,
const float win_pos_x, const float win_pos_y);
void on_text_click();
const NotificationData m_data;
int m_id;
// Main text
std::string m_text1;
// Clickable text
std::string m_hypertext;
// Aditional text after hypertext - currently not used
std::string m_text2;
// Countdown variables
long m_remaining_time;
bool m_counting_down;
long m_last_remaining_time;
bool m_paused{ false };
int m_countdown_frame{ 0 };
bool m_fading_out{ false };
// total time left when fading beggins
float m_fading_time{ 0.0f };
float m_current_fade_opacity{ 1.f };
// If hidden the notif is alive but not visible to user
bool m_hidden { false };
// m_finished = true - does not render, marked to delete
bool m_finished { false };
// Will go to m_finished next render
bool m_close_pending { false };
// variables to count positions correctly
float m_window_width_offset;
float m_left_indentation;
// Total size of notification window - varies based on monitor
float m_window_height { 56.0f };
float m_window_width { 450.0f };
//Distance from bottom of notifications to top of this notification
float m_top_y { 0.0f };
// Height of text
// Used as basic scaling unit!
float m_line_height;
std::vector<int> m_endlines;
// Gray are f.e. eorrors when its uknown if they are still valid
bool m_is_gray { false };
//if multiline = true, notification is showing all lines(>2)
bool m_multiline { false };
int m_lines_count{ 1 };
wxEvtHandler* m_evt_handler;
};
class SlicingCompleteLargeNotification : public PopNotification
{
public:
SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool largeds);
void set_large(bool l);
bool get_large() { return m_is_large; }
void set_print_info(std::string info);
protected:
virtual void render_text(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y)
override;
bool m_is_large;
bool m_has_print_info { false };
std::string m_print_info { std::string() };
};
class SlicingWarningNotification : public PopNotification
{
public:
SlicingWarningNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler) : PopNotification(n, id, evt_handler) {}
void set_object_id(size_t id) { object_id = id; }
const size_t get_object_id() { return object_id; }
void set_warning_step(int ws) { warning_step = ws; }
const int get_warning_step() { return warning_step; }
protected:
size_t object_id;
int warning_step;
};
NotificationManager(wxEvtHandler* evt_handler);
~NotificationManager();
// only type means one of basic_notification (see below)
void push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp = 0);
// only text means Undefined type
void push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp = 0);
void push_notification(const std::string& text, NotificationLevel level, GLCanvas3D& canvas, int timestamp = 0);
// creates Slicing Error notification with custom text
void push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas);
// creates Slicing Warning notification with custom text
void push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step);
// marks slicing errors as gray
void set_all_slicing_errors_gray(bool g);
// marks slicing warings as gray
void set_all_slicing_warnings_gray(bool g);
void set_slicing_warning_gray(const std::string& text, bool g);
// imidietly stops showing slicing errors
void close_slicing_errors_and_warnings();
void compare_warning_oids(const std::vector<size_t>& living_oids);
void push_plater_error_notification(const std::string& text, GLCanvas3D& canvas);
void push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas);
void close_plater_error_notification();
void close_plater_warning_notification(const std::string& text);
// creates special notification slicing complete
// if large = true prints printing time and export button
void push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large);
void set_slicing_complete_print_time(std::string info);
void set_slicing_complete_large(bool large);
// renders notifications in queue and deletes expired ones
void render_notifications(GLCanvas3D& canvas);
// finds and closes all notifications of given type
void close_notification_of_type(const NotificationType type);
void dpi_changed();
private:
//pushes notification into the queue of notifications that are rendered
//can be used to create custom notification
bool push_notification_data(const NotificationData& notification_data, GLCanvas3D& canvas, int timestamp);
bool push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp);
//finds older notification of same type and moves it to the end of queue. returns true if found
bool find_older(NotificationManager::PopNotification* notification);
void sort_notifications();
wxEvtHandler* m_evt_handler;
std::deque<PopNotification*> m_pop_notifications;
int m_next_id { 1 };
long m_last_time { 0 };
bool m_hovered { false };
//timestamps used for slining finished - notification could be gone so it needs to be stored here
std::unordered_set<int> m_used_timestamps;
//prepared (basic) notifications
const std::vector<NotificationData> basic_notifications = {
{NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")},
{NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0, _u8L("Exporting finished."), _u8L("Eject drive.") },
{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") },
{NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5, _u8L("3D Mouse connected.") },
{NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20, _u8L("New Presets are available."), _u8L("See here.") },
{NotificationType::PresetUpdateAviable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more.")},
{NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page.")},
//{NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },
//{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") },
//{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
};
};
}//namespace GUI
}//namespace Slic3r
#endif //slic3r_GUI_NotificationManager_hpp_

View File

@ -75,8 +75,10 @@
#include "../Utils/PrintHost.hpp" #include "../Utils/PrintHost.hpp"
#include "../Utils/FixModelByWin10.hpp" #include "../Utils/FixModelByWin10.hpp"
#include "../Utils/UndoRedo.hpp" #include "../Utils/UndoRedo.hpp"
#include "../Utils/PresetUpdater.hpp"
#include "RemovableDriveManager.hpp" #include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp" #include "InstanceCheck.hpp"
#include "NotificationManager.hpp"
#ifdef __APPLE__ #ifdef __APPLE__
#include "Gizmos/GLGizmosManager.hpp" #include "Gizmos/GLGizmosManager.hpp"
@ -102,6 +104,7 @@ wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent); wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent);
wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent); wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent);
wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent); wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent);
wxDEFINE_EVENT(EVT_EXPORT_BEGAN, wxCommandEvent);
// Sidebar widgets // Sidebar widgets
@ -716,7 +719,7 @@ struct Sidebar::priv
wxButton *btn_export_gcode; wxButton *btn_export_gcode;
wxButton *btn_reslice; wxButton *btn_reslice;
ScalableButton *btn_send_gcode; ScalableButton *btn_send_gcode;
ScalableButton *btn_remove_device; ScalableButton *btn_eject_device;
ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected) ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected)
bool is_collapsed {false}; bool is_collapsed {false};
@ -889,12 +892,12 @@ Sidebar::Sidebar(Plater *parent)
}; };
init_scalable_btn(&p->btn_send_gcode , "export_gcode", _L("Send to printer") + "\tCtrl+Shift+G"); init_scalable_btn(&p->btn_send_gcode , "export_gcode", _L("Send to printer") + "\tCtrl+Shift+G");
init_scalable_btn(&p->btn_remove_device, "eject_sd" , _L("Remove device") + "\tCtrl+T"); init_scalable_btn(&p->btn_eject_device, "eject_sd" , _L("Remove device") + "\tCtrl+T");
init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export to SD card / Flash drive") + "\tCtrl+U"); init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export to SD card / Flash drive") + "\tCtrl+U");
// regular buttons "Slice now" and "Export G-code" // regular buttons "Slice now" and "Export G-code"
const int scaled_height = p->btn_remove_device->GetBitmapHeight() + 4; const int scaled_height = p->btn_eject_device->GetBitmapHeight() + 4;
auto init_btn = [this](wxButton **btn, wxString label, const int button_height) { auto init_btn = [this](wxButton **btn, wxString label, const int button_height) {
*btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition, *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition,
wxSize(-1, button_height), wxBU_EXACTFIT); wxSize(-1, button_height), wxBU_EXACTFIT);
@ -912,7 +915,7 @@ Sidebar::Sidebar(Plater *parent)
complect_btns_sizer->Add(p->btn_export_gcode, 1, wxEXPAND); complect_btns_sizer->Add(p->btn_export_gcode, 1, wxEXPAND);
complect_btns_sizer->Add(p->btn_send_gcode); complect_btns_sizer->Add(p->btn_send_gcode);
complect_btns_sizer->Add(p->btn_export_gcode_removable); complect_btns_sizer->Add(p->btn_export_gcode_removable);
complect_btns_sizer->Add(p->btn_remove_device); complect_btns_sizer->Add(p->btn_eject_device);
btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, margin_5); btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, margin_5);
@ -935,7 +938,7 @@ Sidebar::Sidebar(Plater *parent)
p->plater->select_view_3D("Preview"); p->plater->select_view_3D("Preview");
}); });
p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); });
p->btn_remove_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); }); p->btn_eject_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); });
p->btn_export_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(true); }); p->btn_export_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(true); });
} }
@ -1083,9 +1086,9 @@ void Sidebar::msw_rescale()
p->object_info->msw_rescale(); p->object_info->msw_rescale();
p->btn_send_gcode->msw_rescale(); p->btn_send_gcode->msw_rescale();
p->btn_remove_device->msw_rescale(); p->btn_eject_device->msw_rescale();
p->btn_export_gcode_removable->msw_rescale(); p->btn_export_gcode_removable->msw_rescale();
const int scaled_height = p->btn_remove_device->GetBitmap().GetHeight() + 4; const int scaled_height = p->btn_eject_device->GetBitmap().GetHeight() + 4;
p->btn_export_gcode->SetMinSize(wxSize(-1, scaled_height)); p->btn_export_gcode->SetMinSize(wxSize(-1, scaled_height));
p->btn_reslice ->SetMinSize(wxSize(-1, scaled_height)); p->btn_reslice ->SetMinSize(wxSize(-1, scaled_height));
@ -1114,7 +1117,7 @@ void Sidebar::sys_color_changed()
// btn...->msw_rescale() updates icon on button, so use it // btn...->msw_rescale() updates icon on button, so use it
p->btn_send_gcode->msw_rescale(); p->btn_send_gcode->msw_rescale();
p->btn_remove_device->msw_rescale(); p->btn_eject_device->msw_rescale();
p->btn_export_gcode_removable->msw_rescale(); p->btn_export_gcode_removable->msw_rescale();
p->scrolled->Layout(); p->scrolled->Layout();
@ -1350,6 +1353,12 @@ void Sidebar::update_sliced_info_sizer()
new_label += format_wxstr("\n - %1%", _L("normal mode")); new_label += format_wxstr("\n - %1%", _L("normal mode"));
info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time); info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time);
fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text); fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text);
// uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate
//if (p->plater->is_sidebar_collapsed())
p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed());
p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time);
} }
if (ps.estimated_silent_print_time != "N/A") { if (ps.estimated_silent_print_time != "N/A") {
new_label += format_wxstr("\n - %1%", _L("stealth mode")); new_label += format_wxstr("\n - %1%", _L("stealth mode"));
@ -1385,15 +1394,16 @@ void Sidebar::enable_buttons(bool enable)
p->btn_reslice->Enable(enable); p->btn_reslice->Enable(enable);
p->btn_export_gcode->Enable(enable); p->btn_export_gcode->Enable(enable);
p->btn_send_gcode->Enable(enable); p->btn_send_gcode->Enable(enable);
p->btn_remove_device->Enable(enable); p->btn_eject_device->Enable(enable);
p->btn_export_gcode_removable->Enable(enable); p->btn_export_gcode_removable->Enable(enable);
} }
bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->Show(show); } bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->Show(show); }
bool Sidebar::show_export(bool show) const { return p->btn_export_gcode->Show(show); } bool Sidebar::show_export(bool show) const { return p->btn_export_gcode->Show(show); }
bool Sidebar::show_send(bool show) const { return p->btn_send_gcode->Show(show); } bool Sidebar::show_send(bool show) const { return p->btn_send_gcode->Show(show); }
bool Sidebar::show_disconnect(bool show) const { return p->btn_remove_device->Show(show); } bool Sidebar::show_export_removable(bool show) const { return p->btn_export_gcode_removable->Show(show); }
bool Sidebar::show_export_removable(bool show)const { return p->btn_export_gcode_removable->Show(show); } bool Sidebar::show_eject(bool show) const { return p->btn_eject_device->Show(show); }
bool Sidebar::get_eject_shown() const { return p->btn_eject_device->IsShown(); }
bool Sidebar::is_multifilament() bool Sidebar::is_multifilament()
{ {
@ -1591,6 +1601,7 @@ struct Plater::priv
GLToolbar view_toolbar; GLToolbar view_toolbar;
GLToolbar collapse_toolbar; GLToolbar collapse_toolbar;
Preview *preview; Preview *preview;
NotificationManager* notification_manager;
BackgroundSlicingProcess background_process; BackgroundSlicingProcess background_process;
bool suppressed_backround_processing_update { false }; bool suppressed_backround_processing_update { false };
@ -1775,7 +1786,17 @@ struct Plater::priv
void on_slicing_update(SlicingStatusEvent&); void on_slicing_update(SlicingStatusEvent&);
void on_slicing_completed(wxCommandEvent&); void on_slicing_completed(wxCommandEvent&);
void on_process_completed(wxCommandEvent&); void on_process_completed(wxCommandEvent&);
void on_export_began(wxCommandEvent&);
void on_layer_editing_toggled(bool enable); void on_layer_editing_toggled(bool enable);
void on_slicing_began();
void clear_warnings();
void add_warning(const Slic3r::PrintStateBase::Warning &warning, size_t oid);
void actualizate_warnings(const Model& model, size_t print_oid);
// Displays dialog window with list of warnings.
// Returns true if user clicks OK.
// Returns true if current_warnings vector is empty without showning the dialog
bool warnings_dialog();
void on_action_add(SimpleEvent&); void on_action_add(SimpleEvent&);
void on_action_split_objects(SimpleEvent&); void on_action_split_objects(SimpleEvent&);
@ -1826,7 +1847,7 @@ struct Plater::priv
// Flag indicating that the G-code export targets a removable device, therefore the show_action_buttons() needs to be called at any case when the background processing finishes. // Flag indicating that the G-code export targets a removable device, therefore the show_action_buttons() needs to be called at any case when the background processing finishes.
bool writing_to_removable_device = { false }; bool writing_to_removable_device = { false };
bool inside_snapshot_capture() { return m_prevent_snapshots != 0; } bool inside_snapshot_capture() { return m_prevent_snapshots != 0; }
bool process_completed_with_error { false };
private: private:
bool init_object_menu(); bool init_object_menu();
bool init_common_menu(wxMenu* menu, const bool is_part = false); bool init_common_menu(wxMenu* menu, const bool is_part = false);
@ -1854,6 +1875,11 @@ private:
* */ * */
std::string m_last_fff_printer_profile_name; std::string m_last_fff_printer_profile_name;
std::string m_last_sla_printer_profile_name; std::string m_last_sla_printer_profile_name;
// vector of all warnings generated by last slicing
std::vector<std::pair<Slic3r::PrintStateBase::Warning, size_t>> current_warnings;
bool show_warning_dialog { false };
}; };
const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase);
@ -1899,6 +1925,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
}); });
background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED); background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
background_process.set_finished_event(EVT_PROCESS_COMPLETED); background_process.set_finished_event(EVT_PROCESS_COMPLETED);
background_process.set_export_began_event(EVT_EXPORT_BEGAN);
// Default printer technology for default config. // Default printer technology for default config.
background_process.select_technology(this->printer_technology); background_process.select_technology(this->printer_technology);
// Register progress callback from the Print class to the Plater. // Register progress callback from the Print class to the Plater.
@ -2010,8 +2037,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); });
q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this); q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this);
q->Bind(EVT_EXPORT_BEGAN, &priv::on_export_began, this);
q->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); }); q->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); });
q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); }); q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); });
@ -2038,16 +2066,27 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
}); });
#endif /* _WIN32 */ #endif /* _WIN32 */
this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this](RemovableDriveEjectEvent &evt) { notification_manager = new NotificationManager(this->q);
this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); });
this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); });
this->q->Bind(EVT_PRESET_UPDATE_AVIABLE_CLICKED, [this](PresetUpdateAviableClickedEvent&) { wxGetApp().get_preset_updater()->on_update_notification_confirm(); });
this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this, q](RemovableDriveEjectEvent &evt) {
if (evt.data.second) { if (evt.data.second) {
this->show_action_buttons(this->ready_to_slice); this->show_action_buttons(this->ready_to_slice);
Slic3r::GUI::show_info(this->q, format_wxstr(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."), notification_manager->push_notification(format(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),evt.data.first.name, evt.data.first.path),
evt.data.first.name, evt.data.first.path)); NotificationManager::NotificationLevel::RegularNotification, *q->get_current_canvas3D());
} else } else {
Slic3r::GUI::show_info(this->q, format_wxstr(_L("Ejecting of device %s(%s) has failed."), notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path),
evt.data.first.name, evt.data.first.path)); NotificationManager::NotificationLevel::ErrorNotification, *q->get_current_canvas3D());
}
});
this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) {
this->show_action_buttons(this->ready_to_slice);
if (!this->sidebar->get_eject_shown()) {
notification_manager->close_notification_of_type(NotificationType::ExportToRemovableFinished);
}
}); });
this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this](RemovableDrivesChangedEvent &) { this->show_action_buttons(this->ready_to_slice); });
// Start the background thread and register this window as a target for update events. // Start the background thread and register this window as a target for update events.
wxGetApp().removable_drive_manager()->init(this->q); wxGetApp().removable_drive_manager()->init(this->q);
#ifdef _WIN32 #ifdef _WIN32
@ -2675,6 +2714,8 @@ void Plater::priv::reset()
{ {
Plater::TakeSnapshot snapshot(q, _L("Reset Project")); Plater::TakeSnapshot snapshot(q, _L("Reset Project"));
clear_warnings();
set_project_filename(wxEmptyString); set_project_filename(wxEmptyString);
// Prevent toolpaths preview from rendering while we modify the Print object // Prevent toolpaths preview from rendering while we modify the Print object
@ -2844,22 +2885,13 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
// The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors. // The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors.
std::string err = this->background_process.validate(); std::string err = this->background_process.validate();
if (err.empty()) { if (err.empty()) {
notification_manager->set_all_slicing_errors_gray(true);
if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled()) if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled())
return_state |= UPDATE_BACKGROUND_PROCESS_RESTART; return_state |= UPDATE_BACKGROUND_PROCESS_RESTART;
} else { } else {
// The print is not valid. // The print is not valid.
// Only show the error message immediately, if the top level parent of this window is active. // Show error as notification.
auto p = dynamic_cast<wxWindow*>(this->q); notification_manager->push_slicing_error_notification(err, *q->get_current_canvas3D());
while (p->GetParent())
p = p->GetParent();
auto *top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
if (! postpone_error_messages && top_level_wnd && top_level_wnd->IsActive()) {
// The error returned from the Print needs to be translated into the local language.
GUI::show_error(this->q, err);
} else {
// Show the error message once the main window gets activated.
this->delayed_error_message = err;
}
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
} }
} else if (! this->delayed_error_message.empty()) { } else if (! this->delayed_error_message.empty()) {
@ -2867,6 +2899,14 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
} }
//actualizate warnings
if (invalidated != Print::APPLY_STATUS_UNCHANGED) {
actualizate_warnings(this->q->model(), this->background_process.current_print()->id().id);
notification_manager->set_all_slicing_warnings_gray(true);
show_warning_dialog = false;
process_completed_with_error = false;
}
if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() && if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() &&
(return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) { (return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) {
// The background processing was killed and it will not be restarted. // The background processing was killed and it will not be restarted.
@ -2929,6 +2969,8 @@ bool Plater::priv::restart_background_process(unsigned int state)
this->statusbar()->set_status_text(_L("Cancelling")); this->statusbar()->set_status_text(_L("Cancelling"));
this->background_process.stop(); this->background_process.stop();
}); });
if (!show_warning_dialog)
on_slicing_began();
return true; return true;
} }
} }
@ -2955,6 +2997,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova
if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
return; return;
show_warning_dialog = true;
if (! output_path.empty()) { if (! output_path.empty()) {
background_process.schedule_export(output_path.string(), output_path_on_removable_media); background_process.schedule_export(output_path.string(), output_path_on_removable_media);
} else { } else {
@ -3433,11 +3476,20 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
state = print_object->step_state_with_warnings(static_cast<SLAPrintObjectStep>(warning_step)); state = print_object->step_state_with_warnings(static_cast<SLAPrintObjectStep>(warning_step));
} }
// Now process state.warnings. // Now process state.warnings.
for (auto const& warning : state.warnings) {
if (warning.current) {
notification_manager->push_slicing_warning_notification(warning.message, false, *q->get_current_canvas3D(), object_id.id, warning_step);
add_warning(warning, object_id.id);
}
}
} }
} }
void Plater::priv::on_slicing_completed(wxCommandEvent &) void Plater::priv::on_slicing_completed(wxCommandEvent & evt)
{ {
//notification_manager->push_notification(NotificationType::SlicingComplete, *q->get_current_canvas3D(), evt.GetInt());
notification_manager->push_slicing_complete_notification(*q->get_current_canvas3D(), evt.GetInt(), is_sidebar_collapsed());
switch (this->printer_technology) { switch (this->printer_technology) {
case ptFFF: case ptFFF:
this->update_fff_scene(); this->update_fff_scene();
@ -3450,8 +3502,63 @@ void Plater::priv::on_slicing_completed(wxCommandEvent &)
break; break;
default: break; default: break;
} }
}
}
void Plater::priv::on_export_began(wxCommandEvent& evt)
{
if (show_warning_dialog)
warnings_dialog();
}
void Plater::priv::on_slicing_began()
{
clear_warnings();
notification_manager->close_notification_of_type(NotificationType::SlicingComplete);
}
void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid)
{
for (auto const& it : current_warnings) {
if (warning.message_id == it.first.message_id) {
if (warning.message_id != 0 || (warning.message_id == 0 && warning.message == it.first.message))
return;
}
}
current_warnings.emplace_back(std::pair<Slic3r::PrintStateBase::Warning, size_t>(warning, oid));
}
void Plater::priv::actualizate_warnings(const Model& model, size_t print_oid)
{
std::vector<size_t> living_oids;
living_oids.push_back(model.id().id);
living_oids.push_back(print_oid);
for (auto it = model.objects.begin(); it != model.objects.end(); ++it) {
living_oids.push_back((*it)->id().id);
}
notification_manager->compare_warning_oids(living_oids);
}
void Plater::priv::clear_warnings()
{
notification_manager->close_slicing_errors_and_warnings();
this->current_warnings.clear();
}
bool Plater::priv::warnings_dialog()
{
if (current_warnings.empty())
return true;
std::string text = _u8L("There are active warnings concerning sliced models:\n");
bool empt = true;
for (auto const& it : current_warnings) {
int next_n = it.first.message.find_first_of('\n', 0);
text += "\n";
if (next_n != std::string::npos)
text += it.first.message.substr(0, next_n);
else
text += it.first.message;
}
//text += "\n\nDo you still wish to export?";
wxMessageDialog msg_wingow(this->q, text, wxString(SLIC3R_APP_NAME " ") + _L("generated warnings"), wxOK);
const auto res = msg_wingow.ShowModal();
return res == wxID_OK;
}
void Plater::priv::on_process_completed(wxCommandEvent &evt) void Plater::priv::on_process_completed(wxCommandEvent &evt)
{ {
// Stop the background task, wait until the thread goes into the "Idle" state. // Stop the background task, wait until the thread goes into the "Idle" state.
@ -3470,14 +3577,13 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
if (error) { if (error) {
wxString message = evt.GetString(); wxString message = evt.GetString();
if (message.IsEmpty()) if (message.IsEmpty())
message = _L("Export failed"); message = _L("Export failed.");
if (q->m_tracking_popup_menu) notification_manager->push_slicing_error_notification(boost::nowide::narrow(message), *q->get_current_canvas3D());
// We don't want to pop-up a message box when tracking a pop-up menu.
// We postpone the error message instead.
q->m_tracking_popup_menu_error_message = message;
else
show_error(q, message);
this->statusbar()->set_status_text(message); this->statusbar()->set_status_text(message);
const wxString invalid_str = _L("Invalid data");
for (auto btn : { ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport })
sidebar->set_btn_label(btn, invalid_str);
process_completed_with_error = true;
} }
if (canceled) if (canceled)
this->statusbar()->set_status_text(_L("Cancelled")); this->statusbar()->set_status_text(_L("Cancelled"));
@ -3503,18 +3609,21 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
default: break; default: break;
} }
if (canceled) { if (canceled) {
if (wxGetApp().get_mode() == comSimple) if (wxGetApp().get_mode() == comSimple)
sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now"); sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now");
show_action_buttons(true); show_action_buttons(true);
} }
else if (this->writing_to_removable_device || wxGetApp().get_mode() == comSimple) else if (wxGetApp().get_mode() == comSimple)
{ {
wxGetApp().removable_drive_manager()->set_exporting_finished(true);
show_action_buttons(false); show_action_buttons(false);
} }
this->writing_to_removable_device = false; else if (this->writing_to_removable_device)
{
show_action_buttons(false);
notification_manager->push_notification(NotificationType::ExportToRemovableFinished, *q->get_current_canvas3D());
}
this->writing_to_removable_device = false;
} }
void Plater::priv::on_layer_editing_toggled(bool enable) void Plater::priv::on_layer_editing_toggled(bool enable)
@ -4156,7 +4265,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const
sidebar->show_export(true) | sidebar->show_export(true) |
sidebar->show_send(send_gcode_shown) | sidebar->show_send(send_gcode_shown) |
sidebar->show_export_removable(removable_media_status.has_removable_drives) | sidebar->show_export_removable(removable_media_status.has_removable_drives) |
sidebar->show_disconnect(removable_media_status.has_eject)) sidebar->show_eject(removable_media_status.has_eject))
sidebar->Layout(); sidebar->Layout();
} }
else else
@ -4168,7 +4277,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const
sidebar->show_export(!ready_to_slice) | sidebar->show_export(!ready_to_slice) |
sidebar->show_send(send_gcode_shown && !ready_to_slice) | sidebar->show_send(send_gcode_shown && !ready_to_slice) |
sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives) | sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives) |
sidebar->show_disconnect(!ready_to_slice && removable_media_status.has_eject)) sidebar->show_eject(!ready_to_slice && removable_media_status.has_eject))
sidebar->Layout(); sidebar->Layout();
} }
} }
@ -4731,6 +4840,9 @@ void Plater::export_gcode(bool prefer_removable)
if (p->model.objects.empty()) if (p->model.objects.empty())
return; return;
if (p->process_completed_with_error)//here
return;
// If possible, remove accents from accented latin characters. // If possible, remove accents from accented latin characters.
// This function is useful for generating file names to be processed by legacy firmwares. // This function is useful for generating file names to be processed by legacy firmwares.
fs::path default_output_file; fs::path default_output_file;
@ -4990,7 +5102,6 @@ void Plater::export_toolpaths_to_obj() const
p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str()); p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str());
} }
void Plater::reslice() void Plater::reslice()
{ {
// Stop arrange and (or) optimize rotation tasks. // Stop arrange and (or) optimize rotation tasks.
@ -5676,6 +5787,16 @@ Mouse3DController& Plater::get_mouse3d_controller()
return p->mouse3d_controller; return p->mouse3d_controller;
} }
const NotificationManager* Plater::get_notification_manager() const
{
return p->notification_manager;
}
NotificationManager* Plater::get_notification_manager()
{
return p->notification_manager;
}
bool Plater::can_delete() const { return p->can_delete(); } bool Plater::can_delete() const { return p->can_delete(); }
bool Plater::can_delete_all() const { return p->can_delete_all(); } bool Plater::can_delete_all() const { return p->can_delete_all(); }
bool Plater::can_increase_instances() const { return p->can_increase_instances(); } bool Plater::can_increase_instances() const { return p->can_increase_instances(); }

View File

@ -47,6 +47,7 @@ class ObjectLayers;
class ObjectList; class ObjectList;
class GLCanvas3D; class GLCanvas3D;
class Mouse3DController; class Mouse3DController;
class NotificationManager;
struct Camera; struct Camera;
class Bed3D; class Bed3D;
class GLToolbar; class GLToolbar;
@ -130,8 +131,9 @@ public:
bool show_reslice(bool show) const; bool show_reslice(bool show) const;
bool show_export(bool show) const; bool show_export(bool show) const;
bool show_send(bool show) const; bool show_send(bool show) const;
bool show_disconnect(bool show)const; bool show_eject(bool show)const;
bool show_export_removable(bool show) const; bool show_export_removable(bool show) const;
bool get_eject_shown() const;
bool is_multifilament(); bool is_multifilament();
void update_mode(); void update_mode();
bool is_collapsed(); bool is_collapsed();
@ -338,6 +340,9 @@ public:
Mouse3DController& get_mouse3d_controller(); Mouse3DController& get_mouse3d_controller();
void set_bed_shape() const; void set_bed_shape() const;
const NotificationManager* get_notification_manager() const;
NotificationManager* get_notification_manager();
// ROII wrapper for suppressing the Undo / Redo snapshot to be taken. // ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
class SuppressSnapshots class SuppressSnapshots

View File

@ -27,6 +27,7 @@
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/format.hpp" #include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/Utils/Http.hpp" #include "slic3r/Utils/Http.hpp"
#include "slic3r/Config/Version.hpp" #include "slic3r/Config/Version.hpp"
#include "slic3r/Config/Snapshot.hpp" #include "slic3r/Config/Snapshot.hpp"
@ -154,6 +155,9 @@ struct PresetUpdater::priv
bool cancel; bool cancel;
std::thread thread; std::thread thread;
bool has_waiting_updates { false };
Updates waiting_updates;
priv(); priv();
void set_download_prefs(AppConfig *app_config); void set_download_prefs(AppConfig *app_config);
@ -165,6 +169,7 @@ struct PresetUpdater::priv
void check_install_indices() const; void check_install_indices() const;
Updates get_config_updates(const Semver& old_slic3r_version) const; Updates get_config_updates(const Semver& old_slic3r_version) const;
void perform_updates(Updates &&updates, bool snapshot = true) const; void perform_updates(Updates &&updates, bool snapshot = true) const;
void set_waiting_updates(Updates u);
}; };
PresetUpdater::priv::priv() PresetUpdater::priv::priv()
@ -326,7 +331,15 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors)
continue; continue;
} }
Slic3r::rename_file(idx_path_temp, idx_path); Slic3r::rename_file(idx_path_temp, idx_path);
index = std::move(new_index); //if we rename path we need to change it in Index object too or create the object again
//index = std::move(new_index);
try {
index.load(idx_path);
}
catch (const std::exception& /* err */) {
BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path, vendor.name);
continue;
}
if (cancel) if (cancel)
return; return;
} }
@ -632,6 +645,12 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
} }
} }
void PresetUpdater::priv::set_waiting_updates(Updates u)
{
waiting_updates = u;
has_waiting_updates = true;
}
PresetUpdater::PresetUpdater() : PresetUpdater::PresetUpdater() :
p(new priv()) p(new priv())
{} {}
@ -690,9 +709,9 @@ void PresetUpdater::slic3r_update_notify()
} }
} }
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3r_version) const PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const
{ {
if (! p->enabled_config_update) { return R_NOOP; } if (! p->enabled_config_update) { return R_NOOP; }
auto updates = p->get_config_updates(old_slic3r_version); auto updates = p->get_config_updates(old_slic3r_version);
if (updates.incompats.size() > 0) { if (updates.incompats.size() > 0) {
@ -779,30 +798,38 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3
} }
// regular update // regular update
BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", updates.updates.size()); if (no_notification) {
BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size());
std::vector<GUI::MsgUpdateConfig::Update> updates_msg; std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
for (const auto &update : updates.updates) { for (const auto& update : updates.updates) {
std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string(); std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string();
updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url)); updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
} }
GUI::MsgUpdateConfig dlg(updates_msg); GUI::MsgUpdateConfig dlg(updates_msg);
const auto res = dlg.ShowModal(); const auto res = dlg.ShowModal();
if (res == wxID_OK) { if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
p->perform_updates(std::move(updates)); p->perform_updates(std::move(updates));
// Reload global configuration // Reload global configuration
auto *app_config = GUI::wxGetApp().app_config; auto* app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config); GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets(); GUI::wxGetApp().load_current_presets();
return R_UPDATE_INSTALLED; return R_UPDATE_INSTALLED;
}
else {
BOOST_LOG_TRIVIAL(info) << "User refused the update";
return R_UPDATE_REJECT;
}
} else { } else {
BOOST_LOG_TRIVIAL(info) << "User refused the update"; p->set_waiting_updates(updates);
return R_UPDATE_REJECT; GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAviable, *(GUI::wxGetApp().plater()->get_current_canvas3D()));
} }
// MsgUpdateConfig will show after the notificaation is clicked
} else { } else {
BOOST_LOG_TRIVIAL(info) << "No configuration updates available."; BOOST_LOG_TRIVIAL(info) << "No configuration updates available.";
} }
@ -825,5 +852,37 @@ void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool
p->perform_updates(std::move(updates), snapshot); p->perform_updates(std::move(updates), snapshot);
} }
void PresetUpdater::on_update_notification_confirm()
{
if (!p->has_waiting_updates)
return;
BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size());
std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
for (const auto& update : p->waiting_updates.updates) {
std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string();
updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
}
GUI::MsgUpdateConfig dlg(updates_msg);
const auto res = dlg.ShowModal();
if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
p->perform_updates(std::move(p->waiting_updates));
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets();
p->has_waiting_updates = false;
//return R_UPDATE_INSTALLED;
}
else {
BOOST_LOG_TRIVIAL(info) << "User refused the update";
//return R_UPDATE_REJECT;
}
}
} }

View File

@ -35,16 +35,20 @@ public:
R_INCOMPAT_CONFIGURED, R_INCOMPAT_CONFIGURED,
R_UPDATE_INSTALLED, R_UPDATE_INSTALLED,
R_UPDATE_REJECT, R_UPDATE_REJECT,
R_UPDATE_NOTIFICATION
}; };
// If updating is enabled, check if updates are available in cache, if so, ask about installation. // If updating is enabled, check if updates are available in cache, if so, ask about installation.
// A false return value implies Slic3r should exit due to incompatibility of configuration. // A false return value implies Slic3r should exit due to incompatibility of configuration.
// Providing old slic3r version upgrade profiles on upgrade of an application even in case // Providing old slic3r version upgrade profiles on upgrade of an application even in case
// that the config index installed from the Internet is equal to the index contained in the installation package. // that the config index installed from the Internet is equal to the index contained in the installation package.
UpdateResult config_update(const Semver &old_slic3r_version) const; // no_notification = force modal textbox, otherwise some cases only shows notification
UpdateResult config_update(const Semver &old_slic3r_version, bool no_notification) const;
// "Update" a list of bundles from resources (behaves like an online update). // "Update" a list of bundles from resources (behaves like an online update).
void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const; void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
void on_update_notification_confirm();
private: private:
struct priv; struct priv;
std::unique_ptr<priv> p; std::unique_ptr<priv> p;