mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-02-01 16:38:43 +03:00
QIDISlicer1.0.0
This commit is contained in:
1957
src/slic3r/Utils/ASCIIFolding.cpp
Normal file
1957
src/slic3r/Utils/ASCIIFolding.cpp
Normal file
File diff suppressed because it is too large
Load Diff
19
src/slic3r/Utils/ASCIIFolding.hpp
Normal file
19
src/slic3r/Utils/ASCIIFolding.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef slic3r_ASCIIFolding_hpp_
|
||||
#define slic3r_ASCIIFolding_hpp_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// If possible, remove accents from accented latin characters.
|
||||
// This function is useful for generating file names to be processed by legacy firmwares.
|
||||
extern std::string fold_utf8_to_ascii(const std::string &src);
|
||||
|
||||
// Convert the input UNICODE character to a string of maximum 4 output ASCII characters.
|
||||
// Return the end of the string written to the output.
|
||||
// The output buffer must be at least 4 characters long.
|
||||
extern wchar_t* fold_to_ascii(wchar_t c, wchar_t *out);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_ASCIIFolding_hpp_ */
|
||||
654
src/slic3r/Utils/AppUpdater.cpp
Normal file
654
src/slic3r/Utils/AppUpdater.cpp
Normal file
@@ -0,0 +1,654 @@
|
||||
#include "AppUpdater.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/fstream.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/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.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(_u8L("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/QIDISlicer", 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 QIDIslicer.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 = (m_cancel ? true : !progress_fn(std::move(progress)));
|
||||
if (cancel) {
|
||||
// Lets keep error_message empty here - if there is need to show error dialog, the message will be probably shown by whatever caused the cancel.
|
||||
//error_message = GUI::format(_u8L("Error getting: `%1%`: Download was canceled."), 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())
|
||||
{
|
||||
std::string line1 = GUI::format(_u8L("Internal download error for url %1%:"), data.url);
|
||||
std::string line2 = _u8L("Destination path is empty.");
|
||||
std::string message = GUI::format("%1%\n%2%", line1, line2);
|
||||
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();
|
||||
}
|
||||
|
||||
boost::filesystem::path tmp_path = dest_path;
|
||||
tmp_path += format(".%1%%2%", std::to_string(GUI::GLCanvas3D::timestamp_now()), ".download");
|
||||
FILE* file;
|
||||
wxString temp_path_wstring(tmp_path.wstring());
|
||||
file = fopen(temp_path_wstring.c_str(), "wb");
|
||||
assert(file != NULL);
|
||||
if (file == NULL) {
|
||||
std::string line1 = GUI::format(_u8L("Download from %1% couldn't start:"), data.url);
|
||||
std::string line2 = GUI::format(_u8L("Can't create file at %1%"), tmp_path.string());
|
||||
std::string message = GUI::format("%1%\n%2%", line1, line2);
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
// This 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(debug) << "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
|
||||
, [&file, dest_path, tmp_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) {
|
||||
error_message = GUI::format(_u8L("Downloaded file has wrong size. Expected size: %1% Downloaded size: %2%"), expected_size, body_size);
|
||||
return false;
|
||||
}
|
||||
if (file == NULL) {
|
||||
error_message = GUI::format(_u8L("Can't create file at %1%"), tmp_path.string());
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
fwrite(body.c_str(), 1, body.size(), file);
|
||||
fclose(file);
|
||||
boost::filesystem::rename(tmp_path, dest_path);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
error_message = GUI::format(_u8L("Failed to write to file or to move %1% to %2%:\n%3%"), tmp_path, dest_path, e.what());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
, error_message
|
||||
);
|
||||
if (!res)
|
||||
{
|
||||
if (m_cancel) {
|
||||
BOOST_LOG_TRIVIAL(info) << error_message;
|
||||
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 = (error_message.empty()
|
||||
? std::string()
|
||||
: GUI::format(_u8L("Downloading new %1% has failed:\n%2%"), SLIC3R_APP_NAME, error_message));
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED);
|
||||
if (!message.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << message;
|
||||
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.";
|
||||
// Lets send event with current version, this way if user triggered this check, it will notify him about no new version online.
|
||||
std::string version = Semver().to_string();
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
|
||||
evt->SetString(GUI::from_u8(version));
|
||||
GUI::wxGetApp().QueueEvent(evt);
|
||||
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(info) << 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(info) << 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 QIDISlicer.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
|
||||
174
src/slic3r/Utils/AstroBox.cpp
Normal file
174
src/slic3r/Utils/AstroBox.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "AstroBox.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <exception>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <wx/progdlg.h>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "Http.hpp"
|
||||
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
AstroBox::AstroBox(DynamicPrintConfig *config) :
|
||||
host(config->opt_string("print_host")),
|
||||
apikey(config->opt_string("printhost_apikey")),
|
||||
cafile(config->opt_string("printhost_cafile"))
|
||||
{}
|
||||
|
||||
const char* AstroBox::get_name() const { return "AstroBox"; }
|
||||
|
||||
bool AstroBox::test(wxString &msg) const
|
||||
{
|
||||
// Since the request is performed synchronously here,
|
||||
// it is ok to refer to `msg` from within the closure
|
||||
|
||||
const char *name = get_name();
|
||||
|
||||
bool res = true;
|
||||
auto url = make_url("api/version");
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
set_auth(http);
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
res = false;
|
||||
msg = format_error(body, error, status);
|
||||
})
|
||||
.on_complete([&, this](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body;
|
||||
|
||||
try {
|
||||
std::stringstream ss(body);
|
||||
pt::ptree ptree;
|
||||
pt::read_json(ss, ptree);
|
||||
|
||||
if (! ptree.get_optional<std::string>("api")) {
|
||||
res = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto text = ptree.get_optional<std::string>("text");
|
||||
res = validate_version_text(text);
|
||||
if (! res) {
|
||||
msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : "AstroBox"));
|
||||
}
|
||||
}
|
||||
catch (const std::exception &) {
|
||||
res = false;
|
||||
msg = "Could not parse server response";
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
wxString AstroBox::get_test_ok_msg () const
|
||||
{
|
||||
return _(L("Connection to AstroBox works correctly."));
|
||||
}
|
||||
|
||||
wxString AstroBox::get_test_failed_msg (wxString &msg) const
|
||||
{
|
||||
return GUI::format_wxstr("%s: %s\n\n%s"
|
||||
, _L("Could not connect to AstroBox")
|
||||
, msg
|
||||
, _L("Note: AstroBox version at least 1.1.0 is required."));
|
||||
}
|
||||
|
||||
bool AstroBox::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
const char *name = get_name();
|
||||
|
||||
const auto upload_filename = upload_data.upload_path.filename();
|
||||
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||
|
||||
wxString test_msg;
|
||||
if (! test(test_msg)) {
|
||||
error_fn(std::move(test_msg));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool res = true;
|
||||
|
||||
auto url = make_url("api/files/local");
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%")
|
||||
% name
|
||||
% upload_data.source_path
|
||||
% url
|
||||
% upload_filename.string()
|
||||
% upload_parent_path.string()
|
||||
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
|
||||
|
||||
auto http = Http::post(std::move(url));
|
||||
set_auth(http);
|
||||
http.form_add("print", upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
|
||||
.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ???
|
||||
.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
error_fn(format_error(body, error, status));
|
||||
res = false;
|
||||
})
|
||||
.on_progress([&](Http::Progress progress, bool &cancel) {
|
||||
prorgess_fn(std::move(progress), cancel);
|
||||
if (cancel) {
|
||||
// Upload was canceled
|
||||
BOOST_LOG_TRIVIAL(info) << "AstroBox: Upload canceled";
|
||||
res = false;
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool AstroBox::validate_version_text(const boost::optional<std::string> &version_text) const
|
||||
{
|
||||
return version_text ? boost::starts_with(*version_text, "AstroBox") : true;
|
||||
}
|
||||
|
||||
void AstroBox::set_auth(Http &http) const
|
||||
{
|
||||
http.header("X-Api-Key", apikey);
|
||||
|
||||
if (! cafile.empty()) {
|
||||
http.ca_file(cafile);
|
||||
}
|
||||
}
|
||||
|
||||
std::string AstroBox::make_url(const std::string &path) const
|
||||
{
|
||||
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
||||
if (host.back() == '/') {
|
||||
return (boost::format("%1%%2%") % host % path).str();
|
||||
} else {
|
||||
return (boost::format("%1%/%2%") % host % path).str();
|
||||
}
|
||||
} else {
|
||||
return (boost::format("http://%1%/%2%") % host % path).str();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
46
src/slic3r/Utils/AstroBox.hpp
Normal file
46
src/slic3r/Utils/AstroBox.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#ifndef slic3r_AstroBox_hpp_
|
||||
#define slic3r_AstroBox_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "PrintHost.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
|
||||
class AstroBox : public PrintHost
|
||||
{
|
||||
public:
|
||||
AstroBox(DynamicPrintConfig *config);
|
||||
~AstroBox() override = default;
|
||||
|
||||
const char* get_name() const override;
|
||||
|
||||
bool test(wxString &curl_msg) const override;
|
||||
wxString get_test_ok_msg () const override;
|
||||
wxString get_test_failed_msg (wxString &msg) const override;
|
||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||
bool has_auto_discovery() const override { return true; }
|
||||
bool can_test() const override { return true; }
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||
std::string get_host() const override { return host; }
|
||||
|
||||
protected:
|
||||
bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
||||
|
||||
private:
|
||||
std::string host;
|
||||
std::string apikey;
|
||||
std::string cafile;
|
||||
|
||||
void set_auth(Http &http) const;
|
||||
std::string make_url(const std::string &path) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
1206
src/slic3r/Utils/Bonjour.cpp
Normal file
1206
src/slic3r/Utils/Bonjour.cpp
Normal file
File diff suppressed because it is too large
Load Diff
287
src/slic3r/Utils/Bonjour.hpp
Normal file
287
src/slic3r/Utils/Bonjour.hpp
Normal file
@@ -0,0 +1,287 @@
|
||||
#ifndef slic3r_Bonjour_hpp_
|
||||
#define slic3r_Bonjour_hpp_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
|
||||
struct BonjourReply
|
||||
{
|
||||
typedef std::unordered_map<std::string, std::string> TxtData;
|
||||
|
||||
boost::asio::ip::address ip;
|
||||
uint16_t port;
|
||||
std::string service_name;
|
||||
std::string hostname;
|
||||
std::string full_address;
|
||||
|
||||
TxtData txt_data;
|
||||
|
||||
BonjourReply() = delete;
|
||||
BonjourReply(boost::asio::ip::address ip,
|
||||
uint16_t port,
|
||||
std::string service_name,
|
||||
std::string hostname,
|
||||
TxtData txt_data);
|
||||
|
||||
std::string path() const;
|
||||
|
||||
bool operator==(const BonjourReply &other) const;
|
||||
bool operator<(const BonjourReply &other) const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream &, const BonjourReply &);
|
||||
|
||||
/// Bonjour lookup performer
|
||||
class Bonjour : public std::enable_shared_from_this<Bonjour> {
|
||||
private:
|
||||
struct priv;
|
||||
public:
|
||||
typedef std::shared_ptr<Bonjour> Ptr;
|
||||
typedef std::function<void(BonjourReply &&)> ReplyFn;
|
||||
typedef std::function<void()> CompleteFn;
|
||||
typedef std::function<void(const std::vector<BonjourReply>&)> ResolveFn;
|
||||
typedef std::set<std::string> TxtKeys;
|
||||
|
||||
Bonjour(std::string service);
|
||||
Bonjour(Bonjour &&other);
|
||||
~Bonjour();
|
||||
|
||||
// Set requested service protocol, "tcp" by default
|
||||
Bonjour& set_protocol(std::string protocol);
|
||||
// Set which TXT key-values should be collected
|
||||
// Note that "path" is always collected
|
||||
Bonjour& set_txt_keys(TxtKeys txt_keys);
|
||||
Bonjour& set_timeout(unsigned timeout);
|
||||
Bonjour& set_retries(unsigned retries);
|
||||
// ^ Note: By default there is 1 retry (meaning 1 broadcast is sent).
|
||||
// Timeout is per one retry, ie. total time spent listening = retries * timeout.
|
||||
// If retries > 1, then care needs to be taken as more than one reply from the same service may be received.
|
||||
|
||||
// sets hostname queried by resolve()
|
||||
Bonjour& set_hostname(const std::string& hostname);
|
||||
|
||||
Bonjour& on_reply(ReplyFn fn);
|
||||
Bonjour& on_complete(CompleteFn fn);
|
||||
|
||||
Bonjour& on_resolve(ResolveFn fn);
|
||||
// lookup all devices by given TxtKeys
|
||||
// each correct reply is passed back in ReplyFn, finishes with CompleteFn
|
||||
Ptr lookup();
|
||||
// performs resolving of hostname into vector of ip adresses passed back by ResolveFn
|
||||
// needs set_hostname and on_resolve to be called before.
|
||||
Ptr resolve();
|
||||
// resolve on the current thread
|
||||
void resolve_sync();
|
||||
private:
|
||||
std::unique_ptr<priv> p;
|
||||
};
|
||||
|
||||
struct BonjourRequest
|
||||
{
|
||||
static const boost::asio::ip::address_v4 MCAST_IP4;
|
||||
static const boost::asio::ip::address_v6 MCAST_IP6;
|
||||
static const uint16_t MCAST_PORT;
|
||||
|
||||
std::vector<char> m_data;
|
||||
|
||||
static boost::optional<BonjourRequest> make_PTR(const std::string& service, const std::string& protocol);
|
||||
static boost::optional<BonjourRequest> make_A(const std::string& hostname);
|
||||
static boost::optional<BonjourRequest> make_AAAA(const std::string& hostname);
|
||||
private:
|
||||
BonjourRequest(std::vector<char>&& data) : m_data(std::move(data)) {}
|
||||
};
|
||||
|
||||
|
||||
class LookupSocket;
|
||||
class ResolveSocket;
|
||||
|
||||
// Session is created for each async_receive of socket. On receive, its handle_receive method is called (Thru io_service->post).
|
||||
// ReplyFn is called if correct datagram was received.
|
||||
class UdpSession
|
||||
{
|
||||
public:
|
||||
UdpSession(Bonjour::ReplyFn rfn);
|
||||
virtual void handle_receive(const boost::system::error_code& error, size_t bytes) = 0;
|
||||
std::vector<char> buffer;
|
||||
boost::asio::ip::udp::endpoint remote_endpoint;
|
||||
protected:
|
||||
Bonjour::ReplyFn replyfn;
|
||||
};
|
||||
typedef std::shared_ptr<UdpSession> SharedSession;
|
||||
// Session for LookupSocket
|
||||
class LookupSession : public UdpSession
|
||||
{
|
||||
public:
|
||||
LookupSession(const LookupSocket* sckt, Bonjour::ReplyFn rfn) : UdpSession(rfn), socket(sckt) {}
|
||||
void handle_receive(const boost::system::error_code& error, size_t bytes) override;
|
||||
protected:
|
||||
// const pointer to socket to get needed data as txt_keys etc.
|
||||
const LookupSocket* socket;
|
||||
};
|
||||
// Session for ResolveSocket
|
||||
class ResolveSession : public UdpSession
|
||||
{
|
||||
public:
|
||||
ResolveSession(const ResolveSocket* sckt, Bonjour::ReplyFn rfn) : UdpSession(rfn), socket(sckt) {}
|
||||
void handle_receive(const boost::system::error_code& error, size_t bytes) override;
|
||||
protected:
|
||||
// const pointer to seocket to get hostname during handle_receive
|
||||
const ResolveSocket* socket;
|
||||
};
|
||||
|
||||
// Udp socket, starts receiving answers after first send() call until io_service is stopped.
|
||||
class UdpSocket
|
||||
{
|
||||
public:
|
||||
// Two constructors: 1st is with interface which must be resolved before calling this
|
||||
UdpSocket(Bonjour::ReplyFn replyfn
|
||||
, const boost::asio::ip::address& multicast_address
|
||||
, const boost::asio::ip::address& interface_address
|
||||
, std::shared_ptr< boost::asio::io_service > io_service);
|
||||
|
||||
UdpSocket(Bonjour::ReplyFn replyfn
|
||||
, const boost::asio::ip::address& multicast_address
|
||||
, std::shared_ptr< boost::asio::io_service > io_service);
|
||||
|
||||
void send();
|
||||
void async_receive();
|
||||
void cancel() { socket.cancel(); }
|
||||
protected:
|
||||
void receive_handler(SharedSession session, const boost::system::error_code& error, size_t bytes);
|
||||
virtual SharedSession create_session() const = 0;
|
||||
|
||||
Bonjour::ReplyFn replyfn;
|
||||
boost::asio::ip::address multicast_address;
|
||||
boost::asio::ip::udp::socket socket;
|
||||
boost::asio::ip::udp::endpoint mcast_endpoint;
|
||||
std::shared_ptr< boost::asio::io_service > io_service;
|
||||
std::vector<BonjourRequest> requests;
|
||||
};
|
||||
|
||||
class LookupSocket : public UdpSocket
|
||||
{
|
||||
public:
|
||||
LookupSocket(Bonjour::TxtKeys txt_keys
|
||||
, std::string service
|
||||
, std::string service_dn
|
||||
, std::string protocol
|
||||
, Bonjour::ReplyFn replyfn
|
||||
, const boost::asio::ip::address& multicast_address
|
||||
, const boost::asio::ip::address& interface_address
|
||||
, std::shared_ptr< boost::asio::io_service > io_service)
|
||||
: UdpSocket(replyfn, multicast_address, interface_address, io_service)
|
||||
, txt_keys(txt_keys)
|
||||
, service(service)
|
||||
, service_dn(service_dn)
|
||||
, protocol(protocol)
|
||||
{
|
||||
assert(!service.empty() && replyfn);
|
||||
create_request();
|
||||
}
|
||||
|
||||
LookupSocket(Bonjour::TxtKeys txt_keys
|
||||
, std::string service
|
||||
, std::string service_dn
|
||||
, std::string protocol
|
||||
, Bonjour::ReplyFn replyfn
|
||||
, const boost::asio::ip::address& multicast_address
|
||||
, std::shared_ptr< boost::asio::io_service > io_service)
|
||||
: UdpSocket(replyfn, multicast_address, io_service)
|
||||
, txt_keys(txt_keys)
|
||||
, service(service)
|
||||
, service_dn(service_dn)
|
||||
, protocol(protocol)
|
||||
{
|
||||
assert(!service.empty() && replyfn);
|
||||
create_request();
|
||||
}
|
||||
|
||||
const Bonjour::TxtKeys get_txt_keys() const { return txt_keys; }
|
||||
const std::string get_service() const { return service; }
|
||||
const std::string get_service_dn() const { return service_dn; }
|
||||
|
||||
protected:
|
||||
SharedSession create_session() const override;
|
||||
void create_request()
|
||||
{
|
||||
requests.clear();
|
||||
// create PTR request
|
||||
if (auto rqst = BonjourRequest::make_PTR(service, protocol); rqst)
|
||||
requests.push_back(std::move(rqst.get()));
|
||||
}
|
||||
boost::optional<BonjourRequest> request;
|
||||
Bonjour::TxtKeys txt_keys;
|
||||
std::string service;
|
||||
std::string service_dn;
|
||||
std::string protocol;
|
||||
};
|
||||
|
||||
class ResolveSocket : public UdpSocket
|
||||
{
|
||||
public:
|
||||
ResolveSocket(const std::string& hostname
|
||||
, Bonjour::ReplyFn replyfn
|
||||
, const boost::asio::ip::address& multicast_address
|
||||
, const boost::asio::ip::address& interface_address
|
||||
, std::shared_ptr< boost::asio::io_service > io_service)
|
||||
: UdpSocket(replyfn, multicast_address, interface_address, io_service)
|
||||
, hostname(hostname)
|
||||
|
||||
{
|
||||
assert(!hostname.empty() && replyfn);
|
||||
create_requests();
|
||||
}
|
||||
|
||||
ResolveSocket(const std::string& hostname
|
||||
, Bonjour::ReplyFn replyfn
|
||||
, const boost::asio::ip::address& multicast_address
|
||||
, std::shared_ptr< boost::asio::io_service > io_service)
|
||||
: UdpSocket(replyfn, multicast_address, io_service)
|
||||
, hostname(hostname)
|
||||
|
||||
{
|
||||
assert(!hostname.empty() && replyfn);
|
||||
create_requests();
|
||||
}
|
||||
|
||||
std::string get_hostname() const { return hostname; }
|
||||
protected:
|
||||
SharedSession create_session() const override;
|
||||
void create_requests()
|
||||
{
|
||||
requests.clear();
|
||||
// BonjourRequest::make_A / AAAA is now implemented to add .local correctly after the hostname.
|
||||
// If that is unsufficient, we need to change make_A / AAAA and pass full hostname.
|
||||
std::string trimmed_hostname = hostname;
|
||||
if (size_t dot_pos = trimmed_hostname.find_first_of('.'); dot_pos != std::string::npos)
|
||||
trimmed_hostname = trimmed_hostname.substr(0, dot_pos);
|
||||
if (auto rqst = BonjourRequest::make_A(trimmed_hostname); rqst)
|
||||
requests.push_back(std::move(rqst.get()));
|
||||
|
||||
trimmed_hostname = hostname;
|
||||
if (size_t dot_pos = trimmed_hostname.find_first_of('.'); dot_pos != std::string::npos)
|
||||
trimmed_hostname = trimmed_hostname.substr(0, dot_pos);
|
||||
if (auto rqst = BonjourRequest::make_AAAA(trimmed_hostname); rqst)
|
||||
requests.push_back(std::move(rqst.get()));
|
||||
}
|
||||
|
||||
std::string hostname;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
285
src/slic3r/Utils/Duet.cpp
Normal file
285
src/slic3r/Utils/Duet.cpp
Normal file
@@ -0,0 +1,285 @@
|
||||
#include "Duet.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "Http.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
Duet::Duet(DynamicPrintConfig *config) :
|
||||
host(config->opt_string("print_host")),
|
||||
password(config->opt_string("printhost_apikey"))
|
||||
{}
|
||||
|
||||
const char* Duet::get_name() const { return "Duet"; }
|
||||
|
||||
bool Duet::test(wxString &msg) const
|
||||
{
|
||||
auto connectionType = connect(msg);
|
||||
disconnect(connectionType);
|
||||
|
||||
return connectionType != ConnectionType::error;
|
||||
}
|
||||
|
||||
wxString Duet::get_test_ok_msg () const
|
||||
{
|
||||
return _(L("Connection to Duet works correctly."));
|
||||
}
|
||||
|
||||
wxString Duet::get_test_failed_msg (wxString &msg) const
|
||||
{
|
||||
return GUI::format_wxstr("%s: %s", _L("Could not connect to Duet"), msg);
|
||||
}
|
||||
|
||||
bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
wxString connect_msg;
|
||||
auto connectionType = connect(connect_msg);
|
||||
if (connectionType == ConnectionType::error) {
|
||||
error_fn(std::move(connect_msg));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool res = true;
|
||||
bool dsf = (connectionType == ConnectionType::dsf);
|
||||
|
||||
auto upload_cmd = get_upload_url(upload_data.upload_path.string(), connectionType);
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filepath: %2%, post_action: %3%, command: %4%")
|
||||
% upload_data.source_path
|
||||
% upload_data.upload_path
|
||||
% int(upload_data.post_action)
|
||||
% upload_cmd;
|
||||
|
||||
auto http = (dsf ? Http::put(std::move(upload_cmd)) : Http::post(std::move(upload_cmd)));
|
||||
if (dsf) {
|
||||
http.set_put_body(upload_data.source_path);
|
||||
} else {
|
||||
http.set_post_body(upload_data.source_path);
|
||||
}
|
||||
http.on_complete([&](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
|
||||
|
||||
int err_code = dsf ? (status == 201 ? 0 : 1) : get_err_code_from_body(body);
|
||||
if (err_code != 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Request completed but error code was received: %1%") % err_code;
|
||||
error_fn(format_error(body, L("Unknown error occured"), 0));
|
||||
res = false;
|
||||
} else if (upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
|
||||
wxString errormsg;
|
||||
res = start_print(errormsg, upload_data.upload_path.string(), connectionType, false);
|
||||
if (! res) {
|
||||
error_fn(std::move(errormsg));
|
||||
}
|
||||
} else if (upload_data.post_action == PrintHostPostUploadAction::StartSimulation) {
|
||||
wxString errormsg;
|
||||
res = start_print(errormsg, upload_data.upload_path.string(), connectionType, true);
|
||||
if (! res) {
|
||||
error_fn(std::move(errormsg));
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
|
||||
error_fn(format_error(body, error, status));
|
||||
res = false;
|
||||
})
|
||||
.on_progress([&](Http::Progress progress, bool &cancel) {
|
||||
prorgess_fn(std::move(progress), cancel);
|
||||
if (cancel) {
|
||||
// Upload was canceled
|
||||
BOOST_LOG_TRIVIAL(info) << "Duet: Upload canceled";
|
||||
res = false;
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
disconnect(connectionType);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Duet::ConnectionType Duet::connect(wxString &msg) const
|
||||
{
|
||||
auto res = ConnectionType::error;
|
||||
auto url = get_connect_url(false);
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
auto dsfUrl = get_connect_url(true);
|
||||
auto dsfHttp = Http::get(std::move(dsfUrl));
|
||||
dsfHttp.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error connecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
|
||||
msg = format_error(body, error, status);
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned) {
|
||||
res = ConnectionType::dsf;
|
||||
})
|
||||
.perform_sync();
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body;
|
||||
|
||||
int err_code = get_err_code_from_body(body);
|
||||
switch (err_code) {
|
||||
case 0:
|
||||
res = ConnectionType::rrf;
|
||||
break;
|
||||
case 1:
|
||||
msg = format_error(body, L("Wrong password"), 0);
|
||||
break;
|
||||
case 2:
|
||||
msg = format_error(body, L("Could not get resources to create a new connection"), 0);
|
||||
break;
|
||||
default:
|
||||
msg = format_error(body, L("Unknown error occured"), 0);
|
||||
break;
|
||||
}
|
||||
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void Duet::disconnect(ConnectionType connectionType) const
|
||||
{
|
||||
// we don't need to disconnect from DSF or if it failed anyway
|
||||
if (connectionType != ConnectionType::rrf) {
|
||||
return;
|
||||
}
|
||||
auto url = (boost::format("%1%rr_disconnect")
|
||||
% get_base_url()).str();
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
// we don't care about it, if disconnect is not working Duet will disconnect automatically after some time
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error disconnecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
|
||||
})
|
||||
.perform_sync();
|
||||
}
|
||||
|
||||
std::string Duet::get_upload_url(const std::string &filename, ConnectionType connectionType) const
|
||||
{
|
||||
assert(connectionType != ConnectionType::error);
|
||||
|
||||
if (connectionType == ConnectionType::dsf) {
|
||||
return (boost::format("%1%machine/file/gcodes/%2%")
|
||||
% get_base_url()
|
||||
% Http::url_encode(filename)).str();
|
||||
} else {
|
||||
return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%")
|
||||
% get_base_url()
|
||||
% Http::url_encode(filename)
|
||||
% timestamp_str()).str();
|
||||
}
|
||||
}
|
||||
|
||||
std::string Duet::get_connect_url(const bool dsfUrl) const
|
||||
{
|
||||
if (dsfUrl) {
|
||||
return (boost::format("%1%machine/status")
|
||||
% get_base_url()).str();
|
||||
} else {
|
||||
return (boost::format("%1%rr_connect?password=%2%&%3%")
|
||||
% get_base_url()
|
||||
% (password.empty() ? "reprap" : password)
|
||||
% timestamp_str()).str();
|
||||
}
|
||||
}
|
||||
|
||||
std::string Duet::get_base_url() const
|
||||
{
|
||||
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
||||
if (host.back() == '/') {
|
||||
return host;
|
||||
} else {
|
||||
return (boost::format("%1%/") % host).str();
|
||||
}
|
||||
} else {
|
||||
return (boost::format("http://%1%/") % host).str();
|
||||
}
|
||||
}
|
||||
|
||||
std::string Duet::timestamp_str() const
|
||||
{
|
||||
enum { BUFFER_SIZE = 32 };
|
||||
|
||||
auto t = std::time(nullptr);
|
||||
auto tm = *std::localtime(&t);
|
||||
|
||||
char buffer[BUFFER_SIZE];
|
||||
std::strftime(buffer, BUFFER_SIZE, "time=%Y-%m-%dT%H:%M:%S", &tm);
|
||||
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
bool Duet::start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const
|
||||
{
|
||||
assert(connectionType != ConnectionType::error);
|
||||
|
||||
bool res = false;
|
||||
bool dsf = (connectionType == ConnectionType::dsf);
|
||||
|
||||
auto url = dsf
|
||||
? (boost::format("%1%machine/code")
|
||||
% get_base_url()).str()
|
||||
: (boost::format(simulationMode
|
||||
? "%1%rr_gcode?gcode=M37%%20P\"0:/gcodes/%2%\""
|
||||
: "%1%rr_gcode?gcode=M32%%20\"0:/gcodes/%2%\"")
|
||||
% get_base_url()
|
||||
% Http::url_encode(filename)).str();
|
||||
|
||||
auto http = (dsf ? Http::post(std::move(url)) : Http::get(std::move(url)));
|
||||
if (dsf) {
|
||||
http.set_post_body(
|
||||
(boost::format(simulationMode
|
||||
? "M37 P\"0:/gcodes/%1%\""
|
||||
: "M32 \"0:/gcodes/%1%\"")
|
||||
% filename).str()
|
||||
);
|
||||
}
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error starting print: %1%, HTTP %2%, body: `%3%`") % error % status % body;
|
||||
msg = format_error(body, error, status);
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body;
|
||||
res = true;
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int Duet::get_err_code_from_body(const std::string &body) const
|
||||
{
|
||||
pt::ptree root;
|
||||
std::istringstream iss (body); // wrap returned json to istringstream
|
||||
pt::read_json(iss, root);
|
||||
|
||||
return root.get<int>("err", 0);
|
||||
}
|
||||
|
||||
}
|
||||
48
src/slic3r/Utils/Duet.hpp
Normal file
48
src/slic3r/Utils/Duet.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef slic3r_Duet_hpp_
|
||||
#define slic3r_Duet_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
|
||||
#include "PrintHost.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
|
||||
class Duet : public PrintHost
|
||||
{
|
||||
public:
|
||||
explicit Duet(DynamicPrintConfig *config);
|
||||
~Duet() override = default;
|
||||
|
||||
const char* get_name() const override;
|
||||
|
||||
bool test(wxString &curl_msg) const override;
|
||||
wxString get_test_ok_msg() const override;
|
||||
wxString get_test_failed_msg(wxString &msg) const override;
|
||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||
bool has_auto_discovery() const override { return false; }
|
||||
bool can_test() const override { return true; }
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint | PrintHostPostUploadAction::StartSimulation; }
|
||||
std::string get_host() const override { return host; }
|
||||
|
||||
private:
|
||||
enum class ConnectionType { rrf, dsf, error };
|
||||
std::string host;
|
||||
std::string password;
|
||||
|
||||
std::string get_upload_url(const std::string &filename, ConnectionType connectionType) const;
|
||||
std::string get_connect_url(const bool dsfUrl) const;
|
||||
std::string get_base_url() const;
|
||||
std::string timestamp_str() const;
|
||||
ConnectionType connect(wxString &msg) const;
|
||||
void disconnect(ConnectionType connectionType) const;
|
||||
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const;
|
||||
int get_err_code_from_body(const std::string &body) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
544
src/slic3r/Utils/EmbossStyleManager.cpp
Normal file
544
src/slic3r/Utils/EmbossStyleManager.cpp
Normal file
@@ -0,0 +1,544 @@
|
||||
#include "EmbossStyleManager.hpp"
|
||||
#include <GL/glew.h> // Imgui texture
|
||||
#include <imgui/imgui_internal.h> // ImTextCharFromUtf8
|
||||
#include "WxFontUtils.hpp"
|
||||
#include "libslic3r/Utils.hpp" // ScopeGuard
|
||||
|
||||
#include "slic3r/GUI/3DScene.hpp" // ::glsafe
|
||||
#include "slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp"
|
||||
#include "slic3r/GUI/ImGuiWrapper.hpp" // check of font ranges
|
||||
|
||||
#include "slic3r/Utils/EmbossStylesSerializable.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Emboss;
|
||||
using namespace Slic3r::GUI::Emboss;
|
||||
|
||||
StyleManager::StyleManager(const ImWchar *language_glyph_range, std::function<EmbossStyles()> create_default_styles)
|
||||
: m_imgui_init_glyph_range(language_glyph_range)
|
||||
, m_create_default_styles(create_default_styles)
|
||||
, m_exist_style_images(false)
|
||||
, m_temp_style_images(nullptr)
|
||||
, m_app_config(nullptr)
|
||||
, m_last_style_index(std::numeric_limits<size_t>::max())
|
||||
{}
|
||||
|
||||
StyleManager::~StyleManager() {
|
||||
clear_imgui_font();
|
||||
free_style_images();
|
||||
}
|
||||
|
||||
void StyleManager::init(AppConfig *app_config)
|
||||
{
|
||||
m_app_config = app_config;
|
||||
EmbossStyles styles = (app_config != nullptr) ?
|
||||
EmbossStylesSerializable::load_styles(*app_config) :
|
||||
EmbossStyles{};
|
||||
if (styles.empty())
|
||||
styles = m_create_default_styles();
|
||||
for (EmbossStyle &style : styles) {
|
||||
make_unique_name(style.name);
|
||||
m_style_items.push_back({style});
|
||||
}
|
||||
|
||||
std::optional<size_t> active_index_opt = (app_config != nullptr) ?
|
||||
EmbossStylesSerializable::load_style_index(*app_config) :
|
||||
std::optional<size_t>{};
|
||||
|
||||
size_t active_index = 0;
|
||||
if (active_index_opt.has_value()) active_index = *active_index_opt;
|
||||
if (active_index >= m_style_items.size()) active_index = 0;
|
||||
|
||||
// find valid font item
|
||||
if (load_style(active_index))
|
||||
return; // style is loaded
|
||||
|
||||
// Try to fix that style can't be loaded
|
||||
m_style_items.erase(m_style_items.begin() + active_index);
|
||||
|
||||
load_valid_style();
|
||||
}
|
||||
|
||||
bool StyleManager::store_styles_to_app_config(bool use_modification,
|
||||
bool store_active_index)
|
||||
{
|
||||
assert(m_app_config != nullptr);
|
||||
if (m_app_config == nullptr) return false;
|
||||
if (use_modification) {
|
||||
if (exist_stored_style()) {
|
||||
// update stored item
|
||||
m_style_items[m_style_cache.style_index].style = m_style_cache.style;
|
||||
} else {
|
||||
// add new into stored list
|
||||
EmbossStyle &style = m_style_cache.style;
|
||||
make_unique_name(style.name);
|
||||
m_style_cache.truncated_name.clear();
|
||||
m_style_cache.style_index = m_style_items.size();
|
||||
m_style_items.push_back({style});
|
||||
}
|
||||
m_style_cache.stored_wx_font = m_style_cache.wx_font;
|
||||
}
|
||||
|
||||
if (store_active_index)
|
||||
{
|
||||
size_t style_index = exist_stored_style() ?
|
||||
m_style_cache.style_index :
|
||||
m_last_style_index;
|
||||
EmbossStylesSerializable::store_style_index(*m_app_config, style_index);
|
||||
}
|
||||
|
||||
EmbossStyles styles;
|
||||
styles.reserve(m_style_items.size());
|
||||
for (const Item &item : m_style_items) styles.push_back(item.style);
|
||||
EmbossStylesSerializable::store_styles(*m_app_config, styles);
|
||||
return true;
|
||||
}
|
||||
|
||||
void StyleManager::add_style(const std::string &name) {
|
||||
EmbossStyle& style = m_style_cache.style;
|
||||
style.name = name;
|
||||
make_unique_name(style.name);
|
||||
m_style_cache.style_index = m_style_items.size();
|
||||
m_style_cache.stored_wx_font = m_style_cache.wx_font;
|
||||
m_style_cache.truncated_name.clear();
|
||||
m_style_items.push_back({style});
|
||||
}
|
||||
|
||||
void StyleManager::swap(size_t i1, size_t i2) {
|
||||
if (i1 >= m_style_items.size() ||
|
||||
i2 >= m_style_items.size()) return;
|
||||
std::swap(m_style_items[i1], m_style_items[i2]);
|
||||
// fix selected index
|
||||
if (!exist_stored_style()) return;
|
||||
if (m_style_cache.style_index == i1) {
|
||||
m_style_cache.style_index = i2;
|
||||
} else if (m_style_cache.style_index == i2) {
|
||||
m_style_cache.style_index = i1;
|
||||
}
|
||||
}
|
||||
|
||||
void StyleManager::discard_style_changes() {
|
||||
if (exist_stored_style()) {
|
||||
if (load_style(m_style_cache.style_index))
|
||||
return; // correct reload style
|
||||
} else {
|
||||
if(load_style(m_last_style_index))
|
||||
return; // correct load last used style
|
||||
}
|
||||
|
||||
// try to save situation by load some font
|
||||
load_valid_style();
|
||||
}
|
||||
|
||||
void StyleManager::erase(size_t index) {
|
||||
if (index >= m_style_items.size()) return;
|
||||
|
||||
// fix selected index
|
||||
if (exist_stored_style()) {
|
||||
size_t &i = m_style_cache.style_index;
|
||||
if (index < i) --i;
|
||||
else if (index == i) i = std::numeric_limits<size_t>::max();
|
||||
}
|
||||
|
||||
m_style_items.erase(m_style_items.begin() + index);
|
||||
}
|
||||
|
||||
void StyleManager::rename(const std::string& name) {
|
||||
m_style_cache.style.name = name;
|
||||
m_style_cache.truncated_name.clear();
|
||||
if (exist_stored_style()) {
|
||||
Item &it = m_style_items[m_style_cache.style_index];
|
||||
it.style.name = name;
|
||||
it.truncated_name.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void StyleManager::load_valid_style()
|
||||
{
|
||||
// iterate over all known styles
|
||||
while (!m_style_items.empty()) {
|
||||
if (load_style(0))
|
||||
return;
|
||||
// can't load so erase it from list
|
||||
m_style_items.erase(m_style_items.begin());
|
||||
}
|
||||
|
||||
// no one style is loadable
|
||||
// set up default font list
|
||||
EmbossStyles def_style = m_create_default_styles();
|
||||
for (EmbossStyle &style : def_style) {
|
||||
make_unique_name(style.name);
|
||||
m_style_items.push_back({std::move(style)});
|
||||
}
|
||||
|
||||
// iterate over default styles
|
||||
// There have to be option to use build in font
|
||||
while (!m_style_items.empty()) {
|
||||
if (load_style(0))
|
||||
return;
|
||||
// can't load so erase it from list
|
||||
m_style_items.erase(m_style_items.begin());
|
||||
}
|
||||
|
||||
// This OS doesn't have TTF as default font,
|
||||
// find some loadable font out of default list
|
||||
assert(false);
|
||||
}
|
||||
|
||||
bool StyleManager::load_style(size_t style_index)
|
||||
{
|
||||
if (style_index >= m_style_items.size()) return false;
|
||||
if (!load_style(m_style_items[style_index].style)) return false;
|
||||
m_style_cache.style_index = style_index;
|
||||
m_style_cache.stored_wx_font = m_style_cache.wx_font; // copy
|
||||
m_last_style_index = style_index;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StyleManager::load_style(const EmbossStyle &style) {
|
||||
if (style.type == EmbossStyle::Type::file_path) {
|
||||
std::unique_ptr<FontFile> font_ptr =
|
||||
create_font_file(style.path.c_str());
|
||||
if (font_ptr == nullptr) return false;
|
||||
m_style_cache.wx_font = {};
|
||||
m_style_cache.font_file =
|
||||
FontFileWithCache(std::move(font_ptr));
|
||||
m_style_cache.style = style; // copy
|
||||
m_style_cache.style_index = std::numeric_limits<size_t>::max();
|
||||
m_style_cache.stored_wx_font = {};
|
||||
return true;
|
||||
}
|
||||
if (style.type != WxFontUtils::get_actual_type()) return false;
|
||||
std::optional<wxFont> wx_font_opt = WxFontUtils::load_wxFont(style.path);
|
||||
if (!wx_font_opt.has_value()) return false;
|
||||
return load_style(style, *wx_font_opt);
|
||||
}
|
||||
|
||||
bool StyleManager::load_style(const EmbossStyle &style, const wxFont &font)
|
||||
{
|
||||
m_style_cache.style = style; // copy
|
||||
|
||||
// wx font property has bigger priority to set
|
||||
// it must be after copy of the style
|
||||
if (!set_wx_font(font)) return false;
|
||||
|
||||
m_style_cache.style_index = std::numeric_limits<size_t>::max();
|
||||
m_style_cache.stored_wx_font = {};
|
||||
m_style_cache.truncated_name.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StyleManager::is_font_changed() const
|
||||
{
|
||||
const wxFont &wx_font = get_wx_font();
|
||||
if (!wx_font.IsOk())
|
||||
return false;
|
||||
if (!exist_stored_style())
|
||||
return false;
|
||||
const EmbossStyle *stored_style = get_stored_style();
|
||||
if (stored_style == nullptr)
|
||||
return false;
|
||||
|
||||
const wxFont &wx_font_stored = get_stored_wx_font();
|
||||
if (!wx_font_stored.IsOk())
|
||||
return false;
|
||||
|
||||
const FontProp &prop = get_style().prop;
|
||||
const FontProp &prop_stored = stored_style->prop;
|
||||
|
||||
// Exist change in face name?
|
||||
if(wx_font_stored.GetFaceName() != wx_font.GetFaceName()) return true;
|
||||
|
||||
const std::optional<float> &skew = prop.skew;
|
||||
bool is_italic = skew.has_value() || WxFontUtils::is_italic(wx_font);
|
||||
const std::optional<float> &skew_stored = prop_stored.skew;
|
||||
bool is_stored_italic = skew_stored.has_value() || WxFontUtils::is_italic(wx_font_stored);
|
||||
// is italic changed
|
||||
if (is_italic != is_stored_italic)
|
||||
return true;
|
||||
|
||||
const std::optional<float> &boldness = prop.boldness;
|
||||
bool is_bold = boldness.has_value() || WxFontUtils::is_bold(wx_font);
|
||||
const std::optional<float> &boldness_stored = prop_stored.boldness;
|
||||
bool is_stored_bold = boldness_stored.has_value() || WxFontUtils::is_bold(wx_font_stored);
|
||||
// is bold changed
|
||||
return is_bold != is_stored_bold;
|
||||
}
|
||||
|
||||
bool StyleManager::is_active_font() { return m_style_cache.font_file.has_value(); }
|
||||
|
||||
const EmbossStyle* StyleManager::get_stored_style() const
|
||||
{
|
||||
if (m_style_cache.style_index >= m_style_items.size()) return nullptr;
|
||||
return &m_style_items[m_style_cache.style_index].style;
|
||||
}
|
||||
|
||||
void StyleManager::clear_glyphs_cache()
|
||||
{
|
||||
FontFileWithCache &ff = m_style_cache.font_file;
|
||||
if (!ff.has_value()) return;
|
||||
ff.cache = std::make_shared<Glyphs>();
|
||||
}
|
||||
|
||||
void StyleManager::clear_imgui_font() { m_style_cache.atlas.Clear(); }
|
||||
|
||||
ImFont *StyleManager::get_imgui_font()
|
||||
{
|
||||
if (!is_active_font()) return nullptr;
|
||||
|
||||
ImVector<ImFont *> &fonts = m_style_cache.atlas.Fonts;
|
||||
if (fonts.empty()) return nullptr;
|
||||
|
||||
// check correct index
|
||||
int f_size = fonts.size();
|
||||
assert(f_size == 1);
|
||||
if (f_size != 1) return nullptr;
|
||||
ImFont *font = fonts.front();
|
||||
if (font == nullptr) return nullptr;
|
||||
return font;
|
||||
}
|
||||
|
||||
const std::vector<StyleManager::Item> &StyleManager::get_styles() const{ return m_style_items; }
|
||||
|
||||
void StyleManager::make_unique_name(std::string &name)
|
||||
{
|
||||
auto is_unique = [&](const std::string &name) -> bool {
|
||||
for (const Item &it : m_style_items)
|
||||
if (it.style.name == name) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Style name can't be empty so default name is set
|
||||
if (name.empty()) name = "Text style";
|
||||
|
||||
// When name is already unique, nothing need to be changed
|
||||
if (is_unique(name)) return;
|
||||
|
||||
// when there is previous version of style name only find number
|
||||
const char *prefix = " (";
|
||||
const char suffix = ')';
|
||||
auto pos = name.find_last_of(prefix);
|
||||
if (name.c_str()[name.size() - 1] == suffix &&
|
||||
pos != std::string::npos) {
|
||||
// short name by ord number
|
||||
name = name.substr(0, pos);
|
||||
}
|
||||
|
||||
int order = 1; // start with value 2 to represents same font name
|
||||
std::string new_name;
|
||||
do {
|
||||
new_name = name + prefix + std::to_string(++order) + suffix;
|
||||
} while (!is_unique(new_name));
|
||||
name = new_name;
|
||||
}
|
||||
|
||||
void StyleManager::init_trunc_names(float max_width) {
|
||||
for (auto &s : m_style_items)
|
||||
if (s.truncated_name.empty()) {
|
||||
std::string name = s.style.name;
|
||||
ImGuiWrapper::escape_double_hash(name);
|
||||
s.truncated_name = ImGuiWrapper::trunc(name, max_width);
|
||||
}
|
||||
}
|
||||
|
||||
// for access to worker
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
|
||||
// for get DPI
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/MainFrame.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
|
||||
void StyleManager::init_style_images(const Vec2i &max_size,
|
||||
const std::string &text)
|
||||
{
|
||||
// check already initialized
|
||||
if (m_exist_style_images) return;
|
||||
|
||||
// check is initializing
|
||||
if (m_temp_style_images != nullptr) {
|
||||
// is initialization finished
|
||||
if (!m_temp_style_images->styles.empty()) {
|
||||
assert(m_temp_style_images->images.size() ==
|
||||
m_temp_style_images->styles.size());
|
||||
// copy images into styles
|
||||
for (StyleManager::StyleImage &image : m_temp_style_images->images){
|
||||
size_t index = &image - &m_temp_style_images->images.front();
|
||||
StyleImagesData::Item &style = m_temp_style_images->styles[index];
|
||||
|
||||
// find style in font list and copy to it
|
||||
for (auto &it : m_style_items) {
|
||||
if (it.style.name != style.text ||
|
||||
!(it.style.prop == style.prop))
|
||||
continue;
|
||||
it.image = image;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_temp_style_images = nullptr;
|
||||
m_exist_style_images = true;
|
||||
return;
|
||||
}
|
||||
// in process of initialization inside of job
|
||||
return;
|
||||
}
|
||||
|
||||
// create job for init images
|
||||
m_temp_style_images = std::make_shared<StyleImagesData::StyleImages>();
|
||||
StyleImagesData::Items styles;
|
||||
styles.reserve(m_style_items.size());
|
||||
for (const Item &item : m_style_items) {
|
||||
const EmbossStyle &style = item.style;
|
||||
std::optional<wxFont> wx_font_opt = WxFontUtils::load_wxFont(style.path);
|
||||
if (!wx_font_opt.has_value()) continue;
|
||||
std::unique_ptr<FontFile> font_file =
|
||||
WxFontUtils::create_font_file(*wx_font_opt);
|
||||
if (font_file == nullptr) continue;
|
||||
styles.push_back({
|
||||
FontFileWithCache(std::move(font_file)),
|
||||
style.name,
|
||||
style.prop
|
||||
});
|
||||
}
|
||||
|
||||
auto mf = wxGetApp().mainframe;
|
||||
// dot per inch for monitor
|
||||
int dpi = get_dpi_for_window(mf);
|
||||
// pixel per milimeter
|
||||
double ppm = dpi / ObjectManipulation::in_to_mm;
|
||||
|
||||
auto &worker = wxGetApp().plater()->get_ui_job_worker();
|
||||
StyleImagesData data{std::move(styles), max_size, text, m_temp_style_images, ppm};
|
||||
queue_job(worker, std::make_unique<CreateFontStyleImagesJob>(std::move(data)));
|
||||
}
|
||||
|
||||
void StyleManager::free_style_images() {
|
||||
if (!m_exist_style_images) return;
|
||||
GLuint tex_id = 0;
|
||||
for (Item &it : m_style_items) {
|
||||
if (tex_id == 0 && it.image.has_value())
|
||||
tex_id = (GLuint)(intptr_t) it.image->texture_id;
|
||||
it.image.reset();
|
||||
}
|
||||
if (tex_id != 0)
|
||||
glsafe(::glDeleteTextures(1, &tex_id));
|
||||
m_exist_style_images = false;
|
||||
}
|
||||
|
||||
float StyleManager::min_imgui_font_size = 18.f;
|
||||
float StyleManager::max_imgui_font_size = 60.f;
|
||||
float StyleManager::get_imgui_font_size(const FontProp &prop, const FontFile &file, double scale)
|
||||
{
|
||||
const auto &cn = prop.collection_number;
|
||||
unsigned int font_index = (cn.has_value()) ? *cn : 0;
|
||||
const auto &font_info = file.infos[font_index];
|
||||
// coeficient for convert line height to font size
|
||||
float c1 = (font_info.ascent - font_info.descent + font_info.linegap) /
|
||||
(float) font_info.unit_per_em;
|
||||
|
||||
// The point size is defined as 1/72 of the Anglo-Saxon inch (25.4 mm):
|
||||
// It is approximately 0.0139 inch or 352.8 um.
|
||||
return c1 * std::abs(prop.size_in_mm) / 0.3528f * scale;
|
||||
}
|
||||
|
||||
ImFont *StyleManager::create_imgui_font(const std::string &text, double scale)
|
||||
{
|
||||
// inspiration inside of ImGuiWrapper::init_font
|
||||
auto& ff = m_style_cache.font_file;
|
||||
if (!ff.has_value()) return nullptr;
|
||||
const FontFile &font_file = *ff.font_file;
|
||||
|
||||
ImFontGlyphRangesBuilder builder;
|
||||
builder.AddRanges(m_imgui_init_glyph_range);
|
||||
if (!text.empty())
|
||||
builder.AddText(text.c_str());
|
||||
|
||||
ImVector<ImWchar> &ranges = m_style_cache.ranges;
|
||||
ranges.clear();
|
||||
builder.BuildRanges(&ranges);
|
||||
|
||||
m_style_cache.atlas.Flags |= ImFontAtlasFlags_NoMouseCursors |
|
||||
ImFontAtlasFlags_NoPowerOfTwoHeight;
|
||||
|
||||
const FontProp &font_prop = m_style_cache.style.prop;
|
||||
float font_size = get_imgui_font_size(font_prop, font_file, scale);
|
||||
if (font_size < min_imgui_font_size)
|
||||
font_size = min_imgui_font_size;
|
||||
if (font_size > max_imgui_font_size)
|
||||
font_size = max_imgui_font_size;
|
||||
|
||||
ImFontConfig font_config;
|
||||
// TODO: start using merge mode
|
||||
//font_config.MergeMode = true;
|
||||
|
||||
unsigned int font_index = font_prop.collection_number.value_or(0);
|
||||
const auto &font_info = font_file.infos[font_index];
|
||||
if (font_prop.char_gap.has_value()) {
|
||||
float coef = font_size / (double) font_info.unit_per_em;
|
||||
font_config.GlyphExtraSpacing.x = coef * (*font_prop.char_gap);
|
||||
}
|
||||
if (font_prop.line_gap.has_value()) {
|
||||
float coef = font_size / (double) font_info.unit_per_em;
|
||||
font_config.GlyphExtraSpacing.y = coef * (*font_prop.line_gap);
|
||||
}
|
||||
|
||||
font_config.FontDataOwnedByAtlas = false;
|
||||
|
||||
const std::vector<unsigned char> &buffer = *font_file.data;
|
||||
ImFont * font = m_style_cache.atlas.AddFontFromMemoryTTF(
|
||||
(void *) buffer.data(), buffer.size(), font_size, &font_config, m_style_cache.ranges.Data);
|
||||
|
||||
unsigned char *pixels;
|
||||
int width, height;
|
||||
m_style_cache.atlas.GetTexDataAsRGBA32(&pixels, &width, &height);
|
||||
|
||||
// Upload texture to graphics system
|
||||
GLint last_texture;
|
||||
glsafe(::glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
|
||||
ScopeGuard sg([last_texture]() {
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, last_texture));
|
||||
});
|
||||
|
||||
GLuint font_texture;
|
||||
glsafe(::glGenTextures(1, &font_texture));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, font_texture));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
|
||||
if (OpenGLManager::are_compressed_textures_supported())
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
|
||||
else
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
|
||||
|
||||
// Store our identifier
|
||||
m_style_cache.atlas.TexID = (ImTextureID) (intptr_t) font_texture;
|
||||
assert(!m_style_cache.atlas.Fonts.empty());
|
||||
if (m_style_cache.atlas.Fonts.empty()) return nullptr;
|
||||
assert(font == m_style_cache.atlas.Fonts.back());
|
||||
if (!font->IsLoaded()) return nullptr;
|
||||
assert(font->IsLoaded());
|
||||
return font;
|
||||
}
|
||||
|
||||
bool StyleManager::set_wx_font(const wxFont &wx_font) {
|
||||
std::unique_ptr<FontFile> font_file =
|
||||
WxFontUtils::create_font_file(wx_font);
|
||||
return set_wx_font(wx_font, std::move(font_file));
|
||||
}
|
||||
|
||||
bool StyleManager::set_wx_font(const wxFont &wx_font, std::unique_ptr<FontFile> font_file)
|
||||
{
|
||||
if (font_file == nullptr) return false;
|
||||
m_style_cache.wx_font = wx_font; // copy
|
||||
m_style_cache.font_file =
|
||||
FontFileWithCache(std::move(font_file));
|
||||
|
||||
EmbossStyle &style = m_style_cache.style;
|
||||
style.type = WxFontUtils::get_actual_type();
|
||||
// update string path
|
||||
style.path = WxFontUtils::store_wxFont(wx_font);
|
||||
WxFontUtils::update_property(style.prop, wx_font);
|
||||
clear_imgui_font();
|
||||
return true;
|
||||
}
|
||||
300
src/slic3r/Utils/EmbossStyleManager.hpp
Normal file
300
src/slic3r/Utils/EmbossStyleManager.hpp
Normal file
@@ -0,0 +1,300 @@
|
||||
#ifndef slic3r_EmbossStyleManager_hpp_
|
||||
#define slic3r_EmbossStyleManager_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <imgui/imgui.h>
|
||||
#include <wx/font.h>
|
||||
#include <GL/glew.h>
|
||||
#include <libslic3r/BoundingBox.hpp>
|
||||
#include <libslic3r/Emboss.hpp>
|
||||
#include <libslic3r/AppConfig.hpp>
|
||||
|
||||
namespace Slic3r::GUI::Emboss {
|
||||
/// <summary>
|
||||
/// Manage Emboss text styles
|
||||
/// Cache actual state of style
|
||||
/// + imgui font
|
||||
/// + wx font
|
||||
/// </summary>
|
||||
class StyleManager
|
||||
{
|
||||
friend class CreateFontStyleImagesJob; // access to StyleImagesData
|
||||
|
||||
public:
|
||||
/// <param name="language_glyph_range">Character to load for imgui when initialize imgui font</param>
|
||||
/// <param name="create_default_styles">Function to create default styles</param>
|
||||
StyleManager(const ImWchar *language_glyph_range, std::function<EmbossStyles()> create_default_styles);
|
||||
|
||||
/// <summary>
|
||||
/// Release imgui font and style images from GPU
|
||||
/// </summary>
|
||||
~StyleManager();
|
||||
|
||||
/// <summary>
|
||||
/// Load font style list from config
|
||||
/// Also select actual activ font
|
||||
/// </summary>
|
||||
/// <param name="app_config">Application configuration loaded from file "QIDISlicer.ini"
|
||||
/// + cfg is stored to privat variable</param>
|
||||
void init(AppConfig *app_config);
|
||||
|
||||
/// <summary>
|
||||
/// Write font list into AppConfig
|
||||
/// </summary>
|
||||
/// <param name="item_to_store">Configuration</param>
|
||||
/// <param name="use_modification">When true cache state will be used for store</param>
|
||||
/// <param name="use_modification">When true store activ index into configuration</param>
|
||||
/// <returns>True on succes otherwise False.</returns>
|
||||
bool store_styles_to_app_config(bool use_modification = true, bool store_active_index = true);
|
||||
|
||||
/// <summary>
|
||||
/// Append actual style to style list
|
||||
/// </summary>
|
||||
/// <param name="name">New name for style</param>
|
||||
void add_style(const std::string& name);
|
||||
|
||||
/// <summary>
|
||||
/// Change order of style item in m_style_items.
|
||||
/// Fix selected font index when (i1 || i2) == m_font_selected
|
||||
/// </summary>
|
||||
/// <param name="i1">First index to m_style_items</param>
|
||||
/// <param name="i2">Second index to m_style_items</param>
|
||||
void swap(size_t i1, size_t i2);
|
||||
|
||||
/// <summary>
|
||||
/// Discard changes in activ style
|
||||
/// When no activ style use last used OR first loadable
|
||||
/// </summary>
|
||||
void discard_style_changes();
|
||||
|
||||
/// <summary>
|
||||
/// Remove style from m_style_items.
|
||||
/// Fix selected font index when index is under m_font_selected
|
||||
/// </summary>
|
||||
/// <param name="index">Index of style to be removed</param>
|
||||
void erase(size_t index);
|
||||
|
||||
/// <summary>
|
||||
/// Rename actual selected font item
|
||||
/// </summary>
|
||||
/// <param name="name">New name</param>
|
||||
void rename(const std::string &name);
|
||||
|
||||
/// <summary>
|
||||
/// load some valid style
|
||||
/// </summary>
|
||||
void load_valid_style();
|
||||
|
||||
/// <summary>
|
||||
/// Change active font
|
||||
/// When font not loaded roll back activ font
|
||||
/// </summary>
|
||||
/// <param name="font_index">New font index(from m_style_items range)</param>
|
||||
/// <returns>True on succes. False on fail load font</returns>
|
||||
bool load_style(size_t font_index);
|
||||
// load font style not stored in list
|
||||
bool load_style(const EmbossStyle &style);
|
||||
// fastering load font on index by wxFont, ignore type and descriptor
|
||||
bool load_style(const EmbossStyle &style, const wxFont &font);
|
||||
|
||||
// clear actual selected glyphs cache
|
||||
void clear_glyphs_cache();
|
||||
|
||||
// remove cached imgui font for actual selected font
|
||||
void clear_imgui_font();
|
||||
|
||||
// getters for private data
|
||||
const EmbossStyle *get_stored_style() const;
|
||||
|
||||
const EmbossStyle &get_style() const { return m_style_cache.style; }
|
||||
EmbossStyle &get_style() { return m_style_cache.style; }
|
||||
size_t get_style_index() const { return m_style_cache.style_index; }
|
||||
std::string &get_truncated_name() { return m_style_cache.truncated_name; }
|
||||
const ImFontAtlas &get_atlas() const { return m_style_cache.atlas; }
|
||||
const FontProp &get_font_prop() const { return get_style().prop; }
|
||||
FontProp &get_font_prop() { return get_style().prop; }
|
||||
const wxFont &get_wx_font() const { return m_style_cache.wx_font; }
|
||||
const wxFont &get_stored_wx_font() const { return m_style_cache.stored_wx_font; }
|
||||
Slic3r::Emboss::FontFileWithCache &get_font_file_with_cache() { return m_style_cache.font_file; }
|
||||
bool has_collections() const { return m_style_cache.font_file.font_file != nullptr &&
|
||||
m_style_cache.font_file.font_file->infos.size() > 1; }
|
||||
|
||||
// True when activ style has same name as some of stored style
|
||||
bool exist_stored_style() const { return m_style_cache.style_index != std::numeric_limits<size_t>::max(); }
|
||||
|
||||
/// <summary>
|
||||
/// check whether current style differ to selected
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool is_font_changed() const;
|
||||
|
||||
/// <summary>
|
||||
/// Setter on wx_font when changed
|
||||
/// </summary>
|
||||
/// <param name="wx_font">new wx font</param>
|
||||
/// <returns>True on success set otherwise FALSE</returns>
|
||||
bool set_wx_font(const wxFont &wx_font);
|
||||
|
||||
/// <summary>
|
||||
/// Faster way of set wx_font when font file is known(do not load font file twice)
|
||||
/// When you not sure that wx_font is made by font_file use only set_wx_font(wx_font)
|
||||
/// </summary>
|
||||
/// <param name="wx_font">Must be source of font file</param>
|
||||
/// <param name="font_file">font file created by WxFontUtils::create_font_file(wx_font)</param>
|
||||
/// <returns>True on success otherwise false</returns>
|
||||
bool set_wx_font(const wxFont &wx_font, std::unique_ptr<Slic3r::Emboss::FontFile> font_file);
|
||||
|
||||
// Getter on acitve font pointer for imgui
|
||||
// Initialize imgui font(generate texture) when doesn't exist yet.
|
||||
// Extend font atlas when not in glyph range
|
||||
ImFont *get_imgui_font();
|
||||
// initialize font range by unique symbols in text
|
||||
ImFont *create_imgui_font(const std::string& text, double scale);
|
||||
|
||||
// init truncated names of styles
|
||||
void init_trunc_names(float max_width);
|
||||
|
||||
/// <summary>
|
||||
/// Initialization texture with rendered font style
|
||||
/// </summary>
|
||||
/// <param name="max_size">Maximal width and height of one style texture</param>
|
||||
/// <param name="text">Text to render by style</param>
|
||||
void init_style_images(const Vec2i& max_size, const std::string &text);
|
||||
void free_style_images();
|
||||
|
||||
struct Item;
|
||||
// access to all managed font styles
|
||||
const std::vector<Item> &get_styles() const;
|
||||
|
||||
/// <summary>
|
||||
/// Describe image in GPU to show settings of style
|
||||
/// </summary>
|
||||
struct StyleImage
|
||||
{
|
||||
void* texture_id = 0; // GLuint
|
||||
BoundingBox bounding_box;
|
||||
ImVec2 tex_size, uv0, uv1;
|
||||
Point offset = Point(0, 0);
|
||||
StyleImage() = default;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// All connected with one style
|
||||
/// keep temporary data and caches for style
|
||||
/// </summary>
|
||||
struct Item
|
||||
{
|
||||
// define font, style and other property of text
|
||||
EmbossStyle style;
|
||||
|
||||
// cache for view font name with maximal width in imgui
|
||||
std::string truncated_name;
|
||||
|
||||
// visualization of style
|
||||
std::optional<StyleImage> image;
|
||||
};
|
||||
|
||||
// check if exist selected font style in manager
|
||||
bool is_active_font();
|
||||
|
||||
// Limits for imgui loaded font size
|
||||
// Value out of limits is crop
|
||||
static float min_imgui_font_size;
|
||||
static float max_imgui_font_size;
|
||||
static float get_imgui_font_size(const FontProp &prop, const Slic3r::Emboss::FontFile &file, double scale);
|
||||
|
||||
private:
|
||||
std::function<EmbossStyles()> m_create_default_styles;
|
||||
|
||||
/// <summary>
|
||||
/// Cache data from style to reduce amount of:
|
||||
/// 1) loading font from file
|
||||
/// 2) Create atlas of symbols for imgui
|
||||
/// 3) Keep loaded(and modified by style) glyphs from font
|
||||
/// </summary>
|
||||
struct StyleCache
|
||||
{
|
||||
// share font file data with emboss job thread
|
||||
Slic3r::Emboss::FontFileWithCache font_file = {};
|
||||
|
||||
// must live same as imgui_font inside of atlas
|
||||
ImVector<ImWchar> ranges = {};
|
||||
|
||||
// Keep only actual style in atlas
|
||||
ImFontAtlas atlas = {};
|
||||
|
||||
// wx widget font
|
||||
wxFont wx_font = {};
|
||||
|
||||
// cache for view font name with maximal width in imgui
|
||||
std::string truncated_name;
|
||||
|
||||
// actual used font item
|
||||
EmbossStyle style = {};
|
||||
|
||||
// cache for stored wx font to not create every frame
|
||||
wxFont stored_wx_font = {};
|
||||
|
||||
// index into m_style_items
|
||||
size_t style_index = std::numeric_limits<size_t>::max();
|
||||
|
||||
} m_style_cache;
|
||||
|
||||
void make_unique_name(std::string &name);
|
||||
|
||||
// Privat member
|
||||
std::vector<Item> m_style_items;
|
||||
AppConfig *m_app_config;
|
||||
size_t m_last_style_index;
|
||||
|
||||
/// <summary>
|
||||
/// Keep data needed to create Font Style Images in Job
|
||||
/// </summary>
|
||||
struct StyleImagesData
|
||||
{
|
||||
struct Item
|
||||
{
|
||||
Slic3r::Emboss::FontFileWithCache font;
|
||||
std::string text;
|
||||
FontProp prop;
|
||||
};
|
||||
using Items = std::vector<Item>;
|
||||
|
||||
// Keep styles to render
|
||||
Items styles;
|
||||
// Maximal width and height in pixels of image
|
||||
Vec2i max_size;
|
||||
// Text to render
|
||||
std::string text;
|
||||
|
||||
/// <summary>
|
||||
/// Result of job
|
||||
/// </summary>
|
||||
struct StyleImages
|
||||
{
|
||||
// vector of inputs
|
||||
StyleImagesData::Items styles;
|
||||
// job output
|
||||
std::vector<StyleImage> images;
|
||||
};
|
||||
|
||||
// place to store result in main thread in Finalize
|
||||
std::shared_ptr<StyleImages> result;
|
||||
|
||||
// pixel per milimeter (scaled DPI)
|
||||
double ppm;
|
||||
};
|
||||
std::shared_ptr<StyleImagesData::StyleImages> m_temp_style_images;
|
||||
bool m_exist_style_images;
|
||||
|
||||
// store all font GLImages
|
||||
//ImFontAtlas m_imgui_font_atlas;
|
||||
const ImWchar *m_imgui_init_glyph_range;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_EmbossStyleManager_hpp_
|
||||
201
src/slic3r/Utils/EmbossStylesSerializable.cpp
Normal file
201
src/slic3r/Utils/EmbossStylesSerializable.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#include "EmbossStylesSerializable.hpp"
|
||||
|
||||
#include <libslic3r/AppConfig.hpp>
|
||||
#include "WxFontUtils.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_NAME = "name";
|
||||
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_DESCRIPTOR = "descriptor";
|
||||
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_LINE_HEIGHT = "line_height";
|
||||
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_DEPTH = "depth";
|
||||
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_USE_SURFACE = "use_surface";
|
||||
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_BOLDNESS = "boldness";
|
||||
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_SKEW = "skew";
|
||||
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_DISTANCE = "distance";
|
||||
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_ANGLE = "angle";
|
||||
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_COLLECTION = "collection";
|
||||
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_CHAR_GAP = "char_gap";
|
||||
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_LINE_GAP = "line_gap";
|
||||
|
||||
const std::string EmbossStylesSerializable::APP_CONFIG_ACTIVE_FONT = "active_font";
|
||||
|
||||
std::string EmbossStylesSerializable::create_section_name(unsigned index)
|
||||
{
|
||||
return AppConfig::SECTION_EMBOSS_STYLE + ':' + std::to_string(index);
|
||||
}
|
||||
|
||||
// check only existence of flag
|
||||
bool EmbossStylesSerializable::read(const std::map<std::string, std::string>& section, const std::string& key, bool& value){
|
||||
auto item = section.find(key);
|
||||
if (item == section.end()) return false;
|
||||
|
||||
value = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
#include "fast_float/fast_float.h"
|
||||
bool EmbossStylesSerializable::read(const std::map<std::string, std::string>& section, const std::string& key, float& value){
|
||||
auto item = section.find(key);
|
||||
if (item == section.end()) return false;
|
||||
const std::string &data = item->second;
|
||||
if (data.empty()) return false;
|
||||
float value_;
|
||||
fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_);
|
||||
// read only non zero value
|
||||
if (fabs(value_) <= std::numeric_limits<float>::epsilon()) return false;
|
||||
|
||||
value = value_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmbossStylesSerializable::read(const std::map<std::string, std::string>& section, const std::string& key, std::optional<int>& value){
|
||||
auto item = section.find(key);
|
||||
if (item == section.end()) return false;
|
||||
const std::string &data = item->second;
|
||||
if (data.empty()) return false;
|
||||
int value_ = std::atoi(data.c_str());
|
||||
if (value_ == 0) return false;
|
||||
|
||||
value = value_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmbossStylesSerializable::read(const std::map<std::string, std::string>& section, const std::string& key, std::optional<unsigned int>& value){
|
||||
auto item = section.find(key);
|
||||
if (item == section.end()) return false;
|
||||
const std::string &data = item->second;
|
||||
if (data.empty()) return false;
|
||||
int value_ = std::atoi(data.c_str());
|
||||
if (value_ <= 0) return false;
|
||||
|
||||
value = static_cast<unsigned int>(value_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmbossStylesSerializable::read(const std::map<std::string, std::string>& section, const std::string& key, std::optional<float>& value){
|
||||
auto item = section.find(key);
|
||||
if (item == section.end()) return false;
|
||||
const std::string &data = item->second;
|
||||
if (data.empty()) return false;
|
||||
float value_;
|
||||
fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_);
|
||||
// read only non zero value
|
||||
if (fabs(value_) <= std::numeric_limits<float>::epsilon()) return false;
|
||||
|
||||
value = value_;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<EmbossStyle> EmbossStylesSerializable::load_style(
|
||||
const std::map<std::string, std::string> &app_cfg_section)
|
||||
{
|
||||
auto path_it = app_cfg_section.find(APP_CONFIG_FONT_DESCRIPTOR);
|
||||
if (path_it == app_cfg_section.end()) return {};
|
||||
const std::string &path = path_it->second;
|
||||
|
||||
auto name_it = app_cfg_section.find(APP_CONFIG_FONT_NAME);
|
||||
const std::string default_name = "font_name";
|
||||
const std::string &name =
|
||||
(name_it == app_cfg_section.end()) ?
|
||||
default_name : name_it->second;
|
||||
|
||||
FontProp fp;
|
||||
read(app_cfg_section, APP_CONFIG_FONT_LINE_HEIGHT, fp.size_in_mm);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_DEPTH, fp.emboss);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_USE_SURFACE, fp.use_surface);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_BOLDNESS, fp.boldness);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_SKEW, fp.skew);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_DISTANCE, fp.distance);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_ANGLE, fp.angle);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_COLLECTION, fp.collection_number);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_CHAR_GAP, fp.char_gap);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_LINE_GAP, fp.line_gap);
|
||||
|
||||
EmbossStyle::Type type = WxFontUtils::get_actual_type();
|
||||
return EmbossStyle{ name, path, type, fp };
|
||||
}
|
||||
|
||||
void EmbossStylesSerializable::store_style(AppConfig & cfg,
|
||||
const EmbossStyle &fi,
|
||||
unsigned index)
|
||||
{
|
||||
std::map<std::string, std::string> data;
|
||||
data[APP_CONFIG_FONT_NAME] = fi.name;
|
||||
data[APP_CONFIG_FONT_DESCRIPTOR] = fi.path;
|
||||
const FontProp &fp = fi.prop;
|
||||
data[APP_CONFIG_FONT_LINE_HEIGHT] = std::to_string(fp.size_in_mm);
|
||||
data[APP_CONFIG_FONT_DEPTH] = std::to_string(fp.emboss);
|
||||
if (fp.use_surface)
|
||||
data[APP_CONFIG_FONT_USE_SURFACE] = "true";
|
||||
if (fp.boldness.has_value())
|
||||
data[APP_CONFIG_FONT_BOLDNESS] = std::to_string(*fp.boldness);
|
||||
if (fp.skew.has_value())
|
||||
data[APP_CONFIG_FONT_SKEW] = std::to_string(*fp.skew);
|
||||
if (fp.distance.has_value())
|
||||
data[APP_CONFIG_FONT_DISTANCE] = std::to_string(*fp.distance);
|
||||
if (fp.angle.has_value())
|
||||
data[APP_CONFIG_FONT_ANGLE] = std::to_string(*fp.angle);
|
||||
if (fp.collection_number.has_value())
|
||||
data[APP_CONFIG_FONT_COLLECTION] = std::to_string(*fp.collection_number);
|
||||
if (fp.char_gap.has_value())
|
||||
data[APP_CONFIG_FONT_CHAR_GAP] = std::to_string(*fp.char_gap);
|
||||
if (fp.line_gap.has_value())
|
||||
data[APP_CONFIG_FONT_LINE_GAP] = std::to_string(*fp.line_gap);
|
||||
cfg.set_section(create_section_name(index), std::move(data));
|
||||
}
|
||||
|
||||
void EmbossStylesSerializable::store_style_index(AppConfig &cfg, unsigned index) {
|
||||
// store actual font index
|
||||
// active font first index is +1 to correspond with section name
|
||||
std::map<std::string, std::string> data;
|
||||
data[APP_CONFIG_ACTIVE_FONT] = std::to_string(index);
|
||||
cfg.set_section(AppConfig::SECTION_EMBOSS_STYLE, std::move(data));
|
||||
}
|
||||
|
||||
std::optional<size_t> EmbossStylesSerializable::load_style_index(const AppConfig &cfg)
|
||||
{
|
||||
if (!cfg.has_section(AppConfig::SECTION_EMBOSS_STYLE)) return {};
|
||||
|
||||
auto section = cfg.get_section(AppConfig::SECTION_EMBOSS_STYLE);
|
||||
auto it = section.find(APP_CONFIG_ACTIVE_FONT);
|
||||
if (it == section.end()) return {};
|
||||
|
||||
size_t active_font = static_cast<size_t>(std::atoi(it->second.c_str()));
|
||||
// order in config starts with number 1
|
||||
return active_font - 1;
|
||||
}
|
||||
|
||||
EmbossStyles EmbossStylesSerializable::load_styles(const AppConfig &cfg)
|
||||
{
|
||||
EmbossStyles result;
|
||||
// human readable index inside of config starts from 1 !!
|
||||
unsigned index = 1;
|
||||
std::string section_name = create_section_name(index);
|
||||
while (cfg.has_section(section_name)) {
|
||||
std::optional<EmbossStyle> style_opt = load_style(cfg.get_section(section_name));
|
||||
if (style_opt.has_value()) result.emplace_back(*style_opt);
|
||||
section_name = create_section_name(++index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void EmbossStylesSerializable::store_styles(AppConfig &cfg, const EmbossStyles& styles)
|
||||
{
|
||||
// store styles
|
||||
unsigned index = 1;
|
||||
for (const EmbossStyle &style : styles) {
|
||||
// skip file paths + fonts from other OS(loaded from .3mf)
|
||||
assert(style.type == WxFontUtils::get_actual_type());
|
||||
// if (style_opt.type != WxFontUtils::get_actual_type()) continue;
|
||||
store_style(cfg, style, index++);
|
||||
}
|
||||
|
||||
// remove rest of font sections (after deletation)
|
||||
std::string section_name = create_section_name(index);
|
||||
while (cfg.has_section(section_name)) {
|
||||
cfg.clear_section(section_name);
|
||||
section_name = create_section_name(++index);
|
||||
}
|
||||
}
|
||||
58
src/slic3r/Utils/EmbossStylesSerializable.hpp
Normal file
58
src/slic3r/Utils/EmbossStylesSerializable.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef slic3r_EmbossStylesSerializable_hpp_
|
||||
#define slic3r_EmbossStylesSerializable_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <libslic3r/TextConfiguration.hpp> // EmbossStyles+EmbossStyle
|
||||
|
||||
namespace Slic3r {
|
||||
class AppConfig;
|
||||
}
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
/// <summary>
|
||||
/// For store/load font list to/from AppConfig
|
||||
/// </summary>
|
||||
class EmbossStylesSerializable
|
||||
{
|
||||
static const std::string APP_CONFIG_FONT_NAME;
|
||||
static const std::string APP_CONFIG_FONT_DESCRIPTOR;
|
||||
static const std::string APP_CONFIG_FONT_LINE_HEIGHT;
|
||||
static const std::string APP_CONFIG_FONT_DEPTH;
|
||||
static const std::string APP_CONFIG_FONT_USE_SURFACE;
|
||||
static const std::string APP_CONFIG_FONT_BOLDNESS;
|
||||
static const std::string APP_CONFIG_FONT_SKEW;
|
||||
static const std::string APP_CONFIG_FONT_DISTANCE;
|
||||
static const std::string APP_CONFIG_FONT_ANGLE;
|
||||
static const std::string APP_CONFIG_FONT_COLLECTION;
|
||||
static const std::string APP_CONFIG_FONT_CHAR_GAP;
|
||||
static const std::string APP_CONFIG_FONT_LINE_GAP;
|
||||
|
||||
static const std::string APP_CONFIG_ACTIVE_FONT;
|
||||
public:
|
||||
EmbossStylesSerializable() = delete;
|
||||
|
||||
static void store_style_index(AppConfig &cfg, unsigned index);
|
||||
static std::optional<size_t> load_style_index(const AppConfig &cfg);
|
||||
|
||||
static EmbossStyles load_styles(const AppConfig &cfg);
|
||||
static void store_styles(AppConfig &cfg, const EmbossStyles& styles);
|
||||
|
||||
private:
|
||||
static std::string create_section_name(unsigned index);
|
||||
static std::optional<EmbossStyle> load_style(const std::map<std::string, std::string> &app_cfg_section);
|
||||
static void store_style(AppConfig &cfg, const EmbossStyle &style, unsigned index);
|
||||
|
||||
// TODO: move to app config like read from section
|
||||
static bool read(const std::map<std::string, std::string>& section, const std::string& key, bool& value);
|
||||
static bool read(const std::map<std::string, std::string>& section, const std::string& key, float& value);
|
||||
static bool read(const std::map<std::string, std::string>& section, const std::string& key, std::optional<int>& value);
|
||||
static bool read(const std::map<std::string, std::string>& section, const std::string& key, std::optional<unsigned int>& value);
|
||||
static bool read(const std::map<std::string, std::string>& section, const std::string& key, std::optional<float>& value);
|
||||
};
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // #define slic3r_EmbossStylesSerializable_hpp_
|
||||
|
||||
449
src/slic3r/Utils/FixModelByWin10.cpp
Normal file
449
src/slic3r/Utils/FixModelByWin10.cpp
Normal file
@@ -0,0 +1,449 @@
|
||||
#ifdef HAS_WIN10SDK
|
||||
|
||||
#ifndef NOMINMAX
|
||||
# define NOMINMAX
|
||||
#endif
|
||||
|
||||
// Windows Runtime
|
||||
#include <roapi.h>
|
||||
// for ComPtr
|
||||
#include <wrl/client.h>
|
||||
|
||||
// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/
|
||||
#include <winrt/robuffer.h>
|
||||
#include <winrt/windows.storage.provider.h>
|
||||
#include <winrt/windows.graphics.printing3d.h>
|
||||
|
||||
#include "FixModelByWin10.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <condition_variable>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Format/3mf.hpp"
|
||||
#include "../GUI/GUI.hpp"
|
||||
#include "../GUI/I18N.hpp"
|
||||
#include "../GUI/MsgDialog.hpp"
|
||||
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/progdlg.h>
|
||||
|
||||
extern "C"{
|
||||
// from rapi.h
|
||||
typedef HRESULT (__stdcall* FunctionRoInitialize)(int);
|
||||
typedef HRESULT (__stdcall* FunctionRoUninitialize)();
|
||||
typedef HRESULT (__stdcall* FunctionRoActivateInstance)(HSTRING activatableClassId, IInspectable **instance);
|
||||
typedef HRESULT (__stdcall* FunctionRoGetActivationFactory)(HSTRING activatableClassId, REFIID iid, void **factory);
|
||||
// from winstring.h
|
||||
typedef HRESULT (__stdcall* FunctionWindowsCreateString)(LPCWSTR sourceString, UINT32 length, HSTRING *string);
|
||||
typedef HRESULT (__stdcall* FunctionWindowsDelteString)(HSTRING string);
|
||||
}
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
HMODULE s_hRuntimeObjectLibrary = nullptr;
|
||||
FunctionRoInitialize s_RoInitialize = nullptr;
|
||||
FunctionRoUninitialize s_RoUninitialize = nullptr;
|
||||
FunctionRoActivateInstance s_RoActivateInstance = nullptr;
|
||||
FunctionRoGetActivationFactory s_RoGetActivationFactory = nullptr;
|
||||
FunctionWindowsCreateString s_WindowsCreateString = nullptr;
|
||||
FunctionWindowsDelteString s_WindowsDeleteString = nullptr;
|
||||
|
||||
bool winrt_load_runtime_object_library()
|
||||
{
|
||||
if (s_hRuntimeObjectLibrary == nullptr)
|
||||
s_hRuntimeObjectLibrary = LoadLibrary(L"ComBase.dll");
|
||||
if (s_hRuntimeObjectLibrary != nullptr) {
|
||||
s_RoInitialize = (FunctionRoInitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoInitialize");
|
||||
s_RoUninitialize = (FunctionRoUninitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoUninitialize");
|
||||
s_RoActivateInstance = (FunctionRoActivateInstance) GetProcAddress(s_hRuntimeObjectLibrary, "RoActivateInstance");
|
||||
s_RoGetActivationFactory = (FunctionRoGetActivationFactory) GetProcAddress(s_hRuntimeObjectLibrary, "RoGetActivationFactory");
|
||||
s_WindowsCreateString = (FunctionWindowsCreateString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsCreateString");
|
||||
s_WindowsDeleteString = (FunctionWindowsDelteString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsDeleteString");
|
||||
}
|
||||
return s_RoInitialize && s_RoUninitialize && s_RoActivateInstance && s_WindowsCreateString && s_WindowsDeleteString;
|
||||
}
|
||||
|
||||
static HRESULT winrt_activate_instance(const std::wstring &class_name, IInspectable **pinst)
|
||||
{
|
||||
HSTRING hClassName;
|
||||
HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName);
|
||||
if (S_OK != hr)
|
||||
return hr;
|
||||
hr = (*s_RoActivateInstance)(hClassName, pinst);
|
||||
(*s_WindowsDeleteString)(hClassName);
|
||||
return hr;
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
static HRESULT winrt_activate_instance(const std::wstring &class_name, TYPE **pinst)
|
||||
{
|
||||
IInspectable *pinspectable = nullptr;
|
||||
HRESULT hr = winrt_activate_instance(class_name, &pinspectable);
|
||||
if (S_OK != hr)
|
||||
return hr;
|
||||
hr = pinspectable->QueryInterface(__uuidof(TYPE), (void**)pinst);
|
||||
pinspectable->Release();
|
||||
return hr;
|
||||
}
|
||||
|
||||
static HRESULT winrt_get_activation_factory(const std::wstring &class_name, REFIID iid, void **pinst)
|
||||
{
|
||||
HSTRING hClassName;
|
||||
HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName);
|
||||
if (S_OK != hr)
|
||||
return hr;
|
||||
hr = (*s_RoGetActivationFactory)(hClassName, iid, pinst);
|
||||
(*s_WindowsDeleteString)(hClassName);
|
||||
return hr;
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
static HRESULT winrt_get_activation_factory(const std::wstring &class_name, TYPE **pinst)
|
||||
{
|
||||
return winrt_get_activation_factory(class_name, __uuidof(TYPE), reinterpret_cast<void**>(pinst));
|
||||
}
|
||||
|
||||
// To be called often to test whether to cancel the operation.
|
||||
typedef std::function<void ()> ThrowOnCancelFn;
|
||||
|
||||
template<typename T>
|
||||
static AsyncStatus winrt_async_await(const Microsoft::WRL::ComPtr<T> &asyncAction, ThrowOnCancelFn throw_on_cancel, int blocking_tick_ms = 100)
|
||||
{
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
|
||||
asyncAction.As(&asyncInfo);
|
||||
AsyncStatus status;
|
||||
// Ugly blocking loop until the RepairAsync call finishes.
|
||||
//FIXME replace with a callback.
|
||||
// https://social.msdn.microsoft.com/Forums/en-US/a5038fb4-b7b7-4504-969d-c102faa389fb/trying-to-block-an-async-operation-and-wait-for-a-particular-time?forum=vclanguage
|
||||
for (;;) {
|
||||
asyncInfo->get_Status(&status);
|
||||
if (status != AsyncStatus::Started)
|
||||
return status;
|
||||
throw_on_cancel();
|
||||
::Sleep(blocking_tick_ms);
|
||||
}
|
||||
}
|
||||
|
||||
static HRESULT winrt_open_file_stream(
|
||||
const std::wstring &path,
|
||||
ABI::Windows::Storage::FileAccessMode mode,
|
||||
ABI::Windows::Storage::Streams::IRandomAccessStream **fileStream,
|
||||
ThrowOnCancelFn throw_on_cancel)
|
||||
{
|
||||
// Get the file factory.
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFileStatics> fileFactory;
|
||||
HRESULT hr = winrt_get_activation_factory(L"Windows.Storage.StorageFile", fileFactory.GetAddressOf());
|
||||
if (FAILED(hr)) return hr;
|
||||
|
||||
// Open the file asynchronously.
|
||||
HSTRING hstr_path;
|
||||
hr = (*s_WindowsCreateString)(path.c_str(), path.size(), &hstr_path);
|
||||
if (FAILED(hr)) return hr;
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::StorageFile*>> fileOpenAsync;
|
||||
hr = fileFactory->GetFileFromPathAsync(hstr_path, fileOpenAsync.GetAddressOf());
|
||||
if (FAILED(hr)) return hr;
|
||||
(*s_WindowsDeleteString)(hstr_path);
|
||||
|
||||
// Wait until the file gets open, get the actual file.
|
||||
AsyncStatus status = winrt_async_await(fileOpenAsync, throw_on_cancel);
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFile> storageFile;
|
||||
if (status == AsyncStatus::Completed) {
|
||||
hr = fileOpenAsync->GetResults(storageFile.GetAddressOf());
|
||||
} else {
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
|
||||
hr = fileOpenAsync.As(&asyncInfo);
|
||||
if (FAILED(hr)) return hr;
|
||||
HRESULT err;
|
||||
hr = asyncInfo->get_ErrorCode(&err);
|
||||
return FAILED(hr) ? hr : err;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> fileStreamAsync;
|
||||
hr = storageFile->OpenAsync(mode, fileStreamAsync.GetAddressOf());
|
||||
if (FAILED(hr)) return hr;
|
||||
|
||||
status = winrt_async_await(fileStreamAsync, throw_on_cancel);
|
||||
if (status == AsyncStatus::Completed) {
|
||||
hr = fileStreamAsync->GetResults(fileStream);
|
||||
} else {
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
|
||||
hr = fileStreamAsync.As(&asyncInfo);
|
||||
if (FAILED(hr)) return hr;
|
||||
HRESULT err;
|
||||
hr = asyncInfo->get_ErrorCode(&err);
|
||||
if (!FAILED(hr))
|
||||
hr = err;
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
bool is_windows10()
|
||||
{
|
||||
HKEY hKey;
|
||||
LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey);
|
||||
if (lRes == ERROR_SUCCESS) {
|
||||
WCHAR szBuffer[512];
|
||||
DWORD dwBufferSize = sizeof(szBuffer);
|
||||
lRes = RegQueryValueExW(hKey, L"ProductName", 0, nullptr, (LPBYTE)szBuffer, &dwBufferSize);
|
||||
if (lRes == ERROR_SUCCESS)
|
||||
return wcsncmp(szBuffer, L"Windows 10", 10) == 0;
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Progress function, to be called regularly to update the progress.
|
||||
typedef std::function<void (const char * /* message */, unsigned /* progress */)> ProgressFn;
|
||||
|
||||
void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path_dst, ProgressFn on_progress, ThrowOnCancelFn throw_on_cancel)
|
||||
{
|
||||
if (! is_windows10())
|
||||
throw Slic3r::RuntimeError("fix_model_by_win10_sdk called on non Windows 10 system");
|
||||
|
||||
if (! winrt_load_runtime_object_library())
|
||||
throw Slic3r::RuntimeError("Failed to initialize the WinRT library.");
|
||||
|
||||
HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED);
|
||||
{
|
||||
on_progress(L("Exporting source model"), 20);
|
||||
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> fileStream;
|
||||
hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel);
|
||||
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3D3MFPackage> printing3d3mfpackage;
|
||||
hr = winrt_activate_instance(L"Windows.Graphics.Printing3D.Printing3D3MFPackage", printing3d3mfpackage.GetAddressOf());
|
||||
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Graphics::Printing3D::Printing3DModel*>> modelAsync;
|
||||
hr = printing3d3mfpackage->LoadModelFromPackageAsync(fileStream.Get(), modelAsync.GetAddressOf());
|
||||
|
||||
AsyncStatus status = winrt_async_await(modelAsync, throw_on_cancel);
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3DModel> model;
|
||||
if (status == AsyncStatus::Completed)
|
||||
hr = modelAsync->GetResults(model.GetAddressOf());
|
||||
else
|
||||
throw Slic3r::RuntimeError("Failed loading the input model.");
|
||||
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::Collections::IVector<ABI::Windows::Graphics::Printing3D::Printing3DMesh*>> meshes;
|
||||
hr = model->get_Meshes(meshes.GetAddressOf());
|
||||
unsigned num_meshes = 0;
|
||||
hr = meshes->get_Size(&num_meshes);
|
||||
|
||||
on_progress(L("Repairing model by the Netfabb service"), 40);
|
||||
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> repairAsync;
|
||||
hr = model->RepairAsync(repairAsync.GetAddressOf());
|
||||
status = winrt_async_await(repairAsync, throw_on_cancel);
|
||||
if (status != AsyncStatus::Completed)
|
||||
throw Slic3r::RuntimeError("Mesh repair failed.");
|
||||
repairAsync->GetResults();
|
||||
|
||||
on_progress(L("Loading repaired model"), 60);
|
||||
|
||||
// Verify the number of meshes returned after the repair action.
|
||||
meshes.Reset();
|
||||
hr = model->get_Meshes(meshes.GetAddressOf());
|
||||
hr = meshes->get_Size(&num_meshes);
|
||||
|
||||
// Save model to this class' Printing3D3MFPackage.
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> saveToPackageAsync;
|
||||
hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf());
|
||||
status = winrt_async_await(saveToPackageAsync, throw_on_cancel);
|
||||
if (status != AsyncStatus::Completed)
|
||||
throw Slic3r::RuntimeError("Saving mesh into the 3MF container failed.");
|
||||
hr = saveToPackageAsync->GetResults();
|
||||
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> generatorStreamAsync;
|
||||
hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf());
|
||||
status = winrt_async_await(generatorStreamAsync, throw_on_cancel);
|
||||
if (status != AsyncStatus::Completed)
|
||||
throw Slic3r::RuntimeError("Saving mesh into the 3MF container failed.");
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> generatorStream;
|
||||
hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf());
|
||||
|
||||
// Go to the beginning of the stream.
|
||||
generatorStream->Seek(0);
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IInputStream> inputStream;
|
||||
hr = generatorStream.As(&inputStream);
|
||||
|
||||
// Get the buffer factory.
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory;
|
||||
hr = winrt_get_activation_factory(L"Windows.Storage.Streams.Buffer", bufferFactory.GetAddressOf());
|
||||
|
||||
// Open the destination file.
|
||||
FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb");
|
||||
try {
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
|
||||
byte *buffer_ptr;
|
||||
bufferFactory->Create(65536 * 2048, buffer.GetAddressOf());
|
||||
{
|
||||
Microsoft::WRL::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
|
||||
buffer.As(&bufferByteAccess);
|
||||
hr = bufferByteAccess->Buffer(&buffer_ptr);
|
||||
}
|
||||
uint32_t length;
|
||||
hr = buffer->get_Length(&length);
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer*, UINT32>> asyncRead;
|
||||
for (;;) {
|
||||
hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf());
|
||||
status = winrt_async_await(asyncRead, throw_on_cancel);
|
||||
if (status != AsyncStatus::Completed)
|
||||
throw Slic3r::RuntimeError("Saving mesh into the 3MF container failed.");
|
||||
hr = buffer->get_Length(&length);
|
||||
if (length == 0)
|
||||
break;
|
||||
fwrite(buffer_ptr, length, 1, fout);
|
||||
}
|
||||
} catch (...) {
|
||||
fclose(fout);
|
||||
throw;
|
||||
}
|
||||
fclose(fout);
|
||||
// Here all the COM objects will be released through the ComPtr destructors.
|
||||
}
|
||||
(*s_RoUninitialize)();
|
||||
}
|
||||
|
||||
class RepairCanceledException : public std::exception {
|
||||
public:
|
||||
const char* what() const throw() { return "Model repair has been canceled"; }
|
||||
};
|
||||
|
||||
// returt FALSE, if fixing was canceled
|
||||
// fix_result is empty, if fixing finished successfully
|
||||
// fix_result containes a message if fixing failed
|
||||
bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxProgressDialog& progress_dialog, const wxString& msg_header, std::string& fix_result)
|
||||
{
|
||||
std::mutex mtx;
|
||||
std::condition_variable condition;
|
||||
struct Progress {
|
||||
std::string message;
|
||||
int percent = 0;
|
||||
bool updated = false;
|
||||
} progress;
|
||||
std::atomic<bool> canceled = false;
|
||||
std::atomic<bool> finished = false;
|
||||
|
||||
std::vector<ModelVolume*> volumes;
|
||||
if (volume_idx == -1)
|
||||
volumes = model_object.volumes;
|
||||
else
|
||||
volumes.emplace_back(model_object.volumes[volume_idx]);
|
||||
|
||||
// Executing the calculation in a background thread, so that the COM context could be created with its own threading model.
|
||||
// (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context).
|
||||
bool success = false;
|
||||
size_t ivolume = 0;
|
||||
auto on_progress = [&mtx, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) {
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
progress.message = msg;
|
||||
progress.percent = (int)floor((float(prcnt) + float(ivolume) * 100.f) / float(volumes.size()));
|
||||
progress.updated = true;
|
||||
condition.notify_all();
|
||||
};
|
||||
auto worker_thread = boost::thread([&model_object, &volumes, &ivolume, on_progress, &success, &canceled, &finished]() {
|
||||
try {
|
||||
std::vector<TriangleMesh> meshes_repaired;
|
||||
meshes_repaired.reserve(volumes.size());
|
||||
for (; ivolume < volumes.size(); ++ ivolume) {
|
||||
on_progress(L("Exporting source model"), 0);
|
||||
boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
|
||||
path_src += ".3mf";
|
||||
Model model;
|
||||
ModelObject *mo = model.add_object();
|
||||
mo->add_volume(*volumes[ivolume]);
|
||||
|
||||
// We are about to save a 3mf, fix it by netfabb and load the fixed 3mf back.
|
||||
// store_3mf currently bakes the volume transformation into the mesh itself.
|
||||
// If we then loaded the repaired 3mf and pushed the mesh into the original ModelVolume
|
||||
// (which remembers the matrix the whole time), the transformation would be used twice.
|
||||
// We will therefore set the volume transform on the dummy ModelVolume to identity.
|
||||
mo->volumes.back()->set_transformation(Geometry::Transformation());
|
||||
|
||||
mo->add_instance();
|
||||
if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false, nullptr, false)) {
|
||||
boost::filesystem::remove(path_src);
|
||||
throw Slic3r::RuntimeError("Export of a temporary 3mf file failed");
|
||||
}
|
||||
model.clear_objects();
|
||||
model.clear_materials();
|
||||
boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
|
||||
path_dst += ".3mf";
|
||||
fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress,
|
||||
[&canceled]() { if (canceled) throw RepairCanceledException(); });
|
||||
boost::filesystem::remove(path_src);
|
||||
// PresetBundle bundle;
|
||||
on_progress(L("Loading repaired model"), 80);
|
||||
DynamicPrintConfig config;
|
||||
ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::EnableSilent };
|
||||
bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), config, config_substitutions, &model, false);
|
||||
boost::filesystem::remove(path_dst);
|
||||
if (! loaded)
|
||||
throw Slic3r::RuntimeError("Import of the repaired 3mf file failed");
|
||||
if (model.objects.size() == 0)
|
||||
throw Slic3r::RuntimeError("Repaired 3MF file does not contain any object");
|
||||
if (model.objects.size() > 1)
|
||||
throw Slic3r::RuntimeError("Repaired 3MF file contains more than one object");
|
||||
if (model.objects.front()->volumes.size() == 0)
|
||||
throw Slic3r::RuntimeError("Repaired 3MF file does not contain any volume");
|
||||
if (model.objects.front()->volumes.size() > 1)
|
||||
throw Slic3r::RuntimeError("Repaired 3MF file contains more than one volume");
|
||||
meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh()));
|
||||
}
|
||||
for (size_t i = 0; i < volumes.size(); ++ i) {
|
||||
volumes[i]->set_mesh(std::move(meshes_repaired[i]));
|
||||
volumes[i]->calculate_convex_hull();
|
||||
volumes[i]->set_new_unique_id();
|
||||
}
|
||||
model_object.invalidate_bounding_box();
|
||||
-- ivolume;
|
||||
on_progress(L("Model repair finished"), 100);
|
||||
success = true;
|
||||
finished = true;
|
||||
} catch (RepairCanceledException & /* ex */) {
|
||||
canceled = true;
|
||||
finished = true;
|
||||
on_progress(L("Model repair canceled"), 100);
|
||||
} catch (std::exception &ex) {
|
||||
success = false;
|
||||
finished = true;
|
||||
on_progress(ex.what(), 100);
|
||||
}
|
||||
});
|
||||
while (! finished) {
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
condition.wait_for(lock, std::chrono::milliseconds(250), [&progress]{ return progress.updated; });
|
||||
// decrease progress.percent value to avoid closing of the progress dialog
|
||||
if (!progress_dialog.Update(progress.percent-1, msg_header + _(progress.message)))
|
||||
canceled = true;
|
||||
else
|
||||
progress_dialog.Fit();
|
||||
progress.updated = false;
|
||||
}
|
||||
|
||||
if (canceled) {
|
||||
// Nothing to show.
|
||||
} else if (success) {
|
||||
fix_result = "";
|
||||
} else {
|
||||
fix_result = progress.message;
|
||||
}
|
||||
worker_thread.join();
|
||||
return !canceled;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* HAS_WIN10SDK */
|
||||
30
src/slic3r/Utils/FixModelByWin10.hpp
Normal file
30
src/slic3r/Utils/FixModelByWin10.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef slic3r_GUI_Utils_FixModelByWin10_hpp_
|
||||
#define slic3r_GUI_Utils_FixModelByWin10_hpp_
|
||||
|
||||
#include <string>
|
||||
|
||||
class wxProgressDialog;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
class ModelObject;
|
||||
class Print;
|
||||
|
||||
#ifdef HAS_WIN10SDK
|
||||
|
||||
extern bool is_windows10();
|
||||
// returt false, if fixing was canceled
|
||||
extern bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxProgressDialog& progress_dlg, const wxString& msg_header, std::string& fix_result);
|
||||
|
||||
#else /* HAS_WIN10SDK */
|
||||
|
||||
inline bool is_windows10() { return false; }
|
||||
// returt false, if fixing was canceled
|
||||
inline bool fix_model_by_win10_sdk_gui(ModelObject&, int, wxProgressDialog&, const wxString&, std::string&) { return false; }
|
||||
|
||||
#endif /* HAS_WIN10SDK */
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GUI_Utils_FixModelByWin10_hpp_ */
|
||||
230
src/slic3r/Utils/FlashAir.cpp
Normal file
230
src/slic3r/Utils/FlashAir.cpp
Normal file
@@ -0,0 +1,230 @@
|
||||
#include "FlashAir.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "Http.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
FlashAir::FlashAir(DynamicPrintConfig *config) :
|
||||
host(config->opt_string("print_host"))
|
||||
{}
|
||||
|
||||
const char* FlashAir::get_name() const { return "FlashAir"; }
|
||||
|
||||
bool FlashAir::test(wxString &msg) const
|
||||
{
|
||||
// Since the request is performed synchronously here,
|
||||
// it is ok to refer to `msg` from within the closure
|
||||
|
||||
const char *name = get_name();
|
||||
|
||||
bool res = false;
|
||||
auto url = make_url("command.cgi", "op", "118");
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get upload enabled at: %2%") % name % url;
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting upload enabled: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
res = false;
|
||||
msg = format_error(body, error, status);
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got upload enabled: %2%") % name % body;
|
||||
|
||||
res = boost::starts_with(body, "1");
|
||||
if (! res) {
|
||||
msg = _(L("Upload not enabled on FlashAir card."));
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
wxString FlashAir::get_test_ok_msg () const
|
||||
{
|
||||
return _(L("Connection to FlashAir works correctly and upload is enabled."));
|
||||
}
|
||||
|
||||
wxString FlashAir::get_test_failed_msg (wxString &msg) const
|
||||
{
|
||||
return GUI::format_wxstr("%s: %s\n%s"
|
||||
, _u8L("Could not connect to FlashAir")
|
||||
, msg
|
||||
, _u8L("Note: FlashAir with firmware 2.00.02 or newer and activated upload function is required."));
|
||||
}
|
||||
|
||||
bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
const char *name = get_name();
|
||||
|
||||
const auto upload_filename = upload_data.upload_path.filename();
|
||||
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||
wxString test_msg;
|
||||
if (! test(test_msg)) {
|
||||
error_fn(std::move(test_msg));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool res = false;
|
||||
|
||||
std::string strDest = upload_parent_path.string();
|
||||
if (strDest.front()!='/') // Needs a leading / else root uploads fail.
|
||||
{
|
||||
strDest.insert(0,"/");
|
||||
}
|
||||
|
||||
auto urlPrepare = make_url("upload.cgi", "WRITEPROTECT=ON&FTIME", timestamp_str());
|
||||
auto urlSetDir = make_url("upload.cgi","UPDIR",strDest);
|
||||
auto urlUpload = make_url("upload.cgi");
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3% / %4%, filename: %5%")
|
||||
% name
|
||||
% upload_data.source_path
|
||||
% urlPrepare
|
||||
% urlUpload
|
||||
% upload_filename.string();
|
||||
|
||||
// set filetime for upload and make card writeprotect to prevent filesystem damage
|
||||
auto httpPrepare = Http::get(std::move(urlPrepare));
|
||||
httpPrepare.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error preparing upload: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
error_fn(format_error(body, error, status));
|
||||
res = false;
|
||||
})
|
||||
.on_complete([&, this](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got prepare result: %2%") % name % body;
|
||||
res = boost::icontains(body, "SUCCESS");
|
||||
if (! res) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
|
||||
error_fn(format_error(body, L("Unknown error occured"), 0));
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
if(! res ) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// start file upload
|
||||
auto httpDir = Http::get(std::move(urlSetDir));
|
||||
httpDir.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error setting upload dir: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
error_fn(format_error(body, error, status));
|
||||
res = false;
|
||||
})
|
||||
.on_complete([&, this](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got dir select result: %2%") % name % body;
|
||||
res = boost::icontains(body, "SUCCESS");
|
||||
if (! res) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
|
||||
error_fn(format_error(body, L("Unknown error occured"), 0));
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
if(! res ) {
|
||||
return res;
|
||||
}
|
||||
|
||||
auto http = Http::post(std::move(urlUpload));
|
||||
http.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
|
||||
res = boost::icontains(body, "SUCCESS");
|
||||
if (! res) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
|
||||
error_fn(format_error(body, L("Unknown error occured"), 0));
|
||||
}
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
error_fn(format_error(body, error, status));
|
||||
res = false;
|
||||
})
|
||||
.on_progress([&](Http::Progress progress, bool &cancel) {
|
||||
prorgess_fn(std::move(progress), cancel);
|
||||
if (cancel) {
|
||||
// Upload was canceled
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Upload canceled") % name;
|
||||
res = false;
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string FlashAir::timestamp_str() const
|
||||
{
|
||||
auto t = std::time(nullptr);
|
||||
auto tm = *std::localtime(&t);
|
||||
|
||||
unsigned long fattime = ((tm.tm_year - 80) << 25) |
|
||||
((tm.tm_mon + 1) << 21) |
|
||||
(tm.tm_mday << 16) |
|
||||
(tm.tm_hour << 11) |
|
||||
(tm.tm_min << 5) |
|
||||
(tm.tm_sec >> 1);
|
||||
|
||||
return (boost::format("%1$#x") % fattime).str();
|
||||
}
|
||||
|
||||
std::string FlashAir::make_url(const std::string &path) const
|
||||
{
|
||||
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
||||
if (host.back() == '/') {
|
||||
return (boost::format("%1%%2%") % host % path).str();
|
||||
} else {
|
||||
return (boost::format("%1%/%2%") % host % path).str();
|
||||
}
|
||||
} else {
|
||||
if (host.back() == '/') {
|
||||
return (boost::format("http://%1%%2%") % host % path).str();
|
||||
} else {
|
||||
return (boost::format("http://%1%/%2%") % host % path).str();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string FlashAir::make_url(const std::string &path, const std::string &arg, const std::string &val) const
|
||||
{
|
||||
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
||||
if (host.back() == '/') {
|
||||
return (boost::format("%1%%2%?%3%=%4%") % host % path % arg % val).str();
|
||||
} else {
|
||||
return (boost::format("%1%/%2%?%3%=%4%") % host % path % arg % val).str();
|
||||
}
|
||||
} else {
|
||||
if (host.back() == '/') {
|
||||
return (boost::format("http://%1%%2%?%3%=%4%") % host % path % arg % val).str();
|
||||
} else {
|
||||
return (boost::format("http://%1%/%2%?%3%=%4%") % host % path % arg % val).str();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
42
src/slic3r/Utils/FlashAir.hpp
Normal file
42
src/slic3r/Utils/FlashAir.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef slic3r_FlashAir_hpp_
|
||||
#define slic3r_FlashAir_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
|
||||
#include "PrintHost.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
|
||||
class FlashAir : public PrintHost
|
||||
{
|
||||
public:
|
||||
FlashAir(DynamicPrintConfig *config);
|
||||
~FlashAir() override = default;
|
||||
|
||||
const char* get_name() const override;
|
||||
|
||||
bool test(wxString &curl_msg) const override;
|
||||
wxString get_test_ok_msg() const override;
|
||||
wxString get_test_failed_msg(wxString &msg) const override;
|
||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||
bool has_auto_discovery() const override { return false; }
|
||||
bool can_test() const override { return true; }
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return {}; }
|
||||
std::string get_host() const override { return host; }
|
||||
|
||||
private:
|
||||
std::string host;
|
||||
|
||||
std::string timestamp_str() const;
|
||||
std::string make_url(const std::string &path) const;
|
||||
std::string make_url(const std::string &path, const std::string &arg, const std::string &val) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
140
src/slic3r/Utils/FontConfigHelp.cpp
Normal file
140
src/slic3r/Utils/FontConfigHelp.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
#include "FontConfigHelp.hpp"
|
||||
|
||||
#ifdef EXIST_FONT_CONFIG_INCLUDE
|
||||
|
||||
#include <wx/filename.h>
|
||||
#include <fontconfig/fontconfig.h>
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
|
||||
// @Vojta suggest to make static variable global
|
||||
// Guard for finalize Font Config
|
||||
// Will be finalized on application exit
|
||||
// It seams that it NOT work
|
||||
static std::optional<Slic3r::ScopeGuard> finalize_guard;
|
||||
// cache for Loading of the default configuration file and building information about the available fonts.
|
||||
static FcConfig *fc = nullptr;
|
||||
|
||||
std::string Slic3r::GUI::get_font_path(const wxFont &font, bool reload_fonts)
|
||||
{
|
||||
if (!finalize_guard.has_value()) {
|
||||
FcInit();
|
||||
fc = FcInitLoadConfigAndFonts();
|
||||
finalize_guard.emplace([]() {
|
||||
// Some internal problem of Font config or other library use FC too(like wxWidget)
|
||||
// fccache.c:795: FcCacheFini: Assertion `fcCacheChains[i] == NULL' failed.
|
||||
//FcFini();
|
||||
FcConfigDestroy(fc);
|
||||
});
|
||||
} else if (reload_fonts) {
|
||||
FcConfigDestroy(fc);
|
||||
fc = FcInitLoadConfigAndFonts();
|
||||
}
|
||||
|
||||
if (fc == nullptr) return "";
|
||||
|
||||
wxString fontDesc = font.GetNativeFontInfoUserDesc();
|
||||
wxString faceName = font.GetFaceName();
|
||||
const wxScopedCharBuffer faceNameBuffer = faceName.ToUTF8();
|
||||
const char * fontFamily = faceNameBuffer;
|
||||
|
||||
// Check font slant
|
||||
int slant = FC_SLANT_ROMAN;
|
||||
if (fontDesc.Find(wxS("Oblique")) != wxNOT_FOUND)
|
||||
slant = FC_SLANT_OBLIQUE;
|
||||
else if (fontDesc.Find(wxS("Italic")) != wxNOT_FOUND)
|
||||
slant = FC_SLANT_ITALIC;
|
||||
|
||||
// Check font weight
|
||||
int weight = FC_WEIGHT_NORMAL;
|
||||
if (fontDesc.Find(wxS("Book")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_BOOK;
|
||||
else if (fontDesc.Find(wxS("Medium")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_MEDIUM;
|
||||
#ifdef FC_WEIGHT_ULTRALIGHT
|
||||
else if (fontDesc.Find(wxS("Ultra-Light")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_ULTRALIGHT;
|
||||
#endif
|
||||
else if (fontDesc.Find(wxS("Light")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_LIGHT;
|
||||
else if (fontDesc.Find(wxS("Semi-Bold")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_DEMIBOLD;
|
||||
#ifdef FC_WEIGHT_ULTRABOLD
|
||||
else if (fontDesc.Find(wxS("Ultra-Bold")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_ULTRABOLD;
|
||||
#endif
|
||||
else if (fontDesc.Find(wxS("Bold")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_BOLD;
|
||||
else if (fontDesc.Find(wxS("Heavy")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_BLACK;
|
||||
|
||||
// Check font width
|
||||
int width = FC_WIDTH_NORMAL;
|
||||
if (fontDesc.Find(wxS("Ultra-Condensed")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_ULTRACONDENSED;
|
||||
else if (fontDesc.Find(wxS("Extra-Condensed")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_EXTRACONDENSED;
|
||||
else if (fontDesc.Find(wxS("Semi-Condensed")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_SEMICONDENSED;
|
||||
else if (fontDesc.Find(wxS("Condensed")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_CONDENSED;
|
||||
else if (fontDesc.Find(wxS("Ultra-Expanded")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_ULTRAEXPANDED;
|
||||
else if (fontDesc.Find(wxS("Extra-Expanded")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_EXTRAEXPANDED;
|
||||
else if (fontDesc.Find(wxS("Semi-Expanded")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_SEMIEXPANDED;
|
||||
else if (fontDesc.Find(wxS("Expanded")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_EXPANDED;
|
||||
|
||||
FcResult res;
|
||||
FcPattern *matchPattern = FcPatternBuild(NULL, FC_FAMILY, FcTypeString,
|
||||
(FcChar8 *) fontFamily, NULL);
|
||||
ScopeGuard sg_mp([matchPattern]() { FcPatternDestroy(matchPattern); });
|
||||
|
||||
FcPatternAddInteger(matchPattern, FC_SLANT, slant);
|
||||
FcPatternAddInteger(matchPattern, FC_WEIGHT, weight);
|
||||
FcPatternAddInteger(matchPattern, FC_WIDTH, width);
|
||||
|
||||
FcConfigSubstitute(NULL, matchPattern, FcMatchPattern);
|
||||
FcDefaultSubstitute(matchPattern);
|
||||
|
||||
FcPattern *resultPattern = FcFontMatch(NULL, matchPattern, &res);
|
||||
if (resultPattern == nullptr) return "";
|
||||
ScopeGuard sg_rp([resultPattern]() { FcPatternDestroy(resultPattern); });
|
||||
|
||||
FcChar8 *fileName;
|
||||
if (FcPatternGetString(resultPattern, FC_FILE, 0, &fileName) !=
|
||||
FcResultMatch)
|
||||
return "";
|
||||
wxString fontFileName = wxString::FromUTF8((char *) fileName);
|
||||
|
||||
if (fontFileName.IsEmpty()) return "";
|
||||
|
||||
// find full file path
|
||||
wxFileName myFileName(fontFileName);
|
||||
if (!myFileName.IsOk()) return "";
|
||||
|
||||
if (myFileName.IsRelative()) {
|
||||
// Check whether the file is relative to the current working directory
|
||||
if (!(myFileName.MakeAbsolute() && myFileName.FileExists())) {
|
||||
return "";
|
||||
// File not found, search in given search paths
|
||||
// wxString foundFileName =
|
||||
// m_searchPaths.FindAbsoluteValidPath(fileName); if
|
||||
// (!foundFileName.IsEmpty()) {
|
||||
// myFileName.Assign(foundFileName);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
if (!myFileName.FileExists() || !myFileName.IsFileReadable()) return "";
|
||||
|
||||
// File exists and is accessible
|
||||
wxString fullFileName = myFileName.GetFullPath();
|
||||
return std::string(fullFileName.c_str());
|
||||
}
|
||||
|
||||
#endif // EXIST_FONT_CONFIG_INCLUDE
|
||||
25
src/slic3r/Utils/FontConfigHelp.hpp
Normal file
25
src/slic3r/Utils/FontConfigHelp.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef slic3r_FontConfigHelp_hpp_
|
||||
#define slic3r_FontConfigHelp_hpp_
|
||||
|
||||
#ifdef __linux__
|
||||
#define EXIST_FONT_CONFIG_INCLUDE
|
||||
#endif
|
||||
|
||||
#ifdef EXIST_FONT_CONFIG_INCLUDE
|
||||
#include <wx/font.h>
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
/// <summary>
|
||||
/// initialize font config
|
||||
/// Convert wx widget font to file path
|
||||
/// inspired by wxpdfdoc -
|
||||
/// https://github.com/utelle/wxpdfdoc/blob/5bdcdb9953327d06dc50ec312685ccd9bc8400e0/src/pdffontmanager.cpp
|
||||
/// </summary>
|
||||
/// <param name="font">Wx descriptor of font</param>
|
||||
/// <param name="reload_fonts">flag to reinitialize font list</param>
|
||||
/// <returns>Font FilePath by FontConfig</returns>
|
||||
std::string get_font_path(const wxFont &font, bool reload_fonts = false);
|
||||
|
||||
} // namespace Slic3r
|
||||
#endif // EXIST_FONT_CONFIG_INCLUDE
|
||||
#endif // slic3r_FontConfigHelp_hpp_
|
||||
108
src/slic3r/Utils/HexFile.cpp
Normal file
108
src/slic3r/Utils/HexFile.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "HexFile.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
|
||||
static HexFile::DeviceKind parse_device_kind(const std::string &str)
|
||||
{
|
||||
if (str == "mk2") { return HexFile::DEV_MK2; }
|
||||
else if (str == "mk3") { return HexFile::DEV_MK3; }
|
||||
else if (str == "mm-control") { return HexFile::DEV_MM_CONTROL; }
|
||||
else if (str == "cw1") { return HexFile::DEV_CW1; }
|
||||
else if (str == "cw1s") { return HexFile::DEV_CW1S; }
|
||||
else { return HexFile::DEV_GENERIC; }
|
||||
}
|
||||
|
||||
static size_t hex_num_sections(fs::ifstream &file)
|
||||
{
|
||||
file.seekg(0);
|
||||
if (! file.good()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *hex_terminator = ":00000001FF\r";
|
||||
size_t res = 0;
|
||||
std::string line;
|
||||
while (getline(file, line, '\n').good()) {
|
||||
// Account for LF vs CRLF
|
||||
if (!line.empty() && line.back() != '\r') {
|
||||
line.push_back('\r');
|
||||
}
|
||||
|
||||
if (line == hex_terminator) {
|
||||
res++;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
HexFile::HexFile(fs::path path) :
|
||||
path(std::move(path))
|
||||
{
|
||||
fs::ifstream file(this->path);
|
||||
if (! file.good()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
std::stringstream header_ini;
|
||||
while (std::getline(file, line, '\n').good()) {
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Account for LF vs CRLF
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
|
||||
if (line.front() == ';') {
|
||||
line.front() = ' ';
|
||||
header_ini << line << std::endl;
|
||||
} else if (line.front() == ':') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pt::ptree ptree;
|
||||
try {
|
||||
pt::read_ini(header_ini, ptree);
|
||||
} catch (std::exception & /* e */) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_device_meta = false;
|
||||
const auto device = ptree.find("device");
|
||||
if (device != ptree.not_found()) {
|
||||
this->device = parse_device_kind(device->second.data());
|
||||
has_device_meta = true;
|
||||
}
|
||||
|
||||
const auto model_id = ptree.find("model_id");
|
||||
if (model_id != ptree.not_found()) {
|
||||
this->model_id = model_id->second.data();
|
||||
}
|
||||
|
||||
if (! has_device_meta) {
|
||||
// No device metadata, look at the number of 'sections'
|
||||
if (hex_num_sections(file) == 2) {
|
||||
// Looks like a pre-metadata l10n firmware for the MK3, assume that's the case
|
||||
this->device = DEV_MK3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
35
src/slic3r/Utils/HexFile.hpp
Normal file
35
src/slic3r/Utils/HexFile.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef slic3r_Hex_hpp_
|
||||
#define slic3r_Hex_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
|
||||
struct HexFile
|
||||
{
|
||||
enum DeviceKind {
|
||||
DEV_GENERIC,
|
||||
DEV_MK2,
|
||||
DEV_MK3,
|
||||
DEV_MM_CONTROL,
|
||||
DEV_CW1,
|
||||
DEV_CW1S,
|
||||
};
|
||||
|
||||
boost::filesystem::path path;
|
||||
DeviceKind device = DEV_GENERIC;
|
||||
std::string model_id;
|
||||
|
||||
HexFile() {}
|
||||
HexFile(boost::filesystem::path path);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
680
src/slic3r/Utils/Http.cpp
Normal file
680
src/slic3r/Utils/Http.cpp
Normal file
@@ -0,0 +1,680 @@
|
||||
#include "Http.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <deque>
|
||||
#include <sstream>
|
||||
#include <exception>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#ifdef OPENSSL_CERT_OVERRIDE
|
||||
#include <openssl/x509.h>
|
||||
#endif
|
||||
|
||||
#include <libslic3r/libslic3r.h>
|
||||
#include <libslic3r/Utils.hpp>
|
||||
#include <slic3r/GUI/I18N.hpp>
|
||||
#include <slic3r/GUI/format.hpp>
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
// Private
|
||||
|
||||
struct CurlGlobalInit
|
||||
{
|
||||
static std::unique_ptr<CurlGlobalInit> instance;
|
||||
std::string message;
|
||||
|
||||
CurlGlobalInit()
|
||||
{
|
||||
#ifdef OPENSSL_CERT_OVERRIDE // defined if SLIC3R_STATIC=ON
|
||||
|
||||
// Look for a set of distro specific directories. Don't change the
|
||||
// order: https://bugzilla.redhat.com/show_bug.cgi?id=1053882
|
||||
static const char * CA_BUNDLES[] = {
|
||||
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6
|
||||
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
|
||||
"/usr/share/ssl/certs/ca-bundle.crt",
|
||||
"/usr/local/share/certs/ca-root-nss.crt", // FreeBSD
|
||||
"/etc/ssl/cert.pem",
|
||||
"/etc/ssl/ca-bundle.pem" // OpenSUSE Tumbleweed
|
||||
};
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
// Env var name for the OpenSSL CA bundle (SSL_CERT_FILE nomally)
|
||||
const char *const SSL_CA_FILE = X509_get_default_cert_file_env();
|
||||
const char * ssl_cafile = ::getenv(SSL_CA_FILE);
|
||||
|
||||
if (!ssl_cafile)
|
||||
ssl_cafile = X509_get_default_cert_file();
|
||||
|
||||
int replace = true;
|
||||
if (!ssl_cafile || !fs::exists(fs::path(ssl_cafile))) {
|
||||
const char * bundle = nullptr;
|
||||
for (const char * b : CA_BUNDLES) {
|
||||
if (fs::exists(fs::path(b))) {
|
||||
::setenv(SSL_CA_FILE, bundle = b, replace);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bundle)
|
||||
message = _u8L("Could not detect system SSL certificate store. "
|
||||
"QIDISlicer will be unable to establish secure "
|
||||
"network connections.");
|
||||
else
|
||||
message = Slic3r::GUI::format(
|
||||
_L("QIDISlicer detected system SSL certificate store in: %1%"),
|
||||
bundle);
|
||||
|
||||
message += "\n" + Slic3r::GUI::format(
|
||||
_L("To specify the system certificate store manually, please "
|
||||
"set the %1% environment variable to the correct CA bundle "
|
||||
"and restart the application."),
|
||||
SSL_CA_FILE);
|
||||
}
|
||||
|
||||
#endif // OPENSSL_CERT_OVERRIDE
|
||||
|
||||
if (CURLcode ec = ::curl_global_init(CURL_GLOBAL_DEFAULT)) {
|
||||
message += _u8L("CURL init has failed. QIDISlicer will be unable to establish "
|
||||
"network connections. See logs for additional details.");
|
||||
|
||||
BOOST_LOG_TRIVIAL(error) << ::curl_easy_strerror(ec);
|
||||
}
|
||||
}
|
||||
|
||||
~CurlGlobalInit() { ::curl_global_cleanup(); }
|
||||
};
|
||||
|
||||
std::unique_ptr<CurlGlobalInit> CurlGlobalInit::instance;
|
||||
|
||||
struct Http::priv
|
||||
{
|
||||
enum {
|
||||
DEFAULT_TIMEOUT_CONNECT = 10,
|
||||
DEFAULT_TIMEOUT_MAX = 0,
|
||||
DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024,
|
||||
};
|
||||
|
||||
::CURL *curl;
|
||||
::curl_httppost *form;
|
||||
::curl_httppost *form_end;
|
||||
::curl_slist *headerlist;
|
||||
// Used for reading the body
|
||||
std::string buffer;
|
||||
// Used for storing file streams added as multipart form parts
|
||||
// Using a deque here because unlike vector it doesn't ivalidate pointers on insertion
|
||||
std::deque<fs::ifstream> form_files;
|
||||
std::string postfields;
|
||||
std::string error_buffer; // Used for CURLOPT_ERRORBUFFER
|
||||
size_t limit;
|
||||
bool cancel;
|
||||
std::unique_ptr<fs::ifstream> putFile;
|
||||
|
||||
std::thread io_thread;
|
||||
Http::CompleteFn completefn;
|
||||
Http::ErrorFn errorfn;
|
||||
Http::ProgressFn progressfn;
|
||||
Http::IPResolveFn ipresolvefn;
|
||||
|
||||
priv(const std::string &url);
|
||||
~priv();
|
||||
|
||||
static bool ca_file_supported(::CURL *curl);
|
||||
static size_t writecb(void *data, size_t size, size_t nmemb, void *userp);
|
||||
static int xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
|
||||
static int xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow);
|
||||
static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp);
|
||||
|
||||
void set_timeout_connect(long timeout);
|
||||
void set_timeout_max(long timeout);
|
||||
void form_add_file(const char *name, const fs::path &path, const char* filename);
|
||||
void set_post_body(const fs::path &path);
|
||||
void set_post_body(const std::string &body);
|
||||
void set_put_body(const fs::path &path);
|
||||
void set_range(const std::string& range);
|
||||
|
||||
std::string curl_error(CURLcode curlcode);
|
||||
std::string body_size_error();
|
||||
void http_perform();
|
||||
};
|
||||
|
||||
Http::priv::priv(const std::string &url)
|
||||
: curl(::curl_easy_init())
|
||||
, form(nullptr)
|
||||
, form_end(nullptr)
|
||||
, headerlist(nullptr)
|
||||
, error_buffer(CURL_ERROR_SIZE + 1, '\0')
|
||||
, limit(0)
|
||||
, cancel(false)
|
||||
{
|
||||
Http::tls_global_init();
|
||||
|
||||
if (curl == nullptr) {
|
||||
throw Slic3r::RuntimeError(std::string("Could not construct Curl object"));
|
||||
}
|
||||
|
||||
set_timeout_connect(DEFAULT_TIMEOUT_CONNECT);
|
||||
set_timeout_max(DEFAULT_TIMEOUT_MAX);
|
||||
::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally
|
||||
::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_APP_NAME "/" SLIC3R_VERSION);
|
||||
::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer.front());
|
||||
}
|
||||
|
||||
Http::priv::~priv()
|
||||
{
|
||||
::curl_easy_cleanup(curl);
|
||||
::curl_formfree(form);
|
||||
::curl_slist_free_all(headerlist);
|
||||
}
|
||||
|
||||
bool Http::priv::ca_file_supported(::CURL *curl)
|
||||
{
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
bool res = false;
|
||||
#else
|
||||
bool res = true;
|
||||
#endif
|
||||
|
||||
if (curl == nullptr) { return res; }
|
||||
|
||||
#if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 48
|
||||
::curl_tlssessioninfo *tls;
|
||||
if (::curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &tls) == CURLE_OK) {
|
||||
if (tls->backend == CURLSSLBACKEND_SCHANNEL || tls->backend == CURLSSLBACKEND_DARWINSSL) {
|
||||
// With Windows and OS X native SSL support, cert files cannot be set
|
||||
// DK: OSX is now not building CURL and links system one, thus we do not know which backend is installed. Still, false will be returned since the ifdef at the begining if this function.
|
||||
res = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
|
||||
{
|
||||
auto self = static_cast<priv*>(userp);
|
||||
const char *cdata = static_cast<char*>(data);
|
||||
const size_t realsize = size * nmemb;
|
||||
const size_t limit = self->limit > 0 ? self->limit : DEFAULT_SIZE_LIMIT;
|
||||
if (self->buffer.size() + realsize > limit) {
|
||||
// This makes curl_easy_perform return CURLE_WRITE_ERROR
|
||||
return 0;
|
||||
}
|
||||
|
||||
self->buffer.append(cdata, realsize);
|
||||
|
||||
return realsize;
|
||||
}
|
||||
|
||||
int Http::priv::xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
|
||||
{
|
||||
auto self = static_cast<priv*>(userp);
|
||||
bool cb_cancel = false;
|
||||
|
||||
if (self->progressfn) {
|
||||
Progress progress(dltotal, dlnow, ultotal, ulnow, self->buffer);
|
||||
self->progressfn(progress, cb_cancel);
|
||||
}
|
||||
|
||||
if (cb_cancel) { self->cancel = true; }
|
||||
|
||||
return self->cancel;
|
||||
}
|
||||
|
||||
int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow)
|
||||
{
|
||||
return xfercb(userp, dltotal, dlnow, ultotal, ulnow);
|
||||
}
|
||||
|
||||
size_t Http::priv::form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp)
|
||||
{
|
||||
auto stream = reinterpret_cast<fs::ifstream*>(userp);
|
||||
|
||||
try {
|
||||
stream->read(buffer, size * nitems);
|
||||
} catch (const std::exception &) {
|
||||
return CURL_READFUNC_ABORT;
|
||||
}
|
||||
|
||||
return stream->gcount();
|
||||
}
|
||||
|
||||
void Http::priv::set_timeout_connect(long timeout)
|
||||
{
|
||||
::curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout);
|
||||
}
|
||||
|
||||
void Http::priv::set_timeout_max(long timeout)
|
||||
{
|
||||
::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
|
||||
}
|
||||
|
||||
void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename)
|
||||
{
|
||||
// We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows
|
||||
// and so we use CURLFORM_STREAM with boost ifstream to read the file.
|
||||
|
||||
if (filename == nullptr) {
|
||||
filename = path.string().c_str();
|
||||
}
|
||||
|
||||
form_files.emplace_back(path, std::ios::in | std::ios::binary);
|
||||
auto &stream = form_files.back();
|
||||
stream.seekg(0, std::ios::end);
|
||||
size_t size = stream.tellg();
|
||||
stream.seekg(0);
|
||||
|
||||
if (filename != nullptr) {
|
||||
::curl_formadd(&form, &form_end,
|
||||
CURLFORM_COPYNAME, name,
|
||||
CURLFORM_FILENAME, filename,
|
||||
CURLFORM_CONTENTTYPE, "application/octet-stream",
|
||||
CURLFORM_STREAM, static_cast<void*>(&stream),
|
||||
CURLFORM_CONTENTSLENGTH, static_cast<long>(size),
|
||||
CURLFORM_END
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//FIXME may throw! Is the caller aware of it?
|
||||
void Http::priv::set_post_body(const fs::path &path)
|
||||
{
|
||||
std::ifstream file(path.string());
|
||||
std::string file_content { std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>() };
|
||||
postfields = std::move(file_content);
|
||||
}
|
||||
|
||||
void Http::priv::set_post_body(const std::string &body)
|
||||
{
|
||||
postfields = body;
|
||||
}
|
||||
|
||||
void Http::priv::set_put_body(const fs::path &path)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
boost::uintmax_t filesize = file_size(path, ec);
|
||||
if (!ec) {
|
||||
putFile = std::make_unique<fs::ifstream>(path);
|
||||
::curl_easy_setopt(curl, CURLOPT_READDATA, (void *) (putFile.get()));
|
||||
::curl_easy_setopt(curl, CURLOPT_INFILESIZE, filesize);
|
||||
}
|
||||
}
|
||||
|
||||
void Http::priv::set_range(const std::string& range)
|
||||
{
|
||||
::curl_easy_setopt(curl, CURLOPT_RANGE, range.c_str());
|
||||
}
|
||||
|
||||
std::string Http::priv::curl_error(CURLcode curlcode)
|
||||
{
|
||||
return (boost::format("%1%:\n%2%\n[Error %3%]")
|
||||
% ::curl_easy_strerror(curlcode)
|
||||
% error_buffer.c_str()
|
||||
% curlcode
|
||||
).str();
|
||||
}
|
||||
|
||||
std::string Http::priv::body_size_error()
|
||||
{
|
||||
return (boost::format("HTTP body data size exceeded limit (%1% bytes)") % limit).str();
|
||||
}
|
||||
|
||||
void Http::priv::http_perform()
|
||||
{
|
||||
::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
::curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
|
||||
::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb);
|
||||
::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(this));
|
||||
::curl_easy_setopt(curl, CURLOPT_READFUNCTION, form_file_read_cb);
|
||||
|
||||
::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
||||
#if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 32
|
||||
::curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xfercb);
|
||||
::curl_easy_setopt(curl, CURLOPT_XFERINFODATA, static_cast<void*>(this));
|
||||
#ifndef _WIN32
|
||||
(void)xfercb_legacy; // prevent unused function warning
|
||||
#endif
|
||||
#else
|
||||
::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, xfercb);
|
||||
::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, static_cast<void*>(this));
|
||||
#endif
|
||||
|
||||
::curl_easy_setopt(curl, CURLOPT_VERBOSE, get_logging_level() >= 5);
|
||||
|
||||
if (headerlist != nullptr) {
|
||||
::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
|
||||
}
|
||||
|
||||
if (form != nullptr) {
|
||||
::curl_easy_setopt(curl, CURLOPT_HTTPPOST, form);
|
||||
}
|
||||
|
||||
if (!postfields.empty()) {
|
||||
::curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields.c_str());
|
||||
::curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, postfields.size());
|
||||
}
|
||||
|
||||
CURLcode res = ::curl_easy_perform(curl);
|
||||
|
||||
putFile.reset();
|
||||
|
||||
if (res != CURLE_OK) {
|
||||
if (res == CURLE_ABORTED_BY_CALLBACK) {
|
||||
if (cancel) {
|
||||
// The abort comes from the request being cancelled programatically
|
||||
Progress dummyprogress(0, 0, 0, 0, std::string());
|
||||
bool cancel = true;
|
||||
if (progressfn) { progressfn(dummyprogress, cancel); }
|
||||
} else {
|
||||
// The abort comes from the CURLOPT_READFUNCTION callback, which means reading file failed
|
||||
if (errorfn) { errorfn(std::move(buffer), "Error reading file for file upload", 0); }
|
||||
}
|
||||
}
|
||||
else if (res == CURLE_WRITE_ERROR) {
|
||||
if (errorfn) { errorfn(std::move(buffer), body_size_error(), 0); }
|
||||
} else {
|
||||
if (errorfn) { errorfn(std::move(buffer), curl_error(res), 0); }
|
||||
};
|
||||
} else {
|
||||
long http_status = 0;
|
||||
::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status);
|
||||
|
||||
if (http_status >= 400) {
|
||||
if (errorfn) { errorfn(std::move(buffer), std::string(), http_status); }
|
||||
} else {
|
||||
if (completefn) { completefn(std::move(buffer), http_status); }
|
||||
if (ipresolvefn) {
|
||||
char* ct;
|
||||
res = curl_easy_getinfo(curl, CURLINFO_PRIMARY_IP, &ct);
|
||||
if ((CURLE_OK == res) && ct) {
|
||||
ipresolvefn(ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Http::Http(const std::string &url) : p(new priv(url)) {}
|
||||
|
||||
|
||||
// Public
|
||||
|
||||
Http::Http(Http &&other) : p(std::move(other.p)) {}
|
||||
|
||||
Http::~Http()
|
||||
{
|
||||
assert(! p || ! p->putFile);
|
||||
if (p && p->io_thread.joinable()) {
|
||||
p->io_thread.detach();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Http& Http::timeout_connect(long timeout)
|
||||
{
|
||||
if (timeout < 1) { timeout = priv::DEFAULT_TIMEOUT_CONNECT; }
|
||||
if (p) { p->set_timeout_connect(timeout); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::timeout_max(long timeout)
|
||||
{
|
||||
if (timeout < 1) { timeout = priv::DEFAULT_TIMEOUT_MAX; }
|
||||
if (p) { p->set_timeout_max(timeout); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::size_limit(size_t sizeLimit)
|
||||
{
|
||||
if (p) { p->limit = sizeLimit; }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::set_range(const std::string& range)
|
||||
{
|
||||
if (p) { p->set_range(range); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::header(std::string name, const std::string &value)
|
||||
{
|
||||
if (!p) { return * this; }
|
||||
|
||||
if (name.size() > 0) {
|
||||
name.append(": ").append(value);
|
||||
} else {
|
||||
name.push_back(':');
|
||||
}
|
||||
p->headerlist = curl_slist_append(p->headerlist, name.c_str());
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::remove_header(std::string name)
|
||||
{
|
||||
if (p) {
|
||||
name.push_back(':');
|
||||
p->headerlist = curl_slist_append(p->headerlist, name.c_str());
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Authorization by HTTP digest, based on RFC2617.
|
||||
Http& Http::auth_digest(const std::string &user, const std::string &password)
|
||||
{
|
||||
curl_easy_setopt(p->curl, CURLOPT_USERNAME, user.c_str());
|
||||
curl_easy_setopt(p->curl, CURLOPT_PASSWORD, password.c_str());
|
||||
curl_easy_setopt(p->curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::auth_basic(const std::string &user, const std::string &password)
|
||||
{
|
||||
curl_easy_setopt(p->curl, CURLOPT_USERNAME, user.c_str());
|
||||
curl_easy_setopt(p->curl, CURLOPT_PASSWORD, password.c_str());
|
||||
curl_easy_setopt(p->curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::ca_file(const std::string &name)
|
||||
{
|
||||
if (p && priv::ca_file_supported(p->curl)) {
|
||||
::curl_easy_setopt(p->curl, CURLOPT_CAINFO, name.c_str());
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::form_add(const std::string &name, const std::string &contents)
|
||||
{
|
||||
if (p) {
|
||||
::curl_formadd(&p->form, &p->form_end,
|
||||
CURLFORM_COPYNAME, name.c_str(),
|
||||
CURLFORM_COPYCONTENTS, contents.c_str(),
|
||||
CURLFORM_END
|
||||
);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::form_add_file(const std::string &name, const fs::path &path)
|
||||
{
|
||||
if (p) { p->form_add_file(name.c_str(), path.c_str(), nullptr); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::form_add_file(const std::string &name, const fs::path &path, const std::string &filename)
|
||||
{
|
||||
if (p) { p->form_add_file(name.c_str(), path.c_str(), filename.c_str()); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
// Tells libcurl to ignore certificate revocation checks in case of missing or offline distribution points for those SSL backends where such behavior is present.
|
||||
// This option is only supported for Schannel (the native Windows SSL library).
|
||||
Http& Http::ssl_revoke_best_effort(bool set)
|
||||
{
|
||||
if(p && set){
|
||||
::curl_easy_setopt(p->curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
#endif // WIN32
|
||||
|
||||
Http& Http::set_post_body(const fs::path &path)
|
||||
{
|
||||
if (p) { p->set_post_body(path);}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::set_post_body(const std::string &body)
|
||||
{
|
||||
if (p) { p->set_post_body(body); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::set_put_body(const fs::path &path)
|
||||
{
|
||||
if (p) { p->set_put_body(path);}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::on_complete(CompleteFn fn)
|
||||
{
|
||||
if (p) { p->completefn = std::move(fn); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::on_error(ErrorFn fn)
|
||||
{
|
||||
if (p) { p->errorfn = std::move(fn); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::on_progress(ProgressFn fn)
|
||||
{
|
||||
if (p) { p->progressfn = std::move(fn); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::on_ip_resolve(IPResolveFn fn)
|
||||
{
|
||||
if (p) { p->ipresolvefn = std::move(fn); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http::Ptr Http::perform()
|
||||
{
|
||||
auto self = std::make_shared<Http>(std::move(*this));
|
||||
|
||||
if (self->p) {
|
||||
auto io_thread = std::thread([self](){
|
||||
self->p->http_perform();
|
||||
});
|
||||
self->p->io_thread = std::move(io_thread);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void Http::perform_sync()
|
||||
{
|
||||
if (p) { p->http_perform(); }
|
||||
}
|
||||
|
||||
void Http::cancel()
|
||||
{
|
||||
if (p) { p->cancel = true; }
|
||||
}
|
||||
|
||||
Http Http::get(std::string url)
|
||||
{
|
||||
return Http{std::move(url)};
|
||||
}
|
||||
|
||||
Http Http::post(std::string url)
|
||||
{
|
||||
Http http{std::move(url)};
|
||||
curl_easy_setopt(http.p->curl, CURLOPT_POST, 1L);
|
||||
return http;
|
||||
}
|
||||
|
||||
Http Http::put(std::string url)
|
||||
{
|
||||
Http http{std::move(url)};
|
||||
curl_easy_setopt(http.p->curl, CURLOPT_UPLOAD, 1L);
|
||||
return http;
|
||||
}
|
||||
|
||||
bool Http::ca_file_supported()
|
||||
{
|
||||
::CURL *curl = ::curl_easy_init();
|
||||
bool res = priv::ca_file_supported(curl);
|
||||
if (curl != nullptr) { ::curl_easy_cleanup(curl); }
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string Http::tls_global_init()
|
||||
{
|
||||
if (!CurlGlobalInit::instance)
|
||||
CurlGlobalInit::instance = std::make_unique<CurlGlobalInit>();
|
||||
|
||||
return CurlGlobalInit::instance->message;
|
||||
}
|
||||
|
||||
std::string Http::tls_system_cert_store()
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
#ifdef OPENSSL_CERT_OVERRIDE
|
||||
ret = ::getenv(X509_get_default_cert_file_env());
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Http::url_encode(const std::string &str)
|
||||
{
|
||||
::CURL *curl = ::curl_easy_init();
|
||||
if (curl == nullptr) {
|
||||
return str;
|
||||
}
|
||||
char *ce = ::curl_easy_escape(curl, str.c_str(), str.length());
|
||||
std::string encoded = std::string(ce);
|
||||
|
||||
::curl_free(ce);
|
||||
::curl_easy_cleanup(curl);
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream &os, const Http::Progress &progress)
|
||||
{
|
||||
os << "Http::Progress("
|
||||
<< "dltotal = " << progress.dltotal
|
||||
<< ", dlnow = " << progress.dlnow
|
||||
<< ", ultotal = " << progress.ultotal
|
||||
<< ", ulnow = " << progress.ulnow
|
||||
<< ")";
|
||||
return os;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
152
src/slic3r/Utils/Http.hpp
Normal file
152
src/slic3r/Utils/Http.hpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#ifndef slic3r_Http_hpp_
|
||||
#define slic3r_Http_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
/// Represetns a Http request
|
||||
class Http : public std::enable_shared_from_this<Http> {
|
||||
private:
|
||||
struct priv;
|
||||
public:
|
||||
struct Progress
|
||||
{
|
||||
size_t dltotal; // Total bytes to download
|
||||
size_t dlnow; // Bytes downloaded so far
|
||||
size_t ultotal; // Total bytes to upload
|
||||
size_t ulnow; // Bytes uploaded so far
|
||||
const std::string& buffer; // reference to buffer containing all data
|
||||
|
||||
Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow, const std::string& buffer) :
|
||||
dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow), buffer(buffer)
|
||||
{}
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<Http> Ptr;
|
||||
typedef std::function<void(std::string /* body */, unsigned /* http_status */)> CompleteFn;
|
||||
|
||||
// A HTTP request may fail at various stages of completeness (URL parsing, DNS lookup, TCP connection, ...).
|
||||
// If the HTTP request could not be made or failed before completion, the `error` arg contains a description
|
||||
// of the error and `http_status` is zero.
|
||||
// If the HTTP request was completed but the response HTTP code is >= 400, `error` is empty and `http_status` contains the response code.
|
||||
// In either case there may or may not be a body.
|
||||
typedef std::function<void(std::string /* body */, std::string /* error */, unsigned /* http_status */)> ErrorFn;
|
||||
|
||||
// See the Progress struct above.
|
||||
// Writing true to the `cancel` reference cancels the request in progress.
|
||||
typedef std::function<void(Progress, bool& /* cancel */)> ProgressFn;
|
||||
|
||||
typedef std::function<void(std::string/* address */)> IPResolveFn;
|
||||
|
||||
Http(Http &&other);
|
||||
|
||||
// Note: strings are expected to be UTF-8-encoded
|
||||
|
||||
// These are the primary constructors that create a HTTP object
|
||||
// for a GET and a POST request respectively.
|
||||
static Http get(std::string url);
|
||||
static Http post(std::string url);
|
||||
static Http put(std::string url);
|
||||
~Http();
|
||||
|
||||
Http(const Http &) = delete;
|
||||
Http& operator=(const Http &) = delete;
|
||||
Http& operator=(Http &&) = delete;
|
||||
|
||||
// Sets a maximum connection timeout in seconds
|
||||
Http& timeout_connect(long timeout);
|
||||
// Sets a maximum total request timeout in seconds
|
||||
Http& timeout_max(long timeout);
|
||||
// Sets a maximum size of the data that can be received.
|
||||
// A value of zero sets the default limit, which is is 5MB.
|
||||
Http& size_limit(size_t sizeLimit);
|
||||
// range of donloaded bytes. example: curl_easy_setopt(curl, CURLOPT_RANGE, "0-199");
|
||||
Http& set_range(const std::string& range);
|
||||
// Sets a HTTP header field.
|
||||
Http& header(std::string name, const std::string &value);
|
||||
// Removes a header field.
|
||||
Http& remove_header(std::string name);
|
||||
// Authorization by HTTP digest, based on RFC2617.
|
||||
Http& auth_digest(const std::string &user, const std::string &password);
|
||||
// Basic HTTP authorization
|
||||
Http& auth_basic(const std::string &user, const std::string &password);
|
||||
// Sets a CA certificate file for usage with HTTPS. This is only supported on some backends,
|
||||
// specifically, this is supported with OpenSSL and NOT supported with Windows and OS X native certificate store.
|
||||
// See also ca_file_supported().
|
||||
Http& ca_file(const std::string &filename);
|
||||
// Add a HTTP multipart form field
|
||||
Http& form_add(const std::string &name, const std::string &contents);
|
||||
// Add a HTTP multipart form file data contents, `name` is the name of the part
|
||||
Http& form_add_file(const std::string &name, const boost::filesystem::path &path);
|
||||
// Same as above except also override the file's filename with a custom one
|
||||
Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename);
|
||||
|
||||
#ifdef WIN32
|
||||
// Tells libcurl to ignore certificate revocation checks in case of missing or offline distribution points for those SSL backends where such behavior is present.
|
||||
// This option is only supported for Schannel (the native Windows SSL library).
|
||||
Http& ssl_revoke_best_effort(bool set);
|
||||
#endif // WIN32
|
||||
|
||||
// Set the file contents as a POST request body.
|
||||
// The data is used verbatim, it is not additionally encoded in any way.
|
||||
// This can be used for hosts which do not support multipart requests.
|
||||
Http& set_post_body(const boost::filesystem::path &path);
|
||||
|
||||
// Set the POST request body.
|
||||
// The data is used verbatim, it is not additionally encoded in any way.
|
||||
// This can be used for hosts which do not support multipart requests.
|
||||
Http& set_post_body(const std::string &body);
|
||||
|
||||
// Set the file contents as a PUT request body.
|
||||
// The data is used verbatim, it is not additionally encoded in any way.
|
||||
// This can be used for hosts which do not support multipart requests.
|
||||
Http& set_put_body(const boost::filesystem::path &path);
|
||||
|
||||
// Callback called on HTTP request complete
|
||||
Http& on_complete(CompleteFn fn);
|
||||
// Callback called on an error occuring at any stage of the requests: Url parsing, DNS lookup,
|
||||
// TCP connection, HTTP transfer, and finally also when the response indicates an error (status >= 400).
|
||||
// Therefore, a response body may or may not be present.
|
||||
Http& on_error(ErrorFn fn);
|
||||
// Callback called on data download/upload prorgess (called fairly frequently).
|
||||
// See the `Progress` structure for description of the data passed.
|
||||
// Writing a true-ish value into the cancel reference parameter cancels the request.
|
||||
Http& on_progress(ProgressFn fn);
|
||||
// Callback called after succesful HTTP request (after on_complete callback)
|
||||
// Called if curl_easy_getinfo resolved just used IP address.
|
||||
Http& on_ip_resolve(IPResolveFn fn);
|
||||
|
||||
// Starts performing the request in a background thread
|
||||
Ptr perform();
|
||||
// Starts performing the request on the current thread
|
||||
void perform_sync();
|
||||
// Cancels a request in progress
|
||||
void cancel();
|
||||
|
||||
// Tells whether current backend supports seting up a CA file using ca_file()
|
||||
static bool ca_file_supported();
|
||||
|
||||
// Return empty string on success or error message on fail.
|
||||
static std::string tls_global_init();
|
||||
static std::string tls_system_cert_store();
|
||||
|
||||
// converts the given string to an url_encoded_string
|
||||
static std::string url_encode(const std::string &str);
|
||||
private:
|
||||
Http(const std::string &url);
|
||||
|
||||
std::unique_ptr<priv> p;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream &, const Http::Progress &);
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
149
src/slic3r/Utils/MKS.cpp
Normal file
149
src/slic3r/Utils/MKS.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
#include "MKS.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "Http.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
MKS::MKS(DynamicPrintConfig* config) :
|
||||
m_host(config->opt_string("print_host")), m_console_port("8080")
|
||||
{}
|
||||
|
||||
const char* MKS::get_name() const { return "MKS"; }
|
||||
|
||||
bool MKS::test(wxString& msg) const
|
||||
{
|
||||
Utils::TCPConsole console(m_host, m_console_port);
|
||||
|
||||
console.enqueue_cmd("M105");
|
||||
bool ret = console.run_queue();
|
||||
|
||||
if (!ret)
|
||||
msg = wxString::FromUTF8(console.error_message().c_str());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
wxString MKS::get_test_ok_msg() const
|
||||
{
|
||||
return _(L("Connection to MKS works correctly."));
|
||||
}
|
||||
|
||||
wxString MKS::get_test_failed_msg(wxString& msg) const
|
||||
{
|
||||
return GUI::format_wxstr("%s: %s", _L("Could not connect to MKS"), msg);
|
||||
}
|
||||
|
||||
bool MKS::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
bool res = true;
|
||||
|
||||
auto upload_cmd = get_upload_url(upload_data.upload_path.string());
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("MKS: Uploading file %1%, filepath: %2%, print: %3%, command: %4%")
|
||||
% upload_data.source_path
|
||||
% upload_data.upload_path
|
||||
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint)
|
||||
% upload_cmd;
|
||||
|
||||
auto http = Http::post(std::move(upload_cmd));
|
||||
http.set_post_body(upload_data.source_path);
|
||||
|
||||
http.on_complete([&](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("MKS: File uploaded: HTTP %1%: %2%") % status % body;
|
||||
|
||||
int err_code = get_err_code_from_body(body);
|
||||
if (err_code != 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("MKS: Request completed but error code was received: %1%") % err_code;
|
||||
error_fn(format_error(body, L("Unknown error occured"), 0));
|
||||
res = false;
|
||||
}
|
||||
else if (upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
|
||||
wxString errormsg;
|
||||
res = start_print(errormsg, upload_data.upload_path.string());
|
||||
if (!res) {
|
||||
error_fn(std::move(errormsg));
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("MKS: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
|
||||
error_fn(format_error(body, error, status));
|
||||
res = false;
|
||||
})
|
||||
.on_progress([&](Http::Progress progress, bool& cancel) {
|
||||
prorgess_fn(std::move(progress), cancel);
|
||||
if (cancel) {
|
||||
// Upload was canceled
|
||||
BOOST_LOG_TRIVIAL(info) << "MKS: Upload canceled";
|
||||
res = false;
|
||||
}
|
||||
}).perform_sync();
|
||||
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string MKS::get_upload_url(const std::string& filename) const
|
||||
{
|
||||
return (boost::format("http://%1%/upload?X-Filename=%2%")
|
||||
% m_host
|
||||
% Http::url_encode(filename)).str();
|
||||
}
|
||||
|
||||
bool MKS::start_print(wxString& msg, const std::string& filename) const
|
||||
{
|
||||
// For some reason printer firmware does not want to respond on gcode commands immediately after file upload.
|
||||
// So we just introduce artificial delay to workaround it.
|
||||
// TODO: Inspect reasons
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
|
||||
|
||||
Utils::TCPConsole console(m_host, m_console_port);
|
||||
|
||||
console.enqueue_cmd(std::string("M23 ") + filename);
|
||||
console.enqueue_cmd("M24");
|
||||
|
||||
bool ret = console.run_queue();
|
||||
|
||||
if (!ret)
|
||||
msg = wxString::FromUTF8(console.error_message().c_str());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int MKS::get_err_code_from_body(const std::string& body) const
|
||||
{
|
||||
pt::ptree root;
|
||||
std::istringstream iss(body); // wrap returned json to istringstream
|
||||
pt::read_json(iss, root);
|
||||
|
||||
return root.get<int>("err", 0);
|
||||
}
|
||||
|
||||
} // Slic3r
|
||||
42
src/slic3r/Utils/MKS.hpp
Normal file
42
src/slic3r/Utils/MKS.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef slic3r_MKS_hpp_
|
||||
#define slic3r_MKS_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
|
||||
#include "PrintHost.hpp"
|
||||
#include "TCPConsole.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
|
||||
class MKS : public PrintHost
|
||||
{
|
||||
public:
|
||||
explicit MKS(DynamicPrintConfig* config);
|
||||
~MKS() override = default;
|
||||
|
||||
const char* get_name() const override;
|
||||
|
||||
bool test(wxString& curl_msg) const override;
|
||||
wxString get_test_ok_msg() const override;
|
||||
wxString get_test_failed_msg(wxString& msg) const override;
|
||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||
bool has_auto_discovery() const override { return false; }
|
||||
bool can_test() const override { return true; }
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||
std::string get_host() const override { return m_host; }
|
||||
|
||||
private:
|
||||
std::string m_host;
|
||||
std::string m_console_port;
|
||||
|
||||
std::string get_upload_url(const std::string& filename) const;
|
||||
bool start_print(wxString& msg, const std::string& filename) const;
|
||||
int get_err_code_from_body(const std::string& body) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
16
src/slic3r/Utils/MacDarkMode.hpp
Normal file
16
src/slic3r/Utils/MacDarkMode.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef slic3r_MacDarkMode_hpp_
|
||||
#define slic3r_MacDarkMode_hpp_
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
#if __APPLE__
|
||||
extern bool mac_dark_mode();
|
||||
extern double mac_max_scaling_factor();
|
||||
#endif
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // MacDarkMode_h
|
||||
39
src/slic3r/Utils/MacDarkMode.mm
Normal file
39
src/slic3r/Utils/MacDarkMode.mm
Normal file
@@ -0,0 +1,39 @@
|
||||
#import "MacDarkMode.hpp"
|
||||
|
||||
#import <algorithm>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AppKit/NSScreen.h>
|
||||
|
||||
@interface MacDarkMode : NSObject {}
|
||||
@end
|
||||
|
||||
@implementation MacDarkMode
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
bool mac_dark_mode()
|
||||
{
|
||||
NSString *style = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
|
||||
return style && [style isEqualToString:@"Dark"];
|
||||
|
||||
}
|
||||
|
||||
double mac_max_scaling_factor()
|
||||
{
|
||||
double scaling = 1.;
|
||||
if ([NSScreen screens] == nil) {
|
||||
scaling = [[NSScreen mainScreen] backingScaleFactor];
|
||||
} else {
|
||||
for (int i = 0; i < [[NSScreen screens] count]; ++ i)
|
||||
scaling = std::max<double>(scaling, [[[NSScreen screens] objectAtIndex:0] backingScaleFactor]);
|
||||
}
|
||||
return scaling;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
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();
|
||||
}
|
||||
}
|
||||
257
src/slic3r/Utils/Mainsail.cpp
Normal file
257
src/slic3r/Utils/Mainsail.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#include "Mainsail.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <exception>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "Http.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
#ifdef WIN32
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
std::string substitute_host(const std::string& orig_addr, std::string sub_addr)
|
||||
{
|
||||
// put ipv6 into [] brackets
|
||||
if (sub_addr.find(':') != std::string::npos && sub_addr.at(0) != '[')
|
||||
sub_addr = "[" + sub_addr + "]";
|
||||
// Using the new CURL API for handling URL. https://everything.curl.dev/libcurl/url
|
||||
// If anything fails, return the input unchanged.
|
||||
std::string out = orig_addr;
|
||||
CURLU* hurl = curl_url();
|
||||
if (hurl) {
|
||||
// Parse the input URL.
|
||||
CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, orig_addr.c_str(), 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
// Replace the address.
|
||||
rc = curl_url_set(hurl, CURLUPART_HOST, sub_addr.c_str(), 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
// Extract a string fromt the CURL URL handle.
|
||||
char* url;
|
||||
rc = curl_url_get(hurl, CURLUPART_URL, &url, 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
out = url;
|
||||
curl_free(url);
|
||||
}
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to extract the URL after substitution";
|
||||
}
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to substitute host " << sub_addr << " in URL " << orig_addr;
|
||||
}
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to parse URL " << orig_addr;
|
||||
curl_url_cleanup(hurl);
|
||||
}
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to allocate curl_url";
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Mainsail::Mainsail(DynamicPrintConfig *config) :
|
||||
m_host(config->opt_string("print_host")),
|
||||
m_apikey(config->opt_string("printhost_apikey")),
|
||||
m_cafile(config->opt_string("printhost_cafile")),
|
||||
m_ssl_revoke_best_effort(config->opt_bool("printhost_ssl_ignore_revoke"))
|
||||
{}
|
||||
|
||||
const char* Mainsail::get_name() const { return "Mainsail"; }
|
||||
|
||||
wxString Mainsail::get_test_ok_msg () const
|
||||
{
|
||||
return _(L("Connection to Mainsail works correctly."));
|
||||
}
|
||||
|
||||
wxString Mainsail::get_test_failed_msg (wxString &msg) const
|
||||
{
|
||||
return GUI::format_wxstr("%s: %s"
|
||||
, _L("Could not connect to Mainsail")
|
||||
, msg);
|
||||
}
|
||||
|
||||
bool Mainsail::test(wxString& msg) const
|
||||
{
|
||||
// GET /server/info
|
||||
|
||||
// Since the request is performed synchronously here,
|
||||
// it is ok to refer to `msg` from within the closure
|
||||
const char* name = get_name();
|
||||
|
||||
bool res = true;
|
||||
auto url = make_url("server/info");
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
set_auth(http);
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
res = false;
|
||||
msg = format_error(body, error, status);
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got server/info: %2%") % name % body;
|
||||
|
||||
try {
|
||||
// All successful HTTP requests will return a json encoded object in the form of :
|
||||
// {result: <response data>}
|
||||
std::stringstream ss(body);
|
||||
pt::ptree ptree;
|
||||
pt::read_json(ss, ptree);
|
||||
if (ptree.front().first != "result") {
|
||||
msg = "Could not parse server response";
|
||||
res = false;
|
||||
return;
|
||||
}
|
||||
if (!ptree.front().second.get_optional<std::string>("moonraker_version")) {
|
||||
msg = "Could not parse server response";
|
||||
res = false;
|
||||
return;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Got version: %2%") % name % ptree.front().second.get_optional<std::string>("moonraker_version");
|
||||
} catch (const std::exception&) {
|
||||
res = false;
|
||||
msg = "Could not parse server response";
|
||||
}
|
||||
})
|
||||
#ifdef _WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
.on_ip_resolve([&](std::string address) {
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
// Remember resolved address to be reused at successive REST API call.
|
||||
msg = GUI::from_u8(address);
|
||||
})
|
||||
#endif // _WIN32
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool Mainsail::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
// POST /server/files/upload
|
||||
|
||||
const char* name = get_name();
|
||||
const auto upload_filename = upload_data.upload_path.filename();
|
||||
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||
|
||||
// If test fails, test_msg_or_host_ip contains the error message.
|
||||
wxString test_msg_or_host_ip;
|
||||
if (!test(test_msg_or_host_ip)) {
|
||||
error_fn(std::move(test_msg_or_host_ip));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url;
|
||||
bool res = true;
|
||||
|
||||
#ifdef WIN32
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve"))
|
||||
#endif // _WIN32
|
||||
{
|
||||
// If https is entered we assume signed ceritificate is being used
|
||||
// IP resolving will not happen - it could resolve into address not being specified in cert
|
||||
url = make_url("server/files/upload");
|
||||
}
|
||||
#ifdef WIN32
|
||||
else {
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
// Curl uses easy_getinfo to get ip address of last successful transaction.
|
||||
// If it got the address use it instead of the stored in "host" variable.
|
||||
// This new address returns in "test_msg_or_host_ip" variable.
|
||||
// Solves troubles of uploades failing with name address.
|
||||
// in original address (m_host) replace host for resolved ip
|
||||
info_fn(L"resolve", test_msg_or_host_ip);
|
||||
url = substitute_host(make_url("server/files/upload"), GUI::into_u8(test_msg_or_host_ip));
|
||||
BOOST_LOG_TRIVIAL(info) << "Upload address after ip resolve: " << url;
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%")
|
||||
% name
|
||||
% upload_data.source_path
|
||||
% url
|
||||
% upload_filename.string()
|
||||
% upload_parent_path.string()
|
||||
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
|
||||
/*
|
||||
The file must be uploaded in the request's body multipart/form-data (ie: <input type="file">). The following arguments may also be added to the form-data:
|
||||
root: The root location in which to upload the file.Currently this may be gcodes or config.If not specified the default is gcodes.
|
||||
path : This argument may contain a path(relative to the root) indicating a subdirectory to which the file is written.If a path is present the server will attempt to create any subdirectories that do not exist.
|
||||
checksum : A SHA256 hex digest calculated by the client for the uploaded file.If this argument is supplied the server will compare it to its own checksum calculation after the upload has completed.A checksum mismatch will result in a 422 error.
|
||||
Arguments available only for the gcodes root :
|
||||
print: If set to "true", Klippy will attempt to start the print after uploading.Note that this value should be a string type, not boolean.This provides compatibility with OctoPrint's upload API.
|
||||
*/
|
||||
auto http = Http::post(std::move(url));
|
||||
set_auth(http);
|
||||
|
||||
http.form_add("root", "gcodes");
|
||||
if (!upload_parent_path.empty())
|
||||
http.form_add("path", upload_parent_path.string());
|
||||
if (upload_data.post_action == PrintHostPostUploadAction::StartPrint)
|
||||
http.form_add("print", "true");
|
||||
|
||||
http.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
error_fn(format_error(body, error, status));
|
||||
res = false;
|
||||
})
|
||||
.on_progress([&](Http::Progress progress, bool& cancel) {
|
||||
prorgess_fn(std::move(progress), cancel);
|
||||
if (cancel) {
|
||||
// Upload was canceled
|
||||
BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled";
|
||||
res = false;
|
||||
}
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
#endif
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void Mainsail::set_auth(Http &http) const
|
||||
{
|
||||
if (!m_apikey.empty())
|
||||
http.header("X-Api-Key", m_apikey);
|
||||
if (!m_cafile.empty())
|
||||
http.ca_file(m_cafile);
|
||||
}
|
||||
|
||||
std::string Mainsail::make_url(const std::string &path) const
|
||||
{
|
||||
if (m_host.find("http://") == 0 || m_host.find("https://") == 0) {
|
||||
if (m_host.back() == '/') {
|
||||
return (boost::format("%1%%2%") % m_host % path).str();
|
||||
} else {
|
||||
return (boost::format("%1%/%2%") % m_host % path).str();
|
||||
}
|
||||
} else {
|
||||
return (boost::format("http://%1%/%2%") % m_host % path).str();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
64
src/slic3r/Utils/Mainsail.hpp
Normal file
64
src/slic3r/Utils/Mainsail.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#ifndef slic3r_Mainsail_hpp_
|
||||
#define slic3r_Mainsail_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
|
||||
#include "PrintHost.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
|
||||
// https://moonraker.readthedocs.io/en/latest/web_api
|
||||
class Mainsail : public PrintHost
|
||||
{
|
||||
public:
|
||||
Mainsail(DynamicPrintConfig *config);
|
||||
~Mainsail() override = default;
|
||||
|
||||
const char* get_name() const override;
|
||||
|
||||
virtual bool test(wxString &curl_msg) const override;
|
||||
wxString get_test_ok_msg () const override;
|
||||
wxString get_test_failed_msg (wxString &msg) const override;
|
||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||
bool has_auto_discovery() const override { return true; }
|
||||
bool can_test() const override { return true; }
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||
std::string get_host() const override { return m_host; }
|
||||
const std::string& get_apikey() const { return m_apikey; }
|
||||
const std::string& get_cafile() const { return m_cafile; }
|
||||
|
||||
protected:
|
||||
/*
|
||||
#ifdef WIN32
|
||||
virtual bool upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const;
|
||||
#endif
|
||||
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
||||
virtual bool upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const;
|
||||
*/
|
||||
std::string m_host;
|
||||
std::string m_apikey;
|
||||
std::string m_cafile;
|
||||
bool m_ssl_revoke_best_effort;
|
||||
|
||||
virtual void set_auth(Http &http) const;
|
||||
std::string make_url(const std::string &path) const;
|
||||
|
||||
private:
|
||||
/*
|
||||
#ifdef WIN32
|
||||
bool test_with_resolved_ip(wxString& curl_msg) const;
|
||||
#endif
|
||||
*/
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
1172
src/slic3r/Utils/OctoPrint.cpp
Normal file
1172
src/slic3r/Utils/OctoPrint.cpp
Normal file
File diff suppressed because it is too large
Load Diff
138
src/slic3r/Utils/OctoPrint.hpp
Normal file
138
src/slic3r/Utils/OctoPrint.hpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#ifndef slic3r_OctoPrint_hpp_
|
||||
#define slic3r_OctoPrint_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
|
||||
#include "PrintHost.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
|
||||
class OctoPrint : public PrintHost
|
||||
{
|
||||
public:
|
||||
OctoPrint(DynamicPrintConfig *config);
|
||||
~OctoPrint() override = default;
|
||||
|
||||
const char* get_name() const override;
|
||||
|
||||
virtual bool test(wxString &curl_msg) const override;
|
||||
wxString get_test_ok_msg () const override;
|
||||
wxString get_test_failed_msg (wxString &msg) const override;
|
||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||
bool has_auto_discovery() const override { return true; }
|
||||
bool can_test() const override { return true; }
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||
std::string get_host() const override { return m_host; }
|
||||
const std::string& get_apikey() const { return m_apikey; }
|
||||
const std::string& get_cafile() const { return m_cafile; }
|
||||
|
||||
protected:
|
||||
#ifdef WIN32
|
||||
virtual bool upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const;
|
||||
#endif
|
||||
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
||||
virtual bool upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const;
|
||||
|
||||
std::string m_host;
|
||||
std::string m_apikey;
|
||||
std::string m_cafile;
|
||||
bool m_ssl_revoke_best_effort;
|
||||
|
||||
virtual void set_auth(Http &http) const;
|
||||
std::string make_url(const std::string &path) const;
|
||||
|
||||
private:
|
||||
#ifdef WIN32
|
||||
bool test_with_resolved_ip(wxString& curl_msg) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
class QIDILink : public OctoPrint
|
||||
{
|
||||
public:
|
||||
QIDILink(DynamicPrintConfig* config) : QIDILink(config, false) {}
|
||||
QIDILink(DynamicPrintConfig* config, bool show_after_message);
|
||||
~QIDILink() override = default;
|
||||
|
||||
const char* get_name() const override;
|
||||
|
||||
wxString get_test_ok_msg() const override;
|
||||
wxString get_test_failed_msg(wxString& msg) const override;
|
||||
virtual PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||
|
||||
// gets possible storage to be uploaded to. This allows different printer to have different storage. F.e. local vs sdcard vs usb.
|
||||
bool get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const override;
|
||||
protected:
|
||||
bool test(wxString& curl_msg) const override;
|
||||
bool validate_version_text(const boost::optional<std::string>& version_text) const override;
|
||||
bool upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||
|
||||
void set_auth(Http& http) const override;
|
||||
virtual void set_http_post_header_args(Http& http, PrintHostPostUploadAction post_action) const;
|
||||
#ifdef WIN32
|
||||
bool upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const override;
|
||||
#endif
|
||||
|
||||
// Host authorization type.
|
||||
AuthorizationType m_authorization_type;
|
||||
// username and password for HTTP Digest Authentization (RFC RFC2617)
|
||||
std::string m_username;
|
||||
std::string m_password;
|
||||
|
||||
private:
|
||||
bool test_with_method_check(wxString& curl_msg, bool& use_put) const;
|
||||
bool put_inner(PrintHostUpload upload_data, std::string url, const std::string& name, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const;
|
||||
bool post_inner(PrintHostUpload upload_data, std::string url, const std::string& name, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const;
|
||||
#ifdef WIN32
|
||||
bool test_with_resolved_ip_and_method_check(wxString& curl_msg, bool& use_put) const;
|
||||
#endif
|
||||
|
||||
bool m_show_after_message;
|
||||
|
||||
#if 0
|
||||
bool version_check(const boost::optional<std::string>& version_text) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
class QIDIConnect : public QIDILink
|
||||
{
|
||||
public:
|
||||
QIDIConnect(DynamicPrintConfig* config);
|
||||
~QIDIConnect() override = default;
|
||||
wxString get_test_ok_msg() const override;
|
||||
wxString get_test_failed_msg(wxString& msg) const override;
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint | PrintHostPostUploadAction::QueuePrint; }
|
||||
const char* get_name() const override { return "QIDIConnect"; }
|
||||
bool get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const override { return false; }
|
||||
protected:
|
||||
void set_http_post_header_args(Http& http, PrintHostPostUploadAction post_action) const override;
|
||||
};
|
||||
|
||||
class SL1Host : public QIDILink
|
||||
{
|
||||
public:
|
||||
SL1Host(DynamicPrintConfig* config);
|
||||
~SL1Host() override = default;
|
||||
|
||||
const char* get_name() const override;
|
||||
|
||||
wxString get_test_ok_msg() const override;
|
||||
wxString get_test_failed_msg(wxString& msg) const override;
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return {}; }
|
||||
|
||||
protected:
|
||||
bool validate_version_text(const boost::optional<std::string>& version_text) const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
1454
src/slic3r/Utils/PresetUpdater.cpp
Normal file
1454
src/slic3r/Utils/PresetUpdater.cpp
Normal file
File diff suppressed because it is too large
Load Diff
74
src/slic3r/Utils/PresetUpdater.hpp
Normal file
74
src/slic3r/Utils/PresetUpdater.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#ifndef slic3r_PresetUpdate_hpp_
|
||||
#define slic3r_PresetUpdate_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <wx/event.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
class AppConfig;
|
||||
class PresetBundle;
|
||||
class Semver;
|
||||
|
||||
static constexpr const int SLIC3R_VERSION_BODY_MAX = 256;
|
||||
|
||||
class PresetUpdater
|
||||
{
|
||||
public:
|
||||
PresetUpdater();
|
||||
PresetUpdater(PresetUpdater &&) = delete;
|
||||
PresetUpdater(const PresetUpdater &) = delete;
|
||||
PresetUpdater &operator=(PresetUpdater &&) = delete;
|
||||
PresetUpdater &operator=(const PresetUpdater &) = delete;
|
||||
~PresetUpdater();
|
||||
|
||||
// If either version check or config updating is enabled, get the appropriate data in the background and cache it.
|
||||
void sync(const PresetBundle *preset_bundle);
|
||||
void cancel_sync();
|
||||
|
||||
// If version check is enabled, check if chaced online slic3r version is newer, notify if so.
|
||||
void slic3r_update_notify();
|
||||
|
||||
enum UpdateResult {
|
||||
R_NOOP,
|
||||
R_INCOMPAT_EXIT,
|
||||
R_INCOMPAT_CONFIGURED,
|
||||
R_UPDATE_INSTALLED,
|
||||
R_UPDATE_REJECT,
|
||||
R_UPDATE_NOTIFICATION,
|
||||
R_ALL_CANCELED
|
||||
};
|
||||
|
||||
enum class UpdateParams {
|
||||
SHOW_TEXT_BOX, // force modal textbox
|
||||
SHOW_NOTIFICATION, // only shows notification
|
||||
FORCED_BEFORE_WIZARD // indicates that check of updated is forced before ConfigWizard opening
|
||||
};
|
||||
|
||||
// If updating is enabled, check if updates are available in cache, if so, ask about installation.
|
||||
// A false return value implies Slic3r should exit due to incompatibility of configuration.
|
||||
// Providing old slic3r version upgrade profiles on upgrade of an application even in case
|
||||
// that the config index installed from the Internet is equal to the index contained in the installation package.
|
||||
UpdateResult config_update(const Semver &old_slic3r_version, UpdateParams params) const;
|
||||
|
||||
void update_index_db();
|
||||
|
||||
// "Update" a list of bundles from resources or cache/vendor (behaves like an online update).
|
||||
bool install_bundles_rsrc_or_cache_vendor(std::vector<std::string> bundles, bool snapshot = true) const;
|
||||
|
||||
void on_update_notification_confirm();
|
||||
|
||||
bool version_check_enabled() const;
|
||||
|
||||
private:
|
||||
struct priv;
|
||||
std::unique_ptr<priv> p;
|
||||
};
|
||||
|
||||
//wxDECLARE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
|
||||
//wxDECLARE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
|
||||
}
|
||||
#endif
|
||||
326
src/slic3r/Utils/PrintHost.cpp
Normal file
326
src/slic3r/Utils/PrintHost.cpp
Normal file
@@ -0,0 +1,326 @@
|
||||
#include "PrintHost.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <exception>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <wx/string.h>
|
||||
#include <wx/app.h>
|
||||
#include <wx/arrstr.h>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/Channel.hpp"
|
||||
#include "OctoPrint.hpp"
|
||||
#include "Duet.hpp"
|
||||
#include "FlashAir.hpp"
|
||||
#include "AstroBox.hpp"
|
||||
#include "Repetier.hpp"
|
||||
#include "MKS.hpp"
|
||||
#include "Mainsail.hpp"
|
||||
#include "../GUI/PrintHostDialogs.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
using boost::optional;
|
||||
using Slic3r::GUI::PrintHostQueueDialog;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
PrintHost::~PrintHost() {}
|
||||
|
||||
PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
|
||||
{
|
||||
PrinterTechnology tech = ptFFF;
|
||||
|
||||
{
|
||||
const auto opt = config->option<ConfigOptionEnum<PrinterTechnology>>("printer_technology");
|
||||
if (opt != nullptr) {
|
||||
tech = opt->value;
|
||||
}
|
||||
}
|
||||
|
||||
if (tech == ptFFF) {
|
||||
const auto opt = config->option<ConfigOptionEnum<PrintHostType>>("host_type");
|
||||
const auto host_type = opt != nullptr ? opt->value : htOctoPrint;
|
||||
|
||||
switch (host_type) {
|
||||
case htOctoPrint: return new OctoPrint(config);
|
||||
case htDuet: return new Duet(config);
|
||||
case htFlashAir: return new FlashAir(config);
|
||||
case htAstroBox: return new AstroBox(config);
|
||||
case htRepetier: return new Repetier(config);
|
||||
case htQIDILink: return new QIDILink(config);
|
||||
case htQIDIConnect: return new QIDIConnect(config);
|
||||
case htMKS: return new MKS(config);
|
||||
case htMainSail: return new Mainsail(config);
|
||||
default: return nullptr;
|
||||
}
|
||||
} else {
|
||||
return new SL1Host(config);
|
||||
}
|
||||
}
|
||||
|
||||
wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const
|
||||
{
|
||||
if (status != 0) {
|
||||
auto wxbody = wxString::FromUTF8(body.data());
|
||||
return wxString::Format("HTTP %u: %s", status, wxbody);
|
||||
} else {
|
||||
return wxString::FromUTF8(error.data());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct PrintHostJobQueue::priv
|
||||
{
|
||||
// XXX: comment on how bg thread works
|
||||
|
||||
PrintHostJobQueue *q;
|
||||
|
||||
Channel<PrintHostJob> channel_jobs;
|
||||
Channel<size_t> channel_cancels;
|
||||
size_t job_id = 0;
|
||||
int prev_progress = -1;
|
||||
fs::path source_to_remove;
|
||||
|
||||
std::thread bg_thread;
|
||||
bool bg_exit = false;
|
||||
|
||||
PrintHostQueueDialog *queue_dialog;
|
||||
|
||||
priv(PrintHostJobQueue *q) : q(q) {}
|
||||
|
||||
void emit_progress(int progress);
|
||||
void emit_error(wxString error);
|
||||
void emit_cancel(size_t id);
|
||||
void emit_info(wxString tag, wxString status);
|
||||
void start_bg_thread();
|
||||
void stop_bg_thread();
|
||||
void bg_thread_main();
|
||||
void progress_fn(Http::Progress progress, bool &cancel);
|
||||
void error_fn(wxString error);
|
||||
void info_fn(wxString tag, wxString status);
|
||||
void remove_source(const fs::path &path);
|
||||
void remove_source();
|
||||
void perform_job(PrintHostJob the_job);
|
||||
};
|
||||
|
||||
PrintHostJobQueue::PrintHostJobQueue(PrintHostQueueDialog *queue_dialog)
|
||||
: p(new priv(this))
|
||||
{
|
||||
p->queue_dialog = queue_dialog;
|
||||
}
|
||||
|
||||
PrintHostJobQueue::~PrintHostJobQueue()
|
||||
{
|
||||
if (p) { p->stop_bg_thread(); }
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::emit_progress(int progress)
|
||||
{
|
||||
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_PROGRESS, queue_dialog->GetId(), job_id, progress);
|
||||
wxQueueEvent(queue_dialog, evt);
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::emit_error(wxString error)
|
||||
{
|
||||
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_ERROR, queue_dialog->GetId(), job_id, std::move(error));
|
||||
wxQueueEvent(queue_dialog, evt);
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::emit_info(wxString tag, wxString status)
|
||||
{
|
||||
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_INFO, queue_dialog->GetId(), job_id, std::move(tag), std::move(status));
|
||||
wxQueueEvent(queue_dialog, evt);
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::emit_cancel(size_t id)
|
||||
{
|
||||
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, queue_dialog->GetId(), id);
|
||||
wxQueueEvent(queue_dialog, evt);
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::start_bg_thread()
|
||||
{
|
||||
if (bg_thread.joinable()) { return; }
|
||||
|
||||
std::shared_ptr<priv> p2 = q->p;
|
||||
bg_thread = std::thread([p2]() {
|
||||
p2->bg_thread_main();
|
||||
});
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::stop_bg_thread()
|
||||
{
|
||||
if (bg_thread.joinable()) {
|
||||
bg_exit = true;
|
||||
channel_jobs.push(PrintHostJob()); // Push an empty job to wake up bg_thread in case it's sleeping
|
||||
bg_thread.detach(); // Let the background thread go, it should exit on its own
|
||||
}
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::bg_thread_main()
|
||||
{
|
||||
// bg thread entry point
|
||||
|
||||
try {
|
||||
// Pick up jobs from the job channel:
|
||||
while (! bg_exit) {
|
||||
auto job = channel_jobs.pop(); // Sleeps in a cond var if there are no jobs
|
||||
if (job.empty()) {
|
||||
// This happens when the thread is being stopped
|
||||
break;
|
||||
}
|
||||
|
||||
source_to_remove = job.upload_data.source_path;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("PrintHostJobQueue/bg_thread: Received job: [%1%]: `%2%` -> `%3%`, cancelled: %4%")
|
||||
% job_id
|
||||
% job.upload_data.upload_path
|
||||
% job.printhost->get_host()
|
||||
% job.cancelled;
|
||||
|
||||
if (! job.cancelled) {
|
||||
perform_job(std::move(job));
|
||||
}
|
||||
|
||||
remove_source();
|
||||
job_id++;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
emit_error(e.what());
|
||||
}
|
||||
|
||||
// Cleanup leftover files, if any
|
||||
remove_source();
|
||||
auto jobs = channel_jobs.lock_rw();
|
||||
for (const PrintHostJob &job : *jobs) {
|
||||
remove_source(job.upload_data.source_path);
|
||||
}
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::progress_fn(Http::Progress progress, bool &cancel)
|
||||
{
|
||||
if (cancel) {
|
||||
// When cancel is true from the start, Http indicates request has been cancelled
|
||||
emit_cancel(job_id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bg_exit) {
|
||||
cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel_cancels.size_hint() > 0) {
|
||||
// Lock both queues
|
||||
auto cancels = channel_cancels.lock_rw();
|
||||
auto jobs = channel_jobs.lock_rw();
|
||||
|
||||
for (size_t cancel_id : *cancels) {
|
||||
if (cancel_id == job_id) {
|
||||
cancel = true;
|
||||
} else if (cancel_id > job_id) {
|
||||
const size_t idx = cancel_id - job_id - 1;
|
||||
if (idx < jobs->size()) {
|
||||
jobs->at(idx).cancelled = true;
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("PrintHostJobQueue: Job id %1% cancelled") % cancel_id;
|
||||
emit_cancel(cancel_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancels->clear();
|
||||
}
|
||||
|
||||
if (! cancel) {
|
||||
int gui_progress = progress.ultotal > 0 ? 100*progress.ulnow / progress.ultotal : 0;
|
||||
if (gui_progress != prev_progress) {
|
||||
emit_progress(gui_progress);
|
||||
prev_progress = gui_progress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::error_fn(wxString error)
|
||||
{
|
||||
// check if transfer was not canceled before error occured - than do not show the error
|
||||
bool do_emit_err = true;
|
||||
if (channel_cancels.size_hint() > 0) {
|
||||
// Lock both queues
|
||||
auto cancels = channel_cancels.lock_rw();
|
||||
auto jobs = channel_jobs.lock_rw();
|
||||
|
||||
for (size_t cancel_id : *cancels) {
|
||||
if (cancel_id == job_id) {
|
||||
do_emit_err = false;
|
||||
emit_cancel(job_id);
|
||||
}
|
||||
else if (cancel_id > job_id) {
|
||||
const size_t idx = cancel_id - job_id - 1;
|
||||
if (idx < jobs->size()) {
|
||||
jobs->at(idx).cancelled = true;
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("PrintHostJobQueue: Job id %1% cancelled") % cancel_id;
|
||||
emit_cancel(cancel_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
cancels->clear();
|
||||
}
|
||||
if (do_emit_err)
|
||||
emit_error(std::move(error));
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::info_fn(wxString tag, wxString status)
|
||||
{
|
||||
emit_info(tag, status);
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::remove_source(const fs::path &path)
|
||||
{
|
||||
if (! path.empty()) {
|
||||
boost::system::error_code ec;
|
||||
fs::remove(path, ec);
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("PrintHostJobQueue: Error removing file `%1%`: %2%") % path % ec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::remove_source()
|
||||
{
|
||||
remove_source(source_to_remove);
|
||||
source_to_remove.clear();
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
|
||||
{
|
||||
emit_progress(0); // Indicate the upload is starting
|
||||
|
||||
bool success = the_job.printhost->upload(std::move(the_job.upload_data),
|
||||
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
|
||||
[this](wxString error) { this->error_fn(std::move(error)); },
|
||||
[this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
|
||||
);
|
||||
|
||||
if (success) {
|
||||
emit_progress(100);
|
||||
}
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::enqueue(PrintHostJob job)
|
||||
{
|
||||
p->start_bg_thread();
|
||||
p->queue_dialog->append_job(job);
|
||||
p->channel_jobs.push(std::move(job));
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::cancel(size_t id)
|
||||
{
|
||||
p->channel_cancels.push(id);
|
||||
}
|
||||
|
||||
}
|
||||
135
src/slic3r/Utils/PrintHost.hpp
Normal file
135
src/slic3r/Utils/PrintHost.hpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#ifndef slic3r_PrintHost_hpp_
|
||||
#define slic3r_PrintHost_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
#include <libslic3r/enum_bitmask.hpp>
|
||||
#include "Http.hpp"
|
||||
|
||||
class wxArrayString;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
|
||||
enum class PrintHostPostUploadAction {
|
||||
None,
|
||||
StartPrint,
|
||||
StartSimulation,
|
||||
QueuePrint
|
||||
};
|
||||
using PrintHostPostUploadActions = enum_bitmask<PrintHostPostUploadAction>;
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(PrintHostPostUploadAction);
|
||||
|
||||
struct PrintHostUpload
|
||||
{
|
||||
boost::filesystem::path source_path;
|
||||
boost::filesystem::path upload_path;
|
||||
|
||||
std::string group;
|
||||
std::string storage;
|
||||
|
||||
PrintHostPostUploadAction post_action { PrintHostPostUploadAction::None };
|
||||
};
|
||||
|
||||
class PrintHost
|
||||
{
|
||||
public:
|
||||
virtual ~PrintHost();
|
||||
|
||||
typedef Http::ProgressFn ProgressFn;
|
||||
typedef std::function<void(wxString /* error */)> ErrorFn;
|
||||
typedef std::function<void(wxString /* tag */, wxString /* status */)> InfoFn;
|
||||
|
||||
virtual const char* get_name() const = 0;
|
||||
|
||||
virtual bool test(wxString &curl_msg) const = 0;
|
||||
virtual wxString get_test_ok_msg () const = 0;
|
||||
virtual wxString get_test_failed_msg (wxString &msg) const = 0;
|
||||
virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const = 0;
|
||||
virtual bool has_auto_discovery() const = 0;
|
||||
virtual bool can_test() const = 0;
|
||||
virtual PrintHostPostUploadActions get_post_upload_actions() const = 0;
|
||||
// A print host usually does not support multiple printers, with the exception of Repetier server.
|
||||
virtual bool supports_multiple_printers() const { return false; }
|
||||
virtual std::string get_host() const = 0;
|
||||
|
||||
// Support for Repetier server multiple groups & printers. Not supported by other print hosts.
|
||||
// Returns false if not supported. May throw HostNetworkError.
|
||||
virtual bool get_groups(wxArrayString & /* groups */) const { return false; }
|
||||
virtual bool get_printers(wxArrayString & /* printers */) const { return false; }
|
||||
// Support for QIDILink uploading to different storage. Not supported by other print hosts.
|
||||
// Returns false if not supported or fail.
|
||||
virtual bool get_storage(wxArrayString& /*storage_path*/, wxArrayString& /*storage_name*/) const { return false; }
|
||||
|
||||
static PrintHost* get_print_host(DynamicPrintConfig *config);
|
||||
|
||||
protected:
|
||||
virtual wxString format_error(const std::string &body, const std::string &error, unsigned status) const;
|
||||
};
|
||||
|
||||
|
||||
struct PrintHostJob
|
||||
{
|
||||
PrintHostUpload upload_data;
|
||||
std::unique_ptr<PrintHost> printhost;
|
||||
bool cancelled = false;
|
||||
|
||||
PrintHostJob() {}
|
||||
PrintHostJob(const PrintHostJob&) = delete;
|
||||
PrintHostJob(PrintHostJob &&other)
|
||||
: upload_data(std::move(other.upload_data))
|
||||
, printhost(std::move(other.printhost))
|
||||
, cancelled(other.cancelled)
|
||||
{}
|
||||
|
||||
PrintHostJob(DynamicPrintConfig *config)
|
||||
: printhost(PrintHost::get_print_host(config))
|
||||
{}
|
||||
|
||||
PrintHostJob& operator=(const PrintHostJob&) = delete;
|
||||
PrintHostJob& operator=(PrintHostJob &&other)
|
||||
{
|
||||
upload_data = std::move(other.upload_data);
|
||||
printhost = std::move(other.printhost);
|
||||
cancelled = other.cancelled;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool empty() const { return !printhost; }
|
||||
operator bool() const { return !!printhost; }
|
||||
};
|
||||
|
||||
|
||||
namespace GUI { class PrintHostQueueDialog; }
|
||||
|
||||
class PrintHostJobQueue
|
||||
{
|
||||
public:
|
||||
PrintHostJobQueue(GUI::PrintHostQueueDialog *queue_dialog);
|
||||
PrintHostJobQueue(const PrintHostJobQueue &) = delete;
|
||||
PrintHostJobQueue(PrintHostJobQueue &&other) = delete;
|
||||
~PrintHostJobQueue();
|
||||
|
||||
PrintHostJobQueue& operator=(const PrintHostJobQueue &) = delete;
|
||||
PrintHostJobQueue& operator=(PrintHostJobQueue &&other) = delete;
|
||||
|
||||
void enqueue(PrintHostJob job);
|
||||
void cancel(size_t id);
|
||||
|
||||
private:
|
||||
struct priv;
|
||||
std::shared_ptr<priv> p;
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
174
src/slic3r/Utils/Process.cpp
Normal file
174
src/slic3r/Utils/Process.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "Process.hpp"
|
||||
|
||||
#include <libslic3r/AppConfig.hpp>
|
||||
|
||||
#include "../GUI/GUI.hpp"
|
||||
// for file_wildcards()
|
||||
#include "../GUI/GUI_App.hpp"
|
||||
// localization
|
||||
#include "../GUI/I18N.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
// For starting another QIDISlicer instance on OSX.
|
||||
// Fails to compile on Windows on the build server.
|
||||
#ifdef __APPLE__
|
||||
#include <boost/process/spawn.hpp>
|
||||
#include <boost/process/args.hpp>
|
||||
#endif
|
||||
|
||||
#include <wx/stdpaths.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
enum class NewSlicerInstanceType {
|
||||
Slicer,
|
||||
GCodeViewer
|
||||
};
|
||||
|
||||
// Start a new Slicer process instance either in a Slicer mode or in a G-code mode.
|
||||
// Optionally load a 3MF, STL or a G-code on start.
|
||||
static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance_type, const std::vector<wxString> paths_to_open, bool single_instance, bool delete_after_load)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
wxString path;
|
||||
wxFileName::SplitPath(wxStandardPaths::Get().GetExecutablePath(), &path, nullptr, nullptr, wxPATH_NATIVE);
|
||||
path += "\\";
|
||||
path += (instance_type == NewSlicerInstanceType::Slicer) ? "qidi-slicer.exe" : "qidi-gcodeviewer.exe";
|
||||
std::vector<const wchar_t*> args;
|
||||
args.reserve(4);
|
||||
args.emplace_back(path.wc_str());
|
||||
if (!paths_to_open.empty()) {
|
||||
for (const auto& file : paths_to_open)
|
||||
args.emplace_back(file);
|
||||
}
|
||||
if (instance_type == NewSlicerInstanceType::Slicer && single_instance)
|
||||
args.emplace_back(L"--single-instance");
|
||||
if(delete_after_load && !paths_to_open.empty())
|
||||
args.emplace_back(L"--delete-after-load=1");
|
||||
|
||||
args.emplace_back(nullptr);
|
||||
BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << into_u8(path) << "\"";
|
||||
// Don't call with wxEXEC_HIDE_CONSOLE, QIDISlicer in GUI mode would just show the splash screen. It would not open the main window though, it would
|
||||
// just hang in the background.
|
||||
if (wxExecute(const_cast<wchar_t**>(args.data()), wxEXEC_ASYNC) <= 0)
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << into_u8(path);
|
||||
#else
|
||||
// Own executable path.
|
||||
boost::filesystem::path bin_path = into_path(wxStandardPaths::Get().GetExecutablePath());
|
||||
#if defined(__APPLE__)
|
||||
{
|
||||
// Maybe one day we will be able to run QIDIGCodeViewer, but for now the Apple notarization
|
||||
// process refuses Apps with multiple binaries and Vojtech does not know any workaround.
|
||||
// ((instance_type == NewSlicerInstanceType::Slicer) ? "QIDISlicer" : "QIDIGCodeViewer");
|
||||
// Just run QIDISlicer and give it a --gcodeviewer parameter.
|
||||
bin_path = bin_path.parent_path() / "QIDISlicer";
|
||||
// On Apple the wxExecute fails, thus we use boost::process instead.
|
||||
BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << bin_path.string() << "\"";
|
||||
try {
|
||||
std::vector<std::string> args;
|
||||
if (instance_type == NewSlicerInstanceType::GCodeViewer)
|
||||
args.emplace_back("--gcodeviewer");
|
||||
if (!paths_to_open.empty()) {
|
||||
for (const auto& file : paths_to_open)
|
||||
args.emplace_back(into_u8(file));
|
||||
}
|
||||
if (instance_type == NewSlicerInstanceType::Slicer && single_instance)
|
||||
args.emplace_back("--single-instance");
|
||||
if (delete_after_load && !paths_to_open.empty())
|
||||
args.emplace_back("--delete-after-load=1");
|
||||
boost::process::spawn(bin_path, args);
|
||||
// boost::process::spawn() sets SIGCHLD to SIGIGN for the child process, thus if a child QIDISlicer spawns another
|
||||
// subprocess and the subrocess dies, the child QIDISlicer will not receive information on end of subprocess
|
||||
// (posix waitpid() call will always fail).
|
||||
// https://jmmv.dev/2008/10/boostprocess-and-sigchld.html
|
||||
// The child instance of QIDISlicer has to reset SIGCHLD to its default, so that posix waitpid() and similar continue to work.
|
||||
// See GH issue #5507
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << bin_path.string() << "\": " << ex.what();
|
||||
}
|
||||
}
|
||||
#else // Linux or Unix
|
||||
{
|
||||
std::vector<const char*> args;
|
||||
args.reserve(3);
|
||||
#ifdef __linux__
|
||||
static const char* gcodeviewer_param = "--gcodeviewer";
|
||||
{
|
||||
// If executed by an AppImage, start the AppImage, not the main process.
|
||||
// see https://docs.appimage.org/packaging-guide/environment-variables.html#id2
|
||||
const char* appimage_binary = std::getenv("APPIMAGE");
|
||||
if (appimage_binary) {
|
||||
args.emplace_back(appimage_binary);
|
||||
if (instance_type == NewSlicerInstanceType::GCodeViewer)
|
||||
args.emplace_back(gcodeviewer_param);
|
||||
}
|
||||
}
|
||||
#endif // __linux__
|
||||
std::string my_path;
|
||||
if (args.empty()) {
|
||||
// Binary path was not set to the AppImage in the Linux specific block above, call the application directly.
|
||||
my_path = (bin_path.parent_path() / ((instance_type == NewSlicerInstanceType::Slicer) ? "qidi-slicer" : "qidi-gcodeviewer")).string();
|
||||
args.emplace_back(my_path.c_str());
|
||||
}
|
||||
std::string to_open;
|
||||
if (!paths_to_open.empty()) {
|
||||
for (const auto& file : paths_to_open) {
|
||||
to_open = into_u8(file);
|
||||
args.emplace_back(to_open.c_str());
|
||||
}
|
||||
}
|
||||
if (instance_type == NewSlicerInstanceType::Slicer && single_instance)
|
||||
args.emplace_back("--single-instance");
|
||||
if (delete_after_load && !paths_to_open.empty())
|
||||
args.emplace_back("--delete-after-load=1");
|
||||
args.emplace_back(nullptr);
|
||||
BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << args[0] << "\"";
|
||||
if (wxExecute(const_cast<char**>(args.data()), wxEXEC_ASYNC | wxEXEC_MAKE_GROUP_LEADER) <= 0)
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << args[0];
|
||||
}
|
||||
#endif // Linux or Unix
|
||||
#endif // Win32
|
||||
}
|
||||
static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance_type, const wxString* path_to_open, bool single_instance, bool delete_after_load)
|
||||
{
|
||||
std::vector<wxString> paths;
|
||||
if (path_to_open != nullptr)
|
||||
paths.emplace_back(path_to_open->wc_str());
|
||||
start_new_slicer_or_gcodeviewer(instance_type, paths, single_instance, delete_after_load);
|
||||
}
|
||||
|
||||
void start_new_slicer(const wxString *path_to_open, bool single_instance/*=false*/, bool delete_after_load/*=false*/)
|
||||
{
|
||||
start_new_slicer_or_gcodeviewer(NewSlicerInstanceType::Slicer, path_to_open, single_instance, delete_after_load);
|
||||
}
|
||||
void start_new_slicer(const std::vector<wxString>& files, bool single_instance/*=false*/, bool delete_after_load/*=false*/)
|
||||
{
|
||||
start_new_slicer_or_gcodeviewer(NewSlicerInstanceType::Slicer, files, single_instance, delete_after_load);
|
||||
}
|
||||
|
||||
void start_new_gcodeviewer(const wxString *path_to_open)
|
||||
{
|
||||
start_new_slicer_or_gcodeviewer(NewSlicerInstanceType::GCodeViewer, path_to_open, false, false);
|
||||
}
|
||||
|
||||
void start_new_gcodeviewer_open_file(wxWindow *parent)
|
||||
{
|
||||
wxFileDialog dialog(parent ? parent : wxGetApp().GetTopWindow(),
|
||||
_L("Open G-code file:"),
|
||||
from_u8(wxGetApp().app_config->get_last_dir()), wxString(),
|
||||
file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dialog.ShowModal() == wxID_OK) {
|
||||
wxString path = dialog.GetPath();
|
||||
start_new_gcodeviewer(&path);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
25
src/slic3r/Utils/Process.hpp
Normal file
25
src/slic3r/Utils/Process.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef GUI_PROCESS_HPP
|
||||
#define GUI_PROCESS_HPP
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
class wxWindow;
|
||||
class wxString;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
// Start a new slicer instance, optionally with a file to open.
|
||||
void start_new_slicer(const wxString *path_to_open = nullptr, bool single_instance = false, bool delete_after_load = false);
|
||||
void start_new_slicer(const std::vector<wxString>& files, bool single_instance = false, bool delete_after_load = false);
|
||||
|
||||
// Start a new G-code viewer instance, optionally with a file to open.
|
||||
void start_new_gcodeviewer(const wxString *path_to_open = nullptr);
|
||||
// Open a file dialog, ask the user to select a new G-code to open, start a new G-code viewer.
|
||||
void start_new_gcodeviewer_open_file(wxWindow *parent = nullptr);
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // GUI_PROCESS_HPP
|
||||
362
src/slic3r/Utils/RaycastManager.cpp
Normal file
362
src/slic3r/Utils/RaycastManager.cpp
Normal file
@@ -0,0 +1,362 @@
|
||||
#include "RaycastManager.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
#include "slic3r/GUI/CameraUtils.hpp"
|
||||
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
namespace{
|
||||
using namespace Slic3r;
|
||||
void actualize(RaycastManager::Meshes &meshes, const ModelVolumePtrs &volumes, const RaycastManager::ISkip *skip, RaycastManager::Meshes *input = nullptr);
|
||||
const AABBMesh * get_mesh(const RaycastManager::Meshes &meshes, size_t volume_id);
|
||||
RaycastManager::TrKey create_key(const ModelVolume& volume, const ModelInstance& instance){
|
||||
return std::make_pair(instance.id().id, volume.id().id); }
|
||||
RaycastManager::TrItems::iterator find(RaycastManager::TrItems &items, const RaycastManager::TrKey &key);
|
||||
RaycastManager::TrItems::const_iterator find(const RaycastManager::TrItems &items, const RaycastManager::TrKey &key);
|
||||
bool is_lower_key(const RaycastManager::TrKey &k1, const RaycastManager::TrKey &k2) {
|
||||
return k1.first < k2.first || (k1.first == k2.first && k1.second < k2.second); }
|
||||
bool is_lower(const RaycastManager::TrItem &i1, const RaycastManager::TrItem &i2) {
|
||||
return is_lower_key(i1.first, i2.first); };
|
||||
template<typename VecType> inline void erase(std::vector<VecType> &vec, const std::vector<bool> &flags);
|
||||
}
|
||||
|
||||
void RaycastManager::actualize(const ModelObject &object, const ISkip *skip, Meshes *meshes)
|
||||
{
|
||||
// actualize MeshRaycaster
|
||||
::actualize(m_meshes, object.volumes, skip, meshes);
|
||||
|
||||
// check if inscance was removed
|
||||
std::vector<bool> removed_transf(m_transformations.size(), {true});
|
||||
|
||||
bool need_sort = false;
|
||||
// actualize transformation matrices
|
||||
for (const ModelVolume *volume : object.volumes) {
|
||||
if (skip != nullptr && skip->skip(volume->id().id)) continue;
|
||||
const Transform3d &volume_tr = volume->get_matrix();
|
||||
for (const ModelInstance *instance : object.instances) {
|
||||
const Transform3d &instrance_tr = instance->get_matrix();
|
||||
Transform3d transformation = instrance_tr * volume_tr;
|
||||
TrKey key = ::create_key(*volume, *instance);
|
||||
auto item = ::find(m_transformations, key);
|
||||
if (item != m_transformations.end()) {
|
||||
// actualize transformation all the time
|
||||
item->second = transformation;
|
||||
size_t index = item - m_transformations.begin();
|
||||
removed_transf[index] = false;
|
||||
} else {
|
||||
// add new transformation
|
||||
m_transformations.emplace_back(key, transformation);
|
||||
need_sort = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clean other transformation
|
||||
::erase(m_transformations, removed_transf);
|
||||
|
||||
if (need_sort)
|
||||
std::sort(m_transformations.begin(), m_transformations.end(), ::is_lower);
|
||||
}
|
||||
|
||||
void RaycastManager::actualize(const ModelInstance &instance, const ISkip *skip, Meshes *meshes)
|
||||
{
|
||||
const ModelVolumePtrs &volumes = instance.get_object()->volumes;
|
||||
|
||||
// actualize MeshRaycaster
|
||||
::actualize(m_meshes, volumes, skip, meshes);
|
||||
|
||||
// check if inscance was removed
|
||||
std::vector<bool> removed_transf(m_transformations.size(), {true});
|
||||
|
||||
bool need_sort = false;
|
||||
// actualize transformation matrices
|
||||
for (const ModelVolume *volume : volumes) {
|
||||
if (skip != nullptr && skip->skip(volume->id().id))
|
||||
continue;
|
||||
const Transform3d &volume_tr = volume->get_matrix();
|
||||
const Transform3d &instrance_tr = instance.get_matrix();
|
||||
Transform3d transformation = instrance_tr * volume_tr;
|
||||
TrKey key = ::create_key(*volume, instance);
|
||||
auto item = ::find(m_transformations, key);
|
||||
if (item != m_transformations.end()) {
|
||||
// actualize transformation all the time
|
||||
item->second = transformation;
|
||||
size_t index = item - m_transformations.begin();
|
||||
removed_transf[index] = false;
|
||||
} else {
|
||||
// add new transformation
|
||||
m_transformations.emplace_back(key, transformation);
|
||||
need_sort = true;
|
||||
}
|
||||
}
|
||||
|
||||
// clean other transformation
|
||||
::erase(m_transformations, removed_transf);
|
||||
|
||||
if (need_sort)
|
||||
std::sort(m_transformations.begin(), m_transformations.end(), ::is_lower);
|
||||
}
|
||||
|
||||
std::optional<RaycastManager::Hit> RaycastManager::first_hit(const Vec3d& point, const Vec3d& direction, const ISkip *skip) const
|
||||
{
|
||||
// Improve: it is not neccessaru to use AABBMesh and calc normal for every hit
|
||||
|
||||
// Results
|
||||
const AABBMesh *hit_mesh = nullptr;
|
||||
double hit_squared_distance = 0.;
|
||||
int hit_face = -1;
|
||||
Vec3d hit_world;
|
||||
const Transform3d *hit_tramsformation = nullptr;
|
||||
const TrKey *hit_key = nullptr;
|
||||
|
||||
for (const auto &[key, transformation]: m_transformations) {
|
||||
size_t volume_id = key.second;
|
||||
if (skip != nullptr && skip->skip(volume_id)) continue;
|
||||
const AABBMesh *mesh = ::get_mesh(m_meshes, volume_id);
|
||||
if (mesh == nullptr) continue;
|
||||
Transform3d inv = transformation.inverse();
|
||||
|
||||
// transform input into mesh world
|
||||
Vec3d point_ = inv * point;
|
||||
Vec3d direction_= inv.linear() * direction;
|
||||
|
||||
std::vector<AABBMesh::hit_result> hits = mesh->query_ray_hits(point_, direction_);
|
||||
if (hits.empty()) continue; // no intersection found
|
||||
|
||||
const AABBMesh::hit_result &hit = hits.front();
|
||||
|
||||
// convert to world
|
||||
Vec3d world = transformation * hit.position();
|
||||
double squared_distance = (point - world).squaredNorm();
|
||||
if (hit_mesh != nullptr &&
|
||||
hit_squared_distance < squared_distance)
|
||||
continue; // exist closer one
|
||||
|
||||
hit_mesh = mesh;
|
||||
hit_squared_distance = squared_distance;
|
||||
hit_face = hit.face();
|
||||
hit_world = world;
|
||||
hit_tramsformation = &transformation;
|
||||
hit_key = &key;
|
||||
}
|
||||
|
||||
if (hit_mesh == nullptr)
|
||||
return {};
|
||||
|
||||
// Calculate normal from transformed triangle
|
||||
// NOTE: Anisotropic transformation of normal is not perpendiculat to triangle
|
||||
const Vec3i tri = hit_mesh->indices(hit_face);
|
||||
std::array<Vec3d,3> pts;
|
||||
auto tr = hit_tramsformation->linear();
|
||||
for (int i = 0; i < 3; ++i)
|
||||
pts[i] = tr * hit_mesh->vertices(tri[i]).cast<double>();
|
||||
Vec3d normal_world = (pts[1] - pts[0]).cross(pts[2] - pts[1]);
|
||||
if (has_reflection(*hit_tramsformation))
|
||||
normal_world *= -1;
|
||||
normal_world.normalize();
|
||||
|
||||
SurfacePoint<double> point_world{hit_world, normal_world};
|
||||
return RaycastManager::Hit{point_world, *hit_key, hit_squared_distance};
|
||||
}
|
||||
|
||||
std::optional<RaycastManager::Hit> RaycastManager::closest_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip) const
|
||||
{
|
||||
std::optional<Hit> closest;
|
||||
for (const auto &[key, transformation] : m_transformations) {
|
||||
size_t volume_id = key.second;
|
||||
if (skip != nullptr && skip->skip(volume_id)) continue;
|
||||
const AABBMesh *mesh = ::get_mesh(m_meshes, volume_id);
|
||||
if (mesh == nullptr) continue;
|
||||
Transform3d tr_inv = transformation.inverse();
|
||||
Vec3d mesh_point = tr_inv * point;
|
||||
Vec3d mesh_direction = tr_inv.linear() * direction;
|
||||
|
||||
// Need for detect that actual point position is on correct place
|
||||
Vec3d point_positive = mesh_point - mesh_direction;
|
||||
Vec3d point_negative = mesh_point + mesh_direction;
|
||||
|
||||
// Throw ray to both directions of ray
|
||||
std::vector<AABBMesh::hit_result> hits = mesh->query_ray_hits(point_positive, mesh_direction);
|
||||
std::vector<AABBMesh::hit_result> hits_neg = mesh->query_ray_hits(point_negative, -mesh_direction);
|
||||
hits.insert(hits.end(), std::make_move_iterator(hits_neg.begin()), std::make_move_iterator(hits_neg.end()));
|
||||
for (const AABBMesh::hit_result &hit : hits) {
|
||||
double squared_distance = (mesh_point - hit.position()).squaredNorm();
|
||||
if (closest.has_value() &&
|
||||
closest->squared_distance < squared_distance)
|
||||
continue;
|
||||
closest = Hit{{hit.position(), hit.normal()}, key, squared_distance};
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
std::optional<RaycastManager::ClosePoint> RaycastManager::closest(const Vec3d &point, const ISkip *skip) const
|
||||
{
|
||||
std::optional<ClosePoint> closest;
|
||||
for (const auto &[key, transformation] : m_transformations) {
|
||||
size_t volume_id = key.second;
|
||||
if (skip != nullptr && skip->skip(volume_id))
|
||||
continue;
|
||||
const AABBMesh *mesh = ::get_mesh(m_meshes, volume_id);
|
||||
if (mesh == nullptr) continue;
|
||||
Transform3d tr_inv = transformation.inverse();
|
||||
Vec3d mesh_point = tr_inv * point;
|
||||
|
||||
int face_idx = 0;
|
||||
Vec3d closest_point;
|
||||
Vec3d pointd = point.cast<double>();
|
||||
mesh->squared_distance(pointd, face_idx, closest_point);
|
||||
|
||||
double squared_distance = (mesh_point - closest_point).squaredNorm();
|
||||
if (closest.has_value() && closest->squared_distance < squared_distance)
|
||||
continue;
|
||||
|
||||
closest = ClosePoint{key, closest_point, squared_distance};
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
Slic3r::Transform3d RaycastManager::get_transformation(const TrKey &tr_key) const {
|
||||
auto tr = ::find(m_transformations, tr_key);
|
||||
if (tr == m_transformations.end())
|
||||
return Transform3d::Identity();
|
||||
return tr->second;
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
void actualize(RaycastManager::Meshes &meshes, const ModelVolumePtrs &volumes, const RaycastManager::ISkip *skip, RaycastManager::Meshes* inputs)
|
||||
{
|
||||
// check if volume was removed
|
||||
std::vector<bool> removed_meshes(meshes.size(), {true});
|
||||
bool need_sort = false;
|
||||
// actualize MeshRaycaster
|
||||
for (const ModelVolume *volume : volumes) {
|
||||
size_t oid = volume->id().id;
|
||||
if (skip != nullptr && skip->skip(oid))
|
||||
continue;
|
||||
auto is_oid = [oid](const RaycastManager::Mesh &it) { return oid == it.first; };
|
||||
if (auto item = std::find_if(meshes.begin(), meshes.end(), is_oid);
|
||||
item != meshes.end()) {
|
||||
size_t index = item - meshes.begin();
|
||||
removed_meshes[index] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// exist AABB in inputs ?
|
||||
if (inputs != nullptr) {
|
||||
auto input = std::find_if(inputs->begin(), inputs->end(), is_oid);
|
||||
if (input != inputs->end()) {
|
||||
meshes.emplace_back(std::move(*input));
|
||||
need_sort = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// add new raycaster
|
||||
bool calculate_epsilon = true;
|
||||
auto mesh = std::make_unique<AABBMesh>(volume->mesh(), calculate_epsilon);
|
||||
meshes.emplace_back(std::make_pair(oid, std::move(mesh)));
|
||||
need_sort = true;
|
||||
}
|
||||
|
||||
// clean other raycasters
|
||||
erase(meshes, removed_meshes);
|
||||
|
||||
// All the time meshes must be sorted by volume id - for faster search
|
||||
if (need_sort) {
|
||||
auto is_lower = [](const RaycastManager::Mesh &m1, const RaycastManager::Mesh &m2) { return m1.first < m2.first; };
|
||||
std::sort(meshes.begin(), meshes.end(), is_lower);
|
||||
}
|
||||
}
|
||||
|
||||
const Slic3r::AABBMesh *get_mesh(const RaycastManager::Meshes &meshes, size_t volume_id)
|
||||
{
|
||||
auto is_lower_index = [](const RaycastManager::Mesh &m, size_t i) { return m.first < i; };
|
||||
auto it = std::lower_bound(meshes.begin(), meshes.end(), volume_id, is_lower_index);
|
||||
if (it == meshes.end() || it->first != volume_id)
|
||||
return nullptr;
|
||||
return &(*(it->second));
|
||||
}
|
||||
|
||||
RaycastManager::TrItems::iterator find(RaycastManager::TrItems &items, const RaycastManager::TrKey &key) {
|
||||
auto fnc = [](const RaycastManager::TrItem &it, const RaycastManager::TrKey &l_key) { return is_lower_key(it.first, l_key); };
|
||||
auto it = std::lower_bound(items.begin(), items.end(), key, fnc);
|
||||
if (it != items.end() && it->first != key)
|
||||
return items.end();
|
||||
return it;
|
||||
}
|
||||
|
||||
RaycastManager::TrItems::const_iterator find(const RaycastManager::TrItems &items, const RaycastManager::TrKey &key)
|
||||
{
|
||||
auto fnc = [](const RaycastManager::TrItem &it, const RaycastManager::TrKey &l_key) { return is_lower_key(it.first, l_key); };
|
||||
auto it = std::lower_bound(items.begin(), items.end(), key, fnc);
|
||||
if (it != items.end() && it->first != key)
|
||||
return items.end();
|
||||
return it;
|
||||
}
|
||||
|
||||
template<typename VecType> inline void erase(std::vector<VecType> &vec, const std::vector<bool> &flags)
|
||||
{
|
||||
if (vec.size() < flags.size() || flags.empty())
|
||||
return;
|
||||
|
||||
// reverse iteration over flags to erase indices from back to front.
|
||||
for (int i = static_cast<int>(flags.size()) - 1; i >= 0; --i)
|
||||
if (flags[i])
|
||||
vec.erase(vec.begin() + i);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Slic3r::GUI{
|
||||
|
||||
RaycastManager::Meshes create_meshes(GLCanvas3D &canvas, const RaycastManager::AllowVolumes &condition)
|
||||
{
|
||||
SceneRaycaster::EType type = SceneRaycaster::EType::Volume;
|
||||
auto scene_casters = canvas.get_raycasters_for_picking(type);
|
||||
if (scene_casters == nullptr)
|
||||
return {};
|
||||
const std::vector<std::shared_ptr<SceneRaycasterItem>> &casters = *scene_casters;
|
||||
|
||||
const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes;
|
||||
const ModelObjectPtrs &objects = canvas.get_model()->objects;
|
||||
|
||||
RaycastManager::Meshes meshes;
|
||||
for (const std::shared_ptr<SceneRaycasterItem> &caster : casters) {
|
||||
int index = SceneRaycaster::decode_id(type, caster->get_id());
|
||||
if (index < 0)
|
||||
continue;
|
||||
auto index_ = static_cast<size_t>(index);
|
||||
if(index_ >= gl_volumes.size())
|
||||
continue;
|
||||
const GLVolume *gl_volume = gl_volumes[index_];
|
||||
if (gl_volume == nullptr)
|
||||
continue;
|
||||
const ModelVolume *volume = get_model_volume(*gl_volume, objects);
|
||||
if (volume == nullptr)
|
||||
continue;
|
||||
size_t id = volume->id().id;
|
||||
if (condition.skip(id))
|
||||
continue;
|
||||
auto mesh = std::make_unique<AABBMesh>(caster->get_raycaster()->get_aabb_mesh());
|
||||
meshes.emplace_back(std::make_pair(id, std::move(mesh)));
|
||||
}
|
||||
return meshes;
|
||||
}
|
||||
|
||||
|
||||
std::optional<RaycastManager::Hit> ray_from_camera(const RaycastManager &raycaster,
|
||||
const Vec2d &mouse_pos,
|
||||
const Camera &camera,
|
||||
const RaycastManager::ISkip *skip)
|
||||
{
|
||||
Vec3d point;
|
||||
Vec3d direction;
|
||||
CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction);
|
||||
return raycaster.first_hit(point, direction, skip);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
172
src/slic3r/Utils/RaycastManager.hpp
Normal file
172
src/slic3r/Utils/RaycastManager.hpp
Normal file
@@ -0,0 +1,172 @@
|
||||
#ifndef slic3r_RaycastManager_hpp_
|
||||
#define slic3r_RaycastManager_hpp_
|
||||
|
||||
#include <memory> // unique_ptr
|
||||
#include <optional>
|
||||
#include "libslic3r/AABBMesh.hpp" // Structure to cast rays
|
||||
#include "libslic3r/Point.hpp" // Transform3d
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
#include "libslic3r/Model.hpp" // ModelObjectPtrs, ModelObject, ModelInstance, ModelVolume
|
||||
|
||||
namespace Slic3r::GUI{
|
||||
|
||||
/// <summary>
|
||||
/// Cast rays from camera to scene
|
||||
/// Used for find hit point on model volume under mouse cursor
|
||||
/// </summary>
|
||||
class RaycastManager
|
||||
{
|
||||
// Public structures used by RaycastManager
|
||||
public:
|
||||
|
||||
// ModelVolume.id
|
||||
using Mesh = std::pair<size_t, std::unique_ptr<AABBMesh> >;
|
||||
using Meshes = std::vector<Mesh>;
|
||||
|
||||
// Key for transformation consist of unique volume and instance id ... ObjectId()
|
||||
// ModelInstance, ModelVolume
|
||||
using TrKey = std::pair<size_t, size_t>;
|
||||
using TrItem = std::pair<TrKey, Transform3d>;
|
||||
using TrItems = std::vector<TrItem>;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for identify allowed volumes to cast rays.
|
||||
/// </summary>
|
||||
class ISkip{
|
||||
public:
|
||||
virtual ~ISkip() = default;
|
||||
|
||||
/// <summary>
|
||||
/// Condition to not process model volume
|
||||
/// </summary>
|
||||
/// <param name="model_volume_id">ObjectID of model volume to not process</param>
|
||||
/// <returns>True on skip otherwise false</returns>
|
||||
virtual bool skip(const size_t &model_volume_id) const { return false; }
|
||||
};
|
||||
|
||||
// TODO: it is more general object move outside of this class
|
||||
template<typename T>
|
||||
struct SurfacePoint {
|
||||
using Vec3 = Eigen::Matrix<T, 3, 1, Eigen::DontAlign>;
|
||||
Vec3 position = Vec3::Zero();
|
||||
Vec3 normal = Vec3::UnitZ();
|
||||
};
|
||||
|
||||
struct Hit : public SurfacePoint<double>
|
||||
{
|
||||
TrKey tr_key;
|
||||
double squared_distance;
|
||||
};
|
||||
|
||||
struct ClosePoint
|
||||
{
|
||||
TrKey tr_key;
|
||||
Vec3d point;
|
||||
double squared_distance;
|
||||
};
|
||||
|
||||
// Members
|
||||
private:
|
||||
// Keep structure to fast cast rays
|
||||
// meshes are sorted by volume_id for faster search
|
||||
Meshes m_meshes;
|
||||
|
||||
// Keep transformation of meshes
|
||||
TrItems m_transformations;
|
||||
// Note: one mesh could have more transformations ... instances
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Actualize raycasters + transformation
|
||||
/// Detection of removed object
|
||||
/// Detection of removed instance
|
||||
/// Detection of removed volume
|
||||
/// </summary>
|
||||
/// <param name="object">Model representation</param>
|
||||
/// <param name="skip">Condifiton for skip actualization</param>
|
||||
/// <param name="meshes">Speed up for already created AABBtrees</param>
|
||||
void actualize(const ModelObject &object, const ISkip *skip = nullptr, Meshes *meshes = nullptr);
|
||||
void actualize(const ModelInstance &instance, const ISkip *skip = nullptr, Meshes* meshes = nullptr);
|
||||
|
||||
class SkipVolume: public ISkip
|
||||
{
|
||||
size_t volume_id;
|
||||
public:
|
||||
SkipVolume(size_t volume_id) : volume_id(volume_id) {}
|
||||
bool skip(const size_t &model_volume_id) const override { return model_volume_id == volume_id; }
|
||||
};
|
||||
|
||||
class AllowVolumes: public ISkip
|
||||
{
|
||||
std::vector<size_t> allowed_id;
|
||||
public:
|
||||
AllowVolumes(std::vector<size_t> allowed_id) : allowed_id(allowed_id) {}
|
||||
bool skip(const size_t &model_volume_id) const override {
|
||||
auto it = std::find(allowed_id.begin(), allowed_id.end(), model_volume_id);
|
||||
return it == allowed_id.end();
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Unproject on mesh and return closest hit to point in given direction
|
||||
/// </summary>
|
||||
/// <param name="point">Position in space</param>
|
||||
/// <param name="direction">Casted ray direction</param>
|
||||
/// <param name="skip">Define which caster will be skipped, null mean no skip</param>
|
||||
/// <returns>Position on surface, normal direction in world coorinate
|
||||
/// + key, to know hitted instance and volume</returns>
|
||||
std::optional<Hit> first_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip = nullptr) const;
|
||||
|
||||
/// <summary>
|
||||
/// Unproject Ray(point direction) on mesh to find closest hit of surface in given direction
|
||||
/// NOTE: It inspect also oposit direction of ray !!
|
||||
/// </summary>
|
||||
/// <param name="point">Start point for ray</param>
|
||||
/// <param name="direction">Direction of ray, orientation doesn't matter, both are used</param>
|
||||
/// <param name="skip">Define which caster will be skipped, null mean no skip</param>
|
||||
/// <returns>Position on surface, normal direction and transformation key, which define hitted object instance</returns>
|
||||
std::optional<Hit> closest_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip = nullptr) const;
|
||||
|
||||
/// <summary>
|
||||
/// Search of closest point
|
||||
/// </summary>
|
||||
/// <param name="point">Point</param>
|
||||
/// <param name="skip">Define which caster will be skipped, null mean no skip</param>
|
||||
/// <returns></returns>
|
||||
std::optional<ClosePoint> closest(const Vec3d &point, const ISkip *skip = nullptr) const;
|
||||
|
||||
/// <summary>
|
||||
/// Getter on transformation from hitted volume to world
|
||||
/// </summary>
|
||||
/// <param name="tr_key">Define transformation</param>
|
||||
/// <returns>Transformation for key</returns>
|
||||
Transform3d get_transformation(const TrKey &tr_key) const;
|
||||
};
|
||||
|
||||
class GLCanvas3D;
|
||||
/// <summary>
|
||||
/// Use scene Raycasters and prepare data for actualize RaycasterManager
|
||||
/// </summary>
|
||||
/// <param name="canvas">contain Scene raycasters</param>
|
||||
/// <param name="condition">Limit for scene casters</param>
|
||||
/// <returns>Meshes</returns>
|
||||
RaycastManager::Meshes create_meshes(GLCanvas3D &canvas, const RaycastManager::AllowVolumes &condition);
|
||||
|
||||
struct Camera;
|
||||
/// <summary>
|
||||
/// Unproject on mesh by Mesh raycasters
|
||||
/// </summary>
|
||||
/// <param name="mouse_pos">Position of mouse on screen</param>
|
||||
/// <param name="camera">Projection params</param>
|
||||
/// <param name="skip">Define which caster will be skipped, null mean no skip</param>
|
||||
/// <returns>Position on surface, normal direction in world coorinate
|
||||
/// + key, to know hitted instance and volume</returns>
|
||||
std::optional<RaycastManager::Hit> ray_from_camera(const RaycastManager &raycaster,
|
||||
const Vec2d &mouse_pos,
|
||||
const Camera &camera,
|
||||
const RaycastManager::ISkip *skip);
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_RaycastManager_hpp_
|
||||
291
src/slic3r/Utils/Repetier.cpp
Normal file
291
src/slic3r/Utils/Repetier.cpp
Normal file
@@ -0,0 +1,291 @@
|
||||
#include "Repetier.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <exception>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <wx/progdlg.h>
|
||||
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "Http.hpp"
|
||||
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
Repetier::Repetier(DynamicPrintConfig *config) :
|
||||
host(config->opt_string("print_host")),
|
||||
apikey(config->opt_string("printhost_apikey")),
|
||||
cafile(config->opt_string("printhost_cafile")),
|
||||
port(config->opt_string("printhost_port"))
|
||||
{}
|
||||
|
||||
const char* Repetier::get_name() const { return "Repetier"; }
|
||||
|
||||
|
||||
|
||||
static bool validate_repetier(const boost::optional<std::string>& name,
|
||||
const boost::optional<std::string>& soft)
|
||||
{
|
||||
if (soft) {
|
||||
// See https://github.com/qidi3d/QIDISlicer/issues/7807:
|
||||
// Repetier allows "rebranding", so the "name" value is not reliable when detecting
|
||||
// server type. Newer Repetier versions send "software", which should be invariant.
|
||||
return ((*soft) == "Repetier-Server");
|
||||
} else {
|
||||
// If there is no "software" value, validate as we did before:
|
||||
return name ? boost::starts_with(*name, "Repetier") : true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Repetier::test(wxString &msg) const
|
||||
{
|
||||
// Since the request is performed synchronously here,
|
||||
// it is ok to refer to `msg` from within the closure
|
||||
|
||||
const char *name = get_name();
|
||||
|
||||
bool res = true;
|
||||
auto url = make_url("printer/info");
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: List version at: %2%") % name % url;
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
set_auth(http);
|
||||
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
res = false;
|
||||
msg = format_error(body, error, status);
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body;
|
||||
|
||||
try {
|
||||
std::stringstream ss(body);
|
||||
pt::ptree ptree;
|
||||
pt::read_json(ss, ptree);
|
||||
|
||||
const auto text = ptree.get_optional<std::string>("name");
|
||||
const auto soft = ptree.get_optional<std::string>("software");
|
||||
res = validate_repetier(text, soft);
|
||||
if (! res) {
|
||||
msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (soft ? *soft : (text ? *text : "Repetier")));
|
||||
}
|
||||
}
|
||||
catch (const std::exception &) {
|
||||
res = false;
|
||||
msg = "Could not parse server response";
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
wxString Repetier::get_test_ok_msg () const
|
||||
{
|
||||
return _(L("Connection to Repetier works correctly."));
|
||||
}
|
||||
|
||||
wxString Repetier::get_test_failed_msg (wxString &msg) const
|
||||
{
|
||||
return GUI::format_wxstr("%s: %s\n\n%s"
|
||||
, _L("Could not connect to Repetier")
|
||||
, msg
|
||||
, _L("Note: Repetier version at least 0.90.0 is required."));
|
||||
}
|
||||
|
||||
bool Repetier::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
const char *name = get_name();
|
||||
|
||||
const auto upload_filename = upload_data.upload_path.filename();
|
||||
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||
|
||||
wxString test_msg;
|
||||
if (! test(test_msg)) {
|
||||
error_fn(std::move(test_msg));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool res = true;
|
||||
|
||||
auto url = upload_data.post_action == PrintHostPostUploadAction::StartPrint
|
||||
? make_url((boost::format("printer/job/%1%") % port).str())
|
||||
: make_url((boost::format("printer/model/%1%") % port).str());
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%, group: %7%")
|
||||
% name
|
||||
% upload_data.source_path
|
||||
% url
|
||||
% upload_filename.string()
|
||||
% upload_parent_path.string()
|
||||
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
|
||||
% upload_data.group;
|
||||
|
||||
auto http = Http::post(std::move(url));
|
||||
set_auth(http);
|
||||
|
||||
if (! upload_data.group.empty() && upload_data.group != _u8L("Default")) {
|
||||
http.form_add("group", upload_data.group);
|
||||
}
|
||||
|
||||
if(upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
|
||||
http.form_add("name", upload_filename.string());
|
||||
http.form_add("autostart", "true"); // See https://github.com/qidi3d/QIDISlicer/issues/7807#issuecomment-1235519371
|
||||
}
|
||||
|
||||
http.form_add("a", "upload")
|
||||
.form_add_file("filename", upload_data.source_path.string(), upload_filename.string())
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
error_fn(format_error(body, error, status));
|
||||
res = false;
|
||||
})
|
||||
.on_progress([&](Http::Progress progress, bool &cancel) {
|
||||
prorgess_fn(std::move(progress), cancel);
|
||||
if (cancel) {
|
||||
// Upload was canceled
|
||||
BOOST_LOG_TRIVIAL(info) << "Repetier: Upload canceled";
|
||||
res = false;
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Repetier::set_auth(Http &http) const
|
||||
{
|
||||
http.header("X-Api-Key", apikey);
|
||||
|
||||
if (! cafile.empty()) {
|
||||
http.ca_file(cafile);
|
||||
}
|
||||
}
|
||||
|
||||
std::string Repetier::make_url(const std::string &path) const
|
||||
{
|
||||
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
||||
if (host.back() == '/') {
|
||||
return (boost::format("%1%%2%") % host % path).str();
|
||||
} else {
|
||||
return (boost::format("%1%/%2%") % host % path).str();
|
||||
}
|
||||
} else {
|
||||
return (boost::format("http://%1%/%2%") % host % path).str();
|
||||
}
|
||||
}
|
||||
|
||||
bool Repetier::get_groups(wxArrayString& groups) const
|
||||
{
|
||||
bool res = true;
|
||||
|
||||
const char *name = get_name();
|
||||
auto url = make_url((boost::format("printer/api/%1%") % port).str());
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get groups at: %2%") % name % url;
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
set_auth(http);
|
||||
http.form_add("a", "listModelGroups");
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got groups: %2%") % name % body;
|
||||
|
||||
try {
|
||||
std::stringstream ss(body);
|
||||
pt::ptree ptree;
|
||||
pt::read_json(ss, ptree);
|
||||
|
||||
BOOST_FOREACH(boost::property_tree::ptree::value_type &v, ptree.get_child("groupNames.")) {
|
||||
if (v.second.data() == "#") {
|
||||
groups.push_back(_L("Default"));
|
||||
} else {
|
||||
// Is it safe to assume that the data are utf-8 encoded?
|
||||
groups.push_back(GUI::from_u8(v.second.data()));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception &) {
|
||||
//msg = "Could not parse server response";
|
||||
res = false;
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool Repetier::get_printers(wxArrayString& printers) const
|
||||
{
|
||||
const char *name = get_name();
|
||||
|
||||
bool res = true;
|
||||
auto url = make_url("printer/list");
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: List printers at: %2%") % name % url;
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
set_auth(http);
|
||||
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error listing printers: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
res = false;
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned http_status) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got printers: %2%, HTTP status: %3%") % name % body % http_status;
|
||||
|
||||
if (http_status != 200)
|
||||
throw HostNetworkError(GUI::format(_L("HTTP status: %1%\nMessage body: \"%2%\""), http_status, body));
|
||||
|
||||
std::stringstream ss(body);
|
||||
pt::ptree ptree;
|
||||
try {
|
||||
pt::read_json(ss, ptree);
|
||||
} catch (const pt::ptree_error &err) {
|
||||
throw HostNetworkError(GUI::format(_L("Parsing of host response failed.\nMessage body: \"%1%\"\nError: \"%2%\""), body, err.what()));
|
||||
}
|
||||
|
||||
const auto error = ptree.get_optional<std::string>("error");
|
||||
if (error)
|
||||
throw HostNetworkError(*error);
|
||||
|
||||
try {
|
||||
BOOST_FOREACH(boost::property_tree::ptree::value_type &v, ptree.get_child("data.")) {
|
||||
const auto port = v.second.get<std::string>("slug");
|
||||
printers.push_back(Slic3r::GUI::from_u8(port));
|
||||
}
|
||||
} catch (const std::exception &err) {
|
||||
throw HostNetworkError(GUI::format(_L("Enumeration of host printers failed.\nMessage body: \"%1%\"\nError: \"%2%\""), body, err.what()));
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
48
src/slic3r/Utils/Repetier.hpp
Normal file
48
src/slic3r/Utils/Repetier.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef slic3r_Repetier_hpp_
|
||||
#define slic3r_Repetier_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "PrintHost.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
|
||||
class Repetier : public PrintHost
|
||||
{
|
||||
public:
|
||||
Repetier(DynamicPrintConfig *config);
|
||||
~Repetier() override = default;
|
||||
|
||||
const char* get_name() const override;
|
||||
|
||||
bool test(wxString &curl_msg) const override;
|
||||
wxString get_test_ok_msg () const override;
|
||||
wxString get_test_failed_msg (wxString &msg) const override;
|
||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||
bool has_auto_discovery() const override { return false; }
|
||||
bool can_test() const override { return true; }
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||
bool supports_multiple_printers() const override { return true; }
|
||||
std::string get_host() const override { return host; }
|
||||
|
||||
bool get_groups(wxArrayString &groups) const override;
|
||||
bool get_printers(wxArrayString &printers) const override;
|
||||
|
||||
private:
|
||||
std::string host;
|
||||
std::string apikey;
|
||||
std::string cafile;
|
||||
std::string port;
|
||||
|
||||
void set_auth(Http &http) const;
|
||||
std::string make_url(const std::string &path) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
31
src/slic3r/Utils/RetinaHelper.hpp
Normal file
31
src/slic3r/Utils/RetinaHelper.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef slic3r_RetinaHelper_hpp_
|
||||
#define slic3r_RetinaHelper_hpp_
|
||||
|
||||
class wxWindow;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class RetinaHelper
|
||||
{
|
||||
public:
|
||||
RetinaHelper(wxWindow* window);
|
||||
~RetinaHelper();
|
||||
|
||||
void set_use_retina(bool value);
|
||||
bool get_use_retina();
|
||||
float get_scale_factor();
|
||||
|
||||
private:
|
||||
#ifdef __WXGTK3__
|
||||
wxWindow* m_window;
|
||||
#endif // __WXGTK3__
|
||||
void* m_self;
|
||||
};
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // RetinaHelper_h
|
||||
15
src/slic3r/Utils/RetinaHelperImpl.hmm
Normal file
15
src/slic3r/Utils/RetinaHelperImpl.hmm
Normal file
@@ -0,0 +1,15 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
class wxEvtHandler;
|
||||
|
||||
@interface RetinaHelperImpl : NSObject
|
||||
{
|
||||
NSView *view;
|
||||
wxEvtHandler* handler;
|
||||
}
|
||||
|
||||
-(id)initWithView:(NSView *)view handler:(wxEvtHandler *)handler;
|
||||
-(void)setViewWantsBestResolutionOpenGLSurface:(BOOL)value;
|
||||
-(BOOL)getViewWantsBestResolutionOpenGLSurface;
|
||||
-(float)getBackingScaleFactor;
|
||||
@end
|
||||
110
src/slic3r/Utils/RetinaHelperImpl.mm
Normal file
110
src/slic3r/Utils/RetinaHelperImpl.mm
Normal file
@@ -0,0 +1,110 @@
|
||||
// The RetinaHelper was originally written by Andreas Stahl, 2013
|
||||
|
||||
#import "RetinaHelper.hpp"
|
||||
#import "RetinaHelperImpl.hmm"
|
||||
#import <OpenGL/OpenGL.h>
|
||||
|
||||
#import "wx/window.h"
|
||||
|
||||
@implementation RetinaHelperImpl
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
RetinaHelper::RetinaHelper(wxWindow *window)
|
||||
{
|
||||
m_self = nullptr;
|
||||
m_self = [[RetinaHelperImpl alloc] initWithView:window->GetHandle() handler:window->GetEventHandler()];
|
||||
}
|
||||
|
||||
RetinaHelper::~RetinaHelper()
|
||||
{
|
||||
[(id)m_self release];
|
||||
}
|
||||
|
||||
void RetinaHelper::set_use_retina(bool aValue)
|
||||
{
|
||||
[(id)m_self setViewWantsBestResolutionOpenGLSurface:aValue];
|
||||
}
|
||||
|
||||
bool RetinaHelper::get_use_retina()
|
||||
{
|
||||
return [(id)m_self getViewWantsBestResolutionOpenGLSurface];
|
||||
}
|
||||
|
||||
float RetinaHelper::get_scale_factor()
|
||||
{
|
||||
return [(id)m_self getViewWantsBestResolutionOpenGLSurface] ? [(id)m_self getBackingScaleFactor] : 1.0f;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
-(id)initWithView:(NSView *)aView handler:(wxEvtHandler *)aHandler
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
handler = aHandler;
|
||||
view = aView;
|
||||
// register for backing change notifications
|
||||
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
||||
if (nc) {
|
||||
[nc addObserver:self selector:@selector(windowDidChangeBackingProperties:)
|
||||
name:NSWindowDidChangeBackingPropertiesNotification object:nil];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void) dealloc
|
||||
{
|
||||
// unregister from all notifications
|
||||
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
||||
if (nc) {
|
||||
[nc removeObserver:self];
|
||||
}
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
-(void)setViewWantsBestResolutionOpenGLSurface:(BOOL)value
|
||||
{
|
||||
[view setWantsBestResolutionOpenGLSurface:value];
|
||||
}
|
||||
|
||||
-(BOOL)getViewWantsBestResolutionOpenGLSurface
|
||||
{
|
||||
return [view wantsBestResolutionOpenGLSurface];
|
||||
}
|
||||
|
||||
-(float)getBackingScaleFactor
|
||||
{
|
||||
return [[view window] backingScaleFactor];
|
||||
}
|
||||
|
||||
- (void)windowDidChangeBackingProperties:(NSNotification *)notification
|
||||
{
|
||||
NSWindow *theWindow = (NSWindow *)[notification object];
|
||||
|
||||
if (theWindow == [view window]) {
|
||||
CGFloat newBackingScaleFactor = [theWindow backingScaleFactor];
|
||||
CGFloat oldBackingScaleFactor = [[[notification userInfo]
|
||||
objectForKey:@"NSBackingPropertyOldScaleFactorKey"]
|
||||
doubleValue];
|
||||
|
||||
if (newBackingScaleFactor != oldBackingScaleFactor) {
|
||||
// generate a wx resize event and pass it to the handler's queue
|
||||
wxSizeEvent *event = new wxSizeEvent();
|
||||
// use the following line if this resize event should have the physical pixel resolution
|
||||
// but that is not recommended, because ordinary resize events won't do so either
|
||||
// which would necessitate a case-by-case switch in the resize handler method.
|
||||
// NSRect nsrect = [view convertRectToBacking:[view bounds]];
|
||||
NSRect nsrect = [view bounds];
|
||||
wxRect rect = wxRect(nsrect.origin.x, nsrect.origin.y, nsrect.size.width, nsrect.size.height);
|
||||
event->SetRect(rect);
|
||||
event->SetSize(rect.GetSize());
|
||||
handler->QueueEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@end
|
||||
504
src/slic3r/Utils/Serial.cpp
Normal file
504
src/slic3r/Utils/Serial.cpp
Normal file
@@ -0,0 +1,504 @@
|
||||
#include "Serial.hpp"
|
||||
|
||||
#include "libslic3r/Exception.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <fstream>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#if _WIN32
|
||||
#include <Windows.h>
|
||||
#include <Setupapi.h>
|
||||
#include <initguid.h>
|
||||
#include <devguid.h>
|
||||
#include <regex>
|
||||
// Undefine min/max macros incompatible with the standard library
|
||||
// For example, std::numeric_limits<std::streamsize>::max()
|
||||
// produces some weird errors
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
#include "boost/nowide/convert.hpp"
|
||||
#pragma comment(lib, "user32.lib")
|
||||
#elif __APPLE__
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <CoreFoundation/CFString.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/serial/IOSerialKeys.h>
|
||||
#include <IOKit/serial/ioss.h>
|
||||
#include <sys/syslimits.h>
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/unistd.h>
|
||||
#include <sys/select.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) || defined(__OpenBSD__)
|
||||
#include <termios.h>
|
||||
#elif defined __linux__
|
||||
#include <fcntl.h>
|
||||
#include <asm-generic/ioctls.h>
|
||||
#endif
|
||||
|
||||
using boost::optional;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
static bool looks_like_printer(const std::string &friendly_name)
|
||||
{
|
||||
return friendly_name.find("Original QIDI") != std::string::npos;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
void parse_hardware_id(const std::string &hardware_id, SerialPortInfo &spi)
|
||||
{
|
||||
unsigned vid, pid;
|
||||
std::regex pattern("USB\\\\.*VID_([[:xdigit:]]+)&PID_([[:xdigit:]]+).*");
|
||||
std::smatch matches;
|
||||
if (std::regex_match(hardware_id, matches, pattern)) {
|
||||
vid = std::stoul(matches[1].str(), 0, 16);
|
||||
pid = std::stoul(matches[2].str(), 0, 16);
|
||||
spi.id_vendor = vid;
|
||||
spi.id_product = pid;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
optional<std::string> sysfs_tty_prop(const std::string &tty_dev, const std::string &name)
|
||||
{
|
||||
const auto prop_path = (boost::format("/sys/class/tty/%1%/device/../%2%") % tty_dev % name).str();
|
||||
std::ifstream file(prop_path);
|
||||
std::string res;
|
||||
|
||||
std::getline(file, res);
|
||||
if (file.good()) { return res; }
|
||||
else { return boost::none; }
|
||||
}
|
||||
|
||||
optional<unsigned long> sysfs_tty_prop_hex(const std::string &tty_dev, const std::string &name)
|
||||
{
|
||||
auto prop = sysfs_tty_prop(tty_dev, name);
|
||||
if (!prop) { return boost::none; }
|
||||
|
||||
try { return std::stoul(*prop, 0, 16); }
|
||||
catch (const std::exception&) { return boost::none; }
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<SerialPortInfo> scan_serial_ports_extended()
|
||||
{
|
||||
std::vector<SerialPortInfo> output;
|
||||
|
||||
#ifdef _WIN32
|
||||
SP_DEVINFO_DATA devInfoData = { 0 };
|
||||
devInfoData.cbSize = sizeof(devInfoData);
|
||||
// Get the tree containing the info for the ports.
|
||||
HDEVINFO hDeviceInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS, 0, nullptr, DIGCF_PRESENT);
|
||||
if (hDeviceInfo != INVALID_HANDLE_VALUE) {
|
||||
// Iterate over all the devices in the tree.
|
||||
for (int nDevice = 0; SetupDiEnumDeviceInfo(hDeviceInfo, nDevice, &devInfoData); ++ nDevice) {
|
||||
SerialPortInfo port_info;
|
||||
// Get the registry key which stores the ports settings.
|
||||
HKEY hDeviceKey = SetupDiOpenDevRegKey(hDeviceInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
|
||||
if (hDeviceKey) {
|
||||
// Read in the name of the port.
|
||||
wchar_t pszPortName[4096];
|
||||
DWORD dwSize = sizeof(pszPortName);
|
||||
DWORD dwType = 0;
|
||||
if (RegQueryValueEx(hDeviceKey, L"PortName", NULL, &dwType, (LPBYTE)pszPortName, &dwSize) == ERROR_SUCCESS)
|
||||
port_info.port = boost::nowide::narrow(pszPortName);
|
||||
RegCloseKey(hDeviceKey);
|
||||
if (port_info.port.empty())
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the size required to hold the device info.
|
||||
DWORD regDataType;
|
||||
DWORD reqSize = 0;
|
||||
SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, nullptr, nullptr, 0, &reqSize);
|
||||
std::vector<wchar_t> hardware_id(reqSize > 1 ? reqSize : 1);
|
||||
// Now store it in a buffer.
|
||||
if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, ®DataType, (BYTE*)hardware_id.data(), reqSize, nullptr))
|
||||
continue;
|
||||
parse_hardware_id(boost::nowide::narrow(hardware_id.data()), port_info);
|
||||
|
||||
// Find the size required to hold the friendly name.
|
||||
reqSize = 0;
|
||||
SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, nullptr, 0, &reqSize);
|
||||
std::vector<wchar_t> friendly_name;
|
||||
friendly_name.reserve(reqSize > 1 ? reqSize : 1);
|
||||
// Now store it in a buffer.
|
||||
if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, (BYTE*)friendly_name.data(), reqSize, nullptr)) {
|
||||
port_info.friendly_name = port_info.port;
|
||||
} else {
|
||||
port_info.friendly_name = boost::nowide::narrow(friendly_name.data());
|
||||
port_info.is_printer = looks_like_printer(port_info.friendly_name);
|
||||
}
|
||||
output.emplace_back(std::move(port_info));
|
||||
}
|
||||
}
|
||||
#elif __APPLE__
|
||||
// inspired by https://sigrok.org/wiki/Libserialport
|
||||
CFMutableDictionaryRef classes = IOServiceMatching(kIOSerialBSDServiceValue);
|
||||
if (classes != 0) {
|
||||
io_iterator_t iter;
|
||||
if (IOServiceGetMatchingServices(kIOMasterPortDefault, classes, &iter) == KERN_SUCCESS) {
|
||||
io_object_t port;
|
||||
while ((port = IOIteratorNext(iter)) != 0) {
|
||||
CFTypeRef cf_property = IORegistryEntryCreateCFProperty(port, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
|
||||
if (cf_property) {
|
||||
char path[PATH_MAX];
|
||||
Boolean result = CFStringGetCString((CFStringRef)cf_property, path, sizeof(path), kCFStringEncodingUTF8);
|
||||
CFRelease(cf_property);
|
||||
if (result) {
|
||||
SerialPortInfo port_info;
|
||||
port_info.port = path;
|
||||
|
||||
// Attempt to read out the device friendly name
|
||||
if ((cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
|
||||
CFSTR("USB Interface Name"), kCFAllocatorDefault,
|
||||
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
|
||||
(cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
|
||||
CFSTR("USB Product Name"), kCFAllocatorDefault,
|
||||
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
|
||||
(cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
|
||||
CFSTR("Product Name"), kCFAllocatorDefault,
|
||||
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
|
||||
(cf_property = IORegistryEntryCreateCFProperty(port,
|
||||
CFSTR(kIOTTYDeviceKey), kCFAllocatorDefault, 0))) {
|
||||
// Description limited to 127 char, anything longer would not be user friendly anyway.
|
||||
char description[128];
|
||||
if (CFStringGetCString((CFStringRef)cf_property, description, sizeof(description), kCFStringEncodingUTF8)) {
|
||||
port_info.friendly_name = std::string(description) + " (" + port_info.port + ")";
|
||||
port_info.is_printer = looks_like_printer(port_info.friendly_name);
|
||||
}
|
||||
CFRelease(cf_property);
|
||||
}
|
||||
if (port_info.friendly_name.empty())
|
||||
port_info.friendly_name = port_info.port;
|
||||
|
||||
// Attempt to read out the VID & PID
|
||||
int vid, pid;
|
||||
auto cf_vendor = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idVendor"),
|
||||
kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents);
|
||||
auto cf_product = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idProduct"),
|
||||
kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents);
|
||||
if (cf_vendor && cf_product) {
|
||||
if (CFNumberGetValue((CFNumberRef)cf_vendor, kCFNumberIntType, &vid) &&
|
||||
CFNumberGetValue((CFNumberRef)cf_product, kCFNumberIntType, &pid)) {
|
||||
port_info.id_vendor = vid;
|
||||
port_info.id_product = pid;
|
||||
}
|
||||
}
|
||||
if (cf_vendor) { CFRelease(cf_vendor); }
|
||||
if (cf_product) { CFRelease(cf_product); }
|
||||
|
||||
output.emplace_back(std::move(port_info));
|
||||
}
|
||||
}
|
||||
IOObjectRelease(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
// UNIX / Linux
|
||||
std::initializer_list<const char*> prefixes { "ttyUSB" , "ttyACM", "tty.", "cu.", "rfcomm" };
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(boost::filesystem::path("/dev"))) {
|
||||
std::string name = dir_entry.path().filename().string();
|
||||
for (const char *prefix : prefixes) {
|
||||
if (boost::starts_with(name, prefix)) {
|
||||
const auto path = dir_entry.path().string();
|
||||
SerialPortInfo spi;
|
||||
spi.port = path;
|
||||
#ifdef __linux__
|
||||
auto friendly_name = sysfs_tty_prop(name, "product");
|
||||
if (friendly_name) {
|
||||
spi.is_printer = looks_like_printer(*friendly_name);
|
||||
spi.friendly_name = (boost::format("%1% (%2%)") % *friendly_name % path).str();
|
||||
} else {
|
||||
spi.friendly_name = path;
|
||||
}
|
||||
auto vid = sysfs_tty_prop_hex(name, "idVendor");
|
||||
auto pid = sysfs_tty_prop_hex(name, "idProduct");
|
||||
if (vid && pid) {
|
||||
spi.id_vendor = *vid;
|
||||
spi.id_product = *pid;
|
||||
}
|
||||
#else
|
||||
spi.friendly_name = path;
|
||||
#endif
|
||||
output.emplace_back(std::move(spi));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
output.erase(std::remove_if(output.begin(), output.end(),
|
||||
[](const SerialPortInfo &info) {
|
||||
return boost::starts_with(info.port, "Bluetooth") || boost::starts_with(info.port, "FireFly");
|
||||
}),
|
||||
output.end());
|
||||
return output;
|
||||
}
|
||||
|
||||
std::vector<std::string> scan_serial_ports()
|
||||
{
|
||||
std::vector<SerialPortInfo> ports = scan_serial_ports_extended();
|
||||
std::vector<std::string> output;
|
||||
output.reserve(ports.size());
|
||||
for (const SerialPortInfo &spi : ports)
|
||||
output.emplace_back(std::move(spi.port));
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Class Serial
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using boost::system::error_code;
|
||||
|
||||
Serial::Serial(asio::io_service& io_service) :
|
||||
asio::serial_port(io_service)
|
||||
{}
|
||||
|
||||
Serial::Serial(asio::io_service& io_service, const std::string &name, unsigned baud_rate) :
|
||||
asio::serial_port(io_service, name)
|
||||
{
|
||||
set_baud_rate(baud_rate);
|
||||
}
|
||||
|
||||
Serial::~Serial() {}
|
||||
|
||||
void Serial::set_baud_rate(unsigned baud_rate)
|
||||
{
|
||||
try {
|
||||
// This does not support speeds > 115200
|
||||
set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
|
||||
} catch (boost::system::system_error &) {
|
||||
auto handle = native_handle();
|
||||
|
||||
auto handle_errno = [](int retval) {
|
||||
if (retval != 0) {
|
||||
throw Slic3r::RuntimeError(
|
||||
(boost::format("Could not set baud rate: %1%") % strerror(errno)).str()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
#if __APPLE__
|
||||
termios ios;
|
||||
handle_errno(::tcgetattr(handle, &ios));
|
||||
handle_errno(::cfsetspeed(&ios, baud_rate));
|
||||
speed_t newSpeed = baud_rate;
|
||||
handle_errno(::ioctl(handle, IOSSIOSPEED, &newSpeed));
|
||||
handle_errno(::tcsetattr(handle, TCSANOW, &ios));
|
||||
#elif __linux__
|
||||
|
||||
/* The following definitions are kindly borrowed from:
|
||||
/usr/include/asm-generic/termbits.h
|
||||
Unfortunately we cannot just include that one because
|
||||
it would redefine the "struct termios" already defined
|
||||
the <termios.h> already included by Boost.ASIO. */
|
||||
#define K_NCCS 19
|
||||
struct termios2 {
|
||||
tcflag_t c_iflag;
|
||||
tcflag_t c_oflag;
|
||||
tcflag_t c_cflag;
|
||||
tcflag_t c_lflag;
|
||||
cc_t c_line;
|
||||
cc_t c_cc[K_NCCS];
|
||||
speed_t c_ispeed;
|
||||
speed_t c_ospeed;
|
||||
};
|
||||
#define BOTHER CBAUDEX
|
||||
|
||||
termios2 ios;
|
||||
handle_errno(::ioctl(handle, TCGETS2, &ios));
|
||||
ios.c_ispeed = ios.c_ospeed = baud_rate;
|
||||
ios.c_cflag &= ~CBAUD;
|
||||
ios.c_cflag |= BOTHER | CLOCAL | CREAD;
|
||||
ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read
|
||||
ios.c_cc[VTIME] = 1;
|
||||
handle_errno(::ioctl(handle, TCSETS2, &ios));
|
||||
|
||||
#elif __OpenBSD__
|
||||
struct termios ios;
|
||||
handle_errno(::tcgetattr(handle, &ios));
|
||||
handle_errno(::cfsetspeed(&ios, baud_rate));
|
||||
handle_errno(::tcsetattr(handle, TCSAFLUSH, &ios));
|
||||
#else
|
||||
throw Slic3r::RuntimeError("Custom baud rates are not currently supported on this OS");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
void Serial::set_DTR(bool on)
|
||||
{
|
||||
auto handle = native_handle();
|
||||
#if defined(_WIN32) && !defined(__SYMBIAN32__)
|
||||
if (! EscapeCommFunction(handle, on ? SETDTR : CLRDTR)) {
|
||||
throw Slic3r::RuntimeError("Could not set serial port DTR");
|
||||
}
|
||||
#else
|
||||
int status;
|
||||
if (::ioctl(handle, TIOCMGET, &status) == 0) {
|
||||
on ? status |= TIOCM_DTR : status &= ~TIOCM_DTR;
|
||||
if (::ioctl(handle, TIOCMSET, &status) == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw Slic3r::RuntimeError(
|
||||
(boost::format("Could not set serial port DTR: %1%") % strerror(errno)).str()
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Serial::reset_line_num()
|
||||
{
|
||||
// See https://github.com/MarlinFirmware/Marlin/wiki/M110
|
||||
write_string("M110 N0\n");
|
||||
m_line_num = 0;
|
||||
}
|
||||
|
||||
bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec)
|
||||
{
|
||||
auto& io_service =
|
||||
#if BOOST_VERSION >= 107000
|
||||
//FIXME this is most certainly wrong!
|
||||
(boost::asio::io_context&)this->get_executor().context();
|
||||
#else
|
||||
this->get_io_service();
|
||||
#endif
|
||||
asio::deadline_timer timer(io_service);
|
||||
char c = 0;
|
||||
bool fail = false;
|
||||
|
||||
while (true) {
|
||||
io_service.reset();
|
||||
|
||||
asio::async_read(*this, boost::asio::buffer(&c, 1), [&](const error_code &read_ec, size_t size) {
|
||||
if (ec || size == 0) {
|
||||
fail = true;
|
||||
ec = read_ec; // FIXME: only if operation not aborted
|
||||
}
|
||||
timer.cancel(); // FIXME: ditto
|
||||
});
|
||||
|
||||
if (timeout > 0) {
|
||||
timer.expires_from_now(boost::posix_time::milliseconds(timeout));
|
||||
timer.async_wait([&](const error_code &ec) {
|
||||
// Ignore timer aborts
|
||||
if (!ec) {
|
||||
fail = true;
|
||||
this->cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
io_service.run();
|
||||
|
||||
if (fail) {
|
||||
return false;
|
||||
} else if (c != '\n') {
|
||||
line += c;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Serial::printer_setup()
|
||||
{
|
||||
printer_reset();
|
||||
write_string("\r\r\r\r\r\r\r\r\r\r"); // Gets rid of line noise, if any
|
||||
}
|
||||
|
||||
size_t Serial::write_string(const std::string &str)
|
||||
{
|
||||
// TODO: might be wise to timeout here as well
|
||||
return asio::write(*this, asio::buffer(str));
|
||||
}
|
||||
|
||||
bool Serial::printer_ready_wait(unsigned retries, unsigned timeout)
|
||||
{
|
||||
std::string line;
|
||||
error_code ec;
|
||||
|
||||
for (; retries > 0; retries--) {
|
||||
reset_line_num();
|
||||
|
||||
while (read_line(timeout, line, ec)) {
|
||||
if (line == "ok") {
|
||||
return true;
|
||||
}
|
||||
line.clear();
|
||||
}
|
||||
|
||||
line.clear();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t Serial::printer_write_line(const std::string &line, unsigned line_num)
|
||||
{
|
||||
const auto formatted_line = Utils::Serial::printer_format_line(line, line_num);
|
||||
return write_string(formatted_line);
|
||||
}
|
||||
|
||||
size_t Serial::printer_write_line(const std::string &line)
|
||||
{
|
||||
m_line_num++;
|
||||
return printer_write_line(line, m_line_num);
|
||||
}
|
||||
|
||||
void Serial::printer_reset()
|
||||
{
|
||||
this->set_DTR(false);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
this->set_DTR(true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
this->set_DTR(false);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
}
|
||||
|
||||
std::string Serial::printer_format_line(const std::string &line, unsigned line_num)
|
||||
{
|
||||
const auto line_num_str = std::to_string(line_num);
|
||||
|
||||
unsigned checksum = 'N';
|
||||
for (auto c : line_num_str) { checksum ^= c; }
|
||||
checksum ^= ' ';
|
||||
for (auto c : line) { checksum ^= c; }
|
||||
|
||||
return (boost::format("N%1% %2%*%3%\n") % line_num_str % line % checksum).str();
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
} // namespace Utils
|
||||
} // namespace Slic3r
|
||||
97
src/slic3r/Utils/Serial.hpp
Normal file
97
src/slic3r/Utils/Serial.hpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#ifndef slic3r_GUI_Utils_Serial_hpp_
|
||||
#define slic3r_GUI_Utils_Serial_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
struct SerialPortInfo {
|
||||
std::string port;
|
||||
unsigned id_vendor = -1;
|
||||
unsigned id_product = -1;
|
||||
std::string friendly_name;
|
||||
bool is_printer = false;
|
||||
|
||||
SerialPortInfo() {}
|
||||
SerialPortInfo(std::string port) : port(port), friendly_name(std::move(port)) {}
|
||||
|
||||
bool id_match(unsigned id_vendor, unsigned id_product) const { return id_vendor == this->id_vendor && id_product == this->id_product; }
|
||||
};
|
||||
|
||||
inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2)
|
||||
{
|
||||
return
|
||||
sp1.port == sp2.port &&
|
||||
sp1.id_vendor == sp2.id_vendor &&
|
||||
sp1.id_product == sp2.id_product &&
|
||||
sp1.is_printer == sp2.is_printer;
|
||||
}
|
||||
|
||||
extern std::vector<std::string> scan_serial_ports();
|
||||
extern std::vector<SerialPortInfo> scan_serial_ports_extended();
|
||||
|
||||
|
||||
class Serial : public boost::asio::serial_port
|
||||
{
|
||||
public:
|
||||
Serial(boost::asio::io_service &io_service);
|
||||
Serial(boost::asio::io_service &io_service, const std::string &name, unsigned baud_rate);
|
||||
Serial(const Serial &) = delete;
|
||||
Serial &operator=(const Serial &) = delete;
|
||||
~Serial();
|
||||
|
||||
void set_baud_rate(unsigned baud_rate);
|
||||
|
||||
// The Serial implementation is currently in disarray and therefore commented out.
|
||||
// The boost implementation seems to have several problems, such as lack of support
|
||||
// for custom baud rates, few weird implementation bugs and a history of API breakages.
|
||||
// It's questionable whether it solves more problems than causes. Probably not.
|
||||
// TODO: Custom implementation not based on asio.
|
||||
//
|
||||
// As of now, this class is only kept for the purpose of rebooting AVR109,
|
||||
// see FirmwareDialog::priv::avr109_reboot()
|
||||
|
||||
/*
|
||||
void set_DTR(bool on);
|
||||
|
||||
// Resets the line number both internally as well as with the firmware using M110
|
||||
void reset_line_num();
|
||||
|
||||
// Reads a line or times out, the timeout is in milliseconds
|
||||
bool read_line(unsigned timeout, std::string &line, boost::system::error_code &ec);
|
||||
|
||||
// Perform an initial setup for communicating with a printer
|
||||
void printer_setup();
|
||||
|
||||
// Write data from a string
|
||||
size_t write_string(const std::string &str);
|
||||
|
||||
// Attempts to reset the line numer and waits until the printer says "ok"
|
||||
bool printer_ready_wait(unsigned retries, unsigned timeout);
|
||||
|
||||
// Write Marlin-formatted line, with a line number and a checksum
|
||||
size_t printer_write_line(const std::string &line, unsigned line_num);
|
||||
|
||||
// Same as above, but with internally-managed line number
|
||||
size_t printer_write_line(const std::string &line);
|
||||
|
||||
// Toggles DTR to reset the printer
|
||||
void printer_reset();
|
||||
|
||||
// Formats a line Marlin-style, ie. with a sequential number and a checksum
|
||||
static std::string printer_format_line(const std::string &line, unsigned line_num);
|
||||
private:
|
||||
unsigned m_line_num = 0;
|
||||
*/
|
||||
};
|
||||
|
||||
|
||||
} // Utils
|
||||
} // Slic3r
|
||||
|
||||
#endif /* slic3r_GUI_Utils_Serial_hpp_ */
|
||||
205
src/slic3r/Utils/TCPConsole.cpp
Normal file
205
src/slic3r/Utils/TCPConsole.cpp
Normal file
@@ -0,0 +1,205 @@
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/read_until.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/bind/bind.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "TCPConsole.hpp"
|
||||
|
||||
using boost::asio::steady_timer;
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
void TCPConsole::transmit_next_command()
|
||||
{
|
||||
if (m_cmd_queue.empty()) {
|
||||
m_io_context.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string cmd = m_cmd_queue.front();
|
||||
m_cmd_queue.pop_front();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("TCPConsole: transmitting '%3%' to %1%:%2%")
|
||||
% m_host_name
|
||||
% m_port_name
|
||||
% cmd;
|
||||
|
||||
m_send_buffer = cmd + m_newline;
|
||||
|
||||
set_deadline_in(m_write_timeout);
|
||||
boost::asio::async_write(
|
||||
m_socket,
|
||||
boost::asio::buffer(m_send_buffer),
|
||||
boost::bind(&TCPConsole::handle_write, this, boost::placeholders::_1, boost::placeholders::_2)
|
||||
);
|
||||
}
|
||||
|
||||
void TCPConsole::wait_next_line()
|
||||
{
|
||||
set_deadline_in(m_read_timeout);
|
||||
boost::asio::async_read_until(
|
||||
m_socket,
|
||||
m_recv_buffer,
|
||||
m_newline,
|
||||
boost::bind(&TCPConsole::handle_read, this, boost::placeholders::_1, boost::placeholders::_2)
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Use std::optional here
|
||||
std::string TCPConsole::extract_next_line()
|
||||
{
|
||||
char linebuf[1024];
|
||||
std::istream is(&m_recv_buffer);
|
||||
is.getline(linebuf, sizeof(linebuf));
|
||||
return is.good() ? linebuf : std::string{};
|
||||
}
|
||||
|
||||
void TCPConsole::handle_read(
|
||||
const boost::system::error_code& ec,
|
||||
std::size_t bytes_transferred)
|
||||
{
|
||||
m_error_code = ec;
|
||||
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("TCPConsole: Can't read from %1%:%2%: %3%")
|
||||
% m_host_name
|
||||
% m_port_name
|
||||
% ec.message();
|
||||
|
||||
m_io_context.stop();
|
||||
}
|
||||
else {
|
||||
std::string line = extract_next_line();
|
||||
boost::trim(line);
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("TCPConsole: received '%3%' from %1%:%2%")
|
||||
% m_host_name
|
||||
% m_port_name
|
||||
% line;
|
||||
|
||||
boost::to_lower(line);
|
||||
|
||||
if (line == m_done_string)
|
||||
transmit_next_command();
|
||||
else
|
||||
wait_next_line();
|
||||
}
|
||||
}
|
||||
|
||||
void TCPConsole::handle_write(
|
||||
const boost::system::error_code& ec,
|
||||
std::size_t)
|
||||
{
|
||||
m_error_code = ec;
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("TCPConsole: Can't write to %1%:%2%: %3%")
|
||||
% m_host_name
|
||||
% m_port_name
|
||||
% ec.message();
|
||||
|
||||
m_io_context.stop();
|
||||
}
|
||||
else {
|
||||
wait_next_line();
|
||||
}
|
||||
}
|
||||
|
||||
void TCPConsole::handle_connect(const boost::system::error_code& ec)
|
||||
{
|
||||
m_error_code = ec;
|
||||
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("TCPConsole: Can't connect to %1%:%2%: %3%")
|
||||
% m_host_name
|
||||
% m_port_name
|
||||
% ec.message();
|
||||
|
||||
m_io_context.stop();
|
||||
}
|
||||
else {
|
||||
m_is_connected = true;
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("TCPConsole: connected to %1%:%2%")
|
||||
% m_host_name
|
||||
% m_port_name;
|
||||
|
||||
transmit_next_command();
|
||||
}
|
||||
}
|
||||
|
||||
void TCPConsole::set_deadline_in(std::chrono::steady_clock::duration d)
|
||||
{
|
||||
m_deadline = std::chrono::steady_clock::now() + d;
|
||||
}
|
||||
bool TCPConsole::is_deadline_over() const
|
||||
{
|
||||
return m_deadline < std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
bool TCPConsole::run_queue()
|
||||
{
|
||||
try {
|
||||
// TODO: Add more resets and initializations after previous run (reset() method?..)
|
||||
set_deadline_in(m_connect_timeout);
|
||||
m_is_connected = false;
|
||||
m_io_context.restart();
|
||||
|
||||
auto endpoints = m_resolver.resolve(m_host_name, m_port_name);
|
||||
|
||||
m_socket.async_connect(endpoints->endpoint(),
|
||||
boost::bind(&TCPConsole::handle_connect, this, boost::placeholders::_1)
|
||||
);
|
||||
|
||||
// Loop until we get any reasonable result. Negative result is also result.
|
||||
// TODO: Rewrite to more graceful way using deadlime_timer
|
||||
bool timeout = false;
|
||||
while (!(timeout = is_deadline_over()) && !m_io_context.stopped()) {
|
||||
if (m_error_code) {
|
||||
m_io_context.stop();
|
||||
}
|
||||
m_io_context.run_for(boost::asio::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
// Override error message if timeout is set
|
||||
if (timeout)
|
||||
m_error_code = make_error_code(boost::asio::error::timed_out);
|
||||
|
||||
// Socket is not closed automatically by boost
|
||||
m_socket.close();
|
||||
|
||||
if (m_error_code) {
|
||||
// We expect that message is logged in handler
|
||||
return false;
|
||||
}
|
||||
|
||||
// It's expected to have empty queue after successful exchange
|
||||
if (!m_cmd_queue.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "TCPConsole: command queue is not empty after end of exchange";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("TCPConsole: Exception while talking with %1%:%2%: %3%")
|
||||
% m_host_name
|
||||
% m_port_name
|
||||
% e.what();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Utils
|
||||
} // namespace Slic3r
|
||||
93
src/slic3r/Utils/TCPConsole.hpp
Normal file
93
src/slic3r/Utils/TCPConsole.hpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#ifndef slic3r_Utils_TCPConsole_hpp_
|
||||
#define slic3r_Utils_TCPConsole_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/streambuf.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
// Generic command / response TCP telnet like console class.
|
||||
// Used by the MKS host to send G-code commands to test connection ("M105") and to start printing ("M23 filename", "M24").
|
||||
class TCPConsole
|
||||
{
|
||||
public:
|
||||
TCPConsole() : m_resolver(m_io_context), m_socket(m_io_context) { set_defaults(); }
|
||||
TCPConsole(const std::string& host_name, const std::string& port_name) : m_resolver(m_io_context), m_socket(m_io_context)
|
||||
{ set_defaults(); set_remote(host_name, port_name); }
|
||||
~TCPConsole() = default;
|
||||
|
||||
void set_defaults()
|
||||
{
|
||||
m_newline = "\n";
|
||||
m_done_string = "ok";
|
||||
m_connect_timeout = std::chrono::milliseconds(5000);
|
||||
m_write_timeout = std::chrono::milliseconds(10000);
|
||||
m_read_timeout = std::chrono::milliseconds(10000);
|
||||
}
|
||||
|
||||
void set_line_delimiter(const std::string& newline) {
|
||||
m_newline = newline;
|
||||
}
|
||||
void set_command_done_string(const std::string& done_string) {
|
||||
m_done_string = done_string;
|
||||
}
|
||||
|
||||
void set_remote(const std::string& host_name, const std::string& port_name)
|
||||
{
|
||||
m_host_name = host_name;
|
||||
m_port_name = port_name;
|
||||
}
|
||||
|
||||
bool enqueue_cmd(const std::string& cmd) {
|
||||
// TODO: Add multithread protection to queue
|
||||
m_cmd_queue.push_back(cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool run_queue();
|
||||
std::string error_message() const { return m_error_code.message(); }
|
||||
|
||||
private:
|
||||
void handle_connect(const boost::system::error_code& ec);
|
||||
void handle_read(const boost::system::error_code& ec, std::size_t bytes_transferred);
|
||||
void handle_write(const boost::system::error_code& ec, std::size_t bytes_transferred);
|
||||
|
||||
void transmit_next_command();
|
||||
void wait_next_line();
|
||||
std::string extract_next_line();
|
||||
|
||||
void set_deadline_in(std::chrono::steady_clock::duration);
|
||||
bool is_deadline_over() const;
|
||||
|
||||
std::string m_host_name;
|
||||
std::string m_port_name;
|
||||
std::string m_newline;
|
||||
std::string m_done_string;
|
||||
std::chrono::steady_clock::duration m_connect_timeout;
|
||||
std::chrono::steady_clock::duration m_write_timeout;
|
||||
std::chrono::steady_clock::duration m_read_timeout;
|
||||
|
||||
std::deque<std::string> m_cmd_queue;
|
||||
|
||||
boost::asio::io_context m_io_context;
|
||||
tcp::resolver m_resolver;
|
||||
tcp::socket m_socket;
|
||||
boost::asio::streambuf m_recv_buffer;
|
||||
std::string m_send_buffer;
|
||||
|
||||
bool m_is_connected;
|
||||
boost::system::error_code m_error_code;
|
||||
std::chrono::steady_clock::time_point m_deadline;
|
||||
};
|
||||
|
||||
} // Utils
|
||||
} // Slic3r
|
||||
|
||||
#endif
|
||||
1271
src/slic3r/Utils/UndoRedo.cpp
Normal file
1271
src/slic3r/Utils/UndoRedo.cpp
Normal file
File diff suppressed because it is too large
Load Diff
173
src/slic3r/Utils/UndoRedo.hpp
Normal file
173
src/slic3r/Utils/UndoRedo.hpp
Normal file
@@ -0,0 +1,173 @@
|
||||
#ifndef slic3r_Utils_UndoRedo_hpp_
|
||||
#define slic3r_Utils_UndoRedo_hpp_
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
#include <libslic3r/ObjectID.hpp>
|
||||
#include <libslic3r/Config.hpp>
|
||||
|
||||
typedef double coordf_t;
|
||||
typedef std::pair<coordf_t, coordf_t> t_layer_height_range;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
|
||||
namespace GUI {
|
||||
class Selection;
|
||||
class GLGizmosManager;
|
||||
} // namespace GUI
|
||||
|
||||
namespace UndoRedo {
|
||||
|
||||
enum class SnapshotType : unsigned char {
|
||||
// Some action modifying project state, outside any EnteringGizmo / LeavingGizmo interval.
|
||||
Action,
|
||||
// Some action modifying project state, inside some EnteringGizmo / LeavingGizmo interval.
|
||||
GizmoAction,
|
||||
// Selection change at the Plater.
|
||||
Selection,
|
||||
// New project, Reset project, Load project ...
|
||||
ProjectSeparator,
|
||||
// Entering a Gizmo, which opens a secondary Undo / Redo stack.
|
||||
EnteringGizmo,
|
||||
// Leaving a Gizmo, which closes a secondary Undo / Redo stack.
|
||||
// No action modifying a project state was done between EnteringGizmo / LeavingGizmo.
|
||||
LeavingGizmoNoAction,
|
||||
// Leaving a Gizmo, which closes a secondary Undo / Redo stack.
|
||||
// Some action modifying a project state was done between EnteringGizmo / LeavingGizmo.
|
||||
LeavingGizmoWithAction,
|
||||
};
|
||||
|
||||
// Data structure to be stored with each snapshot.
|
||||
// Storing short data (bit masks, ints) with each snapshot instead of being serialized into the Undo / Redo stack
|
||||
// is likely cheaper in term of both the runtime and memory allocation.
|
||||
// Also the SnapshotData is available without having to deserialize the snapshot from the Undo / Redo stack,
|
||||
// which may be handy sometimes.
|
||||
struct SnapshotData
|
||||
{
|
||||
SnapshotType snapshot_type;
|
||||
PrinterTechnology printer_technology { ptUnknown };
|
||||
// Bitmap of Flags (see the Flags enum).
|
||||
unsigned int flags { 0 };
|
||||
int layer_range_idx { -1 };
|
||||
|
||||
// Bitmask of various binary flags to be stored with the snapshot.
|
||||
enum Flags {
|
||||
VARIABLE_LAYER_EDITING_ACTIVE = 1,
|
||||
SELECTED_SETTINGS_ON_SIDEBAR = 2,
|
||||
SELECTED_LAYERROOT_ON_SIDEBAR = 4,
|
||||
SELECTED_LAYER_ON_SIDEBAR = 8,
|
||||
RECALCULATE_SLA_SUPPORTS = 16
|
||||
};
|
||||
};
|
||||
|
||||
struct Snapshot
|
||||
{
|
||||
Snapshot(size_t timestamp) : timestamp(timestamp) {}
|
||||
Snapshot(const std::string &name, size_t timestamp, size_t model_id, const SnapshotData &snapshot_data) :
|
||||
name(name), timestamp(timestamp), model_id(model_id), snapshot_data(snapshot_data) {}
|
||||
|
||||
std::string name;
|
||||
size_t timestamp;
|
||||
size_t model_id;
|
||||
SnapshotData snapshot_data;
|
||||
|
||||
bool operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; }
|
||||
bool operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; }
|
||||
|
||||
// The topmost snapshot represents the current state when going forward.
|
||||
bool is_topmost() const;
|
||||
// The topmost snapshot is not being serialized to the Undo / Redo stack until going back in time,
|
||||
// when the top most state is being serialized, so we can redo back to the top most state.
|
||||
bool is_topmost_captured() const { assert(this->is_topmost()); return model_id > 0; }
|
||||
};
|
||||
|
||||
// Excerpt of Slic3r::GUI::Selection for serialization onto the Undo / Redo stack.
|
||||
struct Selection : public Slic3r::ObjectBase {
|
||||
void clear() { mode = 0; volumes_and_instances.clear(); }
|
||||
unsigned char mode = 0;
|
||||
std::vector<std::pair<size_t, size_t>> volumes_and_instances;
|
||||
template<class Archive> void serialize(Archive &ar) { ar(mode, volumes_and_instances); }
|
||||
};
|
||||
|
||||
class StackImpl;
|
||||
|
||||
class Stack
|
||||
{
|
||||
public:
|
||||
// Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning.
|
||||
// The first "New Project" snapshot shall not be removed.
|
||||
Stack();
|
||||
~Stack();
|
||||
|
||||
void clear();
|
||||
bool empty() const;
|
||||
|
||||
// Set maximum memory threshold. If the threshold is exceeded, least recently used snapshots are released.
|
||||
void set_memory_limit(size_t memsize);
|
||||
size_t get_memory_limit() const;
|
||||
|
||||
// Estimate size of the RAM consumed by the Undo / Redo stack.
|
||||
size_t memsize() const;
|
||||
|
||||
// Release least recently used snapshots up to the memory limit set above.
|
||||
void release_least_recently_used();
|
||||
|
||||
// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
|
||||
void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data);
|
||||
// To be called just after take_snapshot() when leaving a gizmo, inside which small edits like support point add / remove events or paiting actions were allowed.
|
||||
// Remove all but the last edit between the gizmo enter / leave snapshots.
|
||||
void reduce_noisy_snapshots(const std::string& new_name);
|
||||
|
||||
// To be queried to enable / disable the Undo / Redo buttons at the UI.
|
||||
bool has_undo_snapshot() const;
|
||||
bool has_redo_snapshot() const;
|
||||
// To query whether one can undo to a snapshot. Useful for notifications, that want to Undo a specific operation.
|
||||
bool has_undo_snapshot(size_t time_to_load) const;
|
||||
|
||||
// Roll back the time. If time_to_load is SIZE_MAX, the previous snapshot is activated.
|
||||
// Undoing an action may need to take a snapshot of the current application state, so that redo to the current state is possible.
|
||||
bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data, size_t time_to_load = SIZE_MAX);
|
||||
|
||||
// Jump forward in time. If time_to_load is SIZE_MAX, the next snapshot is activated.
|
||||
bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX);
|
||||
|
||||
// Snapshot history (names with timestamps).
|
||||
// Each snapshot indicates start of an interval in which this operation is performed.
|
||||
// There is one additional snapshot taken at the very end, which indicates the current unnamed state.
|
||||
|
||||
const std::vector<Snapshot>& snapshots() const;
|
||||
const Snapshot& snapshot(size_t time) const;
|
||||
|
||||
// Timestamp of the active snapshot. One of the snapshots of this->snapshots() shall have Snapshot::timestamp equal to this->active_snapshot_time().
|
||||
// The active snapshot may be a special placeholder "@@@ Topmost @@@" indicating an uncaptured current state,
|
||||
// or the active snapshot may be an active state to which the application state was undoed or redoed.
|
||||
size_t active_snapshot_time() const;
|
||||
const Snapshot& active_snapshot() const { return this->snapshot(this->active_snapshot_time()); }
|
||||
// Temporary snapshot is active if the topmost snapshot is active and it has not been captured yet.
|
||||
// In that case the Undo action will capture the last snapshot.
|
||||
bool temp_snapshot_active() const;
|
||||
|
||||
// Resets the "dirty project" status.
|
||||
void mark_current_as_saved();
|
||||
// Is the project modified with regard to the last "saved" state marked with mark_current_as_saved()?
|
||||
bool project_modified() const;
|
||||
|
||||
// After load_snapshot() / undo() / redo() the selection is deserialized into a list of ObjectIDs, which needs to be converted
|
||||
// into the list of GLVolume pointers once the 3D scene is updated.
|
||||
const Selection& selection_deserialized() const;
|
||||
|
||||
private:
|
||||
friend class StackImpl;
|
||||
std::unique_ptr<StackImpl> pimpl;
|
||||
};
|
||||
|
||||
}; // namespace UndoRedo
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Utils_UndoRedo_hpp_ */
|
||||
490
src/slic3r/Utils/WinRegistry.cpp
Normal file
490
src/slic3r/Utils/WinRegistry.cpp
Normal 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
|
||||
23
src/slic3r/Utils/WinRegistry.hpp
Normal file
23
src/slic3r/Utils/WinRegistry.hpp
Normal 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_
|
||||
351
src/slic3r/Utils/WxFontUtils.cpp
Normal file
351
src/slic3r/Utils/WxFontUtils.cpp
Normal file
@@ -0,0 +1,351 @@
|
||||
#include "WxFontUtils.hpp"
|
||||
#include <boost/assign.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <CoreText/CTFont.h>
|
||||
#include <wx/uri.h>
|
||||
#include <wx/fontutil.h> // wxNativeFontInfo
|
||||
#include <wx/osx/core/cfdictionary.h>
|
||||
#elif defined(__linux__)
|
||||
#include "slic3r/Utils/FontConfigHelp.hpp"
|
||||
#endif
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
#ifdef __APPLE__
|
||||
namespace {
|
||||
bool is_valid_ttf(std::string_view file_path)
|
||||
{
|
||||
if (file_path.empty()) return false;
|
||||
auto const pos_point = file_path.find_last_of('.');
|
||||
if (pos_point == std::string_view::npos) return false;
|
||||
|
||||
// use point only after last directory delimiter
|
||||
auto const pos_directory_delimiter = file_path.find_last_of("/\\");
|
||||
if (pos_directory_delimiter != std::string_view::npos &&
|
||||
pos_point < pos_directory_delimiter)
|
||||
return false; // point is before directory delimiter
|
||||
|
||||
// check count of extension chars
|
||||
size_t extension_size = file_path.size() - pos_point;
|
||||
if (extension_size >= 5) return false; // a lot of symbols for extension
|
||||
if (extension_size <= 1) return false; // few letters for extension
|
||||
|
||||
std::string_view extension = file_path.substr(pos_point + 1, extension_size);
|
||||
|
||||
// Because of MacOs - Courier, Geneva, Monaco
|
||||
if (extension == std::string_view("dfont")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// get filepath from wxFont on Mac OsX
|
||||
std::string get_file_path(const wxFont& font) {
|
||||
const wxNativeFontInfo *info = font.GetNativeFontInfo();
|
||||
if (info == nullptr) return {};
|
||||
CTFontDescriptorRef descriptor = info->GetCTFontDescriptor();
|
||||
CFURLRef typeref = (CFURLRef)CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute);
|
||||
if (typeref == NULL) return {};
|
||||
ScopeGuard sg([&typeref]() { CFRelease(typeref); });
|
||||
CFStringRef url = CFURLGetString(typeref);
|
||||
if (url == NULL) return {};
|
||||
wxString file_uri(wxCFStringRef::AsString(url));
|
||||
wxURI uri(file_uri);
|
||||
const wxString &path = uri.GetPath();
|
||||
wxString path_unescaped = wxURI::Unescape(path);
|
||||
std::string path_str = path_unescaped.ToUTF8().data();
|
||||
BOOST_LOG_TRIVIAL(trace) << "input uri(" << file_uri.c_str() << ") convert to path(" << path.c_str() << ") string(" << path_str << ").";
|
||||
return path_str;
|
||||
}
|
||||
} // namespace
|
||||
#endif // __APPLE__
|
||||
|
||||
bool WxFontUtils::can_load(const wxFont &font)
|
||||
{
|
||||
|
||||
if (!font.IsOk()) return false;
|
||||
#ifdef _WIN32
|
||||
return Emboss::can_load(font.GetHFONT()) != nullptr;
|
||||
#elif defined(__APPLE__)
|
||||
return true;
|
||||
//return is_valid_ttf(get_file_path(font));
|
||||
#elif defined(__linux__)
|
||||
return true;
|
||||
// font config check file path take about 4000ms for chech them all
|
||||
//std::string font_path = Slic3r::GUI::get_font_path(font);
|
||||
//return !font_path.empty();
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<Emboss::FontFile> WxFontUtils::create_font_file(const wxFont &font)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return Emboss::create_font_file(font.GetHFONT());
|
||||
#elif defined(__APPLE__)
|
||||
std::string file_path = get_file_path(font);
|
||||
if (!is_valid_ttf(file_path)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Can not process font('" << get_human_readable_name(font) << "'), "
|
||||
<< "file in path('" << file_path << "') is not valid TTF.";
|
||||
return nullptr;
|
||||
}
|
||||
return Emboss::create_font_file(file_path.c_str());
|
||||
#elif defined(__linux__)
|
||||
std::string font_path = Slic3r::GUI::get_font_path(font);
|
||||
if (font_path.empty()){
|
||||
BOOST_LOG_TRIVIAL(error) << "Can not read font('" << get_human_readable_name(font) << "'), "
|
||||
<< "file path is empty.";
|
||||
return nullptr;
|
||||
}
|
||||
return Emboss::create_font_file(font_path.c_str());
|
||||
#else
|
||||
// HERE is place to add implementation for another platform
|
||||
// to convert wxFont to font data as windows or font file path as linux
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
EmbossStyle::Type WxFontUtils::get_actual_type()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return EmbossStyle::Type::wx_win_font_descr;
|
||||
#elif defined(__APPLE__)
|
||||
return EmbossStyle::Type::wx_mac_font_descr;
|
||||
#elif defined(__linux__)
|
||||
return EmbossStyle::Type::wx_lin_font_descr;
|
||||
#else
|
||||
return EmbossStyle::Type::undefined;
|
||||
#endif
|
||||
}
|
||||
|
||||
EmbossStyle WxFontUtils::create_emboss_style(const wxFont &font, const std::string& name)
|
||||
{
|
||||
std::string name_item = name.empty()? get_human_readable_name(font) : name;
|
||||
std::string fontDesc = store_wxFont(font);
|
||||
EmbossStyle::Type type = get_actual_type();
|
||||
|
||||
// synchronize font property with actual font
|
||||
FontProp font_prop;
|
||||
|
||||
// The point size is defined as 1/72 of the Anglo-Saxon inch (25.4 mm): it
|
||||
// is approximately 0.0139 inch or 352.8 um. But it is too small, so I
|
||||
// decide use point size as mm for emboss
|
||||
font_prop.size_in_mm = font.GetPointSize(); // *0.3528f;
|
||||
font_prop.emboss = font_prop.size_in_mm / 2.f;
|
||||
|
||||
WxFontUtils::update_property(font_prop, font);
|
||||
return { name_item, fontDesc, type, font_prop };
|
||||
}
|
||||
|
||||
// NOT working on linux GTK2
|
||||
// load font used by Operating system as default GUI
|
||||
//EmbossStyle WxFontUtils::get_os_font()
|
||||
//{
|
||||
// wxSystemFont system_font = wxSYS_DEFAULT_GUI_FONT;
|
||||
// wxFont font = wxSystemSettings::GetFont(system_font);
|
||||
// EmbossStyle es = create_emboss_style(font);
|
||||
// es.name += std::string(" (OS default)");
|
||||
// return es;
|
||||
//}
|
||||
|
||||
std::string WxFontUtils::get_human_readable_name(const wxFont &font)
|
||||
{
|
||||
if (!font.IsOk()) return "Font is NOT ok.";
|
||||
// Face name is optional in wxFont
|
||||
if (!font.GetFaceName().empty()) {
|
||||
return std::string(font.GetFaceName().c_str());
|
||||
} else {
|
||||
return std::string((font.GetFamilyString() + " " +
|
||||
font.GetStyleString() + " " +
|
||||
font.GetWeightString())
|
||||
.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::string WxFontUtils::store_wxFont(const wxFont &font)
|
||||
{
|
||||
// wxString os = wxPlatformInfo::Get().GetOperatingSystemIdName();
|
||||
wxString font_descriptor = font.GetNativeFontInfoDesc();
|
||||
BOOST_LOG_TRIVIAL(trace) << "'" << font_descriptor << "' wx string get from GetNativeFontInfoDesc. wxFont " <<
|
||||
"IsOk(" << font.IsOk() << "), " <<
|
||||
"isNull(" << font.IsNull() << ")" <<
|
||||
// "IsFree(" << font.IsFree() << "), " << // on MacOs is no function is free
|
||||
"IsFixedWidth(" << font.IsFixedWidth() << "), " <<
|
||||
"IsUsingSizeInPixels(" << font.IsUsingSizeInPixels() << "), " <<
|
||||
"Encoding(" << (int)font.GetEncoding() << "), " ;
|
||||
return std::string(font_descriptor.c_str());
|
||||
}
|
||||
|
||||
wxFont WxFontUtils::load_wxFont(const std::string &font_descriptor)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "'" << font_descriptor << "'font descriptor string param of load_wxFont()";
|
||||
wxString font_descriptor_wx(font_descriptor);
|
||||
BOOST_LOG_TRIVIAL(trace) << "'" << font_descriptor_wx.c_str() << "' wx string descriptor";
|
||||
wxFont wx_font(font_descriptor_wx);
|
||||
BOOST_LOG_TRIVIAL(trace) << "loaded font is '" << get_human_readable_name(wx_font) << "'.";
|
||||
return wx_font;
|
||||
}
|
||||
|
||||
using TypeToFamily = boost::bimap<wxFontFamily, std::string_view>;
|
||||
const TypeToFamily WxFontUtils::type_to_family =
|
||||
boost::assign::list_of<TypeToFamily::relation>
|
||||
(wxFONTFAMILY_DEFAULT, "default")
|
||||
(wxFONTFAMILY_DECORATIVE, "decorative")
|
||||
(wxFONTFAMILY_ROMAN, "roman")
|
||||
(wxFONTFAMILY_SCRIPT, "script")
|
||||
(wxFONTFAMILY_SWISS, "swiss")
|
||||
(wxFONTFAMILY_MODERN, "modern")
|
||||
(wxFONTFAMILY_TELETYPE, "teletype");
|
||||
|
||||
using TypeToStyle = boost::bimap<wxFontStyle, std::string_view>;
|
||||
const TypeToStyle WxFontUtils::type_to_style =
|
||||
boost::assign::list_of<TypeToStyle::relation>
|
||||
(wxFONTSTYLE_ITALIC, "italic")
|
||||
(wxFONTSTYLE_SLANT, "slant")
|
||||
(wxFONTSTYLE_NORMAL, "normal");
|
||||
|
||||
using TypeToWeight = boost::bimap<wxFontWeight, std::string_view>;
|
||||
const TypeToWeight WxFontUtils::type_to_weight =
|
||||
boost::assign::list_of<TypeToWeight::relation>
|
||||
(wxFONTWEIGHT_THIN, "thin")
|
||||
(wxFONTWEIGHT_EXTRALIGHT, "extraLight")
|
||||
(wxFONTWEIGHT_LIGHT, "light")
|
||||
(wxFONTWEIGHT_NORMAL, "normal")
|
||||
(wxFONTWEIGHT_MEDIUM, "medium")
|
||||
(wxFONTWEIGHT_SEMIBOLD, "semibold")
|
||||
(wxFONTWEIGHT_BOLD, "bold")
|
||||
(wxFONTWEIGHT_EXTRABOLD, "extraBold")
|
||||
(wxFONTWEIGHT_HEAVY, "heavy")
|
||||
(wxFONTWEIGHT_EXTRAHEAVY, "extraHeavy");
|
||||
|
||||
wxFont WxFontUtils::create_wxFont(const EmbossStyle &style)
|
||||
{
|
||||
const FontProp &fp = style.prop;
|
||||
double point_size = static_cast<double>(fp.size_in_mm);
|
||||
wxFontInfo info(point_size);
|
||||
if (fp.family.has_value()) {
|
||||
auto it = type_to_family.right.find(*fp.family);
|
||||
if (it != type_to_family.right.end()) info.Family(it->second);
|
||||
}
|
||||
// Face names are not portable, so prefer to use Family() in portable code.
|
||||
/* if (fp.face_name.has_value()) {
|
||||
wxString face_name(*fp.face_name);
|
||||
info.FaceName(face_name);
|
||||
}*/
|
||||
if (fp.style.has_value()) {
|
||||
auto it = type_to_style.right.find(*fp.style);
|
||||
if (it != type_to_style.right.end()) info.Style(it->second);
|
||||
}
|
||||
if (fp.weight.has_value()) {
|
||||
auto it = type_to_weight.right.find(*fp.weight);
|
||||
if (it != type_to_weight.right.end()) info.Weight(it->second);
|
||||
}
|
||||
|
||||
// Improve: load descriptor instead of store to font property to 3mf
|
||||
// switch (es.type) {
|
||||
// case EmbossStyle::Type::wx_lin_font_descr:
|
||||
// case EmbossStyle::Type::wx_win_font_descr:
|
||||
// case EmbossStyle::Type::wx_mac_font_descr:
|
||||
// case EmbossStyle::Type::file_path:
|
||||
// case EmbossStyle::Type::undefined:
|
||||
// default:
|
||||
//}
|
||||
|
||||
wxFont wx_font(info);
|
||||
// Check if exist font file
|
||||
std::unique_ptr<Emboss::FontFile> ff = create_font_file(wx_font);
|
||||
if (ff == nullptr) return {};
|
||||
|
||||
return wx_font;
|
||||
}
|
||||
|
||||
void WxFontUtils::update_property(FontProp &font_prop, const wxFont &font)
|
||||
{
|
||||
wxString wx_face_name = font.GetFaceName();
|
||||
std::string face_name((const char *) wx_face_name.ToUTF8());
|
||||
if (!face_name.empty()) font_prop.face_name = face_name;
|
||||
|
||||
wxFontFamily wx_family = font.GetFamily();
|
||||
if (wx_family != wxFONTFAMILY_DEFAULT) {
|
||||
auto it = type_to_family.left.find(wx_family);
|
||||
if (it != type_to_family.left.end()) font_prop.family = it->second;
|
||||
}
|
||||
|
||||
wxFontStyle wx_style = font.GetStyle();
|
||||
if (wx_style != wxFONTSTYLE_NORMAL) {
|
||||
auto it = type_to_style.left.find(wx_style);
|
||||
if (it != type_to_style.left.end()) font_prop.style = it->second;
|
||||
}
|
||||
|
||||
wxFontWeight wx_weight = font.GetWeight();
|
||||
if (wx_weight != wxFONTWEIGHT_NORMAL) {
|
||||
auto it = type_to_weight.left.find(wx_weight);
|
||||
if (it != type_to_weight.left.end()) font_prop.weight = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
bool WxFontUtils::is_italic(const wxFont &font) {
|
||||
wxFontStyle wx_style = font.GetStyle();
|
||||
return wx_style == wxFONTSTYLE_ITALIC ||
|
||||
wx_style == wxFONTSTYLE_SLANT;
|
||||
}
|
||||
|
||||
bool WxFontUtils::is_bold(const wxFont &font) {
|
||||
wxFontWeight wx_weight = font.GetWeight();
|
||||
return wx_weight != wxFONTWEIGHT_NORMAL;
|
||||
}
|
||||
|
||||
std::unique_ptr<Emboss::FontFile> WxFontUtils::set_italic(wxFont &font, const Emboss::FontFile &font_file)
|
||||
{
|
||||
static std::vector<wxFontStyle> italic_styles = {
|
||||
wxFontStyle::wxFONTSTYLE_ITALIC,
|
||||
wxFontStyle::wxFONTSTYLE_SLANT
|
||||
};
|
||||
wxFontStyle orig_style = font.GetStyle();
|
||||
for (wxFontStyle style : italic_styles) {
|
||||
font.SetStyle(style);
|
||||
std::unique_ptr<Emboss::FontFile> new_font_file =
|
||||
WxFontUtils::create_font_file(font);
|
||||
|
||||
// can create italic font?
|
||||
if (new_font_file == nullptr) continue;
|
||||
|
||||
// is still same font file pointer?
|
||||
if (font_file == *new_font_file) continue;
|
||||
|
||||
return new_font_file;
|
||||
}
|
||||
// There is NO italic font by wx
|
||||
font.SetStyle(orig_style);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Emboss::FontFile> WxFontUtils::set_bold(wxFont &font, const Emboss::FontFile& font_file)
|
||||
{
|
||||
static std::vector<wxFontWeight> bold_weight = {
|
||||
wxFontWeight::wxFONTWEIGHT_BOLD,
|
||||
wxFontWeight::wxFONTWEIGHT_HEAVY,
|
||||
wxFontWeight::wxFONTWEIGHT_EXTRABOLD,
|
||||
wxFontWeight::wxFONTWEIGHT_EXTRAHEAVY
|
||||
};
|
||||
wxFontWeight orig_weight = font.GetWeight();
|
||||
for (wxFontWeight weight : bold_weight) {
|
||||
font.SetWeight(weight);
|
||||
std::unique_ptr<Emboss::FontFile> new_font_file =
|
||||
WxFontUtils::create_font_file(font);
|
||||
|
||||
// can create bold font file?
|
||||
if (new_font_file == nullptr) continue;
|
||||
|
||||
// is still same font file pointer?
|
||||
if (font_file == *new_font_file) continue;
|
||||
|
||||
return new_font_file;
|
||||
}
|
||||
// There is NO bold font by wx
|
||||
font.SetWeight(orig_weight);
|
||||
return nullptr;
|
||||
}
|
||||
71
src/slic3r/Utils/WxFontUtils.hpp
Normal file
71
src/slic3r/Utils/WxFontUtils.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#ifndef slic3r_WxFontUtils_hpp_
|
||||
#define slic3r_WxFontUtils_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <boost/bimap.hpp>
|
||||
#include <wx/font.h>
|
||||
#include "libslic3r/Emboss.hpp"
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
// Help class to work with wx widget font object( wxFont )
|
||||
class WxFontUtils
|
||||
{
|
||||
public:
|
||||
// only static functions
|
||||
WxFontUtils() = delete;
|
||||
|
||||
// check if exist file for wxFont
|
||||
// return pointer on data or nullptr when can't load
|
||||
static bool can_load(const wxFont &font);
|
||||
|
||||
// os specific load of wxFont
|
||||
static std::unique_ptr<Slic3r::Emboss::FontFile> create_font_file(const wxFont &font);
|
||||
|
||||
static EmbossStyle::Type get_actual_type();
|
||||
static EmbossStyle create_emboss_style(const wxFont &font, const std::string& name = "");
|
||||
|
||||
static std::string get_human_readable_name(const wxFont &font);
|
||||
|
||||
// serialize / deserialize font
|
||||
static std::string store_wxFont(const wxFont &font);
|
||||
static wxFont load_wxFont(const std::string &font_descriptor);
|
||||
|
||||
// Try to create similar font, loaded from 3mf from different Computer
|
||||
static wxFont create_wxFont(const EmbossStyle &style);
|
||||
// update font property by wxFont - without emboss depth and font size
|
||||
static void update_property(FontProp &font_prop, const wxFont &font);
|
||||
|
||||
static bool is_italic(const wxFont &font);
|
||||
static bool is_bold(const wxFont &font);
|
||||
|
||||
/// <summary>
|
||||
/// Set italic into wx font
|
||||
/// When italic font is same as original return nullptr.
|
||||
/// To not load font file twice on success is font_file returned.
|
||||
/// </summary>
|
||||
/// <param name="font">wx descriptor of font</param>
|
||||
/// <param name="font_file">file described in wx font</param>
|
||||
/// <returns>New created font fileon success otherwise nullptr</returns>
|
||||
static std::unique_ptr<Slic3r::Emboss::FontFile> set_italic(wxFont &font, const Slic3r::Emboss::FontFile &prev_font_file);
|
||||
|
||||
/// <summary>
|
||||
/// Set boldness into wx font
|
||||
/// When bolded font is same as original return nullptr.
|
||||
/// To not load font file twice on success is font_file returned.
|
||||
/// </summary>
|
||||
/// <param name="font">wx descriptor of font</param>
|
||||
/// <param name="font_file">file described in wx font</param>
|
||||
/// <returns>New created font fileon success otherwise nullptr</returns>
|
||||
static std::unique_ptr<Slic3r::Emboss::FontFile> set_bold(wxFont &font, const Slic3r::Emboss::FontFile &font_file);
|
||||
|
||||
// convert wxFont types to string and vice versa
|
||||
static const boost::bimap<wxFontFamily, std::string_view> type_to_family;
|
||||
static const boost::bimap<wxFontStyle, std::string_view> type_to_style;
|
||||
static const boost::bimap<wxFontWeight, std::string_view> type_to_weight;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
#endif // slic3r_WxFontUtils_hpp_
|
||||
Reference in New Issue
Block a user