From 7cfc5204c85c68e07ca351ac810a7f14a8e65eaf Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 7 Feb 2018 11:37:15 +0100 Subject: [PATCH] WIP: OctoPrint --- lib/Slic3r/GUI/MainFrame.pm | 12 +++- lib/Slic3r/GUI/Plater.pm | 48 ++------------ xs/CMakeLists.txt | 3 + xs/src/libslic3r/PrintConfig.cpp | 11 +++- xs/src/libslic3r/PrintConfig.hpp | 2 + xs/src/perlglue.cpp | 1 + xs/src/slic3r/GUI/GUI.cpp | 16 ++++- xs/src/slic3r/GUI/GUI.hpp | 3 + xs/src/slic3r/GUI/Preset.cpp | 2 +- xs/src/slic3r/Utils/OctoPrint.cpp | 105 ++++++++++++++++++++++++++++++ xs/src/slic3r/Utils/OctoPrint.hpp | 35 ++++++++++ xs/xsp/Utils_OctoPrint.xsp | 14 ++++ xs/xsp/my.map | 4 ++ 13 files changed, 210 insertions(+), 46 deletions(-) create mode 100644 xs/src/slic3r/Utils/OctoPrint.cpp create mode 100644 xs/src/slic3r/Utils/OctoPrint.hpp create mode 100644 xs/xsp/Utils_OctoPrint.xsp diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 572bdac32..034cc2dd5 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -409,7 +409,15 @@ sub _init_menubar { wxTheApp->about; }); } - + + my $hokusMenu = Wx::Menu->new; # XXX: tmp + { + $self->_append_menu_item($hokusMenu, "Pokus", "Pokus", sub { + # Slic3r::Http::download(); + Slic3r::OctoPrint::send_gcode("10.0.0.46", "70E4CFD0E0D7423CB6B1CF055DBAEFA5", "/home/vojta/prog/tisk/jesterka/jesterka.gcode"); + }); + } + # menubar # assign menubar to frame after appending items, otherwise special items # will not be handled correctly @@ -424,6 +432,8 @@ sub _init_menubar { # (Select application language from the list of installed languages) Slic3r::GUI::add_debug_menu($menubar, $self->{lang_ch_event}); $menubar->Append($helpMenu, L("&Help")); + $menubar->Append($hokusMenu, "Hoku&s"); + # Add an optional debug menu. In production code, the add_debug_menu() call should do nothing. $self->SetMenuBar($menubar); } } diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 5cedfbb08..d48e31462 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -52,7 +52,7 @@ sub new { my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{config} = Slic3r::Config::new_from_defaults_keys([qw( bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height - serial_port serial_speed octoprint_host octoprint_apikey + serial_port serial_speed octoprint_host octoprint_apikey octoprint_cafile nozzle_diameter single_extruder_multi_material wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe extruder_colour filament_colour )]); @@ -1458,8 +1458,13 @@ sub on_export_completed { wxTheApp->notify($message); $self->do_print if $do_print; + # Send $self->{send_gcode_file} to OctoPrint. - $self->send_gcode if $send_gcode; + if ($send_gcode) { + my $op = Slic3r::OctoPrint->new($self->{config}); + $op->send_gcode($self->GetId(), $PROGRESS_BAR_EVENT, $ERROR_EVENT, $self->{send_gcode_file}); + } + $self->{print_file} = undef; $self->{send_gcode_file} = undef; $self->{"print_info_cost"}->SetLabel(sprintf("%.2f" , $self->{print}->total_cost)); @@ -1488,45 +1493,6 @@ sub do_print { my $filament_names = wxTheApp->{preset_bundle}->filament_presets; $filament_stats = { map { $filament_names->[$_] => $filament_stats->{$_} } keys %$filament_stats }; $printer_panel->load_print_job($self->{print_file}, $filament_stats); - - $self->GetFrame->select_tab(1); -} - -# Send $self->{send_gcode_file} to OctoPrint. -#FIXME Currently this call blocks the UI. Make it asynchronous. -sub send_gcode { - my ($self) = @_; - - $self->statusbar->StartBusy; - - my $ua = LWP::UserAgent->new; - $ua->timeout(180); - - my $res = $ua->post( - "http://" . $self->{config}->octoprint_host . "/api/files/local", - Content_Type => 'form-data', - 'X-Api-Key' => $self->{config}->octoprint_apikey, - Content => [ - file => [ - # On Windows, the path has to be encoded in local code page for perl to be able to open it. - Slic3r::encode_path($self->{send_gcode_file}), - # Remove the UTF-8 flag from the perl string, so the LWP::UserAgent can insert - # the UTF-8 encoded string into the request as a byte stream. - Slic3r::path_to_filename_raw($self->{send_gcode_file}) - ], - print => $self->{send_gcode_file_print} ? 1 : 0, - ], - ); - - $self->statusbar->StopBusy; - - if ($res->is_success) { - $self->statusbar->SetStatusText(L("G-code file successfully uploaded to the OctoPrint server")); - } else { - my $message = L("Error while uploading to the OctoPrint server: ") . $res->status_line; - Slic3r::GUI::show_error($self, $message); - $self->statusbar->SetStatusText($message); - } } sub export_stl { diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 06456d607..b73f8336d 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -201,6 +201,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/wxExtensions.hpp ${LIBDIR}/slic3r/Utils/Http.cpp ${LIBDIR}/slic3r/Utils/Http.hpp + ${LIBDIR}/slic3r/Utils/OctoPrint.cpp + ${LIBDIR}/slic3r/Utils/OctoPrint.hpp ) add_library(admesh STATIC @@ -340,6 +342,7 @@ set(XS_XSP_FILES ${XSP_DIR}/Surface.xsp ${XSP_DIR}/SurfaceCollection.xsp ${XSP_DIR}/TriangleMesh.xsp + ${XSP_DIR}/Utils_OctoPrint.xsp ${XSP_DIR}/XS.xsp ) foreach (file ${XS_XSP_FILES}) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 4077668f8..2b95ffa84 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -904,10 +904,17 @@ PrintConfigDef::PrintConfigDef() def->cli = "octoprint-apikey=s"; def->default_value = new ConfigOptionString(""); + def = this->add("octoprint_cafile", coString); + def->label = "HTTPS CA file"; + def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " + "If left blank, the default OS CA certificate repository is used."; + def->cli = "octoprint-cafile=s"; + def->default_value = new ConfigOptionString(""); + def = this->add("octoprint_host", coString); - def->label = L("Host or IP"); + def->label = L("Hostname, IP or URL"); def->tooltip = L("Slic3r can upload G-code files to OctoPrint. This field should contain " - "the hostname or IP address of the OctoPrint instance."); + "the hostname, IP address or URL of the OctoPrint instance."); def->cli = "octoprint-host=s"; def->default_value = new ConfigOptionString(""); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 7d2fb0145..c589d917a 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -684,6 +684,7 @@ class HostConfig : public StaticPrintConfig public: ConfigOptionString octoprint_host; ConfigOptionString octoprint_apikey; + ConfigOptionString octoprint_cafile; ConfigOptionString serial_port; ConfigOptionInt serial_speed; @@ -692,6 +693,7 @@ protected: { OPT_PTR(octoprint_host); OPT_PTR(octoprint_apikey); + OPT_PTR(octoprint_cafile); OPT_PTR(serial_port); OPT_PTR(serial_speed); } diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index cb2ef7368..d7c9a590a 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -64,6 +64,7 @@ REGISTER_CLASS(PresetCollection, "GUI::PresetCollection"); REGISTER_CLASS(PresetBundle, "GUI::PresetBundle"); REGISTER_CLASS(PresetHints, "GUI::PresetHints"); REGISTER_CLASS(TabIface, "GUI::Tab"); +REGISTER_CLASS(OctoPrint, "OctoPrint"); SV* ConfigBase__as_hash(ConfigBase* THIS) { diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index b0d3f7629..f053d7b16 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -5,9 +5,9 @@ #include #include #include - #include #include +#include #if __APPLE__ #import @@ -570,4 +570,18 @@ wxString from_u8(std::string str) return wxString::FromUTF8(str.c_str()); } +wxWindow *get_widget_by_id(int id) +{ + if (g_wxMainFrame == nullptr) { + throw std::runtime_error("Main frame not set"); + } + + wxWindow *window = g_wxMainFrame->FindWindow(id); + if (window == nullptr) { + throw std::runtime_error((boost::format("Could not find widget by ID: %1%") % id).str()); + } + + return window; +} + } } diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 3e519b691..e913a9450 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -6,6 +6,7 @@ #include "Config.hpp" class wxApp; +class wxWindow; class wxFrame; class wxWindow; class wxMenuBar; @@ -118,6 +119,8 @@ wxString L_str(std::string str); // Return wxString from std::string in UTF8 wxString from_u8(std::string str); +wxWindow *get_widget_by_id(int id); + } } diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index c28c989fb..52717e1fc 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -224,7 +224,7 @@ const std::vector& Preset::printer_options() if (s_opts.empty()) { s_opts = { "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", - "octoprint_host", "octoprint_apikey", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", + "octoprint_host", "octoprint_apikey", "octoprint_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "between_objects_gcode", "printer_notes" }; diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp new file mode 100644 index 000000000..019a4e238 --- /dev/null +++ b/xs/src/slic3r/Utils/OctoPrint.cpp @@ -0,0 +1,105 @@ +#include "OctoPrint.hpp" + +#include +#include + +#include +#include + +#include "libslic3r/PrintConfig.hpp" +#include "slic3r/GUI/GUI.hpp" +#include "Http.hpp" + + +namespace Slic3r { + + +OctoPrint::OctoPrint(DynamicPrintConfig *config) : + host(config->opt_string("octoprint_host")), + apikey(config->opt_string("octoprint_apikey")), + cafile(config->opt_string("octoprint_cafile")) +{} + +std::string OctoPrint::test() const +{ + // Since the request is performed synchronously here, + // it is ok to refer to `res` from within the closure + std::string res; + + auto http = Http::get(std::move(make_url("api/version"))); + set_auth(http); + http.on_error([&](std::string, std::string error, unsigned status) { + res = format_error(error, status); + }) + .perform_sync(); + + return res; +} + +void OctoPrint::send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print) const +{ + auto http = Http::post(std::move(make_url("api/files/local"))); + set_auth(http); + http.form_add("print", print ? "true" : "false") + .form_add_file("file", filename) + .on_complete([=](std::string body, unsigned status) { + wxWindow *window = GUI::get_widget_by_id(windowId); + wxCommandEvent* evt = new wxCommandEvent(completeEvt); + evt->SetString("G-code file successfully uploaded to the OctoPrint server"); + evt->SetInt(100); + wxQueueEvent(window, evt); + }) + .on_error([=](std::string body, std::string error, unsigned status) { + wxWindow *window = GUI::get_widget_by_id(windowId); + + wxCommandEvent* evt_complete = new wxCommandEvent(completeEvt); + evt_complete->SetInt(100); + wxQueueEvent(window, evt_complete); + + wxCommandEvent* evt_error = new wxCommandEvent(errorEvt); + evt_error->SetString(wxString::Format("Error while uploading to the OctoPrint server: %s", format_error(error, status))); + wxQueueEvent(window, evt_error); + }) + .perform(); +} + +void OctoPrint::set_auth(Http &http) const +{ + http.header("X-Api-Key", apikey); + + if (! cafile.empty()) { + http.ca_file(cafile); + } +} + +std::string OctoPrint::make_url(const std::string &path) const +{ + if (host.find("http://") == 0 || host.find("https://") == 0) { + if (host.back() == '/') { + return std::move((boost::format("%1%%2%") % host % path).str()); + } else { + return std::move((boost::format("%1%/%2%") % host % path).str()); + } + } else { + return std::move((boost::format("http://%1%/%2%") % host % path).str()); + } +} + +std::string OctoPrint::format_error(std::string error, unsigned status) +{ + if (status != 0) { + std::string res{"HTTP "}; + res.append(std::to_string(status)); + + if (status == 401) { + res.append(": Invalid API key"); + } + + return std::move(res); + } else { + return std::move(error); + } +} + + +} diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp new file mode 100644 index 000000000..c5ed70ab5 --- /dev/null +++ b/xs/src/slic3r/Utils/OctoPrint.hpp @@ -0,0 +1,35 @@ +#ifndef slic3r_OctoPrint_hpp_ +#define slic3r_OctoPrint_hpp_ + +#include + +#include "Http.hpp" + +namespace Slic3r { + + +class DynamicPrintConfig; +class Http; + +class OctoPrint +{ +public: + OctoPrint(DynamicPrintConfig *config); + + std::string test() const; + // XXX: style + void send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print = false) 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; + static std::string format_error(std::string error, unsigned status); +}; + + +} + +#endif diff --git a/xs/xsp/Utils_OctoPrint.xsp b/xs/xsp/Utils_OctoPrint.xsp new file mode 100644 index 000000000..062af4e0c --- /dev/null +++ b/xs/xsp/Utils_OctoPrint.xsp @@ -0,0 +1,14 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "slic3r/Utils/OctoPrint.hpp" +%} + +%name{Slic3r::OctoPrint} class OctoPrint { + OctoPrint(DynamicPrintConfig *config); + ~OctoPrint(); + + std::string test() const; + void send_gcode(int windowId, int completeEvt, int errorEvt, std::string filename, bool print = false) const; +}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index e54601632..87a8d8d86 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -236,6 +236,10 @@ Ref O_OBJECT_SLIC3R_T TabIface* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T +OctoPrint* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + Axis T_UV ExtrusionLoopRole T_UV ExtrusionRole T_UV