Merge branch 'ms_multiple_beds_autoslice'

This commit is contained in:
Lukas Matena 2024-12-13 12:34:35 +01:00
commit 3db367be4a
16 changed files with 669 additions and 234 deletions

View File

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

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
height="200"
width="200"
sodipodi:docname="notification_warning.svg"
xml:space="preserve"
enable-background="new 0 0 100 100"
viewBox="0 0 200 200"
y="0px"
x="0px"
id="warning"
version="1.0"><metadata
id="metadata19"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs17" /><sodipodi:namedview
inkscape:current-layer="warning"
inkscape:window-maximized="1"
inkscape:window-y="-8"
inkscape:window-x="-8"
inkscape:cy="71.071558"
inkscape:cx="34.775892"
inkscape:zoom="3.5638182"
showgrid="false"
id="namedview15"
inkscape:window-height="1377"
inkscape:window-width="2560"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<g
transform="matrix(2.2,0,0,2.2,-9.9977389,-10)"
id="g4">
<path
id="path2"
d="M 90,89.25 H 10 C 8.48,89.25 7.08,88.44 6.32,87.13 5.56,85.82 5.55,84.2 6.31,82.89 l 40,-70 c 0.76,-1.33 2.17,-2.14 3.69,-2.14 1.53,0 2.93,0.82 3.69,2.14 l 40,70 c 0.75,1.32 0.75,2.93 -0.01,4.24 -0.76,1.32 -2.16,2.12 -3.68,2.12 z M 17.33,80.75 H 82.68 L 50,23.57 Z"
fill="#818181" />
</g>
<g
transform="matrix(2.2,0,0,2.2,-9.9977389,-10)"
id="g8">
<path
id="path6"
d="m 50,59.25 c -2.35,0 -4.25,-1.9 -4.25,-4.25 V 40 c 0,-2.35 1.9,-4.25 4.25,-4.25 2.35,0 4.25,1.9 4.25,4.25 v 15 c 0,2.35 -1.9,4.25 -4.25,4.25 z"
fill="#818181" />
</g>
<g
transform="matrix(2.2,0,0,2.2,-9.9977389,-10)"
id="g12">
<circle
id="circle10"
r="5"
cy="70"
cx="50"
fill="#818181" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
resources/icons/numbers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

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

View File

@ -29,6 +29,20 @@ 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};
enum class PrintStatus {
idle,
running,
finished,
outside,
invalid,
empty,
toolpath_outside
};
bool is_sliceable(const PrintStatus status);
inline std::array<PrintStatus, MAX_NUMBER_OF_BEDS> s_print_statuses;
class MultipleBeds {
public:
MultipleBeds() = default;

View File

@ -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<GLModel>());
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<GLTexture>();
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)

View File

@ -54,6 +54,9 @@ private:
float m_scale_factor{ 1.0f };
std::vector<std::unique_ptr<GLModel>> m_digits_models;
std::unique_ptr<GLTexture> m_digits_texture;
public:
Bed3D() = default;
~Bed3D() = default;

View File

@ -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<const boost::filesystem::path>& 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<fs::path> &paths):
BulkExportDialog::BulkExportDialog(const std::vector<std::pair<int, std::optional<fs::path>>> &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<fs::path> &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<wxButton*>(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<fs::path> &paths):
#endif
}
void BulkExportDialog::AddItem(const fs::path& path)
void BulkExportDialog::AddItem(const std::optional<const boost::filesystem::path>& path, int bed_index)
{
m_items.push_back(std::make_unique<Item>(this, m_sizer, path, PathValidator{m_items}));
m_items.push_back(std::make_unique<Item>(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<boost::filesystem::path> BulkExportDialog::get_paths() const {
std::vector<boost::filesystem::path> result;
std::vector<std::pair<int, std::optional<fs::path>>> BulkExportDialog::get_paths() const {
std::vector<std::pair<int, std::optional<fs::path>>> result;
std::transform(
m_items.begin(),
m_items.end(),
std::back_inserter(result),
[](const auto &item){
return item->path;
[](const auto &item) -> std::pair<int, std::optional<fs::path>> {
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();

View File

@ -9,11 +9,15 @@
#include <boost/filesystem/path.hpp>
#include "wxExtensions.hpp"
#include "GUI_Utils.hpp"
#include <optional>
#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<const boost::filesystem::path>& 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<std::unique_ptr<Item>> m_items;
wxBoxSizer *m_sizer{nullptr};
wxFlexGridSizer*m_sizer{nullptr};
public:
BulkExportDialog(const std::vector<boost::filesystem::path> &paths);
bool Layout() override;
std::vector<boost::filesystem::path> get_paths() const;
BulkExportDialog(const std::vector<std::pair<int, std::optional<boost::filesystem::path>>> &paths);
std::vector<std::pair<int, std::optional<boost::filesystem::path>>> 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<const boost::filesystem::path>& path, int bed_index);
void accept();
bool enable_ok_btn() const;
};

View File

@ -1022,8 +1022,12 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
});
m_paths_bounding_box = BoundingBoxf3(libvgcode::convert(bbox[0]).cast<double>(), libvgcode::convert(bbox[1]).cast<double>());
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<double>(), libvgcode::convert(bbox[1]).cast<double>());
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)

View File

@ -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<bool>(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::pair<
std::size_t,
std::reference_wrapper<const PrintStatistics>
std::optional<std::reference_wrapper<const PrintStatistics>>
>>;
PerBedStatistics get_statistics(){
@ -1835,9 +1838,10 @@ PerBedStatistics get_statistics(){
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::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<const PrintStatistics> 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> &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> &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<bool>(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<bool>(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<PrintStatus> 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<PrintStatus> 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); });

View File

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

View File

@ -135,6 +135,7 @@ static const std::map<const wchar_t, std::string> 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" },

View File

@ -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<Print::ApplyStatus> 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> &print{q->p->fff_prints[bed_index]};
using MultipleBedsUtils::with_single_bed_model_fff;
with_single_bed_model_fff(model, bed_index, [&](){
std::vector<std::string> 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> &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> &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<size_t> Plater::load_files(const std::vector<fs::path>& 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<fs::path> 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<Slic3r::Print> print;
std::reference_wrapper<Slic3r::GCodeProcessorResult> 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<void()> &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<PrintToExport> prints_to_export;
std::vector<fs::path> paths;
for (std::size_t print_index{0}; print_index < this->get_fff_prints().size(); ++print_index) {
std::map<int, PrintToExport> prints_to_export;
std::vector<std::pair< int, std::optional<fs::path> >> paths;
for (int print_index{0}; print_index < s_multiple_beds.get_number_of_beds(); ++print_index) {
const std::unique_ptr<Print> &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<std::pair<int, std::optional<fs::path>>> 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<std::pair< int, std::optional<fs::path> >> 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> &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<std::pair<int, std::optional<fs::path>>> output_paths{dialog.get_paths()};
for (const auto &key_value : output_paths) {
const int bed_index{key_value.first};
const std::optional<fs::path> &optional_path{key_value.second};
if (!optional_path) {
continue;
}
const fs::path &path{*optional_path};
const std::unique_ptr<Print> &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<PrusaConnectNew>(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<PrusaConnectNew>(connect);
upload_job.cancelled = upload_job_template.cancelled;
upload_job.upload_data.upload_path = path;
this->p->background_process.prepare_upload(upload_job);
}
);
}
}

View File

@ -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<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.
@ -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<void()> &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);

View File

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