diff --git a/bundled_deps/imgui/imgui/imconfig.h b/bundled_deps/imgui/imgui/imconfig.h
index 2b7e83e128..c8f5b0f023 100644
--- a/bundled_deps/imgui/imgui/imconfig.h
+++ b/bundled_deps/imgui/imgui/imconfig.h
@@ -213,6 +213,7 @@ namespace ImGui
const wchar_t PrintIdle = 0x2812;
const wchar_t PrintRunning = 0x2813;
const wchar_t PrintFinished = 0x2814;
+ const wchar_t WarningMarkerDisabled = 0x2815;
// void MyFunction(const char* name, const MyMatrix44& v);
}
diff --git a/resources/icons/notification_warning_grey.svg b/resources/icons/notification_warning_grey.svg
new file mode 100644
index 0000000000..a593f6a7bf
--- /dev/null
+++ b/resources/icons/notification_warning_grey.svg
@@ -0,0 +1,70 @@
+
+
diff --git a/resources/icons/numbers.png b/resources/icons/numbers.png
new file mode 100644
index 0000000000..25eb17b360
Binary files /dev/null and b/resources/icons/numbers.png differ
diff --git a/src/libslic3r/MultipleBeds.cpp b/src/libslic3r/MultipleBeds.cpp
index 66847e28d4..6ff38a95cf 100644
--- a/src/libslic3r/MultipleBeds.cpp
+++ b/src/libslic3r/MultipleBeds.cpp
@@ -13,6 +13,19 @@ MultipleBeds s_multiple_beds;
bool s_reload_preview_after_switching_beds = false;
bool s_beds_just_switched = false;
+bool is_sliceable(const PrintStatus status) {
+ if (status == PrintStatus::empty) {
+ return false;
+ }
+ if (status == PrintStatus::invalid) {
+ return false;
+ }
+ if (status == PrintStatus::outside) {
+ return false;
+ }
+ return true;
+}
+
namespace BedsGrid {
Index grid_coords_abs2index(GridCoords coords) {
coords = {std::abs(coords.x()), std::abs(coords.y())};
diff --git a/src/libslic3r/MultipleBeds.hpp b/src/libslic3r/MultipleBeds.hpp
index 9f70bcaa9f..28679ca665 100644
--- a/src/libslic3r/MultipleBeds.hpp
+++ b/src/libslic3r/MultipleBeds.hpp
@@ -29,6 +29,20 @@ inline std::vector s_bed_selector_thumbnail_texture_ids;
inline std::array s_bed_selector_thumbnail_changed;
inline bool bed_selector_updated{false};
+enum class PrintStatus {
+ idle,
+ running,
+ finished,
+ outside,
+ invalid,
+ empty,
+ toolpath_outside
+};
+
+bool is_sliceable(const PrintStatus status);
+
+inline std::array s_print_statuses;
+
class MultipleBeds {
public:
MultipleBeds() = default;
diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp
index 81b39dec6d..4a6f6850c8 100644
--- a/src/slic3r/GUI/3DBed.cpp
+++ b/src/slic3r/GUI/3DBed.cpp
@@ -145,6 +145,58 @@ void Bed3D::render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Tra
mat.translate(s_multiple_beds.get_bed_translation(i));
render_internal(canvas, mat, projection_matrix, bottom, scale_factor, show_texture, false, is_thumbnail || i == bed_to_highlight);
}
+
+ if (m_digits_models.empty()) {
+ for (size_t i = 0; i < 10; ++i) {
+ GLModel::Geometry g;
+ g.format.vertex_layout = GLModel::Geometry::EVertexLayout::P3T2;
+ const double digit_part = 94./1024.;
+ g.add_vertex(Vec3f(0, 0, 0), Vec2f(digit_part * i, 1.));
+ g.add_vertex(Vec3f(1, 0, 0), Vec2f(digit_part * (i+1), 1.));
+ g.add_vertex(Vec3f(1, 1, 0), Vec2f(digit_part * (i+1), 0));
+ g.add_vertex(Vec3f(0, 1, 0), Vec2f(digit_part * i, 0));
+ g.add_triangle(0, 1, 3);
+ g.add_triangle(3, 1, 2);
+ m_digits_models.emplace_back(std::make_unique());
+ m_digits_models.back()->init_from(std::move(g));
+ m_digits_models.back()->set_color(ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f));
+ }
+ m_digits_texture = std::make_unique();
+ m_digits_texture->load_from_file(resources_dir() + "/icons/numbers.png", true, GLTexture::ECompressionType::None, false);
+ m_digits_texture->send_compressed_data_to_gpu();
+ }
+ if (!is_thumbnail && s_multiple_beds.get_number_of_beds() > 1) {
+ GLShaderProgram* shader = wxGetApp().get_shader("flat_texture");
+ shader->start_using();
+ shader->set_uniform("projection_matrix", projection_matrix);
+ glsafe(::glEnable(GL_BLEND));
+ glsafe(::glEnable(GL_DEPTH_TEST));
+ glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
+ glsafe(::glBindTexture(GL_TEXTURE_2D, m_digits_texture->get_id()));
+
+ const BoundingBoxf bb = this->build_volume().bounding_volume2d();
+
+ for (int i : beds_to_render) {
+ if (i + 1 >= m_digits_models.size())
+ break;
+
+ double size_x = std::max(10., std::min(bb.size().x(), bb.size().y()) * 0.11);
+ double aspect = 1.2;
+ Transform3d mat = view_matrix;
+ mat.translate(Vec3d(bb.min.x(), bb.min.y(), 0.));
+ mat.translate(s_multiple_beds.get_bed_translation(i));
+ if (build_volume().type() != BuildVolume::Type::Circle)
+ mat.translate(Vec3d(0.3 * size_x, 0.3 * size_x, 0.));
+ mat.translate(Vec3d(0., 0., 0.5));
+ mat.scale(Vec3d(size_x, size_x * aspect, 1.));
+
+ shader->set_uniform("view_model_matrix", mat);
+ m_digits_models[i + 1]->render();
+ }
+ glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
+ glsafe(::glDisable(GL_DEPTH_TEST));
+ shader->stop_using();
+ }
}
void Bed3D::render_for_picking(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor)
@@ -421,6 +473,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas, const Transform3d& v
else if (m_texture.unsent_compressed_data_available()) {
// sends to gpu the already available compressed levels of the main texture
m_texture.send_compressed_data_to_gpu();
+ wxQueueEvent(wxGetApp().plater(), new SimpleEvent(EVT_REGENERATE_BED_THUMBNAILS));
// the temporary texture is not needed anymore, reset it
if (m_temp_texture.get_id() != 0)
diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp
index 95f39f082c..3c48ddeea2 100644
--- a/src/slic3r/GUI/3DBed.hpp
+++ b/src/slic3r/GUI/3DBed.hpp
@@ -54,6 +54,9 @@ private:
float m_scale_factor{ 1.0f };
+ std::vector> m_digits_models;
+ std::unique_ptr m_digits_texture;
+
public:
Bed3D() = default;
~Bed3D() = default;
diff --git a/src/slic3r/GUI/BulkExportDialog.cpp b/src/slic3r/GUI/BulkExportDialog.cpp
index 2e1b764246..27705e6a29 100644
--- a/src/slic3r/GUI/BulkExportDialog.cpp
+++ b/src/slic3r/GUI/BulkExportDialog.cpp
@@ -18,7 +18,7 @@
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "format.hpp"
-#include "Tab.hpp"
+#include "MsgDialog.hpp"
namespace fs = boost::filesystem;
@@ -27,7 +27,7 @@ namespace GUI {
constexpr auto BORDER_W = 10;
-void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer, const std::string &path)
+void BulkExportDialog::Item::init_input_name_ctrl(wxFlexGridSizer* row_sizer, const std::string &path)
{
#ifdef _WIN32
const long style = wxBORDER_SIMPLE;
@@ -37,31 +37,52 @@ void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer,
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);
m_text_ctrl->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { update(); });
+ m_text_ctrl->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& event) { event.Enable(selected); });
- input_path_sizer->Add(m_text_ctrl, 1, wxEXPAND, BORDER_W);
+ row_sizer->Add(m_text_ctrl, 1, wxEXPAND);
}
+
+void BulkExportDialog::Item::init_selection_ctrl(wxFlexGridSizer* row_sizer, int bed_index)
+{
+ m_checkbox = new ::CheckBox(m_parent, std::to_string(bed_index + 1));
+ m_checkbox->SetFont(wxGetApp().bold_font());
+ wxGetApp().UpdateDarkUI(m_checkbox);
+ m_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) {
+ this->selected = event.IsChecked();
+ });
+
+ row_sizer->Add(m_checkbox, 0, wxALIGN_CENTER_VERTICAL);
+ m_checkbox->SetValue(this->selected);
+}
+
BulkExportDialog::Item::Item(
wxWindow *parent,
- wxBoxSizer *sizer,
- const boost::filesystem::path &path,
+ wxFlexGridSizer*sizer,
+ const std::optional& path_opt,
+ const int bed_index,
Validator validator
):
- path(path),
+ bed_index(bed_index),
m_parent(parent),
m_valid_bmp(new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle("tick_mark"))),
- m_valid_label(new wxStaticText(m_parent, wxID_ANY, "")),
- m_validator(std::move(validator)),
- m_directory(path.parent_path())
+ m_validator(std::move(validator))
{
- m_valid_label->SetFont(wxGetApp().bold_font());
+ if (path_opt) {
+ path = *path_opt;
+ m_directory = path.parent_path();
+ }
- 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, path.filename().string());
+ init_selection_ctrl(sizer, bed_index);
+ init_input_name_ctrl(sizer, path.filename().string());
+ sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, BORDER_W);
- sizer->Add(input_path_sizer,0, wxEXPAND | wxTOP, BORDER_W);
- sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*BORDER_W);
+ if (!path_opt) {
+ m_checkbox->Enable(false);
+ m_checkbox->SetValue(false);
+ selected = false;
+ }
+ m_valid_bmp->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& event) { event.Show(selected); });
update();
}
@@ -157,13 +178,11 @@ void BulkExportDialog::Item::update()
// 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_valid_bmp->SetToolTip(info_line);
+
m_status = status;
update_valid_bmp();
-
- m_parent->Layout();
}
std::string get_bmp_name(const BulkExportDialog::ItemStatus status) {
@@ -181,11 +200,11 @@ void BulkExportDialog::Item::update_valid_bmp()
m_valid_bmp->SetBitmap(*get_bmp_bundle(get_bmp_name(m_status)));
}
-BulkExportDialog::BulkExportDialog(const std::vector &paths):
+BulkExportDialog::BulkExportDialog(const std::vector>> &paths):
DPIDialog(
nullptr,
wxID_ANY,
- paths.size() == 1 ? _L("Save bed") : _L("Save beds"),
+ paths.size() == 1 ? _L("Export bed") : _L("Export beds"),
wxDefaultPosition,
wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()),
wxDEFAULT_DIALOG_STYLE | wxICON_WARNING
@@ -199,15 +218,16 @@ BulkExportDialog::BulkExportDialog(const std::vector &paths):
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
- m_sizer = new wxBoxSizer(wxVERTICAL);
+ m_sizer = new wxFlexGridSizer(paths.size(), 3, wxSize(0.5*BORDER_W, BORDER_W));
- for (const fs::path& path : paths) {
- AddItem(path);
+ for (const auto&[bed_index, path] : paths) {
+ AddItem(path, bed_index);
}
// Add dialog's buttons
wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this));
+ btnOK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); });
btnOK->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(enable_ok_btn()); });
topSizer->Add(m_sizer, 0, wxEXPAND | wxALL, BORDER_W);
@@ -224,41 +244,66 @@ BulkExportDialog::BulkExportDialog(const std::vector &paths):
#endif
}
-void BulkExportDialog::AddItem(const fs::path& path)
+void BulkExportDialog::AddItem(const std::optional& path, int bed_index)
{
- m_items.push_back(std::make_unique- (this, m_sizer, path, PathValidator{m_items}));
+ m_items.push_back(std::make_unique
- (this, m_sizer, path, bed_index, PathValidator{m_items}));
+}
+
+void BulkExportDialog::accept()
+{
+ if (has_warnings()) {
+ MessageDialog dialog(nullptr,
+ _L("Some of the selected files already exists. Do you want to replace them?"),
+ _L("Export Beds"), wxYES_NO | wxICON_QUESTION);
+ if (dialog.ShowModal() == wxID_NO)
+ return;
+ }
+
+ EndModal(wxID_OK);
}
bool BulkExportDialog::enable_ok_btn() const
{
for (const auto &item : m_items)
- if (!item->is_valid()) {
+ if (item->selected && !item->is_valid()) {
return false;
}
- return true;
+ bool all_unselected{ true };
+ for (const auto& item : m_items)
+ if (item->selected) {
+ all_unselected = false;
+ break;
+ }
+
+ return !all_unselected;
}
-bool BulkExportDialog::Layout()
-{
- const bool ret = DPIDialog::Layout();
- this->Fit();
- return ret;
-}
-
-std::vector BulkExportDialog::get_paths() const {
- std::vector result;
+std::vector>> BulkExportDialog::get_paths() const {
+ std::vector>> result;
std::transform(
m_items.begin(),
m_items.end(),
std::back_inserter(result),
- [](const auto &item){
- return item->path;
+ [](const auto &item) -> std::pair> {
+ if (!item->selected) {
+ return {item->bed_index, std::nullopt};
+ }
+ return {item->bed_index, item->path};
}
);
return result;
}
+bool BulkExportDialog::has_warnings() const
+{
+ for (const auto& item : m_items)
+ if (item->selected && item->is_warning()) {
+ return true;
+ }
+ return false;
+}
+
void BulkExportDialog::on_dpi_changed(const wxRect&)
{
const int& em = em_unit();
diff --git a/src/slic3r/GUI/BulkExportDialog.hpp b/src/slic3r/GUI/BulkExportDialog.hpp
index ba8a9e265b..83b72f4fba 100644
--- a/src/slic3r/GUI/BulkExportDialog.hpp
+++ b/src/slic3r/GUI/BulkExportDialog.hpp
@@ -9,11 +9,15 @@
#include
#include "wxExtensions.hpp"
#include "GUI_Utils.hpp"
+#include
+
+#include "Widgets/CheckBox.hpp"
class wxString;
class wxStaticText;
class wxTextCtrl;
class wxStaticBitmap;
+class wxFlexGridSizer;
namespace Slic3r {
class Print;
@@ -36,8 +40,9 @@ public:
>;
Item(
wxWindow *parent,
- wxBoxSizer *sizer,
- const boost::filesystem::path &path,
+ wxFlexGridSizer *sizer,
+ const std::optional& path,
+ const int bed_index,
Validator validator
);
Item(const Item &) = delete;
@@ -49,39 +54,45 @@ public:
// directly to its address.
void update_valid_bmp();
- bool is_valid() const { return m_status != ItemStatus::NoValid; }
+ bool is_valid() const { return m_status != ItemStatus::NoValid; }
+ bool is_warning() const { return m_status == ItemStatus::Warning; }
boost::filesystem::path path;
+ int bed_index{};
+ bool selected{true};
private:
ItemStatus m_status{ItemStatus::NoValid};
wxWindow *m_parent{nullptr};
wxStaticBitmap *m_valid_bmp{nullptr};
wxTextCtrl *m_text_ctrl{nullptr};
- wxStaticText *m_valid_label{nullptr};
+ ::CheckBox *m_checkbox{nullptr};
Validator m_validator;
boost::filesystem::path m_directory{};
- void init_input_name_ctrl(wxBoxSizer *input_name_sizer, const std::string &path);
+ void init_input_name_ctrl(wxFlexGridSizer*row_sizer, const std::string &path);
+ void init_selection_ctrl(wxFlexGridSizer*row_sizer, int bed_index);
void update();
};
private:
// This must be a unique ptr, because Item does not have copy nor move constructors.
std::vector> m_items;
- wxBoxSizer *m_sizer{nullptr};
+ wxFlexGridSizer*m_sizer{nullptr};
public:
- BulkExportDialog(const std::vector &paths);
- bool Layout() override;
- std::vector get_paths() const;
+
+ BulkExportDialog(const std::vector>> &paths);
+ std::vector>> get_paths() const;
+ bool has_warnings() const;
protected:
void on_dpi_changed(const wxRect &) override;
void on_sys_color_changed() override {}
private:
- void AddItem(const boost::filesystem::path &path);
+ void AddItem(const std::optional& path, int bed_index);
+ void accept();
bool enable_ok_btn() const;
};
diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp
index 8db4c200b5..158f2b39fe 100644
--- a/src/slic3r/GUI/GCodeViewer.cpp
+++ b/src/slic3r/GUI/GCodeViewer.cpp
@@ -1022,8 +1022,12 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
});
m_paths_bounding_box = BoundingBoxf3(libvgcode::convert(bbox[0]).cast(), libvgcode::convert(bbox[1]).cast());
- if (wxGetApp().is_editor())
+ if (wxGetApp().is_editor()) {
m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box);
+ if (!m_contained_in_bed) {
+ s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::toolpath_outside;
+ }
+ }
m_extruders_count = gcode_result.extruders_count;
m_sequential_view.gcode_window.load_gcode(gcode_result);
@@ -1115,6 +1119,9 @@ void GCodeViewer::load_as_preview(libvgcode::GCodeInputData&& data)
const libvgcode::AABox bbox = m_viewer.get_extrusion_bounding_box();
const BoundingBoxf3 paths_bounding_box(libvgcode::convert(bbox[0]).cast(), libvgcode::convert(bbox[1]).cast());
m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(GCodeProcessorResult(), paths_bounding_box);
+ if (!m_contained_in_bed) {
+ s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::toolpath_outside;
+ }
}
void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* config)
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index f094a965f9..8dc8c56c8b 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -125,6 +125,8 @@ void GLCanvas3D::select_bed(int i, bool triggered_by_user)
m_sequential_print_clearance.m_evaluating = true;
reset_sequential_print_clearance();
+ post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, is_sliceable(s_print_statuses[i])));
+
// The stop call above schedules some events that would be processed after the switch.
// Among else, on_process_completed would be called, which would stop slicing of
// the new bed. We need to stop the process, pump all the events out of the queue
@@ -1560,8 +1562,9 @@ bool GLCanvas3D::check_volumes_outside_state(GLVolumeCollection& volumes, ModelI
if (volume->printable) {
if (overall_state == ModelInstancePVS_Inside && volume->is_outside)
overall_state = ModelInstancePVS_Fully_Outside;
- if (overall_state == ModelInstancePVS_Fully_Outside && volume->is_outside && state == BuildVolume::ObjectState::Colliding)
+ if (overall_state == ModelInstancePVS_Fully_Outside && volume->is_outside && state == BuildVolume::ObjectState::Colliding) {
overall_state = ModelInstancePVS_Partly_Outside;
+ }
contained_min_one |= !volume->is_outside;
if (bed_idx != -1 && bed_idx == s_multiple_beds.get_number_of_beds())
@@ -1827,7 +1830,7 @@ void GLCanvas3D::update_volumes_colors_by_extruder()
using PerBedStatistics = std::vector
+ std::optional>
>>;
PerBedStatistics get_statistics(){
@@ -1835,9 +1838,10 @@ PerBedStatistics get_statistics(){
for (int bed_index=0; bed_indexget_fff_prints()[bed_index].get();
if (print->empty() || !print->finished()) {
- continue;
+ result.emplace_back(bed_index, std::nullopt);
+ } else {
+ result.emplace_back(bed_index, std::optional{std::ref(print->print_statistics())});
}
- result.emplace_back(bed_index, std::ref(print->print_statistics()));
}
return result;
}
@@ -1853,11 +1857,14 @@ struct StatisticsSum {
StatisticsSum get_statistics_sum() {
StatisticsSum result;
for (const auto &[_, statistics] : get_statistics()) {
- result.cost += statistics.get().total_cost;
- result.filement_weight += statistics.get().total_weight;
- result.filament_length += statistics.get().total_used_filament;
- result.normal_print_time += statistics.get().normal_print_time_seconds;
- result.silent_print_time += statistics.get().silent_print_time_seconds;
+ if (!statistics) {
+ continue;
+ }
+ result.cost += statistics->get().total_cost;
+ result.filement_weight += statistics->get().total_weight;
+ result.filament_length += statistics->get().total_used_filament;
+ result.normal_print_time += statistics->get().normal_print_time_seconds;
+ result.silent_print_time += statistics->get().silent_print_time_seconds;
}
return result;
@@ -1897,20 +1904,37 @@ float project_overview_table(float scale) {
);
ImGui::TableHeadersRow();
- for (const auto &[bed_index, statistics] : get_statistics()) {
- ImGui::TableNextRow();
- ImGui::TableNextColumn();
- ImGui::Text("%s", (_u8L("Plate") + wxString::Format(" %d", bed_index + 1)).ToStdString().c_str());
- ImGui::TableNextColumn();
- ImGui::Text("%s", wxString::Format("%.2f", statistics.get().total_cost).ToStdString().c_str());
- ImGui::TableNextColumn();
- ImGui::Text("%s", wxString::Format("%.2f", statistics.get().total_weight).ToStdString().c_str());
- ImGui::TableNextColumn();
- ImGui::Text("%s", wxString::Format("%.2f", statistics.get().total_used_filament / 1000).ToStdString().c_str());
- ImGui::TableNextColumn();
- ImGui::Text("%s", statistics.get().estimated_silent_print_time.c_str());
- ImGui::TableNextColumn();
- ImGui::Text("%s", statistics.get().estimated_normal_print_time.c_str());
+ for (const auto &[bed_index, optional_statistics] : get_statistics()) {
+ if (optional_statistics) {
+ const std::reference_wrapper statistics{*optional_statistics};
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", (_u8L("Bed") + wxString::Format(" %d", bed_index + 1)).ToStdString().c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", wxString::Format("%.2f", statistics.get().total_cost).ToStdString().c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", wxString::Format("%.2f", statistics.get().total_weight).ToStdString().c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", wxString::Format("%.2f", statistics.get().total_used_filament / 1000).ToStdString().c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", statistics.get().estimated_silent_print_time.c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", statistics.get().estimated_normal_print_time.c_str());
+ } else {
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", (_u8L("Bed") + wxString::Format(" %d", bed_index + 1)).ToStdString().c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("-");
+ ImGui::TableNextColumn();
+ ImGui::Text("-");
+ ImGui::TableNextColumn();
+ ImGui::Text("-");
+ ImGui::TableNextColumn();
+ ImGui::Text("-");
+ ImGui::TableNextColumn();
+ ImGui::Text("-");
+ }
}
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiPureWrap::COL_ORANGE_LIGHT);
@@ -2211,22 +2235,20 @@ void GLCanvas3D::render()
if (m_picking_enabled && m_rectangle_selection.is_dragging())
m_rectangle_selection.render(*this);
} else {
- const auto &prints{
- tcb::span{wxGetApp().plater()->get_fff_prints()}
- .subspan(0, s_multiple_beds.get_number_of_beds())
- };
+ const auto &prints{wxGetApp().plater()->get_fff_prints()};
- const bool all_finished{std::all_of(
- prints.begin(),
- prints.end(),
- [](const std::unique_ptr &print){
- return print->finished() || print->empty();
+ bool all_finished{true};
+ for (std::size_t bed_index{}; bed_index < s_multiple_beds.get_number_of_beds(); ++bed_index) {
+ const std::unique_ptr &print{prints[bed_index]};
+ if (!print->finished() && is_sliceable(s_print_statuses[bed_index])) {
+ all_finished = false;
+ break;
}
- )};
+ }
if (!all_finished) {
render_autoslicing_wait();
- if (fff_print()->finished() || fff_print()->empty()) {
+ if (fff_print()->finished() || !is_sliceable(s_print_statuses[s_multiple_beds.get_active_bed()])) {
s_multiple_beds.autoslice_next_bed();
wxYield();
} else {
@@ -2847,7 +2869,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
// checks for geometry outside the print volume to render it accordingly
if (!m_volumes.empty()) {
ModelInstanceEPrintVolumeState state;
- const bool contained_min_one = check_volumes_outside_state(m_volumes, &state, !force_full_scene_refresh);
+ check_volumes_outside_state(m_volumes, &state, !force_full_scene_refresh);
const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside);
const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside);
@@ -2870,15 +2892,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
_set_warning_notification(EWarning::SlaSupportsOutside, false);
}
}
-
- post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS,
- contained_min_one && !m_model->objects.empty() && !partlyOut));
}
else {
_set_warning_notification(EWarning::ObjectOutside, false);
_set_warning_notification(EWarning::ObjectClashed, false);
_set_warning_notification(EWarning::SlaSupportsOutside, false);
- post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false));
}
refresh_camera_scene_box();
@@ -6233,10 +6251,20 @@ void GLCanvas3D::_render_background()
use_error_color = m_dynamic_background_enabled &&
(current_printer_technology() != ptSLA || !m_volumes.empty());
- if (!m_volumes.empty())
- use_error_color &= _is_any_volume_outside().first;
- else
- use_error_color &= m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed();
+ if (s_multiple_beds.is_autoslicing()) {
+ use_error_color &= std::any_of(
+ s_print_statuses.begin(),
+ s_print_statuses.end(),
+ [](const PrintStatus status){
+ return status == PrintStatus::toolpath_outside;
+ }
+ );
+ } else {
+ if (!m_volumes.empty())
+ use_error_color &= _is_any_volume_outside().first;
+ else
+ use_error_color &= m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed();
+ }
}
// Draws a bottom to top gradient over the complete screen.
@@ -6581,17 +6609,15 @@ void GLCanvas3D::_render_overlays()
#define use_scrolling 1
-enum class PrintStatus {
- idle,
- running,
- finished
-};
-
std::string get_status_text(PrintStatus status) {
switch(status) {
case PrintStatus::idle: return _u8L("Unsliced");
- case PrintStatus::running: return _u8L("Slicing...");
+ case PrintStatus::running: return _u8L("Slicing") + "...";
case PrintStatus::finished: return _u8L("Sliced");
+ case PrintStatus::outside: return _u8L("Object at boundary");
+ case PrintStatus::invalid: return _u8L("Invalid data");
+ case PrintStatus::empty: return _u8L("Empty");
+ case PrintStatus::toolpath_outside: return _u8L("Toolpath exceeds bounds");
}
return {};
}
@@ -6601,6 +6627,10 @@ wchar_t get_raw_status_icon(const PrintStatus status) {
case PrintStatus::finished: return ImGui::PrintFinished;
case PrintStatus::running: return ImGui::PrintRunning;
case PrintStatus::idle: return ImGui::PrintIdle;
+ case PrintStatus::outside: return ImGui::PrintIdle;
+ case PrintStatus::invalid: return ImGui::PrintIdle;
+ case PrintStatus::empty: return ImGui::PrintIdle;
+ case PrintStatus::toolpath_outside: return ImGui::PrintIdle;
}
return ImGui::PrintIdle;
}
@@ -6615,13 +6645,14 @@ bool bed_selector_thumbnail(
const float side,
const float border,
const float scale,
- const GLuint texture_id,
+ const int bed_id,
const std::optional status
) {
ImGuiWindow* window = GImGui->CurrentWindow;
const ImVec2 current_position = GImGui->CurrentWindow->DC.CursorPos;
- const ImVec2 state_pos = current_position + ImVec2(3.f * border, side - 20.f * scale) * wxGetApp().imgui()->get_style_scaling();
+ const ImVec2 state_pos = current_position + ImVec2(3.f * border, side - 20.f * scale);
+ const GLuint texture_id = s_bed_selector_thumbnail_texture_ids[bed_id];
const bool clicked{ImGui::ImageButton(
(void*)(int64_t)texture_id,
size - padding,
@@ -6643,26 +6674,66 @@ bool bed_selector_thumbnail(
);
}
+ const ImVec2 id_pos = current_position + ImVec2(3.f * border, 1.5f * border);
+ const std::string id = std::to_string(bed_id+1);
+
+ window->DrawList->AddText(
+ GImGui->Font,
+ GImGui->FontSize * 1.5f,
+ id_pos,
+ ImGui::GetColorU32(ImGuiCol_Text),
+ id.c_str(),
+ id.c_str() + id.size()
+ );
+
return clicked;
}
-bool slice_all_beds_button(bool is_active, const ImVec2 size, const ImVec2 padding)
+bool button_with_icon(const wchar_t icon, const std::string& tooltip, bool is_active, const ImVec2 size)
{
- ImGui::PushStyleColor(ImGuiCol_Button, ImGuiPureWrap::COL_GREY_DARK);
- ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGuiPureWrap::COL_ORANGE_DARK);
- ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGuiPureWrap::COL_ORANGE_DARK);
+ std::string btn_name = boost::nowide::narrow(std::wstring{ icon });
+
+ ImGuiButtonFlags flags = ImGuiButtonFlags_None;
+
+ ImGuiWindow* window = ImGui::GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+ const ImGuiID id = window->GetID(btn_name.c_str());
+ const ImFontAtlasCustomRect* const rect = wxGetApp().imgui()->GetTextureCustomRect(icon);
+ const ImVec2 label_size = ImVec2(rect->Width, rect->Height);
+
+ ImVec2 pos = window->DC.CursorPos;
+ const ImRect bb(pos, pos + size);
+ ImGui::ItemSize(size, style.FramePadding.y);
+ if (!ImGui::ItemAdd(bb, id))
+ return false;
+
+ if (g.CurrentItemFlags & ImGuiItemFlags_ButtonRepeat)
+ flags |= ImGuiButtonFlags_Repeat;
+
+ bool hovered, held;
+ bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags);
+
+ // Render
+ const ImU32 col = ImGui::GetColorU32((held && hovered) ? ImGuiPureWrap::COL_ORANGE_DARK : hovered ? ImGuiPureWrap::COL_ORANGE_LIGHT : ImGuiPureWrap::COL_GREY_DARK);
+ ImGui::RenderNavHighlight(bb, id);
ImGui::PushStyleColor(ImGuiCol_Border, is_active ? ImGuiPureWrap::COL_BUTTON_ACTIVE : ImGuiPureWrap::COL_GREY_DARK);
+ ImGui::RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
+ ImGui::PopStyleColor();
- std::string slice_all_btn_name = boost::nowide::narrow(std::wstring{ ImGui::SliceAllBtnIcon });
- bool clicked = ImGui::Button(slice_all_btn_name.c_str(), size + padding);
+ if (g.LogEnabled)
+ ImGui::LogSetNextTextDecoration("[", "]");
+ ImGui::RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, btn_name.c_str(), NULL, &label_size, style.ButtonTextAlign, &bb);
- if (ImGui::IsItemHovered()) {
- ImGui::SetTooltip("%s", _u8L("Slice all").c_str());
- }
+ IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);
- ImGui::PopStyleColor(4);
+ if (ImGui::IsItemHovered())
+ ImGui::SetTooltip("%s", tooltip.c_str());
- return clicked;
+ return pressed;
}
void Slic3r::GUI::GLCanvas3D::_render_bed_selector()
@@ -6685,39 +6756,56 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector()
auto render_bed_button = [btn_side, btn_border, btn_size, btn_padding, this, &extra_frame, scale](int i)
{
- bool empty = ! s_multiple_beds.is_bed_occupied(i);
bool inactive = i != s_multiple_beds.get_active_bed() || s_multiple_beds.is_autoslicing();
ImGui::PushStyleColor(ImGuiCol_Button, ImGuiPureWrap::COL_GREY_DARK);
ImGui::PushStyleColor(ImGuiCol_Border, inactive ? ImGuiPureWrap::COL_GREY_DARK : ImGuiPureWrap::COL_BUTTON_ACTIVE);
- if (empty)
- ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
+ const PrintStatus print_status{s_print_statuses[i]};
- bool clicked = false;
-
- std::optional print_status;
if (current_printer_technology() == ptFFF) {
- print_status = PrintStatus::idle;
- if (wxGetApp().plater()->get_fff_prints()[i]->finished()) {
- print_status = PrintStatus::finished;
- } else if (m_process->fff_print() == wxGetApp().plater()->get_fff_prints()[i].get() && m_process->running()) {
- print_status = PrintStatus::running;
+ if ( !previous_print_status[i]
+ || print_status != previous_print_status[i]
+ ) {
+ extra_frame = true;
}
+ previous_print_status[i] = print_status;
}
- if (!previous_print_status[i] || print_status != previous_print_status[i]) {
- extra_frame = true;
- }
- previous_print_status[i] = print_status;
-
if (s_bed_selector_thumbnail_changed[i]) {
extra_frame = true;
s_bed_selector_thumbnail_changed[i] = false;
}
- if (i >= int(s_bed_selector_thumbnail_texture_ids.size()) || empty) {
- clicked = ImGui::Button(empty ? "empty" : std::to_string(i + 1).c_str(), btn_size + btn_padding);
+ if (
+ !is_sliceable(print_status)
+ ) {
+ ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
+ }
+
+ bool clicked = false;
+ if (
+ !is_sliceable(print_status)
+ ) {
+ clicked = button_with_icon(
+ ImGui::WarningMarkerDisabled,
+ get_status_text(print_status),
+ !inactive,
+ btn_size + btn_padding
+ );
+ } else if (print_status == PrintStatus::toolpath_outside) {
+ clicked = button_with_icon(
+ ImGui::WarningMarker,
+ get_status_text(print_status),
+ !inactive,
+ btn_size + btn_padding
+ );
+ } else if (
+ i >= int(s_bed_selector_thumbnail_texture_ids.size())
+ ) {
+ clicked = ImGui::Button(
+ std::to_string(i + 1).c_str(), btn_size + btn_padding
+ );
} else {
clicked = bed_selector_thumbnail(
btn_size,
@@ -6725,20 +6813,23 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector()
btn_side,
btn_border,
scale,
- s_bed_selector_thumbnail_texture_ids[i],
- print_status
+ i,
+ current_printer_technology() == ptFFF ? std::optional{print_status} : std::nullopt
);
}
- if (clicked && ! empty)
+ if (clicked && is_sliceable(print_status))
select_bed(i, true);
ImGui::PopStyleColor(2);
- if (empty)
+ if (
+ !is_sliceable(print_status)
+ ) {
ImGui::PopItemFlag();
+ }
- if (print_status) {
- const std::string status_text{get_status_text(*print_status)};
+ if (current_printer_technology() == ptFFF) {
+ const std::string status_text{get_status_text(print_status)};
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", status_text.c_str());
}
@@ -6782,7 +6873,7 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector()
if (
current_printer_technology() == ptFFF &&
- slice_all_beds_button(s_multiple_beds.is_autoslicing(), btn_size, btn_padding)
+ button_with_icon(ImGui::SliceAllBtnIcon, _u8L("Slice all"), s_multiple_beds.is_autoslicing(), btn_size + btn_padding)
) {
if (!s_multiple_beds.is_autoslicing()) {
s_multiple_beds.start_autoslice([this](int i, bool user) { this->select_bed(i, user); });
diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp
index 06249fbf53..76619eeeab 100644
--- a/src/slic3r/GUI/GUI_Preview.cpp
+++ b/src/slic3r/GUI/GUI_Preview.cpp
@@ -938,6 +938,7 @@ void Preview::load_print_as_fff(bool keep_z_range)
}
if (wxGetApp().is_editor() && !has_layers) {
+ m_canvas->reset_gcode_toolpaths();
m_canvas->reset_gcode_layers_times_cache();
m_canvas->load_gcode_shells();
hide_layers_slider();
diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp
index c652b25e24..019b481b47 100644
--- a/src/slic3r/GUI/ImGuiWrapper.cpp
+++ b/src/slic3r/GUI/ImGuiWrapper.cpp
@@ -135,6 +135,7 @@ static const std::map font_icons_large = {
{ImGui::EjectButton , "notification_eject_sd" },
{ImGui::EjectHoverButton , "notification_eject_sd_hover" },
{ImGui::WarningMarker , "notification_warning" },
+ {ImGui::WarningMarkerDisabled , "notification_warning_grey" },
{ImGui::ErrorMarker , "notification_error" },
{ImGui::CancelButton , "notification_cancel" },
{ImGui::CancelHoverButton , "notification_cancel_hover" },
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 67f5ec33d2..cbead96a7b 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -170,6 +170,7 @@ wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent);
// BackgroundSlicingProcess finished either with success or error.
wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, SlicingProcessCompletedEvent);
wxDEFINE_EVENT(EVT_EXPORT_BEGAN, wxCommandEvent);
+wxDEFINE_EVENT(EVT_REGENERATE_BED_THUMBNAILS, SimpleEvent);
// Plater::DropTarget
@@ -563,7 +564,7 @@ struct Plater::priv
void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type);
ThumbnailsList generate_thumbnails(const ThumbnailsParams& params, Camera::EType camera_type);
- void regenerate_thumbnails();
+ void regenerate_thumbnails(SimpleEvent&);
void bring_instance_forward() const;
@@ -581,8 +582,7 @@ struct Plater::priv
std::string last_output_path;
std::string last_output_dir_path;
bool inside_snapshot_capture() { return m_prevent_snapshots != 0; }
- bool process_completed_with_error { false };
-
+
private:
bool layers_height_allowed() const;
@@ -772,6 +772,7 @@ void Plater::priv::init()
q->Bind(EVT_EXPORT_BEGAN, &priv::on_export_began, this);
q->Bind(EVT_GLVIEWTOOLBAR_3D, [this](SimpleEvent&) { q->select_view_3D("3D"); });
q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [this](SimpleEvent&) { q->select_view_3D("Preview"); });
+ q->Bind(EVT_REGENERATE_BED_THUMBNAILS, &priv::regenerate_thumbnails, this);
}
// Drop target:
@@ -1891,10 +1892,39 @@ void Plater::priv::selection_changed()
void Plater::priv::object_list_changed()
{
const bool export_in_progress = this->background_process.is_export_scheduled(); // || ! send_gcode_file.empty());
- // XXX: is this right?
- const bool model_fits = view3D->get_canvas3d()->check_volumes_outside_state() == ModelInstancePVS_Inside;
+ //
+ if (printer_technology == ptFFF) {
+ for (std::size_t bed_index{}; bed_index < s_multiple_beds.get_number_of_beds(); ++bed_index) {
+ if (
+ wxGetApp().plater()->get_fff_prints()[bed_index]->empty()) {
+ s_print_statuses[bed_index] = PrintStatus::empty;
+ }
+ for (const ModelObject *object : wxGetApp().model().objects) {
+ for (const ModelInstance *instance : object->instances) {
+ const auto it{s_multiple_beds.get_inst_map().find(instance->id())};
+ if (
+ it != s_multiple_beds.get_inst_map().end()
+ && it->second == bed_index
+ && instance->printable
+ && instance->print_volume_state == ModelInstancePVS_Partly_Outside
+ ) {
+ s_print_statuses[bed_index] = PrintStatus::outside;
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ if (model.objects.empty()) {
+ s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::empty;
+ }
+ }
- sidebar->enable_buttons(s_multiple_beds.is_bed_occupied(s_multiple_beds.get_active_bed()) && !model.objects.empty() && !export_in_progress && model_fits);
+ sidebar->enable_buttons(
+ s_multiple_beds.is_bed_occupied(s_multiple_beds.get_active_bed())
+ && !export_in_progress
+ && is_sliceable(s_print_statuses[s_multiple_beds.get_active_bed()])
+ );
}
void Plater::priv::select_all()
@@ -2182,7 +2212,7 @@ std::vector apply_to_inactive_beds(
return result;
}
-void Plater::priv::regenerate_thumbnails() {
+void Plater::priv::regenerate_thumbnails(SimpleEvent&) {
const int num{s_multiple_beds.get_number_of_beds()};
if (num <= 1 || num > MAX_NUMBER_OF_BEDS) {
return;
@@ -2302,6 +2332,34 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
throw std::runtime_error{"Ivalid printer technology!"};
}
+ for (std::size_t bed_index{}; bed_index < s_multiple_beds.get_number_of_beds(); ++bed_index) {
+ if (printer_technology == ptFFF) {
+ if (apply_statuses[bed_index] != Print::ApplyStatus::APPLY_STATUS_UNCHANGED) {
+ s_print_statuses[bed_index] = PrintStatus::idle;
+ }
+ } else if (printer_technology == ptSLA) {
+ if (apply_statuses[0] != Print::ApplyStatus::APPLY_STATUS_UNCHANGED) {
+ s_print_statuses[bed_index] = PrintStatus::idle;
+ }
+ } else {
+ throw std::runtime_error{"Ivalid printer technology!"};
+ }
+ }
+
+ if (printer_technology == ptFFF) {
+ for (std::size_t bed_index{0}; bed_index < q->p->fff_prints.size(); ++bed_index) {
+ const std::unique_ptr &print{q->p->fff_prints[bed_index]};
+ using MultipleBedsUtils::with_single_bed_model_fff;
+ with_single_bed_model_fff(model, bed_index, [&](){
+ std::vector warnings;
+ std::string err{print->validate(&warnings)};
+ if (!err.empty()) {
+ s_print_statuses[bed_index] = PrintStatus::invalid;
+ }
+ });
+ }
+ }
+
const bool any_status_changed{std::any_of(
apply_statuses.begin(),
apply_statuses.end(),
@@ -2316,7 +2374,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
// If current bed was invalidated, update thumbnails for all beds:
if (any_status_changed) {
- regenerate_thumbnails();
+ wxQueueEvent(this->q, new SimpleEvent(EVT_REGENERATE_BED_THUMBNAILS));
}
// Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile.
@@ -2346,7 +2404,6 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
notification_manager->set_slicing_progress_hidden();
}
-
if ((invalidated != Print::APPLY_STATUS_UNCHANGED || force_validation) && ! background_process.empty()) {
// The delayed error message is no more valid.
delayed_error_message.clear();
@@ -2410,7 +2467,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
return return_state;
}
}
-
+
if (! this->delayed_error_message.empty())
// Reusing the old state.
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
@@ -2423,7 +2480,6 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
actualize_slicing_warnings(*this->background_process.current_print());
actualize_object_warnings(*this->background_process.current_print());
show_warning_dialog = false;
- process_completed_with_error = false;
}
if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() &&
@@ -2439,7 +2495,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
const wxString invalid_str = _L("Invalid data");
for (auto btn : {ActionButtonType::Reslice, ActionButtonType::SendGCode, ActionButtonType::Export})
sidebar->set_btn_label(btn, invalid_str);
- process_completed_with_error = true;
+ s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::invalid;
}
else
{
@@ -2455,9 +2511,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
const wxString slice_string = background_process.running() && wxGetApp().get_mode() == comSimple ?
_L("Slicing") + dots : _L("Slice now");
sidebar->set_btn_label(ActionButtonType::Reslice, slice_string);
- if (background_process.empty()) {
- sidebar->enable_buttons(false);
- } else if (background_process.finished())
+ if (background_process.finished())
show_action_buttons(false);
else if (!background_process.empty() &&
!background_process.running()) /* Do not update buttons if background process is running
@@ -2467,6 +2521,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
show_action_buttons(true);
}
+ this->q->object_list_changed();
return return_state;
}
@@ -3076,10 +3131,12 @@ void Plater::priv::set_current_panel(wxPanel* panel)
if (wxGetApp().is_editor()) {
// see: Plater::priv::object_list_changed()
- // FIXME: it may be better to have a single function making this check and let it be called wherever needed
bool export_in_progress = this->background_process.is_export_scheduled();
- bool model_fits = view3D->get_canvas3d()->check_volumes_outside_state() != ModelInstancePVS_Partly_Outside;
- if (s_multiple_beds.is_bed_occupied(s_multiple_beds.get_active_bed()) && !model.objects.empty() && !export_in_progress && model_fits) {
+ if (
+ s_multiple_beds.is_bed_occupied(s_multiple_beds.get_active_bed())
+ && !export_in_progress
+ && is_sliceable(s_print_statuses[s_multiple_beds.get_active_bed()])
+ ) {
preview->get_canvas3d()->init_gcode_viewer();
preview->get_canvas3d()->load_gcode_shells();
q->reslice();
@@ -3258,6 +3315,7 @@ void Plater::priv::on_slicing_began()
notification_manager->close_notification_of_type(NotificationType::SignDetected);
notification_manager->close_notification_of_type(NotificationType::ExportFinished);
notification_manager->set_slicing_progress_began();
+ s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::running;
}
void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid)
{
@@ -3349,15 +3407,19 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
const wxString invalid_str = _L("Invalid data");
for (auto btn : { ActionButtonType::Reslice, ActionButtonType::SendGCode, ActionButtonType::Export })
sidebar->set_btn_label(btn, invalid_str);
- process_completed_with_error = true;
}
has_error = true;
+ s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::invalid;
}
if (evt.cancelled()) {
this->notification_manager->set_slicing_progress_canceled(_u8L("Slicing Cancelled."));
+ s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::idle;
}
this->sidebar->show_sliced_info_sizer(evt.success());
+ if (evt.success()) {
+ s_print_statuses[s_multiple_beds.get_active_bed()] = PrintStatus::finished;
+ }
// This updates the "Slice now", "Export G-code", "Arrange" buttons status.
// Namely, it refreshes the "Out of print bed" property of all the ModelObjects, and it enables
@@ -4008,13 +4070,15 @@ void Plater::priv::show_autoslicing_action_buttons() const {
sidebar->Layout();
}
- const bool all_finished{std::all_of(
- this->fff_prints.begin(),
- this->fff_prints.end(),
- [](const std::unique_ptr &print){
- return print->finished() || print->empty();
+ bool all_finished{true};
+ for (std::size_t bed_index{}; bed_index < s_multiple_beds.get_number_of_beds(); ++bed_index) {
+ const std::unique_ptr &print{this->fff_prints[bed_index]};
+ if (!print->finished() && is_sliceable(s_print_statuses[bed_index])) {
+ all_finished = false;
+ break;
}
- )};
+ }
+
sidebar->enable_bulk_buttons(all_finished);
}
@@ -4710,10 +4774,6 @@ void Plater::object_list_changed()
p->object_list_changed();
}
-void Plater::regenerate_thumbnails() {
- p->regenerate_thumbnails();
-}
-
std::vector Plater::load_files(const std::vector& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/) { return p->load_files(input_files, load_model, load_config, imperial_units); }
// To be called when providing a list of files to the GUI slic3r on command line.
@@ -5887,11 +5947,6 @@ std::optional Plater::get_multiple_output_dir(const std::string &start
}
const fs::path output_path{into_path(dlg.GetPath())};
- if (auto error{check_output_path_has_error(output_path)}) {
- const t_link_clicked on_link_clicked = [](const std::string& key) -> void { wxGetApp().jump_to_option(key); };
- ErrorDialog(this, *error, on_link_clicked).ShowModal();
- return std::nullopt;
- }
return output_path;
}
@@ -5904,7 +5959,7 @@ void Plater::export_gcode(bool prefer_removable)
return;
- if (p->process_completed_with_error)
+ if (!is_sliceable(s_print_statuses[s_multiple_beds.get_active_bed()]))
return;
// If possible, remove accents from accented latin characters.
@@ -5956,15 +6011,40 @@ void Plater::export_gcode_to_path(
struct PrintToExport {
std::reference_wrapper print;
std::reference_wrapper processor_result;
- boost::filesystem::path output_path;
- std::size_t bed{};
+ int bed{};
};
+void Plater::with_mocked_fff_background_process(
+ Print &print,
+ GCodeProcessorResult &result,
+ const int bed_index,
+ const std::function &callable
+) {
+ Print *original_print{&active_fff_print()};
+ GCodeProcessorResult *original_result{this->p->background_process.get_gcode_result()};
+ const int original_bed{s_multiple_beds.get_active_bed()};
+ PrinterTechnology original_technology{this->printer_technology()};
+ ScopeGuard guard{[&](){
+ this->p->background_process.set_fff_print(original_print);
+ this->p->background_process.set_gcode_result(original_result);
+ this->p->background_process.select_technology(original_technology);
+ s_multiple_beds.set_active_bed(original_bed);
+ }};
+
+ this->p->background_process.set_fff_print(&print);
+ this->p->background_process.set_gcode_result(&result);
+ this->p->background_process.select_technology(this->p->printer_technology);
+ s_multiple_beds.set_active_bed(bed_index);
+
+ callable();
+}
+
void Plater::export_all_gcodes(bool prefer_removable) {
const auto optional_default_output_file{this->get_default_output_file()};
if (!optional_default_output_file) {
return;
}
+
const fs::path &default_output_file{*optional_default_output_file};
const std::string start_dir{get_output_start_dir(prefer_removable, default_output_file)};
const auto optional_output_dir{get_multiple_output_dir(start_dir)};
@@ -5973,58 +6053,76 @@ void Plater::export_all_gcodes(bool prefer_removable) {
}
const fs_path &output_dir{*optional_output_dir};
- std::vector prints_to_export;
- std::vector paths;
- for (std::size_t print_index{0}; print_index < this->get_fff_prints().size(); ++print_index) {
+ std::map prints_to_export;
+ std::vector >> paths;
+
+ for (int print_index{0}; print_index < s_multiple_beds.get_number_of_beds(); ++print_index) {
const std::unique_ptr &print{this->get_fff_prints()[print_index]};
- if (!print || print->empty()) {
+ if (!print || !is_sliceable(s_print_statuses[print_index])) {
+ paths.emplace_back(print_index, std::nullopt);
continue;
}
+ fs::path default_filename{default_output_file.filename()};
+ this->with_mocked_fff_background_process(
+ *print,
+ this->p->gcode_results[print_index],
+ print_index,
+ [&](){
+ const auto optional_file{this->get_default_output_file()};
+ if (!optional_file) {
+ return;
+ }
+ const fs::path &default_file{*optional_file};
+ default_filename = default_file.filename();
+ }
+ );
+
const fs::path filename{
- default_output_file.stem().string()
+ default_filename.stem().string()
+ "_bed"
+ std::to_string(print_index + 1)
- + default_output_file.extension().string()
+ + default_filename.extension().string()
};
const fs::path output_file{output_dir / filename};
- prints_to_export.push_back({*print, this->p->gcode_results[print_index], output_file, print_index});
- paths.push_back(output_file);
+ prints_to_export.insert({
+ print_index,
+ {*print, this->p->gcode_results[print_index], print_index}
+ });
+ paths.emplace_back(print_index, output_file);
}
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];
- }
+ const std::vector>> output_paths{dialog.get_paths()};
bool path_on_removable_media{false};
+ for (auto &[bed_index, optional_path] : output_paths) {
+ if (!optional_path) {
+ continue;
+ }
- Print *original_print{&active_fff_print()};
- GCodeProcessorResult *original_result{this->p->background_process.get_gcode_result()};
- const int original_bed{s_multiple_beds.get_active_bed()};
- ScopeGuard guard{[&](){
- this->p->background_process.set_fff_print(original_print);
- this->p->background_process.set_gcode_result(original_result);
- s_multiple_beds.set_active_bed(original_bed);
- }};
-
- 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());
- this->p->background_process.set_temp_output_path(print_to_export.bed);
- export_gcode_to_path(
- print_to_export.output_path,
- [&](const bool on_removable){
- this->p->background_process.finalize_gcode(
- print_to_export.output_path.string(),
- path_on_removable_media
+ const PrintToExport &print_to_export{prints_to_export.at(bed_index)};
+ const fs::path &path{*optional_path};
+ with_mocked_fff_background_process(
+ print_to_export.print,
+ print_to_export.processor_result,
+ print_to_export.bed,
+ [&](){
+ this->p->background_process.set_temp_output_path(print_to_export.bed);
+ export_gcode_to_path(
+ path,
+ [&](const bool on_removable){
+ this->p->background_process.finalize_gcode(
+ path.string(),
+ path_on_removable_media
+ );
+ path_on_removable_media = on_removable || path_on_removable_media;
+ }
);
- path_on_removable_media = on_removable || path_on_removable_media;
}
);
}
@@ -6395,7 +6493,7 @@ void Plater::export_toolpaths_to_obj() const
void Plater::reslice()
{
// There is "invalid data" button instead "slice now"
- if (p->process_completed_with_error)
+ if (!is_sliceable(s_print_statuses[s_multiple_beds.get_active_bed()]))
return;
// In case SLA gizmo is in editing mode, refuse to continue
@@ -6605,35 +6703,55 @@ void Plater::connect_gcode_all() {
}
const PrusaConnectNew connect{*print_host_ptr};
- Print *original_print{&active_fff_print()};
- const int original_bed{s_multiple_beds.get_active_bed()};
- PrinterTechnology original_technology{this->printer_technology()};
+ std::vector >> paths;
- ScopeGuard guard{[&](){
- this->p->background_process.set_fff_print(original_print);
- s_multiple_beds.set_active_bed(original_bed);
- this->p->background_process.select_technology(original_technology);
- }};
-
- for (std::size_t print_index{0}; print_index < this->get_fff_prints().size(); ++print_index) {
+ for (std::size_t print_index{0}; print_index < s_multiple_beds.get_number_of_beds(); ++print_index) {
const std::unique_ptr &print{this->get_fff_prints()[print_index]};
+ if (!print || !is_sliceable(s_print_statuses[print_index])) {
+ paths.emplace_back(print_index, std::nullopt);
+ continue;
+ }
+
+ const fs::path filename{upload_job_template.upload_data.upload_path};
+ paths.emplace_back(print_index, fs::path{
+ filename.stem().string()
+ + "_bed" + std::to_string(print_index + 1)
+ + filename.extension().string()
+ });
+ }
+
+ BulkExportDialog dialog{paths};
+ if (dialog.ShowModal() != wxID_OK) {
+ return;
+ }
+ const std::vector>> output_paths{dialog.get_paths()};
+
+ for (const auto &key_value : output_paths) {
+ const int bed_index{key_value.first};
+ const std::optional &optional_path{key_value.second};
+ if (!optional_path) {
+ continue;
+ }
+ const fs::path &path{*optional_path};
+
+ const std::unique_ptr &print{this->get_fff_prints()[bed_index]};
if (!print || print->empty()) {
continue;
}
- this->p->background_process.set_fff_print(print.get());
- this->p->background_process.set_temp_output_path(print_index);
- this->p->background_process.select_technology(this->p->printer_technology);
-
- PrintHostJob upload_job;
- upload_job.upload_data = upload_job_template.upload_data;
- upload_job.printhost = std::make_unique(connect);
- upload_job.cancelled = upload_job_template.cancelled;
- const fs::path filename{upload_job.upload_data.upload_path};
- upload_job.upload_data.upload_path =
- filename.stem().string()
- + "_bed" + std::to_string(print_index + 1)
- + filename.extension().string();
- this->p->background_process.prepare_upload(upload_job);
+ with_mocked_fff_background_process(
+ *print,
+ this->p->gcode_results[bed_index],
+ bed_index,
+ [&](){
+ this->p->background_process.set_temp_output_path(bed_index);
+ PrintHostJob upload_job;
+ upload_job.upload_data = upload_job_template.upload_data;
+ upload_job.printhost = std::make_unique(connect);
+ upload_job.cancelled = upload_job_template.cancelled;
+ upload_job.upload_data.upload_path = path;
+ this->p->background_process.prepare_upload(upload_job);
+ }
+ );
}
}
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index 37c229721c..10e5d769fb 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -58,6 +58,7 @@ namespace UndoRedo {
namespace GUI {
wxDECLARE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
+wxDECLARE_EVENT(EVT_REGENERATE_BED_THUMBNAILS, SimpleEvent);
class MainFrame;
class GLCanvas3D;
@@ -122,7 +123,6 @@ public:
void reload_print();
void object_list_changed();
void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type);
- void regenerate_thumbnails();
std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false);
// To be called when providing a list of files to the GUI slic3r on command line.
@@ -216,6 +216,12 @@ public:
void apply_cut_object_to_model(size_t init_obj_idx, const ModelObjectPtrs& cut_objects);
+ void with_mocked_fff_background_process(
+ Print &print,
+ GCodeProcessorResult &result,
+ const int bed_index,
+ const std::function &callable
+ );
void export_gcode(bool prefer_removable);
void export_all_gcodes(bool prefer_removable);
void export_stl_obj(bool extended = false, bool selection_only = false);
diff --git a/src/slic3r/GUI/Sidebar.cpp b/src/slic3r/GUI/Sidebar.cpp
index 3476a97507..cc39d38e17 100644
--- a/src/slic3r/GUI/Sidebar.cpp
+++ b/src/slic3r/GUI/Sidebar.cpp
@@ -761,8 +761,9 @@ void Sidebar::on_select_preset(wxCommandEvent& evt)
m_object_list->update_object_list_by_printer_technology();
s_multiple_beds.stop_autoslice(false);
this->switch_from_autoslicing_mode();
- this->m_plater->regenerate_thumbnails();
+ wxQueueEvent(this->m_plater, new SimpleEvent(EVT_REGENERATE_BED_THUMBNAILS));
this->m_plater->update();
+ s_print_statuses.fill(PrintStatus::idle);
}
#ifdef __WXMSW__