Autoslicing and aggregated statistics

- Call print apply on all print within update_background_process
- Render multiple bed thumnail only when there is a reason to (by
  requesting extra frame)
- Show status of the slicing process for each bed
- Add aggregated statistics
This commit is contained in:
Martin Šach 2024-11-29 21:19:16 +01:00 committed by Lukas Matena
parent 3fb8b71627
commit dbb12e9537
12 changed files with 668 additions and 98 deletions

View File

@ -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);
}

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 8.9375L6.45062 11.6772C6.67263 11.8535 6.99663 11.8101 7.16447 11.5817L12 5" stroke="white" stroke-linecap="round"/>
<path d="M3 8.9375L6.45062 11.6772C6.67263 11.8535 6.99663 11.8101 7.16447 11.5817L12 5" stroke="white" stroke-opacity="0.2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 382 B

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 348.882 348.882"
style="enable-background:new 0 0 348.882 348.882;"
xml:space="preserve"
sodipodi:docname="edit_button - Copy.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs209">
</defs><sodipodi:namedview
id="namedview207"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="2.3274346"
inkscape:cx="89.583613"
inkscape:cy="139.85355"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="3191"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1" />
<path
d="m 333.988,11.758 -0.42,-0.383 C 325.538,4.04 315.129,0 304.258,0 292.071,0 280.37,5.159 272.154,14.153 L 116.803,184.231 c -1.416,1.55 -2.49,3.379 -3.154,5.37 l -18.267,54.762 c -2.112,6.331 -1.052,13.333 2.835,18.729 3.918,5.438 10.23,8.685 16.886,8.685 0,0 0.001,0 0.001,0 2.879,0 5.693,-0.592 8.362,-1.76 l 52.89,-23.138 c 1.923,-0.841 3.648,-2.076 5.063,-3.626 L 336.771,73.176 C 352.937,55.479 351.69,27.929 333.988,11.758 Z m -203.607,222.489 10.719,-32.134 0.904,-0.99 20.316,18.556 -0.904,0.99 z M 314.621,52.943 182.553,197.53 162.237,178.974 294.305,34.386 c 2.583,-2.828 6.118,-4.386 9.954,-4.386 3.365,0 6.588,1.252 9.082,3.53 l 0.419,0.383 c 5.484,5.009 5.87,13.546 0.861,19.03 z"
id="path170"
style="fill:#ed6b21;fill-opacity:1" /><path
d="m 303.85,138.388 c -8.284,0 -15,6.716 -15,15 v 127.347 c 0,21.034 -17.113,38.147 -38.147,38.147 H 68.904 c -21.035,0 -38.147,-17.113 -38.147,-38.147 V 100.413 c 0,-21.034 17.113,-38.147 38.147,-38.147 h 131.587 c 8.284,0 15,-6.716 15,-15 0,-8.284 -6.716,-15 -15,-15 H 68.904 c -37.577,0 -68.147,30.571 -68.147,68.147 v 180.321 c 0,37.576 30.571,68.147 68.147,68.147 h 181.798 c 37.576,0 68.147,-30.571 68.147,-68.147 V 153.388 c 0.001,-8.284 -6.715,-15 -14.999,-15 z"
id="path172"
style="fill:#ed6b21;fill-opacity:1" />
<g
id="g176">
</g>
<g
id="g178">
</g>
<g
id="g180">
</g>
<g
id="g182">
</g>
<g
id="g184">
</g>
<g
id="g186">
</g>
<g
id="g188">
</g>
<g
id="g190">
</g>
<g
id="g192">
</g>
<g
id="g194">
</g>
<g
id="g196">
</g>
<g
id="g198">
</g>
<g
id="g200">
</g>
<g
id="g202">
</g>
<g
id="g204">
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" viewBox="0 0 800 800">
<circle cx="400" cy="400" r="400" fill="#fff"/>
<path d="M599.3,186.8c-93.9-93.9-246.1-93.9-340,0s-93.9,246.1,0,340Z" transform="translate(0 0)" fill="#363636"/>
<path d="M202.7,612.5c93.9,93.9,246.1,93.9,340,0s93.9-246.1,0-340" transform="translate(0 0)" fill="#ed6b21"/>
</svg>

After

Width:  |  Height:  |  Size: 374 B

View File

@ -466,9 +466,11 @@ namespace DoExport {
static void update_print_estimated_stats(const GCodeProcessor& processor, const std::vector<Extruder>& 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<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time);
print_statistics.normal_print_time_seconds = result.print_statistics.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time;
print_statistics.silent_print_time_seconds = result.print_statistics.modes[static_cast<size_t>(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<size_t>(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;

View File

@ -5,6 +5,7 @@
#include "Print.hpp"
#include <cassert>
#include <algorithm>
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<std::pair<Vec3d, bool>> 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<ModelObject *, ModelInstancePtrs> &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<void()> &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
{

View File

@ -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<unsigned> s_bed_selector_thumbnail_texture_ids;
inline std::array<bool, MAX_NUMBER_OF_BEDS> 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<Vec3d>;
// 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<std::pair<ModelObject*, ModelInstancePtrs>>;
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<void()> &callable);
}
} // namespace Slic3r
#endif // libslic3r_MultipleBeds_hpp_

View File

@ -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;

View File

@ -13,6 +13,7 @@
#include <igl/unproject.h> // IWYU pragma: keep
#include <LocalesUtils.hpp>
#include <nanosvgrast.h>
#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 <boost/log/trivial.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/crc.hpp>
#include <iostream>
#include <float.h>
@ -1823,6 +1826,242 @@ void GLCanvas3D::update_volumes_colors_by_extruder()
m_volumes.update_colors_by_extruder(m_config);
}
using PerBedStatistics = std::vector<std::pair<
std::size_t,
std::reference_wrapper<const PrintStatistics>
>>;
PerBedStatistics get_statistics(){
PerBedStatistics result;
for (int bed_index=0; bed_index<s_multiple_beds.get_number_of_beds(); ++bed_index) {
const Print* print = wxGetApp().plater()->get_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_index<s_multiple_beds.get_number_of_beds(); ++bed_index) {
const Print* print = wxGetApp().plater()->get_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; i<s_multiple_beds.get_number_of_beds(); ++i) {
const Print* print = wxGetApp().plater()->get_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> &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<int>(bed_idx) < s_multiple_beds.get_number_of_beds()) {
m_volumes.volumes.emplace_back(volume);
const auto volume_idx_wipe_tower_new{static_cast<int>(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<std::optional<PrintStatus>, 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 +

View File

@ -113,6 +113,9 @@ static const std::map<const wchar_t, std::string> 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<const wchar_t, std::string> font_icons_large = {

View File

@ -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 <cstddef>
#include <algorithm>
#include <nanosvgrast.h>
#include <numeric>
#include <vector>
#include <string>
@ -155,8 +157,6 @@ using Slic3r::GUI::format_wxstr;
static const std::pair<unsigned int, unsigned int> THUMBNAIL_SIZE_3MF = { 256, 256 };
std::vector<GLuint> s_th_tex_id;
namespace Slic3r {
namespace GUI {
@ -603,7 +603,6 @@ private:
// vector of all warnings generated by last slicing
std::vector<std::pair<Slic3r::PrintStateBase::Warning, size_t>> 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<std::string>& wa
}
}
std::array<Print::ApplyStatus, MAX_NUMBER_OF_BEDS> apply_to_inactive_beds(
Model &model,
std::vector<std::unique_ptr<Print>> &prints,
const DynamicPrintConfig &config
) {
std::array<Print::ApplyStatus, MAX_NUMBER_OF_BEDS> result;
for (std::size_t bed_index{0}; bed_index < prints.size(); ++bed_index) {
const std::unique_ptr<Print> &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<Print::ApplyStatus, MAX_NUMBER_OF_BEDS> 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<GLsizei>(w), static_cast<GLsizei>(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()) {

View File

@ -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<size_t> load_files(const std::vector<boost::filesystem::path>& 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.