mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-05 14:30:43 +08:00
Polish BulkExportDialog
- Add duplicate detection to validation - Add file existance detection to validation - Remove dependency on PrintToExport - Remove destructor and use unique_ptr instead
This commit is contained in:
parent
8f7b2b2259
commit
fd43fb6e9b
@ -92,6 +92,8 @@ public:
|
||||
void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
|
||||
void set_gcode_result(GCodeProcessorResult* result) { m_gcode_result = result; }
|
||||
|
||||
GCodeProcessorResult *get_gcode_result() { return m_gcode_result; }
|
||||
|
||||
// The following wxCommandEvent will be sent to the UI thread / Plater window, when the slicing is finished
|
||||
// and the background processing will transition into G-code export.
|
||||
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
|
||||
|
@ -20,24 +20,19 @@
|
||||
#include "format.hpp"
|
||||
#include "Tab.hpp"
|
||||
|
||||
using Slic3r::GUI::format_wxstr;
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
constexpr auto BORDER_W = 10;
|
||||
|
||||
//-----------------------------------------------
|
||||
// BulkExportDialog::Item
|
||||
//-----------------------------------------------
|
||||
|
||||
|
||||
void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer, const std::string path)
|
||||
void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer, const std::string &path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
long style = wxBORDER_SIMPLE;
|
||||
const long style = wxBORDER_SIMPLE;
|
||||
#else
|
||||
long style = 0L;
|
||||
const long style = 0L;
|
||||
#endif
|
||||
m_text_ctrl = new wxTextCtrl(m_parent, wxID_ANY, from_u8(path), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), style);
|
||||
wxGetApp().UpdateDarkUI(m_text_ctrl);
|
||||
@ -45,18 +40,24 @@ void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer,
|
||||
|
||||
input_path_sizer->Add(m_text_ctrl, 1, wxEXPAND, BORDER_W);
|
||||
}
|
||||
|
||||
BulkExportDialog::Item::Item(wxWindow* parent, wxBoxSizer* sizer, PrintToExport& pte):
|
||||
BulkExportDialog::Item::Item(
|
||||
wxWindow *parent,
|
||||
wxBoxSizer *sizer,
|
||||
const boost::filesystem::path &path,
|
||||
Validator validator
|
||||
):
|
||||
path(path),
|
||||
m_parent(parent),
|
||||
m_print_to_export(&pte),
|
||||
m_valid_bmp(new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle("tick_mark"))),
|
||||
m_valid_label(new wxStaticText(m_parent, wxID_ANY, ""))
|
||||
m_valid_label(new wxStaticText(m_parent, wxID_ANY, "")),
|
||||
m_validator(std::move(validator)),
|
||||
m_directory(path.parent_path())
|
||||
{
|
||||
m_valid_label->SetFont(wxGetApp().bold_font());
|
||||
|
||||
wxBoxSizer* input_path_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
input_path_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W);
|
||||
init_input_name_ctrl(input_path_sizer, pte.output_path.filename().string());
|
||||
input_path_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W);
|
||||
init_input_name_ctrl(input_path_sizer, path.filename().string());
|
||||
|
||||
sizer->Add(input_path_sizer,0, wxEXPAND | wxTOP, BORDER_W);
|
||||
sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*BORDER_W);
|
||||
@ -64,82 +65,131 @@ BulkExportDialog::Item::Item(wxWindow* parent, wxBoxSizer* sizer, PrintToExport&
|
||||
update();
|
||||
}
|
||||
|
||||
void BulkExportDialog::Item::update()
|
||||
{
|
||||
m_path = into_u8(m_text_ctrl->GetValue());
|
||||
|
||||
m_valid_type = ValidationType::Valid;
|
||||
wxString info_line;
|
||||
|
||||
const char* unusable_symbols = "<>[]:/\\|?*\"";
|
||||
|
||||
for (size_t i = 0; i < std::strlen(unusable_symbols); i++) {
|
||||
if (m_path.find_first_of(unusable_symbols[i]) != std::string::npos) {
|
||||
info_line = _L("The following characters are not allowed in the name") + ": " + unusable_symbols;
|
||||
m_valid_type = ValidationType::NoValid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool existing = false;// is_existing(m_path);
|
||||
if (m_valid_type == ValidationType::Valid && existing) {
|
||||
info_line = _L("This name is already used, use another.");
|
||||
m_valid_type = ValidationType::Warning;
|
||||
}
|
||||
|
||||
if (m_valid_type == ValidationType::Valid && m_path.empty()) {
|
||||
info_line = _L("The name cannot be empty.");
|
||||
m_valid_type = ValidationType::NoValid;
|
||||
}
|
||||
|
||||
#ifdef __WXMSW__
|
||||
const int max_path_length = MAX_PATH;
|
||||
constexpr int max_path_length = MAX_PATH;
|
||||
#else
|
||||
const int max_path_length = 255;
|
||||
constexpr int max_path_length = 255;
|
||||
#endif
|
||||
|
||||
if (m_valid_type == ValidationType::Valid && m_path.length() >= max_path_length) {
|
||||
info_line = _L("The name is too long.");
|
||||
m_valid_type = ValidationType::NoValid;
|
||||
struct PathValidator {
|
||||
std::reference_wrapper<std::vector<std::unique_ptr<BulkExportDialog::Item>>> items;
|
||||
using ItemStatus = BulkExportDialog::ItemStatus;
|
||||
|
||||
bool is_duplicate(const fs::path &path) {
|
||||
const int64_t count{std::count_if(
|
||||
items.get().begin(),
|
||||
items.get().end(),
|
||||
[&](const auto &item){
|
||||
return item->path == path;
|
||||
}
|
||||
)};
|
||||
|
||||
return count >= 2;
|
||||
}
|
||||
|
||||
if (m_valid_type == ValidationType::Valid && m_path.find_first_of(' ') == 0) {
|
||||
info_line = _L("The name cannot start with space character.");
|
||||
m_valid_type = ValidationType::NoValid;
|
||||
}
|
||||
std::pair<BulkExportDialog::ItemStatus, wxString> operator()(
|
||||
const fs::path &path,
|
||||
const std::string &filename
|
||||
) {
|
||||
const char* unusable_symbols = "<>[]:/\\|?*\"";
|
||||
for (size_t i = 0; i < std::strlen(unusable_symbols); i++) {
|
||||
if (filename.find_first_of(unusable_symbols[i]) != std::string::npos) {
|
||||
return {
|
||||
ItemStatus::NoValid,
|
||||
_L("The following characters are not allowed in the name") + ": " + unusable_symbols
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (m_valid_type == ValidationType::Valid && m_path.find_last_of(' ') == m_path.length()-1) {
|
||||
info_line = _L("The name cannot end with space character.");
|
||||
m_valid_type = ValidationType::NoValid;
|
||||
if (filename.empty()) {
|
||||
return {
|
||||
ItemStatus::NoValid,
|
||||
_L("The name cannot be empty.")
|
||||
};
|
||||
}
|
||||
|
||||
if (path.string().length() >= max_path_length) {
|
||||
return {
|
||||
ItemStatus::NoValid,
|
||||
_L("The name is too long.")
|
||||
};
|
||||
}
|
||||
|
||||
if (filename.find_first_of(' ') == 0) {
|
||||
return {
|
||||
ItemStatus::NoValid,
|
||||
_L("The name cannot start with space character.")
|
||||
};
|
||||
}
|
||||
|
||||
if (filename.find_last_of(' ') == filename.length()-1) {
|
||||
return {
|
||||
ItemStatus::NoValid,
|
||||
_L("The name cannot end with space character.")
|
||||
};
|
||||
}
|
||||
|
||||
if (is_duplicate(path)) {
|
||||
return {
|
||||
ItemStatus::NoValid,
|
||||
_L("This name is already used, use another.")
|
||||
};
|
||||
}
|
||||
|
||||
if (fs::exists(path)) {
|
||||
return {
|
||||
ItemStatus::Warning,
|
||||
_L("The file already exists!")
|
||||
};
|
||||
}
|
||||
|
||||
return {ItemStatus::Valid, ""};
|
||||
}
|
||||
};
|
||||
|
||||
void BulkExportDialog::Item::update()
|
||||
{
|
||||
std::string filename{into_u8(m_text_ctrl->GetValue())};
|
||||
path = m_directory / filename;
|
||||
|
||||
// Validator needs to be called after path is set!
|
||||
// It has has a reference to all items and searches
|
||||
// for duplicates;
|
||||
const auto [status, info_line]{m_validator(path, filename)};
|
||||
|
||||
m_valid_label->SetLabel(info_line);
|
||||
m_valid_label->Show(!info_line.IsEmpty());
|
||||
m_status = status;
|
||||
|
||||
update_valid_bmp();
|
||||
|
||||
if (is_valid()) {
|
||||
boost::filesystem::path field = m_print_to_export->output_path.parent_path();
|
||||
m_print_to_export->output_path = field / m_path;
|
||||
}
|
||||
|
||||
m_parent->Layout();
|
||||
}
|
||||
|
||||
void BulkExportDialog::Item::update_valid_bmp()
|
||||
{
|
||||
std::string bmp_name = m_valid_type == ValidationType::Warning ? "exclamation_manifold" :
|
||||
m_valid_type == ValidationType::NoValid ? "exclamation" : "tick_mark" ;
|
||||
m_valid_bmp->SetBitmap(*get_bmp_bundle(bmp_name));
|
||||
std::string get_bmp_name(const BulkExportDialog::ItemStatus status) {
|
||||
using ItemStatus = BulkExportDialog::ItemStatus;
|
||||
switch(status) {
|
||||
case ItemStatus::Warning: return "exclamation_manifold";
|
||||
case ItemStatus::NoValid: return "exclamation";
|
||||
case ItemStatus::Valid: return "tick_mark";
|
||||
}
|
||||
return ""; // unreachable
|
||||
}
|
||||
|
||||
//-----------------------------------------------
|
||||
// BulkExportDialog
|
||||
//-----------------------------------------------
|
||||
void BulkExportDialog::Item::update_valid_bmp()
|
||||
{
|
||||
m_valid_bmp->SetBitmap(*get_bmp_bundle(get_bmp_name(m_status)));
|
||||
}
|
||||
|
||||
BulkExportDialog::BulkExportDialog(wxWindow* parent, std::vector<PrintToExport>& exports)
|
||||
: DPIDialog(parent, wxID_ANY, exports.size() == 1 ? _L("Save bed") : _L("Save beds"),
|
||||
wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING)
|
||||
BulkExportDialog::BulkExportDialog(const std::vector<fs::path> &paths):
|
||||
DPIDialog(
|
||||
nullptr,
|
||||
wxID_ANY,
|
||||
paths.size() == 1 ? _L("Save bed") : _L("Save beds"),
|
||||
wxDefaultPosition,
|
||||
wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()),
|
||||
wxDEFAULT_DIALOG_STYLE | wxICON_WARNING
|
||||
)
|
||||
{
|
||||
this->SetFont(wxGetApp().normal_font());
|
||||
|
||||
@ -151,8 +201,9 @@ BulkExportDialog::BulkExportDialog(wxWindow* parent, std::vector<PrintToExport>&
|
||||
|
||||
m_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
for (PrintToExport& exp : exports)
|
||||
AddItem(exp);
|
||||
for (const fs::path& path : paths) {
|
||||
AddItem(path);
|
||||
}
|
||||
|
||||
// Add dialog's buttons
|
||||
wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
|
||||
@ -173,22 +224,17 @@ BulkExportDialog::BulkExportDialog(wxWindow* parent, std::vector<PrintToExport>&
|
||||
#endif
|
||||
}
|
||||
|
||||
BulkExportDialog::~BulkExportDialog()
|
||||
void BulkExportDialog::AddItem(const fs::path& path)
|
||||
{
|
||||
for (auto item : m_items)
|
||||
delete item;
|
||||
}
|
||||
|
||||
void BulkExportDialog::AddItem(PrintToExport& pte)
|
||||
{
|
||||
m_items.emplace_back(new Item{ this, m_sizer, pte });
|
||||
m_items.push_back(std::make_unique<Item>(this, m_sizer, path, PathValidator{m_items}));
|
||||
}
|
||||
|
||||
bool BulkExportDialog::enable_ok_btn() const
|
||||
{
|
||||
for (const Item* item : m_items)
|
||||
if (!item->is_valid())
|
||||
for (const auto &item : m_items)
|
||||
if (!item->is_valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -200,13 +246,26 @@ bool BulkExportDialog::Layout()
|
||||
return ret;
|
||||
}
|
||||
|
||||
void BulkExportDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
std::vector<boost::filesystem::path> BulkExportDialog::get_paths() const {
|
||||
std::vector<boost::filesystem::path> result;
|
||||
std::transform(
|
||||
m_items.begin(),
|
||||
m_items.end(),
|
||||
std::back_inserter(result),
|
||||
[](const auto &item){
|
||||
return item->path;
|
||||
}
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
void BulkExportDialog::on_dpi_changed(const wxRect&)
|
||||
{
|
||||
const int& em = em_unit();
|
||||
|
||||
msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL });
|
||||
|
||||
for (Item* item : m_items)
|
||||
for (auto &item : m_items)
|
||||
item->update_valid_bmp();
|
||||
|
||||
const wxSize& size = wxSize(65 * em, 35 * em);
|
||||
|
@ -16,79 +16,74 @@ class wxTextCtrl;
|
||||
class wxStaticBitmap;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Print;
|
||||
struct GCodeProcessorResult;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
struct PrintToExport {
|
||||
std::reference_wrapper<Slic3r::Print> print;
|
||||
std::reference_wrapper<Slic3r::GCodeProcessorResult> processor_result;
|
||||
boost::filesystem::path output_path;
|
||||
};
|
||||
|
||||
class BulkExportDialog : public DPIDialog
|
||||
{
|
||||
public:
|
||||
enum class ItemStatus { Valid, NoValid, Warning };
|
||||
|
||||
struct Item
|
||||
{
|
||||
enum class ValidationType
|
||||
{
|
||||
Valid,
|
||||
NoValid,
|
||||
Warning
|
||||
};
|
||||
using Validator = std::function<
|
||||
std::pair<BulkExportDialog::ItemStatus, wxString>(
|
||||
boost::filesystem::path,
|
||||
std::string
|
||||
)
|
||||
>;
|
||||
Item(
|
||||
wxWindow *parent,
|
||||
wxBoxSizer *sizer,
|
||||
const boost::filesystem::path &path,
|
||||
Validator validator
|
||||
);
|
||||
Item(const Item &) = delete;
|
||||
Item& operator=(const Item &) = delete;
|
||||
Item(Item &&) = delete;
|
||||
Item& operator=(Item &&) = delete;
|
||||
|
||||
// Item as a separate control(f.e. as a part of ConfigWizard to check name of the new custom priter)
|
||||
Item(wxWindow* parent, wxBoxSizer* sizer, PrintToExport& path);
|
||||
// Item cannot have copy or move constructors, because a wx event binds
|
||||
// directly to its address.
|
||||
|
||||
void update_valid_bmp();
|
||||
bool is_valid() const { return m_valid_type != ValidationType::NoValid; }
|
||||
void update_valid_bmp();
|
||||
bool is_valid() const { return m_status != ItemStatus::NoValid; }
|
||||
|
||||
boost::filesystem::path path;
|
||||
|
||||
private:
|
||||
std::string m_path;
|
||||
PrintToExport* m_print_to_export { nullptr };
|
||||
ItemStatus m_status{ItemStatus::NoValid};
|
||||
wxWindow *m_parent{nullptr};
|
||||
wxStaticBitmap *m_valid_bmp{nullptr};
|
||||
wxTextCtrl *m_text_ctrl{nullptr};
|
||||
wxStaticText *m_valid_label{nullptr};
|
||||
Validator m_validator;
|
||||
boost::filesystem::path m_directory{};
|
||||
|
||||
ValidationType m_valid_type {ValidationType::NoValid};
|
||||
wxWindow* m_parent {nullptr};
|
||||
wxStaticBitmap* m_valid_bmp {nullptr};
|
||||
wxTextCtrl* m_text_ctrl {nullptr};
|
||||
wxStaticText* m_valid_label {nullptr};
|
||||
|
||||
|
||||
void init_input_name_ctrl(wxBoxSizer *input_name_sizer, std::string path);
|
||||
void update();
|
||||
void init_input_name_ctrl(wxBoxSizer *input_name_sizer, const std::string &path);
|
||||
void update();
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<Item*> m_items;
|
||||
|
||||
std::vector<PrintToExport>* m_exports {nullptr};
|
||||
|
||||
wxBoxSizer* m_sizer {nullptr};
|
||||
wxStaticText* m_label {nullptr};
|
||||
|
||||
std::string m_ph_printer_name;
|
||||
std::string m_old_preset_name;
|
||||
wxString m_info_line_extention{wxEmptyString};
|
||||
// This must be a unique ptr, because Item does not have copy nor move constructors.
|
||||
std::vector<std::unique_ptr<Item>> m_items;
|
||||
wxBoxSizer *m_sizer{nullptr};
|
||||
|
||||
public:
|
||||
|
||||
BulkExportDialog(wxWindow* parent, std::vector<PrintToExport>& exports);
|
||||
~BulkExportDialog() override;
|
||||
BulkExportDialog(const std::vector<boost::filesystem::path> &paths);
|
||||
bool Layout() override;
|
||||
|
||||
void AddItem(PrintToExport& pte);
|
||||
std::vector<boost::filesystem::path> get_paths() const;
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
void on_dpi_changed(const wxRect &) override;
|
||||
void on_sys_color_changed() override {}
|
||||
|
||||
private:
|
||||
void AddItem(const boost::filesystem::path &path);
|
||||
bool enable_ok_btn() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
}
|
||||
|
@ -5923,6 +5923,12 @@ void Plater::export_gcode_to_path(
|
||||
appconfig.update_last_output_dir(output_path.parent_path().string(), path_on_removable_media);
|
||||
}
|
||||
|
||||
struct PrintToExport {
|
||||
std::reference_wrapper<Slic3r::Print> print;
|
||||
std::reference_wrapper<Slic3r::GCodeProcessorResult> processor_result;
|
||||
boost::filesystem::path output_path;
|
||||
};
|
||||
|
||||
void Plater::export_all_gcodes(bool prefer_removable) {
|
||||
const auto optional_default_output_file{this->get_default_output_file()};
|
||||
if (!optional_default_output_file) {
|
||||
@ -5937,6 +5943,7 @@ void Plater::export_all_gcodes(bool prefer_removable) {
|
||||
const fs_path &output_dir{*optional_output_dir};
|
||||
|
||||
std::vector<PrintToExport> prints_to_export;
|
||||
std::vector<fs::path> paths;
|
||||
|
||||
for (std::size_t print_index{0}; print_index < this->get_fff_prints().size(); ++print_index) {
|
||||
const std::unique_ptr<Print> &print{this->get_fff_prints()[print_index]};
|
||||
@ -5952,14 +5959,27 @@ void Plater::export_all_gcodes(bool prefer_removable) {
|
||||
};
|
||||
const fs::path output_file{output_dir / filename};
|
||||
prints_to_export.push_back({*print, this->p->gcode_results[print_index], output_file});
|
||||
paths.push_back(output_file);
|
||||
}
|
||||
|
||||
BulkExportDialog dialog(nullptr, prints_to_export);
|
||||
BulkExportDialog dialog{paths};
|
||||
if (dialog.ShowModal() != wxID_OK) {
|
||||
return;
|
||||
}
|
||||
paths = dialog.get_paths();
|
||||
for (std::size_t path_index{0}; path_index < paths.size(); ++path_index) {
|
||||
prints_to_export[path_index].output_path = paths[path_index];
|
||||
}
|
||||
|
||||
bool path_on_removable_media{false};
|
||||
|
||||
Print *original_print{&active_fff_print()};
|
||||
GCodeProcessorResult *original_result{this->p->background_process.get_gcode_result()};
|
||||
ScopeGuard guard{[&](){
|
||||
this->p->background_process.set_fff_print(original_print);
|
||||
this->p->background_process.set_gcode_result(original_result);
|
||||
}};
|
||||
|
||||
for (const PrintToExport &print_to_export : prints_to_export) {
|
||||
this->p->background_process.set_fff_print(&print_to_export.print.get());
|
||||
this->p->background_process.set_gcode_result(&print_to_export.processor_result.get());
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "Jobs/Worker.hpp"
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
#include "slic3r/Utils/PrintHost.hpp"
|
||||
|
||||
class wxString;
|
||||
|
||||
@ -240,6 +241,8 @@ public:
|
||||
void send_gcode();
|
||||
void send_gcode_inner(DynamicPrintConfig* physical_printer_config);
|
||||
void eject_drive();
|
||||
|
||||
std::optional<PrintHostJob> get_connect_print_host_job();
|
||||
void connect_gcode();
|
||||
void printables_to_connect_gcode(const std::string& url);
|
||||
std::string get_upload_filename();
|
||||
|
Loading…
x
Reference in New Issue
Block a user