Merge branch 'master' into fs_emboss
This commit is contained in:
commit
583babb3b4
20 changed files with 1419 additions and 234 deletions
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance)
|
static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance)
|
||||||
{
|
{
|
||||||
ExtrusionPaths paths;
|
ExtrusionPaths paths;
|
||||||
ExtrusionPath path(role);
|
ExtrusionPath path(role);
|
||||||
|
@ -71,7 +71,7 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi
|
||||||
path.height = new_flow.height();
|
path.height = new_flow.height();
|
||||||
} else {
|
} else {
|
||||||
thickness_delta = fabs(scale_(flow.width()) - w);
|
thickness_delta = fabs(scale_(flow.width()) - w);
|
||||||
if (thickness_delta <= tolerance) {
|
if (thickness_delta <= merge_tolerance) {
|
||||||
// the width difference between this line and the current flow width is
|
// the width difference between this line and the current flow width is
|
||||||
// within the accepted tolerance
|
// within the accepted tolerance
|
||||||
path.polyline.append(line.b);
|
path.polyline.append(line.b);
|
||||||
|
@ -95,7 +95,7 @@ static void variable_width(const ThickPolylines& polylines, ExtrusionRole role,
|
||||||
// of segments, and any pruning shall be performed before we apply this tolerance.
|
// of segments, and any pruning shall be performed before we apply this tolerance.
|
||||||
const float tolerance = float(scale_(0.05));
|
const float tolerance = float(scale_(0.05));
|
||||||
for (const ThickPolyline &p : polylines) {
|
for (const ThickPolyline &p : polylines) {
|
||||||
ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance);
|
ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance, tolerance);
|
||||||
// Append paths to collection.
|
// Append paths to collection.
|
||||||
if (! paths.empty()) {
|
if (! paths.empty()) {
|
||||||
if (paths.front().first_point() == paths.back().last_point())
|
if (paths.front().first_point() == paths.back().last_point())
|
||||||
|
|
|
@ -225,6 +225,8 @@ set(SLIC3R_GUI_SOURCES
|
||||||
GUI/DesktopIntegrationDialog.hpp
|
GUI/DesktopIntegrationDialog.hpp
|
||||||
GUI/HintNotification.cpp
|
GUI/HintNotification.cpp
|
||||||
GUI/HintNotification.hpp
|
GUI/HintNotification.hpp
|
||||||
|
Utils/AppUpdater.cpp
|
||||||
|
Utils/AppUpdater.hpp
|
||||||
Utils/Http.cpp
|
Utils/Http.cpp
|
||||||
Utils/Http.hpp
|
Utils/Http.hpp
|
||||||
Utils/FixModelByWin10.cpp
|
Utils/FixModelByWin10.cpp
|
||||||
|
@ -272,6 +274,7 @@ if (APPLE)
|
||||||
list(APPEND SLIC3R_GUI_SOURCES
|
list(APPEND SLIC3R_GUI_SOURCES
|
||||||
Utils/RetinaHelperImpl.mm
|
Utils/RetinaHelperImpl.mm
|
||||||
Utils/MacDarkMode.mm
|
Utils/MacDarkMode.mm
|
||||||
|
Utils/MacUtils.mm
|
||||||
GUI/RemovableDriveManagerMM.mm
|
GUI/RemovableDriveManagerMM.mm
|
||||||
GUI/RemovableDriveManagerMM.h
|
GUI/RemovableDriveManagerMM.h
|
||||||
GUI/Mouse3DHandlerMac.mm
|
GUI/Mouse3DHandlerMac.mm
|
||||||
|
|
|
@ -106,7 +106,11 @@ double Camera::get_fov() const
|
||||||
void Camera::apply_viewport(int x, int y, unsigned int w, unsigned int h)
|
void Camera::apply_viewport(int x, int y, unsigned int w, unsigned int h)
|
||||||
{
|
{
|
||||||
glsafe(::glViewport(0, 0, w, h));
|
glsafe(::glViewport(0, 0, w, h));
|
||||||
|
#if ENABLE_LEGACY_OPENGL_REMOVAL
|
||||||
|
m_viewport = { 0, 0, int(w), int(h) };
|
||||||
|
#else
|
||||||
glsafe(::glGetIntegerv(GL_VIEWPORT, m_viewport.data()));
|
glsafe(::glGetIntegerv(GL_VIEWPORT, m_viewport.data()));
|
||||||
|
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !ENABLE_LEGACY_OPENGL_REMOVAL
|
#if !ENABLE_LEGACY_OPENGL_REMOVAL
|
||||||
|
|
|
@ -3438,6 +3438,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||||
#endif // !ENABLE_NEW_RECTANGLE_SELECTION
|
#endif // !ENABLE_NEW_RECTANGLE_SELECTION
|
||||||
else {
|
else {
|
||||||
#if ENABLE_NEW_RECTANGLE_SELECTION
|
#if ENABLE_NEW_RECTANGLE_SELECTION
|
||||||
|
const bool rectangle_selection_dragging = m_rectangle_selection.is_dragging();
|
||||||
if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) {
|
if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) {
|
||||||
if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports &&
|
if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports &&
|
||||||
m_gizmos.get_current_type() != GLGizmosManager::FdmSupports &&
|
m_gizmos.get_current_type() != GLGizmosManager::FdmSupports &&
|
||||||
|
@ -3454,8 +3455,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||||
// during the scene manipulation.
|
// during the scene manipulation.
|
||||||
|
|
||||||
#if ENABLE_NEW_RECTANGLE_SELECTION
|
#if ENABLE_NEW_RECTANGLE_SELECTION
|
||||||
if (m_picking_enabled && (!any_gizmo_active || !evt.ShiftDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled()) &&
|
if (m_picking_enabled && !any_gizmo_active && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled()) && !rectangle_selection_dragging) {
|
||||||
!m_rectangle_selection.is_dragging()) {
|
|
||||||
#else
|
#else
|
||||||
if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled())) {
|
if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled())) {
|
||||||
#endif // ENABLE_NEW_RECTANGLE_SELECTION
|
#endif // ENABLE_NEW_RECTANGLE_SELECTION
|
||||||
|
@ -3599,9 +3599,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||||
}
|
}
|
||||||
// do not process the dragging if the left mouse was set down in another canvas
|
// do not process the dragging if the left mouse was set down in another canvas
|
||||||
#if ENABLE_NEW_CAMERA_MOVEMENTS
|
#if ENABLE_NEW_CAMERA_MOVEMENTS
|
||||||
else if (evt.LeftIsDown() || evt.MiddleIsDown()) {
|
else if (evt.LeftIsDown()) {
|
||||||
// if dragging over blank area with left button, rotate
|
// if dragging over blank area with left button, rotate
|
||||||
if ((any_gizmo_active || evt.CmdDown() || evt.MiddleIsDown() || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) {
|
if ((any_gizmo_active || evt.CmdDown() || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) {
|
||||||
#else
|
#else
|
||||||
// if dragging over blank area with left button, rotate
|
// if dragging over blank area with left button, rotate
|
||||||
else if (evt.LeftIsDown()) {
|
else if (evt.LeftIsDown()) {
|
||||||
|
@ -3625,13 +3625,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||||
}
|
}
|
||||||
m_mouse.drag.start_position_3D = Vec3d((double)pos.x(), (double)pos.y(), 0.0);
|
m_mouse.drag.start_position_3D = Vec3d((double)pos.x(), (double)pos.y(), 0.0);
|
||||||
}
|
}
|
||||||
#if ENABLE_NEW_CAMERA_MOVEMENTS
|
|
||||||
else if (evt.RightIsDown()) {
|
|
||||||
// If dragging with right button, pan.
|
|
||||||
#else
|
|
||||||
else if (evt.MiddleIsDown() || evt.RightIsDown()) {
|
else if (evt.MiddleIsDown() || evt.RightIsDown()) {
|
||||||
// If dragging over blank area with right button, pan.
|
// If dragging over blank area with right/middle button, pan.
|
||||||
#endif // ENABLE_NEW_CAMERA_MOVEMENTS
|
|
||||||
if (m_mouse.is_start_position_2D_defined()) {
|
if (m_mouse.is_start_position_2D_defined()) {
|
||||||
// get point in model space at Z = 0
|
// get point in model space at Z = 0
|
||||||
float z = 0.0f;
|
float z = 0.0f;
|
||||||
|
@ -6078,35 +6073,34 @@ void GLCanvas3D::_render_camera_target()
|
||||||
|
|
||||||
#if ENABLE_LEGACY_OPENGL_REMOVAL
|
#if ENABLE_LEGACY_OPENGL_REMOVAL
|
||||||
const Vec3f& target = wxGetApp().plater()->get_camera().get_target().cast<float>();
|
const Vec3f& target = wxGetApp().plater()->get_camera().get_target().cast<float>();
|
||||||
bool target_changed = !m_camera_target.target.isApprox(target.cast<double>());
|
|
||||||
m_camera_target.target = target.cast<double>();
|
m_camera_target.target = target.cast<double>();
|
||||||
|
|
||||||
for (int i = 0; i < 3; ++i) {
|
for (int i = 0; i < 3; ++i) {
|
||||||
if (!m_camera_target.axis[i].is_initialized() || target_changed) {
|
if (!m_camera_target.axis[i].is_initialized()) {
|
||||||
m_camera_target.axis[i].reset();
|
m_camera_target.axis[i].reset();
|
||||||
|
|
||||||
GLModel::Geometry init_data;
|
GLModel::Geometry init_data;
|
||||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
|
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
|
||||||
init_data.color = (i == X) ? ColorRGBA::X() : ((i == Y) ? ColorRGBA::Y() : ColorRGBA::Z());
|
init_data.color = (i == X) ? ColorRGBA::X() : ((i == Y) ? ColorRGBA::Y() : ColorRGBA::Z());
|
||||||
init_data.reserve_vertices(2);
|
init_data.reserve_vertices(2);
|
||||||
init_data.reserve_indices(2);
|
init_data.reserve_indices(2);
|
||||||
|
|
||||||
// vertices
|
// vertices
|
||||||
if (i == X) {
|
if (i == X) {
|
||||||
init_data.add_vertex(Vec3f(target.x() - half_length, target.y(), target.z()));
|
init_data.add_vertex(Vec3f(-half_length, 0.0f, 0.0f));
|
||||||
init_data.add_vertex(Vec3f(target.x() + half_length, target.y(), target.z()));
|
init_data.add_vertex(Vec3f(+half_length, 0.0f, 0.0f));
|
||||||
}
|
}
|
||||||
else if (i == Y) {
|
else if (i == Y) {
|
||||||
init_data.add_vertex(Vec3f(target.x(), target.y() - half_length, target.z()));
|
init_data.add_vertex(Vec3f(0.0f, -half_length, 0.0f));
|
||||||
init_data.add_vertex(Vec3f(target.x(), target.y() + half_length, target.z()));
|
init_data.add_vertex(Vec3f(0.0f, +half_length, 0.0f));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
init_data.add_vertex(Vec3f(target.x(), target.y(), target.z() - half_length));
|
init_data.add_vertex(Vec3f(0.0f, 0.0f, -half_length));
|
||||||
init_data.add_vertex(Vec3f(target.x(), target.y(), target.z() + half_length));
|
init_data.add_vertex(Vec3f(0.0f, 0.0f, +half_length));
|
||||||
}
|
}
|
||||||
|
|
||||||
// indices
|
// indices
|
||||||
init_data.add_ushort_line(0, 1);
|
init_data.add_line(0, 1);
|
||||||
|
|
||||||
m_camera_target.axis[i].init_from(std::move(init_data));
|
m_camera_target.axis[i].init_from(std::move(init_data));
|
||||||
}
|
}
|
||||||
|
@ -6117,7 +6111,7 @@ void GLCanvas3D::_render_camera_target()
|
||||||
shader->start_using();
|
shader->start_using();
|
||||||
#if ENABLE_GL_SHADERS_ATTRIBUTES
|
#if ENABLE_GL_SHADERS_ATTRIBUTES
|
||||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||||
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
|
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::assemble_transform(m_camera_target.target));
|
||||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||||
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
|
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
|
||||||
for (int i = 0; i < 3; ++i) {
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
|
|
@ -1130,6 +1130,7 @@ bool GLModel::send_to_gpu()
|
||||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
data.index_type = Geometry::EIndexType::UINT;
|
||||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.indices_size_bytes(), data.indices.data(), GL_STATIC_DRAW));
|
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.indices_size_bytes(), data.indices.data(), GL_STATIC_DRAW));
|
||||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
#include <boost/any.hpp>
|
#include <boost/any.hpp>
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
#if __APPLE__
|
#if __APPLE__
|
||||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||||
|
@ -476,50 +477,135 @@ void about()
|
||||||
|
|
||||||
void desktop_open_datadir_folder()
|
void desktop_open_datadir_folder()
|
||||||
{
|
{
|
||||||
|
boost::filesystem::path path(data_dir());
|
||||||
|
desktop_open_folder(std::move(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
void desktop_open_folder(const boost::filesystem::path& path)
|
||||||
|
{
|
||||||
|
if (!boost::filesystem::is_directory(path))
|
||||||
|
return;
|
||||||
|
|
||||||
// Execute command to open a file explorer, platform dependent.
|
// Execute command to open a file explorer, platform dependent.
|
||||||
// FIXME: The const_casts aren't needed in wxWidgets 3.1, remove them when we upgrade.
|
|
||||||
|
|
||||||
const auto path = data_dir();
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
const wxString widepath = from_u8(path);
|
const wxString widepath = path.wstring();
|
||||||
const wchar_t *argv[] = { L"explorer", widepath.GetData(), nullptr };
|
const wchar_t* argv[] = { L"explorer", widepath.GetData(), nullptr };
|
||||||
::wxExecute(const_cast<wchar_t**>(argv), wxEXEC_ASYNC, nullptr);
|
::wxExecute(const_cast<wchar_t**>(argv), wxEXEC_ASYNC, nullptr);
|
||||||
#elif __APPLE__
|
#elif __APPLE__
|
||||||
const char *argv[] = { "open", path.data(), nullptr };
|
const char* argv[] = { "open", path.string().c_str(), nullptr };
|
||||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr);
|
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr);
|
||||||
#else
|
#else
|
||||||
const char *argv[] = { "xdg-open", path.data(), nullptr };
|
const char* argv[] = { "xdg-open", path.string().c_str(), nullptr };
|
||||||
|
desktop_execute(argv);
|
||||||
// Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
|
|
||||||
// because they may mess up the environment expected by the file manager.
|
|
||||||
// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
|
|
||||||
if (wxGetEnv("APPIMAGE", nullptr)) {
|
|
||||||
// We're running from AppImage
|
|
||||||
wxEnvVariableHashMap env_vars;
|
|
||||||
wxGetEnvMap(&env_vars);
|
|
||||||
|
|
||||||
env_vars.erase("APPIMAGE");
|
|
||||||
env_vars.erase("APPDIR");
|
|
||||||
env_vars.erase("LD_LIBRARY_PATH");
|
|
||||||
env_vars.erase("LD_PRELOAD");
|
|
||||||
env_vars.erase("UNION_PRELOAD");
|
|
||||||
|
|
||||||
wxExecuteEnv exec_env;
|
|
||||||
exec_env.env = std::move(env_vars);
|
|
||||||
|
|
||||||
wxString owd;
|
|
||||||
if (wxGetEnv("OWD", &owd)) {
|
|
||||||
// This is the original work directory from which the AppImage image was run,
|
|
||||||
// set it as CWD for the child process:
|
|
||||||
exec_env.cwd = std::move(owd);
|
|
||||||
}
|
|
||||||
|
|
||||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, &exec_env);
|
|
||||||
} else {
|
|
||||||
// Looks like we're NOT running from AppImage, we'll make no changes to the environment.
|
|
||||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} }
|
#ifdef __linux__
|
||||||
|
namespace {
|
||||||
|
wxExecuteEnv get_appimage_exec_env()
|
||||||
|
{
|
||||||
|
// If we're running in an AppImage container, we need to remove AppImage's env vars,
|
||||||
|
// because they may mess up the environment expected by the file manager.
|
||||||
|
// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
|
||||||
|
wxEnvVariableHashMap env_vars;
|
||||||
|
wxGetEnvMap(&env_vars);
|
||||||
|
|
||||||
|
env_vars.erase("APPIMAGE");
|
||||||
|
env_vars.erase("APPDIR");
|
||||||
|
env_vars.erase("LD_LIBRARY_PATH");
|
||||||
|
env_vars.erase("LD_PRELOAD");
|
||||||
|
env_vars.erase("UNION_PRELOAD");
|
||||||
|
|
||||||
|
wxExecuteEnv exec_env;
|
||||||
|
exec_env.env = std::move(env_vars);
|
||||||
|
|
||||||
|
wxString owd;
|
||||||
|
if (wxGetEnv("OWD", &owd)) {
|
||||||
|
// This is the original work directory from which the AppImage image was run,
|
||||||
|
// set it as CWD for the child process:
|
||||||
|
exec_env.cwd = std::move(owd);
|
||||||
|
}
|
||||||
|
return exec_env;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
void desktop_execute(const char* argv[])
|
||||||
|
{
|
||||||
|
if (sizeof(argv) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
|
||||||
|
// because they may mess up the environment expected by the file manager.
|
||||||
|
// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
|
||||||
|
if (wxGetEnv("APPIMAGE", nullptr)) {
|
||||||
|
// We're running from AppImage
|
||||||
|
wxExecuteEnv exec_env = get_appimage_exec_env();
|
||||||
|
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, &exec_env);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Looks like we're NOT running from AppImage, we'll make no changes to the environment.
|
||||||
|
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void desktop_execute_get_result(wxString command, wxArrayString& output)
|
||||||
|
{
|
||||||
|
output.Clear();
|
||||||
|
//Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
|
||||||
|
// because they may mess up the environment expected by the file manager.
|
||||||
|
// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
|
||||||
|
if (wxGetEnv("APPIMAGE", nullptr)) {
|
||||||
|
// We're running from AppImage
|
||||||
|
wxExecuteEnv exec_env = get_appimage_exec_env();
|
||||||
|
::wxExecute(command, output, 0, &exec_env);
|
||||||
|
} else {
|
||||||
|
// Looks like we're NOT running from AppImage, we'll make no changes to the environment.
|
||||||
|
::wxExecute(command, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // __linux__
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
bool create_process(const boost::filesystem::path& path, const std::wstring& cmd_opt, std::string& error_msg)
|
||||||
|
{
|
||||||
|
// find updater exe
|
||||||
|
if (boost::filesystem::exists(path)) {
|
||||||
|
// Using quoted string as mentioned in CreateProcessW docs.
|
||||||
|
std::wstring wcmd = L"\"" + path.wstring() + L"\"";
|
||||||
|
if (!cmd_opt.empty())
|
||||||
|
wcmd += L" " + cmd_opt;
|
||||||
|
|
||||||
|
// additional information
|
||||||
|
STARTUPINFOW si;
|
||||||
|
PROCESS_INFORMATION pi;
|
||||||
|
|
||||||
|
// set the size of the structures
|
||||||
|
ZeroMemory(&si, sizeof(si));
|
||||||
|
si.cb = sizeof(si);
|
||||||
|
ZeroMemory(&pi, sizeof(pi));
|
||||||
|
|
||||||
|
// start the program up
|
||||||
|
if (CreateProcessW(NULL, // the path
|
||||||
|
wcmd.data(), // Command line
|
||||||
|
NULL, // Process handle not inheritable
|
||||||
|
NULL, // Thread handle not inheritable
|
||||||
|
FALSE, // Set handle inheritance to FALSE
|
||||||
|
0, // No creation flags
|
||||||
|
NULL, // Use parent's environment block
|
||||||
|
NULL, // Use parent's starting directory
|
||||||
|
&si, // Pointer to STARTUPINFO structure
|
||||||
|
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
|
||||||
|
)) {
|
||||||
|
// Close process and thread handles.
|
||||||
|
CloseHandle(pi.hProcess);
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
error_msg = "CreateProcessW failed to create process " + boost::nowide::narrow(path.wstring());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
error_msg = "Executable doesn't exists. Path: " + boost::nowide::narrow(path.wstring());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif //_WIN32
|
||||||
|
|
||||||
|
} } // namespaces GUI / Slic3r
|
||||||
|
|
|
@ -13,6 +13,7 @@ class wxWindow;
|
||||||
class wxMenuBar;
|
class wxMenuBar;
|
||||||
class wxComboCtrl;
|
class wxComboCtrl;
|
||||||
class wxFileDialog;
|
class wxFileDialog;
|
||||||
|
class wxArrayString;
|
||||||
class wxTopLevelWindow;
|
class wxTopLevelWindow;
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
@ -80,6 +81,24 @@ boost::filesystem::path into_path(const wxString &str);
|
||||||
extern void about();
|
extern void about();
|
||||||
// Ask the destop to open the datadir using the default file explorer.
|
// Ask the destop to open the datadir using the default file explorer.
|
||||||
extern void desktop_open_datadir_folder();
|
extern void desktop_open_datadir_folder();
|
||||||
|
// Ask the destop to open the directory specified by path using the default file explorer.
|
||||||
|
void desktop_open_folder(const boost::filesystem::path& path);
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
// Calling wxExecute on Linux with proper handling of AppImage's env vars.
|
||||||
|
// argv example: { "xdg-open", path.c_str(), nullptr }
|
||||||
|
void desktop_execute(const char* argv[]);
|
||||||
|
void desktop_execute_get_result(wxString command, wxArrayString& output);
|
||||||
|
#endif // __linux__
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Call CreateProcessW to start external proccess on path
|
||||||
|
// returns true on success
|
||||||
|
// path should contain path to the process
|
||||||
|
// cmd_opt can be empty or contain command line options. Example: L"/silent"
|
||||||
|
// error_msg will contain error message if create_process return false
|
||||||
|
bool create_process(const boost::filesystem::path& path, const std::wstring& cmd_opt, std::string& error_msg);
|
||||||
|
#endif //_WIN32
|
||||||
|
|
||||||
} // namespace GUI
|
} // namespace GUI
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
|
@ -57,6 +57,7 @@
|
||||||
#include "../Utils/PrintHost.hpp"
|
#include "../Utils/PrintHost.hpp"
|
||||||
#include "../Utils/Process.hpp"
|
#include "../Utils/Process.hpp"
|
||||||
#include "../Utils/MacDarkMode.hpp"
|
#include "../Utils/MacDarkMode.hpp"
|
||||||
|
#include "../Utils/AppUpdater.hpp"
|
||||||
#include "slic3r/Config/Snapshot.hpp"
|
#include "slic3r/Config/Snapshot.hpp"
|
||||||
#include "ConfigSnapshotDialog.hpp"
|
#include "ConfigSnapshotDialog.hpp"
|
||||||
#include "FirmwareDialog.hpp"
|
#include "FirmwareDialog.hpp"
|
||||||
|
@ -430,50 +431,21 @@ bool static check_old_linux_datadir(const wxString& app_name) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
#if 0 // External Updater is replaced with AppUpdater.cpp
|
||||||
static bool run_updater_win()
|
static bool run_updater_win()
|
||||||
{
|
{
|
||||||
// find updater exe
|
// find updater exe
|
||||||
boost::filesystem::path path_updater = boost::dll::program_location().parent_path() / "prusaslicer-updater.exe";
|
boost::filesystem::path path_updater = boost::dll::program_location().parent_path() / "prusaslicer-updater.exe";
|
||||||
if (boost::filesystem::exists(path_updater)) {
|
// run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst
|
||||||
// run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst
|
std::string msg;
|
||||||
|
bool res = create_process(path_updater, L"/silent", msg);
|
||||||
// Using quoted string as mentioned in CreateProcessW docs, silent execution parameter.
|
if (!res)
|
||||||
std::wstring wcmd = L"\"" + path_updater.wstring() + L"\" /silent";
|
BOOST_LOG_TRIVIAL(error) << msg;
|
||||||
|
return res;
|
||||||
// additional information
|
|
||||||
STARTUPINFOW si;
|
|
||||||
PROCESS_INFORMATION pi;
|
|
||||||
|
|
||||||
// set the size of the structures
|
|
||||||
ZeroMemory(&si, sizeof(si));
|
|
||||||
si.cb = sizeof(si);
|
|
||||||
ZeroMemory(&pi, sizeof(pi));
|
|
||||||
|
|
||||||
// start the program up
|
|
||||||
if (CreateProcessW(NULL, // the path
|
|
||||||
wcmd.data(), // Command line
|
|
||||||
NULL, // Process handle not inheritable
|
|
||||||
NULL, // Thread handle not inheritable
|
|
||||||
FALSE, // Set handle inheritance to FALSE
|
|
||||||
0, // No creation flags
|
|
||||||
NULL, // Use parent's environment block
|
|
||||||
NULL, // Use parent's starting directory
|
|
||||||
&si, // Pointer to STARTUPINFO structure
|
|
||||||
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
|
|
||||||
)) {
|
|
||||||
// Close process and thread handles.
|
|
||||||
CloseHandle(pi.hProcess);
|
|
||||||
CloseHandle(pi.hThread);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
BOOST_LOG_TRIVIAL(error) << "Failed to start prusaslicer-updater.exe with command " << wcmd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
#endif //_WIN32
|
#endif // 0
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
struct FileWildcards {
|
struct FileWildcards {
|
||||||
std::string_view title;
|
std::string_view title;
|
||||||
|
@ -817,18 +789,13 @@ void GUI_App::post_init()
|
||||||
CallAfter([this] {
|
CallAfter([this] {
|
||||||
bool cw_showed = this->config_wizard_startup();
|
bool cw_showed = this->config_wizard_startup();
|
||||||
this->preset_updater->sync(preset_bundle);
|
this->preset_updater->sync(preset_bundle);
|
||||||
|
this->app_version_check(false);
|
||||||
if (! cw_showed) {
|
if (! cw_showed) {
|
||||||
// The CallAfter is needed as well, without it, GL extensions did not show.
|
// The CallAfter is needed as well, without it, GL extensions did not show.
|
||||||
// Also, we only want to show this when the wizard does not, so the new user
|
// Also, we only want to show this when the wizard does not, so the new user
|
||||||
// sees something else than "we want something" on the first start.
|
// sees something else than "we want something" on the first start.
|
||||||
show_send_system_info_dialog_if_needed();
|
show_send_system_info_dialog_if_needed();
|
||||||
}
|
}
|
||||||
#ifdef _WIN32
|
|
||||||
// Run external updater on Windows if version check is enabled.
|
|
||||||
if (this->preset_updater->version_check_enabled() && ! run_updater_win())
|
|
||||||
// "prusaslicer-updater.exe" was not started, run our own update check.
|
|
||||||
#endif // _WIN32
|
|
||||||
this->preset_updater->slic3r_update_notify();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -854,6 +821,8 @@ GUI_App::GUI_App(EAppMode mode)
|
||||||
{
|
{
|
||||||
//app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp
|
//app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp
|
||||||
this->init_app_config();
|
this->init_app_config();
|
||||||
|
// init app downloader after path to datadir is set
|
||||||
|
m_app_updater = std::make_unique<AppUpdater>();
|
||||||
}
|
}
|
||||||
|
|
||||||
GUI_App::~GUI_App()
|
GUI_App::~GUI_App()
|
||||||
|
@ -1243,23 +1212,8 @@ bool GUI_App::on_init_inner()
|
||||||
#endif // __WXMSW__
|
#endif // __WXMSW__
|
||||||
|
|
||||||
preset_updater = new PresetUpdater();
|
preset_updater = new PresetUpdater();
|
||||||
Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
|
Bind(EVT_SLIC3R_VERSION_ONLINE, &GUI_App::on_version_read, this);
|
||||||
app_config->set("version_online", into_u8(evt.GetString()));
|
|
||||||
app_config->save();
|
|
||||||
std::string opt = app_config->get("notify_release");
|
|
||||||
if (this->plater_ != nullptr && (opt == "all" || opt == "release")) {
|
|
||||||
if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) {
|
|
||||||
this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable
|
|
||||||
, NotificationManager::NotificationLevel::ImportantNotificationLevel
|
|
||||||
, Slic3r::format(_u8L("New release version %1% is available."), evt.GetString())
|
|
||||||
, _u8L("See Download page.")
|
|
||||||
, [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
|
Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
|
||||||
app_config->save();
|
|
||||||
if (this->plater_ != nullptr && app_config->get("notify_release") == "all") {
|
if (this->plater_ != nullptr && app_config->get("notify_release") == "all") {
|
||||||
std::string evt_string = into_u8(evt.GetString());
|
std::string evt_string = into_u8(evt.GetString());
|
||||||
if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) {
|
if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) {
|
||||||
|
@ -1273,6 +1227,22 @@ bool GUI_App::on_init_inner()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Bind(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, [this](const wxCommandEvent& evt) {
|
||||||
|
//lm:This does not force a render. The progress bar only updateswhen the mouse is moved.
|
||||||
|
if (this->plater_ != nullptr)
|
||||||
|
this->plater_->get_notification_manager()->set_download_progress_percentage((float)std::stoi(into_u8(evt.GetString())) / 100.f );
|
||||||
|
});
|
||||||
|
|
||||||
|
Bind(EVT_SLIC3R_APP_DOWNLOAD_FAILED, [this](const wxCommandEvent& evt) {
|
||||||
|
if (this->plater_ != nullptr)
|
||||||
|
this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::AppDownload);
|
||||||
|
if(!evt.GetString().IsEmpty())
|
||||||
|
show_error(nullptr, evt.GetString());
|
||||||
|
});
|
||||||
|
|
||||||
|
Bind(EVT_SLIC3R_APP_OPEN_FAILED, [](const wxCommandEvent& evt) {
|
||||||
|
show_error(nullptr, evt.GetString());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
#ifdef __WXMSW__
|
#ifdef __WXMSW__
|
||||||
|
@ -2283,7 +2253,8 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
||||||
local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip);
|
local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip);
|
||||||
local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots"));
|
local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots"));
|
||||||
local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot"));
|
local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot"));
|
||||||
local_menu->Append(config_id_base + ConfigMenuUpdate, _L("Check for Configuration Updates"), _L("Check for configuration updates"));
|
local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates"));
|
||||||
|
local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application"));
|
||||||
#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
|
#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||||
//if (DesktopIntegrationDialog::integration_possible())
|
//if (DesktopIntegrationDialog::integration_possible())
|
||||||
local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration"));
|
local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration"));
|
||||||
|
@ -2325,9 +2296,12 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
||||||
case ConfigMenuWizard:
|
case ConfigMenuWizard:
|
||||||
run_wizard(ConfigWizard::RR_USER);
|
run_wizard(ConfigWizard::RR_USER);
|
||||||
break;
|
break;
|
||||||
case ConfigMenuUpdate:
|
case ConfigMenuUpdateConf:
|
||||||
check_updates(true);
|
check_updates(true);
|
||||||
break;
|
break;
|
||||||
|
case ConfigMenuUpdateApp:
|
||||||
|
app_version_check(true);
|
||||||
|
break;
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
case ConfigMenuDesktopIntegration:
|
case ConfigMenuDesktopIntegration:
|
||||||
show_desktop_integration_dialog();
|
show_desktop_integration_dialog();
|
||||||
|
@ -3267,5 +3241,87 @@ void GUI_App::associate_gcode_files()
|
||||||
}
|
}
|
||||||
#endif // __WXMSW__
|
#endif // __WXMSW__
|
||||||
|
|
||||||
|
|
||||||
|
void GUI_App::on_version_read(wxCommandEvent& evt)
|
||||||
|
{
|
||||||
|
app_config->set("version_online", into_u8(evt.GetString()));
|
||||||
|
app_config->save();
|
||||||
|
std::string opt = app_config->get("notify_release");
|
||||||
|
if (this->plater_ == nullptr || (opt != "all" && opt != "release")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// notification
|
||||||
|
/*
|
||||||
|
this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable
|
||||||
|
, NotificationManager::NotificationLevel::ImportantNotificationLevel
|
||||||
|
, Slic3r::format(_u8L("New release version %1% is available."), evt.GetString())
|
||||||
|
, _u8L("See Download page.")
|
||||||
|
, [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; }
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
// updater
|
||||||
|
// read triggered_by_user that was set when calling GUI_App::app_version_check
|
||||||
|
app_updater(m_app_updater->get_triggered_by_user());
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUI_App::app_updater(bool from_user)
|
||||||
|
{
|
||||||
|
DownloadAppData app_data = m_app_updater->get_app_data();
|
||||||
|
|
||||||
|
if (from_user && (!app_data.version || *app_data.version <= *Semver::parse(SLIC3R_VERSION)))
|
||||||
|
{
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "There is no newer version online.";
|
||||||
|
MsgNoAppUpdates no_update_dialog;
|
||||||
|
no_update_dialog.ShowModal();
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!app_data.url.empty());
|
||||||
|
assert(!app_data.target_path.empty());
|
||||||
|
|
||||||
|
// dialog with new version info
|
||||||
|
AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version);
|
||||||
|
auto dialog_result = dialog.ShowModal();
|
||||||
|
// checkbox "do not show again"
|
||||||
|
if (dialog.disable_version_check()) {
|
||||||
|
app_config->set("notify_release", "none");
|
||||||
|
}
|
||||||
|
// Doesn't wish to update
|
||||||
|
if (dialog_result != wxID_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// dialog with new version download (installer or app dependent on system) including path selection
|
||||||
|
AppUpdateDownloadDialog dwnld_dlg(*app_data.version, app_data.target_path);
|
||||||
|
dialog_result = dwnld_dlg.ShowModal();
|
||||||
|
// Doesn't wish to download
|
||||||
|
if (dialog_result != wxID_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
app_data.target_path =dwnld_dlg.get_download_path();
|
||||||
|
|
||||||
|
// start download
|
||||||
|
this->plater_->get_notification_manager()->push_download_progress_notification(_utf8("Download"), std::bind(&AppUpdater::cancel_callback, this->m_app_updater.get()));
|
||||||
|
app_data.start_after = dwnld_dlg.run_after_download();
|
||||||
|
m_app_updater->set_app_data(std::move(app_data));
|
||||||
|
m_app_updater->sync_download();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUI_App::app_version_check(bool from_user)
|
||||||
|
{
|
||||||
|
if (from_user) {
|
||||||
|
if (m_app_updater->get_download_ongoing()) {
|
||||||
|
MessageDialog msgdlg(nullptr, _L("Download of new version is already ongoing. Do you wish to continue?"), _L("Notice"), wxYES_NO);
|
||||||
|
if (msgdlg.ShowModal() != wxID_YES)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string version_check_url = app_config->version_check_url();
|
||||||
|
m_app_updater->sync_version(version_check_url, from_user);
|
||||||
|
}
|
||||||
|
|
||||||
} // GUI
|
} // GUI
|
||||||
} //Slic3r
|
} //Slic3r
|
||||||
|
|
|
@ -32,6 +32,7 @@ class PresetUpdater;
|
||||||
class ModelObject;
|
class ModelObject;
|
||||||
class PrintHostJobQueue;
|
class PrintHostJobQueue;
|
||||||
class Model;
|
class Model;
|
||||||
|
class AppUpdater;
|
||||||
|
|
||||||
namespace GUI{
|
namespace GUI{
|
||||||
|
|
||||||
|
@ -82,7 +83,8 @@ enum ConfigMenuIDs {
|
||||||
ConfigMenuWizard,
|
ConfigMenuWizard,
|
||||||
ConfigMenuSnapshots,
|
ConfigMenuSnapshots,
|
||||||
ConfigMenuTakeSnapshot,
|
ConfigMenuTakeSnapshot,
|
||||||
ConfigMenuUpdate,
|
ConfigMenuUpdateConf,
|
||||||
|
ConfigMenuUpdateApp,
|
||||||
ConfigMenuDesktopIntegration,
|
ConfigMenuDesktopIntegration,
|
||||||
ConfigMenuPreferences,
|
ConfigMenuPreferences,
|
||||||
ConfigMenuModeSimple,
|
ConfigMenuModeSimple,
|
||||||
|
@ -157,6 +159,7 @@ private:
|
||||||
std::unique_ptr<ImGuiWrapper> m_imgui;
|
std::unique_ptr<ImGuiWrapper> m_imgui;
|
||||||
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
|
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
|
||||||
std::unique_ptr <OtherInstanceMessageHandler> m_other_instance_message_handler;
|
std::unique_ptr <OtherInstanceMessageHandler> m_other_instance_message_handler;
|
||||||
|
std::unique_ptr <AppUpdater> m_app_updater;
|
||||||
std::unique_ptr <wxSingleInstanceChecker> m_single_instance_checker;
|
std::unique_ptr <wxSingleInstanceChecker> m_single_instance_checker;
|
||||||
std::string m_instance_hash_string;
|
std::string m_instance_hash_string;
|
||||||
size_t m_instance_hash_int;
|
size_t m_instance_hash_int;
|
||||||
|
@ -361,6 +364,11 @@ private:
|
||||||
// Returns true if the configuration is fine.
|
// Returns true if the configuration is fine.
|
||||||
// Returns true if the configuration is not compatible and the user decided to rather close the slicer instead of reconfiguring.
|
// Returns true if the configuration is not compatible and the user decided to rather close the slicer instead of reconfiguring.
|
||||||
bool check_updates(const bool verbose);
|
bool check_updates(const bool verbose);
|
||||||
|
void on_version_read(wxCommandEvent& evt);
|
||||||
|
// if the data from version file are already downloaded, shows dialogs to start download of new version of app
|
||||||
|
void app_updater(bool from_user);
|
||||||
|
// inititate read of version file online in separate thread
|
||||||
|
void app_version_check(bool from_user);
|
||||||
|
|
||||||
bool m_datadir_redefined { false };
|
bool m_datadir_redefined { false };
|
||||||
};
|
};
|
||||||
|
|
|
@ -825,6 +825,120 @@ void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgu
|
||||||
imgui.text(text.c_str());
|
imgui.text(text.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//------ProgressBarWithCancelNotification----------------
|
||||||
|
|
||||||
|
void NotificationManager::ProgressBarWithCancelNotification::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)
|
||||||
|
{
|
||||||
|
if (m_percentage < 0.f || m_percentage >= 1.f)
|
||||||
|
render_close_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||||
|
else
|
||||||
|
render_cancel_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||||
|
}
|
||||||
|
void NotificationManager::ProgressBarWithCancelNotification::render_close_button_inner(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);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||||
|
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
|
||||||
|
|
||||||
|
std::string button_text;
|
||||||
|
button_text = ImGui::CloseNotifButton;
|
||||||
|
|
||||||
|
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_minimize_b_visible ? 2 * m_line_height : 0)),
|
||||||
|
true))
|
||||||
|
{
|
||||||
|
button_text = ImGui::CloseNotifHoverButton;
|
||||||
|
}
|
||||||
|
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.75f);
|
||||||
|
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
|
||||||
|
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
//invisible large button
|
||||||
|
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f);
|
||||||
|
ImGui::SetCursorPosY(0);
|
||||||
|
if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)))
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor(5);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationManager::ProgressBarWithCancelNotification::render_cancel_button_inner(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);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||||
|
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
|
||||||
|
|
||||||
|
std::string button_text;
|
||||||
|
button_text = ImGui::CancelButton;
|
||||||
|
|
||||||
|
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_minimize_b_visible ? 2 * m_line_height : 0)),
|
||||||
|
true))
|
||||||
|
{
|
||||||
|
button_text = ImGui::CancelHoverButton;
|
||||||
|
}
|
||||||
|
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.75f);
|
||||||
|
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
|
||||||
|
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||||
|
{
|
||||||
|
on_cancel_button();
|
||||||
|
}
|
||||||
|
|
||||||
|
//invisible large button
|
||||||
|
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f);
|
||||||
|
ImGui::SetCursorPosY(0);
|
||||||
|
if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)))
|
||||||
|
{
|
||||||
|
on_cancel_button();
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor(5);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationManager::ProgressBarWithCancelNotification::on_cancel_button()
|
||||||
|
{
|
||||||
|
if (m_cancel_callback) {
|
||||||
|
if (m_cancel_callback()) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationManager::ProgressBarWithCancelNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||||
|
{
|
||||||
|
ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||||
|
std::string text;
|
||||||
|
if (m_percentage < 0.f) {
|
||||||
|
text = _u8L("ERROR");
|
||||||
|
} else {
|
||||||
|
std::stringstream stream;
|
||||||
|
stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "%";
|
||||||
|
text = stream.str();
|
||||||
|
}
|
||||||
|
ImGui::SetCursorPosX(m_left_indentation);
|
||||||
|
ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4));
|
||||||
|
imgui.text(text.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
//------PrintHostUploadNotification----------------
|
//------PrintHostUploadNotification----------------
|
||||||
void NotificationManager::PrintHostUploadNotification::init()
|
void NotificationManager::PrintHostUploadNotification::init()
|
||||||
{
|
{
|
||||||
|
@ -1659,11 +1773,42 @@ void NotificationManager::upload_job_notification_show_error(int id, const std::
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NotificationManager::push_download_progress_notification(const std::string& text, std::function<bool()> cancel_callback)
|
||||||
|
{
|
||||||
|
// If already exists, change text and reset progress
|
||||||
|
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::AppDownload) {
|
||||||
|
notification->update({ NotificationType::AppDownload, NotificationLevel::ProgressBarNotificationLevel, 10, text });
|
||||||
|
auto* pbwcn = dynamic_cast<ProgressBarWithCancelNotification*>(notification.get());
|
||||||
|
pbwcn->set_percentage(0.0f);
|
||||||
|
pbwcn->set_cancel_callback(cancel_callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// push new one
|
||||||
|
NotificationData data{ NotificationType::AppDownload, NotificationLevel::ProgressBarNotificationLevel, 10, text };
|
||||||
|
push_notification_data(std::make_unique<NotificationManager::ProgressBarWithCancelNotification>(data, m_id_provider, m_evt_handler, cancel_callback), 0);
|
||||||
|
}
|
||||||
|
void NotificationManager::set_download_progress_percentage(float percentage)
|
||||||
|
{
|
||||||
|
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::AppDownload) {
|
||||||
|
ProgressBarWithCancelNotification* pbwcn = dynamic_cast<ProgressBarWithCancelNotification*>(notification.get());
|
||||||
|
// if this changes the percentage, it should be shown now
|
||||||
|
float percent_b4 = pbwcn->get_percentage();
|
||||||
|
pbwcn->set_percentage(percentage);
|
||||||
|
if (pbwcn->get_percentage() != percent_b4)
|
||||||
|
wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void NotificationManager::init_slicing_progress_notification(std::function<bool()> cancel_callback)
|
void NotificationManager::init_slicing_progress_notification(std::function<bool()> cancel_callback)
|
||||||
{
|
{
|
||||||
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||||
if (notification->get_type() == NotificationType::SlicingProgress) {
|
if (notification->get_type() == NotificationType::SlicingProgress) {
|
||||||
dynamic_cast<SlicingProgressNotification*>(notification.get())->set_cancel_callback(cancel_callback);
|
dynamic_cast<SlicingProgressNotification*>(notification.get())->set_cancel_callback(cancel_callback);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,8 @@ enum class NotificationType
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
// Progress bar with info from Print Host Upload Queue dialog.
|
// Progress bar with info from Print Host Upload Queue dialog.
|
||||||
PrintHostUpload,
|
PrintHostUpload,
|
||||||
|
// Progress bar of download next version app.
|
||||||
|
AppDownload,
|
||||||
// Progress bar with cancel button, cannot be closed
|
// Progress bar with cancel button, cannot be closed
|
||||||
// On end of slicing and G-code processing (the full G-code preview is available),
|
// On end of slicing and G-code processing (the full G-code preview is available),
|
||||||
// contains a hyperlink to export the G-code to a removable media or hdd.
|
// contains a hyperlink to export the G-code to a removable media or hdd.
|
||||||
|
@ -205,6 +207,9 @@ public:
|
||||||
void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage);
|
void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage);
|
||||||
void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host);
|
void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host);
|
||||||
void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host);
|
void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host);
|
||||||
|
// Download App progress
|
||||||
|
void push_download_progress_notification(const std::string& text, std::function<bool()> cancel_callback);
|
||||||
|
void set_download_progress_percentage(float percentage);
|
||||||
// slicing progress
|
// slicing progress
|
||||||
void init_slicing_progress_notification(std::function<bool()> cancel_callback);
|
void init_slicing_progress_notification(std::function<bool()> cancel_callback);
|
||||||
void set_slicing_progress_began();
|
void set_slicing_progress_began();
|
||||||
|
@ -439,6 +444,7 @@ private:
|
||||||
|
|
||||||
ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) { }
|
ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) { }
|
||||||
virtual void set_percentage(float percent) { m_percentage = percent; }
|
virtual void set_percentage(float percent) { m_percentage = percent; }
|
||||||
|
float get_percentage() const { return m_percentage; }
|
||||||
protected:
|
protected:
|
||||||
virtual void init() override;
|
virtual void init() override;
|
||||||
virtual void render_text(ImGuiWrapper& imgui,
|
virtual void render_text(ImGuiWrapper& imgui,
|
||||||
|
@ -461,7 +467,35 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ProgressBarWithCancelNotification : public ProgressBarNotification
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ProgressBarWithCancelNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, std::function<bool()> cancel_callback)
|
||||||
|
: ProgressBarNotification(n, id_provider, evt_handler)
|
||||||
|
, m_cancel_callback(cancel_callback)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void set_percentage(float percent) override { m_percentage = percent; if(m_percentage >= 1.f) m_state = EState::FadingOut; else m_state = EState::NotFading; }
|
||||||
|
void set_cancel_callback(std::function<bool()> cancel_callback) { m_cancel_callback = cancel_callback; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
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) override;
|
||||||
|
void render_close_button_inner(ImGuiWrapper& imgui,
|
||||||
|
const float win_size_x, const float win_size_y,
|
||||||
|
const float win_pos_x, const float win_pos_y);
|
||||||
|
void render_cancel_button_inner(ImGuiWrapper& imgui,
|
||||||
|
const float win_size_x, const float win_size_y,
|
||||||
|
const float win_pos_x, const float win_pos_y);
|
||||||
|
void render_bar(ImGuiWrapper& imgui,
|
||||||
|
const float win_size_x, const float win_size_y,
|
||||||
|
const float win_pos_x, const float win_pos_y) override;
|
||||||
|
void on_cancel_button();
|
||||||
|
|
||||||
|
std::function<bool()> m_cancel_callback;
|
||||||
|
long m_hover_time{ 0 };
|
||||||
|
};
|
||||||
|
|
||||||
class PrintHostUploadNotification : public ProgressBarNotification
|
class PrintHostUploadNotification : public ProgressBarNotification
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <boost/format.hpp>
|
#include <boost/format.hpp>
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
#include <boost/nowide/convert.hpp>
|
||||||
|
|
||||||
#include <wx/settings.h>
|
#include <wx/settings.h>
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
|
@ -14,11 +15,13 @@
|
||||||
|
|
||||||
#include "libslic3r/libslic3r.h"
|
#include "libslic3r/libslic3r.h"
|
||||||
#include "libslic3r/Utils.hpp"
|
#include "libslic3r/Utils.hpp"
|
||||||
|
#include "../Utils/AppUpdater.hpp"
|
||||||
#include "GUI.hpp"
|
#include "GUI.hpp"
|
||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
#include "I18N.hpp"
|
#include "I18N.hpp"
|
||||||
#include "ConfigWizard.hpp"
|
#include "ConfigWizard.hpp"
|
||||||
#include "wxExtensions.hpp"
|
#include "wxExtensions.hpp"
|
||||||
|
#include "format.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
@ -88,6 +91,125 @@ bool MsgUpdateSlic3r::disable_version_check() const
|
||||||
return cbox->GetValue();
|
return cbox->GetValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wxSize AppUpdateAvailableDialog::AUAD_size;
|
||||||
|
// AppUpdater
|
||||||
|
AppUpdateAvailableDialog::AppUpdateAvailableDialog(const Semver& ver_current, const Semver& ver_online)
|
||||||
|
: MsgDialog(nullptr, _(L("App Update available")), wxString::Format(_(L("New version of %s is available.\nDo you wish to download it?")), SLIC3R_APP_NAME))
|
||||||
|
{
|
||||||
|
auto* versions = new wxFlexGridSizer(1, 0, VERT_SPACING);
|
||||||
|
versions->Add(new wxStaticText(this, wxID_ANY, _(L("Current version:"))));
|
||||||
|
versions->Add(new wxStaticText(this, wxID_ANY, ver_current.to_string()));
|
||||||
|
versions->Add(new wxStaticText(this, wxID_ANY, _(L("New version:"))));
|
||||||
|
versions->Add(new wxStaticText(this, wxID_ANY, ver_online.to_string()));
|
||||||
|
content_sizer->Add(versions);
|
||||||
|
content_sizer->AddSpacer(VERT_SPACING);
|
||||||
|
|
||||||
|
cbox = new wxCheckBox(this, wxID_ANY, _(L("Don't notify about new releases any more")));
|
||||||
|
content_sizer->Add(cbox);
|
||||||
|
content_sizer->AddSpacer(VERT_SPACING);
|
||||||
|
|
||||||
|
AUAD_size = content_sizer->GetSize();
|
||||||
|
|
||||||
|
|
||||||
|
add_button(wxID_CANCEL);
|
||||||
|
|
||||||
|
if (auto* btn_ok = get_button(wxID_OK); btn_ok != NULL) {
|
||||||
|
btn_ok->SetLabel(_L("Next"));
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
AppUpdateAvailableDialog::~AppUpdateAvailableDialog() {}
|
||||||
|
|
||||||
|
|
||||||
|
bool AppUpdateAvailableDialog::disable_version_check() const
|
||||||
|
{
|
||||||
|
return cbox->GetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppUpdateDownloadDialog
|
||||||
|
AppUpdateDownloadDialog::AppUpdateDownloadDialog( const Semver& ver_online, boost::filesystem::path& path)
|
||||||
|
: MsgDialog(nullptr, _(L("App Update download")), wxString::Format(_(L("New version of %s is available.")), SLIC3R_APP_NAME))
|
||||||
|
{
|
||||||
|
auto* versions = new wxFlexGridSizer(2, 0, VERT_SPACING);
|
||||||
|
versions->Add(new wxStaticText(this, wxID_ANY, _(L("New version:"))));
|
||||||
|
versions->Add(new wxStaticText(this, wxID_ANY, ver_online.to_string()));
|
||||||
|
content_sizer->Add(versions);
|
||||||
|
content_sizer->AddSpacer(VERT_SPACING);
|
||||||
|
#ifndef __linux__
|
||||||
|
cbox_run = new wxCheckBox(this, wxID_ANY, _(L("Run installer after download. (Otherwise file explorer will be opened)")));
|
||||||
|
content_sizer->Add(cbox_run);
|
||||||
|
#endif
|
||||||
|
content_sizer->AddSpacer(VERT_SPACING);
|
||||||
|
content_sizer->AddSpacer(VERT_SPACING);
|
||||||
|
content_sizer->Add(new wxStaticText(this, wxID_ANY, _(L("Target path:"))));
|
||||||
|
content_sizer->AddSpacer(VERT_SPACING);
|
||||||
|
txtctrl_path = new wxTextCtrl(this, wxID_ANY, path.wstring());
|
||||||
|
content_sizer->Add(txtctrl_path, 1, wxEXPAND);
|
||||||
|
content_sizer->AddSpacer(VERT_SPACING);
|
||||||
|
|
||||||
|
wxButton* btn = new wxButton(this, wxID_ANY, _L("Select path"));
|
||||||
|
content_sizer->Add(btn/*, 1, wxEXPAND*/);
|
||||||
|
|
||||||
|
// button to open file dialog
|
||||||
|
btn->Bind(wxEVT_BUTTON, ([this, path](wxCommandEvent& e) {
|
||||||
|
std::string extension = path.filename().extension().string();
|
||||||
|
wxString wildcard;
|
||||||
|
if (!extension.empty()) {
|
||||||
|
extension = extension.substr(1);
|
||||||
|
wxString wxext = boost::nowide::widen(extension);
|
||||||
|
wildcard = GUI::format_wxstr("%1% Files (*.%2%)|*.%2%", wxext.Upper(), wxext);
|
||||||
|
}
|
||||||
|
wxFileDialog save_dlg(
|
||||||
|
this
|
||||||
|
, _L("Save as:")
|
||||||
|
, txtctrl_path->GetValue()
|
||||||
|
, boost::nowide::widen(AppUpdater::get_filename_from_url(txtctrl_path->GetValue().ToUTF8().data()))
|
||||||
|
, wildcard
|
||||||
|
, wxFD_SAVE | wxFD_OVERWRITE_PROMPT
|
||||||
|
);
|
||||||
|
if (save_dlg.ShowModal() == wxID_OK) {
|
||||||
|
txtctrl_path->SetValue(save_dlg.GetPath());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
content_sizer->SetMinSize(AppUpdateAvailableDialog::AUAD_size);
|
||||||
|
|
||||||
|
add_button(wxID_CANCEL);
|
||||||
|
|
||||||
|
if (auto* btn_ok = get_button(wxID_OK); btn_ok != NULL) {
|
||||||
|
btn_ok->SetLabel(_L("Download"));
|
||||||
|
btn_ok->Bind(wxEVT_BUTTON, ([this, path](wxCommandEvent& e){
|
||||||
|
if (boost::filesystem::exists(boost::filesystem::path(txtctrl_path->GetValue().ToUTF8().data()))) {
|
||||||
|
MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("File %1% already exists. Do you wish to overwrite it?"), txtctrl_path->GetValue()),_L("Notice"), wxYES_NO);
|
||||||
|
if (msgdlg.ShowModal() != wxID_YES)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->EndModal(wxID_OK);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
AppUpdateDownloadDialog::~AppUpdateDownloadDialog() {}
|
||||||
|
|
||||||
|
|
||||||
|
bool AppUpdateDownloadDialog::run_after_download() const
|
||||||
|
{
|
||||||
|
#ifndef __linux__
|
||||||
|
return cbox_run->GetValue();
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::filesystem::path AppUpdateDownloadDialog::get_download_path() const
|
||||||
|
{
|
||||||
|
return std::move(boost::filesystem::path(txtctrl_path->GetValue().ToUTF8().data()));
|
||||||
|
}
|
||||||
|
|
||||||
// MsgUpdateConfig
|
// MsgUpdateConfig
|
||||||
|
|
||||||
MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_before_wizard/* = false*/) :
|
MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_before_wizard/* = false*/) :
|
||||||
|
@ -314,5 +436,25 @@ MsgNoUpdates::MsgNoUpdates() :
|
||||||
|
|
||||||
MsgNoUpdates::~MsgNoUpdates() {}
|
MsgNoUpdates::~MsgNoUpdates() {}
|
||||||
|
|
||||||
|
// MsgNoAppUpdates
|
||||||
|
MsgNoAppUpdates::MsgNoAppUpdates() :
|
||||||
|
MsgDialog(nullptr, _(L("App update")), _(L("No updates available")), wxICON_ERROR | wxOK)
|
||||||
|
{
|
||||||
|
|
||||||
|
auto* text = new wxStaticText(this, wxID_ANY, wxString::Format(
|
||||||
|
_(L(
|
||||||
|
"%s has no version updates available."
|
||||||
|
)),
|
||||||
|
SLIC3R_APP_NAME
|
||||||
|
));
|
||||||
|
text->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
|
||||||
|
content_sizer->Add(text);
|
||||||
|
content_sizer->AddSpacer(VERT_SPACING);
|
||||||
|
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
MsgNoAppUpdates::~MsgNoAppUpdates() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <wx/hyperlink.h>
|
#include <wx/hyperlink.h>
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
#include "libslic3r/Semver.hpp"
|
#include "libslic3r/Semver.hpp"
|
||||||
#include "MsgDialog.hpp"
|
#include "MsgDialog.hpp"
|
||||||
|
|
||||||
|
@ -37,6 +39,42 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class AppUpdateAvailableDialog : public MsgDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AppUpdateAvailableDialog(const Semver& ver_current, const Semver& ver_online);
|
||||||
|
AppUpdateAvailableDialog(AppUpdateAvailableDialog&&) = delete;
|
||||||
|
AppUpdateAvailableDialog(const AppUpdateAvailableDialog&) = delete;
|
||||||
|
AppUpdateAvailableDialog& operator=(AppUpdateAvailableDialog&&) = delete;
|
||||||
|
AppUpdateAvailableDialog& operator=(const AppUpdateAvailableDialog&) = delete;
|
||||||
|
virtual ~AppUpdateAvailableDialog();
|
||||||
|
|
||||||
|
// Tells whether the user checked the "don't bother me again" checkbox
|
||||||
|
bool disable_version_check() const;
|
||||||
|
static wxSize AUAD_size;
|
||||||
|
private:
|
||||||
|
wxCheckBox* cbox;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AppUpdateDownloadDialog : public MsgDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AppUpdateDownloadDialog(const Semver& ver_online, boost::filesystem::path& path);
|
||||||
|
AppUpdateDownloadDialog(AppUpdateDownloadDialog&&) = delete;
|
||||||
|
AppUpdateDownloadDialog(const AppUpdateDownloadDialog&) = delete;
|
||||||
|
AppUpdateDownloadDialog& operator=(AppUpdateDownloadDialog&&) = delete;
|
||||||
|
AppUpdateDownloadDialog& operator=(const AppUpdateDownloadDialog&) = delete;
|
||||||
|
virtual ~AppUpdateDownloadDialog();
|
||||||
|
|
||||||
|
// Tells whether the user checked the "don't bother me again" checkbox
|
||||||
|
bool run_after_download() const;
|
||||||
|
boost::filesystem::path get_download_path() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
wxCheckBox* cbox_run;
|
||||||
|
wxTextCtrl* txtctrl_path;
|
||||||
|
};
|
||||||
|
|
||||||
// Confirmation dialog informing about configuration update. Lists updated bundles & their versions.
|
// Confirmation dialog informing about configuration update. Lists updated bundles & their versions.
|
||||||
class MsgUpdateConfig : public MsgDialog
|
class MsgUpdateConfig : public MsgDialog
|
||||||
{
|
{
|
||||||
|
@ -129,6 +167,18 @@ public:
|
||||||
~MsgNoUpdates();
|
~MsgNoUpdates();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Informs about absence of new version online.
|
||||||
|
class MsgNoAppUpdates : public MsgDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MsgNoAppUpdates();
|
||||||
|
MsgNoAppUpdates(MsgNoAppUpdates&&) = delete;
|
||||||
|
MsgNoAppUpdates(const MsgNoAppUpdates&) = delete;
|
||||||
|
MsgNoAppUpdates& operator=(MsgNoUpdates&&) = delete;
|
||||||
|
MsgNoAppUpdates& operator=(const MsgNoAppUpdates&) = delete;
|
||||||
|
~MsgNoAppUpdates();
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
618
src/slic3r/Utils/AppUpdater.cpp
Normal file
618
src/slic3r/Utils/AppUpdater.cpp
Normal file
|
@ -0,0 +1,618 @@
|
||||||
|
#include "AppUpdater.hpp"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <boost/nowide/convert.hpp>
|
||||||
|
#include <boost/property_tree/ini_parser.hpp>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
#include "slic3r/GUI/format.hpp"
|
||||||
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
|
#include "slic3r/Utils/Http.hpp"
|
||||||
|
|
||||||
|
#include "libslic3r/Utils.hpp"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <shellapi.h>
|
||||||
|
#include <Shlobj_core.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <KnownFolders.h>
|
||||||
|
#include <shlobj.h>
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
bool run_file(const boost::filesystem::path& path)
|
||||||
|
{
|
||||||
|
std::string msg;
|
||||||
|
bool res = GUI::create_process(path, std::wstring(), msg);
|
||||||
|
if (!res) {
|
||||||
|
std::string full_message = GUI::format("Running downloaded instaler of %1% has failed:\n%2%", SLIC3R_APP_NAME, msg);
|
||||||
|
BOOST_LOG_TRIVIAL(error) << full_message; // lm: maybe UI error msg? // dk: bellow. (maybe some general show error evt would be better?)
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED);
|
||||||
|
evt->SetString(full_message);
|
||||||
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_downloads_path()
|
||||||
|
{
|
||||||
|
std::string ret;
|
||||||
|
PWSTR path = NULL;
|
||||||
|
HRESULT hr = SHGetKnownFolderPath(FOLDERID_Downloads, 0, NULL, &path);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
ret = boost::nowide::narrow(path);
|
||||||
|
}
|
||||||
|
CoTaskMemFree(path);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#elif __APPLE__
|
||||||
|
bool run_file(const boost::filesystem::path& path)
|
||||||
|
{
|
||||||
|
if (boost::filesystem::exists(path)) {
|
||||||
|
// attach downloaded dmg file
|
||||||
|
const char* argv1[] = { "hdiutil", "attach", path.string().c_str(), nullptr };
|
||||||
|
::wxExecute(const_cast<char**>(argv1), wxEXEC_ASYNC, nullptr);
|
||||||
|
// open inside attached as a folder in finder
|
||||||
|
const char* argv2[] = { "open", "/Volumes/PrusaSlicer", nullptr };
|
||||||
|
::wxExecute(const_cast<char**>(argv2), wxEXEC_ASYNC, nullptr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_downloads_path()
|
||||||
|
{
|
||||||
|
// call objective-c implementation
|
||||||
|
return get_downloads_path_mac();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
bool run_file(const boost::filesystem::path& path)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_downloads_path()
|
||||||
|
{
|
||||||
|
wxString command = "xdg-user-dir DOWNLOAD";
|
||||||
|
wxArrayString output;
|
||||||
|
GUI::desktop_execute_get_result(command, output);
|
||||||
|
if (output.GetCount() > 0) {
|
||||||
|
return output[0].ToUTF8().data(); //lm:I would use wxString::ToUTF8(), although on Linux, nothing at all should work too.
|
||||||
|
}
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
#endif // _WIN32 / __apple__ / else
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
|
||||||
|
wxDEFINE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
|
||||||
|
wxDEFINE_EVENT(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, wxCommandEvent);
|
||||||
|
wxDEFINE_EVENT(EVT_SLIC3R_APP_DOWNLOAD_FAILED, wxCommandEvent);
|
||||||
|
wxDEFINE_EVENT(EVT_SLIC3R_APP_OPEN_FAILED, wxCommandEvent);
|
||||||
|
|
||||||
|
// priv handles all operations in separate thread
|
||||||
|
// 1) download version file and parse it.
|
||||||
|
// 2) download new app file and open in folder / run it.
|
||||||
|
struct AppUpdater::priv {
|
||||||
|
priv();
|
||||||
|
// Download file. What happens with the data is specified in completefn.
|
||||||
|
bool http_get_file(const std::string& url
|
||||||
|
, size_t size_limit
|
||||||
|
, std::function<bool(Http::Progress)> progress_fn
|
||||||
|
, std::function<bool(std::string /*body*/, std::string& error_message)> completefn
|
||||||
|
, std::string& error_message
|
||||||
|
) const;
|
||||||
|
|
||||||
|
// Download installer / app
|
||||||
|
boost::filesystem::path download_file(const DownloadAppData& data) const;
|
||||||
|
// Run file in m_last_dest_path
|
||||||
|
bool run_downloaded_file(boost::filesystem::path path);
|
||||||
|
// gets version file via http
|
||||||
|
void version_check(const std::string& version_check_url);
|
||||||
|
#if 0
|
||||||
|
// parsing of Prusaslicer.version2
|
||||||
|
void parse_version_string_old(const std::string& body) const;
|
||||||
|
#endif
|
||||||
|
// parses ini tree of version file, saves to m_online_version_data and queue event(s) to UI
|
||||||
|
void parse_version_string(const std::string& body);
|
||||||
|
// thread
|
||||||
|
std::thread m_thread;
|
||||||
|
std::atomic_bool m_cancel;
|
||||||
|
std::mutex m_data_mutex;
|
||||||
|
// used to tell if notify user hes about to stop ongoing download
|
||||||
|
std::atomic_bool m_download_ongoing { false };
|
||||||
|
bool get_download_ongoing() const { return m_download_ongoing; }
|
||||||
|
// read only variable used to init m_online_version_data.target_path
|
||||||
|
boost::filesystem::path m_default_dest_folder; // readonly
|
||||||
|
// DownloadAppData read / write needs to be locked by m_data_mutex
|
||||||
|
DownloadAppData m_online_version_data;
|
||||||
|
DownloadAppData get_app_data();
|
||||||
|
void set_app_data(DownloadAppData data);
|
||||||
|
// set only before version file is downloaded, to keep information to show info dialog about no updates
|
||||||
|
// should never change during thread run
|
||||||
|
std::atomic_bool m_triggered_by_user {false};
|
||||||
|
bool get_triggered_by_user() const { return m_triggered_by_user; }
|
||||||
|
};
|
||||||
|
|
||||||
|
AppUpdater::priv::priv() :
|
||||||
|
m_cancel (false)
|
||||||
|
#ifdef __linux__
|
||||||
|
, m_default_dest_folder (boost::filesystem::path("/tmp"))
|
||||||
|
#else
|
||||||
|
, m_default_dest_folder (boost::filesystem::path(data_dir()) / "cache")
|
||||||
|
#endif //_WIN32
|
||||||
|
{
|
||||||
|
boost::filesystem::path downloads_path = boost::filesystem::path(get_downloads_path());
|
||||||
|
if (!downloads_path.empty()) {
|
||||||
|
m_default_dest_folder = std::move(downloads_path);
|
||||||
|
}
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "App updater default download path: " << m_default_dest_folder; //lm:Is this an error? // dk: changed to trace
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AppUpdater::priv::http_get_file(const std::string& url, size_t size_limit, std::function<bool(Http::Progress)> progress_fn, std::function<bool(std::string /*body*/, std::string& error_message)> complete_fn, std::string& error_message) const
|
||||||
|
{
|
||||||
|
bool res = false;
|
||||||
|
Http::get(url)
|
||||||
|
.size_limit(size_limit)
|
||||||
|
.on_progress([&, progress_fn](Http::Progress progress, bool& cancel) {
|
||||||
|
// progress function returns true as success (to continue)
|
||||||
|
cancel = (this->m_cancel ? true : !progress_fn(std::move(progress)));
|
||||||
|
if (cancel) {
|
||||||
|
error_message = GUI::format("Error getting: `%1%`: Download was canceled.", //lm:typo //dk: am i blind? :)
|
||||||
|
url);
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "AppUpdater::priv::http_get_file message: "<< error_message;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_error([&](std::string body, std::string error, unsigned http_status) {
|
||||||
|
error_message = GUI::format("Error getting: `%1%`: HTTP %2%, %3%",
|
||||||
|
url,
|
||||||
|
http_status,
|
||||||
|
error);
|
||||||
|
BOOST_LOG_TRIVIAL(error) << error_message;
|
||||||
|
})
|
||||||
|
.on_complete([&](std::string body, unsigned /* http_status */) {
|
||||||
|
assert(complete_fn != nullptr);
|
||||||
|
res = complete_fn(body, error_message);
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& data) const
|
||||||
|
{
|
||||||
|
boost::filesystem::path dest_path;
|
||||||
|
size_t last_gui_progress = 0;
|
||||||
|
size_t expected_size = data.size;
|
||||||
|
dest_path = data.target_path;
|
||||||
|
assert(!dest_path.empty());
|
||||||
|
if (dest_path.empty())
|
||||||
|
{
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Download from " << data.url << " could not start. Destination path is empty.";
|
||||||
|
return boost::filesystem::path();
|
||||||
|
}
|
||||||
|
std::string error_message;
|
||||||
|
bool res = http_get_file(data.url, 130 * 1024 * 1024 //2.4.0 windows installer is 65MB //lm:I don't know, but larger. The binaries will grow. // dk: changed to 130, to have 100% more space. We should put this information into version file.
|
||||||
|
// on_progress
|
||||||
|
, [&last_gui_progress, expected_size](Http::Progress progress) {
|
||||||
|
// size check
|
||||||
|
if (progress.dltotal > 0 && progress.dltotal > expected_size) {
|
||||||
|
std::string message = GUI::format("Downloading new %1% has failed. The file has incorrect file size. Aborting download.\nExpected size: %2%\nDownload size: %3%", SLIC3R_APP_NAME, expected_size, progress.dltotal);
|
||||||
|
BOOST_LOG_TRIVIAL(error) << message;
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED);
|
||||||
|
evt->SetString(message);
|
||||||
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
|
return false;
|
||||||
|
} else if (progress.dltotal > 0 && progress.dltotal < expected_size) {
|
||||||
|
//lm:When will this happen? Is that not an error? // dk: It is possible error, but we cannot know until the download is finished. Somehow the total size can grow during the download.
|
||||||
|
BOOST_LOG_TRIVIAL(info) << GUI::format("Downloading new %1% has incorrect size. The download will continue. \nExpected size: %2%\nDownload size: %3%", SLIC3R_APP_NAME, expected_size, progress.dltotal);
|
||||||
|
}
|
||||||
|
// progress event
|
||||||
|
size_t gui_progress = progress.dltotal > 0 ? 100 * progress.dlnow / progress.dltotal : 0;
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "App download " << gui_progress << "% " << progress.dlnow << " of " << progress.dltotal;
|
||||||
|
if (last_gui_progress < gui_progress && (last_gui_progress != 0 || gui_progress != 100)) {
|
||||||
|
last_gui_progress = gui_progress;
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS);
|
||||||
|
evt->SetString(GUI::from_u8(std::to_string(gui_progress)));
|
||||||
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// on_complete
|
||||||
|
, [dest_path, expected_size](std::string body, std::string& error_message){
|
||||||
|
// Size check. Does always 1 char == 1 byte?
|
||||||
|
size_t body_size = body.size();
|
||||||
|
if (body_size != expected_size) {
|
||||||
|
//lm:UI message? // dk: changed. Now it propagates to UI.
|
||||||
|
error_message = GUI::format("Downloaded file has wrong size. Expected size: %1% Downloaded size: %2%", expected_size, body_size);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boost::filesystem::path tmp_path = dest_path;
|
||||||
|
tmp_path += format(".%1%%2%", get_current_pid(), ".download");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
boost::filesystem::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc);
|
||||||
|
file.write(body.c_str(), body.size());
|
||||||
|
file.close();
|
||||||
|
boost::filesystem::rename(tmp_path, dest_path);
|
||||||
|
}
|
||||||
|
catch (const std::exception&)
|
||||||
|
{
|
||||||
|
error_message = GUI::format("Failed to write and move %1% to %2%", tmp_path, dest_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
, error_message
|
||||||
|
);
|
||||||
|
if (!res)
|
||||||
|
{
|
||||||
|
if (this->m_cancel)
|
||||||
|
{
|
||||||
|
BOOST_LOG_TRIVIAL(info) << error_message; //lm:Is this an error? // dk: changed to info
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED); // FAILED with empty msg only closes progress notification
|
||||||
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
|
} else {
|
||||||
|
std::string message = GUI::format("Downloading new %1% has failed:\n%2%", SLIC3R_APP_NAME, error_message);
|
||||||
|
BOOST_LOG_TRIVIAL(error) << message;
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED);
|
||||||
|
evt->SetString(message);
|
||||||
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
|
}
|
||||||
|
return boost::filesystem::path();
|
||||||
|
}
|
||||||
|
|
||||||
|
return dest_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AppUpdater::priv::run_downloaded_file(boost::filesystem::path path)
|
||||||
|
{
|
||||||
|
assert(!path.empty());
|
||||||
|
return run_file(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppUpdater::priv::version_check(const std::string& version_check_url)
|
||||||
|
{
|
||||||
|
assert(!version_check_url.empty());
|
||||||
|
std::string error_message;
|
||||||
|
bool res = http_get_file(version_check_url, 1024
|
||||||
|
// on_progress
|
||||||
|
, [](Http::Progress progress) { return true; }
|
||||||
|
// on_complete
|
||||||
|
, [&](std::string body, std::string& error_message) {
|
||||||
|
boost::trim(body);
|
||||||
|
parse_version_string(body);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
, error_message
|
||||||
|
);
|
||||||
|
//lm:In case the internet is not available, it will report no updates if run by user.
|
||||||
|
// We might save a flag that we don't know or try to run the version_check again, reporting
|
||||||
|
// the failure.
|
||||||
|
// dk: changed to download version every time. Dialog will show if m_triggered_by_user.
|
||||||
|
if (!res) {
|
||||||
|
std::string message = GUI::format("Downloading %1% version file has failed:\n%2%", SLIC3R_APP_NAME, error_message);
|
||||||
|
BOOST_LOG_TRIVIAL(error) << message;
|
||||||
|
if (m_triggered_by_user) {
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED);
|
||||||
|
evt->SetString(message);
|
||||||
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppUpdater::priv::parse_version_string(const std::string& body)
|
||||||
|
{
|
||||||
|
size_t start = body.find('[');
|
||||||
|
if (start == std::string::npos) {
|
||||||
|
#if 0
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Could not find property tree in version file. Starting old parsing.";
|
||||||
|
parse_version_string_old(body);
|
||||||
|
return;
|
||||||
|
#endif // 0
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Could not find property tree in version file. Checking for application update has failed.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string tree_string = body.substr(start);
|
||||||
|
boost::property_tree::ptree tree;
|
||||||
|
std::stringstream ss(tree_string);
|
||||||
|
try {
|
||||||
|
boost::property_tree::read_ini(ss, tree);
|
||||||
|
} catch (const boost::property_tree::ini_parser::ini_parser_error& err) {
|
||||||
|
//throw Slic3r::RuntimeError(format("Failed reading version file property tree Error: \"%1%\" at line %2%. \nTree:\n%3%", err.message(), err.line(), tree_string).c_str());
|
||||||
|
BOOST_LOG_TRIVIAL(error) << format("Failed reading version file property tree Error: \"%1%\" at line %2%. \nTree:\n%3%", err.message(), err.line(), tree_string);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadAppData new_data;
|
||||||
|
|
||||||
|
for (const auto& section : tree) {
|
||||||
|
std::string section_name = section.first;
|
||||||
|
|
||||||
|
// online release version info
|
||||||
|
if (section_name ==
|
||||||
|
#ifdef _WIN32
|
||||||
|
"release:win64"
|
||||||
|
#elif __APPLE__
|
||||||
|
"release:osx"
|
||||||
|
#else
|
||||||
|
"release:linux"
|
||||||
|
#endif
|
||||||
|
//lm:Related to the ifdefs. We should also support BSD, which behaves similar to Linux in most cases.
|
||||||
|
// Unless you have a reason not to, I would consider doing _WIN32, elif __APPLE__, else ... Not just here.
|
||||||
|
// dk: so its ok now or we need to specify BSD?
|
||||||
|
) {
|
||||||
|
for (const auto& data : section.second) {
|
||||||
|
if (data.first == "url") {
|
||||||
|
new_data.url = data.second.data();
|
||||||
|
new_data.target_path = m_default_dest_folder / AppUpdater::get_filename_from_url(new_data.url);
|
||||||
|
BOOST_LOG_TRIVIAL(error) << format("parsing version string: url: %1%", new_data.url);
|
||||||
|
} else if (data.first == "size"){
|
||||||
|
new_data.size = std::stoi(data.second.data());
|
||||||
|
BOOST_LOG_TRIVIAL(error) << format("parsing version string: expected size: %1%", new_data.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// released versions - to be send to UI layer
|
||||||
|
if (section_name == "common") {
|
||||||
|
std::vector<std::string> prerelease_versions;
|
||||||
|
for (const auto& data : section.second) {
|
||||||
|
// release version - save and send to UI layer
|
||||||
|
if (data.first == "release") {
|
||||||
|
std::string version = data.second.data();
|
||||||
|
boost::optional<Semver> release_version = Semver::parse(version);
|
||||||
|
if (!release_version) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents from version file: Not a correct semver: `%1%`", version);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new_data.version = release_version;
|
||||||
|
// Send after all data is read
|
||||||
|
/*
|
||||||
|
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
|
||||||
|
evt->SetString(GUI::from_u8(version));
|
||||||
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
|
*/
|
||||||
|
// prerelease versions - write down to be sorted and send to UI layer
|
||||||
|
} else if (data.first == "alpha") {
|
||||||
|
prerelease_versions.emplace_back(data.second.data());
|
||||||
|
} else if (data.first == "beta") {
|
||||||
|
prerelease_versions.emplace_back(data.second.data());
|
||||||
|
} else if (data.first == "rc") {
|
||||||
|
prerelease_versions.emplace_back(data.second.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// find recent version that is newer than last full release.
|
||||||
|
boost::optional<Semver> recent_version;
|
||||||
|
std::string version_string;
|
||||||
|
for (const std::string& ver_string : prerelease_versions) {
|
||||||
|
boost::optional<Semver> ver = Semver::parse(ver_string);
|
||||||
|
if (ver && *new_data.version < *ver && ((recent_version && *recent_version < *ver) || !recent_version)) {
|
||||||
|
recent_version = ver;
|
||||||
|
version_string = ver_string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// send prerelease version to UI layer
|
||||||
|
if (recent_version) {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version_string);
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE);
|
||||||
|
evt->SetString(GUI::from_u8(version_string));
|
||||||
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(!new_data.url.empty());
|
||||||
|
assert(new_data.version);
|
||||||
|
// save
|
||||||
|
set_app_data(new_data);
|
||||||
|
// send
|
||||||
|
std::string version = new_data.version.get().to_string();
|
||||||
|
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
|
||||||
|
evt->SetString(GUI::from_u8(version));
|
||||||
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0 //lm:is this meant to be ressurected? //dk: it is code that parses PrusaSlicer.version2 in 2.4.0, It was deleted from PresetUpdater.cpp and I would keep it here for possible reference.
|
||||||
|
void AppUpdater::priv::parse_version_string_old(const std::string& body) const
|
||||||
|
{
|
||||||
|
|
||||||
|
// release version
|
||||||
|
std::string version;
|
||||||
|
const auto first_nl_pos = body.find_first_of("\n\r");
|
||||||
|
if (first_nl_pos != std::string::npos)
|
||||||
|
version = body.substr(0, first_nl_pos);
|
||||||
|
else
|
||||||
|
version = body;
|
||||||
|
boost::optional<Semver> release_version = Semver::parse(version);
|
||||||
|
if (!release_version) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
|
||||||
|
evt->SetString(GUI::from_u8(version));
|
||||||
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
|
|
||||||
|
// alpha / beta version
|
||||||
|
std::vector<std::string> prerelease_versions;
|
||||||
|
size_t nexn_nl_pos = first_nl_pos;
|
||||||
|
while (nexn_nl_pos != std::string::npos && body.size() > nexn_nl_pos + 1) {
|
||||||
|
const auto last_nl_pos = nexn_nl_pos;
|
||||||
|
nexn_nl_pos = body.find_first_of("\n\r", last_nl_pos + 1);
|
||||||
|
std::string line;
|
||||||
|
if (nexn_nl_pos == std::string::npos)
|
||||||
|
line = body.substr(last_nl_pos + 1);
|
||||||
|
else
|
||||||
|
line = body.substr(last_nl_pos + 1, nexn_nl_pos - last_nl_pos - 1);
|
||||||
|
|
||||||
|
// alpha
|
||||||
|
if (line.substr(0, 6) == "alpha=") {
|
||||||
|
version = line.substr(6);
|
||||||
|
if (!Semver::parse(version)) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents for alpha release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prerelease_versions.emplace_back(version);
|
||||||
|
// beta
|
||||||
|
}
|
||||||
|
else if (line.substr(0, 5) == "beta=") {
|
||||||
|
version = line.substr(5);
|
||||||
|
if (!Semver::parse(version)) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents for beta release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prerelease_versions.emplace_back(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// find recent version that is newer than last full release.
|
||||||
|
boost::optional<Semver> recent_version;
|
||||||
|
for (const std::string& ver_string : prerelease_versions) {
|
||||||
|
boost::optional<Semver> ver = Semver::parse(ver_string);
|
||||||
|
if (ver && *release_version < *ver && ((recent_version && *recent_version < *ver) || !recent_version)) {
|
||||||
|
recent_version = ver;
|
||||||
|
version = ver_string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (recent_version) {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE);
|
||||||
|
evt->SetString(GUI::from_u8(version));
|
||||||
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // 0
|
||||||
|
|
||||||
|
DownloadAppData AppUpdater::priv::get_app_data()
|
||||||
|
{
|
||||||
|
const std::lock_guard<std::mutex> lock(m_data_mutex);
|
||||||
|
DownloadAppData ret_val(m_online_version_data);
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppUpdater::priv::set_app_data(DownloadAppData data)
|
||||||
|
{
|
||||||
|
const std::lock_guard<std::mutex> lock(m_data_mutex);
|
||||||
|
m_online_version_data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppUpdater::AppUpdater()
|
||||||
|
:p(new priv())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
AppUpdater::~AppUpdater()
|
||||||
|
{
|
||||||
|
if (p && p->m_thread.joinable()) {
|
||||||
|
// This will stop transfers being done by the thread, if any.
|
||||||
|
// Cancelling takes some time, but should complete soon enough.
|
||||||
|
p->m_cancel = true;
|
||||||
|
p->m_thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void AppUpdater::sync_download()
|
||||||
|
{
|
||||||
|
assert(p);
|
||||||
|
// join thread first - it could have been in sync_version
|
||||||
|
if (p->m_thread.joinable()) {
|
||||||
|
// This will stop transfers being done by the thread, if any.
|
||||||
|
// Cancelling takes some time, but should complete soon enough.
|
||||||
|
p->m_cancel = true;
|
||||||
|
p->m_thread.join();
|
||||||
|
}
|
||||||
|
p->m_cancel = false;
|
||||||
|
|
||||||
|
DownloadAppData input_data = p->get_app_data();
|
||||||
|
assert(!input_data.url.empty());
|
||||||
|
|
||||||
|
p->m_thread = std::thread(
|
||||||
|
[this, input_data]() {
|
||||||
|
p->m_download_ongoing = true;
|
||||||
|
if (boost::filesystem::path dest_path = p->download_file(input_data); boost::filesystem::exists(dest_path)){
|
||||||
|
if (input_data.start_after) {
|
||||||
|
p->run_downloaded_file(std::move(dest_path));
|
||||||
|
} else {
|
||||||
|
GUI::desktop_open_folder(dest_path.parent_path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p->m_download_ongoing = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppUpdater::sync_version(const std::string& version_check_url, bool from_user)
|
||||||
|
{
|
||||||
|
assert(p);
|
||||||
|
// join thread first - it could have been in sync_download
|
||||||
|
if (p->m_thread.joinable()) {
|
||||||
|
// This will stop transfers being done by the thread, if any.
|
||||||
|
// Cancelling takes some time, but should complete soon enough.
|
||||||
|
p->m_cancel = true;
|
||||||
|
p->m_thread.join();
|
||||||
|
}
|
||||||
|
p->m_triggered_by_user = from_user;
|
||||||
|
p->m_cancel = false;
|
||||||
|
p->m_thread = std::thread(
|
||||||
|
[this, version_check_url]() {
|
||||||
|
p->version_check(version_check_url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppUpdater::cancel()
|
||||||
|
{
|
||||||
|
p->m_cancel = true;
|
||||||
|
}
|
||||||
|
bool AppUpdater::cancel_callback()
|
||||||
|
{
|
||||||
|
cancel();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AppUpdater::get_default_dest_folder()
|
||||||
|
{
|
||||||
|
return p->m_default_dest_folder.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AppUpdater::get_filename_from_url(const std::string& url)
|
||||||
|
{
|
||||||
|
size_t slash = url.rfind('/');
|
||||||
|
return (slash != std::string::npos ? url.substr(slash + 1) : url);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AppUpdater::get_file_extension_from_url(const std::string& url)
|
||||||
|
{
|
||||||
|
size_t dot = url.rfind('.');
|
||||||
|
return (dot != std::string::npos ? url.substr(dot) : url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppUpdater::set_app_data(DownloadAppData data)
|
||||||
|
{
|
||||||
|
p->set_app_data(std::move(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadAppData AppUpdater::get_app_data()
|
||||||
|
{
|
||||||
|
return p->get_app_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AppUpdater::get_triggered_by_user() const
|
||||||
|
{
|
||||||
|
return p->get_triggered_by_user();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AppUpdater::get_download_ongoing() const
|
||||||
|
{
|
||||||
|
return p->get_download_ongoing();
|
||||||
|
}
|
||||||
|
|
||||||
|
} //namespace Slic3r
|
66
src/slic3r/Utils/AppUpdater.hpp
Normal file
66
src/slic3r/Utils/AppUpdater.hpp
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#ifndef slic3r_AppUpdate_hpp_
|
||||||
|
#define slic3r_AppUpdate_hpp_
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
#include "libslic3r/Utils.hpp"
|
||||||
|
#include "wx/event.h"
|
||||||
|
|
||||||
|
//class boost::filesystem::path;
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
// implmented at MacUtils.mm
|
||||||
|
std::string get_downloads_path_mac();
|
||||||
|
#endif //__APPLE__
|
||||||
|
|
||||||
|
struct DownloadAppData
|
||||||
|
{
|
||||||
|
std::string url;
|
||||||
|
bool start_after;
|
||||||
|
boost::optional<Semver> version;
|
||||||
|
size_t size;
|
||||||
|
boost::filesystem::path target_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AppUpdater
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AppUpdater();
|
||||||
|
~AppUpdater();
|
||||||
|
AppUpdater(AppUpdater&&) = delete;
|
||||||
|
AppUpdater(const AppUpdater&) = delete;
|
||||||
|
AppUpdater& operator=(AppUpdater&&) = delete;
|
||||||
|
AppUpdater& operator=(const AppUpdater&) = delete;
|
||||||
|
|
||||||
|
// downloads app file
|
||||||
|
void sync_download();
|
||||||
|
// downloads version file
|
||||||
|
void sync_version(const std::string& version_check_url, bool from_user);
|
||||||
|
void cancel();
|
||||||
|
bool cancel_callback();
|
||||||
|
|
||||||
|
std::string get_default_dest_folder();
|
||||||
|
|
||||||
|
static std::string get_filename_from_url(const std::string& url);
|
||||||
|
static std::string get_file_extension_from_url(const std::string& url);
|
||||||
|
|
||||||
|
// atomic bool
|
||||||
|
bool get_triggered_by_user() const;
|
||||||
|
bool get_download_ongoing() const;
|
||||||
|
// mutex access
|
||||||
|
void set_app_data(DownloadAppData data);
|
||||||
|
DownloadAppData get_app_data();
|
||||||
|
private:
|
||||||
|
struct priv;
|
||||||
|
std::unique_ptr<priv> p;
|
||||||
|
};
|
||||||
|
|
||||||
|
wxDECLARE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
|
||||||
|
wxDECLARE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
|
||||||
|
wxDECLARE_EVENT(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, wxCommandEvent);
|
||||||
|
wxDECLARE_EVENT(EVT_SLIC3R_APP_DOWNLOAD_FAILED, wxCommandEvent);
|
||||||
|
wxDECLARE_EVENT(EVT_SLIC3R_APP_OPEN_FAILED, wxCommandEvent);
|
||||||
|
} //namespace Slic3r
|
||||||
|
#endif
|
|
@ -207,7 +207,6 @@ size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
|
||||||
auto self = static_cast<priv*>(userp);
|
auto self = static_cast<priv*>(userp);
|
||||||
const char *cdata = static_cast<char*>(data);
|
const char *cdata = static_cast<char*>(data);
|
||||||
const size_t realsize = size * nmemb;
|
const size_t realsize = size * nmemb;
|
||||||
|
|
||||||
const size_t limit = self->limit > 0 ? self->limit : DEFAULT_SIZE_LIMIT;
|
const size_t limit = self->limit > 0 ? self->limit : DEFAULT_SIZE_LIMIT;
|
||||||
if (self->buffer.size() + realsize > limit) {
|
if (self->buffer.size() + realsize > limit) {
|
||||||
// This makes curl_easy_perform return CURLE_WRITE_ERROR
|
// This makes curl_easy_perform return CURLE_WRITE_ERROR
|
||||||
|
|
18
src/slic3r/Utils/MacUtils.mm
Normal file
18
src/slic3r/Utils/MacUtils.mm
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#import "AppUpdater.hpp"
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// AppUpdater.hpp
|
||||||
|
std::string get_downloads_path_mac()
|
||||||
|
{
|
||||||
|
// 1)
|
||||||
|
NSArray * paths = NSSearchPathForDirectoriesInDomains (NSDownloadsDirectory, NSUserDomainMask, YES);
|
||||||
|
NSString * desktopPath = [paths objectAtIndex:0];
|
||||||
|
return std::string([desktopPath UTF8String]);
|
||||||
|
// 2)
|
||||||
|
//[NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Downloads"]];
|
||||||
|
//return std::string();
|
||||||
|
}
|
||||||
|
}
|
|
@ -135,10 +135,6 @@ struct Updates
|
||||||
std::vector<Update> updates;
|
std::vector<Update> updates;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
|
|
||||||
wxDEFINE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
|
|
||||||
|
|
||||||
struct PresetUpdater::priv
|
struct PresetUpdater::priv
|
||||||
{
|
{
|
||||||
std::vector<Index> index_db;
|
std::vector<Index> index_db;
|
||||||
|
@ -162,8 +158,6 @@ struct PresetUpdater::priv
|
||||||
void set_download_prefs(AppConfig *app_config);
|
void set_download_prefs(AppConfig *app_config);
|
||||||
bool get_file(const std::string &url, const fs::path &target_path) const;
|
bool get_file(const std::string &url, const fs::path &target_path) const;
|
||||||
void prune_tmps() const;
|
void prune_tmps() const;
|
||||||
void sync_version() const;
|
|
||||||
void parse_version_string(const std::string& body) const;
|
|
||||||
void sync_config(const VendorMap vendors);
|
void sync_config(const VendorMap vendors);
|
||||||
|
|
||||||
void check_install_indices() const;
|
void check_install_indices() const;
|
||||||
|
@ -238,101 +232,6 @@ void PresetUpdater::priv::prune_tmps() const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get Slic3rPE version available online, save in AppConfig.
|
|
||||||
void PresetUpdater::priv::sync_version() const
|
|
||||||
{
|
|
||||||
if (! enabled_version_check) { return; }
|
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << format("Downloading %1% online version from: `%2%`", SLIC3R_APP_NAME, version_check_url);
|
|
||||||
|
|
||||||
Http::get(version_check_url)
|
|
||||||
.size_limit(SLIC3R_VERSION_BODY_MAX)
|
|
||||||
.on_progress([this](Http::Progress, bool &cancel) {
|
|
||||||
cancel = this->cancel;
|
|
||||||
})
|
|
||||||
.on_error([&](std::string body, std::string error, unsigned http_status) {
|
|
||||||
(void)body;
|
|
||||||
BOOST_LOG_TRIVIAL(error) << format("Error getting: `%1%`: HTTP %2%, %3%",
|
|
||||||
version_check_url,
|
|
||||||
http_status,
|
|
||||||
error);
|
|
||||||
})
|
|
||||||
.on_complete([&](std::string body, unsigned /* http_status */) {
|
|
||||||
boost::trim(body);
|
|
||||||
parse_version_string(body);
|
|
||||||
})
|
|
||||||
.perform_sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parses version string obtained in sync_version() and sends events to UI thread.
|
|
||||||
// Version string must contain release version on first line. Follows non-mandatory alpha / beta releases on following lines (alpha=2.0.0-alpha1).
|
|
||||||
void PresetUpdater::priv::parse_version_string(const std::string& body) const
|
|
||||||
{
|
|
||||||
// release version
|
|
||||||
std::string version;
|
|
||||||
const auto first_nl_pos = body.find_first_of("\n\r");
|
|
||||||
if (first_nl_pos != std::string::npos)
|
|
||||||
version = body.substr(0, first_nl_pos);
|
|
||||||
else
|
|
||||||
version = body;
|
|
||||||
boost::optional<Semver> release_version = Semver::parse(version);
|
|
||||||
if (!release_version) {
|
|
||||||
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
|
|
||||||
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
|
|
||||||
evt->SetString(GUI::from_u8(version));
|
|
||||||
GUI::wxGetApp().QueueEvent(evt);
|
|
||||||
|
|
||||||
// alpha / beta version
|
|
||||||
std::vector<std::string> prerelease_versions;
|
|
||||||
size_t nexn_nl_pos = first_nl_pos;
|
|
||||||
while (nexn_nl_pos != std::string::npos && body.size() > nexn_nl_pos + 1) {
|
|
||||||
const auto last_nl_pos = nexn_nl_pos;
|
|
||||||
nexn_nl_pos = body.find_first_of("\n\r", last_nl_pos + 1);
|
|
||||||
std::string line;
|
|
||||||
if (nexn_nl_pos == std::string::npos)
|
|
||||||
line = body.substr(last_nl_pos + 1);
|
|
||||||
else
|
|
||||||
line = body.substr(last_nl_pos + 1, nexn_nl_pos - last_nl_pos - 1);
|
|
||||||
|
|
||||||
// alpha
|
|
||||||
if (line.substr(0, 6) == "alpha=") {
|
|
||||||
version = line.substr(6);
|
|
||||||
if (!Semver::parse(version)) {
|
|
||||||
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents for alpha release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
prerelease_versions.emplace_back(version);
|
|
||||||
// beta
|
|
||||||
}
|
|
||||||
else if (line.substr(0, 5) == "beta=") {
|
|
||||||
version = line.substr(5);
|
|
||||||
if (!Semver::parse(version)) {
|
|
||||||
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents for beta release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
prerelease_versions.emplace_back(version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// find recent version that is newer than last full release.
|
|
||||||
boost::optional<Semver> recent_version;
|
|
||||||
for (const std::string& ver_string : prerelease_versions) {
|
|
||||||
boost::optional<Semver> ver = Semver::parse(ver_string);
|
|
||||||
if (ver && *release_version < *ver && ((recent_version && *recent_version < *ver) || !recent_version)) {
|
|
||||||
recent_version = ver;
|
|
||||||
version = ver_string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (recent_version) {
|
|
||||||
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
|
|
||||||
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE);
|
|
||||||
evt->SetString(GUI::from_u8(version));
|
|
||||||
GUI::wxGetApp().QueueEvent(evt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download vendor indices. Also download new bundles if an index indicates there's a new one available.
|
// Download vendor indices. Also download new bundles if an index indicates there's a new one available.
|
||||||
// Both are saved in cache.
|
// Both are saved in cache.
|
||||||
void PresetUpdater::priv::sync_config(const VendorMap vendors)
|
void PresetUpdater::priv::sync_config(const VendorMap vendors)
|
||||||
|
@ -743,7 +642,6 @@ void PresetUpdater::sync(PresetBundle *preset_bundle)
|
||||||
|
|
||||||
p->thread = std::thread([this, vendors]() {
|
p->thread = std::thread([this, vendors]() {
|
||||||
this->p->prune_tmps();
|
this->p->prune_tmps();
|
||||||
this->p->sync_version();
|
|
||||||
this->p->sync_config(std::move(vendors));
|
this->p->sync_config(std::move(vendors));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ private:
|
||||||
std::unique_ptr<priv> p;
|
std::unique_ptr<priv> p;
|
||||||
};
|
};
|
||||||
|
|
||||||
wxDECLARE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
|
//wxDECLARE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
|
||||||
wxDECLARE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
|
//wxDECLARE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2055,6 +2055,50 @@ TEST_CASE("Voronoi missing vertex 3", "[VoronoiMissingVertex3]")
|
||||||
// REQUIRE(!has_missing_voronoi_vertices(poly, vd));
|
// REQUIRE(!has_missing_voronoi_vertices(poly, vd));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Voronoi missing vertex 4", "[VoronoiMissingVertex4]")
|
||||||
|
{
|
||||||
|
// Probably the reason why Voronoi vertex is missing is that 19299999 and 19300000 are very close.
|
||||||
|
Polygon polygon_1 = {
|
||||||
|
Point(27000000, -18900000),
|
||||||
|
Point(27000000, 20000000),
|
||||||
|
Point(19000000, 20000000),
|
||||||
|
Point(19000000, 19299999),
|
||||||
|
Point(26000000, -18000000),
|
||||||
|
Point(-19000000, -18000000),
|
||||||
|
Point(-27000000, 19300000),
|
||||||
|
Point(-19000000, 19300000),
|
||||||
|
Point(-19000000, 20000000),
|
||||||
|
Point(-28000000, 20000000),
|
||||||
|
Point(-20000000, -18900000),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Maybe this is the same case as the previous, but the missing Voronoi vertex is different.
|
||||||
|
Polygon polygon_2 = {
|
||||||
|
Point(27000000, -18900000),
|
||||||
|
Point(27000000, 20000000),
|
||||||
|
Point(19000000, 20000000),
|
||||||
|
Point(19000000, 19299999),
|
||||||
|
Point(19000000, -18000000), // Just this point is different other points are the same as previous.
|
||||||
|
Point(-19000000, -18000000),
|
||||||
|
Point(-27000000, 19300000),
|
||||||
|
Point(-19000000, 19300000),
|
||||||
|
Point(-19000000, 20000000),
|
||||||
|
Point(-28000000, 20000000),
|
||||||
|
Point(-20000000, -18900000),
|
||||||
|
};
|
||||||
|
|
||||||
|
Geometry::VoronoiDiagram vd_1;
|
||||||
|
Geometry::VoronoiDiagram vd_2;
|
||||||
|
Lines lines_1 = to_lines(polygon_1);
|
||||||
|
Lines lines_2 = to_lines(polygon_2);
|
||||||
|
construct_voronoi(lines_1.begin(), lines_1.end(), &vd_1);
|
||||||
|
construct_voronoi(lines_2.begin(), lines_2.end(), &vd_2);
|
||||||
|
#ifdef VORONOI_DEBUG_OUT
|
||||||
|
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex4-1-out.svg").c_str(), vd_1, Points(), lines_1);
|
||||||
|
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex4-2-out.svg").c_str(), vd_2, Points(), lines_2);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// In this case, the Voronoi vertex (146873, -146873) is included twice.
|
// In this case, the Voronoi vertex (146873, -146873) is included twice.
|
||||||
// Also, near to those duplicate Voronoi vertices is another Voronoi vertex (146872, -146872).
|
// Also, near to those duplicate Voronoi vertices is another Voronoi vertex (146872, -146872).
|
||||||
// Rotating the polygon will help solve this problem, but then there arise three very close Voronoi vertices.
|
// Rotating the polygon will help solve this problem, but then there arise three very close Voronoi vertices.
|
||||||
|
|
Loading…
Add table
Reference in a new issue