mirror of
https://github.com/QIDITECH/QIDIStudio.git
synced 2026-02-02 01:48:42 +03:00
update
This commit is contained in:
1974
src/slic3r/Utils/ASCIIFolding.cpp
Normal file
1974
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,bool is_convert_for_filename=false);
|
||||
|
||||
// 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_ */
|
||||
173
src/slic3r/Utils/AstroBox.cpp
Normal file
173
src/slic3r/Utils/AstroBox.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
#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 "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::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "AstroBox")).str());
|
||||
}
|
||||
}
|
||||
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::from_u8((boost::format("%s: %s\n\n%s")
|
||||
% _utf8(L("Could not connect to AstroBox"))
|
||||
% std::string(msg.ToUTF8())
|
||||
% _utf8(L("Note: AstroBox version at least 1.1.0 is required."))).str());
|
||||
}
|
||||
|
||||
bool AstroBox::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_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) 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
|
||||
1201
src/slic3r/Utils/Bonjour.cpp
Normal file
1201
src/slic3r/Utils/Bonjour.cpp
Normal file
File diff suppressed because it is too large
Load Diff
286
src/slic3r/Utils/Bonjour.hpp
Normal file
286
src/slic3r/Utils/Bonjour.hpp
Normal file
@@ -0,0 +1,286 @@
|
||||
#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
|
||||
1301
src/slic3r/Utils/CalibUtils.cpp
Normal file
1301
src/slic3r/Utils/CalibUtils.cpp
Normal file
File diff suppressed because it is too large
Load Diff
78
src/slic3r/Utils/CalibUtils.hpp
Normal file
78
src/slic3r/Utils/CalibUtils.hpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
#include "libslic3r/Calib.hpp"
|
||||
#include "../GUI/DeviceManager.hpp"
|
||||
#include "../GUI/Jobs/PrintJob.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ProgressIndicator;
|
||||
class Preset;
|
||||
|
||||
namespace GUI {
|
||||
extern const float MIN_PA_K_VALUE;
|
||||
extern const float MAX_PA_K_VALUE;
|
||||
|
||||
class CalibInfo
|
||||
{
|
||||
public:
|
||||
Calib_Params params;
|
||||
Preset* printer_prest;
|
||||
Preset* filament_prest;
|
||||
Preset* print_prest;
|
||||
BedType bed_type;
|
||||
std::string dev_id;
|
||||
std::string select_ams;
|
||||
std::shared_ptr<ProgressIndicator> process_bar;
|
||||
};
|
||||
|
||||
class CalibUtils
|
||||
{
|
||||
public:
|
||||
CalibUtils(){};
|
||||
static std::shared_ptr<PrintJob> print_job;
|
||||
|
||||
static CalibMode get_calib_mode_by_name(const std::string name, int &cali_stage);
|
||||
|
||||
static void calib_PA(const X1CCalibInfos& calib_infos, int mode, wxString& error_message);
|
||||
|
||||
static void emit_get_PA_calib_results(float nozzle_diameter);
|
||||
static bool get_PA_calib_results(std::vector<PACalibResult> &pa_calib_results);
|
||||
|
||||
static void emit_get_PA_calib_infos(float nozzle_diameter);
|
||||
static bool get_PA_calib_tab(std::vector<PACalibResult> &pa_calib_infos);
|
||||
|
||||
static void emit_get_PA_calib_info(float nozzle_diameter, const std::string &filament_id);
|
||||
static bool get_PA_calib_info(PACalibResult &pa_calib_info);
|
||||
|
||||
static void set_PA_calib_result(const std::vector<PACalibResult>& pa_calib_values, bool is_auto_cali);
|
||||
static void select_PA_calib_result(const PACalibIndexInfo &pa_calib_info);
|
||||
static void delete_PA_calib_result(const PACalibIndexInfo &pa_calib_info);
|
||||
|
||||
static void calib_flowrate_X1C(const X1CCalibInfos& calib_infos, std::string& error_message);
|
||||
static void emit_get_flow_ratio_calib_results(float nozzle_diameter);
|
||||
static bool get_flow_ratio_calib_results(std::vector<FlowRatioCalibResult> &flow_ratio_calib_results);
|
||||
static bool calib_flowrate(int pass, const CalibInfo &calib_info, wxString &error_message);
|
||||
|
||||
static void calib_pa_pattern(const CalibInfo &calib_info, Model &model);
|
||||
|
||||
static bool calib_generic_PA(const CalibInfo &calib_info, wxString &error_message);
|
||||
static void calib_temptue(const CalibInfo &calib_info, wxString &error_message);
|
||||
static void calib_max_vol_speed(const CalibInfo &calib_info, wxString &error_message);
|
||||
static void calib_VFA(const CalibInfo &calib_info, wxString &error_message);
|
||||
static void calib_retraction(const CalibInfo &calib_info, wxString &error_message);
|
||||
|
||||
//help function
|
||||
static int get_selected_calib_idx(const std::vector<PACalibResult> &pa_calib_values, int cali_idx);
|
||||
static bool get_pa_k_n_value_by_cali_idx(const MachineObject* obj, int cali_idx, float& out_k, float& out_n);
|
||||
|
||||
static bool validate_input_name(wxString name);
|
||||
static bool validate_input_k_value(wxString k_text, float* output_value);
|
||||
static bool validate_input_flow_ratio(wxString flow_ratio, float* output_value);
|
||||
|
||||
private:
|
||||
static bool process_and_store_3mf(Model* model, const DynamicPrintConfig& full_config, const Calib_Params& params, wxString& error_message);
|
||||
static void send_to_print(const CalibInfo &calib_info, wxString& error_message, int flow_ratio_mode = 0); // 0: none 1: coarse 2: fine
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
254
src/slic3r/Utils/ColorSpaceConvert.cpp
Normal file
254
src/slic3r/Utils/ColorSpaceConvert.cpp
Normal file
@@ -0,0 +1,254 @@
|
||||
#include "ColorSpaceConvert.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <wx/colordlg.h>
|
||||
#include <cmath>
|
||||
|
||||
const static float param_13 = 1.0f / 3.0f;
|
||||
const static float param_16116 = 16.0f / 116.0f;
|
||||
const static float Xn = 0.950456f;
|
||||
const static float Yn = 1.0f;
|
||||
const static float Zn = 1.088754f;
|
||||
|
||||
std::tuple<int, int, int> rgb_to_yuv(float r, float g, float b)
|
||||
{
|
||||
int y = (int)(0.2126 * r + 0.7152 * g + 0.0722 * b);
|
||||
int u = (int)(-0.09991 * r - 0.33609 * g + 0.436 * b);
|
||||
int v = (int)(0.615 * r - 0.55861 * g - 0.05639 * b);
|
||||
return std::make_tuple(y, u, v);
|
||||
}
|
||||
|
||||
double PivotRGB(double n)
|
||||
{
|
||||
return (n > 0.04045 ? std::pow((n + 0.055) / 1.055, 2.4) : n / 12.92) * 100.0;
|
||||
}
|
||||
|
||||
double PivotXYZ(double n)
|
||||
{
|
||||
double i = std::cbrt(n);
|
||||
return n > 0.008856 ? i : 7.787 * n + 16.0 / 116.0;
|
||||
}
|
||||
|
||||
void XYZ2RGB(float X, float Y, float Z, float* R, float* G, float* B)
|
||||
{
|
||||
float RR, GG, BB;
|
||||
RR = 3.240479f * X - 1.537150f * Y - 0.498535f * Z;
|
||||
GG = -0.969256f * X + 1.875992f * Y + 0.041556f * Z;
|
||||
BB = 0.055648f * X - 0.204043f * Y + 1.057311f * Z;
|
||||
|
||||
*R = (float)std::clamp(RR, 0.f, 255.f);
|
||||
*G = (float)std::clamp(GG, 0.f, 255.f);
|
||||
*B = (float)std::clamp(BB, 0.f, 255.f);
|
||||
}
|
||||
|
||||
void Lab2XYZ(float L, float a, float b, float* X, float* Y, float* Z)
|
||||
{
|
||||
float fX, fY, fZ;
|
||||
|
||||
fY = (L + 16.0f) / 116.0f;
|
||||
if (fY > 0.206893f)
|
||||
*Y = fY * fY * fY;
|
||||
else
|
||||
*Y = (fY - param_16116) / 7.787f;
|
||||
|
||||
fX = a / 500.0f + fY;
|
||||
if (fX > 0.206893f)
|
||||
*X = fX * fX * fX;
|
||||
else
|
||||
*X = (fX - param_16116) / 7.787f;
|
||||
|
||||
fZ = fY - b / 200.0f;
|
||||
if (fZ > 0.206893f)
|
||||
*Z = fZ * fZ * fZ;
|
||||
else
|
||||
*Z = (fZ - param_16116) / 7.787f;
|
||||
|
||||
(*X) *= Xn;
|
||||
(*Y) *= Yn;
|
||||
(*Z) *= Zn;
|
||||
}
|
||||
|
||||
void Lab2RGB(float L, float a, float b, float* R, float* G, float* B)
|
||||
{
|
||||
float X = 0.0f, Y = 0.0f, Z = 0.0f;
|
||||
Lab2XYZ(L, a, b, &X, &Y, &Z);
|
||||
XYZ2RGB(X, Y, Z, R, G, B);
|
||||
}
|
||||
|
||||
void RGB2XYZ(float R, float G, float B, float* X, float* Y, float* Z)
|
||||
{
|
||||
R = PivotRGB(R);
|
||||
G = PivotRGB(G);
|
||||
B = PivotRGB(B);
|
||||
|
||||
*X = 0.412453f * R + 0.357580f * G + 0.180423f * B;
|
||||
*Y = 0.212671f * R + 0.715160f * G + 0.072169f * B;
|
||||
*Z = 0.019334f * R + 0.119193f * G + 0.950227f * B;
|
||||
}
|
||||
|
||||
void XYZ2Lab(float X, float Y, float Z, float* L, float* a, float* b)
|
||||
{
|
||||
double REF_X = 95.047;
|
||||
double REF_Y = 100.000;
|
||||
double REF_Z = 108.883;
|
||||
|
||||
double x = PivotXYZ(X / REF_X);
|
||||
double y = PivotXYZ(Y / REF_Y);
|
||||
double z = PivotXYZ(Z / REF_Z);
|
||||
|
||||
*L = 116.0 * y - 16.0;
|
||||
*a = 500.0 * (x - y);
|
||||
*b = 200.0 * (y - z);
|
||||
}
|
||||
|
||||
void RGB2Lab(float R, float G, float B, float* L, float* a, float* b)
|
||||
{
|
||||
float X = 0.0f, Y = 0.0f, Z = 0.0f;
|
||||
RGB2XYZ(R, G, B, &X, &Y, &Z);
|
||||
XYZ2Lab(X, Y, Z, L, a, b);
|
||||
}
|
||||
|
||||
// The input r, g, b values should be in range [0, 1]. The output h is in range [0, 360], s is in range [0, 1] and v is in range [0, 1].
|
||||
void RGB2HSV(float r, float g, float b, float* h, float* s, float* v)
|
||||
{
|
||||
float Cmax = std::max(std::max(r, g), b);
|
||||
float Cmin = std::min(std::min(r, g), b);
|
||||
float delta = Cmax - Cmin;
|
||||
|
||||
if (std::abs(delta) < 0.001) {
|
||||
*h = 0.f;
|
||||
}
|
||||
else if (Cmax == r) {
|
||||
*h = 60.f * fmod((g - b) / delta, 6.f);
|
||||
}
|
||||
else if (Cmax == g) {
|
||||
*h = 60.f * ((b - r) / delta + 2);
|
||||
}
|
||||
else {
|
||||
*h = 60.f * ((r - g) / delta + 4);
|
||||
}
|
||||
|
||||
if (std::abs(Cmax) < 0.001) {
|
||||
*s = 0.f;
|
||||
}
|
||||
else {
|
||||
*s = delta / Cmax;
|
||||
}
|
||||
|
||||
*v = Cmax;
|
||||
}
|
||||
|
||||
float DeltaE00(float l1, float a1, float b1, float l2, float a2, float b2)
|
||||
{
|
||||
auto rad2deg = [](float rad) {
|
||||
return 360.0 * rad / (2.0 * M_PI);
|
||||
};
|
||||
|
||||
auto deg2rad = [](float deg) {
|
||||
return (2.0 * M_PI * deg) / 360.0;
|
||||
};
|
||||
|
||||
float avgL = (l1 + l2) / 2.0;
|
||||
float c1 = std::sqrt(std::pow(a1, 2) + std::pow(b1, 2));
|
||||
float c2 = std::sqrt(std::pow(a2, 2) + std::pow(b2, 2));
|
||||
float avgC = (c1 + c2) / 2.0;
|
||||
float g = (1.0 - std::sqrt(std::pow(avgC, 7) / (std::pow(avgC, 7) + std::pow(25.0, 7)))) / 2.0;
|
||||
|
||||
float a1p = a1 * (1.0 + g);
|
||||
float a2p = a2 * (1.0 + g);
|
||||
|
||||
float c1p = std::sqrt(std::pow(a1p, 2) + std::pow(b1, 2));
|
||||
float c2p = std::sqrt(std::pow(a2p, 2) + std::pow(b2, 2));
|
||||
|
||||
float avgCp = (c1p + c2p) / 2.0;
|
||||
|
||||
float h1p = rad2deg(std::atan2(b1, a1p));
|
||||
if (h1p < 0.0) {
|
||||
h1p = h1p + 360.0;
|
||||
}
|
||||
|
||||
float h2p = rad2deg(std::atan2(b2, a2p));
|
||||
if (h2p < 0.0) {
|
||||
h2p = h2p + 360;
|
||||
}
|
||||
|
||||
float avghp = std::abs(h1p - h2p) > 180.0 ? (h1p + h2p + 360.0) / 2.0 : (h1p + h2p) / 2.0;
|
||||
|
||||
float t = 1.0 - 0.17 * std::cos(deg2rad(avghp - 30.0)) + 0.24 * std::cos(deg2rad(2.0 * avghp)) + 0.32 * std::cos(deg2rad(3.0 * avghp + 6.0)) - 0.2 * std::cos(deg2rad(4.0 * avghp - 63.0));
|
||||
|
||||
float deltahp = h2p - h1p;
|
||||
if (std::abs(deltahp) > 180.0) {
|
||||
if (h2p <= h1p) {
|
||||
deltahp += 360.0;
|
||||
}
|
||||
else {
|
||||
deltahp -= 360.0;
|
||||
}
|
||||
}
|
||||
|
||||
float deltalp = l2 - l1;
|
||||
float deltacp = c2p - c1p;
|
||||
|
||||
deltahp = 2.0 * std::sqrt(c1p * c2p) * std::sin(deg2rad(deltahp) / 2.0);
|
||||
|
||||
float sl = 1.0 + ((0.015 * std::pow(avgL - 50.0, 2)) / std::sqrt(20.0 + std::pow(avgL - 50.0, 2)));
|
||||
float sc = 1.0 + 0.045 * avgCp;
|
||||
float sh = 1.0 + 0.015 * avgCp * t;
|
||||
|
||||
float deltaro = 30.0 * std::exp(-(std::pow((avghp - 275.0) / 25.0, 2)));
|
||||
float rc = 2.0 * std::sqrt(std::pow(avgCp, 7) / (std::pow(avgCp, 7) + std::pow(25.0, 7)));
|
||||
float rt = -rc * std::sin(2.0 * deg2rad(deltaro));
|
||||
|
||||
float kl = 1;
|
||||
float kc = 1;
|
||||
float kh = 1;
|
||||
|
||||
float delta_e00 = std::sqrt(std::pow(deltalp / (kl * sl), 2) + std::pow(deltacp / (kc * sc), 2) + std::pow(deltahp / (kh * sh), 2) + rt * (deltacp / (kc * sc)) * (deltahp / (kh * sh)));
|
||||
return delta_e00;
|
||||
}
|
||||
|
||||
float DeltaE94(float l1, float a1, float b1, float l2, float a2, float b2)
|
||||
{
|
||||
float k_L = 1;
|
||||
float k_C = 1;
|
||||
float k_H = 1;
|
||||
float K1 = 0.045;
|
||||
float K2 = 0.015;
|
||||
|
||||
float delta_l = l1 - l2;
|
||||
float C1 = std::sqrt(a1 * a1 + b1 * b1);
|
||||
float C2 = std::sqrt(a2 * a2 + b2 * b2);
|
||||
float delta_c = C1 - C2;
|
||||
float delta_a = a1 - a2;
|
||||
float delta_b = b1 - b2;
|
||||
float delta_h = std::sqrt(delta_a * delta_a + delta_b * delta_b - delta_c * delta_c);
|
||||
float SL = 1.0;
|
||||
float SC = 1 + K1 * C1;
|
||||
float SH = 1 + K2 * C1;
|
||||
|
||||
float delta_e94 = std::sqrt(std::pow(delta_l / (k_L * SL), 2) + std::pow(delta_c / (k_C * SC), 2) + std::pow(delta_h / (k_H / k_C * SH), 2));
|
||||
return delta_e94;
|
||||
}
|
||||
|
||||
float DeltaE76(float l1, float a1, float b1, float l2, float a2, float b2)
|
||||
{
|
||||
return std::sqrt(std::pow((l1 - l2), 2) + std::pow((a1 - a2), 2) + std::pow((b1 - b2), 2));
|
||||
}
|
||||
|
||||
std::string color_to_string(const wxColour &color)
|
||||
{
|
||||
std::string str = std::to_string(color.Red()) + "," + std::to_string(color.Green()) + "," + std::to_string(color.Blue()) + "," + std::to_string(color.Alpha());
|
||||
return str;
|
||||
}
|
||||
|
||||
wxColour string_to_wxColor(const std::string &str)
|
||||
{
|
||||
wxColour color;
|
||||
std::vector<std::string> result;
|
||||
boost::split(result, str, boost::is_any_of(","));
|
||||
if (result.size() == 4) {
|
||||
color = wxColour(std::stoi(result[0]), std::stoi(result[1]), std::stoi(result[2]), std::stoi(result[3]));
|
||||
}
|
||||
return color;
|
||||
};
|
||||
26
src/slic3r/Utils/ColorSpaceConvert.hpp
Normal file
26
src/slic3r/Utils/ColorSpaceConvert.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef slic3r_Utils_ColorSpaceConvert_hpp_
|
||||
#define slic3r_Utils_ColorSpaceConvert_hpp_
|
||||
#include <string>
|
||||
const int CUSTOM_COLOR_COUNT = 16;
|
||||
|
||||
#include <tuple>
|
||||
|
||||
std::tuple<int, int, int> rgb_to_yuv(float r, float g, float b);
|
||||
double PivotRGB(double n);
|
||||
double PivotXYZ(double n);
|
||||
void XYZ2RGB(float X, float Y, float Z, float* R, float* G, float* B);
|
||||
void Lab2XYZ(float L, float a, float b, float* X, float* Y, float* Z);
|
||||
void Lab2RGB(float L, float a, float b, float* R, float* G, float* B);
|
||||
void RGB2XYZ(float R, float G, float B, float* X, float* Y, float* Z);
|
||||
void XYZ2Lab(float X, float Y, float Z, float* L, float* a, float* b);
|
||||
void RGB2Lab(float R, float G, float B, float* L, float* a, float* b);
|
||||
void RGB2HSV(float r, float g, float b, float* h, float* s, float* v);
|
||||
|
||||
float DeltaE00(float l1, float a1, float b1, float l2, float a2, float b2);
|
||||
float DeltaE94(float l1, float a1, float b1, float l2, float a2, float b2);
|
||||
float DeltaE76(float l1, float a1, float b1, float l2, float a2, float b2);
|
||||
|
||||
class wxColour;
|
||||
std::string color_to_string(const wxColour &color);
|
||||
wxColour string_to_wxColor(const std::string &str);
|
||||
#endif /* slic3r_Utils_ColorSpaceConvert_hpp_ */
|
||||
286
src/slic3r/Utils/Duet.cpp
Normal file
286
src/slic3r/Utils/Duet.cpp
Normal file
@@ -0,0 +1,286 @@
|
||||
#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 "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::from_u8((boost::format("%s: %s")
|
||||
% _utf8(L("Could not connect to Duet"))
|
||||
% std::string(msg.ToUTF8())).str());
|
||||
}
|
||||
|
||||
bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_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) 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
|
||||
27
src/slic3r/Utils/FileHelp.cpp
Normal file
27
src/slic3r/Utils/FileHelp.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "FileHelp.hpp"
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <regex>
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
bool is_file_too_large(std::string file_path, bool &try_ok)
|
||||
{
|
||||
try {
|
||||
uintmax_t fileSizeBytes = boost::filesystem::file_size(file_path);
|
||||
double fileSizeMB = static_cast<double>(fileSizeBytes) / 1024 / 1024;
|
||||
try_ok = true;
|
||||
if (fileSizeMB > STL_SVG_MAX_FILE_SIZE_MB) { return true; }
|
||||
} catch (boost::filesystem::filesystem_error &e) {
|
||||
try_ok = false;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " error message: " << e.what();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void slash_to_back_slash(std::string &file_path) {
|
||||
std::regex regex("\\\\");
|
||||
file_path = std::regex_replace(file_path, regex, "/");
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::Utils
|
||||
13
src/slic3r/Utils/FileHelp.hpp
Normal file
13
src/slic3r/Utils/FileHelp.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef file_help_hpp_
|
||||
#define file_help_hpp_
|
||||
#include <string>
|
||||
|
||||
#define STL_SVG_MAX_FILE_SIZE_MB 3
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
bool is_file_too_large(std::string file_path, bool &try_ok);
|
||||
void slash_to_back_slash(std::string& file_path);// "//" to "\"
|
||||
}
|
||||
}
|
||||
#endif // file_help_hpp_
|
||||
447
src/slic3r/Utils/FixModelByWin10.cpp
Normal file
447
src/slic3r/Utils/FixModelByWin10.cpp
Normal file
@@ -0,0 +1,447 @@
|
||||
#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 {
|
||||
|
||||
static std::string saving_failed_str = L("Saving objects into the 3mf failed.");
|
||||
|
||||
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(L("Only Windows 10 is supported."));
|
||||
|
||||
if (! winrt_load_runtime_object_library())
|
||||
throw Slic3r::RuntimeError(L("Failed to initialize the WinRT library."));
|
||||
|
||||
HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED);
|
||||
{
|
||||
on_progress(L("Exporting objects"), 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(L("Failed loading objects."));
|
||||
|
||||
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 object by Windows 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(L("Repair failed."));
|
||||
repairAsync->GetResults();
|
||||
|
||||
on_progress(L("Loading repaired objects"), 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_failed_str);
|
||||
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_failed_str);
|
||||
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");
|
||||
|
||||
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_failed_str);
|
||||
hr = buffer->get_Length(&length);
|
||||
if (length == 0)
|
||||
break;
|
||||
fwrite(buffer_ptr, length, 1, fout);
|
||||
}
|
||||
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, GUI::ProgressDialog& progress_dialog, const wxString& msg_header, std::string& fix_result)
|
||||
{
|
||||
std::mutex mutex;
|
||||
std::condition_variable condition;
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
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 = [&mutex, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) {
|
||||
std::lock_guard<std::mutex> lk(mutex);
|
||||
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 objects"), 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(L("Exporting 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 objects"), 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(L("Import 3mf file failed"));
|
||||
if (model.objects.size() == 0)
|
||||
throw Slic3r::RuntimeError(L("Repaired 3mf file does not contain any object"));
|
||||
if (model.objects.size() > 1)
|
||||
throw Slic3r::RuntimeError(L("Repaired 3mf file contains more than one object"));
|
||||
if (model.objects.front()->volumes.size() == 0)
|
||||
throw Slic3r::RuntimeError(L("Repaired 3mf file does not contain any volume"));
|
||||
if (model.objects.front()->volumes.size() > 1)
|
||||
throw Slic3r::RuntimeError(L("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]->invalidate_convex_hull_2d();
|
||||
volumes[i]->set_new_unique_id();
|
||||
}
|
||||
model_object.invalidate_bounding_box();
|
||||
-- ivolume;
|
||||
on_progress(L("Repair finished"), 100);
|
||||
success = true;
|
||||
finished = true;
|
||||
} catch (RepairCanceledException & /* ex */) {
|
||||
canceled = true;
|
||||
finished = true;
|
||||
on_progress(L("Repair canceled"), 100);
|
||||
} catch (std::exception &ex) {
|
||||
success = false;
|
||||
finished = true;
|
||||
on_progress(ex.what(), 100);
|
||||
}
|
||||
});
|
||||
while (! finished) {
|
||||
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 */
|
||||
31
src/slic3r/Utils/FixModelByWin10.hpp
Normal file
31
src/slic3r/Utils/FixModelByWin10.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef slic3r_GUI_Utils_FixModelByWin10_hpp_
|
||||
#define slic3r_GUI_Utils_FixModelByWin10_hpp_
|
||||
|
||||
#include <string>
|
||||
#include "../GUI/Widgets/ProgressDialog.hpp"
|
||||
|
||||
class ProgressDialog;
|
||||
|
||||
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,GUI::ProgressDialog &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, GUI::ProgressDialog &, const wxString &, std::string &) { return false; }
|
||||
|
||||
#endif /* HAS_WIN10SDK */
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GUI_Utils_FixModelByWin10_hpp_ */
|
||||
229
src/slic3r/Utils/FlashAir.cpp
Normal file
229
src/slic3r/Utils/FlashAir.cpp
Normal file
@@ -0,0 +1,229 @@
|
||||
#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 "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::from_u8((boost::format("%s: %s\n%s")
|
||||
% _utf8(L("Could not connect to FlashAir"))
|
||||
% std::string(msg.ToUTF8())
|
||||
% _utf8(L("Note: FlashAir with firmware 2.00.02 or newer and activated upload function is required."))).str());
|
||||
}
|
||||
|
||||
bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_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) 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
|
||||
144
src/slic3r/Utils/FontConfigHelp.cpp
Normal file
144
src/slic3r/Utils/FontConfigHelp.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
///|/ Copyright (c) Prusa Research 2021 - 2022 Filip Sykala @Jony01
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#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
|
||||
29
src/slic3r/Utils/FontConfigHelp.hpp
Normal file
29
src/slic3r/Utils/FontConfigHelp.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
///|/ Copyright (c) Prusa Research 2021 - 2022 Filip Sykala @Jony01
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#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_
|
||||
311
src/slic3r/Utils/FontUtils.cpp
Normal file
311
src/slic3r/Utils/FontUtils.cpp
Normal file
@@ -0,0 +1,311 @@
|
||||
#include "FontUtils.hpp"
|
||||
#include "imgui/imstb_truetype.h"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include <boost/log/trivial.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 "FontConfigHelp.hpp"
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#ifdef __APPLE__
|
||||
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;
|
||||
}
|
||||
#endif // __APPLE__
|
||||
|
||||
using fontinfo_opt = std::optional<stbtt_fontinfo>;
|
||||
|
||||
std::string 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());
|
||||
}
|
||||
}
|
||||
|
||||
fontinfo_opt load_font_info(const unsigned char *data, unsigned int index)
|
||||
{
|
||||
int font_offset = stbtt_GetFontOffsetForIndex(data, index);
|
||||
if (font_offset < 0) {
|
||||
assert(false);
|
||||
// "Font index(" << index << ") doesn't exist.";
|
||||
return {};
|
||||
}
|
||||
stbtt_fontinfo font_info;
|
||||
if (stbtt_InitFont(&font_info, data, font_offset) == 0) {
|
||||
// Can't initialize font.
|
||||
//assert(false);
|
||||
return {};
|
||||
}
|
||||
return font_info;
|
||||
}
|
||||
|
||||
std::unique_ptr<FontFile> create_font_file(std::unique_ptr<std::vector<unsigned char>> data)
|
||||
{
|
||||
int collection_size = stbtt_GetNumberOfFonts(data->data());
|
||||
// at least one font must be inside collection
|
||||
if (collection_size < 1) {
|
||||
assert(false);
|
||||
// There is no font collection inside font data
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unsigned int c_size = static_cast<unsigned int>(collection_size);
|
||||
std::vector<FontFile::Info> infos;
|
||||
infos.reserve(c_size);
|
||||
for (unsigned int i = 0; i < c_size; ++i) {
|
||||
auto font_info = load_font_info(data->data(), i);
|
||||
if (!font_info.has_value()) return nullptr;
|
||||
|
||||
const stbtt_fontinfo *info = &(*font_info);
|
||||
// load information about line gap
|
||||
int ascent, descent, linegap;
|
||||
stbtt_GetFontVMetrics(info, &ascent, &descent, &linegap);
|
||||
|
||||
float pixels = 1000.; // value is irelevant
|
||||
float em_pixels = stbtt_ScaleForMappingEmToPixels(info, pixels);
|
||||
int units_per_em = static_cast<int>(std::round(pixels / em_pixels));
|
||||
|
||||
infos.emplace_back(FontFile::Info{ascent, descent, linegap, units_per_em});
|
||||
}
|
||||
return std::make_unique<FontFile>(std::move(data), std::move(infos));
|
||||
}
|
||||
|
||||
std::unique_ptr<FontFile> create_font_file(const char *file_path)
|
||||
{
|
||||
FILE *file = std::fopen(file_path, "rb");
|
||||
if (file == nullptr) {
|
||||
assert(false);
|
||||
BOOST_LOG_TRIVIAL(error) << "Couldn't open " << file_path << " for reading.";
|
||||
return nullptr;
|
||||
}
|
||||
ScopeGuard sg([&file]() { std::fclose(file); });
|
||||
|
||||
// find size of file
|
||||
if (fseek(file, 0L, SEEK_END) != 0) {
|
||||
assert(false);
|
||||
BOOST_LOG_TRIVIAL(error) << "Couldn't fseek file " << file_path << " for size measure.";
|
||||
return nullptr;
|
||||
}
|
||||
size_t size = ftell(file);
|
||||
if (size == 0) {
|
||||
assert(false);
|
||||
BOOST_LOG_TRIVIAL(error) << "Size of font file is zero. Can't read.";
|
||||
return nullptr;
|
||||
}
|
||||
rewind(file);
|
||||
auto buffer = std::make_unique<std::vector<unsigned char>>(size);
|
||||
size_t count_loaded_bytes = fread((void *) &buffer->front(), 1, size, file);
|
||||
if (count_loaded_bytes != size) {
|
||||
assert(false);
|
||||
BOOST_LOG_TRIVIAL(error) << "Different loaded(from file) data size.";
|
||||
return nullptr;
|
||||
}
|
||||
return create_font_file(std::move(buffer));
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
bool load_hfont(void *hfont, DWORD &dwTable, DWORD &dwOffset, size_t &size, HDC hdc = nullptr)
|
||||
{
|
||||
bool del_hdc = false;
|
||||
if (hdc == nullptr) {
|
||||
del_hdc = true;
|
||||
hdc = ::CreateCompatibleDC(NULL);
|
||||
if (hdc == NULL) {
|
||||
DWORD errorCode = GetLastError();
|
||||
LPVOID lpMsgBuf;
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPTSTR) &lpMsgBuf, 0, NULL);
|
||||
|
||||
BOOST_LOG_TRIVIAL(error) << "CreateCompatibleDC failed in load_hfont : "
|
||||
<< "Error: " << reinterpret_cast<LPTSTR>(lpMsgBuf);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// To retrieve the data from the beginning of the file for TrueType
|
||||
// Collection files specify 'ttcf' (0x66637474).
|
||||
dwTable = 0x66637474;
|
||||
dwOffset = 0;
|
||||
|
||||
::SelectObject(hdc, hfont);
|
||||
size = ::GetFontData(hdc, dwTable, dwOffset, NULL, 0);
|
||||
if (size == GDI_ERROR) {
|
||||
// HFONT is NOT TTC(collection)
|
||||
dwTable = 0;
|
||||
size = ::GetFontData(hdc, dwTable, dwOffset, NULL, 0);
|
||||
}
|
||||
|
||||
if (size == 0 || size == GDI_ERROR) {
|
||||
if (del_hdc)
|
||||
::DeleteDC(hdc);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (del_hdc)
|
||||
::DeleteDC(hdc);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<FontFile> create_font_file(void *hfont)
|
||||
{
|
||||
HDC hdc = ::CreateCompatibleDC(NULL);
|
||||
if (hdc == NULL) {
|
||||
assert(false);
|
||||
DWORD errorCode = GetLastError();
|
||||
LPVOID lpMsgBuf;
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL);
|
||||
|
||||
BOOST_LOG_TRIVIAL(error) << "CreateCompatibleDC failed in create_font_file : "
|
||||
<< "Error: " << reinterpret_cast<LPTSTR>(lpMsgBuf);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DWORD dwTable = 0, dwOffset = 0;
|
||||
size_t size;
|
||||
if (!load_hfont(hfont, dwTable, dwOffset, size, hdc)) {
|
||||
::DeleteDC(hdc);
|
||||
return nullptr;
|
||||
}
|
||||
auto buffer = std::make_unique<std::vector<unsigned char>>(size);
|
||||
size_t loaded_size = ::GetFontData(hdc, dwTable, dwOffset, buffer->data(), size);
|
||||
::DeleteDC(hdc);
|
||||
if (size != loaded_size) {
|
||||
assert(false);
|
||||
BOOST_LOG_TRIVIAL(error) << "Different loaded(from HFONT) data size.";
|
||||
return nullptr;
|
||||
}
|
||||
return create_font_file(std::move(buffer));
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unique_ptr<FontFile> create_font_file(const wxFont &font)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return 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 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 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
|
||||
}
|
||||
|
||||
bool can_generate_text_shape_from_font(const stbtt_fontinfo &font_info)
|
||||
{
|
||||
const float flatness = 0.0125f; // [in mm]
|
||||
wchar_t letter = 'A';
|
||||
int unicode_letter = static_cast<int>(letter);
|
||||
|
||||
int glyph_index = stbtt_FindGlyphIndex(&font_info, unicode_letter);
|
||||
if (glyph_index == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int advance_width=0, left_side_bearing=0;
|
||||
stbtt_GetGlyphHMetrics(&font_info, glyph_index, &advance_width, &left_side_bearing);
|
||||
|
||||
stbtt_vertex *vertices;
|
||||
int num_verts = stbtt_GetGlyphShape(&font_info, glyph_index, &vertices);
|
||||
if (num_verts <= 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool can_generate_text_shape(const std::string& font_name) {
|
||||
wxFont wx_font(wxFontInfo().FaceName(font_name.c_str()).Encoding(wxFontEncoding::wxFONTENCODING_SYSTEM));
|
||||
std::unique_ptr<FontFile> font = create_font_file(wx_font);
|
||||
if (!font)
|
||||
return false;
|
||||
|
||||
fontinfo_opt font_info_opt = load_font_info(font->data->data(), 0);
|
||||
if (!font_info_opt.has_value())
|
||||
return false;
|
||||
|
||||
return can_generate_text_shape_from_font(*font_info_opt);
|
||||
}
|
||||
|
||||
bool can_load(const wxFont &font)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD dwTable = 0, dwOffset = 0;
|
||||
size_t size = 0;
|
||||
void * hfont = font.GetHFONT();
|
||||
if (!load_hfont(hfont, dwTable, dwOffset, size)) return false;
|
||||
return hfont != nullptr;
|
||||
#elif defined(__APPLE__)
|
||||
return true;
|
||||
#elif defined(__linux__)
|
||||
return true;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
56
src/slic3r/Utils/FontUtils.hpp
Normal file
56
src/slic3r/Utils/FontUtils.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef __FONT_UTILS_HPP__
|
||||
#define __FONT_UTILS_HPP__
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
#include <wx/font.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/// <summary>
|
||||
/// keep information from file about font
|
||||
/// (store file data itself)
|
||||
/// + cache data readed from buffer
|
||||
/// </summary>
|
||||
struct FontFile
|
||||
{
|
||||
// loaded data from font file
|
||||
// must store data size for imgui rasterization
|
||||
// To not store data on heap and To prevent unneccesary copy
|
||||
// data are stored inside unique_ptr
|
||||
std::unique_ptr<std::vector<unsigned char>> data;
|
||||
|
||||
struct Info
|
||||
{
|
||||
// vertical position is "scale*(ascent - descent + lineGap)"
|
||||
int ascent, descent, linegap;
|
||||
|
||||
// for convert font units to pixel
|
||||
int unit_per_em;
|
||||
};
|
||||
// info for each font in data
|
||||
std::vector<Info> infos;
|
||||
|
||||
FontFile(std::unique_ptr<std::vector<unsigned char>> data, std::vector<Info> &&infos) : data(std::move(data)), infos(std::move(infos))
|
||||
{
|
||||
assert(this->data != nullptr);
|
||||
assert(!this->data->empty());
|
||||
}
|
||||
|
||||
bool operator==(const FontFile &other) const
|
||||
{
|
||||
if (data->size() != other.data->size()) return false;
|
||||
// if(*data != *other.data) return false;
|
||||
for (size_t i = 0; i < infos.size(); i++)
|
||||
if (infos[i].ascent != other.infos[i].ascent || infos[i].descent == other.infos[i].descent || infos[i].linegap == other.infos[i].linegap) return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
bool can_generate_text_shape(const std::string &font_name);
|
||||
|
||||
bool can_load(const wxFont &font);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
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
|
||||
828
src/slic3r/Utils/Http.cpp
Normal file
828
src/slic3r/Utils/Http.cpp
Normal file
@@ -0,0 +1,828 @@
|
||||
#include "Http.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#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
|
||||
|
||||
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 = "Unable to get system certificate.";
|
||||
else
|
||||
message = (boost::format("use system SSL certificate: %1%") % bundle).str();
|
||||
|
||||
message += "\n" + (boost::format("To manually specify the system certificate store, "
|
||||
"set the %1% environment variable to the correct CA and restart the application") % SSL_CA_FILE).str();
|
||||
}
|
||||
#endif // OPENSSL_CERT_OVERRIDE
|
||||
|
||||
if (CURLcode ec = ::curl_global_init(CURL_GLOBAL_DEFAULT)) {
|
||||
message += "CURL initialization failed. See the log for additional details.";
|
||||
BOOST_LOG_TRIVIAL(error) << ::curl_easy_strerror(ec);
|
||||
}
|
||||
}
|
||||
|
||||
~CurlGlobalInit() { ::curl_global_cleanup(); }
|
||||
};
|
||||
|
||||
std::unique_ptr<CurlGlobalInit> CurlGlobalInit::instance;
|
||||
|
||||
std::map<std::string, std::string> extra_headers;
|
||||
std::mutex g_mutex;
|
||||
|
||||
struct Http::priv
|
||||
{
|
||||
enum {
|
||||
DEFAULT_TIMEOUT_CONNECT = 10,
|
||||
DEFAULT_TIMEOUT_MAX = 0,
|
||||
DEFAULT_SIZE_LIMIT = 1024 * 1024 * 1024,
|
||||
};
|
||||
|
||||
::CURL *curl;
|
||||
::curl_httppost *form;
|
||||
::curl_httppost *form_end;
|
||||
::curl_mime* mime;
|
||||
::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
|
||||
std::string headers;
|
||||
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;
|
||||
Http::HeaderCallbackFn headerfn;
|
||||
|
||||
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);
|
||||
static size_t headers_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);
|
||||
/* mime */
|
||||
void mime_form_add_text(const char* name, const char* value);
|
||||
void mime_form_add_file(const char* name, const char* path);
|
||||
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_del_body(const std::string& body);
|
||||
|
||||
std::string curl_error(CURLcode curlcode);
|
||||
std::string body_size_error();
|
||||
void http_perform();
|
||||
};
|
||||
|
||||
// add a dummy log callback
|
||||
static int log_trace(CURL* handle, curl_infotype type,
|
||||
char* data, size_t size,
|
||||
void* userp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Http::priv::priv(const std::string &url)
|
||||
: curl(::curl_easy_init())
|
||||
, form(nullptr)
|
||||
, form_end(nullptr)
|
||||
, mime(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_DEBUGFUNCTION, log_trace);
|
||||
::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());
|
||||
#ifdef __WINDOWS__
|
||||
::curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_2);
|
||||
#endif
|
||||
::curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
||||
::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
}
|
||||
|
||||
Http::priv::~priv()
|
||||
{
|
||||
::curl_easy_cleanup(curl);
|
||||
::curl_formfree(form);
|
||||
::curl_mime_free(mime);
|
||||
::curl_slist_free_all(headerlist);
|
||||
}
|
||||
|
||||
bool Http::priv::ca_file_supported(::CURL *curl)
|
||||
{
|
||||
//QDS support set ca file by default
|
||||
bool res = true;
|
||||
|
||||
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
|
||||
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) {
|
||||
double speed;
|
||||
curl_easy_getinfo(self->curl, CURLINFO_SPEED_UPLOAD, &speed);
|
||||
if (speed > 0.01)
|
||||
speed = speed;
|
||||
Progress progress(dltotal, dlnow, ultotal, ulnow, speed);
|
||||
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();
|
||||
}
|
||||
|
||||
size_t Http::priv::headers_cb(char *buffer, size_t size, size_t nitems, void *userp)
|
||||
{
|
||||
auto self = static_cast<priv*>(userp);
|
||||
|
||||
if (self->headerfn) {
|
||||
self->headers.append(buffer, nitems * size);
|
||||
self->headerfn(self->headers);
|
||||
}
|
||||
return nitems * size;
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Http::priv::mime_form_add_text(const char* name, const char* value)
|
||||
{
|
||||
if (!mime) {
|
||||
mime = curl_mime_init(curl);
|
||||
}
|
||||
|
||||
curl_mimepart *part;
|
||||
part = curl_mime_addpart(mime);
|
||||
curl_mime_name(part, name);
|
||||
curl_mime_type(part, "multipart/form-data");
|
||||
curl_mime_data(part, value, CURL_ZERO_TERMINATED);
|
||||
}
|
||||
|
||||
void Http::priv::mime_form_add_file(const char* name, const char* path)
|
||||
{
|
||||
if (!mime) {
|
||||
mime = curl_mime_init(curl);
|
||||
}
|
||||
|
||||
curl_mimepart* part;
|
||||
part = curl_mime_addpart(mime);
|
||||
curl_mime_name(part, "file");
|
||||
curl_mime_type(part, "multipart/form-data");
|
||||
curl_mime_filedata(part, path);
|
||||
// QDS specify filename after filedata
|
||||
curl_mime_filename(part, name);
|
||||
}
|
||||
|
||||
//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, std::ios_base::binary |std::ios_base::in);
|
||||
::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
|
||||
::curl_easy_setopt(curl, CURLOPT_READDATA, (void *) (putFile.get()));
|
||||
::curl_easy_setopt(curl, CURLOPT_INFILESIZE, filesize);
|
||||
}
|
||||
}
|
||||
|
||||
void Http::priv::set_del_body(const std::string& body)
|
||||
{
|
||||
postfields = body;
|
||||
}
|
||||
|
||||
std::string Http::priv::curl_error(CURLcode curlcode)
|
||||
{
|
||||
return (boost::format("curl:%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);
|
||||
//QDS set header functions
|
||||
::curl_easy_setopt(curl, CURLOPT_HEADERDATA, static_cast<void *>(this));
|
||||
::curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headers_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, 1L);
|
||||
|
||||
if (headerlist != nullptr) {
|
||||
::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
|
||||
}
|
||||
|
||||
if (form != nullptr) {
|
||||
::curl_easy_setopt(curl, CURLOPT_HTTPPOST, form);
|
||||
}
|
||||
|
||||
if (mime != nullptr) {
|
||||
::curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
//QDS check success http status code
|
||||
if (http_status >= 200 && http_status < 300) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
//QDS check error http status code
|
||||
else if (http_status >= 400) {
|
||||
if (errorfn) { errorfn(std::move(buffer), std::string(), http_status); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Http::Http(const std::string &url) : p(new priv(url)) {
|
||||
|
||||
std::lock_guard<std::mutex> l(g_mutex);
|
||||
for (auto it = extra_headers.begin(); it != extra_headers.end(); it++)
|
||||
this->header(it->first, it->second);
|
||||
}
|
||||
|
||||
|
||||
// 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::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::mime_form_add_text(std::string &name, std::string &value)
|
||||
{
|
||||
if (p) { p->mime_form_add_text(name.c_str(), value.c_str()); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::mime_form_add_file(std::string &name, const char* path)
|
||||
{
|
||||
if (p) { p->mime_form_add_file(name.c_str(), path); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
Http& Http::form_add_file(const std::wstring& name, const fs::path& path)
|
||||
{
|
||||
if (p) { p->form_add_file((char*)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)
|
||||
{
|
||||
// QDS
|
||||
#if 0
|
||||
if(p && set){
|
||||
::curl_easy_setopt(p->curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
|
||||
}
|
||||
#endif
|
||||
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::set_del_body(const std::string &body)
|
||||
{
|
||||
if (p) { p->set_del_body(body); }
|
||||
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 &Http::on_header_callback(HeaderCallbackFn fn)
|
||||
{
|
||||
if (p) { p->headerfn = 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;
|
||||
}
|
||||
|
||||
Http Http::put2(std::string url)
|
||||
{
|
||||
Http http{ std::move(url) };
|
||||
curl_easy_setopt(http.p->curl, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||
return http;
|
||||
}
|
||||
|
||||
Http Http::patch(std::string url)
|
||||
{
|
||||
Http http{ std::move(url) };
|
||||
curl_easy_setopt(http.p->curl, CURLOPT_CUSTOMREQUEST, "PATCH");
|
||||
return http;
|
||||
}
|
||||
|
||||
Http Http::del(std::string url)
|
||||
{
|
||||
Http http{ std::move(url) };
|
||||
curl_easy_setopt(http.p->curl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
||||
return http;
|
||||
}
|
||||
|
||||
void Http::set_extra_headers(std::map<std::string, std::string> headers)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(g_mutex);
|
||||
extra_headers.swap(headers);
|
||||
}
|
||||
|
||||
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::string Http::url_decode(const std::string &str)
|
||||
{
|
||||
::CURL *curl = ::curl_easy_init();
|
||||
if (curl == nullptr) { return str; }
|
||||
int outlen = 0;
|
||||
char *ce = ::curl_easy_unescape(curl, str.c_str(), str.length(), &outlen);
|
||||
std::string dencoded = std::string(ce, outlen);
|
||||
|
||||
::curl_free(ce);
|
||||
::curl_easy_cleanup(curl);
|
||||
|
||||
return dencoded;
|
||||
}
|
||||
|
||||
std::string Http::get_filename_from_url(const std::string &url)
|
||||
{
|
||||
int end_pos = url.find_first_of('?');
|
||||
if (end_pos <= 0) return "";
|
||||
std::string path_url = url.substr(0, end_pos);
|
||||
int start_pos = path_url.find_last_of("/");
|
||||
if (start_pos < 0) return "";
|
||||
return path_url.substr(start_pos + 1, path_url.length() - start_pos - 1);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
203
src/slic3r/Utils/Http.hpp
Normal file
203
src/slic3r/Utils/Http.hpp
Normal file
@@ -0,0 +1,203 @@
|
||||
#ifndef __Http_hpp__
|
||||
#define __Http_hpp__
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include "libslic3r/Exception.hpp"
|
||||
#include "libslic3r_version.h"
|
||||
|
||||
#define MAX_SIZE_TO_FILE 3*1024
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum HttpErrorCode
|
||||
{
|
||||
HttpErrorResourcesNotFound = 2,
|
||||
HtttErrorNoDevice = 3,
|
||||
HttpErrorRequestLogin = 4,
|
||||
HttpErrorResourcesNotExists = 6,
|
||||
HttpErrorMQTTError = 7,
|
||||
HttpErrorResourcesForbidden = 8,
|
||||
HttpErrorInternalRequestError = 9,
|
||||
HttpErrorInternalError = 10,
|
||||
HttpErrorFileFormatError = 11,
|
||||
HttpErrorResoucesConflict = 12,
|
||||
HttpErrorTimeout = 13,
|
||||
HttpErrorResourcesExhaust = 14,
|
||||
HttpErrorVersionLimited = 15,
|
||||
};
|
||||
|
||||
/// 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
|
||||
double upload_spd{0.0f};
|
||||
|
||||
Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow) :
|
||||
dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow)
|
||||
{}
|
||||
|
||||
Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow, double ulspd) :
|
||||
dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow), upload_spd(ulspd)
|
||||
{}
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
typedef std::function<void(std::string headers)> HeaderCallbackFn;
|
||||
|
||||
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);
|
||||
static Http del(std::string url);
|
||||
|
||||
//QDS
|
||||
static Http put2(std::string url);
|
||||
static Http patch(std::string url);
|
||||
|
||||
//QDS set global header for each http request
|
||||
static void set_extra_headers(std::map<std::string, std::string> headers);
|
||||
|
||||
~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);
|
||||
// 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);
|
||||
// Add a HTTP mime form field
|
||||
Http& mime_form_add_text(std::string& name, std::string& value);
|
||||
// Add a HTTP mime form file
|
||||
Http& mime_form_add_file(std::string& name, const char* path);
|
||||
// Same as above except also override the file's filename with a wstring type
|
||||
Http& form_add_file(const std::wstring& 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);
|
||||
|
||||
// Set the file contents as a DELETE 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_del_body(const std::string& body);
|
||||
|
||||
// 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);
|
||||
// Callback called when response header is received
|
||||
Http& on_header_callback(HeaderCallbackFn 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);
|
||||
static std::string url_decode(const std::string &str);
|
||||
|
||||
static std::string get_filename_from_url(const std::string &url);
|
||||
private:
|
||||
Http(const std::string &url);
|
||||
|
||||
std::unique_ptr<priv> p;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream &, const Http::Progress &);
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
150
src/slic3r/Utils/MKS.cpp
Normal file
150
src/slic3r/Utils/MKS.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
#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 "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::from_u8((boost::format("%s: %s")
|
||||
% _utf8(L("Could not connect to MKS"))
|
||||
% std::string(msg.ToUTF8())).str());
|
||||
}
|
||||
|
||||
bool MKS::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_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) 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
|
||||
25
src/slic3r/Utils/MacDarkMode.hpp
Normal file
25
src/slic3r/Utils/MacDarkMode.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef slic3r_MacDarkMode_hpp_
|
||||
#define slic3r_MacDarkMode_hpp_
|
||||
|
||||
#include <wx/event.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
#if __APPLE__
|
||||
extern bool mac_dark_mode();
|
||||
extern double mac_max_scaling_factor();
|
||||
extern void set_miniaturizable(void * window);
|
||||
void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*callback)(wxString const &));
|
||||
void WKWebView_setTransparentBackground(void * web);
|
||||
void set_tag_when_enter_full_screen(bool isfullscreen);
|
||||
void set_title_colour_after_set_title(void * window);
|
||||
void initGestures(void * view, wxEvtHandler * handler);
|
||||
void openFolderForFile(wxString const & file);
|
||||
#endif
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // MacDarkMode_h
|
||||
416
src/slic3r/Utils/MacDarkMode.mm
Normal file
416
src/slic3r/Utils/MacDarkMode.mm
Normal file
@@ -0,0 +1,416 @@
|
||||
#import "MacDarkMode.hpp"
|
||||
#include "../GUI/Widgets/Label.hpp"
|
||||
|
||||
#include "wx/osx/core/cfstring.h"
|
||||
|
||||
#import <algorithm>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AppKit/NSScreen.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
#include <objc/runtime.h>
|
||||
|
||||
@interface MacDarkMode : NSObject {}
|
||||
@end
|
||||
|
||||
@implementation MacDarkMode
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
NSTextField* mainframe_text_field = nil;
|
||||
bool is_in_full_screen_mode = false;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void set_miniaturizable(void * window)
|
||||
{
|
||||
CGFloat rFloat = 34/255.0;
|
||||
CGFloat gFloat = 34/255.0;
|
||||
CGFloat bFloat = 36/255.0;
|
||||
[(NSView*) window window].titlebarAppearsTransparent = true;
|
||||
[(NSView*) window window].backgroundColor = [NSColor colorWithCalibratedRed:rFloat green:gFloat blue:bFloat alpha:1.0];
|
||||
[(NSView*) window window].styleMask |= NSMiniaturizableWindowMask;
|
||||
|
||||
NSEnumerator *viewEnum = [[[[[[[(NSView*) window window] contentView] superview] titlebarViewController] view] subviews] objectEnumerator];
|
||||
NSView *viewObject;
|
||||
|
||||
while(viewObject = (NSView *)[viewEnum nextObject]) {
|
||||
if([viewObject class] == [NSTextField self]) {
|
||||
//[(NSTextField*)viewObject setTextColor : NSColor.whiteColor];
|
||||
mainframe_text_field = viewObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_tag_when_enter_full_screen(bool isfullscreen)
|
||||
{
|
||||
is_in_full_screen_mode = isfullscreen;
|
||||
}
|
||||
|
||||
void set_title_colour_after_set_title(void * window)
|
||||
{
|
||||
NSEnumerator *viewEnum = [[[[[[[(NSView*) window window] contentView] superview] titlebarViewController] view] subviews] objectEnumerator];
|
||||
NSView *viewObject;
|
||||
while(viewObject = (NSView *)[viewEnum nextObject]) {
|
||||
if([viewObject class] == [NSTextField self]) {
|
||||
[(NSTextField*)viewObject setTextColor : NSColor.whiteColor];
|
||||
mainframe_text_field = viewObject;
|
||||
}
|
||||
}
|
||||
|
||||
if (mainframe_text_field) {
|
||||
[(NSTextField*)mainframe_text_field setTextColor : NSColor.whiteColor];
|
||||
}
|
||||
}
|
||||
|
||||
void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*callback)(wxString const &))
|
||||
{
|
||||
[(WKWebView*)web evaluateJavaScript:wxCFStringRef(script).AsNSString() completionHandler: ^(id result, NSError *error) {
|
||||
if (callback && error != nil) {
|
||||
wxString err = wxCFStringRef(error.localizedFailureReason).AsString();
|
||||
callback(err);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
void WKWebView_setTransparentBackground(void * web)
|
||||
{
|
||||
WKWebView * webView = (WKWebView*)web;
|
||||
[webView layer].backgroundColor = [NSColor clearColor].CGColor;
|
||||
[webView registerForDraggedTypes: @[NSFilenamesPboardType]];
|
||||
}
|
||||
|
||||
void openFolderForFile(wxString const & file)
|
||||
{
|
||||
NSArray *fileURLs = [NSArray arrayWithObjects:wxCFStringRef(file).AsNSString(), /* ... */ nil];
|
||||
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:fileURLs];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* WKWebView */
|
||||
@implementation WKWebView (DragDrop)
|
||||
|
||||
+ (void) load
|
||||
{
|
||||
Method draggingEntered = class_getInstanceMethod([WKWebView class], @selector(draggingEntered:));
|
||||
Method draggingEntered2 = class_getInstanceMethod([WKWebView class], @selector(draggingEntered2:));
|
||||
method_exchangeImplementations(draggingEntered, draggingEntered2);
|
||||
|
||||
Method draggingUpdated = class_getInstanceMethod([WKWebView class], @selector(draggingUpdated:));
|
||||
Method draggingUpdated2 = class_getInstanceMethod([WKWebView class], @selector(draggingUpdated2:));
|
||||
method_exchangeImplementations(draggingUpdated, draggingUpdated2);
|
||||
|
||||
Method prepareForDragOperation = class_getInstanceMethod([WKWebView class], @selector(prepareForDragOperation:));
|
||||
Method prepareForDragOperation2 = class_getInstanceMethod([WKWebView class], @selector(prepareForDragOperation2:));
|
||||
method_exchangeImplementations(prepareForDragOperation, prepareForDragOperation2);
|
||||
|
||||
Method performDragOperation = class_getInstanceMethod([WKWebView class], @selector(performDragOperation:));
|
||||
Method performDragOperation2 = class_getInstanceMethod([WKWebView class], @selector(performDragOperation2:));
|
||||
method_exchangeImplementations(performDragOperation, performDragOperation2);
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingEntered2:(id<NSDraggingInfo>)sender
|
||||
{
|
||||
return NSDragOperationCopy;
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingUpdated2:(id<NSDraggingInfo>)sender
|
||||
{
|
||||
return NSDragOperationCopy;
|
||||
}
|
||||
|
||||
- (BOOL)prepareForDragOperation2:(id<NSDraggingInfo>)info
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
- (BOOL)performDragOperation2:(id<NSDraggingInfo>)info
|
||||
{
|
||||
NSURL* url = [NSURL URLFromPasteboard:[info draggingPasteboard]];
|
||||
NSString * path = [url path];
|
||||
url = [NSURL fileURLWithPath: path];
|
||||
[self loadFileURL:url allowingReadAccessToURL:url];
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* textColor for NSTextField */
|
||||
@implementation NSTextField (textColor)
|
||||
|
||||
- (void)setTextColor2:(NSColor *)textColor
|
||||
{
|
||||
if (Slic3r::GUI::mainframe_text_field != self){
|
||||
[self setTextColor2: textColor];
|
||||
}else{
|
||||
if(Slic3r::GUI::is_in_full_screen_mode){
|
||||
[self setTextColor2 : NSColor.darkGrayColor];
|
||||
}else{
|
||||
[self setTextColor2 : NSColor.whiteColor];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+ (void) load
|
||||
{
|
||||
Method setTextColor = class_getInstanceMethod([NSTextField class], @selector(setTextColor:));
|
||||
Method setTextColor2 = class_getInstanceMethod([NSTextField class], @selector(setTextColor2:));
|
||||
method_exchangeImplementations(setTextColor, setTextColor2);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* drawsBackground for NSTextField */
|
||||
@implementation NSTextField (drawsBackground)
|
||||
|
||||
- (instancetype)initWithFrame2:(NSRect)frameRect
|
||||
{
|
||||
[self initWithFrame2:frameRect];
|
||||
self.drawsBackground = false;
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
+ (void) load
|
||||
{
|
||||
Method initWithFrame = class_getInstanceMethod([NSTextField class], @selector(initWithFrame:));
|
||||
Method initWithFrame2 = class_getInstanceMethod([NSTextField class], @selector(initWithFrame2:));
|
||||
method_exchangeImplementations(initWithFrame, initWithFrame2);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* textColor for NSButton */
|
||||
|
||||
@implementation NSButton (NSButton_Extended)
|
||||
|
||||
- (NSColor *)textColor
|
||||
{
|
||||
NSAttributedString *attrTitle = [self attributedTitle];
|
||||
int len = [attrTitle length];
|
||||
NSRange range = NSMakeRange(0, MIN(len, 1)); // get the font attributes from the first character
|
||||
NSDictionary *attrs = [attrTitle fontAttributesInRange:range];
|
||||
NSColor *textColor = [NSColor controlTextColor];
|
||||
if (attrs)
|
||||
{
|
||||
textColor = [attrs objectForKey:NSForegroundColorAttributeName];
|
||||
}
|
||||
|
||||
return textColor;
|
||||
}
|
||||
|
||||
- (void)setTextColor:(NSColor *)textColor
|
||||
{
|
||||
NSMutableAttributedString *attrTitle =
|
||||
[[NSMutableAttributedString alloc] initWithAttributedString:[self attributedTitle]];
|
||||
int len = [attrTitle length];
|
||||
NSRange range = NSMakeRange(0, len);
|
||||
[attrTitle addAttribute:NSForegroundColorAttributeName value:textColor range:range];
|
||||
[attrTitle fixAttributesInRange:range];
|
||||
[self setAttributedTitle:attrTitle];
|
||||
[attrTitle release];
|
||||
}
|
||||
|
||||
- (void)setBezelStyle2:(NSBezelStyle)bezelStyle
|
||||
{
|
||||
if (bezelStyle != NSBezelStyleShadowlessSquare)
|
||||
[self setBordered: YES];
|
||||
[self setBezelStyle2: bezelStyle];
|
||||
}
|
||||
|
||||
+ (void) load
|
||||
{
|
||||
Method setBezelStyle = class_getInstanceMethod([NSButton class], @selector(setBezelStyle:));
|
||||
Method setBezelStyle2 = class_getInstanceMethod([NSButton class], @selector(setBezelStyle2:));
|
||||
method_exchangeImplementations(setBezelStyle, setBezelStyle2);
|
||||
}
|
||||
|
||||
- (NSFocusRingType) focusRingType
|
||||
{
|
||||
return NSFocusRingTypeNone;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* edit column for wxCocoaOutlineView */
|
||||
|
||||
#include <wx/dataview.h>
|
||||
#include <wx/osx/cocoa/dataview.h>
|
||||
#include <wx/osx/dataview.h>
|
||||
|
||||
@implementation wxCocoaOutlineView (Edit)
|
||||
|
||||
bool addObserver = false;
|
||||
|
||||
- (BOOL)outlineView: (NSOutlineView*) view shouldEditTableColumn:(nullable NSTableColumn *)tableColumn item:(nonnull id)item
|
||||
{
|
||||
NSClipView * clipView = [[self enclosingScrollView] contentView];
|
||||
if (!addObserver) {
|
||||
addObserver = true;
|
||||
clipView.postsBoundsChangedNotifications = YES;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(synchronizedViewContentBoundsDidChange:)
|
||||
name:NSViewBoundsDidChangeNotification
|
||||
object:clipView];
|
||||
}
|
||||
|
||||
wxDataViewColumn* const col((wxDataViewColumn *)[tableColumn getColumnPointer]);
|
||||
wxDataViewItem item2([static_cast<wxPointerObject *>(item) pointer]);
|
||||
|
||||
wxDataViewCtrl* const dvc = implementation->GetDataViewCtrl();
|
||||
// Before doing anything we send an event asking if editing of this item is really wanted.
|
||||
wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_EDITING_STARTED, dvc, col, item2);
|
||||
dvc->GetEventHandler()->ProcessEvent( event );
|
||||
if( !event.IsAllowed() )
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)synchronizedViewContentBoundsDidChange:(NSNotification *)notification
|
||||
{
|
||||
wxDataViewCtrl* const dvc = implementation->GetDataViewCtrl();
|
||||
wxDataViewCustomRenderer * r = dvc->GetCustomRendererPtr();
|
||||
if (r)
|
||||
r->FinishEditing();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* Font for wxTextCtrl */
|
||||
|
||||
@implementation NSTableHeaderCell (Font)
|
||||
|
||||
- (NSFont*) font
|
||||
{
|
||||
return Label::sysFont(13).OSXGetNSFont();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* remove focused border for wxTextCtrl */
|
||||
|
||||
@implementation NSTextField (FocusRing)
|
||||
|
||||
- (NSFocusRingType) focusRingType
|
||||
{
|
||||
return NSFocusRingTypeNone;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* gesture handle for Canvas3D */
|
||||
|
||||
@interface wxNSCustomOpenGLView : NSOpenGLView
|
||||
{
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@implementation wxNSCustomOpenGLView (Gesture)
|
||||
|
||||
wxEvtHandler * _gestureHandler = nullptr;
|
||||
|
||||
- (void) onGestureMove: (NSPanGestureRecognizer*) gesture
|
||||
{
|
||||
wxPanGestureEvent evt;
|
||||
NSPoint tr = [gesture translationInView: self];
|
||||
evt.SetDelta({(int) tr.x, (int) tr.y});
|
||||
[self postEvent:evt withGesture:gesture];
|
||||
}
|
||||
|
||||
- (void) onGestureScale: (NSMagnificationGestureRecognizer*) gesture
|
||||
{
|
||||
wxZoomGestureEvent evt;
|
||||
evt.SetZoomFactor(gesture.magnification + 1.0);
|
||||
[self postEvent:evt withGesture:gesture];
|
||||
}
|
||||
|
||||
- (void) onGestureRotate: (NSRotationGestureRecognizer*) gesture
|
||||
{
|
||||
wxRotateGestureEvent evt;
|
||||
evt.SetRotationAngle(-gesture.rotation);
|
||||
[self postEvent:evt withGesture:gesture];
|
||||
}
|
||||
|
||||
- (void) postEvent: (wxGestureEvent &) evt withGesture: (NSGestureRecognizer* ) gesture
|
||||
{
|
||||
NSPoint pos = [gesture locationInView: self];
|
||||
evt.SetPosition({(int) pos.x, (int) pos.y});
|
||||
if (gesture.state == NSGestureRecognizerStateBegan)
|
||||
evt.SetGestureStart();
|
||||
else if (gesture.state == NSGestureRecognizerStateEnded)
|
||||
evt.SetGestureEnd();
|
||||
_gestureHandler->ProcessEvent(evt);
|
||||
}
|
||||
|
||||
- (void) scrollWheel2:(NSEvent *)event
|
||||
{
|
||||
bool shiftDown = [event modifierFlags] & NSShiftKeyMask;
|
||||
if (_gestureHandler && shiftDown && event.hasPreciseScrollingDeltas) {
|
||||
wxPanGestureEvent evt;
|
||||
evt.SetDelta({-(int)[event scrollingDeltaX], - (int)[event scrollingDeltaY]});
|
||||
_gestureHandler->ProcessEvent(evt);
|
||||
} else {
|
||||
[self scrollWheel2: event];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void) load
|
||||
{
|
||||
Method scrollWheel = class_getInstanceMethod([wxNSCustomOpenGLView class], @selector(scrollWheel:));
|
||||
Method scrollWheel2 = class_getInstanceMethod([wxNSCustomOpenGLView class], @selector(scrollWheel2:));
|
||||
method_exchangeImplementations(scrollWheel, scrollWheel2);
|
||||
}
|
||||
|
||||
- (void) initGesturesWithHandler: (wxEvtHandler*) handler
|
||||
{
|
||||
// NSPanGestureRecognizer * pan = [[NSPanGestureRecognizer alloc] initWithTarget: self action: @selector(onGestureMove:)];
|
||||
// pan.numberOfTouchesRequired = 2;
|
||||
// pan.allowedTouchTypes = 0;
|
||||
// NSMagnificationGestureRecognizer * magnification = [[NSMagnificationGestureRecognizer alloc] initWithTarget: self action: @selector(onGestureScale:)];
|
||||
// NSRotationGestureRecognizer * rotation = [[NSRotationGestureRecognizer alloc] initWithTarget: self action: @selector(onGestureRotate:)];
|
||||
// [self addGestureRecognizer:pan];
|
||||
// [self addGestureRecognizer:magnification];
|
||||
// [self addGestureRecognizer:rotation];
|
||||
_gestureHandler = handler;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
void initGestures(void * view, wxEvtHandler * handler)
|
||||
{
|
||||
wxNSCustomOpenGLView * glView = (wxNSCustomOpenGLView *) view;
|
||||
[glView initGesturesWithHandler: handler];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
1540
src/slic3r/Utils/NetworkAgent.cpp
Normal file
1540
src/slic3r/Utils/NetworkAgent.cpp
Normal file
File diff suppressed because it is too large
Load Diff
337
src/slic3r/Utils/NetworkAgent.hpp
Normal file
337
src/slic3r/Utils/NetworkAgent.hpp
Normal file
@@ -0,0 +1,337 @@
|
||||
#ifndef __NETWORK_Agent_HPP__
|
||||
#define __NETWORK_Agent_HPP__
|
||||
|
||||
#include "qidi_networking.hpp"
|
||||
#include "libslic3r/ProjectTask.hpp"
|
||||
|
||||
using namespace QDT;
|
||||
|
||||
namespace Slic3r {
|
||||
typedef bool (*func_check_debug_consistent)(bool is_debug);
|
||||
typedef std::string (*func_get_version)(void);
|
||||
typedef void* (*func_create_agent)(std::string log_dir);
|
||||
typedef int (*func_destroy_agent)(void *agent);
|
||||
typedef int (*func_init_log)(void *agent);
|
||||
typedef int (*func_set_config_dir)(void *agent, std::string config_dir);
|
||||
typedef int (*func_set_cert_file)(void *agent, std::string folder, std::string filename);
|
||||
typedef int (*func_set_country_code)(void *agent, std::string country_code);
|
||||
typedef int (*func_start)(void *agent);
|
||||
typedef int (*func_set_on_ssdp_msg_fn)(void *agent, OnMsgArrivedFn fn);
|
||||
typedef int (*func_set_on_user_login_fn)(void *agent, OnUserLoginFn fn);
|
||||
typedef int (*func_set_on_printer_connected_fn)(void *agent, OnPrinterConnectedFn fn);
|
||||
typedef int (*func_set_on_server_connected_fn)(void *agent, OnServerConnectedFn fn);
|
||||
typedef int (*func_set_on_http_error_fn)(void *agent, OnHttpErrorFn fn);
|
||||
typedef int (*func_set_get_country_code_fn)(void *agent, GetCountryCodeFn fn);
|
||||
typedef int (*func_set_on_subscribe_failure_fn)(void *agent, GetSubscribeFailureFn fn);
|
||||
typedef int (*func_set_on_message_fn)(void *agent, OnMessageFn fn);
|
||||
typedef int (*func_set_on_user_message_fn)(void *agent, OnMessageFn fn);
|
||||
typedef int (*func_set_on_local_connect_fn)(void *agent, OnLocalConnectedFn fn);
|
||||
typedef int (*func_set_on_local_message_fn)(void *agent, OnMessageFn fn);
|
||||
typedef int (*func_set_queue_on_main_fn)(void *agent, QueueOnMainFn fn);
|
||||
typedef int (*func_connect_server)(void *agent);
|
||||
typedef bool (*func_is_server_connected)(void *agent);
|
||||
typedef int (*func_refresh_connection)(void *agent);
|
||||
typedef int (*func_start_subscribe)(void *agent, std::string module);
|
||||
typedef int (*func_stop_subscribe)(void *agent, std::string module);
|
||||
typedef int (*func_add_subscribe)(void *agent, std::vector<std::string> dev_list);
|
||||
typedef int (*func_del_subscribe)(void *agent, std::vector<std::string> dev_list);
|
||||
typedef void (*func_enable_multi_machine)(void *agent, bool enable);
|
||||
typedef int (*func_start_device_subscribe)(void* agent);
|
||||
typedef int (*func_stop_device_subscribe)(void* agent);
|
||||
typedef int (*func_send_message)(void *agent, std::string dev_id, std::string json_str, int qos);
|
||||
typedef int (*func_connect_printer)(void *agent, std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
|
||||
typedef int (*func_disconnect_printer)(void *agent);
|
||||
typedef int (*func_send_message_to_printer)(void *agent, std::string dev_id, std::string json_str, int qos);
|
||||
typedef bool (*func_start_discovery)(void *agent, bool start, bool sending);
|
||||
typedef int (*func_change_user)(void *agent, std::string user_info);
|
||||
typedef bool (*func_is_user_login)(void *agent);
|
||||
typedef int (*func_user_logout)(void *agent);
|
||||
typedef std::string (*func_get_user_id)(void *agent);
|
||||
typedef std::string (*func_get_user_name)(void *agent);
|
||||
typedef std::string (*func_get_user_avatar)(void *agent);
|
||||
typedef std::string (*func_get_user_nickanme)(void *agent);
|
||||
typedef std::string (*func_build_login_cmd)(void *agent);
|
||||
typedef std::string (*func_build_logout_cmd)(void *agent);
|
||||
typedef std::string (*func_build_login_info)(void *agent);
|
||||
typedef int (*func_get_model_id_from_desgin_id)(void *agent, std::string& desgin_id, std::string& model_id);
|
||||
typedef int (*func_ping_bind)(void *agent, std::string ping_code);
|
||||
typedef int (*func_bind)(void *agent, std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn);
|
||||
typedef int (*func_unbind)(void *agent, std::string dev_id);
|
||||
typedef std::string (*func_get_qidilab_host)(void *agent);
|
||||
typedef std::string (*func_get_user_selected_machine)(void *agent);
|
||||
typedef int (*func_set_user_selected_machine)(void *agent, std::string dev_id);
|
||||
typedef int (*func_start_print)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
typedef int (*func_start_local_print_with_record)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
typedef int (*func_start_send_gcode_to_sdcard)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
typedef int (*func_start_local_print)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
typedef int (*func_start_sdcard_print)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
typedef int (*func_get_user_presets)(void *agent, std::map<std::string, std::map<std::string, std::string>>* user_presets);
|
||||
typedef std::string (*func_request_setting_id)(void *agent, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code);
|
||||
typedef int (*func_put_setting)(void *agent, std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code);
|
||||
typedef int (*func_get_setting_list)(void *agent, std::string bundle_version, ProgressFn pro_fn, WasCancelledFn cancel_fn);
|
||||
typedef int (*func_get_setting_list2)(void *agent, std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn, WasCancelledFn cancel_fn);
|
||||
typedef int (*func_delete_setting)(void *agent, std::string setting_id);
|
||||
typedef std::string (*func_get_studio_info_url)(void *agent);
|
||||
typedef int (*func_set_extra_http_header)(void *agent, std::map<std::string, std::string> extra_headers);
|
||||
typedef int (*func_get_my_message)(void *agent, int type, int after, int limit, unsigned int* http_code, std::string* http_body);
|
||||
typedef int (*func_check_user_task_report)(void *agent, int* task_id, bool* printable);
|
||||
typedef int (*func_get_user_print_info)(void *agent, unsigned int* http_code, std::string* http_body);
|
||||
typedef int (*func_get_user_tasks)(void *agent, TaskQueryParams params, std::string* http_body);
|
||||
typedef int (*func_get_printer_firmware)(void *agent, std::string dev_id, unsigned* http_code, std::string* http_body);
|
||||
typedef int (*func_get_task_plate_index)(void *agent, std::string task_id, int* plate_index);
|
||||
typedef int (*func_get_user_info)(void *agent, int* identifier);
|
||||
typedef int (*func_request_bind_ticket)(void *agent, std::string* ticket);
|
||||
typedef int (*func_get_subtask_info)(void *agent, std::string subtask_id, std::string* task_json, unsigned int* http_code, std::string *http_body);
|
||||
typedef int (*func_get_slice_info)(void *agent, std::string project_id, std::string profile_id, int plate_index, std::string* slice_json);
|
||||
typedef int (*func_query_bind_status)(void *agent, std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body);
|
||||
typedef int (*func_modify_printer_name)(void *agent, std::string dev_id, std::string dev_name);
|
||||
typedef int (*func_get_camera_url)(void *agent, std::string dev_id, std::function<void(std::string)> callback);
|
||||
typedef int (*func_get_design_staffpick)(void *agent, int offset, int limit, std::function<void(std::string)> callback);
|
||||
typedef int (*func_start_pubilsh)(void *agent, PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out);
|
||||
typedef int (*func_get_profile_3mf)(void *agent, QDTProfile* profile);
|
||||
typedef int (*func_get_model_publish_url)(void *agent, std::string* url);
|
||||
typedef int (*func_get_subtask)(void *agent, QDTModelTask* task, OnGetSubTaskFn getsub_fn);
|
||||
typedef int (*func_get_model_mall_home_url)(void *agent, std::string* url);
|
||||
typedef int (*func_get_model_mall_detail_url)(void *agent, std::string* url, std::string id);
|
||||
typedef int (*func_get_my_profile)(void *agent, std::string token, unsigned int *http_code, std::string *http_body);
|
||||
typedef int (*func_track_enable)(void *agent, bool enable);
|
||||
typedef int (*func_track_remove_files)(void *agent);
|
||||
typedef int (*func_track_event)(void *agent, std::string evt_key, std::string content);
|
||||
typedef int (*func_track_header)(void *agent, std::string header);
|
||||
typedef int (*func_track_update_property)(void *agent, std::string name, std::string value, std::string type);
|
||||
typedef int (*func_track_get_property)(void *agent, std::string name, std::string& value, std::string type);
|
||||
typedef int (*func_put_model_mall_rating_url)(
|
||||
void *agent, int rating_id, int score, std::string content, std::vector<std::string> images, unsigned int &http_code, std::string &http_error);
|
||||
typedef int (*func_get_oss_config)(void *agent, std::string &config, std::string country_code, unsigned int &http_code, std::string &http_error);
|
||||
typedef int (*func_put_rating_picture_oss)(
|
||||
void *agent, std::string &config, std::string &pic_oss_path, std::string model_id, int profile_id, unsigned int &http_code, std::string &http_error);
|
||||
typedef int (*func_get_model_mall_rating_result)(void *agent, int job_id, std::string &rating_result, unsigned int &http_code, std::string &http_error);
|
||||
|
||||
typedef int (*func_get_mw_user_preference)(void *agent, std::function<void(std::string)> callback);
|
||||
typedef int (*func_get_mw_user_4ulist)(void *agent, int seed, int limit, std::function<void(std::string)> callback);
|
||||
|
||||
//the NetworkAgent class
|
||||
class NetworkAgent
|
||||
{
|
||||
|
||||
public:
|
||||
static int initialize_network_module(bool using_backup = false);
|
||||
static int unload_network_module();
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
static HMODULE get_qidi_source_entry();
|
||||
#else
|
||||
static void* get_qidi_source_entry();
|
||||
#endif
|
||||
static std::string get_version();
|
||||
static void* get_network_function(const char* name);
|
||||
NetworkAgent(std::string log_dir);
|
||||
~NetworkAgent();
|
||||
|
||||
int init_log();
|
||||
int set_config_dir(std::string config_dir);
|
||||
int set_cert_file(std::string folder, std::string filename);
|
||||
int set_country_code(std::string country_code);
|
||||
int start();
|
||||
int set_on_ssdp_msg_fn(OnMsgArrivedFn fn);
|
||||
int set_on_user_login_fn(OnUserLoginFn fn);
|
||||
int set_on_printer_connected_fn(OnPrinterConnectedFn fn);
|
||||
int set_on_server_connected_fn(OnServerConnectedFn fn);
|
||||
int set_on_http_error_fn(OnHttpErrorFn fn);
|
||||
int set_get_country_code_fn(GetCountryCodeFn fn);
|
||||
int set_on_subscribe_failure_fn(GetSubscribeFailureFn fn);
|
||||
int set_on_message_fn(OnMessageFn fn);
|
||||
int set_on_user_message_fn(OnMessageFn fn);
|
||||
int set_on_local_connect_fn(OnLocalConnectedFn fn);
|
||||
int set_on_local_message_fn(OnMessageFn fn);
|
||||
int set_queue_on_main_fn(QueueOnMainFn fn);
|
||||
int connect_server();
|
||||
bool is_server_connected();
|
||||
int refresh_connection();
|
||||
int start_subscribe(std::string module);
|
||||
int stop_subscribe(std::string module);
|
||||
int add_subscribe(std::vector<std::string> dev_list);
|
||||
int del_subscribe(std::vector<std::string> dev_list);
|
||||
void enable_multi_machine(bool enable);
|
||||
int start_device_subscribe();
|
||||
int stop_device_subscribe();
|
||||
int send_message(std::string dev_id, std::string json_str, int qos);
|
||||
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
|
||||
int disconnect_printer();
|
||||
int send_message_to_printer(std::string dev_id, std::string json_str, int qos);
|
||||
bool start_discovery(bool start, bool sending);
|
||||
int change_user(std::string user_info);
|
||||
bool is_user_login();
|
||||
int user_logout();
|
||||
std::string get_user_id();
|
||||
std::string get_user_name();
|
||||
std::string get_user_avatar();
|
||||
std::string get_user_nickanme();
|
||||
std::string build_login_cmd();
|
||||
std::string build_logout_cmd();
|
||||
std::string build_login_info();
|
||||
int get_model_id_from_desgin_id(std::string& desgin_id, std::string& model_id);
|
||||
int ping_bind(std::string ping_code);
|
||||
int bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn);
|
||||
int unbind(std::string dev_id);
|
||||
std::string get_qidilab_host();
|
||||
std::string get_user_selected_machine();
|
||||
int set_user_selected_machine(std::string dev_id);
|
||||
int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
int start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
int start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
int get_user_presets(std::map<std::string, std::map<std::string, std::string>>* user_presets);
|
||||
std::string request_setting_id(std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code);
|
||||
int put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code);
|
||||
int get_setting_list(std::string bundle_version, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr);
|
||||
int get_setting_list2(std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr);
|
||||
int delete_setting(std::string setting_id);
|
||||
std::string get_studio_info_url();
|
||||
int set_extra_http_header(std::map<std::string, std::string> extra_headers);
|
||||
int get_my_message(int type, int after, int limit, unsigned int* http_code, std::string* http_body);
|
||||
int check_user_task_report(int* task_id, bool* printable);
|
||||
int get_user_print_info(unsigned int* http_code, std::string* http_body);
|
||||
int get_user_tasks(TaskQueryParams params, std::string* http_body);
|
||||
int get_printer_firmware(std::string dev_id, unsigned* http_code, std::string* http_body);
|
||||
int get_task_plate_index(std::string task_id, int* plate_index);
|
||||
int get_user_info(int* identifier);
|
||||
int request_bind_ticket(std::string* ticket);
|
||||
int get_subtask_info(std::string subtask_id, std::string* task_json, unsigned int* http_code, std::string* http_body);
|
||||
int get_slice_info(std::string project_id, std::string profile_id, int plate_index, std::string* slice_json);
|
||||
int query_bind_status(std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body);
|
||||
int modify_printer_name(std::string dev_id, std::string dev_name);
|
||||
int get_camera_url(std::string dev_id, std::function<void(std::string)> callback);
|
||||
int get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback);
|
||||
int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out);
|
||||
int get_profile_3mf(QDTProfile* profile);
|
||||
int get_model_publish_url(std::string* url);
|
||||
int get_subtask(QDTModelTask* task, OnGetSubTaskFn getsub_fn);
|
||||
int get_model_mall_home_url(std::string* url);
|
||||
int get_model_mall_detail_url(std::string* url, std::string id);
|
||||
int get_my_profile(std::string token, unsigned int* http_code, std::string* http_body);
|
||||
int track_enable(bool enable);
|
||||
int track_remove_files();
|
||||
int track_event(std::string evt_key, std::string content);
|
||||
int track_header(std::string header);
|
||||
int track_update_property(std::string name, std::string value, std::string type = "string");
|
||||
int track_get_property(std::string name, std::string& value, std::string type = "string");
|
||||
int put_model_mall_rating(int design_id, int score, std::string content, std::vector<std::string> images, unsigned int &http_code, std::string &http_error);
|
||||
int get_oss_config(std::string &config, std::string country_code, unsigned int &http_code, std::string &http_error);
|
||||
int put_rating_picture_oss(std::string &config, std::string &pic_oss_path, std::string model_id, int profile_id, unsigned int &http_code, std::string &http_error);
|
||||
int get_model_mall_rating_result(int job_id, std::string &rating_result, unsigned int &http_code, std::string &http_error);
|
||||
bool get_track_enable() { return enable_track; }
|
||||
|
||||
int get_mw_user_preference(std::function<void(std::string)> callback);
|
||||
int get_mw_user_4ulist(int seed, int limit, std::function<void(std::string)> callback);
|
||||
|
||||
private:
|
||||
bool enable_track = false;
|
||||
void* network_agent { nullptr };
|
||||
|
||||
static func_check_debug_consistent check_debug_consistent_ptr;
|
||||
static func_get_version get_version_ptr;
|
||||
static func_create_agent create_agent_ptr;
|
||||
static func_destroy_agent destroy_agent_ptr;
|
||||
static func_init_log init_log_ptr;
|
||||
static func_set_config_dir set_config_dir_ptr;
|
||||
static func_set_cert_file set_cert_file_ptr;
|
||||
static func_set_country_code set_country_code_ptr;
|
||||
static func_start start_ptr;
|
||||
static func_set_on_ssdp_msg_fn set_on_ssdp_msg_fn_ptr;
|
||||
static func_set_on_user_login_fn set_on_user_login_fn_ptr;
|
||||
static func_set_on_printer_connected_fn set_on_printer_connected_fn_ptr;
|
||||
static func_set_on_server_connected_fn set_on_server_connected_fn_ptr;
|
||||
static func_set_on_http_error_fn set_on_http_error_fn_ptr;
|
||||
static func_set_get_country_code_fn set_get_country_code_fn_ptr;
|
||||
static func_set_on_subscribe_failure_fn set_on_subscribe_failure_fn_ptr;
|
||||
static func_set_on_message_fn set_on_message_fn_ptr;
|
||||
static func_set_on_user_message_fn set_on_user_message_fn_ptr;
|
||||
static func_set_on_local_connect_fn set_on_local_connect_fn_ptr;
|
||||
static func_set_on_local_message_fn set_on_local_message_fn_ptr;
|
||||
static func_set_queue_on_main_fn set_queue_on_main_fn_ptr;
|
||||
static func_connect_server connect_server_ptr;
|
||||
static func_is_server_connected is_server_connected_ptr;
|
||||
static func_refresh_connection refresh_connection_ptr;
|
||||
static func_start_subscribe start_subscribe_ptr;
|
||||
static func_stop_subscribe stop_subscribe_ptr;
|
||||
static func_add_subscribe add_subscribe_ptr;
|
||||
static func_del_subscribe del_subscribe_ptr;
|
||||
static func_enable_multi_machine enable_multi_machine_ptr;
|
||||
static func_start_device_subscribe start_device_subscribe_ptr;
|
||||
static func_stop_device_subscribe stop_device_subscribe_ptr;
|
||||
static func_send_message send_message_ptr;
|
||||
static func_connect_printer connect_printer_ptr;
|
||||
static func_disconnect_printer disconnect_printer_ptr;
|
||||
static func_send_message_to_printer send_message_to_printer_ptr;
|
||||
static func_start_discovery start_discovery_ptr;
|
||||
static func_change_user change_user_ptr;
|
||||
static func_is_user_login is_user_login_ptr;
|
||||
static func_user_logout user_logout_ptr;
|
||||
static func_get_user_id get_user_id_ptr;
|
||||
static func_get_user_name get_user_name_ptr;
|
||||
static func_get_user_avatar get_user_avatar_ptr;
|
||||
static func_get_user_nickanme get_user_nickanme_ptr;
|
||||
static func_build_login_cmd build_login_cmd_ptr;
|
||||
static func_build_logout_cmd build_logout_cmd_ptr;
|
||||
static func_build_login_info build_login_info_ptr;
|
||||
static func_get_model_id_from_desgin_id get_model_id_from_desgin_id_ptr;
|
||||
static func_ping_bind ping_bind_ptr;
|
||||
static func_bind bind_ptr;
|
||||
static func_unbind unbind_ptr;
|
||||
static func_get_qidilab_host get_qidilab_host_ptr;
|
||||
static func_get_user_selected_machine get_user_selected_machine_ptr;
|
||||
static func_set_user_selected_machine set_user_selected_machine_ptr;
|
||||
static func_start_print start_print_ptr;
|
||||
static func_start_local_print_with_record start_local_print_with_record_ptr;
|
||||
static func_start_send_gcode_to_sdcard start_send_gcode_to_sdcard_ptr;
|
||||
static func_start_local_print start_local_print_ptr;
|
||||
static func_start_sdcard_print start_sdcard_print_ptr;
|
||||
static func_get_user_presets get_user_presets_ptr;
|
||||
static func_request_setting_id request_setting_id_ptr;
|
||||
static func_put_setting put_setting_ptr;
|
||||
static func_get_setting_list get_setting_list_ptr;
|
||||
static func_get_setting_list2 get_setting_list2_ptr;
|
||||
static func_delete_setting delete_setting_ptr;
|
||||
static func_get_studio_info_url get_studio_info_url_ptr;
|
||||
static func_set_extra_http_header set_extra_http_header_ptr;
|
||||
static func_get_my_message get_my_message_ptr;
|
||||
static func_check_user_task_report check_user_task_report_ptr;
|
||||
static func_get_user_print_info get_user_print_info_ptr;
|
||||
static func_get_user_tasks get_user_tasks_ptr;
|
||||
static func_get_printer_firmware get_printer_firmware_ptr;
|
||||
static func_get_task_plate_index get_task_plate_index_ptr;
|
||||
static func_get_user_info get_user_info_ptr;
|
||||
static func_request_bind_ticket request_bind_ticket_ptr;
|
||||
static func_get_subtask_info get_subtask_info_ptr;
|
||||
static func_get_slice_info get_slice_info_ptr;
|
||||
static func_query_bind_status query_bind_status_ptr;
|
||||
static func_modify_printer_name modify_printer_name_ptr;
|
||||
static func_get_camera_url get_camera_url_ptr;
|
||||
static func_get_design_staffpick get_design_staffpick_ptr;
|
||||
static func_start_pubilsh start_publish_ptr;
|
||||
static func_get_profile_3mf get_profile_3mf_ptr;
|
||||
static func_get_model_publish_url get_model_publish_url_ptr;
|
||||
static func_get_subtask get_subtask_ptr;
|
||||
static func_get_model_mall_home_url get_model_mall_home_url_ptr;
|
||||
static func_get_model_mall_detail_url get_model_mall_detail_url_ptr;
|
||||
static func_get_my_profile get_my_profile_ptr;
|
||||
static func_track_enable track_enable_ptr;
|
||||
static func_track_remove_files track_remove_files_ptr;
|
||||
static func_track_event track_event_ptr;
|
||||
static func_track_header track_header_ptr;
|
||||
static func_track_update_property track_update_property_ptr;
|
||||
static func_track_get_property track_get_property_ptr;
|
||||
static func_put_model_mall_rating_url put_model_mall_rating_url_ptr;
|
||||
static func_get_oss_config get_oss_config_ptr;
|
||||
static func_put_rating_picture_oss put_rating_picture_oss_ptr;
|
||||
static func_get_model_mall_rating_result get_model_mall_rating_result_ptr;
|
||||
|
||||
static func_get_mw_user_preference get_mw_user_preference_ptr;
|
||||
static func_get_mw_user_4ulist get_mw_user_4ulist_ptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
492
src/slic3r/Utils/OctoPrint.cpp
Normal file
492
src/slic3r/Utils/OctoPrint.cpp
Normal file
@@ -0,0 +1,492 @@
|
||||
#include "OctoPrint.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 <curl/curl.h>
|
||||
|
||||
#include <wx/progdlg.h>
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "Http.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
|
||||
//B
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#ifdef WIN32
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
namespace {
|
||||
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 + "]";
|
||||
|
||||
#if 0
|
||||
//URI = scheme ":"["//"[userinfo "@"] host [":" port]] path["?" query]["#" fragment]
|
||||
std::string final_addr = orig_addr;
|
||||
// http
|
||||
size_t double_dash = orig_addr.find("//");
|
||||
size_t host_start = (double_dash == std::string::npos ? 0 : double_dash + 2);
|
||||
// userinfo
|
||||
size_t at = orig_addr.find("@");
|
||||
host_start = (at != std::string::npos && at > host_start ? at + 1 : host_start);
|
||||
// end of host, could be port(:), subpath(/) (could be query(?) or fragment(#)?)
|
||||
// or it will be ']' if address is ipv6 )
|
||||
size_t potencial_host_end = orig_addr.find_first_of(":/", host_start);
|
||||
// if there are more ':' it must be ipv6
|
||||
if (potencial_host_end != std::string::npos && orig_addr[potencial_host_end] == ':' && orig_addr.rfind(':') != potencial_host_end) {
|
||||
size_t ipv6_end = orig_addr.find(']', host_start);
|
||||
// DK: Uncomment and replace orig_addr.length() if we want to allow subpath after ipv6 without [] parentheses.
|
||||
potencial_host_end = (ipv6_end != std::string::npos ? ipv6_end + 1 : orig_addr.length()); //orig_addr.find('/', host_start));
|
||||
}
|
||||
size_t host_end = (potencial_host_end != std::string::npos ? potencial_host_end : orig_addr.length());
|
||||
// now host_start and host_end should mark where to put resolved addr
|
||||
// check host_start. if its nonsense, lets just use original addr (or resolved addr?)
|
||||
if (host_start >= orig_addr.length()) {
|
||||
return final_addr;
|
||||
}
|
||||
final_addr.replace(host_start, host_end - host_start, sub_addr);
|
||||
return final_addr;
|
||||
#else
|
||||
// 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
|
||||
}
|
||||
} //namespace
|
||||
#endif // WIN32
|
||||
|
||||
OctoPrint::OctoPrint(DynamicPrintConfig *config, bool add_port)
|
||||
: m_host(add_port ? config->opt_string("print_host").find(":") == std::string::npos ? config->opt_string("print_host") + ":10088" :
|
||||
config->opt_string("print_host") :
|
||||
config->opt_string("print_host"))
|
||||
, m_show_ip(config->opt_string("print_host"))
|
||||
{
|
||||
}
|
||||
|
||||
OctoPrint::OctoPrint(std::string host, std::string local_ip)
|
||||
: m_host(host), m_show_ip(local_ip) {}
|
||||
|
||||
const char* OctoPrint::get_name() const { return "OctoPrint"; }
|
||||
|
||||
bool OctoPrint::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::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str());
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
wxString OctoPrint::get_test_ok_msg () const
|
||||
{
|
||||
// y3
|
||||
return _(L("Connection to Moonraker works correctly."));
|
||||
}
|
||||
|
||||
wxString OctoPrint::get_test_failed_msg (wxString &msg) const
|
||||
{
|
||||
return GUI::from_u8((boost::format("%s: %s\n\n%s")
|
||||
% _utf8(L("Could not connect to OctoPrint"))
|
||||
% std::string(msg.ToUTF8())
|
||||
% _utf8(L("Note: OctoPrint version at least 1.1.0 is required."))).str());
|
||||
}
|
||||
|
||||
bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_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();
|
||||
|
||||
// If test fails, test_msg_or_host_ip contains the error message.
|
||||
// Otherwise on Windows it contains the resolved IP address of the host.
|
||||
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() ||
|
||||
m_host.find("aws") != -1 || m_host.find("aliyun") != -1)
|
||||
#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
|
||||
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");
|
||||
|
||||
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) << "Octoprint: Upload canceled";
|
||||
res = false;
|
||||
}
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
#endif
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool OctoPrint::validate_version_text(const boost::optional<std::string> &version_text) const
|
||||
{
|
||||
return version_text ? boost::starts_with(*version_text, "OctoPrint") : true;
|
||||
}
|
||||
|
||||
void OctoPrint::set_auth(Http &http) const
|
||||
{
|
||||
http.header("X-Api-Key", m_apikey);
|
||||
|
||||
if (!m_cafile.empty()) {
|
||||
http.ca_file(m_cafile);
|
||||
}
|
||||
}
|
||||
|
||||
std::string OctoPrint::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();
|
||||
}
|
||||
}
|
||||
|
||||
SL1Host::SL1Host(DynamicPrintConfig *config) :
|
||||
OctoPrint(config, true),
|
||||
m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
|
||||
m_username(config->opt_string("printhost_user")),
|
||||
m_password(config->opt_string("printhost_password"))
|
||||
{
|
||||
}
|
||||
|
||||
// SL1Host
|
||||
const char* SL1Host::get_name() const { return "SL1Host"; }
|
||||
|
||||
wxString SL1Host::get_test_ok_msg () const
|
||||
{
|
||||
return _(L("Connection to Prusa SL1 / SL1S works correctly."));
|
||||
}
|
||||
|
||||
wxString SL1Host::get_test_failed_msg (wxString &msg) const
|
||||
{
|
||||
return GUI::from_u8((boost::format("%s: %s")
|
||||
% _utf8(L("Could not connect to Prusa SLA"))
|
||||
% std::string(msg.ToUTF8())).str());
|
||||
}
|
||||
|
||||
bool SL1Host::validate_version_text(const boost::optional<std::string> &version_text) const
|
||||
{
|
||||
return version_text ? boost::starts_with(*version_text, "Prusa SLA") : false;
|
||||
}
|
||||
|
||||
void SL1Host::set_auth(Http &http) const
|
||||
{
|
||||
switch (m_authorization_type) {
|
||||
case atKeyPassword:
|
||||
http.header("X-Api-Key", get_apikey());
|
||||
break;
|
||||
case atUserPassword:
|
||||
http.auth_digest(m_username, m_password);
|
||||
break;
|
||||
}
|
||||
|
||||
if (! get_cafile().empty()) {
|
||||
http.ca_file(get_cafile());
|
||||
}
|
||||
}
|
||||
|
||||
// PrusaLink
|
||||
PrusaLink::PrusaLink(DynamicPrintConfig* config) :
|
||||
OctoPrint(config, true),
|
||||
m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
|
||||
m_username(config->opt_string("printhost_user")),
|
||||
m_password(config->opt_string("printhost_password"))
|
||||
{
|
||||
}
|
||||
|
||||
const char* PrusaLink::get_name() const { return "PrusaLink"; }
|
||||
|
||||
wxString PrusaLink::get_test_ok_msg() const
|
||||
{
|
||||
return _(L("Connection to PrusaLink works correctly."));
|
||||
}
|
||||
|
||||
wxString PrusaLink::get_test_failed_msg(wxString& msg) const
|
||||
{
|
||||
return GUI::from_u8((boost::format("%s: %s")
|
||||
% _utf8(L("Could not connect to PrusaLink"))
|
||||
% std::string(msg.ToUTF8())).str());
|
||||
}
|
||||
|
||||
bool PrusaLink::validate_version_text(const boost::optional<std::string>& version_text) const
|
||||
{
|
||||
return version_text ? (boost::starts_with(*version_text, "PrusaLink") || boost::starts_with(*version_text, "OctoPrint")) : false;
|
||||
}
|
||||
|
||||
void PrusaLink::set_auth(Http& http) const
|
||||
{
|
||||
switch (m_authorization_type) {
|
||||
case atKeyPassword:
|
||||
http.header("X-Api-Key", get_apikey());
|
||||
break;
|
||||
case atUserPassword:
|
||||
http.auth_digest(m_username, m_password);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!get_cafile().empty()) {
|
||||
http.ca_file(get_cafile());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool QIDILink::get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const
|
||||
{
|
||||
const char* name = get_name();
|
||||
|
||||
bool res = true;
|
||||
auto url = make_url("api/v1/storage");
|
||||
wxString error_msg;
|
||||
|
||||
struct StorageInfo {
|
||||
wxString path;
|
||||
wxString name;
|
||||
bool read_only = false;
|
||||
long long free_space = -1;
|
||||
};
|
||||
std::vector<StorageInfo> storage;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get storage at: %2%") % name % url;
|
||||
|
||||
wxString wlang = GUI::wxGetApp().current_language_code();
|
||||
std::string lang = GUI::format(wlang.SubString(0, 1));
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
set_auth(http);
|
||||
http.header("Accept-Language", lang);
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting storage: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
error_msg = L"\n\n" + boost::nowide::widen(error);
|
||||
res = false;
|
||||
// If status is 0, the communication with the printer has failed completely (most likely a timeout), if the status is <= 400, it is an error returned by the pritner.
|
||||
// If 0, we can show error to the user now, as we know the communication has failed. (res = true will do the trick.)
|
||||
// if not 0, we must not show error, as not all printers support api/v1/storage endpoint.
|
||||
// So we must be extra careful here, or we might be showing errors on perfectly fine communication.
|
||||
if (status == 0)
|
||||
res = true;
|
||||
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got storage: %2%") % name % body;
|
||||
try
|
||||
{
|
||||
std::stringstream ss(body);
|
||||
pt::ptree ptree;
|
||||
pt::read_json(ss, ptree);
|
||||
|
||||
// what if there is more structure added in the future? Enumerate all elements?
|
||||
if (ptree.front().first != "storage_list") {
|
||||
res = false;
|
||||
return;
|
||||
}
|
||||
// each storage has own subtree of storage_list
|
||||
for (const auto& section : ptree.front().second) {
|
||||
const auto name = section.second.get_optional<std::string>("name");
|
||||
const auto path = section.second.get_optional<std::string>("path");
|
||||
const auto space = section.second.get_optional<std::string>("free_space");
|
||||
const auto read_only = section.second.get_optional<bool>("read_only");
|
||||
const auto ro = section.second.get_optional<bool>("ro"); // In QIDILink 0.7.0RC2 "read_only" value is stored under "ro".
|
||||
const auto available = section.second.get_optional<bool>("available");
|
||||
if (path && (!available || *available)) {
|
||||
StorageInfo si;
|
||||
si.path = boost::nowide::widen(*path);
|
||||
si.name = name ? boost::nowide::widen(*name) : wxString();
|
||||
// If read_only is missing, assume it is NOT read only.
|
||||
// si.read_only = read_only ? *read_only : false; // version without "ro"
|
||||
si.read_only = (read_only ? *read_only : (ro ? *ro : false));
|
||||
si.free_space = space ? std::stoll(*space) : 1; // If free_space is missing, assume there is free space.
|
||||
storage.emplace_back(std::move(si));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
res = false;
|
||||
}
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
|
||||
#endif // WIN32
|
||||
.perform_sync();
|
||||
|
||||
for (const auto& si : storage) {
|
||||
if (!si.read_only && si.free_space > 0) {
|
||||
storage_path.push_back(si.path);
|
||||
storage_name.push_back(si.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (res && storage_path.empty()) {
|
||||
if (!storage.empty()) { // otherwise error_msg is already filled
|
||||
error_msg = L"\n\n" + _L("Storages found") + L": \n";
|
||||
for (const auto& si : storage) {
|
||||
error_msg += GUI::format_wxstr(si.read_only ?
|
||||
// TRN %1% = storage path
|
||||
_L("%1% : read only") :
|
||||
// TRN %1% = storage path
|
||||
_L("%1% : no free space"), si.path) + L"\n";
|
||||
}
|
||||
}
|
||||
// TRN %1% = host
|
||||
std::string message = GUI::format(_L("Upload has failed. There is no suitable storage found at %1%."), m_host) + GUI::into_u8(error_msg);
|
||||
BOOST_LOG_TRIVIAL(error) << message;
|
||||
throw Slic3r::IOError(message);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
117
src/slic3r/Utils/OctoPrint.hpp
Normal file
117
src/slic3r/Utils/OctoPrint.hpp
Normal file
@@ -0,0 +1,117 @@
|
||||
#ifndef slic3r_OctoPrint_hpp_
|
||||
#define slic3r_OctoPrint_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "PrintHost.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
|
||||
class OctoPrint : public PrintHost
|
||||
{
|
||||
public:
|
||||
OctoPrint(DynamicPrintConfig *config, bool add_port);
|
||||
OctoPrint(std::string host,std::string local_ip);
|
||||
~OctoPrint() 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) 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_show_ip; }
|
||||
const std::string& get_apikey() const { return m_apikey; }
|
||||
const std::string& get_cafile() const { return m_cafile; }
|
||||
|
||||
protected:
|
||||
std::string m_show_ip;
|
||||
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;
|
||||
|
||||
protected:
|
||||
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
||||
|
||||
};
|
||||
|
||||
class SL1Host: public OctoPrint
|
||||
{
|
||||
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;
|
||||
|
||||
private:
|
||||
void set_auth(Http &http) const override;
|
||||
|
||||
// Host authorization type.
|
||||
AuthorizationType m_authorization_type;
|
||||
// username and password for HTTP Digest Authentization (RFC RFC2617)
|
||||
std::string m_username;
|
||||
std::string m_password;
|
||||
};
|
||||
|
||||
class PrusaLink : public OctoPrint
|
||||
{
|
||||
public:
|
||||
PrusaLink(DynamicPrintConfig* config);
|
||||
~PrusaLink() 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 PrintHostPostUploadAction::StartPrint; }
|
||||
|
||||
protected:
|
||||
bool validate_version_text(const boost::optional<std::string>& version_text) const override;
|
||||
|
||||
private:
|
||||
void set_auth(Http& http) const override;
|
||||
|
||||
// Host authorization type.
|
||||
AuthorizationType m_authorization_type;
|
||||
// username and password for HTTP Digest Authentization (RFC RFC2617)
|
||||
std::string m_username;
|
||||
std::string m_password;
|
||||
};
|
||||
|
||||
|
||||
class QIDILink : public OctoPrint
|
||||
{
|
||||
public:
|
||||
bool get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const override;
|
||||
};
|
||||
|
||||
class QIDIConnect : public QIDILink
|
||||
{
|
||||
public:
|
||||
bool get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
1688
src/slic3r/Utils/PresetUpdater.cpp
Normal file
1688
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(std::string http_url, std::string language, std::string plugin_version, PresetBundle *preset_bundle);
|
||||
|
||||
// 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;
|
||||
|
||||
// "Update" a list of bundles from resources (behaves like an online update).
|
||||
bool install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
|
||||
|
||||
void on_update_notification_confirm();
|
||||
void do_printer_config_update();
|
||||
|
||||
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
|
||||
498
src/slic3r/Utils/PrintHost.cpp
Normal file
498
src/slic3r/Utils/PrintHost.cpp
Normal file
@@ -0,0 +1,498 @@
|
||||
#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 "../GUI/PrintHostDialogs.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
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, true);
|
||||
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 htPrusaLink: return new PrusaLink(config);
|
||||
case htMKS: return new MKS(config);
|
||||
default: return nullptr;
|
||||
}
|
||||
} else {
|
||||
return new SL1Host(config);
|
||||
}
|
||||
}
|
||||
PrintHost *PrintHost::get_print_host_url(std::string url, std::string local_ip) { return new OctoPrint(url, local_ip); }
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//B52
|
||||
std::string PrintHost::make_url(const std::string &path, const std::string& m_host) 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();
|
||||
}
|
||||
}
|
||||
|
||||
//B45
|
||||
std::string PrintHost::get_status(wxString &msg, const wxString &buttonText, const wxString &ip) const
|
||||
{
|
||||
// GET /server/info
|
||||
|
||||
// Since the request is performed synchronously here,
|
||||
// it is ok to refer to `msg` from within the closure
|
||||
const std::string name = buttonText.ToStdString();
|
||||
|
||||
bool res = true;
|
||||
std::string print_state = "standby";
|
||||
std::string m_host = ip.ToStdString();
|
||||
auto url = make_url("printer/objects/query?print_stats=state", m_host);
|
||||
|
||||
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;
|
||||
print_state = "offline";
|
||||
msg = format_error(body, error, status);
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got print_stats: %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";
|
||||
print_state = "offline";
|
||||
return;
|
||||
}
|
||||
if (!ptree.front().second.get_optional<std::string>("status")) {
|
||||
msg = "Could not parse server response";
|
||||
print_state = "offline";
|
||||
return;
|
||||
}
|
||||
print_state = ptree.get<std::string>("result.status.print_stats.state");
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Got state: %2%") % name % print_state;
|
||||
;
|
||||
} catch (const std::exception &) {
|
||||
print_state = "offline";
|
||||
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 = wxString::FromUTF8(address);
|
||||
})
|
||||
#endif // _WIN32
|
||||
.perform_sync();
|
||||
|
||||
return print_state;
|
||||
}
|
||||
|
||||
float PrintHost::get_progress(wxString &msg, const wxString &buttonText, const wxString &ip) const
|
||||
{
|
||||
// GET /server/info
|
||||
|
||||
// Since the request is performed synchronously here,
|
||||
// it is ok to refer to `msg` from within the closure
|
||||
const std::string name = buttonText.ToStdString();
|
||||
|
||||
bool res = true;
|
||||
auto url = make_url("printer/objects/query?display_status=progress", ip.ToStdString());
|
||||
float process = 0;
|
||||
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 display_status: %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>("status")) {
|
||||
msg = "Could not parse server response";
|
||||
res = false;
|
||||
return;
|
||||
}
|
||||
process = std::stof(ptree.get<std::string>("result.status.display_status.progress"));
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Got state: %2%") % name % process;
|
||||
} 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 = wxString::FromUTF8(address);
|
||||
})
|
||||
#endif // _WIN32
|
||||
.perform_sync();
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
|
||||
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 start_bg_thread();
|
||||
void stop_bg_thread();
|
||||
void bg_thread_main();
|
||||
void progress_fn(Http::Progress progress, bool &cancel);
|
||||
void remove_source(const fs::path &path);
|
||||
void remove_source();
|
||||
void perform_job(PrintHostJob the_job);
|
||||
bool cancel_fn();
|
||||
void emit_waittime(int waittime, size_t id);
|
||||
std::vector<PrintHostJob> vec_jobs;
|
||||
std::vector<size_t> vec_jobs_id;
|
||||
};
|
||||
|
||||
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_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
|
||||
}
|
||||
}
|
||||
|
||||
// 10
|
||||
void PrintHostJobQueue::priv::emit_waittime(int waittime, size_t id)
|
||||
{
|
||||
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_WAIT, queue_dialog->GetId(), id, waittime, 0);
|
||||
wxQueueEvent(queue_dialog, evt);
|
||||
}
|
||||
|
||||
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 (!vec_jobs.empty())
|
||||
vec_jobs.erase(vec_jobs.begin());
|
||||
if (!vec_jobs_id.empty())
|
||||
vec_jobs_id.erase(vec_jobs_id.begin());
|
||||
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;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < vec_jobs.size(); i++) {
|
||||
std::chrono::system_clock::time_point curr_time = std::chrono::system_clock::now();
|
||||
auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(curr_time - vec_jobs[i].create_time);
|
||||
emit_waittime((vec_jobs[i].sendinginterval - (diff.count() / 1000)) > 0 ?
|
||||
(vec_jobs[i].sendinginterval - (diff.count() / 1000)) :
|
||||
0,
|
||||
vec_jobs_id[i]);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
bool PrintHostJobQueue::priv::cancel_fn()
|
||||
{
|
||||
bool cancel = false;
|
||||
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(error) << boost::format("PrintHostJobQueue: Job id %1% cancelled") % cancel_id;
|
||||
emit_cancel(cancel_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!cancel) {
|
||||
cancels->clear();
|
||||
}
|
||||
}
|
||||
return cancel;
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
|
||||
{
|
||||
while(true){
|
||||
std::chrono::system_clock::time_point curr_time = std::chrono::system_clock::now();
|
||||
auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(curr_time - the_job.create_time);
|
||||
|
||||
emit_waittime((the_job.sendinginterval - (diff.count() / 1000)) > 0 ? (the_job.sendinginterval - (diff.count() / 1000)) : 0, job_id);
|
||||
|
||||
for (int i = 0; i < vec_jobs.size(); i++) {
|
||||
emit_waittime((vec_jobs[i].sendinginterval - (diff.count() / 1000)) > 0 ?
|
||||
(vec_jobs[i].sendinginterval - (diff.count() / 1000)) :
|
||||
0,
|
||||
vec_jobs_id[i]);
|
||||
}
|
||||
|
||||
|
||||
if (diff.count() > (the_job.sendinginterval)* 1000) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "task_manager: diff count = " << diff.count() << " milliseconds";
|
||||
break;
|
||||
}
|
||||
if (this->cancel_fn())
|
||||
break;
|
||||
boost::this_thread::sleep(boost::posix_time::seconds(1));
|
||||
}
|
||||
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) {
|
||||
emit_error(std::move(error));
|
||||
}
|
||||
);
|
||||
|
||||
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));
|
||||
p->vec_jobs.push_back(std::move(job));
|
||||
p->vec_jobs_id.push_back(p->job_id + p->vec_jobs.size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PrintHostJobQueue::cancel(size_t id)
|
||||
{
|
||||
p->channel_cancels.push(id);
|
||||
}
|
||||
|
||||
}
|
||||
149
src/slic3r/Utils/PrintHost.hpp
Normal file
149
src/slic3r/Utils/PrintHost.hpp
Normal file
@@ -0,0 +1,149 @@
|
||||
#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;
|
||||
|
||||
virtual const char* get_name() const = 0;
|
||||
|
||||
virtual bool test(wxString &curl_msg) const = 0;
|
||||
//B45
|
||||
std::string get_status(wxString &msg, const wxString &buttonText, const wxString &ip) const;
|
||||
float get_progress(wxString &msg, const wxString &buttonText, const wxString &ip) const;
|
||||
std::string make_url(const std::string &path, const std::string &m_host) const;
|
||||
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) 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; }
|
||||
|
||||
// y10
|
||||
virtual bool get_storage(wxArrayString& /*storage_path*/, wxArrayString& /*storage_name*/) const { return false; }
|
||||
static PrintHost *get_print_host_url(std::string url, std::string local_ip);
|
||||
static PrintHost* get_print_host(DynamicPrintConfig *config);
|
||||
|
||||
protected:
|
||||
virtual wxString format_error(const std::string &body, const std::string &error, unsigned status) const;
|
||||
bool m_ssl_revoke_best_effort;
|
||||
};
|
||||
|
||||
|
||||
struct PrintHostJob
|
||||
{
|
||||
// y10
|
||||
std::chrono::system_clock::time_point create_time;
|
||||
int sendinginterval;
|
||||
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)
|
||||
, create_time(std::move(other.create_time))
|
||||
, sendinginterval(other.sendinginterval)
|
||||
{}
|
||||
|
||||
PrintHostJob(DynamicPrintConfig *config)
|
||||
: printhost(PrintHost::get_print_host(config))
|
||||
{}
|
||||
// y10
|
||||
PrintHostJob(std::string url, std::string local_ip) : printhost(PrintHost::get_print_host_url(url, local_ip)) {}
|
||||
PrintHostJob& operator=(const PrintHostJob&) = delete;
|
||||
PrintHostJob& operator=(PrintHostJob &&other)
|
||||
{
|
||||
upload_data = std::move(other.upload_data);
|
||||
printhost = std::move(other.printhost);
|
||||
cancelled = other.cancelled;
|
||||
// y10
|
||||
create_time = std::move(other.create_time);
|
||||
sendinginterval = other.sendinginterval;
|
||||
|
||||
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
|
||||
159
src/slic3r/Utils/Process.cpp
Normal file
159
src/slic3r/Utils/Process.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#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 QIDIStudio 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)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
wxString path;
|
||||
wxFileName::SplitPath(wxStandardPaths::Get().GetExecutablePath(), &path, nullptr, nullptr, wxPATH_NATIVE);
|
||||
path += "\\";
|
||||
path += (instance_type == NewSlicerInstanceType::Slicer) ? "qidi-studio.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");
|
||||
args.emplace_back(nullptr);
|
||||
BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << into_u8(path) << "\"";
|
||||
// Don't call with wxEXEC_HIDE_CONSOLE, QIDIStudio 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__)
|
||||
{
|
||||
bin_path = bin_path.parent_path() / "QIDIStudio";
|
||||
//bin_path = "/usr/bin/open";
|
||||
// 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;// = { "-n", "-a", "QIDIStudio.app"};
|
||||
if (!paths_to_open.empty()) {
|
||||
for (const auto& file : paths_to_open)
|
||||
args.emplace_back(into_u8(file));
|
||||
}
|
||||
//args.emplace_back("--args");
|
||||
if (instance_type == NewSlicerInstanceType::GCodeViewer)
|
||||
args.emplace_back("--gcodeviewer");
|
||||
if (instance_type == NewSlicerInstanceType::Slicer && single_instance)
|
||||
args.emplace_back("--single-instance");
|
||||
boost::process::spawn(bin_path, args);
|
||||
}
|
||||
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-studio" : "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");
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
void start_new_slicer(const wxString *path_to_open, bool single_instance)
|
||||
{
|
||||
start_new_slicer_or_gcodeviewer(NewSlicerInstanceType::Slicer, path_to_open, single_instance);
|
||||
}
|
||||
void start_new_slicer(const std::vector<wxString>& files, bool single_instance)
|
||||
{
|
||||
start_new_slicer_or_gcodeviewer(NewSlicerInstanceType::Slicer, files, single_instance);
|
||||
}
|
||||
|
||||
void start_new_gcodeviewer(const wxString *path_to_open)
|
||||
{
|
||||
start_new_slicer_or_gcodeviewer(NewSlicerInstanceType::GCodeViewer, path_to_open, 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);
|
||||
void start_new_slicer(const std::vector<wxString>& files, bool single_instance = 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
|
||||
19
src/slic3r/Utils/Profile.hpp
Normal file
19
src/slic3r/Utils/Profile.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef slic3r_GUI_Profile_hpp_
|
||||
#define slic3r_GUI_Profile_hpp_
|
||||
|
||||
// Profiling support using the Shiny intrusive profiler
|
||||
//#define SLIC3R_PROFILE_GUI
|
||||
#if defined(SLIC3R_PROFILE) && defined(SLIC3R_PROFILE_GUI)
|
||||
#include <Shiny/Shiny.h>
|
||||
#define SLIC3R_GUI_PROFILE_FUNC() PROFILE_FUNC()
|
||||
#define SLIC3R_GUI_PROFILE_BLOCK(name) PROFILE_BLOCK(name)
|
||||
#define SLIC3R_GUI_PROFILE_UPDATE() PROFILE_UPDATE()
|
||||
#define SLIC3R_GUI_PROFILE_OUTPUT(x) PROFILE_OUTPUT(x)
|
||||
#else
|
||||
#define SLIC3R_GUI_PROFILE_FUNC()
|
||||
#define SLIC3R_GUI_PROFILE_BLOCK(name)
|
||||
#define SLIC3R_GUI_PROFILE_UPDATE()
|
||||
#define SLIC3R_GUI_PROFILE_OUTPUT(x)
|
||||
#endif
|
||||
|
||||
#endif // slic3r_GUI_Profile_hpp_
|
||||
37
src/slic3r/Utils/ProfileDescription.hpp
Normal file
37
src/slic3r/Utils/ProfileDescription.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <I18N.hpp>
|
||||
#include <wx/string.h>
|
||||
#ifndef _L
|
||||
#define _L(s) Slic3r::I18N::translate(s)
|
||||
#endif
|
||||
|
||||
namespace ProfileDescrption {
|
||||
const std::string PROFILE_DESCRIPTION_0 = _L("It has a small layer height, and results in almost negligible layer lines and high printing quality. It is suitable for most general printing cases.");
|
||||
const std::string PROFILE_DESCRIPTION_1 = _L("Compared with the default profile of a 0.2 mm nozzle, it has lower speeds and acceleration, and the sparse infill pattern is Gyroid. So, it results in much higher printing quality, but a much longer printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_2 = _L("Compared with the default profile of a 0.2 mm nozzle, it has a slightly bigger layer height, and results in almost negligible layer lines, and slightly shorter printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_3 = _L("Compared with the default profile of a 0.2 mm nozzle, it has a bigger layer height, and results in slightly visible layer lines, but shorter printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_4 = _L("Compared with the default profile of a 0.2 mm nozzle, it has a smaller layer height, and results in almost invisible layer lines and higher printing quality, but shorter printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_5 = _L("Compared with the default profile of a 0.2 mm nozzle, it has a smaller layer lines, lower speeds and acceleration, and the sparse infill pattern is Gyroid. So, it results in almost invisible layer lines and much higher printing quality, but much longer printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_6 = _L("Compared with the default profile of 0.2 mm nozzle, it has a smaller layer height, and results in minimal layer lines and higher printing quality, but shorter printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_7 = _L("Compared with the default profile of a 0.2 mm nozzle, it has a smaller layer lines, lower speeds and acceleration, and the sparse infill pattern is Gyroid. So, it results in minimal layer lines and much higher printing quality, but much longer printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_8 = _L("It has a general layer height, and results in general layer lines and printing quality. It is suitable for most general printing cases.");
|
||||
const std::string PROFILE_DESCRIPTION_9 = _L("Compared with the default profile of a 0.4 mm nozzle, it has more wall loops and a higher sparse infill density. So, it results in higher strength of the prints, but more filament consumption and longer printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_10 = _L("Compared with the default profile of a 0.4 mm nozzle, it has a bigger layer height, and results in more apparent layer lines and lower printing quality, but slightly shorter printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_11 = _L("Compared with the default profile of a 0.4 mm nozzle, it has a bigger layer height, and results in more apparent layer lines and lower printing quality, but shorter printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_12 = _L("Compared with the default profile of a 0.4 mm nozzle, it has a smaller layer height, and results in less apparent layer lines and higher printing quality, but longer printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_13 = _L("Compared with the default profile of a 0.4 mm nozzle, it has a smaller layer height, lower speeds and acceleration, and the sparse infill pattern is Gyroid. So, it results in less apparent layer lines and much higher printing quality, but much longer printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_14 = _L("Compared with the default profile of a 0.4 mm nozzle, it has a smaller layer height, and results in almost negligible layer lines and higher printing quality, but longer printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_15 = _L("Compared with the default profile of a 0.4 mm nozzle, it has a smaller layer height, lower speeds and acceleration, and the sparse infill pattern is Gyroid. So, it results in almost negligible layer lines and much higher printing quality, but much longer printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_16 = _L("Compared with the default profile of a 0.4 mm nozzle, it has a smaller layer height, and results in almost negligible layer lines and longer printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_17 = _L("It has a big layer height, and results in apparent layer lines and ordinary printing quality and printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_18 = _L("Compared with the default profile of a 0.6 mm nozzle, it has more wall loops and a higher sparse infill density. So, it results in higher strength of the prints, but more filament consumption and longer printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_19 = _L("Compared with the default profile of a 0.6 mm nozzle, it has a bigger layer height, and results in more apparent layer lines and lower printing quality, but shorter printing time in some printing cases.");
|
||||
const std::string PROFILE_DESCRIPTION_20 = _L("Compared with the default profile of a 0.6 mm nozzle, it has a bigger layer height, and results in much more apparent layer lines and much lower printing quality, but shorter printing time in some printing cases.");
|
||||
const std::string PROFILE_DESCRIPTION_21 = _L("Compared with the default profile of a 0.6 mm nozzle, it has a smaller layer height, and results in less apparent layer lines and slight higher printing quality, but longer printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_22 = _L("Compared with the default profile of a 0.6 mm nozzle, it has a smaller layer height, and results in less apparent layer lines and higher printing quality, but longer printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_23 = _L("It has a very big layer height, and results in very apparent layer lines, low printing quality and general printing time.");
|
||||
const std::string PROFILE_DESCRIPTION_24 = _L("Compared with the default profile of a 0.8 mm nozzle, it has a bigger layer height, and results in very apparent layer lines and much lower printing quality, but shorter printing time in some printing cases.");
|
||||
const std::string PROFILE_DESCRIPTION_25 = _L("Compared with the default profile of a 0.8 mm nozzle, it has a much bigger layer height, and results in extremely apparent layer lines and much lower printing quality, but much shorter printing time in some printing cases.");
|
||||
const std::string PROFILE_DESCRIPTION_26 = _L("Compared with the default profile of a 0.8 mm nozzle, it has a slightly smaller layer height, and results in slightly less but still apparent layer lines and slightly higher printing quality, but longer printing time in some printing cases.");
|
||||
const std::string PROFILE_DESCRIPTION_27 = _L("Compared with the default profile of a 0.8 mm nozzle, it has a smaller layer height, and results in less but still apparent layer lines and slightly higher printing quality, but longer printing time in some printing cases.");
|
||||
const std::string PROFILE_DESCRIPTION_28 = _L("This is neither a commonly used filament, nor one of QIDI filaments, and it varies a lot from brand to brand. So, it's highly recommended to ask its vendor for suitable profile before printing and adjust some parameters according to its performances.");
|
||||
}
|
||||
274
src/slic3r/Utils/Repetier.cpp
Normal file
274
src/slic3r/Utils/Repetier.cpp
Normal file
@@ -0,0 +1,274 @@
|
||||
#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"; }
|
||||
|
||||
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([&, 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);
|
||||
|
||||
const auto text = ptree.get_optional<std::string>("name");
|
||||
res = validate_version_text(text);
|
||||
if (! res) {
|
||||
msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "Repetier")).str());
|
||||
}
|
||||
}
|
||||
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::from_u8((boost::format("%s: %s\n\n%s")
|
||||
% _utf8(L("Could not connect to Repetier"))
|
||||
% std::string(msg.ToUTF8())
|
||||
% _utf8(L("Note: Repetier version at least 0.90.0 is required."))).str());
|
||||
}
|
||||
|
||||
bool Repetier::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_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 != _utf8(L("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("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;
|
||||
}
|
||||
|
||||
bool Repetier::validate_version_text(const boost::optional<std::string> &version_text) const
|
||||
{
|
||||
return version_text ? (!version_text->empty()) : true;
|
||||
}
|
||||
|
||||
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(_utf8(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;
|
||||
}
|
||||
|
||||
}
|
||||
51
src/slic3r/Utils/Repetier.hpp
Normal file
51
src/slic3r/Utils/Repetier.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#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) 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;
|
||||
|
||||
protected:
|
||||
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
||||
|
||||
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
|
||||
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.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, _1, _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, _1, _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, _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
|
||||
1235
src/slic3r/Utils/Udp.cpp
Normal file
1235
src/slic3r/Utils/Udp.cpp
Normal file
File diff suppressed because it is too large
Load Diff
294
src/slic3r/Utils/Udp.hpp
Normal file
294
src/slic3r/Utils/Udp.hpp
Normal file
@@ -0,0 +1,294 @@
|
||||
#ifndef slic3r_Udp_hpp_
|
||||
#define slic3r_Udp_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>
|
||||
|
||||
//B35
|
||||
#if defined __linux__
|
||||
#include <boost/array.hpp>
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
|
||||
struct UdpReply
|
||||
{
|
||||
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;
|
||||
|
||||
UdpReply() = delete;
|
||||
UdpReply(boost::asio::ip::address ip,
|
||||
uint16_t port,
|
||||
std::string service_name,
|
||||
std::string hostname);
|
||||
|
||||
std::string path() const;
|
||||
|
||||
bool operator==(const UdpReply &other) const;
|
||||
bool operator<(const UdpReply &other) const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream &, const UdpReply &);
|
||||
|
||||
/// Udp lookup performer
|
||||
class Udp : public std::enable_shared_from_this<Udp> {
|
||||
private:
|
||||
struct priv;
|
||||
public:
|
||||
typedef std::shared_ptr<Udp> Ptr;
|
||||
typedef std::function<void(UdpReply &&)> ReplyFn;
|
||||
typedef std::function<void()> CompleteFn;
|
||||
typedef std::function<void(const std::vector<UdpReply>&)> ResolveFn;
|
||||
typedef std::set<std::string> TxtKeys;
|
||||
|
||||
Udp(std::string service);
|
||||
Udp(Udp &&other);
|
||||
~Udp();
|
||||
|
||||
// Set requested service protocol, "tcp" by default
|
||||
Udp& set_protocol(std::string protocol);
|
||||
// Set which TXT key-values should be collected
|
||||
// Note that "path" is always collected
|
||||
Udp& set_txt_keys(TxtKeys txt_keys);
|
||||
Udp& set_timeout(unsigned timeout);
|
||||
Udp& 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()
|
||||
Udp& set_hostname(const std::string& hostname);
|
||||
|
||||
Udp& on_udp_reply(ReplyFn fn);
|
||||
Udp& on_complete(CompleteFn fn);
|
||||
|
||||
Udp& 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 UdpRequest
|
||||
{
|
||||
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<UdpRequest> make_PTR(const std::string& service, const std::string& protocol);
|
||||
static boost::optional<UdpRequest> make_A(const std::string& hostname);
|
||||
static boost::optional<UdpRequest> make_AAAA(const std::string& hostname);
|
||||
private:
|
||||
UdpRequest(std::vector<char>&& data) : m_data(std::move(data)) {}
|
||||
};
|
||||
|
||||
|
||||
class LookupUdpSocket;
|
||||
class ResolveUdpUdpSocket;
|
||||
|
||||
// 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 UdpUdpSession
|
||||
{
|
||||
public:
|
||||
UdpUdpSession(Udp::ReplyFn rfn);
|
||||
virtual void handle_receive(const boost::system::error_code &error, size_t bytes, std::string pData) = 0;
|
||||
std::vector<char> buffer;
|
||||
boost::asio::ip::udp::endpoint remote_endpoint;
|
||||
protected:
|
||||
Udp::ReplyFn replyfn;
|
||||
};
|
||||
typedef std::shared_ptr<UdpUdpSession> SharedUdpSession;
|
||||
// Session for LookupUdpSocket
|
||||
class LookupUdpSession : public UdpUdpSession
|
||||
{
|
||||
public:
|
||||
LookupUdpSession(const LookupUdpSocket *sckt, Udp::ReplyFn rfn) : UdpUdpSession(rfn), socket(sckt) {}
|
||||
void handle_receive(const boost::system::error_code &error, size_t bytes, std::string pData) override;
|
||||
|
||||
protected:
|
||||
// const pointer to socket to get needed data as txt_keys etc.
|
||||
const LookupUdpSocket *socket;
|
||||
};
|
||||
// Session for ResolveUdpUdpSocket
|
||||
class ResolveUdpSession : public UdpUdpSession
|
||||
{
|
||||
public:
|
||||
ResolveUdpSession(const ResolveUdpUdpSocket* sckt, Udp::ReplyFn rfn) : UdpUdpSession(rfn), socket(sckt) {}
|
||||
void handle_receive(const boost::system::error_code &error, size_t bytes, std::string pData) override;
|
||||
|
||||
protected:
|
||||
// const pointer to seocket to get hostname during handle_receive
|
||||
const ResolveUdpUdpSocket* socket;
|
||||
};
|
||||
|
||||
// Udp socket, starts receiving answers after first send() call until io_service is stopped.
|
||||
class UdpUdpSocket
|
||||
{
|
||||
public:
|
||||
// Two constructors: 1st is with interface which must be resolved before calling this
|
||||
UdpUdpSocket(Udp::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);
|
||||
|
||||
UdpUdpSocket(Udp::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(SharedUdpSession session, const boost::system::error_code& error, size_t bytes);
|
||||
virtual SharedUdpSession create_session() const = 0;
|
||||
|
||||
Udp::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<UdpRequest> requests;
|
||||
boost::array<char, 81920> m_recvBuf;
|
||||
};
|
||||
|
||||
class LookupUdpSocket : public UdpUdpSocket
|
||||
{
|
||||
public:
|
||||
LookupUdpSocket(Udp::TxtKeys txt_keys
|
||||
, std::string service
|
||||
, std::string service_dn
|
||||
, std::string protocol
|
||||
, Udp::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)
|
||||
: UdpUdpSocket(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();
|
||||
}
|
||||
|
||||
LookupUdpSocket(Udp::TxtKeys txt_keys
|
||||
, std::string service
|
||||
, std::string service_dn
|
||||
, std::string protocol
|
||||
, Udp::ReplyFn replyfn
|
||||
, const boost::asio::ip::address& multicast_address
|
||||
, std::shared_ptr< boost::asio::io_service > io_service)
|
||||
: UdpUdpSocket(replyfn, multicast_address, io_service)
|
||||
, txt_keys(txt_keys)
|
||||
, service(service)
|
||||
, service_dn(service_dn)
|
||||
, protocol(protocol)
|
||||
{
|
||||
assert(!service.empty() && replyfn);
|
||||
create_request();
|
||||
}
|
||||
|
||||
const Udp::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:
|
||||
SharedUdpSession create_session() const override;
|
||||
void create_request()
|
||||
{
|
||||
requests.clear();
|
||||
// create PTR request
|
||||
if (auto rqst = UdpRequest::make_PTR(service, protocol); rqst)
|
||||
requests.push_back(std::move(rqst.get()));
|
||||
}
|
||||
boost::optional<UdpRequest> request;
|
||||
Udp::TxtKeys txt_keys;
|
||||
std::string service;
|
||||
std::string service_dn;
|
||||
std::string protocol;
|
||||
};
|
||||
|
||||
class ResolveUdpUdpSocket : public UdpUdpSocket
|
||||
{
|
||||
public:
|
||||
ResolveUdpUdpSocket(const std::string& hostname
|
||||
, Udp::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)
|
||||
: UdpUdpSocket(replyfn, multicast_address, interface_address, io_service)
|
||||
, hostname(hostname)
|
||||
|
||||
{
|
||||
assert(!hostname.empty() && replyfn);
|
||||
create_requests();
|
||||
}
|
||||
|
||||
ResolveUdpUdpSocket(const std::string& hostname
|
||||
, Udp::ReplyFn replyfn
|
||||
, const boost::asio::ip::address& multicast_address
|
||||
, std::shared_ptr< boost::asio::io_service > io_service)
|
||||
: UdpUdpSocket(replyfn, multicast_address, io_service)
|
||||
, hostname(hostname)
|
||||
|
||||
{
|
||||
assert(!hostname.empty() && replyfn);
|
||||
create_requests();
|
||||
}
|
||||
|
||||
std::string get_hostname() const { return hostname; }
|
||||
protected:
|
||||
SharedUdpSession create_session() const override;
|
||||
void create_requests()
|
||||
{
|
||||
requests.clear();
|
||||
// UdpRequest::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 = UdpRequest::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 = UdpRequest::make_AAAA(trimmed_hostname); rqst)
|
||||
requests.push_back(std::move(rqst.get()));
|
||||
}
|
||||
|
||||
std::string hostname;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
1391
src/slic3r/Utils/UndoRedo.cpp
Normal file
1391
src/slic3r/Utils/UndoRedo.cpp
Normal file
File diff suppressed because it is too large
Load Diff
193
src/slic3r/Utils/UndoRedo.hpp
Normal file
193
src/slic3r/Utils/UndoRedo.hpp
Normal file
@@ -0,0 +1,193 @@
|
||||
#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;
|
||||
class PartPlateList;
|
||||
class PartPlate;
|
||||
} // 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; }
|
||||
|
||||
};
|
||||
|
||||
// QDS: moved from UndoRedo.cpp
|
||||
// If a snapshot modifies the snapshot type,
|
||||
inline bool snapshot_modifies_project(SnapshotType type)
|
||||
{
|
||||
return type == SnapshotType::Action || type == SnapshotType::GizmoAction || type == SnapshotType::ProjectSeparator;
|
||||
}
|
||||
|
||||
inline bool snapshot_modifies_project(const Snapshot &snapshot)
|
||||
{
|
||||
return snapshot_modifies_project(snapshot.snapshot_data.snapshot_type) && (snapshot.name.empty() || snapshot.name.back() != '!');
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const Slic3r::GUI::PartPlateList& plate_list, 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, Slic3r::GUI::PartPlateList& plate_list, 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, Slic3r::GUI::PartPlateList& plate_list, 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;
|
||||
// QDS: backup and restore
|
||||
bool has_real_change_from(size_t time) 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_ */
|
||||
287
src/slic3r/Utils/json_diff.cpp
Normal file
287
src/slic3r/Utils/json_diff.cpp
Normal file
@@ -0,0 +1,287 @@
|
||||
#include "json_diff.hpp"
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
|
||||
#include <stdio.h>
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include <libslic3r/Utils.hpp>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/iostream.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
using namespace std;
|
||||
using json = nlohmann::json;
|
||||
|
||||
int json_diff::diff_objects(json const &in, json &out, json const &base)
|
||||
{
|
||||
for (auto& el: in.items()) {
|
||||
if (el.value().empty()) {
|
||||
//QDT_LOG_INFO("json_c diff empty key: " << el.key());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!base.contains(el.key()) ) {
|
||||
out[el.key()] = el.value();
|
||||
BOOST_LOG_TRIVIAL(trace) << "json_c diff new key: " << el.key()
|
||||
<< " type: " << el.value().type_name()
|
||||
<< " value: " << el.value();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (el.value().type() != base[el.key()].type()) {
|
||||
out[el.key()] = el.value();
|
||||
BOOST_LOG_TRIVIAL(trace) << "json_c diff type changed"
|
||||
<< " key: " << el.key() << " value: " << el.value().dump()
|
||||
<< " last value: " << base[el.key()].dump();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (el.value().is_object()) {
|
||||
json recur_out;
|
||||
int recur_ret = diff_objects(
|
||||
el.value(), recur_out, base[el.key()]);
|
||||
if (recur_ret == 0) {
|
||||
out[el.key()] = recur_out;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (el.value() != base[el.key()]) {
|
||||
out[el.key()] = el.value();
|
||||
BOOST_LOG_TRIVIAL(trace) << "json_c diff value changed"
|
||||
<< " key: " << el.key() << " value: " << el.value().dump()
|
||||
<< " last value: " << base[el.key()].dump();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (out.empty())
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_diff::all2diff_base_reset(json const &base)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "all2diff_base_reset";
|
||||
all2diff_base = base;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool json_diff::load_compatible_settings(std::string const &type, std::string const &version)
|
||||
{
|
||||
// Reload on empty type and version
|
||||
if (!type.empty() || !version.empty()) {
|
||||
std::string type2 = type.empty() ? printer_type : type;
|
||||
std::string version2 = version.empty() ? printer_version : version;
|
||||
if (type2 == printer_type && version2 == printer_version)
|
||||
return false;
|
||||
printer_type = type2;
|
||||
printer_version = version2;
|
||||
}
|
||||
settings_base.clear();
|
||||
std::string config_file = Slic3r::data_dir() + "/printers/" + printer_type + ".json";
|
||||
boost::nowide::ifstream json_file(config_file.c_str());
|
||||
try {
|
||||
json versions;
|
||||
if (json_file.is_open()) {
|
||||
json_file >> versions;
|
||||
for (auto iter = versions.begin(); iter != versions.end(); ++iter) {
|
||||
if (iter.key() > printer_version)
|
||||
break;
|
||||
merge_objects(*iter, settings_base);
|
||||
}
|
||||
if (!full_message.empty())
|
||||
diff2all_base_reset(full_message);
|
||||
return true;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_compatible_settings failed, file = " << config_file;
|
||||
}
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_compatible_settings failed, file = " << config_file;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int json_diff::all2diff(json const &in, json &out)
|
||||
{
|
||||
int ret = 0;
|
||||
if (all2diff_base.empty()) {
|
||||
all2diff_base = in;
|
||||
out = in;
|
||||
BOOST_LOG_TRIVIAL(trace) << "json_c diff base reinit";
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = diff_objects(in, out, all2diff_base);
|
||||
if (ret != 0) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "json_c diff no new info";
|
||||
}
|
||||
all2diff_base = in;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_diff::restore_objects(json const &in, json &out, json const &base)
|
||||
{
|
||||
json jout;
|
||||
|
||||
for (auto& el: base.items()) {
|
||||
/*element not in input json,
|
||||
use base element to restore*/
|
||||
if (!in.contains(el.key()) ) {
|
||||
out[el.key()] = el.value();
|
||||
/*
|
||||
QDT_LOG_INFO("json_c restore compressed key " << el.key()
|
||||
<< " type: " << el.value().type_name()
|
||||
<< " value: " << el.value());
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
/*element in both base and input, but json type changed
|
||||
use input to restore*/
|
||||
if (el.value().type() != in[el.key()].type() ){
|
||||
out[el.key()] = in[el.key()];
|
||||
BOOST_LOG_TRIVIAL(trace) << "json_c restore type changed"
|
||||
<< " key: " << el.key() << " value: "
|
||||
<< in[el.key()].dump()
|
||||
<< " last value: " << el.value().dump();
|
||||
continue;
|
||||
}
|
||||
|
||||
/*element in both base and input, it is a object
|
||||
recursive until basic type*/
|
||||
if (el.value().is_object()) {
|
||||
json recur_out;
|
||||
int recur_ret = restore_objects(
|
||||
in[el.key()], recur_out, el.value());
|
||||
if (recur_ret == 0) {
|
||||
out[el.key()] = recur_out;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/*element in both base and input, but value changed
|
||||
use input to restore*/
|
||||
if (el.value() != in[el.key()]) {
|
||||
out[el.key()] = in[el.key()];
|
||||
continue;
|
||||
}
|
||||
/*element in both base and input,value is same
|
||||
use base to restore*/
|
||||
out[el.key()] = el.value();
|
||||
}
|
||||
|
||||
if (out.empty())
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_diff::restore_append_objects(json const &in, json &out)
|
||||
{
|
||||
/*a new element comming, but be recoreded in base
|
||||
need be added to output*/
|
||||
for (auto& el: in.items()) {
|
||||
|
||||
if (!out.contains(el.key()) ) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "json_c append new " << el.key()
|
||||
<< " type: " << el.value().type_name()
|
||||
<< " value: " << el.value();
|
||||
out[el.key()] = el.value();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (el.value().is_object()) {
|
||||
int recur_ret =
|
||||
restore_append_objects(el.value(), out[el.key()]);
|
||||
if (recur_ret != 0) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "json_c append obj failed"
|
||||
<< " key: " << el.key()
|
||||
<< " value: " << el.value();
|
||||
return recur_ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void json_diff::merge_objects(json const &in, json &out)
|
||||
{
|
||||
for (auto &el : in.items()) {
|
||||
if (!out.contains(el.key())) {
|
||||
out[el.key()] = el.value();
|
||||
continue;
|
||||
}
|
||||
if (el.value().is_object()) {
|
||||
merge_objects(el.value(), out[el.key()]);
|
||||
continue;
|
||||
}
|
||||
out[el.key()] = el.value();
|
||||
}
|
||||
}
|
||||
|
||||
int json_diff::diff2all(json const &in, json &out)
|
||||
{
|
||||
if (!diff2all_base.empty()) {
|
||||
int ret = restore_objects(in, out, diff2all_base);
|
||||
if (ret < 0) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "json_c restore failed";
|
||||
decode_error_count++;
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(trace) << "json_c restore base empty";
|
||||
decode_error_count++;
|
||||
return -1;
|
||||
}
|
||||
restore_append_objects(in, out);
|
||||
diff2all_base = out;
|
||||
decode_error_count = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void json_diff::compare_print(json &a, json &b)
|
||||
{
|
||||
for (auto& e: a.items()) {
|
||||
if (!b.contains(e.key()) ) { BOOST_LOG_TRIVIAL(trace) << "json_c compare loss " << e.key()
|
||||
<< " type: " << e.value().type_name();
|
||||
}
|
||||
if (e.value() != b[e.key()]) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "json_c compare not equal: key: " << e.key()
|
||||
<< " value: " << e.value();
|
||||
BOOST_LOG_TRIVIAL(trace) << "json_c compare vs value "
|
||||
<< " vs value: " << b[e.key()];
|
||||
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool json_diff::is_need_request()
|
||||
{
|
||||
if (decode_error_count > 5) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int json_diff::diff2all_base_reset(json &base)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "diff2all_base_reset";
|
||||
full_message = base;
|
||||
if (!settings_base.empty()) {
|
||||
merge_objects(settings_base, base);
|
||||
}
|
||||
diff2all_base = base;
|
||||
return 0;
|
||||
}
|
||||
41
src/slic3r/Utils/json_diff.hpp
Normal file
41
src/slic3r/Utils/json_diff.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef __JSON_DIFF_HPP
|
||||
#define __JSON_DIFF_HPP
|
||||
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
|
||||
using json = nlohmann::json;
|
||||
using namespace std;
|
||||
|
||||
class json_diff
|
||||
{
|
||||
private:
|
||||
std::string printer_type;
|
||||
std::string printer_version = "00.00.00.00";
|
||||
json settings_base;
|
||||
json full_message;
|
||||
|
||||
json diff2all_base;
|
||||
json all2diff_base;
|
||||
int decode_error_count = 0;
|
||||
|
||||
int diff_objects(json const &in, json &out, json const &base);
|
||||
int restore_objects(json const &in, json &out, json const &base);
|
||||
int restore_append_objects(json const &in, json &out);
|
||||
void merge_objects(json const &in, json &out);
|
||||
|
||||
public:
|
||||
bool load_compatible_settings(std::string const &type, std::string const &version);
|
||||
int all2diff(json const &in, json &out);
|
||||
int diff2all(json const &in, json &out);
|
||||
int all2diff_base_reset(json const &base);
|
||||
int diff2all_base_reset(json &base);
|
||||
void compare_print(json &a, json &b);
|
||||
|
||||
bool is_need_request();
|
||||
};
|
||||
#endif // __JSON_DIFF_HPP
|
||||
54
src/slic3r/Utils/minilzo_extension.cpp
Normal file
54
src/slic3r/Utils/minilzo_extension.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include <exception>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "minilzo_extension.hpp"
|
||||
#include "minilzo/minilzo.h"
|
||||
|
||||
static unsigned char wrkmem[LZO1X_1_MEM_COMPRESS];
|
||||
|
||||
static bool initialized = false;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
int lzo_compress(unsigned char *in, uint64_t in_len, unsigned char* out, uint64_t* out_len)
|
||||
{
|
||||
int result = 0;
|
||||
if (!initialized) {
|
||||
if (lzo_init() != LZO_E_OK)
|
||||
return LZO_E_ERROR;
|
||||
else
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
lzo_uint lzo_out_len = *out_len;
|
||||
result = lzo1x_1_compress(in, in_len, out, &lzo_out_len, wrkmem);
|
||||
if (result == LZO_E_OK) {
|
||||
*out_len = lzo_out_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
int lzo_decompress(unsigned char *in, uint64_t in_len, unsigned char* out, uint64_t* out_len)
|
||||
{
|
||||
int result = 0;
|
||||
if (!initialized) {
|
||||
if (lzo_init() != LZO_E_OK)
|
||||
return LZO_E_ERROR;
|
||||
else
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
lzo_uint lzo_out_len = *out_len;
|
||||
result = lzo1x_decompress(in, in_len, out, &lzo_out_len, NULL);
|
||||
if (result == LZO_E_OK) {
|
||||
*out_len = lzo_out_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
16
src/slic3r/Utils/minilzo_extension.hpp
Normal file
16
src/slic3r/Utils/minilzo_extension.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef MINILZO_EXTENSION_HPP
|
||||
#define MINILZO_EXTENSION_HPP
|
||||
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include "minilzo/minilzo.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
int lzo_compress(unsigned char* in, uint64_t in_len, unsigned char* out, uint64_t* out_len);
|
||||
int lzo_decompress(unsigned char* in, uint64_t in_len, unsigned char* out, uint64_t* out_len);
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // MINIZ_EXTENSION_HPP
|
||||
244
src/slic3r/Utils/qidi_networking.hpp
Normal file
244
src/slic3r/Utils/qidi_networking.hpp
Normal file
@@ -0,0 +1,244 @@
|
||||
#ifndef __QIDI_NETWORKING_HPP__
|
||||
#define __QIDI_NETWORKING_HPP__
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
extern std::string g_log_folder;
|
||||
extern std::string g_log_start_time;
|
||||
|
||||
namespace QDT {
|
||||
|
||||
#define QIDI_NETWORK_SUCCESS 0
|
||||
#define QIDI_NETWORK_ERR_INVALID_HANDLE -1
|
||||
#define QIDI_NETWORK_ERR_CONNECT_FAILED -2
|
||||
#define QIDI_NETWORK_ERR_DISCONNECT_FAILED -3
|
||||
#define QIDI_NETWORK_ERR_SEND_MSG_FAILED -4
|
||||
#define QIDI_NETWORK_ERR_BIND_FAILED -5
|
||||
#define QIDI_NETWORK_ERR_UNBIND_FAILED -6
|
||||
#define QIDI_NETWORK_ERR_REQUEST_SETTING_FAILED -7
|
||||
#define QIDI_NETWORK_ERR_PUT_SETTING_FAILED -8
|
||||
#define QIDI_NETWORK_ERR_GET_SETTING_LIST_FAILED -9
|
||||
#define QIDI_NETWORK_ERR_DEL_SETTING_FAILED -10
|
||||
#define QIDI_NETWORK_ERR_GET_USER_PRINTINFO_FAILED -11
|
||||
#define QIDI_NETWORK_ERR_QUERY_BIND_INFO_FAILED -12
|
||||
#define QIDI_NETWORK_ERR_MODIFY_PRINTER_NAME_FAILED -13
|
||||
#define QIDI_NETWORK_ERR_FILE_NOT_EXIST -14
|
||||
#define QIDI_NETWORK_ERR_FILE_OVER_SIZE -15
|
||||
#define QIDI_NETWORK_ERR_CHECK_MD5_FAILED -16
|
||||
#define QIDI_NETWORK_ERR_TIMEOUT -17
|
||||
#define QIDI_NETWORK_ERR_CANCELED -18
|
||||
#define QIDI_NETWORK_ERR_INVALID_RESULT -19
|
||||
#define QIDI_NETWORK_ERR_FTP_UPLOAD_FAILED -20
|
||||
#define QIDI_NETWORK_ERR_GET_RATING_ID_FAILED -21
|
||||
#define QIDI_NETWORK_ERR_OPEN_FILE_FAILED -22
|
||||
#define QIDI_NETWORK_ERR_PARSE_CONFIG_FAILED -23
|
||||
#define QIDI_NETWORK_ERR_NO_CORRESPONDING_BUCKET -24
|
||||
#define QIDI_NETWORK_ERR_GET_INSTANCE_ID_FAILED -25
|
||||
|
||||
//bind error
|
||||
#define QIDI_NETWORK_ERR_BIND_CREATE_SOCKET_FAILED -1010 //failed to create socket
|
||||
#define QIDI_NETWORK_ERR_BIND_SOCKET_CONNECT_FAILED -1020 //failed to socket connect
|
||||
#define QIDI_NETWORK_ERR_BIND_PUBLISH_LOGIN_REQUEST -1030 //failed to publish login request
|
||||
#define QIDI_NETWORK_ERR_BIND_GET_PRINTER_TICKET_TIMEOUT -1040 //timeout to get ticket from printer
|
||||
#define QIDI_NETWORK_ERR_BIND_GET_CLOUD_TICKET_TIMEOUT -1050 //timeout to get ticket from cloud server
|
||||
#define QIDI_NETWORK_ERR_BIND_POST_TICKET_TO_CLOUD_FAILED -1060 //failed to post ticket to cloud server
|
||||
#define QIDI_NETWORK_ERR_BIND_PARSE_LOGIN_REPORT_FAILED -1070 //failed to parse login report reason no error code
|
||||
#define QIDI_NETWORK_ERR_BIND_ECODE_LOGIN_REPORT_FAILED -1080 //failed to parse login report reason has error code
|
||||
#define QIDI_NETWORK_ERR_BIND_RECEIVE_LOGIN_REPORT_TIMEOUT -1090 //timeout to receive login report
|
||||
|
||||
//start_local_print_with_record error
|
||||
#define QIDI_NETWORK_ERR_PRINT_WR_REQUEST_PROJECT_ID_FAILED -2010 //failed to request project id
|
||||
#define QIDI_NETWORK_ERR_PRINT_WR_CHECK_MD5_FAILED -2020 //failed to check md5 for upload 3mf to oss
|
||||
#define QIDI_NETWORK_ERR_PRINT_WR_UPLOAD_3MF_CONFIG_TO_OSS_FAILED -2030 //failed to upload 3mf config to oss
|
||||
#define QIDI_NETWORK_ERR_PRINT_WR_FILE_OVER_SIZE -2040 //the size of the uploaded file cannot exceed 1 GB
|
||||
#define QIDI_NETWORK_ERR_PRINT_WR_PUT_NOTIFICATION_FAILED -2050 //timeout to get notification
|
||||
#define QIDI_NETWORK_ERR_PRINT_WR_GET_NOTIFICATION_TIMEOUT -2060 //timeout to get notification
|
||||
#define QIDI_NETWORK_ERR_PRINT_WR_GET_NOTIFICATION_FAILED -2070 //failed to get notification
|
||||
#define QIDI_NETWORK_ERR_PRINT_WR_PATCH_PROJECT_FAILED -2080 //failed to patch project
|
||||
#define QIDI_NETWORK_ERR_PRINT_WR_GET_MY_SETTING_FAILED -2090 //failed to get my setting
|
||||
#define QIDI_NETWORK_ERR_PRINT_WR_FILE_NOT_EXIST -2100 //3mf file is not exists
|
||||
#define QIDI_NETWORK_ERR_PRINT_WR_UPLOAD_3MF_TO_OSS_FAILED -2110 //failed to upload 3mf to oss
|
||||
#define QIDI_NETWORK_ERR_PRINT_WR_POST_TASK_FAILED -2120 //failed to post task
|
||||
#define QIDI_NETWORK_ERR_PRINT_WR_UPLOAD_FTP_FAILED -2130 //failed to upload to ftp
|
||||
#define QIDI_NETWORK_ERR_PRINT_WR_GET_USER_UPLOAD_FAILED -2140 //failed to get_user_upload
|
||||
|
||||
//start_print error
|
||||
#define QIDI_NETWORK_ERR_PRINT_SP_REQUEST_PROJECT_ID_FAILED -3010 //failed to request project id
|
||||
#define QIDI_NETWORK_ERR_PRINT_SP_CHECK_MD5_FAILED -3020 //failed to check md5 for upload 3mf to oss
|
||||
#define QIDI_NETWORK_ERR_PRINT_SP_UPLOAD_3MF_CONFIG_TO_OSS_FAILED -3030 //failed to upload 3mf config to oss
|
||||
#define QIDI_NETWORK_ERR_PRINT_SP_PUT_NOTIFICATION_FAILED -3040 //failed to put notification
|
||||
#define QIDI_NETWORK_ERR_PRINT_SP_GET_NOTIFICATION_TIMEOUT -3050 //timeout to get notification
|
||||
#define QIDI_NETWORK_ERR_PRINT_SP_GET_NOTIFICATION_FAILED -3060 //failed to get notification
|
||||
#define QIDI_NETWORK_ERR_PRINT_SP_FILE_NOT_EXIST -3070 //3mf file is not exists
|
||||
#define QIDI_NETWORK_ERR_PRINT_SP_GET_USER_UPLOAD_FAILED -3080 //failed to get_user_upload
|
||||
#define QIDI_NETWORK_ERR_PRINT_SP_FILE_OVER_SIZE -3090 //the size of the uploaded file cannot exceed 1 GB
|
||||
#define QIDI_NETWORK_ERR_PRINT_SP_UPLOAD_3MF_TO_OSS_FAILED -3100 //failed to upload 3mf to oss
|
||||
#define QIDI_NETWORK_ERR_PRINT_SP_PATCH_PROJECT_FAILED -3110 //failed to patch project
|
||||
#define QIDI_NETWORK_ERR_PRINT_SP_POST_TASK_FAILED -3120 //failed to post task
|
||||
#define QIDI_NETWORK_ERR_PRINT_SP_WAIT_PRINTER_FAILED -3130 //failed to wait the ack from printer
|
||||
|
||||
//start_local_print error
|
||||
#define QIDI_NETWORK_ERR_PRINT_LP_FILE_OVER_SIZE -4010 //the size of the uploaded file cannot exceed 1 GB
|
||||
#define QIDI_NETWORK_ERR_PRINT_LP_UPLOAD_FTP_FAILED -4020 //failed to upload ftp
|
||||
#define QIDI_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED -4030 //failed to send mqtt message to device
|
||||
|
||||
//start_send_gcode_to_sdcard error
|
||||
#define QIDI_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED -5010 //failed to upload ftp
|
||||
|
||||
//connection to printer failed
|
||||
#define QIDI_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED -6010 //Connection to printer failed
|
||||
#define QIDI_NETWORK_ERR_CONNECTION_TO_SERVER_FAILED -6020 //Connection to server failed
|
||||
|
||||
|
||||
#define QIDI_NETWORK_LIBRARY "qidi_networking"
|
||||
#define QIDI_NETWORK_AGENT_NAME "qidi_network_agent"
|
||||
|
||||
#define QIDI_NETWORK_AGENT_VERSION "01.09.02.05"
|
||||
|
||||
//iot preset type strings
|
||||
#define IOT_PRINTER_TYPE_STRING "printer"
|
||||
#define IOT_FILAMENT_STRING "filament"
|
||||
#define IOT_PRINT_TYPE_STRING "print"
|
||||
|
||||
#define IOT_JSON_KEY_VERSION "version"
|
||||
#define IOT_JSON_KEY_NAME "name"
|
||||
#define IOT_JSON_KEY_TYPE "type"
|
||||
#define IOT_JSON_KEY_UPDATE_TIME "update_time"
|
||||
#define IOT_JSON_KEY_UPDATED_TIME "updated_time"
|
||||
#define IOT_JSON_KEY_BASE_ID "base_id"
|
||||
#define IOT_JSON_KEY_SETTING_ID "setting_id"
|
||||
#define IOT_JSON_KEY_FILAMENT_ID "filament_id"
|
||||
#define IOT_JSON_KEY_USER_ID "user_id"
|
||||
|
||||
// user callbacks
|
||||
typedef std::function<void(int online_login, bool login)> OnUserLoginFn;
|
||||
// printer callbacks
|
||||
typedef std::function<void(std::string topic_str)> OnPrinterConnectedFn;
|
||||
typedef std::function<void(int status, std::string dev_id, std::string msg)> OnLocalConnectedFn;
|
||||
typedef std::function<void(int return_code, int reason_code)> OnServerConnectedFn;
|
||||
typedef std::function<void(std::string dev_id, std::string msg)> OnMessageFn;
|
||||
// http callbacks
|
||||
typedef std::function<void(unsigned http_code, std::string http_body)> OnHttpErrorFn;
|
||||
typedef std::function<std::string()> GetCountryCodeFn;
|
||||
typedef std::function<void(std::string topic)> GetSubscribeFailureFn;
|
||||
// print callbacks
|
||||
typedef std::function<void(int status, int code, std::string msg)> OnUpdateStatusFn;
|
||||
typedef std::function<bool()> WasCancelledFn;
|
||||
typedef std::function<bool(int status, std::string job_info)> OnWaitFn;
|
||||
// local callbacks
|
||||
typedef std::function<void(std::string dev_info_json_str)> OnMsgArrivedFn;
|
||||
// queue call to main thread
|
||||
typedef std::function<void(std::function<void()>)> QueueOnMainFn;
|
||||
|
||||
typedef std::function<void(int progress)> ProgressFn;
|
||||
typedef std::function<void(int retcode, std::string info)> LoginFn;
|
||||
typedef std::function<void(int result, std::string info)> ResultFn;
|
||||
typedef std::function<bool()> CancelFn;
|
||||
typedef std::function<bool(std::map<std::string, std::string> info)> CheckFn;
|
||||
|
||||
enum SendingPrintJobStage {
|
||||
PrintingStageCreate = 0,
|
||||
PrintingStageUpload = 1,
|
||||
PrintingStageWaiting = 2,
|
||||
PrintingStageSending = 3,
|
||||
PrintingStageRecord = 4,
|
||||
PrintingStageWaitPrinter = 5,
|
||||
PrintingStageFinished = 6,
|
||||
PrintingStageERROR = 7,
|
||||
PrintingStageLimit = 8,
|
||||
};
|
||||
|
||||
enum PublishingStage {
|
||||
PublishingCreate = 0,
|
||||
PublishingUpload = 1,
|
||||
PublishingWaiting = 2,
|
||||
PublishingJumpUrl = 3,
|
||||
};
|
||||
|
||||
enum BindJobStage {
|
||||
LoginStageConnect = 0,
|
||||
LoginStageLogin = 1,
|
||||
LoginStageWaitForLogin = 2,
|
||||
LoginStageGetIdentify = 3,
|
||||
LoginStageWaitAuth = 4,
|
||||
LoginStageFinished = 5,
|
||||
};
|
||||
|
||||
enum ConnectStatus {
|
||||
ConnectStatusOk = 0,
|
||||
ConnectStatusFailed = 1,
|
||||
ConnectStatusLost = 2,
|
||||
};
|
||||
|
||||
/* print job*/
|
||||
struct PrintParams {
|
||||
/* basic info */
|
||||
std::string dev_id;
|
||||
std::string task_name;
|
||||
std::string project_name;
|
||||
std::string preset_name;
|
||||
std::string filename;
|
||||
std::string config_filename;
|
||||
int plate_index;
|
||||
std::string ftp_folder;
|
||||
std::string ftp_file;
|
||||
std::string ftp_file_md5;
|
||||
std::string ams_mapping;
|
||||
std::string ams_mapping_info;
|
||||
std::string connection_type;
|
||||
std::string comments;
|
||||
int origin_profile_id = 0;
|
||||
int stl_design_id = 0;
|
||||
std::string origin_model_id;
|
||||
std::string print_type;
|
||||
std::string dst_file;
|
||||
std::string dev_name;
|
||||
|
||||
/* access options */
|
||||
std::string dev_ip;
|
||||
bool use_ssl_for_ftp;
|
||||
bool use_ssl_for_mqtt;
|
||||
std::string username;
|
||||
std::string password;
|
||||
|
||||
/*user options */
|
||||
bool task_bed_leveling; /* bed leveling of task */
|
||||
bool task_flow_cali; /* flow calibration of task */
|
||||
bool task_vibration_cali; /* vibration calibration of task */
|
||||
bool task_layer_inspect; /* first layer inspection of task */
|
||||
bool task_record_timelapse; /* record timelapse of task */
|
||||
bool task_use_ams;
|
||||
std::string task_bed_type;
|
||||
std::string extra_options;
|
||||
};
|
||||
|
||||
struct TaskQueryParams
|
||||
{
|
||||
std::string dev_id;
|
||||
int status = 0;
|
||||
int offset = 0;
|
||||
int limit = 20;
|
||||
};
|
||||
|
||||
struct PublishParams {
|
||||
std::string project_name;
|
||||
std::string project_3mf_file;
|
||||
std::string preset_name;
|
||||
std::string project_model_id;
|
||||
std::string design_id;
|
||||
std::string config_filename;
|
||||
};
|
||||
|
||||
struct CertificateInformation {
|
||||
std::string issuer;
|
||||
std::string sub_name;
|
||||
std::string start_date;
|
||||
std::string end_date;
|
||||
std::string serial_number;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user