diff --git a/bundled_deps/imgui/imgui/imconfig.h b/bundled_deps/imgui/imgui/imconfig.h
index 19aed5817b..2b7e83e128 100644
--- a/bundled_deps/imgui/imgui/imconfig.h
+++ b/bundled_deps/imgui/imgui/imconfig.h
@@ -210,6 +210,9 @@ namespace ImGui
// icon for multiple beds
const wchar_t SliceAllBtnIcon = 0x2811;
+ const wchar_t PrintIdle = 0x2812;
+ const wchar_t PrintRunning = 0x2813;
+ const wchar_t PrintFinished = 0x2814;
// void MyFunction(const char* name, const MyMatrix44& v);
}
diff --git a/resources/icons/print_finished.svg b/resources/icons/print_finished.svg
new file mode 100644
index 0000000000..88747cb95d
--- /dev/null
+++ b/resources/icons/print_finished.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/icons/print_idle.svg b/resources/icons/print_idle.svg
new file mode 100644
index 0000000000..6e4058d10f
--- /dev/null
+++ b/resources/icons/print_idle.svg
@@ -0,0 +1,91 @@
+
+
+
+
diff --git a/resources/icons/print_running.svg b/resources/icons/print_running.svg
new file mode 100644
index 0000000000..927c3e70ba
--- /dev/null
+++ b/resources/icons/print_running.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 3ba85b5bd4..9a81ed8ddc 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -466,9 +466,11 @@ namespace DoExport {
static void update_print_estimated_stats(const GCodeProcessor& processor, const std::vector& extruders, PrintStatistics& print_statistics)
{
const GCodeProcessorResult& result = processor.get_result();
- print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time);
+ print_statistics.normal_print_time_seconds = result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time;
+ print_statistics.silent_print_time_seconds = result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time;
+ print_statistics.estimated_normal_print_time = get_time_dhms(print_statistics.normal_print_time_seconds);
print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ?
- get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A";
+ get_time_dhms(print_statistics.silent_print_time_seconds) : "N/A";
// update filament statictics
double total_extruded_volume = 0.0;
diff --git a/src/libslic3r/MultipleBeds.cpp b/src/libslic3r/MultipleBeds.cpp
index 6c8a803c06..2c31b449df 100644
--- a/src/libslic3r/MultipleBeds.cpp
+++ b/src/libslic3r/MultipleBeds.cpp
@@ -5,6 +5,7 @@
#include "Print.hpp"
#include
+#include
namespace Slic3r {
@@ -154,41 +155,114 @@ void MultipleBeds::set_active_bed(int i)
m_active_bed = i;
}
-void MultipleBeds::move_active_to_first_bed(Model& model, const BuildVolume& build_volume, bool to_or_from) const
-{
- static std::vector> old_state;
- size_t i = 0;
- assert(! to_or_from || old_state.empty());
-
+namespace MultipleBedsUtils {
+InstanceOffsets get_instance_offsets(Model& model) {
+ InstanceOffsets result;
for (ModelObject* mo : model.objects) {
for (ModelInstance* mi : mo->instances) {
- if (to_or_from) {
- old_state.resize(i+1);
- old_state[i] = std::make_pair(mi->get_offset(), mi->printable);
- if (this->is_instance_on_active_bed(mi->id()))
- mi->set_offset(mi->get_offset() - get_bed_translation(get_active_bed()));
- else
- mi->printable = false;
- } else {
- mi->set_offset(old_state[i].first);
- mi->printable = old_state[i].second;
- }
- ++i;
+ result.emplace_back(mi->get_offset());
}
}
- if (! to_or_from)
- old_state.clear();
+ return result;
}
+ObjectInstances get_object_instances(const Model& model) {
+ ObjectInstances result;
+ std::transform(
+ model.objects.begin(),
+ model.objects.end(),
+ std::back_inserter(result),
+ [](ModelObject *object){
+ return std::pair{object, object->instances};
+ }
+ );
-bool MultipleBeds::is_instance_on_active_bed(ObjectID id) const
+ return result;
+}
+
+void restore_instance_offsets(Model& model, const InstanceOffsets &offsets)
+{
+ size_t i = 0;
+ for (ModelObject* mo : model.objects) {
+ for (ModelInstance* mi : mo->instances) {
+ mi->set_offset(offsets[i++]);
+ }
+ }
+}
+
+void restore_object_instances(Model& model, const ObjectInstances &object_instances) {
+ ModelObjectPtrs objects;
+
+ std::transform(
+ object_instances.begin(),
+ object_instances.end(),
+ std::back_inserter(objects),
+ [](const std::pair &key_value){
+ auto [object, instances]{key_value};
+ object->instances = std::move(instances);
+ return object;
+ }
+ );
+
+ model.objects = objects;
+}
+
+void with_single_bed_model(Model &model, const int bed_index, const std::function &callable) {
+ const InstanceOffsets original_offssets{MultipleBedsUtils::get_instance_offsets(model)};
+ const ObjectInstances original_objects{get_object_instances(model)};
+ Slic3r::ScopeGuard guard([&]() {
+ restore_object_instances(model, original_objects);
+ restore_instance_offsets(model, original_offssets);
+ });
+
+ s_multiple_beds.move_from_bed_to_first_bed(model, bed_index);
+ s_multiple_beds.remove_instances_outside_outside_bed(model, bed_index);
+ callable();
+}
+
+}
+
+bool MultipleBeds::is_instance_on_bed(const ObjectID id, const int bed_index) const
{
auto it = m_inst_to_bed.find(id);
- return (it != m_inst_to_bed.end() && it->second == m_active_bed);
+ return (it != m_inst_to_bed.end() && it->second == bed_index);
}
+void MultipleBeds::remove_instances_outside_outside_bed(Model& model, const int bed_index) const {
+ for (ModelObject* mo : model.objects) {
+ mo->instances.erase(std::remove_if(
+ mo->instances.begin(),
+ mo->instances.end(),
+ [&](const ModelInstance* instance){
+ return !this->is_instance_on_bed(instance->id(), bed_index);
+ }
+ ), mo->instances.end());
+ }
+ model.objects.erase(std::remove_if(
+ model.objects.begin(),
+ model.objects.end(),
+ [](const ModelObject *object){
+ return object->instances.empty();
+ }
+ ), model.objects.end());
+}
+
+void MultipleBeds::move_from_bed_to_first_bed(Model& model, const int bed_index) const
+{
+ if (bed_index < 0 || bed_index >= MAX_NUMBER_OF_BEDS) {
+ assert(false);
+ return;
+ }
+ for (ModelObject* mo : model.objects) {
+ for (ModelInstance* mi : mo->instances) {
+ if (this->is_instance_on_bed(mi->id(), bed_index)) {
+ mi->set_offset(mi->get_offset() - get_bed_translation(bed_index));
+ }
+ }
+ }
+}
bool MultipleBeds::is_glvolume_on_thumbnail_bed(const Model& model, int obj_idx, int instance_idx) const
{
diff --git a/src/libslic3r/MultipleBeds.hpp b/src/libslic3r/MultipleBeds.hpp
index d5209d2315..a31bb91cd2 100644
--- a/src/libslic3r/MultipleBeds.hpp
+++ b/src/libslic3r/MultipleBeds.hpp
@@ -1,6 +1,7 @@
#ifndef libslic3r_MultipleBeds_hpp_
#define libslic3r_MultipleBeds_hpp_
+#include "libslic3r/Model.hpp"
#include "libslic3r/ObjectID.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/BoundingBox.hpp"
@@ -24,6 +25,10 @@ Index grid_coords2index(const GridCoords &coords);
GridCoords index2grid_coords(Index index);
}
+inline std::vector s_bed_selector_thumbnail_texture_ids;
+inline std::array s_bed_selector_thumbnail_changed;
+inline bool bed_selector_updated{false};
+
class MultipleBeds {
public:
MultipleBeds() = default;
@@ -44,7 +49,11 @@ public:
int get_active_bed() const { return m_active_bed; }
void set_active_bed(int i);
- void move_active_to_first_bed(Model& model, const BuildVolume& build_volume, bool to_or_from) const;
+
+ void remove_instances_outside_outside_bed(Model& model, const int bed) const;
+
+ // Sets !printable to all instances outside the active bed.
+ void move_from_bed_to_first_bed(Model& model, const int bed) const;
void set_thumbnail_bed_idx(int bed_idx) { m_bed_for_thumbnails_generation = bed_idx; }
int get_thumbnail_bed_idx() const { return m_bed_for_thumbnails_generation; }
@@ -71,7 +80,7 @@ public:
void autoslice_next_bed();
private:
- bool is_instance_on_active_bed(ObjectID id) const;
+ bool is_instance_on_bed(const ObjectID id, const int bed_index) const;
int m_number_of_beds = 1;
int m_active_bed = 0;
@@ -91,6 +100,29 @@ private:
};
extern MultipleBeds s_multiple_beds;
+
+namespace MultipleBedsUtils {
+
+using InstanceOffsets = std::vector;
+// The bool is true if the instance is printable.
+// The order is from 'for o in objects; for i in o.instances.
+InstanceOffsets get_instance_offsets(Model& model);
+
+using ObjectInstances = std::vector>;
+ObjectInstances get_object_instances(const Model& model);
+void restore_instance_offsets(Model& model, const InstanceOffsets &offsets);
+void restore_object_instances(Model& model, const ObjectInstances &object_instances);
+
+
+/**
+For each print apply call do:
+- move all instances according to their active bed
+- apply
+- move all instances back to their respective beds
+*/
+void with_single_bed_model(Model &model, const int bed_index, const std::function &callable);
+}
+
} // namespace Slic3r
#endif // libslic3r_MultipleBeds_hpp_
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index 61d4714633..c1efc63598 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -520,6 +520,8 @@ bool is_toolchange_required(
struct PrintStatistics
{
PrintStatistics() { clear(); }
+ float normal_print_time_seconds;
+ float silent_print_time_seconds;
std::string estimated_normal_print_time;
std::string estimated_silent_print_time;
double total_used_filament;
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 7203247c3c..9b962d8a94 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -13,6 +13,7 @@
#include // IWYU pragma: keep
#include
+#include
#include "libslic3r/BuildVolume.hpp"
#include "libslic3r/ClipperUtils.hpp"
@@ -44,6 +45,7 @@
#include "NotificationManager.hpp"
#include "format.hpp"
+#include "slic3r/GUI/BitmapCache.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
@@ -73,6 +75,7 @@
#include
#include
+#include
#include
#include
@@ -1823,6 +1826,242 @@ void GLCanvas3D::update_volumes_colors_by_extruder()
m_volumes.update_colors_by_extruder(m_config);
}
+using PerBedStatistics = std::vector
+>>;
+
+PerBedStatistics get_statistics(){
+ PerBedStatistics result;
+ for (int bed_index=0; bed_indexget_fff_prints()[bed_index].get();
+ if (print->empty() || !print->finished()) {
+ continue;
+ }
+ result.emplace_back(bed_index, std::ref(print->print_statistics()));
+ }
+ return result;
+}
+
+struct StatisticsSum {
+ float cost{};
+ float filement_weight{};
+ float filament_length{};
+ float normal_print_time{};
+ float silent_print_time{};
+};
+
+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;
+ }
+
+ return result;
+}
+
+void project_overview_table() {
+ ImGui::Text("%s", _u8L("Project overview").c_str());
+ if (ImGui::BeginTable("table1", 6)) {
+ ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 100.0);
+ ImGui::TableSetupColumn(
+ _u8L("Cost").c_str(),
+ ImGuiTableColumnFlags_WidthFixed,
+ 60.0f
+ );
+ ImGui::TableSetupColumn(
+ (_u8L("Filament") + " (g)").c_str(),
+ ImGuiTableColumnFlags_WidthFixed,
+ 100.0f
+ );
+ ImGui::TableSetupColumn(
+ (_u8L("Filament") + " (m)").c_str(),
+ ImGuiTableColumnFlags_WidthFixed,
+ 100.0f
+ );
+ ImGui::TableSetupColumn(
+ (_u8L("Estimate Time") + " (" + _u8L("Stealth mode") +")").c_str(),
+ ImGuiTableColumnFlags_WidthFixed,
+ 200.0f
+ );
+ ImGui::TableSetupColumn(
+ (_u8L("Estimate Time") + " (" + _u8L("Normal mode") +")").c_str(),
+ ImGuiTableColumnFlags_WidthFixed,
+ 200.0f
+ );
+ 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());
+ }
+
+ const StatisticsSum statistics_sum{get_statistics_sum()};
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", _u8L("Total").c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", wxString::Format("%.2f", statistics_sum.cost).ToStdString().c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", wxString::Format("%.2f", statistics_sum.filement_weight).ToStdString().c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", wxString::Format("%.2f", statistics_sum.filament_length / 1000).ToStdString().c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", get_time_dhms(statistics_sum.silent_print_time).c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", get_time_dhms(statistics_sum.normal_print_time).c_str());
+
+ ImGui::EndTable();
+ }
+}
+
+struct ExtruderStatistics {
+ float filament_weight{};
+ float filament_length{};
+};
+
+using PerExtruderStatistics = std::map<
+ std::size_t,
+ ExtruderStatistics
+>;
+
+PerExtruderStatistics get_extruder_statistics(){
+ PerExtruderStatistics result;
+ for (int bed_index=0; bed_indexget_fff_prints()[bed_index].get();
+ if (print->empty() || !print->finished()) {
+ continue;
+ }
+ print->print_statistics();
+ const auto& extruders_filaments{wxGetApp().preset_bundle->extruders_filaments};
+ for (const auto &[filament_id, filament_volume] : print->print_statistics().filament_stats) {
+ const Preset* preset = extruders_filaments[filament_id].get_selected_preset();
+ if (preset == nullptr) {
+ continue;
+ }
+
+ const double filament_density = preset->config.opt_float("filament_density", 0);
+ const double diameter = preset->config.opt_float("filament_diameter", filament_id);
+ result[filament_id].filament_weight += filament_volume * filament_density / 1000.0f;
+ result[filament_id].filament_length += filament_volume / (M_PI * diameter * diameter / 4.0) / 1000.0;
+ }
+ }
+ return result;
+}
+
+ExtruderStatistics sum_extruder_statistics(
+ const PerExtruderStatistics &per_extruder_statistics
+) {
+ ExtruderStatistics result;
+ for (const auto &[_, statistics] : per_extruder_statistics) {
+ result.filament_weight += statistics.filament_weight;
+ result.filament_length += statistics.filament_length;
+ }
+
+ return result;
+}
+
+void extruder_usage_table(const PerExtruderStatistics &extruder_statistics) {
+ ImGui::Text("%s", _u8L("Extruder usage breakdown").c_str());
+ if (ImGui::BeginTable("table1", 3)) {
+ ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 100.0f);
+ ImGui::TableSetupColumn(
+ (_u8L("Filament") + " (g)").c_str(),
+ ImGuiTableColumnFlags_WidthFixed,
+ 100.0f
+ );
+ ImGui::TableSetupColumn(
+ (_u8L("Filament") + " (m)").c_str(),
+ ImGuiTableColumnFlags_WidthFixed,
+ 100.0f
+ );
+ ImGui::TableHeadersRow();
+
+ for (const auto &[extruder_index, statistics] : extruder_statistics) {
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", (_u8L("Extruder") + wxString::Format(" %d", extruder_index + 1)).ToStdString().c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", wxString::Format("%.2f", statistics.filament_weight).ToStdString().c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", wxString::Format("%.2f", statistics.filament_length).ToStdString().c_str());
+ }
+
+ const ExtruderStatistics extruder_statistics_sum{sum_extruder_statistics(extruder_statistics)};
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", _u8L("Total").c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", wxString::Format("%.2f", extruder_statistics_sum.filament_weight).ToStdString().c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", wxString::Format("%.2f", extruder_statistics_sum.filament_length).ToStdString().c_str());
+
+ ImGui::EndTable();
+ }
+}
+
+void begin_statistics(const char *window_name) {
+ ImGuiWindowFlags windows_flags =
+ ImGuiWindowFlags_NoCollapse
+ | ImGuiWindowFlags_NoMove
+ | ImGuiWindowFlags_AlwaysAutoResize
+ | ImGuiWindowFlags_NoScrollbar
+ | ImGuiWindowFlags_NoScrollWithMouse;
+
+ const ImVec2 center{ImGui::GetMainViewport()->GetCenter()};
+ const ImVec2 position{center + ImVec2{0, -150.0f}};
+ ImGui::SetNextWindowPos(position, ImGuiCond_Appearing, ImVec2{0.5f, 0.5f});
+
+ ImGui::Begin(window_name, nullptr, windows_flags);
+}
+
+void render_print_statistics() {
+ begin_statistics(_u8L("Statistics").c_str());
+ ImGui::Spacing();
+ project_overview_table();
+ ImGui::Separator();
+
+ const PerExtruderStatistics extruder_statistics{get_extruder_statistics()};
+ if (extruder_statistics.size() > 1) {
+ ImGui::NewLine();
+ extruder_usage_table(extruder_statistics);
+ ImGui::Separator();
+ }
+ ImGui::End();
+}
+
+void render_autoslicing_wait() {
+ const std::string text{_u8L("Statistics will be available once all beds are sliced")};
+ const float text_width = ImGui::CalcTextSize(text.c_str()).x;
+
+ ImGui::SetNextWindowSize(ImVec2(text_width + 50, 110.0));
+ begin_statistics(_u8L("Waiting for statistics").c_str());
+ ImGui::NewLine();
+ const float width{ImGui::GetContentRegionAvail().x};
+ float offset_x = (width - text_width) / 2.0f;
+ if (offset_x > 0.0f){
+ ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offset_x);
+ }
+ ImGui::Text("%s", text.c_str());
+ ImGui::NewLine();
+ ImGui::End();
+}
+
void GLCanvas3D::render()
{
if (m_in_render) {
@@ -1964,34 +2203,31 @@ void GLCanvas3D::render()
if (m_picking_enabled && m_rectangle_selection.is_dragging())
m_rectangle_selection.render(*this);
} else {
- // Autoslicing.
- // Render the combined statistics if all is ready.
- bool valid = true;
- double total_g = 0;
- for (size_t i=0; iget_fff_prints()[i].get();
- if (!print->finished()) {
- // TODO: Only active bed invalidation can be detected here.
- valid = false;
- break;
- }
- total_g += print->print_statistics().total_used_filament;
- }
- ImGui::Begin("Total stats");
- if (valid)
- ImGui::Text("%s", std::to_string(total_g).c_str());
- else
- ImGui::Text("Wait until all beds are sliced...");
- ImGui::End();
+ const auto &prints{
+ tcb::span{wxGetApp().plater()->get_fff_prints()}
+ .subspan(0, s_multiple_beds.get_number_of_beds())
+ };
- if (!valid) {
- if (fff_print()->finished())
+ const bool all_finished{std::all_of(
+ prints.begin(),
+ prints.end(),
+ [](const std::unique_ptr &print){
+ return print->finished() || print->empty();
+ }
+ )};
+
+ if (!all_finished) {
+ render_autoslicing_wait();
+ if (fff_print()->finished() || fff_print()->empty()) {
s_multiple_beds.autoslice_next_bed();
- else
+ } else {
wxGetApp().plater()->schedule_background_process();
+ }
+ } else {
+ render_print_statistics();
}
}
-
+
_render_overlays();
_render_bed_selector();
@@ -2537,7 +2773,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
#endif // SLIC3R_OPENGL_ES
const BoundingBoxf3& bb = volume->bounding_box();
m_wipe_tower_bounding_boxes[bed_idx] = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)};
- if(bed_idx < s_multiple_beds.get_number_of_beds()) {
+ if(static_cast(bed_idx) < s_multiple_beds.get_number_of_beds()) {
m_volumes.volumes.emplace_back(volume);
const auto volume_idx_wipe_tower_new{static_cast(m_volumes.volumes.size() - 1)};
auto it = volume_idxs_wipe_towers_old.find(m_volumes.volumes.back()->geometry_id.second);
@@ -6330,20 +6566,81 @@ 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 "Idle";
+ case PrintStatus::running: return "Running";
+ case PrintStatus::finished: return "Finished";
+ }
+ return {};
+}
+
+wchar_t get_raw_status_icon(const PrintStatus status) {
+ switch(status) {
+ case PrintStatus::finished: return ImGui::PrintFinished;
+ case PrintStatus::running: return ImGui::PrintRunning;
+ case PrintStatus::idle: return ImGui::PrintIdle;
+ }
+ return ImGui::PrintIdle;
+}
+
+std::string get_status_icon(const PrintStatus status) {
+ return boost::nowide::narrow(std::wstring{get_raw_status_icon(status)});
+}
+
+bool bed_selector_thumbnail(
+ const ImVec2 size,
+ const ImVec2 padding,
+ const float side,
+ const float border,
+ const GLuint texture_id,
+ const PrintStatus status
+) {
+ ImGuiWindow* window = GImGui->CurrentWindow;
+ const ImVec2 current_position = GImGui->CurrentWindow->DC.CursorPos;
+ const ImVec2 state_pos = current_position + ImVec2(border, side - 20.f - border);
+
+ const bool clicked{ImGui::ImageButton(
+ (void*)(int64_t)texture_id,
+ size - padding,
+ ImVec2(0, 1),
+ ImVec2(1, 0),
+ border
+ )};
+
+ const std::string icon{get_status_icon(status)};
+
+ window->DrawList->AddText(
+ GImGui->Font,
+ GImGui->FontSize,
+ state_pos,
+ ImGui::GetColorU32(ImGuiCol_Text),
+ icon.c_str(),
+ icon.c_str() + icon.size()
+ );
+
+ return clicked;
+}
+
void Slic3r::GUI::GLCanvas3D::_render_bed_selector()
{
static float btn_side = 80.f;
static float btn_border = 2.f;
- static bool hide_title = true;
ImVec2 btn_size = ImVec2(btn_side, btn_side);
- if (s_multiple_beds.get_number_of_beds() != 1 && wxGetApp().plater()->is_preview_shown()) {
- auto render_bed_button = [btn_size, this](int i)
- {
- //ImGui::Text("%d", i);
- //ImGui::SameLine();
+ bool extra_frame{ false };
+ static std::array, MAX_NUMBER_OF_BEDS> previous_print_status;
+ if (s_multiple_beds.get_number_of_beds() != 1 && wxGetApp().plater()->is_preview_shown()) {
+ auto render_bed_button = [btn_size, this, &extra_frame](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();
@@ -6355,10 +6652,36 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector()
bool clicked = false;
ImVec2 btn_padding = ImVec2(btn_border, btn_border);
- if (i >= int(s_th_tex_id.size()) || empty)
+
+ PrintStatus 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 (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);
- else
- clicked = ImGui::ImageButton((void*)(int64_t)s_th_tex_id[i], btn_size - btn_padding, ImVec2(0, 1), ImVec2(1, 0), btn_border);
+ } else {
+ clicked = bed_selector_thumbnail(
+ btn_size,
+ btn_padding,
+ btn_side,
+ btn_border,
+ s_bed_selector_thumbnail_texture_ids[i],
+ print_status
+ );
+ }
if (clicked && ! empty)
select_bed(i, true);
@@ -6367,15 +6690,9 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector()
if (empty)
ImGui::PopItemFlag();
- //std::string status_text;
- //if (wxGetApp().plater()->get_fff_prints()[i]->finished())
- // status_text = "Finished";
- //else if (m_process->fff_print() == wxGetApp().plater()->get_fff_prints()[i].get() && m_process->running())
- // status_text = "Running";
- //else
- // status_text = "Idle";
- //if (ImGui::IsItemHovered())
- // ImGui::SetTooltip(status_text.c_str());
+ const std::string status_text{get_status_text(print_status)};
+ if (ImGui::IsItemHovered())
+ ImGui::SetTooltip("%s", status_text.c_str());
};
ImGuiWrapper& imgui = *wxGetApp().imgui();
@@ -6398,11 +6715,11 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector()
ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2());
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2());
- // Disable for now.
- //if (imgui.image_button(ImGui::SliceAllBtnIcon, "Slice All")) {
- // if (!s_multiple_beds.is_autoslicing())
- // s_multiple_beds.start_autoslice([this](int i, bool user) { this->select_bed(i, user); });
- //}
+ if (imgui.image_button(ImGui::SliceAllBtnIcon, "Slice All")) {
+ if (!s_multiple_beds.is_autoslicing())
+ s_multiple_beds.start_autoslice([this](int i, bool user) { this->select_bed(i, user); });
+ }
+
ImGui::SameLine();
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, btn_border);
@@ -6418,8 +6735,6 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector()
ImGui::PopStyleVar(3);
#if use_scrolling
- bool extra_frame{ false };
-
ImVec2 win_size = ImGui::GetCurrentWindow()->ContentSizeIdeal +
ImGui::GetCurrentWindow()->WindowPadding * 2.f +
ImGui::GetCurrentWindow()->ScrollbarSizes +
diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp
index d8799ba114..64b1ca2ad5 100644
--- a/src/slic3r/GUI/ImGuiWrapper.cpp
+++ b/src/slic3r/GUI/ImGuiWrapper.cpp
@@ -113,6 +113,9 @@ static const std::map font_icons = {
{ImGui::HorizontalHide , "horizontal_hide" },
{ImGui::HorizontalShow , "horizontal_show" },
{ImGui::SliceAllBtnIcon , "slice_all" },
+ {ImGui::PrintIdle , "print_idle" },
+ {ImGui::PrintRunning , "print_running" },
+ {ImGui::PrintFinished , "print_finished" },
};
static const std::map font_icons_large = {
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index f8f7154dfc..3a0cee5b03 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -18,10 +18,12 @@
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "Plater.hpp"
+#include "slic3r/GUI/BitmapCache.hpp"
#include "slic3r/GUI/Jobs/UIThreadWorker.hpp"
#include
#include
+#include
#include
#include
#include
@@ -155,8 +157,6 @@ using Slic3r::GUI::format_wxstr;
static const std::pair THUMBNAIL_SIZE_3MF = { 256, 256 };
-std::vector s_th_tex_id;
-
namespace Slic3r {
namespace GUI {
@@ -603,7 +603,6 @@ private:
// vector of all warnings generated by last slicing
std::vector> current_warnings;
bool show_warning_dialog { false };
-
};
const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase);
@@ -2160,6 +2159,24 @@ void Plater::priv::process_validation_warning(const std::vector& wa
}
}
+std::array apply_to_inactive_beds(
+ Model &model,
+ std::vector> &prints,
+ const DynamicPrintConfig &config
+) {
+ std::array result;
+ for (std::size_t bed_index{0}; bed_index < prints.size(); ++bed_index) {
+ const std::unique_ptr &print{prints[bed_index]};
+ if (!print || bed_index == s_multiple_beds.get_active_bed()) {
+ continue;
+ }
+ using MultipleBedsUtils::with_single_bed_model;
+ with_single_bed_model(model, bed_index, [&](){
+ result[bed_index] = print->apply(model, config);
+ });
+ }
+ return result;
+}
// Update background processing thread from the current config and Model.
// Returns a bitmask of UpdateBackgroundProcessReturnState.
@@ -2174,6 +2191,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
background_process.set_gcode_result(&gcode_results[active_bed]);
background_process.select_technology(this->printer_technology);
+
if (s_beds_just_switched && printer_technology == ptFFF) {
PrintBase::SlicingStatus status(q->active_fff_print(), -1);
SlicingStatusEvent evt(EVT_SLICING_UPDATE, 0, status);
@@ -2182,10 +2200,12 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
notification_manager->close_notification_of_type(NotificationType::ExportOngoing);
q->sidebar().show_sliced_info_sizer(background_process.finished());
}
-
// bitmap of enum UpdateBackgroundProcessReturnState
unsigned int return_state = 0;
+ if (s_multiple_beds.is_autoslicing()) {
+ return_state = return_state | UPDATE_BACKGROUND_PROCESS_FORCE_RESTART;
+ }
// Get the config ready. The binary gcode flag depends on Preferences, which the backend has no access to.
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config();
@@ -2211,18 +2231,29 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
// Update the "out of print bed" state of ModelInstances.
update_print_volume_state();
- // Move all instances according to their active bed:
- s_multiple_beds.move_active_to_first_bed(q->model(), q->build_volume(), true);
+ Print::ApplyStatus invalidated{Print::ApplyStatus::APPLY_STATUS_INVALIDATED};
+ bool was_running = background_process.running();
+ using MultipleBedsUtils::with_single_bed_model;
- // Apply new config to the possibly running background task.
- bool was_running = background_process.running();
- Print::ApplyStatus invalidated = background_process.apply(q->model(), full_config);
+ std::array apply_statuses{
+ apply_to_inactive_beds(q->model(), q->p->fff_prints, full_config)
+ };
+ with_single_bed_model(q->model(), s_multiple_beds.get_active_bed(), [&](){
+ // Apply new config to the possibly running background task.
+ invalidated = background_process.apply(q->model(), full_config);
+ apply_statuses[s_multiple_beds.get_active_bed()] = invalidated;
+ });
- // Move all instances back to their respective beds.
- s_multiple_beds.move_active_to_first_bed(q->model(), q->build_volume(), false);
+ const bool any_status_changed{std::any_of(
+ apply_statuses.begin(),
+ apply_statuses.end(),
+ [](Print::ApplyStatus status){
+ return status != Print::ApplyStatus::APPLY_STATUS_UNCHANGED;
+ }
+ )};
// If current bed was invalidated, update thumbnails for all beds:
- if (int num = s_multiple_beds.get_number_of_beds(); num > 1 && ! (invalidated & Print::ApplyStatus::APPLY_STATUS_UNCHANGED)) {
+ if (int num = s_multiple_beds.get_number_of_beds(); num > 1 && any_status_changed) {
ThumbnailData data;
ThumbnailsParams params;
params.parts_only = true;
@@ -2236,19 +2267,21 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
int curr_unpack_alignment = 0;
glsafe(glGetIntegerv(GL_UNPACK_ALIGNMENT, &curr_unpack_alignment));
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
- glsafe(glDeleteTextures(s_th_tex_id.size(), s_th_tex_id.data()));
-
- s_th_tex_id.resize(num);
- glsafe(glGenTextures(num, s_th_tex_id.data()));
+ glsafe(glDeleteTextures(s_bed_selector_thumbnail_texture_ids.size(), s_bed_selector_thumbnail_texture_ids.data()));
+ s_bed_selector_thumbnail_changed.fill(false);
+
+ s_bed_selector_thumbnail_texture_ids.resize(num);
+ glsafe(glGenTextures(num, s_bed_selector_thumbnail_texture_ids.data()));
for (int i = 0; i < num; ++i) {
s_multiple_beds.set_thumbnail_bed_idx(i);
generate_thumbnail(data, w, h, params, GUI::Camera::EType::Ortho);
s_multiple_beds.set_thumbnail_bed_idx(-1);
- glsafe(glBindTexture(GL_TEXTURE_2D, s_th_tex_id[i]));
+ glsafe(glBindTexture(GL_TEXTURE_2D, s_bed_selector_thumbnail_texture_ids[i]));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, static_cast(w), static_cast(h), 0, GL_RGBA, GL_UNSIGNED_BYTE, data.pixels.data()));
+ s_bed_selector_thumbnail_changed[i] = true;
}
glsafe(glBindTexture(GL_TEXTURE_2D, curr_bound_texture));
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, curr_unpack_alignment));
@@ -2411,12 +2444,15 @@ bool Plater::priv::restart_background_process(unsigned int state)
return false;
}
- if ( ! this->background_process.empty() &&
- (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0 &&
- ( ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && ! this->background_process.finished()) ||
- (state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) != 0 ||
- (state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) {
-
+ if (
+ !this->background_process.empty()
+ && (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0
+ && (
+ ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && !this->background_process.finished())
+ || (state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) != 0
+ || (state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0
+ )
+ ) {
// The print is valid and it can be started.
if (this->background_process.start()) {
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index af5058869f..bbe136fb8d 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -29,6 +29,8 @@
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "Jobs/Job.hpp"
#include "Jobs/Worker.hpp"
+#include "libslic3r/GCode/ThumbnailData.hpp"
+#include "slic3r/GUI/Camera.hpp"
class wxString;
@@ -118,6 +120,7 @@ public:
void convert_gcode_to_binary();
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);
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.