WIP: OctoPrint

This commit is contained in:
Vojtech Kral 2018-02-07 11:37:15 +01:00
parent 79ee7c9a36
commit 7cfc5204c8
13 changed files with 210 additions and 46 deletions

View File

@ -410,6 +410,14 @@ sub _init_menubar {
}); });
} }
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 # menubar
# assign menubar to frame after appending items, otherwise special items # assign menubar to frame after appending items, otherwise special items
# will not be handled correctly # will not be handled correctly
@ -424,6 +432,8 @@ sub _init_menubar {
# (Select application language from the list of installed languages) # (Select application language from the list of installed languages)
Slic3r::GUI::add_debug_menu($menubar, $self->{lang_ch_event}); Slic3r::GUI::add_debug_menu($menubar, $self->{lang_ch_event});
$menubar->Append($helpMenu, L("&Help")); $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); $self->SetMenuBar($menubar);
} }
} }

View File

@ -52,7 +52,7 @@ sub new {
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
$self->{config} = Slic3r::Config::new_from_defaults_keys([qw( $self->{config} = Slic3r::Config::new_from_defaults_keys([qw(
bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height 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 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 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); wxTheApp->notify($message);
$self->do_print if $do_print; $self->do_print if $do_print;
# Send $self->{send_gcode_file} to OctoPrint. # 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->{print_file} = undef;
$self->{send_gcode_file} = undef; $self->{send_gcode_file} = undef;
$self->{"print_info_cost"}->SetLabel(sprintf("%.2f" , $self->{print}->total_cost)); $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; my $filament_names = wxTheApp->{preset_bundle}->filament_presets;
$filament_stats = { map { $filament_names->[$_] => $filament_stats->{$_} } keys %$filament_stats }; $filament_stats = { map { $filament_names->[$_] => $filament_stats->{$_} } keys %$filament_stats };
$printer_panel->load_print_job($self->{print_file}, $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 { sub export_stl {

View File

@ -201,6 +201,8 @@ add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/GUI/wxExtensions.hpp ${LIBDIR}/slic3r/GUI/wxExtensions.hpp
${LIBDIR}/slic3r/Utils/Http.cpp ${LIBDIR}/slic3r/Utils/Http.cpp
${LIBDIR}/slic3r/Utils/Http.hpp ${LIBDIR}/slic3r/Utils/Http.hpp
${LIBDIR}/slic3r/Utils/OctoPrint.cpp
${LIBDIR}/slic3r/Utils/OctoPrint.hpp
) )
add_library(admesh STATIC add_library(admesh STATIC
@ -340,6 +342,7 @@ set(XS_XSP_FILES
${XSP_DIR}/Surface.xsp ${XSP_DIR}/Surface.xsp
${XSP_DIR}/SurfaceCollection.xsp ${XSP_DIR}/SurfaceCollection.xsp
${XSP_DIR}/TriangleMesh.xsp ${XSP_DIR}/TriangleMesh.xsp
${XSP_DIR}/Utils_OctoPrint.xsp
${XSP_DIR}/XS.xsp ${XSP_DIR}/XS.xsp
) )
foreach (file ${XS_XSP_FILES}) foreach (file ${XS_XSP_FILES})

View File

@ -904,10 +904,17 @@ PrintConfigDef::PrintConfigDef()
def->cli = "octoprint-apikey=s"; def->cli = "octoprint-apikey=s";
def->default_value = new ConfigOptionString(""); 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 = 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 " 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->cli = "octoprint-host=s";
def->default_value = new ConfigOptionString(""); def->default_value = new ConfigOptionString("");

View File

@ -684,6 +684,7 @@ class HostConfig : public StaticPrintConfig
public: public:
ConfigOptionString octoprint_host; ConfigOptionString octoprint_host;
ConfigOptionString octoprint_apikey; ConfigOptionString octoprint_apikey;
ConfigOptionString octoprint_cafile;
ConfigOptionString serial_port; ConfigOptionString serial_port;
ConfigOptionInt serial_speed; ConfigOptionInt serial_speed;
@ -692,6 +693,7 @@ protected:
{ {
OPT_PTR(octoprint_host); OPT_PTR(octoprint_host);
OPT_PTR(octoprint_apikey); OPT_PTR(octoprint_apikey);
OPT_PTR(octoprint_cafile);
OPT_PTR(serial_port); OPT_PTR(serial_port);
OPT_PTR(serial_speed); OPT_PTR(serial_speed);
} }

View File

@ -64,6 +64,7 @@ REGISTER_CLASS(PresetCollection, "GUI::PresetCollection");
REGISTER_CLASS(PresetBundle, "GUI::PresetBundle"); REGISTER_CLASS(PresetBundle, "GUI::PresetBundle");
REGISTER_CLASS(PresetHints, "GUI::PresetHints"); REGISTER_CLASS(PresetHints, "GUI::PresetHints");
REGISTER_CLASS(TabIface, "GUI::Tab"); REGISTER_CLASS(TabIface, "GUI::Tab");
REGISTER_CLASS(OctoPrint, "OctoPrint");
SV* ConfigBase__as_hash(ConfigBase* THIS) SV* ConfigBase__as_hash(ConfigBase* THIS)
{ {

View File

@ -5,9 +5,9 @@
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/classification.hpp>
#include <boost/format.hpp>
#if __APPLE__ #if __APPLE__
#import <IOKit/pwr_mgt/IOPMLib.h> #import <IOKit/pwr_mgt/IOPMLib.h>
@ -570,4 +570,18 @@ wxString from_u8(std::string str)
return wxString::FromUTF8(str.c_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;
}
} } } }

View File

@ -6,6 +6,7 @@
#include "Config.hpp" #include "Config.hpp"
class wxApp; class wxApp;
class wxWindow;
class wxFrame; class wxFrame;
class wxWindow; class wxWindow;
class wxMenuBar; class wxMenuBar;
@ -118,6 +119,8 @@ wxString L_str(std::string str);
// Return wxString from std::string in UTF8 // Return wxString from std::string in UTF8
wxString from_u8(std::string str); wxString from_u8(std::string str);
wxWindow *get_widget_by_id(int id);
} }
} }

View File

@ -224,7 +224,7 @@ const std::vector<std::string>& Preset::printer_options()
if (s_opts.empty()) { if (s_opts.empty()) {
s_opts = { s_opts = {
"bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "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", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
"between_objects_gcode", "printer_notes" "between_objects_gcode", "printer_notes"
}; };

View File

@ -0,0 +1,105 @@
#include "OctoPrint.hpp"
#include <iostream>
#include <boost/format.hpp>
#include <wx/frame.h>
#include <wx/event.h>
#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);
}
}
}

View File

@ -0,0 +1,35 @@
#ifndef slic3r_OctoPrint_hpp_
#define slic3r_OctoPrint_hpp_
#include <string>
#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

View File

@ -0,0 +1,14 @@
%module{Slic3r::XS};
%{
#include <xsinit.h>
#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;
};

View File

@ -236,6 +236,10 @@ Ref<PresetHints> O_OBJECT_SLIC3R_T
TabIface* O_OBJECT_SLIC3R TabIface* O_OBJECT_SLIC3R
Ref<TabIface> O_OBJECT_SLIC3R_T Ref<TabIface> O_OBJECT_SLIC3R_T
OctoPrint* O_OBJECT_SLIC3R
Ref<OctoPrint> O_OBJECT_SLIC3R_T
Clone<OctoPrint> O_OBJECT_SLIC3R_T
Axis T_UV Axis T_UV
ExtrusionLoopRole T_UV ExtrusionLoopRole T_UV
ExtrusionRole T_UV ExtrusionRole T_UV