Merge branch 'master' of https://github.com/prusa3d/Slic3r into opengl_to_cpp

This commit is contained in:
Enrico Turri 2018-05-22 08:33:23 +02:00
commit 59af3fb866
434 changed files with 185728 additions and 109 deletions

View file

@ -0,0 +1,402 @@
#include "FirmwareDialog.hpp"
#include <numeric>
#include <algorithm>
#include <boost/format.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/log/trivial.hpp>
#include <wx/app.h>
#include <wx/event.h>
#include <wx/sizer.h>
#include <wx/settings.h>
#include <wx/panel.h>
#include <wx/button.h>
#include <wx/filepicker.h>
#include <wx/textctrl.h>
#include <wx/stattext.h>
#include <wx/combobox.h>
#include <wx/gauge.h>
#include <wx/collpane.h>
#include <wx/msgdlg.h>
#include "libslic3r/Utils.hpp"
#include "avrdude/avrdude-slic3r.hpp"
#include "GUI.hpp"
#include "../Utils/Serial.hpp"
namespace fs = boost::filesystem;
namespace Slic3r {
// This enum discriminates the kind of information in EVT_AVRDUDE,
// it's stored in the ExtraLong field of wxCommandEvent.
enum AvrdudeEvent
{
AE_MESSAGE,
AE_PRORGESS,
AE_EXIT,
};
wxDECLARE_EVENT(EVT_AVRDUDE, wxCommandEvent);
wxDEFINE_EVENT(EVT_AVRDUDE, wxCommandEvent);
// Private
struct FirmwareDialog::priv
{
enum AvrDudeComplete
{
AC_NONE,
AC_SUCCESS,
AC_FAILURE,
AC_CANCEL,
};
FirmwareDialog *q; // PIMPL back pointer ("Q-Pointer")
wxComboBox *port_picker;
std::vector<Utils::SerialPortInfo> ports;
wxFilePickerCtrl *hex_picker;
wxStaticText *txt_status;
wxStaticText *txt_progress;
wxGauge *progressbar;
wxCollapsiblePane *spoiler;
wxTextCtrl *txt_stdout;
wxButton *btn_rescan;
wxButton *btn_close;
wxButton *btn_flash;
wxString btn_flash_label_ready;
wxString btn_flash_label_flashing;
// This is a shared pointer holding the background AvrDude task
// also serves as a status indication (it is set _iff_ the background task is running, otherwise it is reset).
AvrDude::Ptr avrdude;
std::string avrdude_config;
unsigned progress_tasks_done;
bool cancelled;
priv(FirmwareDialog *q) :
q(q),
btn_flash_label_ready(_(L("Flash!"))),
btn_flash_label_flashing(_(L("Cancel"))),
avrdude_config((fs::path(::Slic3r::resources_dir()) / "avrdude" / "avrdude.conf").string()),
progress_tasks_done(0),
cancelled(false)
{}
void find_serial_ports();
void flashing_status(bool flashing, AvrDudeComplete complete = AC_NONE);
void perform_upload();
void cancel();
void on_avrdude(const wxCommandEvent &evt);
};
void FirmwareDialog::priv::find_serial_ports()
{
auto new_ports = Utils::scan_serial_ports_extended();
if (new_ports != this->ports) {
this->ports = new_ports;
port_picker->Clear();
for (const auto &port : this->ports)
port_picker->Append(port.friendly_name);
if (ports.size() > 0) {
int idx = port_picker->GetValue().IsEmpty() ? 0 : -1;
for (int i = 0; i < (int)this->ports.size(); ++ i)
if (this->ports[i].is_printer) {
idx = i;
break;
}
if (idx != -1)
port_picker->SetSelection(idx);
}
}
}
void FirmwareDialog::priv::flashing_status(bool value, AvrDudeComplete complete)
{
if (value) {
txt_stdout->Clear();
txt_status->SetLabel(_(L("Flashing in progress. Please do not disconnect the printer!")));
txt_status->SetForegroundColour(GUI::get_label_clr_modified());
port_picker->Disable();
btn_rescan->Disable();
hex_picker->Disable();
btn_close->Disable();
btn_flash->SetLabel(btn_flash_label_flashing);
progressbar->SetRange(200); // See progress callback below
progressbar->SetValue(0);
progress_tasks_done = 0;
cancelled = false;
} else {
auto text_color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
port_picker->Enable();
btn_rescan->Enable();
hex_picker->Enable();
btn_close->Enable();
btn_flash->SetLabel(btn_flash_label_ready);
txt_status->SetForegroundColour(text_color);
progressbar->SetValue(200);
switch (complete) {
case AC_SUCCESS: txt_status->SetLabel(_(L("Flashing succeeded!"))); break;
case AC_FAILURE: txt_status->SetLabel(_(L("Flashing failed. Please see the avrdude log below."))); break;
case AC_CANCEL: txt_status->SetLabel(_(L("Flashing cancelled."))); break;
}
}
}
void FirmwareDialog::priv::perform_upload()
{
auto filename = hex_picker->GetPath();
std::string port = port_picker->GetValue().ToStdString();
int selection = port_picker->GetSelection();
if (selection != -1) {
// Verify whether the combo box list selection equals to the combo box edit value.
if (this->ports[selection].friendly_name == port)
port = this->ports[selection].port;
}
if (filename.IsEmpty() || port.empty()) { return; }
flashing_status(true);
std::vector<std::string> args {{
"-v",
"-p", "atmega2560",
"-c", "wiring",
"-P", port,
"-b", "115200", // XXX: is this ok to hardcode?
"-D",
"-U", (boost::format("flash:w:%1%:i") % filename.ToStdString()).str()
}};
BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: "
<< std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) {
return a + ' ' + b;
});
// It is ok here to use the q-pointer to the FirmwareDialog
// because the dialog ensures it doesn't exit before the background thread is done.
auto q = this->q;
avrdude = AvrDude()
.sys_config(avrdude_config)
.args(args)
.on_message(std::move([q](const char *msg, unsigned /* size */) {
auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId());
evt->SetExtraLong(AE_MESSAGE);
evt->SetString(msg);
wxQueueEvent(q, evt);
}))
.on_progress(std::move([q](const char * /* task */, unsigned progress) {
auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId());
evt->SetExtraLong(AE_PRORGESS);
evt->SetInt(progress);
wxQueueEvent(q, evt);
}))
.on_complete(std::move([q](int status) {
auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId());
evt->SetExtraLong(AE_EXIT);
evt->SetInt(status);
wxQueueEvent(q, evt);
}))
.run();
}
void FirmwareDialog::priv::cancel()
{
if (avrdude) {
cancelled = true;
txt_status->SetLabel(_(L("Cancelling...")));
avrdude->cancel();
}
}
void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt)
{
AvrDudeComplete complete_kind;
switch (evt.GetExtraLong()) {
case AE_MESSAGE:
txt_stdout->AppendText(evt.GetString());
break;
case AE_PRORGESS:
// We try to track overall progress here.
// When uploading the firmware, avrdude first reads a littlebit of status data,
// then performs write, then reading (verification).
// We Pulse() during the first read and combine progress of the latter two tasks.
if (progress_tasks_done == 0) {
progressbar->Pulse();
} else {
progressbar->SetValue(progress_tasks_done - 100 + evt.GetInt());
}
if (evt.GetInt() == 100) {
progress_tasks_done += 100;
}
break;
case AE_EXIT:
BOOST_LOG_TRIVIAL(info) << "avrdude exit code: " << evt.GetInt();
complete_kind = cancelled ? AC_CANCEL : (evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE);
flashing_status(false, complete_kind);
// Make sure the background thread is collected and the AvrDude object reset
if (avrdude) { avrdude->join(); }
avrdude.reset();
break;
default:
break;
}
}
// Public
FirmwareDialog::FirmwareDialog(wxWindow *parent) :
wxDialog(parent, wxID_ANY, _(L("Firmware flasher")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
p(new priv(this))
{
enum {
DIALOG_MARGIN = 15,
SPACING = 10,
MIN_WIDTH = 600,
MIN_HEIGHT = 200,
MIN_HEIGHT_EXPANDED = 500,
};
wxFont status_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
status_font.MakeBold();
wxFont mono_font(wxFontInfo().Family(wxFONTFAMILY_TELETYPE));
mono_font.MakeSmaller();
auto *panel = new wxPanel(this);
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
panel->SetSizer(vsizer);
auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:")));
p->port_picker = new wxComboBox(panel, wxID_ANY);
p->btn_rescan = new wxButton(panel, wxID_ANY, _(L("Rescan")));
auto *port_sizer = new wxBoxSizer(wxHORIZONTAL);
port_sizer->Add(p->port_picker, 1, wxEXPAND | wxRIGHT, SPACING);
port_sizer->Add(p->btn_rescan, 0);
auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:")));
p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY);
auto *label_status = new wxStaticText(panel, wxID_ANY, _(L("Status:")));
p->txt_status = new wxStaticText(panel, wxID_ANY, _(L("Ready")));
p->txt_status->SetFont(status_font);
auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:")));
p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH);
auto *grid = new wxFlexGridSizer(2, SPACING, SPACING);
grid->AddGrowableCol(1);
grid->Add(label_port_picker, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(port_sizer, 0, wxEXPAND);
grid->Add(label_hex_picker, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->hex_picker, 0, wxEXPAND);
grid->Add(label_status, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->txt_status, 0, wxEXPAND);
grid->Add(label_progress, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->progressbar, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
vsizer->Add(grid, 0, wxEXPAND | wxTOP | wxBOTTOM, SPACING);
p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log")));
auto *spoiler_pane = p->spoiler->GetPane();
auto *spoiler_sizer = new wxBoxSizer(wxVERTICAL);
p->txt_stdout = new wxTextCtrl(spoiler_pane, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY);
p->txt_stdout->SetFont(mono_font);
spoiler_sizer->Add(p->txt_stdout, 1, wxEXPAND);
spoiler_pane->SetSizer(spoiler_sizer);
// The doc says proportion need to be 0 for wxCollapsiblePane.
// Experience says it needs to be 1, otherwise things won't get sized properly.
vsizer->Add(p->spoiler, 1, wxEXPAND | wxBOTTOM, SPACING);
p->btn_close = new wxButton(panel, wxID_CLOSE);
p->btn_flash = new wxButton(panel, wxID_ANY, p->btn_flash_label_ready);
auto *bsizer = new wxBoxSizer(wxHORIZONTAL);
bsizer->Add(p->btn_close);
bsizer->AddStretchSpacer();
bsizer->Add(p->btn_flash);
vsizer->Add(bsizer, 0, wxEXPAND);
auto *topsizer = new wxBoxSizer(wxVERTICAL);
topsizer->Add(panel, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT));
SetSizerAndFit(topsizer);
const auto size = GetSize();
SetSize(std::max(size.GetWidth(), static_cast<int>(MIN_WIDTH)), std::max(size.GetHeight(), static_cast<int>(MIN_HEIGHT)));
Layout();
p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [this](wxCollapsiblePaneEvent &evt) {
// Dialog size gets screwed up by wxCollapsiblePane, we need to fix it here
if (evt.GetCollapsed()) {
this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT));
} else {
this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT_EXPANDED));
}
this->Fit();
this->Layout();
});
p->btn_close->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { this->Close(); });
p->btn_rescan->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { this->p->find_serial_ports(); });
p->btn_flash->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
if (this->p->avrdude) {
// Flashing is in progress, ask the user if they're really sure about canceling it
wxMessageDialog dlg(this,
_(L("Are you sure you want to cancel firmware flashing?\nThis could leave your printer in an unusable state!")),
_(L("Confirmation")),
wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
if (dlg.ShowModal() == wxID_YES) {
this->p->cancel();
}
} else {
// Start a flashing task
this->p->perform_upload();
}
});
Bind(EVT_AVRDUDE, [this](wxCommandEvent &evt) { this->p->on_avrdude(evt); });
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt) {
if (this->p->avrdude) {
evt.Veto();
} else {
evt.Skip();
}
});
p->find_serial_ports();
}
FirmwareDialog::~FirmwareDialog()
{
// Needed bacuse of forward defs
}
void FirmwareDialog::run(wxWindow *parent)
{
FirmwareDialog dialog(parent);
dialog.ShowModal();
}
}

View file

@ -0,0 +1,31 @@
#ifndef slic3r_FirmwareDialog_hpp_
#define slic3r_FirmwareDialog_hpp_
#include <memory>
#include <wx/dialog.h>
namespace Slic3r {
class FirmwareDialog: public wxDialog
{
public:
FirmwareDialog(wxWindow *parent);
FirmwareDialog(FirmwareDialog &&) = delete;
FirmwareDialog(const FirmwareDialog &) = delete;
FirmwareDialog &operator=(FirmwareDialog &&) = delete;
FirmwareDialog &operator=(const FirmwareDialog &) = delete;
~FirmwareDialog();
static void run(wxWindow *parent);
private:
struct priv;
std::unique_ptr<priv> p;
};
}
#endif

View file

@ -4,11 +4,8 @@
#include <assert.h>
#include <cmath>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#if __APPLE__
@ -25,7 +22,6 @@
#undef max
#endif
#include "boost/nowide/convert.hpp"
#pragma comment(lib, "user32.lib")
#endif
#include <wx/app.h>
@ -55,10 +51,12 @@
#include "Preferences.hpp"
#include "PresetBundle.hpp"
#include "UpdateDialogs.hpp"
#include "FirmwareDialog.hpp"
#include "../Utils/PresetUpdater.hpp"
#include "../Config/Snapshot.hpp"
namespace Slic3r { namespace GUI {
#if __APPLE__
@ -86,83 +84,6 @@ void enable_screensaver()
#endif
}
std::vector<std::string> scan_serial_ports()
{
std::vector<std::string> out;
#ifdef _WIN32
// 1) Open the registry key SERIALCOM.
HKEY hKey;
LONG lRes = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_READ, &hKey);
assert(lRes == ERROR_SUCCESS);
if (lRes == ERROR_SUCCESS) {
// 2) Get number of values of SERIALCOM key.
DWORD cValues; // number of values for key
{
TCHAR achKey[255]; // buffer for subkey name
DWORD cbName; // size of name string
TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name
DWORD cchClassName = MAX_PATH; // size of class string
DWORD cSubKeys=0; // number of subkeys
DWORD cbMaxSubKey; // longest subkey size
DWORD cchMaxClass; // longest class string
DWORD cchMaxValue; // longest value name
DWORD cbMaxValueData; // longest value data
DWORD cbSecurityDescriptor; // size of security descriptor
FILETIME ftLastWriteTime; // last write time
// Get the class name and the value count.
lRes = RegQueryInfoKey(
hKey, // key handle
achClass, // buffer for class name
&cchClassName, // size of class string
NULL, // reserved
&cSubKeys, // number of subkeys
&cbMaxSubKey, // longest subkey size
&cchMaxClass, // longest class string
&cValues, // number of values for this key
&cchMaxValue, // longest value name
&cbMaxValueData, // longest value data
&cbSecurityDescriptor, // security descriptor
&ftLastWriteTime); // last write time
assert(lRes == ERROR_SUCCESS);
}
// 3) Read the SERIALCOM values.
{
DWORD dwIndex = 0;
for (int i = 0; i < cValues; ++ i, ++ dwIndex) {
wchar_t valueName[2048];
DWORD valNameLen = 2048;
DWORD dataType;
wchar_t data[2048];
DWORD dataSize = 4096;
lRes = ::RegEnumValueW(hKey, dwIndex, valueName, &valNameLen, nullptr, &dataType, (BYTE*)&data, &dataSize);
if (lRes == ERROR_SUCCESS && dataType == REG_SZ && valueName[0] != 0)
out.emplace_back(boost::nowide::narrow(data));
}
}
::RegCloseKey(hKey);
}
#else
// UNIX and OS X
std::initializer_list<const char*> prefixes { "ttyUSB" , "ttyACM", "tty.", "cu.", "rfcomm" };
for (auto &dir_entry : boost::filesystem::directory_iterator(boost::filesystem::path("/dev"))) {
std::string name = dir_entry.path().filename().string();
for (const char *prefix : prefixes) {
if (boost::starts_with(name, prefix)) {
out.emplace_back(dir_entry.path().string());
break;
}
}
}
#endif
out.erase(std::remove_if(out.begin(), out.end(),
[](const std::string &key){
return boost::starts_with(key, "Bluetooth") || boost::starts_with(key, "FireFly");
}),
out.end());
return out;
}
bool debugged()
{
#ifdef _WIN32
@ -383,6 +304,7 @@ enum ConfigMenuIDs {
ConfigMenuUpdate,
ConfigMenuPreferences,
ConfigMenuLanguage,
ConfigMenuFlashFirmware,
ConfigMenuCnt,
};
@ -396,11 +318,15 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l
local_menu->Append(config_id_base + ConfigMenuWizard, ConfigWizard::name() + "\u2026", config_wizard_tooltip);
local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots"))+"\u2026", _(L("Inspect / activate configuration snapshots")));
local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _(L("Take Configuration Snapshot")), _(L("Capture a configuration snapshot")));
local_menu->Append(config_id_base + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates")));
// local_menu->Append(config_id_base + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates")));
local_menu->AppendSeparator();
local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("Preferences"))+"\u2026\tCtrl+,", _(L("Application preferences")));
local_menu->AppendSeparator();
local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language")));
local_menu->AppendSeparator();
local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _(L("Flash printer firmware")), _(L("Upload a firmware image into an Arduino based printer")));
// TODO: for when we're able to flash dictionaries
// local_menu->Append(config_id_base + FirmwareMenuDict, _(L("Flash language file")), _(L("Upload a language dictionary file into a Prusa printer")));
local_menu->Bind(wxEVT_MENU, [config_id_base, event_language_change, event_preferences_changed](wxEvent &event){
switch (event.GetId() - config_id_base) {
case ConfigMenuWizard:
@ -455,12 +381,22 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l
}
}
break;
}
}
case ConfigMenuFlashFirmware:
FirmwareDialog::run(g_wxMainFrame);
break;
default:
break;
}
});
menu->Append(local_menu, _(L("&Configuration")));
}
void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change)
{
add_config_menu(menu, event_language_change, event_language_change);
}
// This is called when closing the application, when loading a config file or when starting the config wizard
// to notify the user whether he is aware that some preset changes will be lost.
bool check_unsaved_changes()
@ -992,4 +928,35 @@ void about()
dlg.Destroy();
}
void desktop_open_datadir_folder()
{
// Execute command to open a file explorer, platform dependent.
std::string cmd =
#ifdef _WIN32
"explorer "
#elif __APPLE__
"open "
#else
"xdg-open "
#endif
;
// Escape the path, platform dependent.
std::string path = data_dir();
#ifdef _WIN32
// Enclose the path into double quotes on Windows. A quote character is forbidden in file names,
// therefore it does not need to be escaped.
cmd += '"';
cmd += path;
cmd += '"';
#else
// Enclose the path into single quotes on Unix / OSX. All single quote characters need to be escaped
// inside a file name.
cmd += '\'';
boost::replace_all(path, "'", "\\'");
cmd += path;
cmd += '\'';
#endif
::wxExecute(wxString::FromUTF8(cmd.c_str()), wxEXEC_ASYNC, nullptr);
}
} }

View file

@ -68,7 +68,6 @@ inline t_file_wild_card& get_file_wild_card() {
void disable_screensaver();
void enable_screensaver();
std::vector<std::string> scan_serial_ports();
bool debugged();
void break_to_debugger();
@ -91,7 +90,7 @@ unsigned get_colour_approx_luma(const wxColour &colour);
void set_label_clr_modified(const wxColour& clr);
void set_label_clr_sys(const wxColour& clr);
extern void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_language_change);
extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change);
// This is called when closing the application, when loading a config file or when starting the config wizard
// to notify the user whether he is aware that some preset changes will be lost.
@ -159,7 +158,9 @@ void add_export_option(wxFileDialog* dlg, const std::string& format);
int get_export_option(wxFileDialog* dlg);
// Display an About dialog
void about();
extern void about();
// Ask the destop to open the datadir using the default file explorer.
extern void desktop_open_datadir_folder();
} // namespace GUI
} // namespace Slic3r

View file

@ -6,6 +6,7 @@
#include "slic3r/Utils/Http.hpp"
#include "slic3r/Utils/OctoPrint.hpp"
#include "slic3r/Utils/Serial.hpp"
#include "BonjourDialog.hpp"
#include "WipeTowerDialog.hpp"
#include "ButtonsDescription.hpp"
@ -1693,7 +1694,7 @@ void TabPrinter::build()
void TabPrinter::update_serial_ports(){
Field *field = get_field("serial_port");
Choice *choice = static_cast<Choice *>(field);
choice->set_values(scan_serial_ports());
choice->set_values(Utils::scan_serial_ports());
}
void TabPrinter::extruders_count_changed(size_t extruders_count){
@ -1709,7 +1710,8 @@ void TabPrinter::build_extruder_pages(){
size_t n_before_extruders = 2; // Count of pages before Extruder pages
size_t n_after_single_extruder_MM = 2; // Count of pages after single_extruder_multi_material page
if (m_extruders_count_old == m_extruders_count || m_extruders_count <= 2)
if (m_extruders_count_old == m_extruders_count ||
(m_has_single_extruder_MM_page && m_extruders_count == 1))
{
// if we have a single extruder MM setup, add a page with configuration options:
for (int i = 0; i < m_pages.size(); ++i) // first make sure it's not there already
@ -1717,16 +1719,19 @@ void TabPrinter::build_extruder_pages(){
m_pages.erase(m_pages.begin() + i);
break;
}
if (m_extruders_count > 1 && m_config->opt_bool("single_extruder_multi_material")) {
// create a page, but pretend it's an extruder page, so we can add it to m_pages ourselves
auto page = add_options_page(_(L("Single extruder MM setup")), "printer_empty.png", true);
auto optgroup = page->new_optgroup(_(L("Single extruder multimaterial parameters")));
optgroup->append_single_option_line("cooling_tube_retraction");
optgroup->append_single_option_line("cooling_tube_length");
optgroup->append_single_option_line("parking_pos_retraction");
m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page);
}
m_has_single_extruder_MM_page = false;
}
if (m_extruders_count > 1 && m_config->opt_bool("single_extruder_multi_material") && !m_has_single_extruder_MM_page) {
// create a page, but pretend it's an extruder page, so we can add it to m_pages ourselves
auto page = add_options_page(_(L("Single extruder MM setup")), "printer_empty.png", true);
auto optgroup = page->new_optgroup(_(L("Single extruder multimaterial parameters")));
optgroup->append_single_option_line("cooling_tube_retraction");
optgroup->append_single_option_line("cooling_tube_length");
optgroup->append_single_option_line("parking_pos_retraction");
m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page);
m_has_single_extruder_MM_page = true;
}
for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx){
//# build page

View file

@ -312,6 +312,7 @@ public:
//Slic3r::GUI::Tab::Printer;
class TabPrinter : public Tab
{
bool m_has_single_extruder_MM_page = false;
public:
wxButton* m_serial_test_btn;
wxButton* m_octoprint_host_test_btn;

View file

@ -0,0 +1,193 @@
#include "Serial.hpp"
#include <algorithm>
#include <string>
#include <vector>
#include <fstream>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/format.hpp>
#if _WIN32
#include <Windows.h>
#include <Setupapi.h>
#include <initguid.h>
#include <devguid.h>
// Undefine min/max macros incompatible with the standard library
// For example, std::numeric_limits<std::streamsize>::max()
// produces some weird errors
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#include "boost/nowide/convert.hpp"
#pragma comment(lib, "user32.lib")
#elif __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFString.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/serial/IOSerialKeys.h>
#include <IOKit/serial/ioss.h>
#include <sys/syslimits.h>
#endif
namespace Slic3r {
namespace Utils {
static bool looks_like_printer(const std::string &friendly_name)
{
return friendly_name.find("Original Prusa") != std::string::npos;
}
#ifdef __linux__
static std::string get_tty_friendly_name(const std::string &path, const std::string &name)
{
const auto sysfs_product = (boost::format("/sys/class/tty/%1%/device/../product") % name).str();
std::ifstream file(sysfs_product);
std::string product;
std::getline(file, product);
return file.good() ? (boost::format("%1% (%2%)") % product % path).str() : path;
}
#endif
std::vector<SerialPortInfo> scan_serial_ports_extended()
{
std::vector<SerialPortInfo> output;
#ifdef _WIN32
SP_DEVINFO_DATA devInfoData = { 0 };
devInfoData.cbSize = sizeof(devInfoData);
// Get the tree containing the info for the ports.
HDEVINFO hDeviceInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS, 0, nullptr, DIGCF_PRESENT);
if (hDeviceInfo != INVALID_HANDLE_VALUE) {
// Iterate over all the devices in the tree.
for (int nDevice = 0; SetupDiEnumDeviceInfo(hDeviceInfo, nDevice, &devInfoData); ++ nDevice) {
SerialPortInfo port_info;
// Get the registry key which stores the ports settings.
HKEY hDeviceKey = SetupDiOpenDevRegKey(hDeviceInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
if (hDeviceKey) {
// Read in the name of the port.
wchar_t pszPortName[4096];
DWORD dwSize = sizeof(pszPortName);
DWORD dwType = 0;
if (RegQueryValueEx(hDeviceKey, L"PortName", NULL, &dwType, (LPBYTE)pszPortName, &dwSize) == ERROR_SUCCESS)
port_info.port = boost::nowide::narrow(pszPortName);
RegCloseKey(hDeviceKey);
if (port_info.port.empty())
continue;
}
// Find the size required to hold the device info.
DWORD regDataType;
DWORD reqSize = 0;
SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, nullptr, nullptr, 0, &reqSize);
std::vector<wchar_t> hardware_id(reqSize > 1 ? reqSize : 1);
// Now store it in a buffer.
if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, &regDataType, (BYTE*)hardware_id.data(), reqSize, nullptr))
continue;
port_info.hardware_id = boost::nowide::narrow(hardware_id.data());
// Find the size required to hold the friendly name.
reqSize = 0;
SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, nullptr, 0, &reqSize);
std::vector<wchar_t> friendly_name;
friendly_name.reserve(reqSize > 1 ? reqSize : 1);
// Now store it in a buffer.
if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, (BYTE*)friendly_name.data(), reqSize, nullptr)) {
port_info.friendly_name = port_info.port;
} else {
port_info.friendly_name = boost::nowide::narrow(friendly_name.data());
port_info.is_printer = looks_like_printer(port_info.friendly_name);
}
output.emplace_back(std::move(port_info));
}
}
#elif __APPLE__
// inspired by https://sigrok.org/wiki/Libserialport
CFMutableDictionaryRef classes = IOServiceMatching(kIOSerialBSDServiceValue);
if (classes != 0) {
io_iterator_t iter;
if (IOServiceGetMatchingServices(kIOMasterPortDefault, classes, &iter) == KERN_SUCCESS) {
io_object_t port;
while ((port = IOIteratorNext(iter)) != 0) {
CFTypeRef cf_property = IORegistryEntryCreateCFProperty(port, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
if (cf_property) {
char path[PATH_MAX];
Boolean result = CFStringGetCString((CFStringRef)cf_property, path, sizeof(path), kCFStringEncodingUTF8);
CFRelease(cf_property);
if (result) {
SerialPortInfo port_info;
port_info.port = path;
if ((cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
CFSTR("USB Interface Name"), kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
(cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
CFSTR("USB Product Name"), kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
(cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
CFSTR("Product Name"), kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
(cf_property = IORegistryEntryCreateCFProperty(port,
CFSTR(kIOTTYDeviceKey), kCFAllocatorDefault, 0))) {
// Description limited to 127 char, anything longer would not be user friendly anyway.
char description[128];
if (CFStringGetCString((CFStringRef)cf_property, description, sizeof(description), kCFStringEncodingUTF8)) {
port_info.friendly_name = std::string(description) + " (" + port_info.port + ")";
port_info.is_printer = looks_like_printer(port_info.friendly_name);
}
CFRelease(cf_property);
}
if (port_info.friendly_name.empty())
port_info.friendly_name = port_info.port;
output.emplace_back(std::move(port_info));
}
}
IOObjectRelease(port);
}
}
}
#else
// UNIX / Linux
std::initializer_list<const char*> prefixes { "ttyUSB" , "ttyACM", "tty.", "cu.", "rfcomm" };
for (auto &dir_entry : boost::filesystem::directory_iterator(boost::filesystem::path("/dev"))) {
std::string name = dir_entry.path().filename().string();
for (const char *prefix : prefixes) {
if (boost::starts_with(name, prefix)) {
const auto path = dir_entry.path().string();
SerialPortInfo spi;
spi.port = path;
spi.hardware_id = path;
#ifdef __linux__
spi.friendly_name = get_tty_friendly_name(path, name);
#else
spi.friendly_name = path;
#endif
output.emplace_back(std::move(spi));
break;
}
}
}
#endif
output.erase(std::remove_if(output.begin(), output.end(),
[](const SerialPortInfo &info) {
return boost::starts_with(info.port, "Bluetooth") || boost::starts_with(info.port, "FireFly");
}),
output.end());
return output;
}
std::vector<std::string> scan_serial_ports()
{
std::vector<SerialPortInfo> ports = scan_serial_ports_extended();
std::vector<std::string> output;
output.reserve(ports.size());
for (const SerialPortInfo &spi : ports)
output.emplace_back(std::move(spi.port));
return output;
}
} // namespace Utils
} // namespace Slic3r

View file

@ -0,0 +1,32 @@
#ifndef slic3r_GUI_Utils_Serial_hpp_
#define slic3r_GUI_Utils_Serial_hpp_
#include <memory>
#include <vector>
#include <string>
#include <vector>
namespace Slic3r {
namespace Utils {
struct SerialPortInfo {
std::string port;
std::string hardware_id;
std::string friendly_name;
bool is_printer = false;
};
inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2)
{
return sp1.port == sp2.port &&
sp1.hardware_id == sp2.hardware_id &&
sp1.is_printer == sp2.is_printer;
}
extern std::vector<std::string> scan_serial_ports();
extern std::vector<SerialPortInfo> scan_serial_ports_extended();
} // Utils
} // Slic3r
#endif /* slic3r_GUI_Utils_Serial_hpp_ */