Merge branch 'master' into fs_emboss

# Conflicts:
#	src/libslic3r/Technologies.hpp
#	src/slic3r/CMakeLists.txt
#	src/slic3r/GUI/GUI_App.cpp
#	src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
This commit is contained in:
Filip Sykala 2022-05-31 11:14:53 +02:00
commit e0d5505413
18 changed files with 793 additions and 218 deletions

2
.gitignore vendored
View file

@ -18,4 +18,4 @@ local-lib
build-linux/* build-linux/*
deps/build-linux/* deps/build-linux/*
**/.DS_Store **/.DS_Store
/.idea/ **/.idea/

View file

@ -1,7 +1,7 @@
#include <cassert> #include <cassert>
#include "PresetBundle.hpp"
#include "libslic3r.h" #include "libslic3r.h"
#include "PresetBundle.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include "Model.hpp" #include "Model.hpp"
#include "format.hpp" #include "format.hpp"
@ -453,6 +453,11 @@ void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::
presets.get_edited_preset().config.apply_only(presets.get_selected_preset().config, unselected_options); presets.get_edited_preset().config.apply_only(presets.get_selected_preset().config, unselected_options);
} }
#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
if (type == Preset::TYPE_PRINTER)
copy_bed_model_and_texture_if_needed(presets.get_edited_preset().config);
#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
// Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini
presets.save_current_preset(new_name); presets.save_current_preset(new_name);
// Mark the print & filament enabled if they are compatible with the currently selected preset. // Mark the print & filament enabled if they are compatible with the currently selected preset.
@ -1860,4 +1865,32 @@ void PresetBundle::set_default_suppressed(bool default_suppressed)
printers.set_default_suppressed(default_suppressed); printers.set_default_suppressed(default_suppressed);
} }
#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
void copy_bed_model_and_texture_if_needed(DynamicPrintConfig& config)
{
const boost::filesystem::path user_dir = boost::filesystem::absolute(boost::filesystem::path(data_dir()) / "printer").make_preferred();
const boost::filesystem::path res_dir = boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
auto do_copy = [&user_dir, &res_dir](ConfigOptionString* cfg, const std::string& type) {
if (cfg == nullptr || cfg->value.empty())
return;
const boost::filesystem::path src_dir = boost::filesystem::absolute(boost::filesystem::path(cfg->value)).make_preferred().parent_path();
if (src_dir != user_dir && src_dir.parent_path() != res_dir) {
const std::string dst_value = (user_dir / boost::filesystem::path(cfg->value).filename()).string();
std::string error;
if (copy_file_inner(cfg->value, dst_value, error) == SUCCESS)
cfg->value = dst_value;
else {
BOOST_LOG_TRIVIAL(error) << "Copying from " << cfg->value << " to " << dst_value << " failed. Unable to set custom bed " << type << ". [" << error << "]";
cfg->value = "";
}
}
};
do_copy(config.option<ConfigOptionString>("bed_custom_texture"), "texture");
do_copy(config.option<ConfigOptionString>("bed_custom_model"), "model");
}
#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
} // namespace Slic3r } // namespace Slic3r

View file

@ -178,6 +178,12 @@ private:
ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute) ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)
#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
// Copies bed texture and model files to 'data_dir()\printer' folder, if needed
// and updates the config accordingly
extern void copy_bed_model_and_texture_if_needed(DynamicPrintConfig& config);
#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
} // namespace Slic3r } // namespace Slic3r
#endif /* slic3r_PresetBundle_hpp_ */ #endif /* slic3r_PresetBundle_hpp_ */

View file

@ -288,7 +288,7 @@ template<unsigned MAX_ITER>
struct RotfinderBoilerplate { struct RotfinderBoilerplate {
static constexpr unsigned MAX_TRIES = MAX_ITER; static constexpr unsigned MAX_TRIES = MAX_ITER;
int status = 0; int status = 0, prev_status = 0;
TriangleMesh mesh; TriangleMesh mesh;
unsigned max_tries; unsigned max_tries;
const RotOptimizeParams &params; const RotOptimizeParams &params;
@ -314,13 +314,20 @@ struct RotfinderBoilerplate {
RotfinderBoilerplate(const ModelObject &mo, const RotOptimizeParams &p) RotfinderBoilerplate(const ModelObject &mo, const RotOptimizeParams &p)
: mesh{get_mesh_to_rotate(mo)} : mesh{get_mesh_to_rotate(mo)}
, params{p}
, max_tries(p.accuracy() * MAX_TRIES) , max_tries(p.accuracy() * MAX_TRIES)
{ , params{p}
{}
void statusfn() {
int s = status * 100 / max_tries;
if (s != prev_status) {
params.statuscb()(s);
prev_status = s;
}
++status;
} }
void statusfn() { params.statuscb()(++status * 100.0 / max_tries); }
bool stopcond() { return ! params.statuscb()(-1); } bool stopcond() { return ! params.statuscb()(-1); }
}; };

View file

@ -91,8 +91,7 @@
#define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1) #define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1)
// Enable gizmo grabbers to share common models // Enable gizmo grabbers to share common models
#define ENABLE_GIZMO_GRABBER_REFACTOR (1 && ENABLE_2_5_0_ALPHA1) #define ENABLE_GIZMO_GRABBER_REFACTOR (1 && ENABLE_2_5_0_ALPHA1)
// Disable association to 3mf and stl files if the application is run on Windows 8 or later // Enable copy of custom bed model and texture
#define ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER (1 && ENABLE_2_5_0_ALPHA1) #define ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE (1 && ENABLE_2_5_0_ALPHA1)
#endif // _prusaslicer_technologies_h_ #endif // _prusaslicer_technologies_h_

View file

@ -180,6 +180,7 @@ set(SLIC3R_GUI_SOURCES
GUI/Jobs/Worker.hpp GUI/Jobs/Worker.hpp
GUI/Jobs/BoostThreadWorker.hpp GUI/Jobs/BoostThreadWorker.hpp
GUI/Jobs/BoostThreadWorker.cpp GUI/Jobs/BoostThreadWorker.cpp
GUI/Jobs/UIThreadWorker.hpp
GUI/Jobs/BusyCursorJob.hpp GUI/Jobs/BusyCursorJob.hpp
GUI/Jobs/PlaterWorker.hpp GUI/Jobs/PlaterWorker.hpp
GUI/Jobs/ArrangeJob.hpp GUI/Jobs/ArrangeJob.hpp
@ -268,6 +269,8 @@ set(SLIC3R_GUI_SOURCES
Utils/TCPConsole.hpp Utils/TCPConsole.hpp
Utils/MKS.cpp Utils/MKS.cpp
Utils/MKS.hpp Utils/MKS.hpp
Utils/WinRegistry.cpp
Utils/WinRegistry.hpp
Utils/WxFontUtils.cpp Utils/WxFontUtils.cpp
Utils/WxFontUtils.hpp Utils/WxFontUtils.hpp
) )

View file

@ -1934,10 +1934,7 @@ void ConfigWizard::priv::load_pages()
index->add_page(page_update); index->add_page(page_update);
index->add_page(page_reload_from_disk); index->add_page(page_reload_from_disk);
#ifdef _WIN32 #ifdef _WIN32
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER index->add_page(page_files_association);
if (page_files_association != nullptr)
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
index->add_page(page_files_association);
#endif // _WIN32 #endif // _WIN32
index->add_page(page_mode); index->add_page(page_mode);
@ -2750,32 +2747,20 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0"); app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0");
#ifdef _WIN32 #ifdef _WIN32
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0");
if (page_files_association != nullptr) { app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0");
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER // app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0");
app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0");
app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0");
// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0");
if (wxGetApp().is_editor()) { if (wxGetApp().is_editor()) {
if (page_files_association->associate_3mf()) if (page_files_association->associate_3mf())
wxGetApp().associate_3mf_files(); wxGetApp().associate_3mf_files();
if (page_files_association->associate_stl()) if (page_files_association->associate_stl())
wxGetApp().associate_stl_files(); wxGetApp().associate_stl_files();
}
// else {
// if (page_files_association->associate_gcode())
// wxGetApp().associate_gcode_files();
// }
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
} }
else { // else {
app_config->set("associate_3mf", "0"); // if (page_files_association->associate_gcode())
app_config->set("associate_stl", "0"); // wxGetApp().associate_gcode_files();
// app_config->set("associate_gcode", "0"); // }
}
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
#endif // _WIN32 #endif // _WIN32
page_mode->serialize_mode(app_config); page_mode->serialize_mode(app_config);
@ -2795,6 +2780,10 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
page_diams->apply_custom_config(*custom_config); page_diams->apply_custom_config(*custom_config);
page_temps->apply_custom_config(*custom_config); page_temps->apply_custom_config(*custom_config);
#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
copy_bed_model_and_texture_if_needed(*custom_config);
#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
const std::string profile_name = page_custom->profile_name(); const std::string profile_name = page_custom->profile_name();
preset_bundle->load_config_from_wizard(profile_name, *custom_config); preset_bundle->load_config_from_wizard(profile_name, *custom_config);
} }
@ -2935,11 +2924,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
p->add_page(p->page_update = new PageUpdate(this)); p->add_page(p->page_update = new PageUpdate(this));
p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this));
#ifdef _WIN32 #ifdef _WIN32
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER p->add_page(p->page_files_association = new PageFilesAssociation(this));
// file association is not possible anymore starting with Win 8
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8)
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
p->add_page(p->page_files_association = new PageFilesAssociation(this));
#endif // _WIN32 #endif // _WIN32
p->add_page(p->page_mode = new PageMode(this)); p->add_page(p->page_mode = new PageMode(this));
p->add_page(p->page_firmware = new PageFirmware(this)); p->add_page(p->page_firmware = new PageFirmware(this));

View file

@ -991,11 +991,13 @@ void GCodeViewer::render()
render_toolpaths(); render_toolpaths();
render_shells(); render_shells();
float legend_height = 0.0f; float legend_height = 0.0f;
render_legend(legend_height); if (!m_layers.empty()) {
if (m_sequential_view.current.last != m_sequential_view.endpoints.last) { render_legend(legend_height);
m_sequential_view.marker.set_world_position(m_sequential_view.current_position); if (m_sequential_view.current.last != m_sequential_view.endpoints.last) {
m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset); m_sequential_view.marker.set_world_position(m_sequential_view.current_position);
m_sequential_view.render(legend_height); m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset);
m_sequential_view.render(legend_height);
}
} }
#if ENABLE_GCODE_VIEWER_STATISTICS #if ENABLE_GCODE_VIEWER_STATISTICS
render_statistics(); render_statistics();

View file

@ -59,6 +59,7 @@
#include "../Utils/Process.hpp" #include "../Utils/Process.hpp"
#include "../Utils/MacDarkMode.hpp" #include "../Utils/MacDarkMode.hpp"
#include "../Utils/AppUpdater.hpp" #include "../Utils/AppUpdater.hpp"
#include "../Utils/WinRegistry.hpp"
#include "slic3r/Config/Snapshot.hpp" #include "slic3r/Config/Snapshot.hpp"
#include "ConfigSnapshotDialog.hpp" #include "ConfigSnapshotDialog.hpp"
#include "FirmwareDialog.hpp" #include "FirmwareDialog.hpp"
@ -1204,17 +1205,10 @@ bool GUI_App::on_init_inner()
if (is_editor()) { if (is_editor()) {
#ifdef __WXMSW__ #ifdef __WXMSW__
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER if (app_config->get("associate_3mf") == "1")
// file association is not possible anymore starting with Win 8 associate_3mf_files();
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { if (app_config->get("associate_stl") == "1")
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER associate_stl_files();
if (app_config->get("associate_3mf") == "1")
associate_3mf_files();
if (app_config->get("associate_stl") == "1")
associate_stl_files();
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
}
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
#endif // __WXMSW__ #endif // __WXMSW__
preset_updater = new PresetUpdater(); preset_updater = new PresetUpdater();
@ -1252,15 +1246,8 @@ bool GUI_App::on_init_inner()
} }
else { else {
#ifdef __WXMSW__ #ifdef __WXMSW__
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER if (app_config->get("associate_gcode") == "1")
// file association is not possible anymore starting with Win 8 associate_gcode_files();
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) {
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
if (app_config->get("associate_gcode") == "1")
associate_gcode_files();
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
}
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
#endif // __WXMSW__ #endif // __WXMSW__
} }
@ -2433,23 +2420,16 @@ void GUI_App::open_preferences(const std::string& highlight_option /*= std::stri
this->plater_->refresh_print(); this->plater_->refresh_print();
#ifdef _WIN32 #ifdef _WIN32
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER if (is_editor()) {
// file association is not possible anymore starting with Win 8 if (app_config->get("associate_3mf") == "1")
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { associate_3mf_files();
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER if (app_config->get("associate_stl") == "1")
if (is_editor()) { associate_stl_files();
if (app_config->get("associate_3mf") == "1") }
associate_3mf_files(); else {
if (app_config->get("associate_stl") == "1") if (app_config->get("associate_gcode") == "1")
associate_stl_files(); associate_gcode_files();
}
else {
if (app_config->get("associate_gcode") == "1")
associate_gcode_files();
}
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
} }
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
#endif // _WIN32 #endif // _WIN32
if (mainframe->preferences_dialog->settings_layout_changed()) { if (mainframe->preferences_dialog->settings_layout_changed()) {
@ -3157,119 +3137,22 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* pa
#ifdef __WXMSW__ #ifdef __WXMSW__
static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue)
{
// see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
wchar_t szValueCurrent[1000];
DWORD dwType;
DWORD dwSize = sizeof(szValueCurrent);
int iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize);
bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
if ((iRC != ERROR_SUCCESS) && !bDidntExist)
// an error occurred
return false;
if (!bDidntExist) {
if (dwType != REG_SZ)
// invalid type
return false;
if (::wcscmp(szValueCurrent, pszValue) == 0)
// value already set
return false;
}
DWORD dwDisposition;
HKEY hkey;
iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition);
bool ret = false;
if (iRC == ERROR_SUCCESS) {
iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t));
if (iRC == ERROR_SUCCESS)
ret = true;
}
RegCloseKey(hkey);
return ret;
}
void GUI_App::associate_3mf_files() void GUI_App::associate_3mf_files()
{ {
wchar_t app_path[MAX_PATH]; associate_file_type(L".3mf", L"Prusa.Slicer.1", L"PrusaSlicer", true);
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
std::wstring prog_id = L"Prusa.Slicer.1";
std::wstring prog_desc = L"PrusaSlicer";
std::wstring prog_command = prog_path + L" \"%1\"";
std::wstring reg_base = L"Software\\Classes";
std::wstring reg_extension = reg_base + L"\\.3mf";
std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
bool is_new = false;
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
if (is_new)
// notify Windows only when any of the values gets changed
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
} }
void GUI_App::associate_stl_files() void GUI_App::associate_stl_files()
{ {
wchar_t app_path[MAX_PATH]; associate_file_type(L".stl", L"Prusa.Slicer.1", L"PrusaSlicer", true);
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
std::wstring prog_id = L"Prusa.Slicer.1";
std::wstring prog_desc = L"PrusaSlicer";
std::wstring prog_command = prog_path + L" \"%1\"";
std::wstring reg_base = L"Software\\Classes";
std::wstring reg_extension = reg_base + L"\\.stl";
std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
bool is_new = false;
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
if (is_new)
// notify Windows only when any of the values gets changed
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
} }
void GUI_App::associate_gcode_files() void GUI_App::associate_gcode_files()
{ {
wchar_t app_path[MAX_PATH]; associate_file_type(L".gcode", L"PrusaSlicer.GCodeViewer.1", L"PrusaSlicerGCodeViewer", true);
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
std::wstring prog_id = L"PrusaSlicer.GCodeViewer.1";
std::wstring prog_desc = L"PrusaSlicerGCodeViewer";
std::wstring prog_command = prog_path + L" \"%1\"";
std::wstring reg_base = L"Software\\Classes";
std::wstring reg_extension = reg_base + L"\\.gcode";
std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
bool is_new = false;
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
if (is_new)
// notify Windows only when any of the values gets changed
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
} }
#endif // __WXMSW__ #endif // __WXMSW__
void GUI_App::on_version_read(wxCommandEvent& evt) void GUI_App::on_version_read(wxCommandEvent& evt)
{ {
app_config->set("version_online", into_u8(evt.GetString())); app_config->set("version_online", into_u8(evt.GetString()));

View file

@ -983,10 +983,11 @@ void Preview::load_print_as_fff(bool keep_z_range)
if (gcode_preview_data_valid) { if (gcode_preview_data_valid) {
// Load the real G-code preview. // Load the real G-code preview.
m_canvas->load_gcode_preview(*m_gcode_result, colors); m_canvas->load_gcode_preview(*m_gcode_result, colors);
m_left_sizer->Show(m_bottom_toolbar_panel);
m_left_sizer->Layout(); m_left_sizer->Layout();
Refresh(); Refresh();
zs = m_canvas->get_gcode_layers_zs(); zs = m_canvas->get_gcode_layers_zs();
if (!zs.empty())
m_left_sizer->Show(m_bottom_toolbar_panel);
m_loaded = true; m_loaded = true;
} }
else if (wxGetApp().is_editor()) { else if (wxGetApp().is_editor()) {

View file

@ -840,7 +840,9 @@ GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_fil
GLGizmoRotate(parent, GLGizmoRotate::X), GLGizmoRotate(parent, GLGizmoRotate::X),
GLGizmoRotate(parent, GLGizmoRotate::Y), GLGizmoRotate(parent, GLGizmoRotate::Y),
GLGizmoRotate(parent, GLGizmoRotate::Z) }) GLGizmoRotate(parent, GLGizmoRotate::Z) })
{} {
load_rotoptimize_state();
}
bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event)
{ {

View file

@ -40,6 +40,12 @@ class PlaterWorker: public Worker {
{ {
wxWakeUpIdle(); wxWakeUpIdle();
ctl.update_status(st, msg); ctl.update_status(st, msg);
// If the worker is not using additional threads, the UI
// is refreshed with this call. If the worker is running
// in it's own thread, the yield should not have any
// visible effects.
wxYieldIfNeeded();
} }
bool was_canceled() const override { return ctl.was_canceled(); } bool was_canceled() const override { return ctl.was_canceled(); }

View file

@ -0,0 +1,125 @@
#ifndef UITHREADWORKER_HPP
#define UITHREADWORKER_HPP
#include <deque>
#include <queue>
#include "Worker.hpp"
#include "ProgressIndicator.hpp"
namespace Slic3r { namespace GUI {
// Implementation of a worker which does not create any additional threads.
class UIThreadWorker : public Worker, private Job::Ctl {
std::queue<std::unique_ptr<Job>, std::deque<std::unique_ptr<Job>>> m_jobqueue;
std::shared_ptr<ProgressIndicator> m_progress;
bool m_running = false;
bool m_canceled = false;
void process_front()
{
std::unique_ptr<Job> job;
if (!m_jobqueue.empty()) {
job = std::move(m_jobqueue.front());
m_jobqueue.pop();
}
if (job) {
std::exception_ptr eptr;
m_running = true;
try {
job->process(*this);
} catch (...) {
eptr= std::current_exception();
}
job->finalize(m_canceled, eptr);
// Unhandled exceptions are rethrown without mercy.
if (eptr)
std::rethrow_exception(eptr);
m_running = false;
m_canceled = false;
}
}
protected:
// Implement Job::Ctl interface:
void update_status(int st, const std::string &msg = "") override
{
if (m_progress) {
m_progress->set_progress(st);
m_progress->set_status_text(msg.c_str());
}
}
bool was_canceled() const override { return m_canceled; }
std::future<void> call_on_main_thread(std::function<void()> fn) override
{
return std::async(std::launch::deferred, [fn]{ fn(); });
}
public:
explicit UIThreadWorker(std::shared_ptr<ProgressIndicator> pri,
const std::string & /*name*/ = "")
: m_progress{pri}
{
if (m_progress)
m_progress->set_cancel_callback([this]() { cancel(); });
}
UIThreadWorker() = default;
bool push(std::unique_ptr<Job> job) override
{
m_canceled = false;
if (job)
m_jobqueue.push(std::move(job));
return bool(job);
}
bool is_idle() const override { return !m_running && m_jobqueue.empty(); }
void cancel() override { m_canceled = true; }
void cancel_all() override
{
m_canceled = true;
process_front();
while (!m_jobqueue.empty())
m_jobqueue.pop();
}
void process_events() override {
while (!m_jobqueue.empty())
process_front();
}
bool wait_for_current_job(unsigned /*timeout_ms*/ = 0) override {
process_front();
return true;
}
bool wait_for_idle(unsigned /*timeout_ms*/ = 0) override {
process_events();
return true;
}
ProgressIndicator * get_pri() { return m_progress.get(); }
const ProgressIndicator * get_pri() const { return m_progress.get(); }
};
}} // namespace Slic3r::GUI
#endif // UITHREADWORKER_HPP

View file

@ -1656,6 +1656,9 @@ struct Plater::priv
// objects would be frozen for the user. In case of arrange, an animation // objects would be frozen for the user. In case of arrange, an animation
// could be shown, or with the optimize orientations, partial results // could be shown, or with the optimize orientations, partial results
// could be displayed. // could be displayed.
//
// UIThreadWorker can be used as a replacement for BoostThreadWorker if
// no additional worker threads are desired (useful for debugging or profiling)
PlaterWorker<BoostThreadWorker> m_worker; PlaterWorker<BoostThreadWorker> m_worker;
SLAImportDialog * m_sla_import_dlg; SLAImportDialog * m_sla_import_dlg;

View file

@ -79,9 +79,11 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin
m_custom_toolbar_size = atoi(get_app_config()->get("custom_toolbar_size").c_str()); m_custom_toolbar_size = atoi(get_app_config()->get("custom_toolbar_size").c_str());
m_use_custom_toolbar_size = get_app_config()->get("use_custom_toolbar_size") == "1"; m_use_custom_toolbar_size = get_app_config()->get("use_custom_toolbar_size") == "1";
// update colors for color pickers if (wxGetApp().is_editor()) {
update_color(m_sys_colour, wxGetApp().get_label_clr_sys()); // update colors for color pickers
update_color(m_mod_colour, wxGetApp().get_label_clr_modified()); update_color(m_sys_colour, wxGetApp().get_label_clr_sys());
update_color(m_mod_colour, wxGetApp().get_label_clr_modified());
}
this->ShowModal(); this->ShowModal();
} }
@ -230,23 +232,16 @@ void PreferencesDialog::build()
app_config->get("export_sources_full_pathnames") == "1"); app_config->get("export_sources_full_pathnames") == "1");
#ifdef _WIN32 #ifdef _WIN32
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER // Please keep in sync with ConfigWizard
// file association is not possible anymore starting with Win 8 append_bool_option(m_optgroup_general, "associate_3mf",
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { L("Associate .3mf files to PrusaSlicer"),
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER L("If enabled, sets PrusaSlicer as default application to open .3mf files."),
// Please keep in sync with ConfigWizard app_config->get("associate_3mf") == "1");
append_bool_option(m_optgroup_general, "associate_3mf",
L("Associate .3mf files to PrusaSlicer"),
L("If enabled, sets PrusaSlicer as default application to open .3mf files."),
app_config->get("associate_3mf") == "1");
append_bool_option(m_optgroup_general, "associate_stl", append_bool_option(m_optgroup_general, "associate_stl",
L("Associate .stl files to PrusaSlicer"), L("Associate .stl files to PrusaSlicer"),
L("If enabled, sets PrusaSlicer as default application to open .stl files."), L("If enabled, sets PrusaSlicer as default application to open .stl files."),
app_config->get("associate_stl") == "1"); app_config->get("associate_stl") == "1");
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
}
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
#endif // _WIN32 #endif // _WIN32
m_optgroup_general->append_separator(); m_optgroup_general->append_separator();

View file

@ -0,0 +1,490 @@
#include "libslic3r/Technologies.hpp"
#include "WinRegistry.hpp"
#ifdef _WIN32
#include <shlobj.h>
#include <wincrypt.h>
#include <winternl.h>
#include <sddl.h>
namespace Slic3r {
// Helper class which automatically closes the handle when
// going out of scope
class AutoHandle
{
HANDLE m_handle{ nullptr };
public:
explicit AutoHandle(HANDLE handle) : m_handle(handle) {}
~AutoHandle() { if (m_handle != nullptr) ::CloseHandle(m_handle); }
HANDLE get() { return m_handle; }
};
// Helper class which automatically closes the key when
// going out of scope
class AutoRegKey
{
HKEY m_key{ nullptr };
public:
explicit AutoRegKey(HKEY key) : m_key(key) {}
~AutoRegKey() { if (m_key != nullptr) ::RegCloseKey(m_key); }
HKEY get() { return m_key; }
};
// returns true if the given value is set/modified into Windows registry
static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue)
{
// see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
wchar_t szValueCurrent[1000];
DWORD dwType;
DWORD dwSize = sizeof(szValueCurrent);
LSTATUS iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize);
const bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
if (iRC != ERROR_SUCCESS && !bDidntExist)
// an error occurred
return false;
if (!bDidntExist) {
if (dwType != REG_SZ)
// invalid type
return false;
if (::wcscmp(szValueCurrent, pszValue) == 0)
// value already set
return false;
}
DWORD dwDisposition;
HKEY hkey;
iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition);
bool ret = false;
if (iRC == ERROR_SUCCESS) {
iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t));
if (iRC == ERROR_SUCCESS)
ret = true;
}
RegCloseKey(hkey);
return ret;
}
static std::wstring get_current_user_string_sid()
{
HANDLE rawProcessToken;
if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY,
&rawProcessToken))
return L"";
AutoHandle processToken(rawProcessToken);
DWORD userSize = 0;
if (!(!::GetTokenInformation(processToken.get(), TokenUser, nullptr, 0, &userSize) &&
GetLastError() == ERROR_INSUFFICIENT_BUFFER))
return L"";
std::vector<unsigned char> userBytes(userSize);
if (!::GetTokenInformation(processToken.get(), TokenUser, userBytes.data(), userSize, &userSize))
return L"";
wchar_t* rawSid = nullptr;
if (!::ConvertSidToStringSidW(reinterpret_cast<PTOKEN_USER>(userBytes.data())->User.Sid, &rawSid))
return L"";
return rawSid;
}
/*
* Create the string which becomes the input to the UserChoice hash.
*
* @see generate_user_choice_hash() for parameters.
*
* @return The formatted string, empty string on failure.
*
* NOTE: This uses the format as of Windows 10 20H2 (latest as of this writing),
* used at least since 1803.
* There was at least one older version, not currently supported: On Win10 RTM
* (build 10240, aka 1507) the hash function is the same, but the timestamp and
* User Experience string aren't included; instead (for protocols) the string
* ends with the exe path. The changelog of SetUserFTA suggests the algorithm
* changed in 1703, so there may be two versions: before 1703, and 1703 to now.
*/
static std::wstring format_user_choice_string(const wchar_t* aExt, const wchar_t* aUserSid, const wchar_t* aProgId, SYSTEMTIME aTimestamp)
{
aTimestamp.wSecond = 0;
aTimestamp.wMilliseconds = 0;
FILETIME fileTime = { 0 };
if (!::SystemTimeToFileTime(&aTimestamp, &fileTime))
return L"";
// This string is built into Windows as part of the UserChoice hash algorithm.
// It might vary across Windows SKUs (e.g. Windows 10 vs. Windows Server), or
// across builds of the same SKU, but this is the only currently known
// version. There isn't any known way of deriving it, so we assume this
// constant value. If we are wrong, we will not be able to generate correct
// UserChoice hashes.
const wchar_t* userExperience =
L"User Choice set via Windows User Experience "
L"{D18B6DD5-6124-4341-9318-804003BAFA0B}";
const wchar_t* userChoiceFmt =
L"%s%s%s"
L"%08lx"
L"%08lx"
L"%s";
int userChoiceLen = _scwprintf(userChoiceFmt, aExt, aUserSid, aProgId,
fileTime.dwHighDateTime, fileTime.dwLowDateTime, userExperience);
userChoiceLen += 1; // _scwprintf does not include the terminator
std::wstring userChoice(userChoiceLen, L'\0');
_snwprintf_s(userChoice.data(), userChoiceLen, _TRUNCATE, userChoiceFmt, aExt,
aUserSid, aProgId, fileTime.dwHighDateTime, fileTime.dwLowDateTime, userExperience);
::CharLowerW(userChoice.data());
return userChoice;
}
// @return The MD5 hash of the input, nullptr on failure.
static std::vector<DWORD> cng_md5(const unsigned char* bytes, ULONG bytesLen) {
constexpr ULONG MD5_BYTES = 16;
constexpr ULONG MD5_DWORDS = MD5_BYTES / sizeof(DWORD);
std::vector<DWORD> hash;
BCRYPT_ALG_HANDLE hAlg = nullptr;
if (NT_SUCCESS(::BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_MD5_ALGORITHM, nullptr, 0))) {
BCRYPT_HASH_HANDLE hHash = nullptr;
// As of Windows 7 the hash handle will manage its own object buffer when
// pbHashObject is nullptr and cbHashObject is 0.
if (NT_SUCCESS(::BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0))) {
// BCryptHashData promises not to modify pbInput.
if (NT_SUCCESS(::BCryptHashData(hHash, const_cast<unsigned char*>(bytes), bytesLen, 0))) {
hash.resize(MD5_DWORDS);
if (!NT_SUCCESS(::BCryptFinishHash(hHash, reinterpret_cast<unsigned char*>(hash.data()),
MD5_DWORDS * sizeof(DWORD), 0)))
hash.clear();
}
::BCryptDestroyHash(hHash);
}
::BCryptCloseAlgorithmProvider(hAlg, 0);
}
return hash;
}
static inline DWORD word_swap(DWORD v)
{
return (v >> 16) | (v << 16);
}
// @return The input bytes encoded as base64, nullptr on failure.
static std::wstring crypto_api_base64_encode(const unsigned char* bytes, DWORD bytesLen) {
DWORD base64Len = 0;
if (!::CryptBinaryToStringW(bytes, bytesLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr, &base64Len))
return L"";
std::wstring base64(base64Len, L'\0');
if (!::CryptBinaryToStringW(bytes, bytesLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, base64.data(), &base64Len))
return L"";
return base64;
}
/*
* Generate the UserChoice Hash.
*
* This implementation is based on the references listed above.
* It is organized to show the logic as clearly as possible, but at some
* point the reasoning is just "this is how it works".
*
* @param inputString A null-terminated string to hash.
*
* @return The base64-encoded hash, or empty string on failure.
*/
static std::wstring hash_string(const wchar_t* inputString)
{
auto inputBytes = reinterpret_cast<const unsigned char*>(inputString);
int inputByteCount = (::lstrlenW(inputString) + 1) * sizeof(wchar_t);
constexpr size_t DWORDS_PER_BLOCK = 2;
constexpr size_t BLOCK_SIZE = sizeof(DWORD) * DWORDS_PER_BLOCK;
// Incomplete blocks are ignored.
int blockCount = inputByteCount / BLOCK_SIZE;
if (blockCount == 0)
return L"";
// Compute an MD5 hash. md5[0] and md5[1] will be used as constant multipliers
// in the scramble below.
auto md5 = cng_md5(inputBytes, inputByteCount);
if (md5.empty())
return L"";
// The following loop effectively computes two checksums, scrambled like a
// hash after every DWORD is added.
// Constant multipliers for the scramble, one set for each DWORD in a block.
const DWORD C0s[DWORDS_PER_BLOCK][5] = {
{md5[0] | 1, 0xCF98B111uL, 0x87085B9FuL, 0x12CEB96DuL, 0x257E1D83uL},
{md5[1] | 1, 0xA27416F5uL, 0xD38396FFuL, 0x7C932B89uL, 0xBFA49F69uL} };
const DWORD C1s[DWORDS_PER_BLOCK][5] = {
{md5[0] | 1, 0xEF0569FBuL, 0x689B6B9FuL, 0x79F8A395uL, 0xC3EFEA97uL},
{md5[1] | 1, 0xC31713DBuL, 0xDDCD1F0FuL, 0x59C3AF2DuL, 0x35BD1EC9uL} };
// The checksums.
DWORD h0 = 0;
DWORD h1 = 0;
// Accumulated total of the checksum after each DWORD.
DWORD h0Acc = 0;
DWORD h1Acc = 0;
for (int i = 0; i < blockCount; ++i) {
for (int j = 0; j < DWORDS_PER_BLOCK; ++j) {
const DWORD* C0 = C0s[j];
const DWORD* C1 = C1s[j];
DWORD input;
memcpy(&input, &inputBytes[(i * DWORDS_PER_BLOCK + j) * sizeof(DWORD)], sizeof(DWORD));
h0 += input;
// Scramble 0
h0 *= C0[0];
h0 = word_swap(h0) * C0[1];
h0 = word_swap(h0) * C0[2];
h0 = word_swap(h0) * C0[3];
h0 = word_swap(h0) * C0[4];
h0Acc += h0;
h1 += input;
// Scramble 1
h1 = word_swap(h1) * C1[1] + h1 * C1[0];
h1 = (h1 >> 16) * C1[2] + h1 * C1[3];
h1 = word_swap(h1) * C1[4] + h1;
h1Acc += h1;
}
}
DWORD hash[2] = { h0 ^ h1, h0Acc ^ h1Acc };
return crypto_api_base64_encode(reinterpret_cast<const unsigned char*>(hash), sizeof(hash));
}
static std::wstring generate_user_choice_hash(const wchar_t* aExt, const wchar_t* aUserSid, const wchar_t* aProgId, SYSTEMTIME aTimestamp)
{
const std::wstring userChoice = format_user_choice_string(aExt, aUserSid, aProgId, aTimestamp);
if (userChoice.empty())
return L"";
return hash_string(userChoice.c_str());
}
static bool add_milliseconds_to_system_time(SYSTEMTIME& aSystemTime, ULONGLONG aIncrementMS)
{
FILETIME fileTime;
ULARGE_INTEGER fileTimeInt;
if (!::SystemTimeToFileTime(&aSystemTime, &fileTime))
return false;
fileTimeInt.LowPart = fileTime.dwLowDateTime;
fileTimeInt.HighPart = fileTime.dwHighDateTime;
// FILETIME is in units of 100ns.
fileTimeInt.QuadPart += aIncrementMS * 1000 * 10;
fileTime.dwLowDateTime = fileTimeInt.LowPart;
fileTime.dwHighDateTime = fileTimeInt.HighPart;
SYSTEMTIME tmpSystemTime;
if (!::FileTimeToSystemTime(&fileTime, &tmpSystemTime))
return false;
aSystemTime = tmpSystemTime;
return true;
}
// Compare two SYSTEMTIMEs as FILETIME after clearing everything
// below minutes.
static bool check_equal_minutes(SYSTEMTIME aSystemTime1, SYSTEMTIME aSystemTime2)
{
aSystemTime1.wSecond = 0;
aSystemTime1.wMilliseconds = 0;
aSystemTime2.wSecond = 0;
aSystemTime2.wMilliseconds = 0;
FILETIME fileTime1;
FILETIME fileTime2;
if (!::SystemTimeToFileTime(&aSystemTime1, &fileTime1) || !::SystemTimeToFileTime(&aSystemTime2, &fileTime2))
return false;
return (fileTime1.dwLowDateTime == fileTime2.dwLowDateTime) && (fileTime1.dwHighDateTime == fileTime2.dwHighDateTime);
}
static std::wstring get_association_key_path(const wchar_t* aExt)
{
const wchar_t* keyPathFmt;
if (aExt[0] == L'.')
keyPathFmt = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\%s";
else
keyPathFmt = L"SOFTWARE\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\%s";
int keyPathLen = _scwprintf(keyPathFmt, aExt);
keyPathLen += 1; // _scwprintf does not include the terminator
std::wstring keyPath(keyPathLen, '\0');
_snwprintf_s(keyPath.data(), keyPathLen, _TRUNCATE, keyPathFmt, aExt);
return keyPath;
}
/*
* Set an association with a UserChoice key
*
* Removes the old key, creates a new one with ProgID and Hash set to
* enable a new asociation.
*
* @param aExt File type or protocol to associate
* @param aProgID ProgID to use for the asociation
*
* @return true if successful, false on error.
*/
static bool set_user_choice(const wchar_t* aExt, const wchar_t* aProgID) {
const std::wstring aSid = get_current_user_string_sid();
if (aSid.empty())
return false;
SYSTEMTIME hashTimestamp;
::GetSystemTime(&hashTimestamp);
std::wstring hash = generate_user_choice_hash(aExt, aSid.c_str(), aProgID, hashTimestamp);
if (hash.empty())
return false;
// The hash changes at the end of each minute, so check that the hash should
// be the same by the time we're done writing.
const ULONGLONG kWriteTimingThresholdMilliseconds = 100;
// Generating the hash could have taken some time, so start from now.
SYSTEMTIME writeEndTimestamp;
::GetSystemTime(&writeEndTimestamp);
if (!add_milliseconds_to_system_time(writeEndTimestamp, +kWriteTimingThresholdMilliseconds))
return false;
if (!check_equal_minutes(hashTimestamp, writeEndTimestamp)) {
::Sleep(kWriteTimingThresholdMilliseconds * 2);
// For consistency, use the current time.
::GetSystemTime(&hashTimestamp);
hash = generate_user_choice_hash(aExt, aSid.c_str(), aProgID, hashTimestamp);
if (hash.empty())
return false;
}
const std::wstring assocKeyPath = get_association_key_path(aExt);
if (assocKeyPath.empty())
return false;
LSTATUS ls;
HKEY rawAssocKey;
ls = ::RegOpenKeyExW(HKEY_CURRENT_USER, assocKeyPath.data(), 0, KEY_READ | KEY_WRITE, &rawAssocKey);
if (ls != ERROR_SUCCESS)
return false;
AutoRegKey assocKey(rawAssocKey);
HKEY currUserChoiceKey;
ls = ::RegOpenKeyExW(assocKey.get(), L"UserChoice", 0, KEY_READ, &currUserChoiceKey);
if (ls == ERROR_SUCCESS) {
::RegCloseKey(currUserChoiceKey);
// When Windows creates this key, it is read-only (Deny Set Value), so we need
// to delete it first.
// We don't set any similar special permissions.
ls = ::RegDeleteKeyW(assocKey.get(), L"UserChoice");
if (ls != ERROR_SUCCESS)
return false;
}
HKEY rawUserChoiceKey;
ls = ::RegCreateKeyExW(assocKey.get(), L"UserChoice", 0, nullptr,
0 /* options */, KEY_READ | KEY_WRITE,
0 /* security attributes */, &rawUserChoiceKey,
nullptr);
if (ls != ERROR_SUCCESS)
return false;
AutoRegKey userChoiceKey(rawUserChoiceKey);
DWORD progIdByteCount = (::lstrlenW(aProgID) + 1) * sizeof(wchar_t);
ls = ::RegSetValueExW(userChoiceKey.get(), L"ProgID", 0, REG_SZ, reinterpret_cast<const unsigned char*>(aProgID), progIdByteCount);
if (ls != ERROR_SUCCESS)
return false;
DWORD hashByteCount = (::lstrlenW(hash.data()) + 1) * sizeof(wchar_t);
ls = ::RegSetValueExW(userChoiceKey.get(), L"Hash", 0, REG_SZ, reinterpret_cast<const unsigned char*>(hash.data()), hashByteCount);
if (ls != ERROR_SUCCESS)
return false;
return true;
}
static bool set_as_default_per_file_type(const std::wstring& extension, const std::wstring& prog_id)
{
const std::wstring reg_extension = get_association_key_path(extension.c_str());
if (reg_extension.empty())
return false;
bool needs_update = true;
bool modified = false;
HKEY rawAssocKey = nullptr;
LSTATUS res = ::RegOpenKeyExW(HKEY_CURRENT_USER, reg_extension.c_str(), 0, KEY_READ, &rawAssocKey);
AutoRegKey assoc_key(rawAssocKey);
if (res == ERROR_SUCCESS) {
DWORD data_size_bytes = 0;
res = ::RegGetValueW(assoc_key.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ, nullptr, nullptr, &data_size_bytes);
if (res == ERROR_SUCCESS) {
// +1 in case dataSizeBytes was odd, +1 to ensure termination
DWORD data_size_chars = (data_size_bytes / sizeof(wchar_t)) + 2;
std::wstring curr_prog_id(data_size_chars, L'\0');
res = ::RegGetValueW(assoc_key.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ, nullptr, curr_prog_id.data(), &data_size_bytes);
if (res == ERROR_SUCCESS) {
const std::wstring::size_type pos = curr_prog_id.find_first_of(L'\0');
if (pos != std::wstring::npos)
curr_prog_id = curr_prog_id.substr(0, pos);
needs_update = !boost::algorithm::iequals(curr_prog_id, prog_id);
}
}
}
if (needs_update)
modified = set_user_choice(extension.c_str(), prog_id.c_str());
return modified;
}
void associate_file_type(const std::wstring& extension, const std::wstring& prog_id, const std::wstring& prog_desc, bool set_as_default)
{
assert(!extension.empty() && extension.front() == L'.');
const std::wstring reg_extension = L"SOFTWARE\\Classes\\" + extension;
const std::wstring reg_prog_id = L"SOFTWARE\\Classes\\" + prog_id;
const std::wstring reg_prog_id_command = L"SOFTWARE\\Classes\\" + prog_id + +L"\\Shell\\Open\\Command";
wchar_t app_path[1040];
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
const std::wstring prog_command = L"\"" + std::wstring(app_path) + L"\"" + L" \"%1\"";
bool modified = false;
modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
if (set_as_default)
modified |= set_as_default_per_file_type(extension, prog_id);
// notify Windows only when any of the values gets changed
if (modified)
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
}
} // namespace Slic3r
#endif // _WIN32

View file

@ -0,0 +1,23 @@
#ifndef slic3r_Utils_WinRegistry_hpp_
#define slic3r_Utils_WinRegistry_hpp_
#ifdef _WIN32
#include <string>
namespace Slic3r {
// Creates a Windows registry key for the files with the given 'extension' and associates them to the application 'prog_id'.
// If 'set_as_default' is true, the application 'prog_id' is set ad default application for the file type 'extension'.
// The file type registration implementation is based on code taken from:
// https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
// The set as default implementation is based on code taken from:
// https://hg.mozilla.org/mozilla-central/rev/e928b3e95a6c3b7257d0ba475fc2303bfbad1874
// https://hg.mozilla.org/releases/mozilla-release/diff/7e775ce432b599c6daf7ac379aa42f1e9b3b33ed/browser/components/shell/WindowsUserChoice.cpp
void associate_file_type(const std::wstring& extension, const std::wstring& prog_id, const std::wstring& prog_desc, bool set_as_default);
} // namespace Slic3r
#endif // _WIN32
#endif // slic3r_Utils_WinRegistry_hpp_

View file

@ -4,10 +4,9 @@
#include <thread> #include <thread>
#include "slic3r/GUI/Jobs/BoostThreadWorker.hpp" #include "slic3r/GUI/Jobs/BoostThreadWorker.hpp"
#include "slic3r/GUI/Jobs/UIThreadWorker.hpp"
#include "slic3r/GUI/Jobs/ProgressIndicator.hpp" #include "slic3r/GUI/Jobs/ProgressIndicator.hpp"
//#include <boost/thread/thread.hpp>
struct Progress: Slic3r::ProgressIndicator { struct Progress: Slic3r::ProgressIndicator {
int range = 100; int range = 100;
int pr = 0; int pr = 0;
@ -19,17 +18,30 @@ struct Progress: Slic3r::ProgressIndicator {
int get_range() const override { return range; } int get_range() const override { return range; }
}; };
TEST_CASE("nullptr job should be ignored", "[Jobs]") { using TestClasses = std::tuple< Slic3r::GUI::UIThreadWorker, Slic3r::GUI::BoostThreadWorker >;
Slic3r::GUI::BoostThreadWorker worker{std::make_unique<Progress>()};
TEMPLATE_LIST_TEST_CASE("Empty worker should not do anything", "[Jobs]", TestClasses) {
TestType worker{std::make_unique<Progress>()};
REQUIRE(worker.is_idle());
worker.wait_for_current_job();
worker.process_events();
REQUIRE(worker.is_idle());
}
TEMPLATE_LIST_TEST_CASE("nullptr job should be ignored", "[Jobs]", TestClasses) {
TestType worker{std::make_unique<Progress>()};
worker.push(nullptr); worker.push(nullptr);
REQUIRE(worker.is_idle()); REQUIRE(worker.is_idle());
} }
TEST_CASE("State should not be idle while running a job", "[Jobs]") { TEMPLATE_LIST_TEST_CASE("State should not be idle while running a job", "[Jobs]", TestClasses) {
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::GUI; using namespace Slic3r::GUI;
BoostThreadWorker worker{std::make_unique<Progress>(), "worker_thread"}; TestType worker{std::make_unique<Progress>(), "worker_thread"};
queue_job(worker, [&worker](Job::Ctl &ctl) { queue_job(worker, [&worker](Job::Ctl &ctl) {
ctl.call_on_main_thread([&worker] { ctl.call_on_main_thread([&worker] {
@ -42,11 +54,11 @@ TEST_CASE("State should not be idle while running a job", "[Jobs]") {
REQUIRE(worker.is_idle()); REQUIRE(worker.is_idle());
} }
TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]") { TEMPLATE_LIST_TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]", TestClasses) {
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::GUI; using namespace Slic3r::GUI;
auto pri = std::make_shared<Progress>(); auto pri = std::make_shared<Progress>();
BoostThreadWorker worker{pri}; TestType worker{pri};
queue_job(worker, [](Job::Ctl &ctl){ queue_job(worker, [](Job::Ctl &ctl){
for (int s = 0; s <= 100; ++s) { for (int s = 0; s <= 100; ++s) {
@ -60,12 +72,12 @@ TEST_CASE("Status messages should be received by the main thread during job exec
REQUIRE(pri->statustxt == "Running"); REQUIRE(pri->statustxt == "Running");
} }
TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") { TEMPLATE_LIST_TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]", TestClasses) {
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::GUI; using namespace Slic3r::GUI;
auto pri = std::make_shared<Progress>(); auto pri = std::make_shared<Progress>();
BoostThreadWorker worker{pri}; TestType worker{pri};
queue_job( queue_job(
worker, worker,
@ -88,12 +100,12 @@ TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") {
REQUIRE(pri->pr != 100); REQUIRE(pri->pr != 100);
} }
TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") { TEMPLATE_LIST_TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]", TestClasses) {
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::GUI; using namespace Slic3r::GUI;
auto pri = std::make_shared<Progress>(); auto pri = std::make_shared<Progress>();
BoostThreadWorker worker{pri}; TestType worker{pri};
std::array<bool, 4> jobres = {false, false, false, false}; std::array<bool, 4> jobres = {false, false, false, false};
@ -128,12 +140,12 @@ TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") {
REQUIRE(jobres[3] == false); REQUIRE(jobres[3] == false);
} }
TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]") { TEMPLATE_LIST_TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]", TestClasses) {
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::GUI; using namespace Slic3r::GUI;
auto pri = std::make_shared<Progress>(); auto pri = std::make_shared<Progress>();
BoostThreadWorker worker{pri}; TestType worker{pri};
queue_job( queue_job(
worker, [](Job::Ctl &) { throw std::runtime_error("test"); }, worker, [](Job::Ctl &) { throw std::runtime_error("test"); },