mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-22 16:21:24 -06:00
Merge branch 'master' into wipe_tower_improvements
This commit is contained in:
commit
1d1c69f300
490 changed files with 201883 additions and 5792 deletions
|
@ -646,29 +646,40 @@ void GLVolumeCollection::render_legacy() const
|
|||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
void GLVolumeCollection::update_outside_state(const DynamicPrintConfig* config, bool all_inside)
|
||||
bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config)
|
||||
{
|
||||
if (config == nullptr)
|
||||
return;
|
||||
return false;
|
||||
|
||||
const ConfigOptionPoints* opt = dynamic_cast<const ConfigOptionPoints*>(config->option("bed_shape"));
|
||||
if (opt == nullptr)
|
||||
return;
|
||||
return false;
|
||||
|
||||
BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values));
|
||||
BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min.x), unscale(bed_box_2D.min.y), 0.0), Pointf3(unscale(bed_box_2D.max.x), unscale(bed_box_2D.max.y), config->opt_float("max_print_height")));
|
||||
// Allow the objects to protrude below the print bed
|
||||
print_volume.min.z = -1e10;
|
||||
|
||||
bool contained = true;
|
||||
for (GLVolume* volume : this->volumes)
|
||||
{
|
||||
if (all_inside)
|
||||
if (volume != nullptr)
|
||||
{
|
||||
volume->is_outside = false;
|
||||
continue;
|
||||
bool state = print_volume.contains(volume->transformed_bounding_box());
|
||||
contained &= state;
|
||||
volume->is_outside = !state;
|
||||
}
|
||||
}
|
||||
|
||||
volume->is_outside = !print_volume.contains(volume->transformed_bounding_box());
|
||||
return contained;
|
||||
}
|
||||
|
||||
void GLVolumeCollection::reset_outside_state()
|
||||
{
|
||||
for (GLVolume* volume : this->volumes)
|
||||
{
|
||||
if (volume != nullptr)
|
||||
volume->is_outside = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -749,13 +760,13 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<double> GLVolumeCollection::get_current_print_zs() const
|
||||
std::vector<double> GLVolumeCollection::get_current_print_zs(bool active_only) const
|
||||
{
|
||||
// Collect layer top positions of all volumes.
|
||||
std::vector<double> print_zs;
|
||||
for (GLVolume *vol : this->volumes)
|
||||
{
|
||||
if (vol->is_active)
|
||||
if (!active_only || vol->is_active)
|
||||
append(print_zs, vol->print_zs);
|
||||
}
|
||||
std::sort(print_zs.begin(), print_zs.end());
|
||||
|
@ -1762,12 +1773,13 @@ void _3DScene::load_gcode_preview(const Print* print, const GCodePreviewData* pr
|
|||
else
|
||||
{
|
||||
_generate_legend_texture(*preview_data, tool_colors);
|
||||
_load_shells(*print, *volumes, use_VBOs);
|
||||
|
||||
// removes empty volumes
|
||||
volumes->volumes.erase(std::remove_if(volumes->volumes.begin(), volumes->volumes.end(),
|
||||
[](const GLVolume *volume) { return volume->print_zs.empty(); }),
|
||||
volumes->volumes.end());
|
||||
|
||||
_load_shells(*print, *volumes, use_VBOs);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2204,7 +2216,7 @@ void _3DScene::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data,
|
|||
return 0.0f;
|
||||
}
|
||||
|
||||
static const GCodePreviewData::Color path_color(const GCodePreviewData& data, const std::vector<float>& tool_colors, float value)
|
||||
static GCodePreviewData::Color path_color(const GCodePreviewData& data, const std::vector<float>& tool_colors, float value)
|
||||
{
|
||||
switch (data.extrusion.view_type)
|
||||
{
|
||||
|
@ -2220,7 +2232,7 @@ void _3DScene::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data,
|
|||
return data.get_volumetric_rate_color(value);
|
||||
case GCodePreviewData::Extrusion::Tool:
|
||||
{
|
||||
static GCodePreviewData::Color color;
|
||||
GCodePreviewData::Color color;
|
||||
::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + (unsigned int)value * 4), 4 * sizeof(float));
|
||||
return color;
|
||||
}
|
||||
|
@ -2445,7 +2457,7 @@ bool _3DScene::_travel_paths_by_type(const GCodePreviewData& preview_data, GLVol
|
|||
TypesList::iterator type = std::find(types.begin(), types.end(), Type(polyline.type));
|
||||
if (type != types.end())
|
||||
{
|
||||
type->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().max.z));
|
||||
type->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
|
||||
type->volume->offsets.push_back(type->volume->indexed_vertex_array.quad_indices.size());
|
||||
type->volume->offsets.push_back(type->volume->indexed_vertex_array.triangle_indices.size());
|
||||
|
||||
|
@ -2511,7 +2523,7 @@ bool _3DScene::_travel_paths_by_feedrate(const GCodePreviewData& preview_data, G
|
|||
FeedratesList::iterator feedrate = std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate));
|
||||
if (feedrate != feedrates.end())
|
||||
{
|
||||
feedrate->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().max.z));
|
||||
feedrate->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
|
||||
feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.quad_indices.size());
|
||||
feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.triangle_indices.size());
|
||||
|
||||
|
@ -2577,7 +2589,7 @@ bool _3DScene::_travel_paths_by_tool(const GCodePreviewData& preview_data, GLVol
|
|||
ToolsList::iterator tool = std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id));
|
||||
if (tool != tools.end())
|
||||
{
|
||||
tool->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().max.z));
|
||||
tool->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
|
||||
tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.quad_indices.size());
|
||||
tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.triangle_indices.size());
|
||||
|
||||
|
@ -2601,7 +2613,10 @@ void _3DScene::_load_gcode_retractions(const GCodePreviewData& preview_data, GLV
|
|||
{
|
||||
volumes.volumes.emplace_back(volume);
|
||||
|
||||
for (const GCodePreviewData::Retraction::Position& position : preview_data.retraction.positions)
|
||||
GCodePreviewData::Retraction::PositionsList copy(preview_data.retraction.positions);
|
||||
std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; });
|
||||
|
||||
for (const GCodePreviewData::Retraction::Position& position : copy)
|
||||
{
|
||||
volume->print_zs.push_back(unscale(position.position.z));
|
||||
volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
|
||||
|
@ -2629,7 +2644,10 @@ void _3DScene::_load_gcode_unretractions(const GCodePreviewData& preview_data, G
|
|||
{
|
||||
volumes.volumes.emplace_back(volume);
|
||||
|
||||
for (const GCodePreviewData::Retraction::Position& position : preview_data.unretraction.positions)
|
||||
GCodePreviewData::Retraction::PositionsList copy(preview_data.unretraction.positions);
|
||||
std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; });
|
||||
|
||||
for (const GCodePreviewData::Retraction::Position& position : copy)
|
||||
{
|
||||
volume->print_zs.push_back(unscale(position.position.z));
|
||||
volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
|
||||
|
|
|
@ -422,11 +422,13 @@ public:
|
|||
print_box_max[0] = max_x; print_box_max[1] = max_y; print_box_max[2] = max_z;
|
||||
}
|
||||
|
||||
void update_outside_state(const DynamicPrintConfig* config, bool all_inside);
|
||||
bool check_outside_state(const DynamicPrintConfig* config);
|
||||
void reset_outside_state();
|
||||
|
||||
void update_colors_by_extruder(const DynamicPrintConfig* config);
|
||||
|
||||
// Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection
|
||||
std::vector<double> get_current_print_zs() const;
|
||||
std::vector<double> get_current_print_zs(bool active_only) const;
|
||||
|
||||
private:
|
||||
GLVolumeCollection(const GLVolumeCollection &other);
|
||||
|
|
|
@ -29,20 +29,27 @@ void AboutDialogLogo::onRepaint(wxEvent &event)
|
|||
}
|
||||
|
||||
AboutDialog::AboutDialog()
|
||||
: wxDialog(NULL, wxID_ANY, _(L("About Slic3r")), wxDefaultPosition, wxSize(600, 340), wxCAPTION)
|
||||
: wxDialog(NULL, wxID_ANY, _(L("About Slic3r")), wxDefaultPosition, wxDefaultSize, wxCAPTION)
|
||||
{
|
||||
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)/**wxWHITE*/);
|
||||
wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
SetBackgroundColour(bgr_clr);
|
||||
wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
this->SetSizer(hsizer);
|
||||
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 20);
|
||||
|
||||
// logo
|
||||
// AboutDialogLogo* logo = new AboutDialogLogo(this);
|
||||
wxBitmap logo_bmp = wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG);
|
||||
auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(logo_bmp));
|
||||
hsizer->Add(logo, 0, wxEXPAND | wxLEFT | wxRIGHT, 30);
|
||||
hsizer->Add(logo, 1, wxALIGN_CENTRE_VERTICAL | wxEXPAND | wxTOP | wxBOTTOM, 35);
|
||||
|
||||
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
hsizer->Add(vsizer, 1, wxEXPAND, 0);
|
||||
#ifdef __WXMSW__
|
||||
int proportion = 2;
|
||||
#else
|
||||
int proportion = 3;
|
||||
#endif
|
||||
hsizer->Add(vsizer, proportion, wxEXPAND|wxLEFT, 20);
|
||||
|
||||
// title
|
||||
{
|
||||
|
@ -52,7 +59,7 @@ AboutDialog::AboutDialog()
|
|||
title_font.SetFamily(wxFONTFAMILY_ROMAN);
|
||||
title_font.SetPointSize(24);
|
||||
title->SetFont(title_font);
|
||||
vsizer->Add(title, 0, wxALIGN_LEFT | wxTOP, 30);
|
||||
vsizer->Add(title, 0, wxALIGN_LEFT | wxTOP, 10);
|
||||
}
|
||||
|
||||
// version
|
||||
|
@ -70,34 +77,34 @@ AboutDialog::AboutDialog()
|
|||
}
|
||||
|
||||
// text
|
||||
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER);
|
||||
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO/*NEVER*/);
|
||||
{
|
||||
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
#ifdef __WXMSW__
|
||||
int size[] = {8,8,8,8,8,8,8};
|
||||
#else
|
||||
int size[] = {11,11,11,11,11,11,11};
|
||||
#endif
|
||||
const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
|
||||
auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
|
||||
|
||||
const int fs = font.GetPointSize()-1;
|
||||
int size[] = {fs,fs,fs,fs,fs,fs,fs};
|
||||
html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
|
||||
html->SetHTMLBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
html->SetBorders(2);
|
||||
const char* text =
|
||||
const auto text = wxString::Format(
|
||||
"<html>"
|
||||
"<body bgcolor=\"#ffffff\" link=\"#808080\">"
|
||||
"<font color=\"#808080\">"
|
||||
"<body bgcolor= %s link= %s>"
|
||||
"<font color=%s>"
|
||||
"Copyright © 2016-2018 Prusa Research. <br />"
|
||||
"Copyright © 2011-2017 Alessandro Ranellucci. <br />"
|
||||
"<a href=\"http://slic3r.org/\">Slic3r</a> is licensed under the "
|
||||
"<a href=\"http://www.gnu.org/licenses/agpl-3.0.html\">GNU Affero General Public License, version 3</a>."
|
||||
"<br /><br /><br />"
|
||||
"<br /><br />"
|
||||
"Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others. "
|
||||
"Manual by Gary Hodgson. Inspired by the RepRap community. <br />"
|
||||
"Slic3r logo designed by Corey Daniels, <a href=\"http://www.famfamfam.com/lab/icons/silk/\">Silk Icon Set</a> designed by Mark James. "
|
||||
"</font>"
|
||||
"</body>"
|
||||
"</html>";
|
||||
"</html>", bgr_clr_str, text_clr_str, text_clr_str);
|
||||
html->SetPage(text);
|
||||
vsizer->Add(html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 20);
|
||||
vsizer->Add(html, 1, wxEXPAND | wxBOTTOM, 10);
|
||||
html->Bind(wxEVT_HTML_LINK_CLICKED, &AboutDialog::onLinkClicked, this);
|
||||
}
|
||||
|
||||
|
@ -108,6 +115,9 @@ AboutDialog::AboutDialog()
|
|||
|
||||
this->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this);
|
||||
logo->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
main_sizer->SetSizeHints(this);
|
||||
}
|
||||
|
||||
void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event)
|
||||
|
|
|
@ -57,6 +57,9 @@ void AppConfig::set_defaults()
|
|||
// https://github.com/prusa3d/Slic3r/issues/233
|
||||
if (get("use_legacy_opengl").empty())
|
||||
set("use_legacy_opengl", "0");
|
||||
|
||||
if (get("remember_output_path").empty())
|
||||
set("remember_output_path", "1");
|
||||
}
|
||||
|
||||
void AppConfig::load()
|
||||
|
@ -100,6 +103,7 @@ void AppConfig::load()
|
|||
auto ini_ver = Semver::parse(get("version"));
|
||||
m_legacy_datadir = false;
|
||||
if (ini_ver) {
|
||||
m_orig_version = *ini_ver;
|
||||
// Make 1.40.0 alphas compare well
|
||||
ini_ver->set_metadata(boost::none);
|
||||
ini_ver->set_prerelease(boost::none);
|
||||
|
|
|
@ -13,7 +13,13 @@ namespace Slic3r {
|
|||
class AppConfig
|
||||
{
|
||||
public:
|
||||
AppConfig() : m_dirty(false), m_legacy_datadir(false) { this->reset(); }
|
||||
AppConfig() :
|
||||
m_dirty(false),
|
||||
m_orig_version(Semver::invalid()),
|
||||
m_legacy_datadir(false)
|
||||
{
|
||||
this->reset();
|
||||
}
|
||||
|
||||
// Clear and reset to defaults.
|
||||
void reset();
|
||||
|
@ -95,11 +101,16 @@ public:
|
|||
|
||||
// Returns true if the user's data directory comes from before Slic3r 1.40.0 (no updating)
|
||||
bool legacy_datadir() const { return m_legacy_datadir; }
|
||||
void set_legacy_datadir(bool value) { m_legacy_datadir = value; }
|
||||
|
||||
// Get the Slic3r version check url.
|
||||
// This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file.
|
||||
std::string version_check_url() const;
|
||||
|
||||
// Returns the original Slic3r version found in the ini file before it was overwritten
|
||||
// by the current version
|
||||
Semver orig_version() const { return m_orig_version; }
|
||||
|
||||
// Does the config file exist?
|
||||
static bool exists();
|
||||
|
||||
|
@ -110,6 +121,8 @@ private:
|
|||
VendorMap m_vendors;
|
||||
// Has any value been modified since the config.ini has been last saved or loaded?
|
||||
bool m_dirty;
|
||||
// Original version found in the ini file before it was overwritten
|
||||
Semver m_orig_version;
|
||||
// Whether the existing version is before system profiles & configuration updating
|
||||
bool m_legacy_datadir;
|
||||
};
|
||||
|
|
|
@ -835,7 +835,9 @@ bool ConfigWizard::run(PresetBundle *preset_bundle, const PresetUpdater *updater
|
|||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "Running ConfigWizard, reason: " << p->run_reason;
|
||||
if (ShowModal() == wxID_OK) {
|
||||
p->apply_config(GUI::get_app_config(), preset_bundle, updater);
|
||||
auto *app_config = GUI::get_app_config();
|
||||
p->apply_config(app_config, preset_bundle, updater);
|
||||
app_config->set_legacy_datadir(false);
|
||||
BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied";
|
||||
return true;
|
||||
} else {
|
||||
|
|
|
@ -106,7 +106,7 @@ namespace Slic3r { namespace GUI {
|
|||
}
|
||||
double val;
|
||||
str.ToCDouble(&val);
|
||||
if (m_opt.min > val && val > m_opt.max)
|
||||
if (m_opt.min > val || val > m_opt.max)
|
||||
{
|
||||
show_error(m_parent, _(L("Input value is out of range")));
|
||||
if (m_opt.min > val) val = m_opt.min;
|
||||
|
|
402
xs/src/slic3r/GUI/FirmwareDialog.cpp
Normal file
402
xs/src/slic3r/GUI/FirmwareDialog.cpp
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
31
xs/src/slic3r/GUI/FirmwareDialog.hpp
Normal file
31
xs/src/slic3r/GUI/FirmwareDialog.hpp
Normal 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
|
|
@ -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()
|
||||
|
@ -671,7 +607,7 @@ void show_error(wxWindow* parent, const wxString& message) {
|
|||
|
||||
void show_error_id(int id, const std::string& message) {
|
||||
auto *parent = id != 0 ? wxWindow::FindWindowById(id) : nullptr;
|
||||
show_error(parent, message);
|
||||
show_error(parent, wxString::FromUTF8(message.data()));
|
||||
}
|
||||
|
||||
void show_info(wxWindow* parent, const wxString& message, const wxString& title){
|
||||
|
@ -809,8 +745,8 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
|
|||
{
|
||||
DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config;
|
||||
m_optgroup = std::make_shared<ConfigOptionsGroup>(parent, "", config);
|
||||
// const wxArrayInt& ar = preset_sizer->GetColWidths();
|
||||
// m_optgroup->label_width = ar.IsEmpty() ? 100 : ar.front(); // doesn't work
|
||||
const wxArrayInt& ar = preset_sizer->GetColWidths();
|
||||
m_optgroup->label_width = ar.IsEmpty() ? 100 : ar.front()-4; // doesn't work
|
||||
m_optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){
|
||||
TabPrint* tab_print = nullptr;
|
||||
for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -71,11 +71,12 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) :
|
|||
text->Wrap(CONTENT_WIDTH);
|
||||
p_sizer->Add(text, 1, wxEXPAND);
|
||||
|
||||
panel->SetMinSize(wxSize(CONTENT_WIDTH, CONTENT_HEIGHT));
|
||||
panel->SetMinSize(wxSize(CONTENT_WIDTH, 0));
|
||||
panel->SetScrollRate(0, 5);
|
||||
|
||||
content_sizer->Add(panel, 1, wxEXPAND);
|
||||
|
||||
SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT));
|
||||
Fit();
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ struct MsgDialog : wxDialog
|
|||
protected:
|
||||
enum {
|
||||
CONTENT_WIDTH = 500,
|
||||
CONTENT_HEIGHT = 300,
|
||||
CONTENT_MAX_HEIGHT = 600,
|
||||
BORDER = 30,
|
||||
VERT_SPACING = 15,
|
||||
HORIZ_SPACING = 5,
|
||||
|
|
|
@ -150,11 +150,11 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/*
|
|||
// Build a label if we have it
|
||||
wxStaticText* label=nullptr;
|
||||
if (label_width != 0) {
|
||||
label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ": "),
|
||||
wxDefaultPosition, staticbox ? wxSize(label_width, -1) : wxDefaultSize);
|
||||
label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ":"),
|
||||
wxDefaultPosition, wxSize(label_width, -1), staticbox ? 0 : wxALIGN_RIGHT);
|
||||
label->SetFont(label_font);
|
||||
label->Wrap(label_width); // avoid a Linux/GTK bug
|
||||
grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT) | wxALIGN_CENTER_VERTICAL, 0);
|
||||
grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5);
|
||||
if (line.label_tooltip.compare("") != 0)
|
||||
label->SetToolTip(line.label_tooltip);
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/*
|
|||
|
||||
// if we have a single option with no sidetext just add it directly to the grid sizer
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
grid_sizer->Add(sizer, 0, wxEXPAND | wxALL, 0);
|
||||
grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM|wxTOP|wxLEFT), staticbox ? 0 : 1);
|
||||
if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 &&
|
||||
option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) {
|
||||
const auto& option = option_set.front();
|
||||
|
@ -180,7 +180,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/*
|
|||
sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL);
|
||||
if (is_window_field(field))
|
||||
sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, (option.opt.full_width ? wxEXPAND : 0) |
|
||||
wxBOTTOM | wxTOP | wxALIGN_CENTER_VERTICAL, wxOSX ? 0 : 2);
|
||||
wxBOTTOM | wxTOP | wxALIGN_CENTER_VERTICAL, (wxOSX||!staticbox) ? 0 : 2);
|
||||
if (is_sizer_field(field))
|
||||
sizer->Add(field->getSizer(), 0, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
|
||||
return;
|
||||
|
|
|
@ -35,8 +35,8 @@ void PreferencesDialog::build()
|
|||
def.type = coBool;
|
||||
def.tooltip = L("If this is enabled, Slic3r will prompt the last output directory "
|
||||
"instead of the one containing the input files.");
|
||||
def.default_value = new ConfigOptionBool{ app_config->get("remember_output_path")[0] == '1' }; // 1;
|
||||
Option option(def, "remember_output_path");
|
||||
def.default_value = new ConfigOptionBool{ app_config->has("remember_output_path") ? app_config->get("remember_output_path")[0] == '1' : true }; // 1;
|
||||
Option option(def, "remember_output_path");
|
||||
m_optgroup->append_single_option_line(option);
|
||||
|
||||
def.label = L("Auto-center parts");
|
||||
|
|
|
@ -188,6 +188,14 @@ void Preset::normalize(DynamicPrintConfig &config)
|
|||
if (opt != nullptr && opt->is_vector())
|
||||
static_cast<ConfigOptionVectorBase*>(opt)->resize(n, defaults.option(key));
|
||||
}
|
||||
// The following keys are mandatory for the UI, but they are not part of FullPrintConfig, therefore they are handled separately.
|
||||
for (const std::string &key : { "filament_settings_id" }) {
|
||||
auto *opt = config.option(key, false);
|
||||
assert(opt != nullptr);
|
||||
assert(opt->type() == coStrings);
|
||||
if (opt != nullptr && opt->type() == coStrings)
|
||||
static_cast<ConfigOptionStrings*>(opt)->values.resize(n, std::string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -244,6 +244,7 @@ public:
|
|||
const std::string& get_suffix_modified();
|
||||
|
||||
// Return a preset possibly with modifications.
|
||||
Preset& default_preset() { return m_presets.front(); }
|
||||
const Preset& default_preset() const { return m_presets.front(); }
|
||||
// Return a preset by an index. If the preset is active, a temporary copy is returned.
|
||||
Preset& preset(size_t idx) { return (int(idx) == m_idx_selected) ? m_edited_preset : m_presets[idx]; }
|
||||
|
|
|
@ -53,27 +53,29 @@ PresetBundle::PresetBundle() :
|
|||
wxImage::AddHandler(new wxPNGHandler);
|
||||
|
||||
// Create the ID config keys, as they are not part of the Static print config classes.
|
||||
this->prints.preset(0).config.opt_string("print_settings_id", true);
|
||||
this->filaments.preset(0).config.opt_string("filament_settings_id", true);
|
||||
this->printers.preset(0).config.opt_string("print_settings_id", true);
|
||||
// Create the "compatible printers" keys, as they are not part of the Static print config classes.
|
||||
this->filaments.preset(0).config.optptr("compatible_printers", true);
|
||||
this->filaments.preset(0).config.optptr("compatible_printers_condition", true);
|
||||
this->prints.preset(0).config.optptr("compatible_printers", true);
|
||||
this->prints.preset(0).config.optptr("compatible_printers_condition", true);
|
||||
// Create the "inherits" keys.
|
||||
this->prints.preset(0).config.optptr("inherits", true);
|
||||
this->filaments.preset(0).config.optptr("inherits", true);
|
||||
this->printers.preset(0).config.optptr("inherits", true);
|
||||
this->prints.default_preset().config.opt_string("print_settings_id", true);
|
||||
this->filaments.default_preset().config.option<ConfigOptionStrings>("filament_settings_id", true)->values.assign(1, std::string());
|
||||
this->printers.default_preset().config.opt_string("printer_settings_id", true);
|
||||
// "compatible printers" are not mandatory yet.
|
||||
//FIXME Rename "compatible_printers" and "compatible_printers_condition", as they are defined in both print and filament profiles,
|
||||
// therefore they are clashing when generating a a config file, G-code or AMF/3MF.
|
||||
// this->filaments.default_preset().config.optptr("compatible_printers", true);
|
||||
// this->filaments.default_preset().config.optptr("compatible_printers_condition", true);
|
||||
// this->prints.default_preset().config.optptr("compatible_printers", true);
|
||||
// this->prints.default_preset().config.optptr("compatible_printers_condition", true);
|
||||
// Create the "printer_vendor", "printer_model" and "printer_variant" keys.
|
||||
this->printers.preset(0).config.optptr("printer_vendor", true);
|
||||
this->printers.preset(0).config.optptr("printer_model", true);
|
||||
this->printers.preset(0).config.optptr("printer_variant", true);
|
||||
|
||||
this->printers.default_preset().config.optptr("printer_vendor", true);
|
||||
this->printers.default_preset().config.optptr("printer_model", true);
|
||||
this->printers.default_preset().config.optptr("printer_variant", true);
|
||||
// Load the default preset bitmaps.
|
||||
this->prints .load_bitmap_default("cog.png");
|
||||
this->filaments.load_bitmap_default("spool.png");
|
||||
this->printers .load_bitmap_default("printer_empty.png");
|
||||
this->load_compatible_bitmaps();
|
||||
// Re-activate the default presets, so their "edited" preset copies will be updated with the additional configuration values above.
|
||||
this->prints .select_preset(0);
|
||||
this->filaments.select_preset(0);
|
||||
this->printers .select_preset(0);
|
||||
|
||||
this->project_config.apply_only(FullPrintConfig::defaults(), s_project_options);
|
||||
}
|
||||
|
@ -104,6 +106,9 @@ void PresetBundle::reset(bool delete_files)
|
|||
this->printers .reset(delete_files);
|
||||
this->filament_presets.clear();
|
||||
this->filament_presets.emplace_back(this->filaments.get_selected_preset().name);
|
||||
this->obsolete_presets.prints.clear();
|
||||
this->obsolete_presets.filaments.clear();
|
||||
this->obsolete_presets.printers.clear();
|
||||
}
|
||||
|
||||
void PresetBundle::setup_directories()
|
||||
|
@ -210,6 +215,10 @@ std::string PresetBundle::load_system_presets()
|
|||
errors_cummulative += "\n";
|
||||
}
|
||||
}
|
||||
if (first) {
|
||||
// No config bundle loaded, reset.
|
||||
this->reset(false);
|
||||
}
|
||||
return errors_cummulative;
|
||||
}
|
||||
|
||||
|
@ -220,7 +229,10 @@ std::vector<std::string> PresetBundle::merge_presets(PresetBundle &&other)
|
|||
std::vector<std::string> duplicate_prints = this->prints .merge_presets(std::move(other.prints), this->vendors);
|
||||
std::vector<std::string> duplicate_filaments = this->filaments.merge_presets(std::move(other.filaments), this->vendors);
|
||||
std::vector<std::string> duplicate_printers = this->printers .merge_presets(std::move(other.printers), this->vendors);
|
||||
append(duplicate_prints, std::move(duplicate_filaments));
|
||||
append(this->obsolete_presets.prints, std::move(other.obsolete_presets.prints));
|
||||
append(this->obsolete_presets.filaments, std::move(other.obsolete_presets.filaments));
|
||||
append(this->obsolete_presets.printers, std::move(other.obsolete_presets.printers));
|
||||
append(duplicate_prints, std::move(duplicate_filaments));
|
||||
append(duplicate_prints, std::move(duplicate_printers));
|
||||
return duplicate_prints;
|
||||
}
|
||||
|
@ -350,7 +362,9 @@ DynamicPrintConfig PresetBundle::full_config() const
|
|||
DynamicPrintConfig out;
|
||||
out.apply(FullPrintConfig());
|
||||
out.apply(this->prints.get_edited_preset().config);
|
||||
out.apply(this->printers.get_edited_preset().config);
|
||||
// Add the default filament preset to have the "filament_preset_id" defined.
|
||||
out.apply(this->filaments.default_preset().config);
|
||||
out.apply(this->printers.get_edited_preset().config);
|
||||
out.apply(this->project_config);
|
||||
|
||||
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(out.option("nozzle_diameter"));
|
||||
|
@ -361,6 +375,7 @@ DynamicPrintConfig PresetBundle::full_config() const
|
|||
} else {
|
||||
// Retrieve filament presets and build a single config object for them.
|
||||
// First collect the filament configurations based on the user selection of this->filament_presets.
|
||||
// Here this->filaments.find_preset() and this->filaments.first_visible() return the edited copy of the preset if active.
|
||||
std::vector<const DynamicPrintConfig*> filament_configs;
|
||||
for (const std::string &filament_preset_name : this->filament_presets)
|
||||
filament_configs.emplace_back(&this->filaments.find_preset(filament_preset_name, true)->config);
|
||||
|
@ -370,7 +385,7 @@ DynamicPrintConfig PresetBundle::full_config() const
|
|||
std::vector<const ConfigOption*> filament_opts(num_extruders, nullptr);
|
||||
// loop through options and apply them to the resulting config.
|
||||
for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) {
|
||||
if (key == "compatible_printers")
|
||||
if (key == "compatible_printers" || key == "compatible_printers_condition")
|
||||
continue;
|
||||
// Get a destination option.
|
||||
ConfigOption *opt_dst = out.option(key, false);
|
||||
|
@ -388,7 +403,9 @@ DynamicPrintConfig PresetBundle::full_config() const
|
|||
}
|
||||
}
|
||||
|
||||
//FIXME These two value types clash between the print and filament profiles. They should be renamed.
|
||||
out.erase("compatible_printers");
|
||||
out.erase("compatible_printers_condition");
|
||||
|
||||
static const char *keys[] = { "perimeter", "infill", "solid_infill", "support_material", "support_material_interface" };
|
||||
for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++ i) {
|
||||
|
@ -756,6 +773,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
flatten_configbundle_hierarchy(tree);
|
||||
|
||||
// 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files.
|
||||
// Parse the obsolete preset names, to be deleted when upgrading from the old configuration structure.
|
||||
std::vector<std::string> loaded_prints;
|
||||
std::vector<std::string> loaded_filaments;
|
||||
std::vector<std::string> loaded_printers;
|
||||
|
@ -795,6 +813,20 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
active_printer = kvp.second.data();
|
||||
}
|
||||
}
|
||||
} else if (section.first == "obsolete_presets") {
|
||||
// Parse the names of obsolete presets. These presets will be deleted from user's
|
||||
// profile directory on installation of this vendor preset.
|
||||
for (auto &kvp : section.second) {
|
||||
std::vector<std::string> *dst = nullptr;
|
||||
if (kvp.first == "print")
|
||||
dst = &this->obsolete_presets.prints;
|
||||
else if (kvp.first == "filament")
|
||||
dst = &this->obsolete_presets.filaments;
|
||||
else if (kvp.first == "printer")
|
||||
dst = &this->obsolete_presets.printers;
|
||||
if (dst)
|
||||
unescape_strings_cstyle(kvp.second.data(), *dst);
|
||||
}
|
||||
} else if (section.first == "settings") {
|
||||
// Load the settings.
|
||||
for (auto &kvp : section.second) {
|
||||
|
@ -806,10 +838,32 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
continue;
|
||||
if (presets != nullptr) {
|
||||
// Load the print, filament or printer preset.
|
||||
DynamicPrintConfig config(presets->default_preset().config);
|
||||
const DynamicPrintConfig &default_config = presets->default_preset().config;
|
||||
DynamicPrintConfig config(default_config);
|
||||
std::vector<std::string> config_keys = config.keys();
|
||||
// The following two keys are valid, but they are not mandatory.
|
||||
config_keys.emplace_back("compatible_printers");
|
||||
config_keys.emplace_back("compatible_printers_condition");
|
||||
for (auto &kvp : section.second)
|
||||
config.set_deserialize(kvp.first, kvp.second.data());
|
||||
Preset::normalize(config);
|
||||
// Report configuration fields, which are misplaced into a wrong group.
|
||||
std::string incorrect_keys;
|
||||
size_t n_incorrect_keys = 0;
|
||||
for (const std::string &key : config_keys)
|
||||
if (! default_config.has(key)) {
|
||||
if (incorrect_keys.empty())
|
||||
incorrect_keys = key;
|
||||
else {
|
||||
incorrect_keys += ", ";
|
||||
incorrect_keys += key;
|
||||
}
|
||||
config.erase(key);
|
||||
++ n_incorrect_keys;
|
||||
}
|
||||
if (! incorrect_keys.empty())
|
||||
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" <<
|
||||
section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
|
||||
if ((flags & LOAD_CFGBNDLE_SYSTEM) && presets == &printers) {
|
||||
// Filter out printer presets, which are not mentioned in the vendor profile.
|
||||
// These presets are considered not installed.
|
||||
|
|
|
@ -54,6 +54,13 @@ public:
|
|||
// and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors.
|
||||
std::set<VendorProfile> vendors;
|
||||
|
||||
struct ObsoletePresets {
|
||||
std::vector<std::string> prints;
|
||||
std::vector<std::string> filaments;
|
||||
std::vector<std::string> printers;
|
||||
};
|
||||
ObsoletePresets obsolete_presets;
|
||||
|
||||
bool has_defauls_only() const
|
||||
{ return prints.size() <= 1 && filaments.size() <= 1 && printers.size() <= 1; }
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
@ -1697,7 +1698,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){
|
||||
|
@ -1713,7 +1714,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
|
||||
|
@ -1721,17 +1723,20 @@ 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");
|
||||
optgroup->append_single_option_line("extra_loading_move");
|
||||
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");
|
||||
optgroup->append_single_option_line("extra_loading_move");
|
||||
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
|
||||
|
@ -2129,7 +2134,7 @@ void Tab::save_preset(std::string name /*= ""*/)
|
|||
return;
|
||||
}
|
||||
if (existing && (existing->is_external)) {
|
||||
show_error(this, _(L("Cannot overwrite an external.")));
|
||||
show_error(this, _(L("Cannot overwrite an external profile.")));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -82,15 +82,27 @@ void wxCheckListBoxComboPopup::OnCheckListBox(wxCommandEvent& evt)
|
|||
{
|
||||
// forwards the checklistbox event to the owner wxComboCtrl
|
||||
|
||||
wxComboCtrl* cmb = GetComboCtrl();
|
||||
if (cmb != nullptr)
|
||||
if (m_check_box_events_status == OnCheckListBoxFunction::FreeToProceed )
|
||||
{
|
||||
wxCommandEvent event(wxEVT_CHECKLISTBOX, cmb->GetId());
|
||||
event.SetEventObject(cmb);
|
||||
cmb->ProcessWindowEvent(event);
|
||||
wxComboCtrl* cmb = GetComboCtrl();
|
||||
if (cmb != nullptr) {
|
||||
wxCommandEvent event(wxEVT_CHECKLISTBOX, cmb->GetId());
|
||||
event.SetEventObject(cmb);
|
||||
cmb->ProcessWindowEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
evt.Skip();
|
||||
|
||||
#ifndef _WIN32 // events are sent differently on OSX+Linux vs Win (more description in header file)
|
||||
if ( m_check_box_events_status == OnCheckListBoxFunction::RefuseToProceed )
|
||||
// this happens if the event was resent by OnListBoxSelection - next call to OnListBoxSelection is due to user clicking the text, so the function should
|
||||
// explicitly change the state on the checkbox
|
||||
m_check_box_events_status = OnCheckListBoxFunction::WasRefusedLastTime;
|
||||
else
|
||||
// if the user clicked the checkbox square, this event was sent before OnListBoxSelection was called, so we don't want it to resend it
|
||||
m_check_box_events_status = OnCheckListBoxFunction::RefuseToProceed;
|
||||
#endif
|
||||
}
|
||||
|
||||
void wxCheckListBoxComboPopup::OnListBoxSelection(wxCommandEvent& evt)
|
||||
|
@ -100,9 +112,14 @@ void wxCheckListBoxComboPopup::OnListBoxSelection(wxCommandEvent& evt)
|
|||
int selId = GetSelection();
|
||||
if (selId != wxNOT_FOUND)
|
||||
{
|
||||
Check((unsigned int)selId, !IsChecked((unsigned int)selId));
|
||||
SetSelection(wxNOT_FOUND);
|
||||
#ifndef _WIN32
|
||||
if (m_check_box_events_status == OnCheckListBoxFunction::RefuseToProceed)
|
||||
#endif
|
||||
Check((unsigned int)selId, !IsChecked((unsigned int)selId));
|
||||
|
||||
m_check_box_events_status = OnCheckListBoxFunction::FreeToProceed; // so the checkbox reacts to square-click the next time
|
||||
|
||||
SetSelection(wxNOT_FOUND);
|
||||
wxCommandEvent event(wxEVT_CHECKLISTBOX, GetId());
|
||||
event.SetInt(selId);
|
||||
event.SetEventObject(this);
|
||||
|
|
|
@ -13,6 +13,21 @@ class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
|
|||
|
||||
wxString m_text;
|
||||
|
||||
// Events sent on mouseclick are quite complex. Function OnListBoxSelection is supposed to pass the event to the checkbox, which works fine on
|
||||
// Win. On OSX and Linux the events are generated differently - clicking on the checkbox square generates the event twice (and the square
|
||||
// therefore seems not to respond).
|
||||
// This enum is meant to save current state of affairs, i.e., if the event forwarding is ok to do or not. It is only used on Linux
|
||||
// and OSX by some #ifdefs. It also stores information whether OnListBoxSelection is supposed to change the checkbox status,
|
||||
// or if it changed status on its own already (which happens when the square is clicked). More comments in OnCheckListBox(...)
|
||||
// There indeed is a better solution, maybe making a custom event used for the event passing to distinguish the original and passed message
|
||||
// and blocking one of them on OSX and Linux. Feel free to refactor, but carefully test on all platforms.
|
||||
enum class OnCheckListBoxFunction{
|
||||
FreeToProceed,
|
||||
RefuseToProceed,
|
||||
WasRefusedLastTime
|
||||
} m_check_box_events_status = OnCheckListBoxFunction::FreeToProceed;
|
||||
|
||||
|
||||
public:
|
||||
virtual bool Create(wxWindow* parent);
|
||||
virtual wxWindow* GetControl();
|
||||
|
|
|
@ -136,7 +136,7 @@ void PresetUpdater::priv::set_download_prefs(AppConfig *app_config)
|
|||
{
|
||||
enabled_version_check = app_config->get("version_check") == "1";
|
||||
version_check_url = app_config->version_check_url();
|
||||
enabled_config_update = app_config->get("preset_update") == "1";
|
||||
enabled_config_update = app_config->get("preset_update") == "1" && !app_config->legacy_datadir();
|
||||
}
|
||||
|
||||
// Downloads a file (http get operation). Cancels if the Updater is being destroyed.
|
||||
|
@ -402,13 +402,33 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
|
|||
PresetBundle bundle;
|
||||
bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% conflicting presets")
|
||||
% (bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
|
||||
|
||||
auto preset_remover = [](const Preset &preset) {
|
||||
BOOST_LOG_TRIVIAL(info) << '\t' << preset.file;
|
||||
fs::remove(preset.file);
|
||||
};
|
||||
|
||||
for (const auto &preset : bundle.prints) { preset_remover(preset); }
|
||||
for (const auto &preset : bundle.filaments) { preset_remover(preset); }
|
||||
for (const auto &preset : bundle.printers) { preset_remover(preset); }
|
||||
|
||||
// Also apply the `obsolete_presets` property, removing obsolete ini files
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% obsolete presets")
|
||||
% (bundle.obsolete_presets.prints.size() + bundle.obsolete_presets.filaments.size() + bundle.obsolete_presets.printers.size());
|
||||
|
||||
auto obsolete_remover = [](const char *subdir, const std::string &preset) {
|
||||
auto path = fs::path(Slic3r::data_dir()) / subdir / preset;
|
||||
path += ".ini";
|
||||
BOOST_LOG_TRIVIAL(info) << '\t' << path.string();
|
||||
fs::remove(path);
|
||||
};
|
||||
|
||||
for (const auto &name : bundle.obsolete_presets.prints) { obsolete_remover("print", name); }
|
||||
for (const auto &name : bundle.obsolete_presets.filaments) { obsolete_remover("filament", name); }
|
||||
for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
193
xs/src/slic3r/Utils/Serial.cpp
Normal file
193
xs/src/slic3r/Utils/Serial.cpp
Normal 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, ®DataType, (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
|
32
xs/src/slic3r/Utils/Serial.hpp
Normal file
32
xs/src/slic3r/Utils/Serial.hpp
Normal 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_ */
|
Loading…
Add table
Add a link
Reference in a new issue