PrusaSlicer-NonPlainar/src/slic3r/GUI/Plater.cpp

5572 lines
218 KiB
C++
Raw Normal View History

2018-09-17 10:15:11 +00:00
#include "Plater.hpp"
#include <cstddef>
#include <algorithm>
#include <numeric>
2018-09-17 10:15:11 +00:00
#include <vector>
#include <string>
#include <regex>
#include <future>
#include <boost/algorithm/string.hpp>
2018-10-04 09:12:55 +00:00
#include <boost/optional.hpp>
2018-09-17 10:15:11 +00:00
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
#include <boost/nowide/convert.hpp>
2018-09-17 10:15:11 +00:00
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <wx/bmpcbox.h>
#include <wx/statbox.h>
#include <wx/statbmp.h>
#include <wx/filedlg.h>
#include <wx/dnd.h>
#include <wx/progdlg.h>
2018-10-01 14:48:08 +00:00
#include <wx/wupdlock.h>
#include <wx/colordlg.h>
2018-10-18 13:13:38 +00:00
#include <wx/numdlg.h>
#include <wx/debug.h>
#include <wx/busyinfo.h>
2018-09-17 10:15:11 +00:00
#include "libslic3r/libslic3r.h"
#include "libslic3r/Format/STL.hpp"
#include "libslic3r/Format/AMF.hpp"
#include "libslic3r/Format/3mf.hpp"
#include "libslic3r/GCode/PreviewData.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
2018-09-17 10:15:11 +00:00
#include "libslic3r/Model.hpp"
#include "libslic3r/SLA/Hollowing.hpp"
#include "libslic3r/SLA/SupportPoint.hpp"
#include "libslic3r/SLA/ReprojectPointsOnMesh.hpp"
#include "libslic3r/Polygon.hpp"
2018-09-17 10:15:11 +00:00
#include "libslic3r/Print.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/SLAPrint.hpp"
2018-09-17 10:15:11 +00:00
#include "libslic3r/Utils.hpp"
2018-09-17 10:15:11 +00:00
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
#include "GUI_ObjectManipulation.hpp"
#include "GUI_ObjectLayers.hpp"
2018-10-04 09:12:55 +00:00
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
2018-09-17 10:15:11 +00:00
#include "MainFrame.hpp"
2020-04-01 08:30:33 +00:00
#include "format.hpp"
2018-09-17 10:15:11 +00:00
#include "3DScene.hpp"
#include "GLCanvas3D.hpp"
#include "Selection.hpp"
2018-09-17 10:15:11 +00:00
#include "GLToolbar.hpp"
#include "GUI_Preview.hpp"
#include "3DBed.hpp"
#include "Camera.hpp"
#include "Mouse3DController.hpp"
2018-09-17 10:15:11 +00:00
#include "Tab.hpp"
2020-04-23 16:56:09 +00:00
#include "Jobs/ArrangeJob.hpp"
#include "Jobs/RotoptimizeJob.hpp"
2020-04-23 17:45:55 +00:00
#include "Jobs/SLAImportJob.hpp"
2018-09-17 10:15:11 +00:00
#include "PresetBundle.hpp"
#include "BackgroundSlicingProcess.hpp"
#include "ProgressStatusBar.hpp"
2018-12-11 09:33:11 +00:00
#include "PrintHostDialogs.hpp"
#include "ConfigWizard.hpp"
#include "../Utils/ASCIIFolding.hpp"
2018-12-11 09:33:11 +00:00
#include "../Utils/PrintHost.hpp"
#include "../Utils/FixModelByWin10.hpp"
#include "../Utils/UndoRedo.hpp"
2019-11-27 12:30:45 +00:00
#include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp"
#ifdef __APPLE__
#include "Gizmos/GLGizmosManager.hpp"
#endif // __APPLE__
2018-09-17 10:15:11 +00:00
#include <wx/glcanvas.h> // Needs to be last because reasons :-/
2018-10-03 13:14:52 +00:00
#include "WipeTowerDialog.hpp"
#include "libslic3r/CustomGCode.hpp"
2018-09-17 10:15:11 +00:00
2018-10-04 09:12:55 +00:00
using boost::optional;
2018-09-17 10:15:11 +00:00
namespace fs = boost::filesystem;
using Slic3r::_3DScene;
using Slic3r::Preset;
2018-12-11 09:33:11 +00:00
using Slic3r::PrintHostJob;
2020-04-01 08:08:03 +00:00
using Slic3r::GUI::format_wxstr;
2018-09-17 10:15:11 +00:00
static const std::pair<unsigned int, unsigned int> THUMBNAIL_SIZE_3MF = { 256, 256 };
2018-09-17 10:15:11 +00:00
namespace Slic3r {
namespace GUI {
wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent);
wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent);
wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent);
2018-09-17 10:15:11 +00:00
// Sidebar widgets
// struct InfoBox : public wxStaticBox
// {
// InfoBox(wxWindow *parent, const wxString &label) :
// wxStaticBox(parent, wxID_ANY, label)
// {
// SetFont(GUI::small_font().Bold());
// }
// };
class ObjectInfo : public wxStaticBoxSizer
{
public:
ObjectInfo(wxWindow *parent);
2018-10-05 21:29:15 +00:00
wxStaticBitmap *manifold_warning_icon;
2018-09-17 10:15:11 +00:00
wxStaticText *info_size;
wxStaticText *info_volume;
wxStaticText *info_facets;
wxStaticText *info_materials;
wxStaticText *info_manifold;
wxStaticText *label_volume;
wxStaticText *label_materials;
std::vector<wxStaticText *> sla_hidden_items;
2018-11-01 11:33:56 +00:00
bool showing_manifold_warning_icon;
void show_sizer(bool show);
void msw_rescale();
2018-09-17 10:15:11 +00:00
};
ObjectInfo::ObjectInfo(wxWindow *parent) :
wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _L("Info")), wxVERTICAL)
2018-09-17 10:15:11 +00:00
{
GetStaticBox()->SetFont(wxGetApp().bold_font());
2018-09-17 10:15:11 +00:00
auto *grid_sizer = new wxFlexGridSizer(4, 5, 15);
2018-09-17 10:15:11 +00:00
grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
// grid_sizer->AddGrowableCol(1, 1);
// grid_sizer->AddGrowableCol(3, 1);
2018-09-17 10:15:11 +00:00
auto init_info_label = [parent, grid_sizer](wxStaticText **info_label, wxString text_label) {
2019-01-21 11:34:28 +00:00
auto *text = new wxStaticText(parent, wxID_ANY, text_label+":");
text->SetFont(wxGetApp().small_font());
2018-09-17 10:15:11 +00:00
*info_label = new wxStaticText(parent, wxID_ANY, "");
(*info_label)->SetFont(wxGetApp().small_font());
2018-09-17 10:15:11 +00:00
grid_sizer->Add(text, 0);
grid_sizer->Add(*info_label, 0);
return text;
2018-09-17 10:15:11 +00:00
};
init_info_label(&info_size, _L("Size"));
label_volume = init_info_label(&info_volume, _L("Volume"));
init_info_label(&info_facets, _L("Facets"));
label_materials = init_info_label(&info_materials, _L("Materials"));
2018-11-01 11:33:56 +00:00
Add(grid_sizer, 0, wxEXPAND);
2018-09-17 10:15:11 +00:00
auto *info_manifold_text = new wxStaticText(parent, wxID_ANY, _L("Manifold") + ":");
info_manifold_text->SetFont(wxGetApp().small_font());
2018-09-17 10:15:11 +00:00
info_manifold = new wxStaticText(parent, wxID_ANY, "");
info_manifold->SetFont(wxGetApp().small_font());
manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap("exclamation"));
2018-09-17 10:15:11 +00:00
auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL);
sizer_manifold->Add(info_manifold_text, 0);
sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2);
sizer_manifold->Add(info_manifold, 0, wxLEFT, 2);
2018-11-01 11:33:56 +00:00
Add(sizer_manifold, 0, wxEXPAND | wxTOP, 4);
sla_hidden_items = { label_volume, info_volume, label_materials, info_materials };
2018-11-01 11:33:56 +00:00
}
2018-09-17 10:15:11 +00:00
2018-11-01 11:33:56 +00:00
void ObjectInfo::show_sizer(bool show)
{
Show(show);
if (show)
manifold_warning_icon->Show(showing_manifold_warning_icon && show);
2018-09-17 10:15:11 +00:00
}
void ObjectInfo::msw_rescale()
{
manifold_warning_icon->SetBitmap(create_scaled_bitmap("exclamation"));
}
enum SlicedInfoIdx
{
siFilament_m,
siFilament_mm3,
siFilament_g,
siMateril_unit,
siCost,
siEstimatedTime,
siWTNumbetOfToolchanges,
siCount
};
2018-09-17 10:15:11 +00:00
class SlicedInfo : public wxStaticBoxSizer
{
public:
SlicedInfo(wxWindow *parent);
void SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const wxString& new_label="");
2018-09-17 10:15:11 +00:00
private:
std::vector<std::pair<wxStaticText*, wxStaticText*>> info_vec;
2018-09-17 10:15:11 +00:00
};
SlicedInfo::SlicedInfo(wxWindow *parent) :
wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _L("Sliced Info")), wxVERTICAL)
2018-09-17 10:15:11 +00:00
{
GetStaticBox()->SetFont(wxGetApp().bold_font());
2018-09-17 10:15:11 +00:00
auto *grid_sizer = new wxFlexGridSizer(2, 5, 15);
grid_sizer->SetFlexibleDirection(wxVERTICAL);
2018-09-17 10:15:11 +00:00
info_vec.reserve(siCount);
auto init_info_label = [this, parent, grid_sizer](wxString text_label) {
2018-09-17 10:15:11 +00:00
auto *text = new wxStaticText(parent, wxID_ANY, text_label);
text->SetFont(wxGetApp().small_font());
auto info_label = new wxStaticText(parent, wxID_ANY, "N/A");
info_label->SetFont(wxGetApp().small_font());
2018-09-17 10:15:11 +00:00
grid_sizer->Add(text, 0);
grid_sizer->Add(info_label, 0);
info_vec.push_back(std::pair<wxStaticText*, wxStaticText*>(text, info_label));
2018-09-17 10:15:11 +00:00
};
init_info_label(_L("Used Filament (m)"));
init_info_label(_L("Used Filament (mm³)"));
init_info_label(_L("Used Filament (g)"));
init_info_label(_L("Used Material (unit)"));
init_info_label(_L("Cost (money)"));
init_info_label(_L("Estimated printing time"));
init_info_label(_L("Number of tool changes"));
2018-09-17 10:15:11 +00:00
Add(grid_sizer, 0, wxEXPAND);
this->Show(false);
}
void SlicedInfo::SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const wxString& new_label/*=""*/)
{
const bool show = text != "N/A";
if (show)
info_vec[idx].second->SetLabelText(text);
if (!new_label.IsEmpty())
info_vec[idx].first->SetLabelText(new_label);
info_vec[idx].first->Show(show);
info_vec[idx].second->Show(show);
2018-09-17 10:15:11 +00:00
}
PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) :
PresetBitmapComboBox(parent, wxSize(15 * wxGetApp().em_unit(), -1)),
2018-09-17 10:15:11 +00:00
preset_type(preset_type),
last_selected(wxNOT_FOUND),
m_em_unit(wxGetApp().em_unit())
2018-09-17 10:15:11 +00:00
{
2019-07-10 13:55:53 +00:00
SetFont(wxGetApp().normal_font());
Various changes in handling of profile compatiblilities and the "show incompatible profiles" option. It was not able to select the incompatible Print profile, which is possible now. (see Cannot select incompatible printer profile #3715) When the Printer profile derived from the Prusa3D system profile was active or a system Prusa3D profile was active, and when the Print profile with the removed "inherits" field was active (or any other profile derived from the "-- default --" profile was active), then the filament selector offered just the profiles with the removed "inherits" field (or any other profile derived from the "-- default--") profile. This behavior has been now changed, so that in this scenario the Filament selector will offer the Prusa3D vendor profiles compatible with the active Print and Printer profile as well as the user profiles. Slicer was also changed to keep an incompatible preset selected at its respective tab if its respective "Red flag" is enabled. For example, if an incompatible Print preset is selected and a Printer profile is switched to another one which is not compatible with the active Print preset that was red already, the active Print preset is not switched if the Print "Red flag" is active. However, if the Print profile was compatible before the Printer profile is switched and now the Print profile becomes incompatible, another compatible Print profile is selected. A likely bug in wxWidgets was worked around when switching a Print preset on Plater, if the last item in the Print preset was active and incompatible, and another Print preset was selected by the user. On Windows, an CBN_EDITCHANGE is sent just after combo box selection change event and the CBN_EDITCHANGE holds an index of the combo box item, which will be removed by the 1st event, therefore leading to an assert in wxWidgets on CBN_EDITCHANGE. The workaround is to disable processing of CBN_EDITCHANGE on Windows for the Plater preset selection combo boxes.
2020-02-27 10:44:01 +00:00
#ifdef _WIN32
// Workaround for ignoring CBN_EDITCHANGE events, which are processed after the content of the combo box changes, so that
// the index of the item inside CBN_EDITCHANGE may no more be valid.
EnableTextChangedEvents(false);
#endif /* _WIN32 */
2018-09-17 10:15:11 +00:00
Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) {
2020-03-23 21:47:35 +00:00
auto selected_item = evt.GetSelection();
2018-09-17 10:15:11 +00:00
auto marker = reinterpret_cast<Marker>(this->GetClientData(selected_item));
if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) {
2018-09-17 10:15:11 +00:00
this->SetSelection(this->last_selected);
evt.StopPropagation();
if (marker >= LABEL_ITEM_WIZARD_PRINTERS) {
ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME;
switch (marker) {
case LABEL_ITEM_WIZARD_PRINTERS: sp = ConfigWizard::SP_PRINTERS; break;
case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break;
case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break;
}
wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); });
}
2019-08-06 16:16:02 +00:00
} else if ( this->last_selected != selected_item ||
wxGetApp().get_tab(this->preset_type)->get_presets()->current_is_dirty() ) {
2018-09-17 10:15:11 +00:00
this->last_selected = selected_item;
evt.SetInt(this->preset_type);
2018-10-09 10:41:05 +00:00
evt.Skip();
2018-09-17 10:15:11 +00:00
} else {
evt.StopPropagation();
}
});
2019-08-06 16:16:02 +00:00
if (preset_type == Slic3r::Preset::TYPE_FILAMENT)
{
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) {
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
const Preset* selected_preset = preset_bundle->filaments.find_preset(preset_bundle->filament_presets[extruder_idx]);
// Wide icons are shown if the currently selected preset is not compatible with the current printer,
// and red flag is drown in front of the selected preset.
bool wide_icons = selected_preset != nullptr && !selected_preset->is_compatible;
float scale = m_em_unit*0.1f;
int shifl_Left = wide_icons ? int(scale * 16 + 0.5) : 0;
#if defined(wxBITMAPCOMBOBOX_OWNERDRAWN_BASED)
shifl_Left += int(scale * 4 + 0.5f); // IMAGE_SPACING_RIGHT = 4 for wxBitmapComboBox -> Space left of image
#endif
int icon_right_pos = shifl_Left + int(scale * (24+4) + 0.5);
int mouse_pos = event.GetLogicalPosition(wxClientDC(this)).x;
if (mouse_pos < shifl_Left || mouse_pos > icon_right_pos ) {
// Let the combo box process the mouse click.
event.Skip();
return;
}
2019-08-06 16:16:02 +00:00
// Swallow the mouse click and open the color picker.
// get current color
DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config();
auto colors = static_cast<ConfigOptionStrings*>(cfg->option("extruder_colour")->clone());
wxColour clr(colors->values[extruder_idx]);
if (!clr.IsOk())
clr = wxColour(0,0,0); // Don't set alfa to transparence
auto data = new wxColourData();
data->SetChooseFull(1);
data->SetColour(clr);
wxColourDialog dialog(this, data);
dialog.CenterOnParent();
if (dialog.ShowModal() == wxID_OK)
{
colors->values[extruder_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
2019-08-06 16:16:02 +00:00
DynamicPrintConfig cfg_new = *cfg;
cfg_new.set_key_value("extruder_colour", colors);
wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new);
preset_bundle->update_plater_filament_ui(extruder_idx, this);
wxGetApp().plater()->on_config_change(cfg_new);
}
});
}
edit_btn = new ScalableButton(parent, wxID_ANY, "cog");
edit_btn->SetToolTip(_L("Click to edit preset"));
edit_btn->Bind(wxEVT_BUTTON, ([preset_type, this](wxCommandEvent)
{
Tab* tab = wxGetApp().get_tab(preset_type);
if (!tab)
return;
int page_id = wxGetApp().tab_panel()->FindPage(tab);
if (page_id == wxNOT_FOUND)
return;
wxGetApp().tab_panel()->SetSelection(page_id);
// Switch to Settings NotePad
wxGetApp().mainframe->select_tab();
2019-08-06 16:16:02 +00:00
/* In a case of a multi-material printing, for editing another Filament Preset
* it's needed to select this preset for the "Filament settings" Tab
2019-03-19 13:36:32 +00:00
*/
2019-08-06 16:16:02 +00:00
if (preset_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
2019-03-19 13:36:32 +00:00
{
const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data();
2019-08-06 16:16:02 +00:00
// Call select_preset() only if there is new preset and not just modified
2019-03-20 09:14:49 +00:00
if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) )
{
const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset);
tab->select_preset(/*selected_preset*/preset_name);
}
2019-03-19 13:36:32 +00:00
}
}));
2018-09-17 10:15:11 +00:00
}
PresetComboBox::~PresetComboBox()
{
if (edit_btn)
edit_btn->Destroy();
}
2018-09-17 10:15:11 +00:00
void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type)
2018-10-09 10:41:05 +00:00
{
this->SetClientData(item, (void*)label_item_type);
2018-10-09 10:41:05 +00:00
}
2020-03-23 21:47:35 +00:00
void PresetComboBox::check_selection(int selection)
2018-12-03 14:16:33 +00:00
{
2020-03-23 21:47:35 +00:00
this->last_selected = selection;
2018-12-03 14:16:33 +00:00
}
void PresetComboBox::msw_rescale()
{
m_em_unit = wxGetApp().em_unit();
edit_btn->msw_rescale();
}
// Frequently changed parameters
class FreqChangedParams : public OG_Settings
{
double m_brim_width = 0.0;
wxButton* m_wiping_dialog_button{ nullptr };
wxSizer* m_sizer {nullptr};
std::shared_ptr<ConfigOptionsGroup> m_og_sla;
std::vector<ScalableButton*> m_empty_buttons;
public:
2019-07-10 13:55:53 +00:00
FreqChangedParams(wxWindow* parent);
~FreqChangedParams() {}
wxButton* get_wiping_dialog_button() { return m_wiping_dialog_button; }
wxSizer* get_sizer() override;
ConfigOptionsGroup* get_og(const bool is_fff);
void Show(const bool is_fff);
void msw_rescale();
};
void FreqChangedParams::msw_rescale()
{
m_og->msw_rescale();
m_og_sla->msw_rescale();
for (auto btn: m_empty_buttons)
btn->msw_rescale();
}
2019-07-10 13:55:53 +00:00
FreqChangedParams::FreqChangedParams(wxWindow* parent) :
OG_Settings(parent, false)
{
DynamicPrintConfig* config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
// Frequently changed parameters for FFF_technology
m_og->set_config(config);
2019-07-10 13:55:53 +00:00
m_og->hide_labels();
2018-10-31 11:56:08 +00:00
m_og->m_on_change = [config, this](t_config_option_key opt_key, boost::any value) {
Tab* tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT);
if (!tab_print) return;
2018-10-31 11:56:08 +00:00
if (opt_key == "fill_density") {
value = m_og->get_config_value(*config, opt_key);
tab_print->set_value(opt_key, value);
tab_print->update();
}
else{
DynamicPrintConfig new_conf = *config;
2018-10-31 11:56:08 +00:00
if (opt_key == "brim") {
double new_val;
double brim_width = config->opt_float("brim_width");
if (boost::any_cast<bool>(value) == true)
{
new_val = m_brim_width == 0.0 ? 5 :
m_brim_width < 0.0 ? m_brim_width * (-1) :
m_brim_width;
}
else {
m_brim_width = brim_width * (-1);
new_val = 0;
}
new_conf.set_key_value("brim_width", new ConfigOptionFloat(new_val));
}
else {
assert(opt_key == "support");
const wxString& selection = boost::any_cast<wxString>(value);
PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
auto support_material = selection == _("None") ? false : true;
new_conf.set_key_value("support_material", new ConfigOptionBool(support_material));
if (selection == _("Everywhere")) {
new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false));
if (printer_technology == ptFFF)
new_conf.set_key_value("support_material_auto", new ConfigOptionBool(true));
} else if (selection == _("Support on build plate only")) {
new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(true));
if (printer_technology == ptFFF)
new_conf.set_key_value("support_material_auto", new ConfigOptionBool(true));
} else if (selection == _("For support enforcers only")) {
assert(printer_technology == ptFFF);
new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false));
new_conf.set_key_value("support_material_auto", new ConfigOptionBool(false));
}
}
tab_print->load_config(new_conf);
}
tab_print->update_dirty();
};
2019-08-06 16:16:02 +00:00
2019-02-07 13:44:05 +00:00
Line line = Line { "", "" };
ConfigOptionDef support_def;
support_def.label = L("Supports");
support_def.type = coStrings;
support_def.gui_type = "select_open";
support_def.tooltip = L("Select what kind of support do you need");
support_def.enum_labels.push_back(L("None"));
support_def.enum_labels.push_back(L("Support on build plate only"));
support_def.enum_labels.push_back(L("For support enforcers only"));
support_def.enum_labels.push_back(L("Everywhere"));
support_def.set_default_value(new ConfigOptionStrings{ "None" });
Option option = Option(support_def, "support");
option.opt.full_width = true;
2019-02-07 13:44:05 +00:00
line.append_option(option);
/* Not a best solution, but
* Temporary workaround for right border alignment
*/
auto empty_widget = [this] (wxWindow* parent) {
auto sizer = new wxBoxSizer(wxHORIZONTAL);
auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString,
wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, int(0.3 * wxGetApp().em_unit()));
m_empty_buttons.push_back(btn);
return sizer;
};
line.append_widget(empty_widget);
2019-02-07 13:44:05 +00:00
m_og->append_line(line);
2019-08-06 16:16:02 +00:00
2019-02-07 13:44:05 +00:00
line = Line { "", "" };
option = m_og->get_option("fill_density");
option.opt.label = L("Infill");
2019-05-13 16:21:17 +00:00
option.opt.width = 7/*6*/;
2019-07-13 08:37:21 +00:00
option.opt.sidetext = " ";
2019-02-07 13:44:05 +00:00
line.append_option(option);
m_brim_width = config->opt_float("brim_width");
ConfigOptionDef def;
def.label = L("Brim");
def.type = coBool;
def.tooltip = L("This flag enables the brim that will be printed around each object on the first layer.");
def.gui_type = "";
def.set_default_value(new ConfigOptionBool{ m_brim_width > 0.0 ? true : false });
option = Option(def, "brim");
2019-07-13 08:37:21 +00:00
option.opt.sidetext = "";
2019-02-07 13:44:05 +00:00
line.append_option(option);
auto wiping_dialog_btn = [this](wxWindow* parent) {
m_wiping_dialog_button = new wxButton(parent, wxID_ANY, _L("Purging volumes") + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
2019-07-10 13:55:53 +00:00
m_wiping_dialog_button->SetFont(wxGetApp().normal_font());
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(m_wiping_dialog_button, 0, wxALIGN_CENTER_VERTICAL);
m_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e)
{
auto &project_config = wxGetApp().preset_bundle->project_config;
const std::vector<double> &init_matrix = (project_config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values;
const std::vector<double> &init_extruders = (project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values;
2019-09-30 12:03:50 +00:00
const std::vector<std::string> extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config();
WipingDialog dlg(parent, cast<float>(init_matrix), cast<float>(init_extruders), extruder_colours);
if (dlg.ShowModal() == wxID_OK) {
std::vector<float> matrix = dlg.get_matrix();
std::vector<float> extruders = dlg.get_extruders();
(project_config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values = std::vector<double>(matrix.begin(), matrix.end());
(project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values = std::vector<double>(extruders.begin(), extruders.end());
2019-02-07 13:44:05 +00:00
wxPostEvent(parent, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, parent));
}
}));
2019-08-06 16:16:02 +00:00
auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString,
wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
sizer->Add(btn , 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT,
int(0.3 * wxGetApp().em_unit()));
m_empty_buttons.push_back(btn);
return sizer;
};
2019-02-07 13:44:05 +00:00
line.append_widget(wiping_dialog_btn);
2019-02-07 13:44:05 +00:00
m_og->append_line(line);
// Frequently changed parameters for SLA_technology
m_og_sla = std::make_shared<ConfigOptionsGroup>(parent, "");
2019-07-10 13:55:53 +00:00
m_og_sla->hide_labels();
DynamicPrintConfig* config_sla = &wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
m_og_sla->set_config(config_sla);
m_og_sla->m_on_change = [config_sla, this](t_config_option_key opt_key, boost::any value) {
Tab* tab = wxGetApp().get_tab(Preset::TYPE_SLA_PRINT);
if (!tab) return;
2019-08-06 16:16:02 +00:00
DynamicPrintConfig new_conf = *config_sla;
if (opt_key == "pad") {
const wxString& selection = boost::any_cast<wxString>(value);
const bool pad_enable = selection == _("None") ? false : true;
new_conf.set_key_value("pad_enable", new ConfigOptionBool(pad_enable));
if (selection == _("Below object"))
new_conf.set_key_value("pad_around_object", new ConfigOptionBool(false));
2019-08-06 16:16:02 +00:00
else if (selection == _("Around object"))
new_conf.set_key_value("pad_around_object", new ConfigOptionBool(true));
}
else
{
assert(opt_key == "support");
const wxString& selection = boost::any_cast<wxString>(value);
const bool supports_enable = selection == _("None") ? false : true;
new_conf.set_key_value("supports_enable", new ConfigOptionBool(supports_enable));
if (selection == _("Everywhere"))
new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(false));
else if (selection == _("Support on build plate only"))
new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(true));
}
2019-08-06 16:16:02 +00:00
tab->load_config(new_conf);
tab->update_dirty();
};
2019-02-07 13:44:05 +00:00
line = Line{ "", "" };
ConfigOptionDef support_def_sla = support_def;
support_def_sla.set_default_value(new ConfigOptionStrings{ "None" });
assert(support_def_sla.enum_labels[2] == L("For support enforcers only"));
support_def_sla.enum_labels.erase(support_def_sla.enum_labels.begin() + 2);
option = Option(support_def_sla, "support");
option.opt.full_width = true;
2019-08-06 16:16:02 +00:00
line.append_option(option);
line.append_widget(empty_widget);
m_og_sla->append_line(line);
line = Line{ "", "" };
2019-02-07 13:44:05 +00:00
2019-08-06 16:16:02 +00:00
ConfigOptionDef pad_def;
pad_def.label = L("Pad");
pad_def.type = coStrings;
pad_def.gui_type = "select_open";
pad_def.tooltip = L("Select what kind of pad do you need");
pad_def.enum_labels.push_back(L("None"));
pad_def.enum_labels.push_back(L("Below object"));
pad_def.enum_labels.push_back(L("Around object"));
pad_def.set_default_value(new ConfigOptionStrings{ "Below object" });
option = Option(pad_def, "pad");
option.opt.full_width = true;
2019-02-07 13:44:05 +00:00
line.append_option(option);
2019-08-06 16:16:02 +00:00
line.append_widget(empty_widget);
2019-02-07 13:44:05 +00:00
m_og_sla->append_line(line);
m_sizer = new wxBoxSizer(wxVERTICAL);
m_sizer->Add(m_og->sizer, 0, wxEXPAND);
2019-02-07 13:44:05 +00:00
m_sizer->Add(m_og_sla->sizer, 0, wxEXPAND);
}
wxSizer* FreqChangedParams::get_sizer()
{
return m_sizer;
}
void FreqChangedParams::Show(const bool is_fff)
{
const bool is_wdb_shown = m_wiping_dialog_button->IsShown();
m_og->Show(is_fff);
m_og_sla->Show(!is_fff);
2019-08-06 16:16:02 +00:00
// correct showing of the FreqChangedParams sizer when m_wiping_dialog_button is hidden
if (is_fff && !is_wdb_shown)
m_wiping_dialog_button->Hide();
}
ConfigOptionsGroup* FreqChangedParams::get_og(const bool is_fff)
{
return is_fff ? m_og.get() : m_og_sla.get();
}
2018-09-17 10:15:11 +00:00
// Sidebar / private
enum class ActionButtonType : int {
abReslice,
abExport,
abSendGCode
};
2018-09-17 10:15:11 +00:00
struct Sidebar::priv
{
2018-10-04 09:12:55 +00:00
Plater *plater;
2018-09-17 10:15:11 +00:00
wxScrolledWindow *scrolled;
wxPanel* presets_panel; // Used for MSW better layouts
2018-09-17 10:15:11 +00:00
ModeSizer *mode_sizer;
2018-09-17 10:15:11 +00:00
wxFlexGridSizer *sizer_presets;
PresetComboBox *combo_print;
std::vector<PresetComboBox*> combos_filament;
wxBoxSizer *sizer_filaments;
PresetComboBox *combo_sla_print;
2018-09-17 10:15:11 +00:00
PresetComboBox *combo_sla_material;
PresetComboBox *combo_printer;
wxBoxSizer *sizer_params;
FreqChangedParams *frequently_changed_parameters{ nullptr };
ObjectList *object_list{ nullptr };
ObjectManipulation *object_manipulation{ nullptr };
ObjectSettings *object_settings{ nullptr };
ObjectLayers *object_layers{ nullptr };
2018-09-17 10:15:11 +00:00
ObjectInfo *object_info;
SlicedInfo *sliced_info;
wxButton *btn_export_gcode;
wxButton *btn_reslice;
ScalableButton *btn_send_gcode;
ScalableButton *btn_remove_device;
ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected)
2018-10-04 09:12:55 +00:00
bool is_collapsed {false};
Search::OptionsSearcher searcher;
2018-10-04 09:12:55 +00:00
priv(Plater *plater) : plater(plater) {}
~priv();
void show_preset_comboboxes();
2018-09-17 10:15:11 +00:00
};
Sidebar::priv::~priv()
{
if (object_manipulation != nullptr)
delete object_manipulation;
if (object_settings != nullptr)
delete object_settings;
if (frequently_changed_parameters != nullptr)
delete frequently_changed_parameters;
if (object_layers != nullptr)
delete object_layers;
}
void Sidebar::priv::show_preset_comboboxes()
{
2019-02-22 08:38:56 +00:00
const bool showSLA = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA;
for (size_t i = 0; i < 4; ++i)
sizer_presets->Show(i, !showSLA);
for (size_t i = 4; i < 8; ++i) {
if (sizer_presets->IsShown(i) != showSLA)
sizer_presets->Show(i, showSLA);
}
frequently_changed_parameters->Show(!showSLA);
scrolled->GetParent()->Layout();
scrolled->Refresh();
}
2018-09-17 10:15:11 +00:00
// Sidebar / public
2018-10-04 09:12:55 +00:00
Sidebar::Sidebar(Plater *parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(40 * wxGetApp().em_unit(), -1)), p(new priv(parent))
2018-09-17 10:15:11 +00:00
{
2019-08-23 14:04:58 +00:00
p->scrolled = new wxScrolledWindow(this);
p->scrolled->SetScrollbars(0, 100, 1, 2);
SetFont(wxGetApp().normal_font());
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
// Sizer in the scrolled area
auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL);
p->scrolled->SetSizer(scrolled_sizer);
2018-09-17 10:15:11 +00:00
// Sizer with buttons for mode changing
2019-07-25 14:23:32 +00:00
p->mode_sizer = new ModeSizer(p->scrolled);
2018-09-17 10:15:11 +00:00
// The preset chooser
2019-02-07 13:44:05 +00:00
p->sizer_presets = new wxFlexGridSizer(10, 1, 1, 2);
p->sizer_presets->AddGrowableCol(0, 1);
p->sizer_presets->SetFlexibleDirection(wxBOTH);
bool is_msw = false;
#ifdef __WINDOWS__
p->scrolled->SetDoubleBuffered(true);
p->presets_panel = new wxPanel(p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
p->presets_panel->SetSizer(p->sizer_presets);
is_msw = true;
#else
2019-03-13 12:37:36 +00:00
p->presets_panel = p->scrolled;
#endif //__WINDOWS__
2018-09-17 10:15:11 +00:00
p->sizer_filaments = new wxBoxSizer(wxVERTICAL);
auto init_combo = [this](PresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) {
auto *text = new wxStaticText(p->presets_panel, wxID_ANY, label + " :");
text->SetFont(wxGetApp().small_font());
*combo = new PresetComboBox(p->presets_panel, preset_type);
2018-09-17 10:15:11 +00:00
auto combo_and_btn_sizer = new wxBoxSizer(wxHORIZONTAL);
combo_and_btn_sizer->Add(*combo, 1, wxEXPAND);
if ((*combo)->edit_btn)
2019-08-06 16:16:02 +00:00
combo_and_btn_sizer->Add((*combo)->edit_btn, 0, wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT,
2019-05-17 13:49:37 +00:00
int(0.3*wxGetApp().em_unit()));
2018-09-17 10:15:11 +00:00
auto *sizer_presets = this->p->sizer_presets;
auto *sizer_filaments = this->p->sizer_filaments;
2019-02-07 13:44:05 +00:00
sizer_presets->Add(text, 0, wxALIGN_LEFT | wxEXPAND | wxRIGHT, 4);
2018-09-17 10:15:11 +00:00
if (! filament) {
sizer_presets->Add(combo_and_btn_sizer, 0, wxEXPAND | wxBOTTOM, 1);
2018-09-17 10:15:11 +00:00
} else {
sizer_filaments->Add(combo_and_btn_sizer, 0, wxEXPAND | wxBOTTOM, 1);
(*combo)->set_extruder_idx(0);
2018-09-17 10:15:11 +00:00
sizer_presets->Add(sizer_filaments, 1, wxEXPAND);
}
};
p->combos_filament.push_back(nullptr);
init_combo(&p->combo_print, _L("Print settings"), Preset::TYPE_PRINT, false);
init_combo(&p->combos_filament[0], _L("Filament"), Preset::TYPE_FILAMENT, true);
init_combo(&p->combo_sla_print, _L("SLA print settings"), Preset::TYPE_SLA_PRINT, false);
init_combo(&p->combo_sla_material, _L("SLA material"), Preset::TYPE_SLA_MATERIAL, false);
init_combo(&p->combo_printer, _L("Printer"), Preset::TYPE_PRINTER, false);
2018-09-17 10:15:11 +00:00
2019-02-07 13:44:05 +00:00
const int margin_5 = int(0.5*wxGetApp().em_unit());// 5;
2018-09-17 10:15:11 +00:00
p->sizer_params = new wxBoxSizer(wxVERTICAL);
// Frequently changed parameters
2019-07-10 13:55:53 +00:00
p->frequently_changed_parameters = new FreqChangedParams(p->scrolled);
p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxTOP | wxBOTTOM, wxOSX ? 1 : margin_5);
2019-08-06 16:16:02 +00:00
2018-10-03 13:14:52 +00:00
// Object List
p->object_list = new ObjectList(p->scrolled);
2019-02-07 13:44:05 +00:00
p->sizer_params->Add(p->object_list->get_sizer(), 1, wxEXPAND);
2019-08-06 16:16:02 +00:00
// Object Manipulations
p->object_manipulation = new ObjectManipulation(p->scrolled);
p->object_manipulation->Hide();
2019-02-07 13:44:05 +00:00
p->sizer_params->Add(p->object_manipulation->get_sizer(), 0, wxEXPAND | wxTOP, margin_5);
2018-09-17 10:15:11 +00:00
// Frequently Object Settings
p->object_settings = new ObjectSettings(p->scrolled);
p->object_settings->Hide();
2019-02-07 13:44:05 +00:00
p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5);
2019-08-06 16:16:02 +00:00
// Object Layers
p->object_layers = new ObjectLayers(p->scrolled);
p->object_layers->Hide();
p->sizer_params->Add(p->object_layers->get_sizer(), 0, wxEXPAND | wxTOP, margin_5);
2018-09-17 10:15:11 +00:00
// Info boxes
p->object_info = new ObjectInfo(p->scrolled);
p->sliced_info = new SlicedInfo(p->scrolled);
2018-09-17 10:15:11 +00:00
// Sizer in the scrolled area
2019-02-07 13:44:05 +00:00
scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL/*RIGHT | wxBOTTOM | wxRIGHT, 5*/);
is_msw ?
2019-08-06 16:16:02 +00:00
scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | wxLEFT, margin_5) :
scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5);
2019-02-07 13:44:05 +00:00
scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND | wxLEFT, margin_5);
scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5);
scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5);
2018-09-17 10:15:11 +00:00
// Buttons underneath the scrolled area
// rescalable bitmap buttons "Send to printer" and "Remove device"
2019-12-10 16:31:27 +00:00
auto init_scalable_btn = [this](ScalableButton** btn, const std::string& icon_name, wxString tooltip = wxEmptyString)
{
2019-12-13 16:56:04 +00:00
#ifdef __APPLE__
int bmp_px_cnt = 16;
#else
int bmp_px_cnt = 32;
#endif //__APPLE__
ScalableBitmap bmp = ScalableBitmap(this, icon_name, bmp_px_cnt);
*btn = new ScalableButton(this, wxID_ANY, bmp, "", wxBU_EXACTFIT);
(*btn)->SetToolTip(tooltip);
(*btn)->Hide();
};
init_scalable_btn(&p->btn_send_gcode , "export_gcode", _L("Send to printer") + "\tCtrl+Shift+G");
init_scalable_btn(&p->btn_remove_device, "eject_sd" , _L("Remove device") + "\tCtrl+T");
init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export to SD card / Flash drive") + "\tCtrl+U");
// regular buttons "Slice now" and "Export G-code"
2019-12-10 16:31:27 +00:00
2019-12-13 16:56:04 +00:00
const int scaled_height = p->btn_remove_device->GetBitmapHeight() + 4;
auto init_btn = [this](wxButton **btn, wxString label, const int button_height) {
2019-08-06 16:16:02 +00:00
*btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition,
wxSize(-1, button_height), wxBU_EXACTFIT);
(*btn)->SetFont(wxGetApp().bold_font());
};
init_btn(&p->btn_export_gcode, _L("Export G-code") + dots , scaled_height);
init_btn(&p->btn_reslice , _L("Slice now") , scaled_height);
2019-12-10 16:31:27 +00:00
enable_buttons(false);
2018-09-17 10:15:11 +00:00
auto *btns_sizer = new wxBoxSizer(wxVERTICAL);
2019-12-10 16:31:27 +00:00
auto* complect_btns_sizer = new wxBoxSizer(wxHORIZONTAL);
complect_btns_sizer->Add(p->btn_export_gcode, 1, wxEXPAND);
2019-12-13 16:56:04 +00:00
complect_btns_sizer->Add(p->btn_send_gcode);
complect_btns_sizer->Add(p->btn_export_gcode_removable);
complect_btns_sizer->Add(p->btn_remove_device);
2019-12-10 16:31:27 +00:00
2019-02-07 13:44:05 +00:00
btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, margin_5);
2019-12-10 16:31:27 +00:00
btns_sizer->Add(complect_btns_sizer, 0, wxEXPAND | wxTOP, margin_5);
2018-09-17 10:15:11 +00:00
auto *sizer = new wxBoxSizer(wxVERTICAL);
2019-02-11 14:36:05 +00:00
sizer->Add(p->scrolled, 1, wxEXPAND);
2019-02-07 13:44:05 +00:00
sizer->Add(btns_sizer, 0, wxEXPAND | wxLEFT, margin_5);
2018-09-17 10:15:11 +00:00
SetSizer(sizer);
2018-10-04 09:12:55 +00:00
// Events
p->btn_export_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(false); });
p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&)
{
const bool export_gcode_after_slicing = wxGetKeyState(WXK_SHIFT);
if (export_gcode_after_slicing)
p->plater->export_gcode();
else
p->plater->reslice();
2019-08-06 16:16:02 +00:00
p->plater->select_view_3D("Preview");
});
2018-10-04 09:12:55 +00:00
p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); });
p->btn_remove_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); });
p->btn_export_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(true); });
2018-09-17 10:15:11 +00:00
}
Sidebar::~Sidebar() {}
void Sidebar::init_filament_combo(PresetComboBox **combo, const int extr_idx) {
*combo = new PresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT);
// # copy icons from first choice
// $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets;
(*combo)->set_extruder_idx(extr_idx);
auto combo_and_btn_sizer = new wxBoxSizer(wxHORIZONTAL);
combo_and_btn_sizer->Add(*combo, 1, wxEXPAND);
2019-05-17 14:05:32 +00:00
combo_and_btn_sizer->Add((*combo)->edit_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT,
2019-05-17 13:49:37 +00:00
int(0.3*wxGetApp().em_unit()));
auto /***/sizer_filaments = this->p->sizer_filaments;
sizer_filaments->Add(combo_and_btn_sizer, 1, wxEXPAND | wxBOTTOM, 1);
}
void Sidebar::remove_unused_filament_combos(const size_t current_extruder_count)
{
if (current_extruder_count >= p->combos_filament.size())
return;
auto sizer_filaments = this->p->sizer_filaments;
while (p->combos_filament.size() > current_extruder_count) {
const int last = p->combos_filament.size() - 1;
sizer_filaments->Remove(last);
(*p->combos_filament[last]).Destroy();
p->combos_filament.pop_back();
}
}
2019-04-10 07:56:32 +00:00
void Sidebar::update_all_preset_comboboxes()
{
PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology();
// Update the print choosers to only contain the compatible presets, update the dirty flags.
if (print_tech == ptFFF)
2019-12-03 03:48:01 +00:00
preset_bundle.prints.update_plater_ui(p->combo_print);
2019-04-10 07:56:32 +00:00
else {
2019-12-03 03:48:01 +00:00
preset_bundle.sla_prints.update_plater_ui(p->combo_sla_print);
preset_bundle.sla_materials.update_plater_ui(p->combo_sla_material);
2019-04-10 07:56:32 +00:00
}
// Update the printer choosers, update the dirty flags.
2019-12-03 03:48:01 +00:00
preset_bundle.printers.update_plater_ui(p->combo_printer);
2019-04-10 07:56:32 +00:00
// Update the filament choosers to only contain the compatible presets, update the color preview,
// update the dirty flags.
if (print_tech == ptFFF) {
for (size_t i = 0; i < p->combos_filament.size(); ++i)
2019-12-03 03:48:01 +00:00
preset_bundle.update_plater_filament_ui(i, p->combos_filament[i]);
2019-04-10 07:56:32 +00:00
}
}
2018-09-17 10:15:11 +00:00
void Sidebar::update_presets(Preset::Type preset_type)
{
2019-08-06 16:16:02 +00:00
PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
2019-02-22 08:38:56 +00:00
const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology();
2018-09-17 10:15:11 +00:00
switch (preset_type) {
2019-08-06 16:16:02 +00:00
case Preset::TYPE_FILAMENT:
{
const size_t extruder_cnt = print_tech != ptFFF ? 1 :
dynamic_cast<ConfigOptionFloats*>(preset_bundle.printers.get_edited_preset().config.option("nozzle_diameter"))->values.size();
const size_t filament_cnt = p->combos_filament.size() > extruder_cnt ? extruder_cnt : p->combos_filament.size();
if (filament_cnt == 1) {
2018-09-17 10:15:11 +00:00
// Single filament printer, synchronize the filament presets.
const std::string &name = preset_bundle.filaments.get_selected_preset_name();
preset_bundle.set_filament_preset(0, name);
2018-09-17 10:15:11 +00:00
}
for (size_t i = 0; i < filament_cnt; i++) {
2019-12-03 03:48:01 +00:00
preset_bundle.update_plater_filament_ui(i, p->combos_filament[i]);
2018-09-17 10:15:11 +00:00
}
2018-10-03 14:27:02 +00:00
2018-09-17 10:15:11 +00:00
break;
}
2018-09-17 10:15:11 +00:00
case Preset::TYPE_PRINT:
2019-12-03 03:48:01 +00:00
preset_bundle.prints.update_plater_ui(p->combo_print);
2018-09-17 10:15:11 +00:00
break;
case Preset::TYPE_SLA_PRINT:
2019-12-03 03:48:01 +00:00
preset_bundle.sla_prints.update_plater_ui(p->combo_sla_print);
break;
2018-09-17 10:15:11 +00:00
case Preset::TYPE_SLA_MATERIAL:
2019-12-03 03:48:01 +00:00
preset_bundle.sla_materials.update_plater_ui(p->combo_sla_material);
2018-09-17 10:15:11 +00:00
break;
2019-08-06 16:16:02 +00:00
case Preset::TYPE_PRINTER:
{
2019-04-10 07:56:32 +00:00
update_all_preset_comboboxes();
2019-08-06 16:16:02 +00:00
p->show_preset_comboboxes();
break;
}
2018-09-17 10:15:11 +00:00
default: break;
}
// Synchronize config.ini with the current selections.
2018-10-03 14:27:02 +00:00
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
2018-09-17 10:15:11 +00:00
}
void Sidebar::update_mode_sizer() const
{
p->mode_sizer->SetMode(m_mode);
}
void Sidebar::update_reslice_btn_tooltip() const
{
wxString tooltip = wxString("Slice") + " [" + GUI::shortkey_ctrl_prefix() + "R]";
if (m_mode != comSimple)
tooltip += wxString("\n") + _L("Hold Shift to Slice & Export G-code");
p->btn_reslice->SetToolTip(tooltip);
}
2019-08-06 16:16:02 +00:00
void Sidebar::msw_rescale()
{
SetMinSize(wxSize(40 * wxGetApp().em_unit(), -1));
p->mode_sizer->msw_rescale();
// Rescale preset comboboxes in respect to the current em_unit ...
for (PresetComboBox* combo : std::vector<PresetComboBox*> { p->combo_print,
p->combo_sla_print,
p->combo_sla_material,
p->combo_printer } )
combo->msw_rescale();
for (PresetComboBox* combo : p->combos_filament)
combo->msw_rescale();
// ... then refill them and set min size to correct layout of the sidebar
update_all_preset_comboboxes();
p->frequently_changed_parameters->msw_rescale();
p->object_list->msw_rescale();
p->object_manipulation->msw_rescale();
p->object_settings->msw_rescale();
p->object_layers->msw_rescale();
p->object_info->msw_rescale();
p->btn_send_gcode->msw_rescale();
p->btn_remove_device->msw_rescale();
p->btn_export_gcode_removable->msw_rescale();
const int scaled_height = p->btn_remove_device->GetBitmap().GetHeight() + 4;
p->btn_export_gcode->SetMinSize(wxSize(-1, scaled_height));
p->btn_reslice ->SetMinSize(wxSize(-1, scaled_height));
p->scrolled->Layout();
}
void Sidebar::search()
{
p->searcher.search();
}
void Sidebar::jump_to_option(size_t selected)
{
const Search::Option& opt = p->searcher.get_option(selected);
wxGetApp().get_tab(opt.type)->activate_option(boost::nowide::narrow(opt.opt_key), boost::nowide::narrow(opt.category));
// Switch to the Settings NotePad
wxGetApp().mainframe->select_tab();
}
ObjectManipulation* Sidebar::obj_manipul()
{
return p->object_manipulation;
}
2018-10-05 21:29:15 +00:00
ObjectList* Sidebar::obj_list()
2018-10-03 13:14:52 +00:00
{
2018-10-05 21:29:15 +00:00
return p->object_list;
2018-10-03 13:14:52 +00:00
}
ObjectSettings* Sidebar::obj_settings()
{
return p->object_settings;
}
ObjectLayers* Sidebar::obj_layers()
{
return p->object_layers;
}
wxScrolledWindow* Sidebar::scrolled_panel()
{
return p->scrolled;
}
wxPanel* Sidebar::presets_panel()
{
return p->presets_panel;
}
ConfigOptionsGroup* Sidebar::og_freq_chng_params(const bool is_fff)
2018-10-05 21:29:15 +00:00
{
return p->frequently_changed_parameters->get_og(is_fff);
2018-10-03 13:14:52 +00:00
}
wxButton* Sidebar::get_wiping_dialog_button()
{
return p->frequently_changed_parameters->get_wiping_dialog_button();
}
void Sidebar::update_objects_list_extruder_column(size_t extruders_count)
{
p->object_list->update_objects_list_extruder_column(extruders_count);
}
void Sidebar::show_info_sizer()
2018-10-05 21:29:15 +00:00
{
if (!p->plater->is_single_full_object_selection() ||
m_mode < comExpert ||
p->plater->model().objects.empty()) {
2018-11-01 11:33:56 +00:00
p->object_info->Show(false);
return;
}
int obj_idx = p->plater->get_selected_object_idx();
const ModelObject* model_object = p->plater->model().objects[obj_idx];
// hack to avoid crash when deleting the last object on the bed
if (model_object->volumes.empty())
{
p->object_info->Show(false);
return;
}
bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
double koef = imperial_units ? ObjectManipulation::mm_to_in : 1.0f;
auto size = model_object->bounding_box().size();
p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0)*koef, size(1)*koef, size(2)*koef));
2018-11-01 11:33:56 +00:00
p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast<int>(model_object->materials_count())));
const auto& stats = model_object->get_object_stl_stats();//model_object->volumes.front()->mesh.stl.stats;
p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume*pow(koef,3)));
p->object_info->info_facets->SetLabel(wxString::Format(_L("%d (%d shells)"), static_cast<int>(model_object->facets_count()), stats.number_of_parts));
2018-11-01 11:33:56 +00:00
int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
stats.facets_added + stats.facets_reversed + stats.backwards_edges;
if (errors > 0) {
wxString tooltip = wxString::Format(_L("Auto-repaired (%d errors)"), errors);
2018-11-01 11:33:56 +00:00
p->object_info->info_manifold->SetLabel(tooltip);
2019-08-06 16:16:02 +00:00
tooltip += ":\n" + wxString::Format(_L("%d degenerate facets, %d edges fixed, %d facets removed, "
"%d facets added, %d facets reversed, %d backwards edges"),
2018-11-01 11:33:56 +00:00
stats.degenerate_facets, stats.edges_fixed, stats.facets_removed,
stats.facets_added, stats.facets_reversed, stats.backwards_edges);
p->object_info->showing_manifold_warning_icon = true;
p->object_info->info_manifold->SetToolTip(tooltip);
p->object_info->manifold_warning_icon->SetToolTip(tooltip);
2019-08-06 16:16:02 +00:00
}
2018-11-01 11:33:56 +00:00
else {
p->object_info->info_manifold->SetLabel(_L("Yes"));
2018-11-01 11:33:56 +00:00
p->object_info->showing_manifold_warning_icon = false;
p->object_info->info_manifold->SetToolTip("");
p->object_info->manifold_warning_icon->SetToolTip("");
}
p->object_info->show_sizer(true);
if (p->plater->printer_technology() == ptSLA) {
for (auto item: p->object_info->sla_hidden_items)
item->Show(false);
}
}
void Sidebar::update_sliced_info_sizer()
{
if (p->sliced_info->IsShown(size_t(0)))
{
if (p->plater->printer_technology() == ptSLA)
{
const SLAPrintStatistics& ps = p->plater->sla_print().print_statistics();
wxString new_label = _L("Used Material (ml)") + ":";
const bool is_supports = ps.support_used_material > 0.0;
if (is_supports)
new_label += format_wxstr("\n - %s\n - %s", _L("object(s)"), _L("supports and pad"));
wxString info_text = is_supports ?
2019-02-18 11:28:58 +00:00
wxString::Format("%.2f \n%.2f \n%.2f", (ps.objects_used_material + ps.support_used_material) / 1000,
ps.objects_used_material / 1000,
ps.support_used_material / 1000) :
2019-02-18 12:18:47 +00:00
wxString::Format("%.2f", (ps.objects_used_material + ps.support_used_material) / 1000);
p->sliced_info->SetTextAndShow(siMateril_unit, info_text, new_label);
wxString str_total_cost = "N/A";
DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_SLA_MATERIAL)->get_config();
if (cfg->option("bottle_cost")->getFloat() > 0.0 &&
cfg->option("bottle_volume")->getFloat() > 0.0)
{
double material_cost = cfg->option("bottle_cost")->getFloat() /
cfg->option("bottle_volume")->getFloat();
str_total_cost = wxString::Format("%.3f", material_cost*(ps.objects_used_material + ps.support_used_material) / 1000);
}
p->sliced_info->SetTextAndShow(siCost, str_total_cost, "Cost");
wxString t_est = std::isnan(ps.estimated_print_time) ? "N/A" : get_time_dhms(float(ps.estimated_print_time));
p->sliced_info->SetTextAndShow(siEstimatedTime, t_est, _L("Estimated printing time") + ":");
// Hide non-SLA sliced info parameters
p->sliced_info->SetTextAndShow(siFilament_m, "N/A");
p->sliced_info->SetTextAndShow(siFilament_mm3, "N/A");
p->sliced_info->SetTextAndShow(siFilament_g, "N/A");
p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, "N/A");
}
else
2019-08-06 16:16:02 +00:00
{
const PrintStatistics& ps = p->plater->fff_print().print_statistics();
const bool is_wipe_tower = ps.total_wipe_tower_filament > 0;
bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
double koef = imperial_units ? ObjectManipulation::in_to_mm : 1000.0;
wxString new_label = imperial_units ? _L("Used Filament (in)") : _L("Used Filament (m)");
if (is_wipe_tower)
new_label += format_wxstr(":\n - %1%\n - %2%", _L("objects"), _L("wipe tower"));
wxString info_text = is_wipe_tower ?
wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / /*1000*/koef,
(ps.total_used_filament - ps.total_wipe_tower_filament) / /*1000*/koef,
ps.total_wipe_tower_filament / /*1000*/koef) :
wxString::Format("%.2f", ps.total_used_filament / /*1000*/koef);
p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label);
koef = imperial_units ? pow(ObjectManipulation::mm_to_in, 3) : 1.0f;
new_label = imperial_units ? _L("Used Filament (in³)") : _L("Used Filament (mm³)");
info_text = wxString::Format("%.2f", imperial_units ? ps.total_extruded_volume * koef : ps.total_extruded_volume);
p->sliced_info->SetTextAndShow(siFilament_mm3, info_text, new_label);
p->sliced_info->SetTextAndShow(siFilament_g, ps.total_weight == 0.0 ? "N/A" : wxString::Format("%.2f", ps.total_weight));
new_label = _L("Cost");
if (is_wipe_tower)
new_label += format_wxstr(":\n - %1%\n - %2%", _L("objects"), _L("wipe tower"));
2019-08-06 16:16:02 +00:00
info_text = ps.total_cost == 0.0 ? "N/A" :
is_wipe_tower ?
wxString::Format("%.2f \n%.2f \n%.2f", ps.total_cost,
2019-08-06 16:16:02 +00:00
(ps.total_cost - ps.total_wipe_tower_cost),
ps.total_wipe_tower_cost) :
wxString::Format("%.2f", ps.total_cost);
p->sliced_info->SetTextAndShow(siCost, info_text, new_label);
if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A")
p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A");
else {
new_label = _L("Estimated printing time") +":";
info_text = "";
wxString str_color = _L("Color");
wxString str_pause = _L("Pause");
auto fill_labels = [str_color, str_pause](const std::vector<std::pair<CustomGcodeType, std::string>>& times,
wxString& new_label, wxString& info_text)
{
int color_change_count = 0;
for (auto time : times)
if (time.first == cgtColorChange)
color_change_count++;
for (int i = (int)times.size() - 1; i >= 0; --i)
2019-07-08 06:40:20 +00:00
{
if (i == 0 || times[i - 1].first == cgtPausePrint)
new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count);
else if (times[i - 1].first == cgtColorChange)
new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count--);
if (i != (int)times.size() - 1 && times[i].first == cgtPausePrint)
new_label += format_wxstr(" -> %1%", str_pause);
info_text += format_wxstr("\n%1%", times[i].second);
2019-07-08 06:40:20 +00:00
}
};
if (ps.estimated_normal_print_time != "N/A") {
new_label += format_wxstr("\n - %1%", _L("normal mode"));
info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time);
fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text);
}
if (ps.estimated_silent_print_time != "N/A") {
new_label += format_wxstr("\n - %1%", _L("stealth mode"));
info_text += format_wxstr("\n%1%", ps.estimated_silent_print_time);
fill_labels(ps.estimated_silent_custom_gcode_print_times, new_label, info_text);
}
p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label);
}
// if there is a wipe tower, insert number of toolchanges info into the array:
p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, is_wipe_tower ? wxString::Format("%.d", ps.total_toolchanges) : "N/A");
// Hide non-FFF sliced info parameters
p->sliced_info->SetTextAndShow(siMateril_unit, "N/A");
2019-08-06 16:16:02 +00:00
}
}
}
void Sidebar::show_sliced_info_sizer(const bool show)
{
wxWindowUpdateLocker freeze_guard(this);
p->sliced_info->Show(show);
if (show)
update_sliced_info_sizer();
Layout();
p->scrolled->Refresh();
2018-10-05 21:29:15 +00:00
}
2018-10-08 17:14:55 +00:00
void Sidebar::enable_buttons(bool enable)
{
p->btn_reslice->Enable(enable);
p->btn_export_gcode->Enable(enable);
p->btn_send_gcode->Enable(enable);
p->btn_remove_device->Enable(enable);
p->btn_export_gcode_removable->Enable(enable);
2018-10-08 17:14:55 +00:00
}
bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->Show(show); }
bool Sidebar::show_export(bool show) const { return p->btn_export_gcode->Show(show); }
bool Sidebar::show_send(bool show) const { return p->btn_send_gcode->Show(show); }
bool Sidebar::show_disconnect(bool show) const { return p->btn_remove_device->Show(show); }
bool Sidebar::show_export_removable(bool show)const { return p->btn_export_gcode_removable->Show(show); }
2018-10-15 08:53:47 +00:00
2018-10-09 10:41:05 +00:00
bool Sidebar::is_multifilament()
{
return p->combos_filament.size() > 1;
2018-10-09 10:41:05 +00:00
}
static std::vector<Search::InputInfo> get_search_inputs(ConfigOptionMode mode)
{
std::vector<Search::InputInfo> ret {};
auto& tabs_list = wxGetApp().tabs_list;
auto print_tech = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology();
for (auto tab : tabs_list)
if (tab->supports_printer_technology(print_tech))
ret.emplace_back(Search::InputInfo {tab->get_config(), tab->type(), mode});
return ret;
}
void Sidebar::update_searcher()
{
p->searcher.init(get_search_inputs(m_mode));
}
void Sidebar::update_mode()
{
m_mode = wxGetApp().get_mode();
update_reslice_btn_tooltip();
update_mode_sizer();
update_searcher();
wxWindowUpdateLocker noUpdates(this);
p->object_list->get_sizer()->Show(m_mode > comSimple);
p->object_list->unselect_objects();
p->object_list->update_selections();
p->object_list->update_object_menu();
2019-08-06 16:16:02 +00:00
Layout();
}
bool Sidebar::is_collapsed() { return p->is_collapsed; }
void Sidebar::collapse(bool collapse)
{
p->is_collapsed = collapse;
this->Show(!collapse);
p->plater->Layout();
// save collapsing state to the AppConfig
wxGetApp().app_config->set("collapsed_sidebar", collapse ? "1" : "0");
}
void Sidebar::update_ui_from_settings()
{
p->object_manipulation->update_ui_from_settings();
show_info_sizer();
update_sliced_info_sizer();
}
std::vector<PresetComboBox*>& Sidebar::combos_filament()
{
return p->combos_filament;
}
Search::OptionsSearcher& Sidebar::get_searcher()
{
return p->searcher;
}
std::string& Sidebar::get_search_line()
{
return p->searcher.search_string();
}
2018-09-17 10:15:11 +00:00
// Plater::DropTarget
class PlaterDropTarget : public wxFileDropTarget
{
public:
2019-08-06 16:16:02 +00:00
PlaterDropTarget(Plater *plater) : plater(plater) { this->SetDefaultAction(wxDragCopy); }
2018-09-17 10:15:11 +00:00
virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames);
private:
Plater *plater;
2018-10-08 17:14:55 +00:00
static const std::regex pattern_drop;
2018-09-17 10:15:11 +00:00
};
2018-10-22 09:52:13 +00:00
const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)", std::regex::icase);
2018-10-08 17:14:55 +00:00
2018-09-17 10:15:11 +00:00
bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames)
{
2018-10-08 17:14:55 +00:00
std::vector<fs::path> paths;
for (const auto &filename : filenames) {
2019-01-02 14:11:05 +00:00
fs::path path(into_path(filename));
2018-10-08 17:14:55 +00:00
if (std::regex_match(path.string(), pattern_drop)) {
paths.push_back(std::move(path));
} else {
return false;
}
}
2019-08-06 16:16:02 +00:00
wxString snapshot_label;
assert(! paths.empty());
if (paths.size() == 1) {
snapshot_label = _L("Load File");
2019-08-06 16:16:02 +00:00
snapshot_label += ": ";
snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
} else {
snapshot_label = _L("Load Files");
2019-08-06 16:16:02 +00:00
snapshot_label += ": ";
snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
for (size_t i = 1; i < paths.size(); ++ i) {
snapshot_label += ", ";
snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str());
}
}
Plater::TakeSnapshot snapshot(plater, snapshot_label);
// FIXME: when drag and drop is done on a .3mf or a .amf file we should clear the plater for consistence with the open project command
// (the following call to plater->load_files() will load the config data, if present)
std::vector<size_t> res = plater->load_files(paths);
// because right now the plater is not cleared, we set the project file (from the latest imported .3mf or .amf file)
// only if not set yet
// if res is empty no data has been loaded
if (!res.empty() && plater->get_project_filename().empty())
{
for (std::vector<fs::path>::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it)
{
std::string filename = (*it).filename().string();
if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf"))
{
plater->set_project_filename(from_path(*it));
break;
}
}
}
2018-10-08 17:14:55 +00:00
return true;
2018-09-17 10:15:11 +00:00
}
// Plater / private
struct Plater::priv
{
// PIMPL back pointer ("Q-Pointer")
Plater *q;
MainFrame *main_frame;
// Object popup menu
MenuWithSeparators object_menu;
// Part popup menu
MenuWithSeparators part_menu;
// SLA-Object popup menu
MenuWithSeparators sla_object_menu;
// Default popup menu (when nothing is selected on 3DScene)
MenuWithSeparators default_menu;
// Removed/Prepended Items according to the view mode
std::vector<wxMenuItem*> items_increase;
std::vector<wxMenuItem*> items_decrease;
std::vector<wxMenuItem*> items_set_number_of_copies;
enum MenuIdentifier {
miObjectFFF=0,
miObjectSLA
};
2018-09-17 10:15:11 +00:00
// Data
Slic3r::DynamicPrintConfig *config; // FIXME: leak?
Slic3r::Print fff_print;
2019-08-06 16:16:02 +00:00
Slic3r::SLAPrint sla_print;
Slic3r::Model model;
PrinterTechnology printer_technology = ptFFF;
Slic3r::GCodePreviewData gcode_preview_data;
2018-09-17 10:15:11 +00:00
// GUI elements
wxSizer* panel_sizer{ nullptr };
wxPanel* current_panel{ nullptr };
std::vector<wxPanel*> panels;
2018-09-17 10:15:11 +00:00
Sidebar *sidebar;
Bed3D bed;
Camera camera;
Mouse3DController mouse3d_controller;
View3D* view3D;
2018-12-17 09:55:14 +00:00
GLToolbar view_toolbar;
Preview *preview;
BackgroundSlicingProcess background_process;
2019-06-26 07:48:52 +00:00
bool suppressed_backround_processing_update { false };
2019-08-06 16:16:02 +00:00
// Jobs defined inside the group class will be managed so that only one can
// run at a time. Also, the background process will be stopped if a job is
// started. It is up the the plater to ensure that the background slicing
// can't be restarted while a ui job is still running.
class Jobs: public ExclusiveJobGroup
{
priv *m;
2020-04-23 17:45:55 +00:00
size_t m_arrange_id, m_rotoptimize_id, m_sla_import_id;
void before_start() override { m->background_process.stop(); }
public:
Jobs(priv *_m) : m(_m)
{
m_arrange_id = add_job(std::make_unique<ArrangeJob>(m->statusbar(), m->q));
m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->statusbar(), m->q));
2020-04-23 17:45:55 +00:00
m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m->statusbar(), m->q));
}
void arrange()
{
m->take_snapshot(_(L("Arrange")));
start(m_arrange_id);
}
void optimize_rotation()
{
m->take_snapshot(_(L("Optimize Rotation")));
start(m_rotoptimize_id);
}
2020-04-23 17:45:55 +00:00
void import_sla_arch()
{
m->take_snapshot(_(L("Import SLA archive")));
start(m_sla_import_id);
}
} m_ui_jobs;
bool delayed_scene_refresh;
std::string delayed_error_message;
wxTimer background_process_timer;
2018-09-17 10:15:11 +00:00
std::string label_btn_export;
std::string label_btn_send;
2018-09-17 10:15:11 +00:00
static const std::regex pattern_bundle;
static const std::regex pattern_3mf;
static const std::regex pattern_zip_amf;
2019-01-03 11:04:14 +00:00
static const std::regex pattern_any_amf;
2019-01-18 15:01:43 +00:00
static const std::regex pattern_prusa;
2018-09-17 10:15:11 +00:00
priv(Plater *q, MainFrame *main_frame);
~priv();
2018-09-17 10:15:11 +00:00
enum class UpdateParams {
FORCE_FULL_SCREEN_REFRESH = 1,
FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
POSTPONE_VALIDATION_ERROR_MESSAGE = 4,
};
void update(unsigned int flags = 0);
2018-10-17 10:59:58 +00:00
void select_view(const std::string& direction);
void select_view_3D(const std::string& name);
void select_next_view_3D();
bool is_preview_shown() const { return current_panel == preview; }
bool is_preview_loaded() const { return preview->is_loaded(); }
bool is_view3D_shown() const { return current_panel == view3D; }
bool are_view3D_labels_shown() const { return (current_panel == view3D) && view3D->get_canvas3d()->are_labels_shown(); }
void show_view3D_labels(bool show) { if (current_panel == view3D) view3D->get_canvas3d()->show_labels(show); }
bool is_sidebar_collapsed() const { return sidebar->is_collapsed(); }
2020-04-29 13:58:57 +00:00
void collapse_sidebar(bool show) { sidebar->collapse(show); }
#if ENABLE_SLOPE_RENDERING
bool is_view3D_slope_shown() const { return (current_panel == view3D) && view3D->get_canvas3d()->is_slope_shown(); }
void show_view3D_slope(bool show) { if (current_panel == view3D) view3D->get_canvas3d()->show_slope(show); }
bool is_view3D_layers_editing_enabled() const { return (current_panel == view3D) && view3D->get_canvas3d()->is_layers_editing_enabled(); }
#endif // ENABLE_SLOPE_RENDERING
void set_current_canvas_as_dirty();
GLCanvas3D* get_current_canvas3D();
void unbind_canvas_event_handlers();
void reset_canvas_volumes();
bool init_view_toolbar();
void reset_all_gizmos();
2018-09-17 10:15:11 +00:00
void update_ui_from_settings();
void update_main_toolbar_tooltips();
std::shared_ptr<ProgressStatusBar> statusbar();
2018-10-01 14:48:08 +00:00
std::string get_config(const std::string &key) const;
2018-10-03 14:18:23 +00:00
BoundingBoxf bed_shape_bb() const;
BoundingBox scaled_bed_shape_bb() const;
2019-08-06 16:16:02 +00:00
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool used_inches = false);
2018-10-01 14:48:08 +00:00
std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
wxString get_export_file(GUI::FileType file_type);
2018-10-04 09:12:55 +00:00
const Selection& get_selection() const;
Selection& get_selection();
int get_selected_object_idx() const;
int get_selected_volume_idx() const;
2018-10-04 09:12:55 +00:00
void selection_changed();
2018-10-08 17:14:55 +00:00
void object_list_changed();
2018-10-04 09:12:55 +00:00
2018-11-21 14:28:35 +00:00
void select_all();
void deselect_all();
2018-10-08 17:14:55 +00:00
void remove(size_t obj_idx);
void delete_object_from_model(size_t obj_idx);
2018-10-04 09:12:55 +00:00
void reset();
void mirror(Axis axis);
void split_object();
2018-10-24 10:55:38 +00:00
void split_volume();
void scale_selection_to_fit_print_volume();
// Return the active Undo/Redo stack. It may be either the main stack or the Gimzo stack.
Slic3r::UndoRedo::Stack& undo_redo_stack() { assert(m_undo_redo_stack_active != nullptr); return *m_undo_redo_stack_active; }
Slic3r::UndoRedo::Stack& undo_redo_stack_main() { return m_undo_redo_stack_main; }
void enter_gizmos_stack();
void leave_gizmos_stack();
2019-08-06 16:16:02 +00:00
void take_snapshot(const std::string& snapshot_name);
void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); }
2019-07-09 18:45:00 +00:00
int get_active_snapshot_index();
void undo();
void redo();
void undo_redo_to(size_t time_to_load);
void suppress_snapshots() { this->m_prevent_snapshots++; }
void allow_snapshots() { this->m_prevent_snapshots--; }
bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; }
void update_print_volume_state();
void schedule_background_process();
// Update background processing thread from the current config and Model.
enum UpdateBackgroundProcessReturnState {
// update_background_process() reports, that the Print / SLAPrint was updated in a way,
// that the background process was invalidated and it needs to be re-run.
UPDATE_BACKGROUND_PROCESS_RESTART = 1,
// update_background_process() reports, that the Print / SLAPrint was updated in a way,
// that a scene needs to be refreshed (you should call _3DScene::reload_scene(canvas3Dwidget, false))
2019-08-06 16:16:02 +00:00
UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE = 2,
// update_background_process() reports, that the Print / SLAPrint is invalid, and the error message
// was sent to the status line.
UPDATE_BACKGROUND_PROCESS_INVALID = 4,
// Restart even if the background processing is disabled.
UPDATE_BACKGROUND_PROCESS_FORCE_RESTART = 8,
// Restart for G-code (or SLA zip) export or upload.
UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT = 16,
};
// returns bit mask of UpdateBackgroundProcessReturnState
unsigned int update_background_process(bool force_validation = false, bool postpone_error_messages = false);
// Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
bool restart_background_process(unsigned int state);
2019-08-06 16:16:02 +00:00
// returns bit mask of UpdateBackgroundProcessReturnState
unsigned int update_restart_background_process(bool force_scene_update, bool force_preview_update);
void show_delayed_error_message() {
if (!this->delayed_error_message.empty()) {
std::string msg = std::move(this->delayed_error_message);
this->delayed_error_message.clear();
GUI::show_error(this->q, msg);
}
}
void export_gcode(fs::path output_path, bool output_path_on_removable_media, PrintHostJob upload_job);
void reload_from_disk();
void reload_all_from_disk();
void fix_through_netfabb(const int obj_idx, const int vol_idx = -1);
2018-09-17 10:15:11 +00:00
void set_current_panel(wxPanel* panel);
void on_select_preset(wxCommandEvent&);
void on_slicing_update(SlicingStatusEvent&);
void on_slicing_completed(wxCommandEvent&);
void on_process_completed(wxCommandEvent&);
2018-09-17 10:15:11 +00:00
void on_layer_editing_toggled(bool enable);
void on_action_add(SimpleEvent&);
2018-10-24 10:55:38 +00:00
void on_action_split_objects(SimpleEvent&);
void on_action_split_volumes(SimpleEvent&);
2018-10-04 09:12:55 +00:00
void on_action_layersediting(SimpleEvent&);
2018-10-03 12:25:35 +00:00
2018-10-16 14:04:19 +00:00
void on_object_select(SimpleEvent&);
void on_right_click(RBtnEvent&);
void on_wipetower_moved(Vec3dEvent&);
void on_wipetower_rotated(Vec3dEvent&);
void on_update_geometry(Vec3dsEvent<2>&);
void on_3dcanvas_mouse_dragging_finished(SimpleEvent&);
void update_object_menu();
void show_action_buttons(const bool is_ready_to_slice) const;
// Set the bed shape to a single closed 2D polygon(array of two element arrays),
// triangulate the bed and store the triangles into m_bed.m_triangles,
// fills the m_bed.m_grid_lines and sets m_bed.m_origin.
// Sets m_bed.m_polygon to limit the object placement.
2019-07-24 12:02:36 +00:00
void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model);
bool can_delete() const;
bool can_delete_all() const;
bool can_increase_instances() const;
bool can_decrease_instances() const;
bool can_split_to_objects() const;
bool can_split_to_volumes() const;
bool can_arrange() const;
bool can_layers_editing() const;
bool can_fix_through_netfabb() const;
bool can_set_instance_to_object() const;
bool can_mirror() const;
bool can_reload_from_disk() const;
void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
void generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
void msw_rescale_object_menu();
// returns the path to project file with the given extension (none if extension == wxEmptyString)
// extension should contain the leading dot, i.e.: ".3mf"
wxString get_project_filename(const wxString& extension = wxEmptyString) const;
void set_project_filename(const wxString& filename);
// Caching last value of show_action_buttons parameter for show_action_buttons(), so that a callback which does not know this state will not override it.
mutable bool ready_to_slice = { false };
// Flag indicating that the G-code export targets a removable device, therefore the show_action_buttons() needs to be called at any case when the background processing finishes.
bool writing_to_removable_device = { false };
bool inside_snapshot_capture() { return m_prevent_snapshots != 0; }
private:
bool init_object_menu();
bool init_common_menu(wxMenu* menu, const bool is_part = false);
bool complit_init_object_menu();
bool complit_init_sla_object_menu();
bool complit_init_part_menu();
bool can_split() const;
bool layers_height_allowed() const;
2018-12-06 14:40:41 +00:00
void update_fff_scene();
void update_sla_scene();
2019-08-06 16:16:02 +00:00
void undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator it_snapshot);
void update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool temp_snapshot_was_taken = false);
// path to project file stored with no extension
wxString m_project_filename;
Slic3r::UndoRedo::Stack m_undo_redo_stack_main;
Slic3r::UndoRedo::Stack m_undo_redo_stack_gizmos;
Slic3r::UndoRedo::Stack *m_undo_redo_stack_active = &m_undo_redo_stack_main;
2019-08-06 16:16:02 +00:00
int m_prevent_snapshots = 0; /* Used for avoid of excess "snapshoting".
* Like for "delete selected" or "set numbers of copies"
2019-08-06 16:16:02 +00:00
* we should call tack_snapshot just ones
* instead of calls for each action separately
* */
std::string m_last_fff_printer_profile_name;
std::string m_last_sla_printer_profile_name;
2018-09-17 10:15:11 +00:00
};
2018-10-22 09:52:13 +00:00
const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase);
const std::regex Plater::priv::pattern_3mf(".*3mf", std::regex::icase);
const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::icase);
2019-01-03 11:04:14 +00:00
const std::regex Plater::priv::pattern_any_amf(".*[.](amf|amf[.]xml|zip[.]amf)", std::regex::icase);
2019-01-18 15:01:43 +00:00
const std::regex Plater::priv::pattern_prusa(".*prusa", std::regex::icase);
2019-01-03 11:04:14 +00:00
2018-10-18 13:13:38 +00:00
Plater::priv::priv(Plater *q, MainFrame *main_frame)
: q(q)
, main_frame(main_frame)
, config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({
"bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance",
2018-09-17 10:15:11 +00:00
"brim_width", "variable_layer_height", "serial_port", "serial_speed", "host_type", "print_host",
"printhost_apikey", "printhost_cafile", "nozzle_diameter", "single_extruder_multi_material",
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle",
2019-01-21 09:06:51 +00:00
"extruder_colour", "filament_colour", "max_print_height", "printer_model", "printer_technology",
// These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor.
"layer_height", "first_layer_height", "min_layer_height", "max_layer_height",
"brim_width", "perimeters", "perimeter_extruder", "fill_density", "infill_extruder", "top_solid_layers",
"support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers"
2018-10-18 13:13:38 +00:00
}))
, sidebar(new Sidebar(q))
, m_ui_jobs(this)
, delayed_scene_refresh(false)
, view_toolbar(GLToolbar::Radio, "View")
, m_project_filename(wxEmptyString)
2018-09-17 10:15:11 +00:00
{
2019-08-06 16:16:02 +00:00
this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font());
background_process.set_fff_print(&fff_print);
2019-08-06 16:16:02 +00:00
background_process.set_sla_print(&sla_print);
2018-09-17 10:15:11 +00:00
background_process.set_gcode_preview_data(&gcode_preview_data);
background_process.set_thumbnail_cb([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
{
std::packaged_task<void(ThumbnailsList&, const Vec2ds&, bool, bool, bool, bool)> task([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) {
generate_thumbnails(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background);
});
std::future<void> result = task.get_future();
wxTheApp->CallAfter([&]() { task(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background); });
result.wait();
});
background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
2018-09-17 10:15:11 +00:00
background_process.set_finished_event(EVT_PROCESS_COMPLETED);
2019-08-06 16:16:02 +00:00
// Default printer technology for default config.
background_process.select_technology(this->printer_technology);
2019-12-03 03:48:01 +00:00
// Register progress callback from the Print class to the Plater.
auto statuscb = [this](const Slic3r::PrintBase::SlicingStatus &status) {
wxQueueEvent(this->q, new Slic3r::SlicingStatusEvent(EVT_SLICING_UPDATE, 0, status));
};
fff_print.set_status_callback(statuscb);
sla_print.set_status_callback(statuscb);
this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this);
2018-09-17 10:15:11 +00:00
view3D = new View3D(q, &model, config, &background_process);
preview = new Preview(q, &model, config, &background_process, &gcode_preview_data, [this]() { schedule_background_process(); });
#ifdef __APPLE__
// set default view_toolbar icons size equal to GLGizmosManager::Default_Icons_Size
2020-03-02 12:09:12 +00:00
view_toolbar.set_icons_size(GLGizmosManager::Default_Icons_Size);
#endif // __APPLE__
panels.push_back(view3D);
panels.push_back(preview);
2018-09-17 10:15:11 +00:00
this->background_process_timer.SetOwner(this->q, 0);
2019-06-26 07:48:52 +00:00
this->q->Bind(wxEVT_TIMER, [this](wxTimerEvent &evt)
{
if (!this->suppressed_backround_processing_update)
this->update_restart_background_process(false, false);
});
2018-09-17 10:15:11 +00:00
update();
auto *hsizer = new wxBoxSizer(wxHORIZONTAL);
panel_sizer = new wxBoxSizer(wxHORIZONTAL);
panel_sizer->Add(view3D, 1, wxEXPAND | wxALL, 0);
panel_sizer->Add(preview, 1, wxEXPAND | wxALL, 0);
hsizer->Add(panel_sizer, 1, wxEXPAND | wxALL, 0);
2018-09-17 10:15:11 +00:00
hsizer->Add(sidebar, 0, wxEXPAND | wxLEFT | wxRIGHT, 0);
q->SetSizer(hsizer);
init_object_menu();
2018-09-17 10:15:11 +00:00
// Events:
// Preset change event
sidebar->Bind(wxEVT_COMBOBOX, &priv::on_select_preset, this);
2018-11-01 11:33:56 +00:00
sidebar->Bind(EVT_OBJ_LIST_OBJECT_SELECT, [this](wxEvent&) { priv::selection_changed(); });
sidebar->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
2018-11-01 11:33:56 +00:00
wxGLCanvas* view3D_canvas = view3D->get_wxglcanvas();
// 3DScene events:
view3D_canvas->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
view3D_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this);
view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this);
view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); });
view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); });
view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
2019-08-06 16:16:02 +00:00
view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int> &evt)
{ if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
view3D_canvas->Bind(EVT_GLCANVAS_FORCE_UPDATE, [this](SimpleEvent&) { update(); });
view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this);
view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this);
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); });
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); });
view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event<bool> &evt) { this->sidebar->enable_buttons(evt.data); });
view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this);
view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this);
2019-02-21 10:54:18 +00:00
view3D_canvas->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); });
view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); });
view3D_canvas->Bind(EVT_GLCANVAS_UNDO, [this](SimpleEvent&) { this->undo(); });
view3D_canvas->Bind(EVT_GLCANVAS_REDO, [this](SimpleEvent&) { this->redo(); });
2020-04-29 13:58:57 +00:00
view3D_canvas->Bind(EVT_GLCANVAS_COLLAPSE_SIDEBAR, [this](SimpleEvent&) { this->q->collapse_sidebar(!this->q->is_sidebar_collapsed()); });
view3D_canvas->Bind(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, [this](SimpleEvent&) { this->view3D->get_canvas3d()->reset_layer_height_profile(); });
view3D_canvas->Bind(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, [this](Event<float>& evt) { this->view3D->get_canvas3d()->adaptive_layer_height_profile(evt.data); });
view3D_canvas->Bind(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, [this](HeightProfileSmoothEvent& evt) { this->view3D->get_canvas3d()->smooth_layer_height_profile(evt.data); });
view3D_canvas->Bind(EVT_GLCANVAS_RELOAD_FROM_DISK, [this](SimpleEvent&) { this->reload_all_from_disk(); });
// 3DScene/Toolbar:
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_FEWER, [q](SimpleEvent&) { q->decrease_instances(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this);
view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&)
{
set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values,
2019-07-24 12:02:36 +00:00
config->option<ConfigOptionString>("bed_custom_texture")->value,
config->option<ConfigOptionString>("bed_custom_model")->value);
});
2018-10-04 09:12:55 +00:00
// Preview events:
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&)
{
set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values,
2019-07-24 12:02:36 +00:00
config->option<ConfigOptionString>("bed_custom_texture")->value,
config->option<ConfigOptionString>("bed_custom_model")->value);
});
2019-02-21 10:54:18 +00:00
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); });
q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
2018-09-17 10:15:11 +00:00
q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this);
q->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); });
q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); });
2018-09-17 10:15:11 +00:00
// Drop target:
q->SetDropTarget(new PlaterDropTarget(q)); // if my understanding is right, wxWindow takes the owenership
q->Layout();
set_current_panel(view3D);
// updates camera type from .ini file
camera.set_type(get_config("use_perspective_camera"));
// Load the 3DConnexion device database.
mouse3d_controller.load_config(*wxGetApp().app_config);
// Start the background thread to detect and connect to a HID device (Windows and Linux).
// Connect to a 3DConnextion driver (OSX).
mouse3d_controller.init();
#ifdef _WIN32
// Register an USB HID (Human Interface Device) attach event. evt contains Win32 path to the USB device containing VID, PID and other info.
// This event wakes up the Mouse3DController's background thread to enumerate HID devices, if the VID of the callback event
// is one of the 3D Mouse vendors (3DConnexion or Logitech).
this->q->Bind(EVT_HID_DEVICE_ATTACHED, [this](HIDDeviceAttachedEvent &evt) {
mouse3d_controller.device_attached(evt.data);
});
#endif /* _WIN32 */
this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this](RemovableDriveEjectEvent &evt) {
if (evt.data.second) {
this->show_action_buttons(this->ready_to_slice);
Slic3r::GUI::show_info(this->q, format_wxstr(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),
evt.data.first.name, evt.data.first.path));
} else
Slic3r::GUI::show_info(this->q, format_wxstr(_L("Ejecting of device %s(%s) has failed."),
evt.data.first.name, evt.data.first.path));
});
this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this](RemovableDrivesChangedEvent &) { this->show_action_buttons(this->ready_to_slice); });
// Start the background thread and register this window as a target for update events.
wxGetApp().removable_drive_manager()->init(this->q);
#ifdef _WIN32
// Trigger enumeration of removable media on Win32 notification.
this->q->Bind(EVT_VOLUME_ATTACHED, [this](VolumeAttachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); });
this->q->Bind(EVT_VOLUME_DETACHED, [this](VolumeDetachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); });
#endif /* _WIN32 */
2019-07-05 17:06:19 +00:00
// Initialize the Undo / Redo stack with a first snapshot.
this->take_snapshot(_L("New Project"));
this->q->Bind(EVT_LOAD_MODEL_OTHER_INSTANCE, [this](LoadFromOtherInstanceEvent &evt) {
BOOST_LOG_TRIVIAL(debug) << "received load from other instance event ";
this->load_files(evt.data, true, true);
});
this->q->Bind(EVT_INSTANCE_GO_TO_FRONT, [this](InstanceGoToFrontEvent &) {
BOOST_LOG_TRIVIAL(debug) << "prusaslicer window going forward";
//this code maximize app window on Fedora
{
wxGetApp().mainframe->Iconize(false);
if (wxGetApp().mainframe->IsMaximized())
wxGetApp().mainframe->Maximize(true);
else
wxGetApp().mainframe->Maximize(false);
}
//this code maximize window on Ubuntu
{
wxGetApp().mainframe->Restore();
wxGetApp().GetTopWindow()->SetFocus(); // focus on my window
wxGetApp().GetTopWindow()->Raise(); // bring window to front
wxGetApp().GetTopWindow()->Show(true); // show the window
}
});
wxGetApp().other_instance_message_handler()->init(this->q);
// collapse sidebar according to saved value
bool is_collapsed = wxGetApp().app_config->get("collapsed_sidebar") == "1";
sidebar->collapse(is_collapsed);
// Update an enable of the collapse_toolbar: if sidebar is collapsed, then collapse_toolbar should be visible
if (is_collapsed)
wxGetApp().app_config->set("show_collapse_button", "1");
2018-09-17 10:15:11 +00:00
}
Plater::priv::~priv()
{
if (config != nullptr)
delete config;
}
void Plater::priv::update(unsigned int flags)
2018-09-17 10:15:11 +00:00
{
// the following line, when enabled, causes flickering on NVIDIA graphics cards
// wxWindowUpdateLocker freeze_guard(q);
if (get_config("autocenter") == "1") {
2018-10-01 14:48:08 +00:00
// auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
// const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values);
// const BoundingBox bed_shape_bb = bed_shape.bounding_box();
2018-10-03 14:18:23 +00:00
const Vec2d& bed_center = bed_shape_bb().center();
2018-10-01 14:48:08 +00:00
model.center_instances_around_point(bed_center);
}
unsigned int update_status = 0;
if (this->printer_technology == ptSLA || (flags & (unsigned int)UpdateParams::FORCE_BACKGROUND_PROCESSING_UPDATE))
// Update the SLAPrint from the current Model, so that the reload_scene()
// pulls the correct data.
update_status = this->update_background_process(false, flags & (unsigned int)UpdateParams::POSTPONE_VALIDATION_ERROR_MESSAGE);
this->view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH);
2019-08-06 16:16:02 +00:00
this->preview->reload_print();
if (this->printer_technology == ptSLA)
this->restart_background_process(update_status);
else
this->schedule_background_process();
2018-09-17 10:15:11 +00:00
}
void Plater::priv::select_view(const std::string& direction)
{
if (current_panel == view3D)
view3D->select_view(direction);
else if (current_panel == preview)
preview->select_view(direction);
}
void Plater::priv::select_view_3D(const std::string& name)
{
if (name == "3D")
set_current_panel(view3D);
else if (name == "Preview")
set_current_panel(preview);
}
void Plater::priv::select_next_view_3D()
{
if (current_panel == view3D)
set_current_panel(preview);
else if (current_panel == preview)
set_current_panel(view3D);
}
2018-10-17 10:59:58 +00:00
void Plater::priv::reset_all_gizmos()
{
view3D->get_canvas3d()->reset_all_gizmos();
}
// Called after the Preferences dialog is closed and the program settings are saved.
// Update the UI based on the current preferences.
2018-09-17 10:15:11 +00:00
void Plater::priv::update_ui_from_settings()
{
camera.set_type(wxGetApp().app_config->get("use_perspective_camera"));
if (wxGetApp().app_config->get("use_free_camera") != "1")
camera.recover_from_free_camera();
view3D->get_canvas3d()->update_ui_from_settings();
preview->get_canvas3d()->update_ui_from_settings();
sidebar->update_ui_from_settings();
2018-09-17 10:15:11 +00:00
}
// Called after the print technology was changed.
// Update the tooltips for "Switch to Settings" button in maintoolbar
void Plater::priv::update_main_toolbar_tooltips()
{
view3D->get_canvas3d()->update_tooltip_for_settings_item_in_main_toolbar();
}
std::shared_ptr<ProgressStatusBar> Plater::priv::statusbar()
2018-09-17 10:15:11 +00:00
{
return main_frame->m_statusbar;
2018-09-17 10:15:11 +00:00
}
2018-10-01 14:48:08 +00:00
std::string Plater::priv::get_config(const std::string &key) const
{
return wxGetApp().app_config->get(key);
}
2018-10-03 14:18:23 +00:00
BoundingBoxf Plater::priv::bed_shape_bb() const
{
BoundingBox bb = scaled_bed_shape_bb();
return BoundingBoxf(unscale(bb.min), unscale(bb.max));
}
BoundingBox Plater::priv::scaled_bed_shape_bb() const
2018-10-01 14:48:08 +00:00
{
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values);
return bed_shape.bounding_box();
}
std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool imperial_units/* = false*/)
2018-09-17 10:15:11 +00:00
{
2018-10-04 09:12:55 +00:00
if (input_files.empty()) { return std::vector<size_t>(); }
2018-09-17 10:15:11 +00:00
auto *nozzle_dmrs = config->opt<ConfigOptionFloats>("nozzle_diameter");
bool one_by_one = input_files.size() == 1 || printer_technology == ptSLA || nozzle_dmrs->values.size() <= 1;
2018-09-17 10:15:11 +00:00
if (! one_by_one) {
for (const auto &path : input_files) {
if (std::regex_match(path.string(), pattern_bundle)) {
one_by_one = true;
break;
}
}
}
const auto loading = _L("Loading") + dots;
2018-09-17 10:15:11 +00:00
wxProgressDialog dlg(loading, loading);
dlg.Pulse();
auto *new_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model();
2018-10-01 14:48:08 +00:00
std::vector<size_t> obj_idxs;
2018-09-17 10:15:11 +00:00
for (size_t i = 0; i < input_files.size(); i++) {
const auto &path = input_files[i];
const auto filename = path.filename();
const auto dlg_info = format_wxstr(_L("Processing input file %s"), from_path(filename)) + "\n";
2018-09-17 10:15:11 +00:00
dlg.Update(100 * i / input_files.size(), dlg_info);
const bool type_3mf = std::regex_match(path.string(), pattern_3mf);
const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf);
2019-01-03 11:04:14 +00:00
const bool type_any_amf = !type_3mf && std::regex_match(path.string(), pattern_any_amf);
2019-01-18 15:01:43 +00:00
const bool type_prusa = std::regex_match(path.string(), pattern_prusa);
2018-09-17 10:15:11 +00:00
Slic3r::Model model;
bool is_project_file = type_prusa;
2018-09-17 10:15:11 +00:00
try {
if (type_3mf || type_zip_amf) {
DynamicPrintConfig config;
2018-11-06 18:09:54 +00:00
{
DynamicPrintConfig config_loaded;
model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, false, load_config);
if (load_config && !config_loaded.empty()) {
// Based on the printer technology field found in the loaded config, select the base for the config,
2019-08-06 16:16:02 +00:00
PrinterTechnology printer_technology = Preset::printer_technology(config_loaded);
// We can't to load SLA project if there is at least one multi-part object on the bed
if (printer_technology == ptSLA)
{
const ModelObjectPtrs& objects = q->model().objects;
for (auto object : objects)
if (object->volumes.size() > 1)
{
Slic3r::GUI::show_info(nullptr,
_L("You cannot load SLA project with a multi-part object on the bed") + "\n\n" +
_L("Please check your object list before preset changing."),
_L("Attention!"));
return obj_idxs;
}
}
2019-08-06 16:16:02 +00:00
config.apply(printer_technology == ptFFF ?
static_cast<const ConfigBase&>(FullPrintConfig::defaults()) :
2018-11-06 18:09:54 +00:00
static_cast<const ConfigBase&>(SLAFullPrintConfig::defaults()));
// and place the loaded config over the base.
config += std::move(config_loaded);
}
this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
2018-11-06 18:09:54 +00:00
}
2018-12-03 14:06:02 +00:00
if (load_config)
{
if (!config.empty()) {
Preset::normalize(config);
wxGetApp().preset_bundle->load_config_model(filename.string(), std::move(config));
wxGetApp().load_current_presets();
is_project_file = true;
}
wxGetApp().app_config->update_config_dir(path.parent_path().string());
}
}
else {
model = Slic3r::Model::read_from_file(path.string(), nullptr, false, load_config);
for (auto obj : model.objects)
if (obj->name.empty())
obj->name = fs::path(obj->input_file).filename().string();
2018-09-17 10:15:11 +00:00
}
} catch (const std::exception &e) {
2018-09-17 10:15:11 +00:00
GUI::show_error(q, e.what());
continue;
}
if (load_model)
{
// The model should now be initialized
auto convert_from_imperial_units = [](Model& model) {
model.convert_from_imperial_units();
wxGetApp().app_config->set("use_inches", "1");
wxGetApp().sidebar().update_ui_from_settings();
};
if (imperial_units)
convert_from_imperial_units(model);
else if (model.looks_like_imperial_units()) {
wxMessageDialog msg_dlg(q, _L(
"This model looks like saved in inches.\n"
"Should I consider this model as a saved in inches and convert it?") + "\n",
_L("Saved in inches object detected"), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES)
convert_from_imperial_units(model);
}
if (! is_project_file) {
if (model.looks_like_multipart_object()) {
wxMessageDialog msg_dlg(q, _L(
"This file contains several objects positioned at multiple heights.\n"
2019-01-21 11:34:28 +00:00
"Instead of considering them as multiple objects, should I consider\n"
"this file as a single object having multiple parts?") + "\n",
_L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES) {
model.convert_multipart_object(nozzle_dmrs->values.size());
}
}
2018-09-17 10:15:11 +00:00
}
else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) {
wxMessageDialog msg_dlg(q, _L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?")+"\n",
_L("Detected advanced data"), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES)
{
Slic3r::GUI::wxGetApp().save_mode(comAdvanced);
view3D->set_as_dirty();
}
else
return obj_idxs;
}
2018-09-17 10:15:11 +00:00
for (ModelObject* model_object : model.objects) {
if (!type_3mf && !type_zip_amf)
model_object->center_around_origin(false);
model_object->ensure_on_bed();
}
2018-09-17 10:15:11 +00:00
// check multi-part object adding for the SLA-printing
if (printer_technology == ptSLA)
{
for (auto obj : model.objects)
if ( obj->volumes.size()>1 ) {
2019-08-06 16:16:02 +00:00
Slic3r::GUI::show_error(nullptr,
format_wxstr(_L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part"),
from_path(filename)));
return obj_idxs;
}
}
if (one_by_one) {
auto loaded_idxs = load_model_objects(model.objects);
obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
} else {
// This must be an .stl or .obj file, which may contain a maximum of one volume.
for (const ModelObject* model_object : model.objects) {
new_model->add_object(*model_object);
}
2018-09-17 10:15:11 +00:00
}
}
}
if (new_model != nullptr && new_model->objects.size() > 1) {
wxMessageDialog msg_dlg(q, _L(
2018-09-17 10:15:11 +00:00
"Multiple objects were loaded for a multi-material printer.\n"
"Instead of considering them as multiple objects, should I consider\n"
"these files to represent a single object having multiple parts?") + "\n",
_L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES) {
2018-09-17 10:15:11 +00:00
new_model->convert_multipart_object(nozzle_dmrs->values.size());
}
auto loaded_idxs = load_model_objects(new_model->objects);
2018-10-01 14:48:08 +00:00
obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
2018-09-17 10:15:11 +00:00
}
if (load_model)
{
wxGetApp().app_config->update_skein_dir(input_files[input_files.size() - 1].parent_path().string());
// XXX: Plater.pm had @loaded_files, but didn't seem to fill them with the filenames...
statusbar()->set_status_text(_L("Loaded"));
}
2018-12-03 14:06:02 +00:00
// automatic selection of added objects
if (!obj_idxs.empty() && (view3D != nullptr))
{
// update printable state for new volumes on canvas3D
wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(obj_idxs);
Selection& selection = view3D->get_canvas3d()->get_selection();
selection.clear();
for (size_t idx : obj_idxs)
{
selection.add_object((unsigned int)idx, false);
}
if (view3D->get_canvas3d()->get_gizmos_manager().is_enabled())
// this is required because the selected object changed and the flatten on face an sla support gizmos need to be updated accordingly
view3D->get_canvas3d()->update_gizmos_on_off_state();
}
2018-10-01 14:48:08 +00:00
return obj_idxs;
2018-09-17 10:15:11 +00:00
}
// #define AUTOPLACEMENT_ON_LOAD
2018-10-04 09:12:55 +00:00
std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &model_objects)
2018-09-17 10:15:11 +00:00
{
2018-10-03 14:18:23 +00:00
const BoundingBoxf bed_shape = bed_shape_bb();
2018-12-19 08:54:15 +00:00
const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast<double>(), 1.0) - 2.0 * Vec3d::Ones();
2018-09-17 10:15:11 +00:00
#ifndef AUTOPLACEMENT_ON_LOAD
bool need_arrange = false;
#endif /* AUTOPLACEMENT_ON_LOAD */
2018-09-17 10:15:11 +00:00
bool scaled_down = false;
2018-10-01 14:48:08 +00:00
std::vector<size_t> obj_idxs;
unsigned int obj_count = model.objects.size();
2018-09-17 10:15:11 +00:00
#ifdef AUTOPLACEMENT_ON_LOAD
ModelInstancePtrs new_instances;
#endif /* AUTOPLACEMENT_ON_LOAD */
2018-10-01 14:48:08 +00:00
for (ModelObject *model_object : model_objects) {
2018-09-17 10:15:11 +00:00
auto *object = model.add_object(*model_object);
std::string object_name = object->name.empty() ? fs::path(object->input_file).filename().string() : object->name;
obj_idxs.push_back(obj_count++);
2018-09-17 10:15:11 +00:00
2018-10-04 09:12:55 +00:00
if (model_object->instances.empty()) {
#ifdef AUTOPLACEMENT_ON_LOAD
object->center_around_origin();
new_instances.emplace_back(object->add_instance());
#else /* AUTOPLACEMENT_ON_LOAD */
2019-03-25 11:07:43 +00:00
// if object has no defined position(s) we need to rearrange everything after loading
need_arrange = true;
2019-08-06 16:16:02 +00:00
// add a default instance and center object around origin
object->center_around_origin(); // also aligns object to Z = 0
ModelInstance* instance = object->add_instance();
instance->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -object->origin_translation(2)));
#endif /* AUTOPLACEMENT_ON_LOAD */
2018-09-17 10:15:11 +00:00
}
2018-10-01 14:48:08 +00:00
const Vec3d size = object->bounding_box().size();
const Vec3d ratio = size.cwiseQuotient(bed_size);
const double max_ratio = std::max(ratio(0), ratio(1));
if (max_ratio > 10000) {
// the size of the object is too big -> this could lead to overflow when moving to clipper coordinates,
// so scale down the mesh
2019-08-06 16:16:02 +00:00
double inv = 1. / max_ratio;
object->scale_mesh_after_creation(Vec3d(inv, inv, inv));
2018-12-19 08:54:15 +00:00
object->origin_translation = Vec3d::Zero();
object->center_around_origin();
2018-10-01 14:48:08 +00:00
scaled_down = true;
} else if (max_ratio > 5) {
const Vec3d inverse = 1.0 / max_ratio * Vec3d::Ones();
for (ModelInstance *instance : object->instances) {
2018-10-01 14:48:08 +00:00
instance->set_scaling_factor(inverse);
}
scaled_down = true;
2018-10-01 14:48:08 +00:00
}
2018-10-10 12:43:07 +00:00
object->ensure_on_bed();
2018-09-17 10:15:11 +00:00
}
#ifdef AUTOPLACEMENT_ON_LOAD
// FIXME distance should be a config value /////////////////////////////////
auto min_obj_distance = static_cast<coord_t>(6/SCALING_FACTOR);
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
assert(bed_shape_opt);
auto& bedpoints = bed_shape_opt->values;
Polyline bed; bed.points.reserve(bedpoints.size());
for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
2019-08-06 16:16:02 +00:00
2019-06-27 19:13:44 +00:00
std::pair<bool, GLCanvas3D::WipeTowerInfo> wti = view3D->get_canvas3d()->get_wipe_tower_info();
2019-08-06 16:16:02 +00:00
arr::find_new_position(model, new_instances, min_obj_distance, bed, wti);
// it remains to move the wipe tower:
view3D->get_canvas3d()->arrange_wipe_tower(wti);
#endif /* AUTOPLACEMENT_ON_LOAD */
2018-10-01 14:48:08 +00:00
if (scaled_down) {
GUI::show_info(q,
_L("Your object appears to be too large, so it was automatically scaled down to fit your print bed."),
_L("Object too large?"));
2018-10-01 14:48:08 +00:00
}
for (const size_t idx : obj_idxs) {
wxGetApp().obj_list()->add_object_to_list(idx);
2018-10-01 14:48:08 +00:00
}
update();
2018-10-11 11:22:36 +00:00
object_list_changed();
this->schedule_background_process();
2018-10-01 14:48:08 +00:00
return obj_idxs;
2018-09-17 10:15:11 +00:00
}
wxString Plater::priv::get_export_file(GUI::FileType file_type)
2018-10-04 09:12:55 +00:00
{
wxString wildcard;
switch (file_type) {
case FT_STL:
case FT_AMF:
case FT_3MF:
2018-10-19 14:52:41 +00:00
case FT_GCODE:
case FT_OBJ:
wildcard = file_wildcards(file_type);
2018-10-04 09:12:55 +00:00
break;
default:
wildcard = file_wildcards(FT_MODEL);
2018-10-04 09:12:55 +00:00
break;
}
// Update printbility state of each of the ModelInstances.
this->update_print_volume_state();
const Selection& selection = get_selection();
int obj_idx = selection.get_object_idx();
fs::path output_file;
if (file_type == FT_3MF)
// for 3mf take the path from the project filename, if any
output_file = into_path(get_project_filename(".3mf"));
if (output_file.empty())
{
// first try to get the file name from the current selection
if ((0 <= obj_idx) && (obj_idx < (int)this->model.objects.size()))
output_file = this->model.objects[obj_idx]->get_export_filename();
if (output_file.empty())
// Find the file name of the first printable object.
output_file = this->model.propose_export_file_name_and_path();
if (output_file.empty() && !model.objects.empty())
// Find the file name of the first object.
output_file = this->model.objects[0]->get_export_filename();
}
2018-10-04 09:12:55 +00:00
wxString dlg_title;
2018-10-04 09:12:55 +00:00
switch (file_type) {
case FT_STL:
{
output_file.replace_extension("stl");
dlg_title = _L("Export STL file:");
break;
}
case FT_AMF:
{
// XXX: Problem on OS X with double extension?
output_file.replace_extension("zip.amf");
dlg_title = _L("Export AMF file:");
break;
}
case FT_3MF:
{
output_file.replace_extension("3mf");
dlg_title = _L("Save file as:");
break;
}
case FT_OBJ:
{
output_file.replace_extension("obj");
dlg_title = _L("Export OBJ file:");
break;
}
2018-10-04 09:12:55 +00:00
default: break;
}
wxFileDialog dlg(q, dlg_title,
from_path(output_file.parent_path()), from_path(output_file.filename()),
wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
2018-10-04 09:12:55 +00:00
if (dlg.ShowModal() != wxID_OK)
return wxEmptyString;
2018-10-04 09:12:55 +00:00
wxString out_path = dlg.GetPath();
fs::path path(into_path(out_path));
2018-10-04 09:12:55 +00:00
wxGetApp().app_config->update_last_output_dir(path.parent_path().string());
return out_path;
2018-10-04 09:12:55 +00:00
}
const Selection& Plater::priv::get_selection() const
{
return view3D->get_canvas3d()->get_selection();
}
Selection& Plater::priv::get_selection()
{
return view3D->get_canvas3d()->get_selection();
}
int Plater::priv::get_selected_object_idx() const
{
int idx = get_selection().get_object_idx();
return ((0 <= idx) && (idx < 1000)) ? idx : -1;
}
2018-10-04 09:12:55 +00:00
int Plater::priv::get_selected_volume_idx() const
{
auto& selection = get_selection();
int idx = selection.get_object_idx();
2019-08-06 16:16:02 +00:00
if ((0 > idx) || (idx > 1000))
return-1;
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
if (model.objects[idx]->volumes.size() > 1)
return v->volume_idx();
return -1;
}
2018-10-04 09:12:55 +00:00
void Plater::priv::selection_changed()
{
// if the selection is not valid to allow for layer editing, we need to turn off the tool if it is running
bool enable_layer_editing = layers_height_allowed();
2019-01-25 07:37:06 +00:00
if (!enable_layer_editing && view3D->is_layers_editing_enabled()) {
SimpleEvent evt(EVT_GLTOOLBAR_LAYERSEDITING);
on_action_layersediting(evt);
}
// forces a frame render to update the view (to avoid a missed update if, for example, the context menu appears)
view3D->render();
2018-10-04 09:12:55 +00:00
}
2018-10-08 17:14:55 +00:00
void Plater::priv::object_list_changed()
2018-10-04 09:12:55 +00:00
{
const bool export_in_progress = this->background_process.is_export_scheduled(); // || ! send_gcode_file.empty());
2018-10-08 17:14:55 +00:00
// XXX: is this right?
const bool model_fits = view3D->check_volumes_outside_state() == ModelInstance::PVS_Inside;
2018-10-08 17:14:55 +00:00
sidebar->enable_buttons(!model.objects.empty() && !export_in_progress && model_fits);
2018-10-04 09:12:55 +00:00
}
2018-11-21 14:28:35 +00:00
void Plater::priv::select_all()
{
view3D->select_all();
2018-12-03 12:29:07 +00:00
this->sidebar->obj_list()->update_selections();
2018-11-21 14:28:35 +00:00
}
void Plater::priv::deselect_all()
{
view3D->deselect_all();
}
2018-10-08 17:14:55 +00:00
void Plater::priv::remove(size_t obj_idx)
2018-10-04 09:12:55 +00:00
{
2018-10-08 17:14:55 +00:00
// Prevent toolpaths preview from rendering while we modify the Print object
preview->set_enabled(false);
if (view3D->is_layers_editing_enabled())
view3D->enable_layers_editing(false);
2018-10-22 13:18:56 +00:00
2018-10-08 17:14:55 +00:00
model.delete_object(obj_idx);
update();
// Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model.
sidebar->obj_list()->delete_object_from_list(obj_idx);
object_list_changed();
2018-10-08 17:14:55 +00:00
}
void Plater::priv::delete_object_from_model(size_t obj_idx)
{
wxString snapshot_label = _L("Delete Object");
2019-08-06 16:16:02 +00:00
if (! model.objects[obj_idx]->name.empty())
snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str());
Plater::TakeSnapshot snapshot(q, snapshot_label);
model.delete_object(obj_idx);
2018-11-14 10:22:13 +00:00
update();
object_list_changed();
}
2018-10-08 17:14:55 +00:00
void Plater::priv::reset()
{
Plater::TakeSnapshot snapshot(q, _L("Reset Project"));
set_project_filename(wxEmptyString);
2018-10-04 09:12:55 +00:00
// Prevent toolpaths preview from rendering while we modify the Print object
preview->set_enabled(false);
if (view3D->is_layers_editing_enabled())
view3D->enable_layers_editing(false);
2018-10-22 13:18:56 +00:00
// Stop and reset the Print content.
this->background_process.reset();
2018-10-04 09:12:55 +00:00
model.clear_objects();
update();
// Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model.
sidebar->obj_list()->delete_all_objects_from_list();
object_list_changed();
// The hiding of the slicing results, if shown, is not taken care by the background process, so we do it here
this->sidebar->show_sliced_info_sizer(false);
model.custom_gcode_per_print_z.gcodes.clear();
2018-10-04 09:12:55 +00:00
}
void Plater::priv::mirror(Axis axis)
{
view3D->mirror_selection(axis);
}
void Plater::find_new_position(const ModelInstancePtrs &instances,
coord_t min_d)
2019-07-15 16:18:34 +00:00
{
arrangement::ArrangePolygons movable, fixed;
for (const ModelObject *mo : p->model.objects)
2019-07-15 16:18:34 +00:00
for (const ModelInstance *inst : mo->instances) {
auto it = std::find(instances.begin(), instances.end(), inst);
auto arrpoly = inst->get_arrange_polygon();
2019-08-06 16:16:02 +00:00
2019-07-15 16:18:34 +00:00
if (it == instances.end())
fixed.emplace_back(std::move(arrpoly));
else
movable.emplace_back(std::move(arrpoly));
}
if (p->view3D->get_canvas3d()->get_wipe_tower_info())
fixed.emplace_back(get_wipe_tower_arrangepoly(*this));
arrangement::arrange(movable, fixed, get_bed_shape(*config()),
arrangement::ArrangeParams{min_d});
2019-08-06 16:16:02 +00:00
2019-07-15 16:18:34 +00:00
for (size_t i = 0; i < instances.size(); ++i)
if (movable[i].bed_idx == 0)
2020-03-30 11:26:24 +00:00
instances[i]->apply_arrange_result(movable[i].translation.cast<double>(),
movable[i].rotation);
2019-07-15 16:18:34 +00:00
}
2018-11-12 13:52:52 +00:00
void Plater::priv::split_object()
{
2018-10-19 13:27:19 +00:00
int obj_idx = get_selected_object_idx();
if (obj_idx == -1)
return;
2018-10-19 13:27:19 +00:00
// we clone model object because split_object() adds the split volumes
// into the same model object, thus causing duplicates when we call load_model_objects()
Model new_model = model;
ModelObject* current_model_object = new_model.objects[obj_idx];
if (current_model_object->volumes.size() > 1)
{
Slic3r::GUI::warning_catcher(q, _L("The selected object can't be split because it contains more than one volume/material."));
2018-10-19 13:27:19 +00:00
return;
}
wxBusyCursor wait;
2018-10-19 13:27:19 +00:00
ModelObjectPtrs new_objects;
current_model_object->split(&new_objects);
if (new_objects.size() == 1)
Slic3r::GUI::warning_catcher(q, _L("The selected object couldn't be split because it contains only one part."));
2018-10-19 13:27:19 +00:00
else
{
Plater::TakeSnapshot snapshot(q, _L("Split to Objects"));
2019-07-04 15:33:19 +00:00
2018-10-24 10:55:38 +00:00
unsigned int counter = 1;
2018-10-19 13:27:19 +00:00
for (ModelObject* m : new_objects)
2018-10-24 10:55:38 +00:00
m->name = current_model_object->name + "_" + std::to_string(counter++);
2018-10-19 13:27:19 +00:00
remove(obj_idx);
// load all model objects at once, otherwise the plate would be rearranged after each one
// causing original positions not to be kept
std::vector<size_t> idxs = load_model_objects(new_objects);
// select newly added objects
for (size_t idx : idxs)
{
get_selection().add_object((unsigned int)idx, false);
}
2018-10-19 13:27:19 +00:00
}
}
2018-10-24 10:55:38 +00:00
void Plater::priv::split_volume()
{
wxGetApp().obj_list()->split();
2018-10-24 10:55:38 +00:00
}
void Plater::priv::scale_selection_to_fit_print_volume()
{
this->view3D->get_canvas3d()->get_selection().scale_to_fit_print_volume(*config);
}
void Plater::priv::schedule_background_process()
{
2019-02-22 10:59:40 +00:00
delayed_error_message.clear();
// Trigger the timer event after 0.5s
this->background_process_timer.Start(500, wxTIMER_ONE_SHOT);
// Notify the Canvas3D that something has changed, so it may invalidate some of the layer editing stuff.
this->view3D->get_canvas3d()->set_config(this->config);
}
void Plater::priv::update_print_volume_state()
{
BoundingBox bed_box_2D = get_extents(Polygon::new_scale(this->config->opt<ConfigOptionPoints>("bed_shape")->values));
BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(this->config->opt_float("max_print_height"))));
// Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced.
print_volume.min(2) = -1e10;
this->q->model().update_print_volume_state(print_volume);
}
// Update background processing thread from the current config and Model.
// Returns a bitmask of UpdateBackgroundProcessReturnState.
unsigned int Plater::priv::update_background_process(bool force_validation, bool postpone_error_messages)
{
// bitmap of enum UpdateBackgroundProcessReturnState
unsigned int return_state = 0;
2019-08-06 16:16:02 +00:00
// If the update_background_process() was not called by the timer, kill the timer,
// so the update_restart_background_process() will not be called again in vain.
this->background_process_timer.Stop();
// Update the "out of print bed" state of ModelInstances.
this->update_print_volume_state();
// Apply new config to the possibly running background task.
bool was_running = this->background_process.running();
Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), wxGetApp().preset_bundle->full_config());
// Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile.
if (view3D->is_layers_editing_enabled())
view3D->get_wxglcanvas()->Refresh();
if (invalidated == Print::APPLY_STATUS_INVALIDATED) {
// Some previously calculated data on the Print was invalidated.
// Hide the slicing results, as the current slicing status is no more valid.
this->sidebar->show_sliced_info_sizer(false);
// Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared.
// Otherwise they will be just refreshed.
2019-08-06 16:16:02 +00:00
if (this->preview != nullptr)
// If the preview is not visible, the following line just invalidates the preview,
// but the G-code paths or SLA preview are calculated first once the preview is made visible.
this->preview->reload_print();
// In FDM mode, we need to reload the 3D scene because of the wipe tower preview box.
// In SLA mode, we need to reload the 3D scene every time to show the support structures.
if (this->printer_technology == ptSLA || (this->printer_technology == ptFFF && this->config->opt_bool("wipe_tower")))
return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE;
}
if ((invalidated != Print::APPLY_STATUS_UNCHANGED || force_validation) && ! this->background_process.empty()) {
// The delayed error message is no more valid.
this->delayed_error_message.clear();
// The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors.
std::string err = this->background_process.validate();
if (err.empty()) {
2019-08-06 16:16:02 +00:00
if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled())
return_state |= UPDATE_BACKGROUND_PROCESS_RESTART;
} else {
// The print is not valid.
// Only show the error message immediately, if the top level parent of this window is active.
auto p = dynamic_cast<wxWindow*>(this->q);
while (p->GetParent())
p = p->GetParent();
auto *top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
if (! postpone_error_messages && top_level_wnd && top_level_wnd->IsActive()) {
// The error returned from the Print needs to be translated into the local language.
GUI::show_error(this->q, err);
} else {
// Show the error message once the main window gets activated.
this->delayed_error_message = err;
}
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
}
} else if (! this->delayed_error_message.empty()) {
// Reusing the old state.
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
}
if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() &&
(return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) {
2019-08-06 16:16:02 +00:00
// The background processing was killed and it will not be restarted.
wxCommandEvent evt(EVT_PROCESS_COMPLETED);
evt.SetInt(-1);
// Post the "canceled" callback message, so that it will be processed after any possible pending status bar update messages.
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
}
if ((return_state & UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
{
// Validation of the background data failed.
const wxString invalid_str = _L("Invalid data");
for (auto btn : {ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport})
sidebar->set_btn_label(btn, invalid_str);
}
else
{
// Background data is valid.
if ((return_state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ||
(return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0 )
this->statusbar()->set_status_text(_L("Ready to slice"));
sidebar->set_btn_label(ActionButtonType::abExport, _(label_btn_export));
sidebar->set_btn_label(ActionButtonType::abSendGCode, _(label_btn_send));
2019-08-06 16:16:02 +00:00
const wxString slice_string = background_process.running() && wxGetApp().get_mode() == comSimple ?
_L("Slicing") + dots : _L("Slice now");
sidebar->set_btn_label(ActionButtonType::abReslice, slice_string);
if (background_process.finished())
show_action_buttons(false);
2019-08-06 16:16:02 +00:00
else if (!background_process.empty() &&
!background_process.running()) /* Do not update buttons if background process is running
2019-08-06 16:16:02 +00:00
* This condition is important for SLA mode especially,
* when this function is called several times during calculations
* */
show_action_buttons(true);
}
return return_state;
}
// Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
bool Plater::priv::restart_background_process(unsigned int state)
{
if (m_ui_jobs.is_any_running()) {
// Avoid a race condition
return false;
}
2019-08-06 16:16:02 +00:00
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()) {
this->statusbar()->set_cancel_callback([this]() {
this->statusbar()->set_status_text(_L("Cancelling"));
this->background_process.stop();
});
return true;
}
}
return false;
}
void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_removable_media, PrintHostJob upload_job)
2018-12-11 09:33:11 +00:00
{
wxCHECK_RET(!(output_path.empty() && upload_job.empty()), "export_gcode: output_path and upload_job empty");
if (model.objects.empty())
return;
if (background_process.is_export_scheduled()) {
GUI::show_error(q, _L("Another export job is currently running."));
2018-12-11 09:33:11 +00:00
return;
}
// bitmask of UpdateBackgroundProcessReturnState
unsigned int state = update_background_process(true);
2018-12-11 09:33:11 +00:00
if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
view3D->reload_scene(false);
2018-12-11 09:33:11 +00:00
if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
return;
if (! output_path.empty()) {
background_process.schedule_export(output_path.string(), output_path_on_removable_media);
2018-12-11 09:33:11 +00:00
} else {
background_process.schedule_upload(std::move(upload_job));
}
// If the SLA processing of just a single object's supports is running, restart slicing for the whole object.
this->background_process.set_task(PrintBase::TaskParams());
this->restart_background_process(priv::UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT);
2018-12-11 09:33:11 +00:00
}
unsigned int Plater::priv::update_restart_background_process(bool force_update_scene, bool force_update_preview)
{
// bitmask of UpdateBackgroundProcessReturnState
unsigned int state = this->update_background_process(false);
if (force_update_scene || (state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0)
view3D->reload_scene(false);
if (force_update_preview)
this->preview->reload_print();
this->restart_background_process(state);
2019-08-06 16:16:02 +00:00
return state;
}
2018-12-06 14:40:41 +00:00
void Plater::priv::update_fff_scene()
{
if (this->preview != nullptr)
this->preview->reload_print();
// In case this was MM print, wipe tower bounding box on 3D tab might need redrawing with exact depth:
2019-08-06 16:16:02 +00:00
view3D->reload_scene(true);
2019-12-12 14:43:14 +00:00
2018-12-06 14:40:41 +00:00
}
void Plater::priv::update_sla_scene()
{
// Update the SLAPrint from the current Model, so that the reload_scene()
// pulls the correct data.
delayed_scene_refresh = false;
this->update_restart_background_process(true, true);
}
void Plater::priv::reload_from_disk()
{
Plater::TakeSnapshot snapshot(q, _L("Reload from disk"));
const Selection& selection = get_selection();
if (selection.is_wipe_tower())
return;
// struct to hold selected ModelVolumes by their indices
struct SelectedVolume
{
int object_idx;
int volume_idx;
// operators needed by std::algorithms
bool operator < (const SelectedVolume& other) const { return (object_idx < other.object_idx) || ((object_idx == other.object_idx) && (volume_idx < other.volume_idx)); }
bool operator == (const SelectedVolume& other) const { return (object_idx == other.object_idx) && (volume_idx == other.volume_idx); }
};
std::vector<SelectedVolume> selected_volumes;
// collects selected ModelVolumes
const std::set<unsigned int>& selected_volumes_idxs = selection.get_volume_idxs();
for (unsigned int idx : selected_volumes_idxs)
{
const GLVolume* v = selection.get_volume(idx);
int v_idx = v->volume_idx();
if (v_idx >= 0)
{
int o_idx = v->object_idx();
if ((0 <= o_idx) && (o_idx < (int)model.objects.size()))
selected_volumes.push_back({ o_idx, v_idx });
}
}
std::sort(selected_volumes.begin(), selected_volumes.end());
selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end());
// collects paths of files to load
std::vector<fs::path> input_paths;
std::vector<fs::path> missing_input_paths;
for (const SelectedVolume& v : selected_volumes)
2019-09-11 13:02:57 +00:00
{
const ModelObject* object = model.objects[v.object_idx];
const ModelVolume* volume = object->volumes[v.volume_idx];
if (!volume->source.input_file.empty())
{
if (fs::exists(volume->source.input_file))
input_paths.push_back(volume->source.input_file);
else
missing_input_paths.push_back(volume->source.input_file);
}
2020-02-26 07:56:05 +00:00
else if (!object->input_file.empty() && volume->is_model_part() && !volume->name.empty())
missing_input_paths.push_back(volume->name);
}
std::sort(missing_input_paths.begin(), missing_input_paths.end());
missing_input_paths.erase(std::unique(missing_input_paths.begin(), missing_input_paths.end()), missing_input_paths.end());
while (!missing_input_paths.empty())
{
// ask user to select the missing file
fs::path search = missing_input_paths.back();
wxString title = _L("Please select the file to reload");
#if defined(__APPLE__)
title += " (" + from_u8(search.filename().string()) + ")";
#endif // __APPLE__
title += ":";
wxFileDialog dialog(q, title, "", from_u8(search.filename().string()), file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() != wxID_OK)
return;
std::string sel_filename_path = dialog.GetPath().ToUTF8().data();
std::string sel_filename = fs::path(sel_filename_path).filename().string();
if (boost::algorithm::iequals(search.filename().string(), sel_filename))
{
input_paths.push_back(sel_filename_path);
missing_input_paths.pop_back();
fs::path sel_path = fs::path(sel_filename_path).remove_filename().string();
std::vector<fs::path>::iterator it = missing_input_paths.begin();
while (it != missing_input_paths.end())
{
// try to use the path of the selected file with all remaining missing files
fs::path repathed_filename = sel_path;
repathed_filename /= it->filename();
if (fs::exists(repathed_filename))
{
input_paths.push_back(repathed_filename.string());
it = missing_input_paths.erase(it);
}
else
++it;
}
}
else
{
wxString message = _L("It is not allowed to change the file to reload") + " (" + from_u8(search.filename().string()) + ").\n" + _L("Do you want to retry") + " ?";
wxMessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION);
if (dlg.ShowModal() != wxID_YES)
return;
}
}
std::sort(input_paths.begin(), input_paths.end());
input_paths.erase(std::unique(input_paths.begin(), input_paths.end()), input_paths.end());
std::vector<wxString> fail_list;
// load one file at a time
for (size_t i = 0; i < input_paths.size(); ++i)
{
const auto& path = input_paths[i].string();
wxBusyCursor wait;
wxBusyInfo info(_L("Reload from:") + " " + from_u8(path), q->get_current_canvas3D()->get_wxglcanvas());
Model new_model;
try
2019-09-11 13:02:57 +00:00
{
new_model = Model::read_from_file(path, nullptr, true, false);
for (ModelObject* model_object : new_model.objects)
{
model_object->center_around_origin();
model_object->ensure_on_bed();
}
}
catch (std::exception&)
2019-09-11 13:02:57 +00:00
{
// error while loading
return;
}
// update the selected volumes whose source is the current file
for (const SelectedVolume& sel_v : selected_volumes)
{
ModelObject* old_model_object = model.objects[sel_v.object_idx];
ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx];
bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string());
bool has_name = !old_volume->name.empty() && boost::algorithm::iequals(old_volume->name, fs::path(path).filename().string());
if (has_source || has_name)
{
int new_volume_idx = -1;
int new_object_idx = -1;
if (has_source)
{
// take idxs from source
new_volume_idx = old_volume->source.volume_idx;
new_object_idx = old_volume->source.object_idx;
}
else
{
// take idxs from the 1st matching volume
for (size_t o = 0; o < new_model.objects.size(); ++o)
{
ModelObject* obj = new_model.objects[o];
bool found = false;
for (size_t v = 0; v < obj->volumes.size(); ++v)
{
if (obj->volumes[v]->name == old_volume->name)
{
new_volume_idx = (int)v;
new_object_idx = (int)o;
found = true;
break;
}
}
if (found)
break;
}
}
if ((new_object_idx < 0) && ((int)new_model.objects.size() <= new_object_idx))
{
fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name));
continue;
}
ModelObject* new_model_object = new_model.objects[new_object_idx];
if ((new_volume_idx < 0) && ((int)new_model.objects.size() <= new_volume_idx))
{
fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name));
continue;
}
if (new_volume_idx < (int)new_model_object->volumes.size())
{
old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]);
ModelVolume* new_volume = old_model_object->volumes.back();
new_volume->set_new_unique_id();
new_volume->config.apply(old_volume->config);
new_volume->set_type(old_volume->type());
new_volume->set_material_id(old_volume->material_id());
new_volume->set_transformation(old_volume->get_transformation() * old_volume->source.transform);
new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
std::swap(old_model_object->volumes[sel_v.volume_idx], old_model_object->volumes.back());
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
old_model_object->ensure_on_bed();
sla::reproject_points_and_holes(old_model_object);
}
}
}
}
if (!fail_list.empty())
{
wxString message = _L("Unable to reload:") + "\n";
for (const wxString& s : fail_list)
{
message += s + "\n";
}
wxMessageDialog dlg(q, message, _L("Error during reload"), wxOK | wxOK_DEFAULT | wxICON_WARNING);
dlg.ShowModal();
}
// update 3D scene
update();
2019-09-06 11:11:20 +00:00
// new GLVolumes have been created at this point, so update their printable state
for (size_t i = 0; i < model.objects.size(); ++i)
{
view3D->get_canvas3d()->update_instance_printable_state_for_object(i);
}
}
void Plater::priv::reload_all_from_disk()
{
if (model.objects.empty())
return;
Plater::TakeSnapshot snapshot(q, _L("Reload all from disk"));
Plater::SuppressSnapshots suppress(q);
Selection& selection = get_selection();
Selection::IndicesList curr_idxs = selection.get_volume_idxs();
// reload from disk uses selection
select_all();
reload_from_disk();
// restore previous selection
selection.clear();
for (unsigned int idx : curr_idxs)
{
selection.add(idx, false);
}
}
void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/)
{
if (obj_idx < 0)
return;
2019-07-04 15:33:19 +00:00
Plater::TakeSnapshot snapshot(q, _L("Fix Throught NetFabb"));
2019-07-04 15:33:19 +00:00
fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx);
sla::reproject_points_and_holes(model.objects[obj_idx]);
this->update();
this->object_list_changed();
this->schedule_background_process();
}
void Plater::priv::set_current_panel(wxPanel* panel)
{
if (std::find(panels.begin(), panels.end(), panel) == panels.end())
return;
#ifdef __WXMAC__
bool force_render = (current_panel != nullptr);
#endif // __WXMAC__
if (current_panel == panel)
return;
current_panel = panel;
// to reduce flickering when changing view, first set as visible the new current panel
for (wxPanel* p : panels)
{
if (p == current_panel)
{
#ifdef __WXMAC__
// On Mac we need also to force a render to avoid flickering when changing view
if (force_render)
{
if (p == view3D)
dynamic_cast<View3D*>(p)->get_canvas3d()->render();
else if (p == preview)
dynamic_cast<Preview*>(p)->get_canvas3d()->render();
}
#endif // __WXMAC__
p->Show();
}
}
// then set to invisible the other
for (wxPanel* p : panels)
{
if (p != current_panel)
p->Hide();
}
panel_sizer->Layout();
if (current_panel == view3D)
{
if (view3D->is_reload_delayed())
{
// Delayed loading of the 3D scene.
if (this->printer_technology == ptSLA)
{
// Update the SLAPrint from the current Model, so that the reload_scene()
// pulls the correct data.
this->update_restart_background_process(true, false);
} else
view3D->reload_scene(true);
}
// sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably)
view3D->set_as_dirty();
view_toolbar.select_item("3D");
}
else if (current_panel == preview)
{
// 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->check_volumes_outside_state() != ModelInstance::PVS_Partly_Outside;
if (!model.objects.empty() && !export_in_progress && model_fits)
this->q->reslice();
// keeps current gcode preview, if any
preview->reload_print(true);
preview->set_as_dirty();
view_toolbar.select_item("Preview");
}
current_panel->SetFocusFromKbd();
}
2018-09-17 10:15:11 +00:00
void Plater::priv::on_select_preset(wxCommandEvent &evt)
{
auto preset_type = static_cast<Preset::Type>(evt.GetInt());
auto *combo = static_cast<PresetComboBox*>(evt.GetEventObject());
2018-09-17 10:15:11 +00:00
2020-03-23 21:47:35 +00:00
// see https://github.com/prusa3d/PrusaSlicer/issues/3889
// Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender"),
// m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive.
// So, use GetSelection() from event parameter
// But in this function we couldn't use evt.GetSelection(), because m_commandInt is used for preset_type
// Thus, get selection in this way:
int selection = combo->FindString(evt.GetString(), true);
auto idx = combo->get_extruder_idx();
2018-10-09 10:41:05 +00:00
2019-08-06 16:16:02 +00:00
//! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox,
//! but the OSX version derived from wxOwnerDrawnCombo.
2019-08-06 16:16:02 +00:00
//! So, to get selected string we do
//! combo->GetString(combo->GetSelection())
//! instead of
//! combo->GetStringSelection().ToUTF8().data());
const std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type,
2020-03-23 21:47:35 +00:00
Preset::remove_suffix_modified(combo->GetString(selection).ToUTF8().data()));
2018-09-17 10:15:11 +00:00
if (preset_type == Preset::TYPE_FILAMENT) {
wxGetApp().preset_bundle->set_filament_preset(idx, preset_name);
2018-09-17 10:15:11 +00:00
}
// TODO: ?
2018-10-09 10:41:05 +00:00
if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) {
2019-12-03 03:48:01 +00:00
// Only update the plater UI for the 2nd and other filaments.
wxGetApp().preset_bundle->update_plater_filament_ui(idx, combo);
2019-08-06 16:16:02 +00:00
}
else {
wxWindowUpdateLocker noUpdates(sidebar->presets_panel());
wxGetApp().get_tab(preset_type)->select_preset(preset_name);
2018-09-17 10:15:11 +00:00
}
// update plater with new config
q->on_config_change(wxGetApp().preset_bundle->full_config());
if (preset_type == Preset::TYPE_PRINTER) {
/* Settings list can be changed after printer preset changing, so
* update all settings items for all item had it.
2019-08-06 16:16:02 +00:00
* Furthermore, Layers editing is implemented only for FFF printers
* and for SLA presets they should be deleted
*/
wxGetApp().obj_list()->update_object_list_by_printer_technology();
}
2018-09-17 10:15:11 +00:00
}
void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
{
if (evt.status.percent >= -1) {
if (m_ui_jobs.is_any_running()) {
// Avoid a race condition
return;
}
this->statusbar()->set_progress(evt.status.percent);
2019-05-04 00:07:07 +00:00
this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8(""));
}
if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE | PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) {
switch (this->printer_technology) {
case ptFFF:
2018-12-06 14:40:41 +00:00
this->update_fff_scene();
break;
case ptSLA:
// If RELOAD_SLA_SUPPORT_POINTS, then the SLA gizmo is updated (reload_scene calls update_gizmos_data)
if (view3D->is_dragging())
delayed_scene_refresh = true;
else
this->update_sla_scene();
break;
default: break;
}
} else if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) {
// Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways.
this->preview->reload_print();
}
}
void Plater::priv::on_slicing_completed(wxCommandEvent &)
2018-09-17 10:15:11 +00:00
{
switch (this->printer_technology) {
case ptFFF:
2018-12-06 14:40:41 +00:00
this->update_fff_scene();
break;
case ptSLA:
if (view3D->is_dragging())
delayed_scene_refresh = true;
else
this->update_sla_scene();
break;
default: break;
}
2018-09-17 10:15:11 +00:00
}
void Plater::priv::on_process_completed(wxCommandEvent &evt)
2018-09-17 10:15:11 +00:00
{
2018-10-18 16:06:40 +00:00
// Stop the background task, wait until the thread goes into the "Idle" state.
// At this point of time the thread should be either finished or canceled,
// so the following call just confirms, that the produced data were consumed.
this->background_process.stop();
this->statusbar()->reset_cancel_callback();
this->statusbar()->stop_busy();
2019-08-06 16:16:02 +00:00
const bool canceled = evt.GetInt() < 0;
2019-08-06 16:16:02 +00:00
const bool error = evt.GetInt() == 0;
const bool success = evt.GetInt() > 0;
// Reset the "export G-code path" name, so that the automatic background processing will be enabled again.
this->background_process.reset_export();
if (error) {
wxString message = evt.GetString();
if (message.IsEmpty())
message = _L("Export failed");
if (q->m_tracking_popup_menu)
// We don't want to pop-up a message box when tracking a pop-up menu.
// We postpone the error message instead.
q->m_tracking_popup_menu_error_message = message;
else
show_error(q, message);
this->statusbar()->set_status_text(message);
2018-10-18 16:06:40 +00:00
}
2019-08-06 16:16:02 +00:00
if (canceled)
this->statusbar()->set_status_text(_L("Cancelled"));
2018-10-18 16:06:40 +00:00
this->sidebar->show_sliced_info_sizer(success);
2018-10-18 16:06:40 +00:00
// 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
// the "Slice now" and "Export G-code" buttons based on their "out of bed" status.
this->object_list_changed();
2019-08-06 16:16:02 +00:00
2018-10-18 16:06:40 +00:00
// refresh preview
switch (this->printer_technology) {
case ptFFF:
2018-12-06 14:40:41 +00:00
this->update_fff_scene();
break;
case ptSLA:
if (view3D->is_dragging())
delayed_scene_refresh = true;
else
this->update_sla_scene();
break;
default: break;
}
2019-12-13 17:02:25 +00:00
2019-03-08 14:40:28 +00:00
if (canceled) {
if (wxGetApp().get_mode() == comSimple)
sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now");
show_action_buttons(true);
}
else if (this->writing_to_removable_device || wxGetApp().get_mode() == comSimple)
show_action_buttons(false);
this->writing_to_removable_device = false;
2018-09-17 10:15:11 +00:00
}
void Plater::priv::on_layer_editing_toggled(bool enable)
{
view3D->enable_layers_editing(enable);
view3D->set_as_dirty();
2018-09-17 10:15:11 +00:00
}
void Plater::priv::on_action_add(SimpleEvent&)
2018-09-17 10:15:11 +00:00
{
if (q != nullptr)
q->add_model();
2018-09-17 10:15:11 +00:00
}
2018-10-24 10:55:38 +00:00
void Plater::priv::on_action_split_objects(SimpleEvent&)
2018-10-04 09:12:55 +00:00
{
2018-10-19 13:27:19 +00:00
split_object();
2018-10-04 09:12:55 +00:00
}
2018-10-24 10:55:38 +00:00
void Plater::priv::on_action_split_volumes(SimpleEvent&)
{
split_volume();
}
2018-10-04 09:12:55 +00:00
void Plater::priv::on_action_layersediting(SimpleEvent&)
{
view3D->enable_layers_editing(!view3D->is_layers_editing_enabled());
2018-10-04 09:12:55 +00:00
}
2018-10-16 14:04:19 +00:00
void Plater::priv::on_object_select(SimpleEvent& evt)
{
wxGetApp().obj_list()->update_selections();
selection_changed();
2018-10-16 14:04:19 +00:00
}
void Plater::priv::on_right_click(RBtnEvent& evt)
{
int obj_idx = get_selected_object_idx();
wxMenu* menu = nullptr;
if (obj_idx == -1) // no one or several object are selected
{
if (evt.data.second) // right button was clicked on empty space
menu = &default_menu;
else
{
sidebar->obj_list()->show_multi_selection_menu();
return;
}
}
else
{
// If in 3DScene is(are) selected volume(s), but right button was clicked on empty space
if (evt.data.second)
return;
if (printer_technology == ptSLA)
menu = &sla_object_menu;
else
{
// show "Object menu" for each one or several FullInstance instead of FullObject
const bool is_some_full_instances = get_selection().is_single_full_instance() ||
get_selection().is_single_full_object() ||
get_selection().is_multiple_full_instance();
menu = is_some_full_instances ? &object_menu : &part_menu;
}
sidebar->obj_list()->append_menu_item_settings(menu);
if (printer_technology != ptSLA)
sidebar->obj_list()->append_menu_item_change_extruder(menu);
if (menu != &part_menu)
{
/* Remove/Prepend "increase/decrease instances" menu items according to the view mode.
* Suppress to show those items for a Simple mode
*/
const MenuIdentifier id = printer_technology == ptSLA ? miObjectSLA : miObjectFFF;
if (wxGetApp().get_mode() == comSimple) {
if (menu->FindItem(_L("Add instance")) != wxNOT_FOUND)
{
/* Detach an items from the menu, but don't delete them
* so that they can be added back later
* (after switching to the Advanced/Expert mode)
*/
menu->Remove(items_increase[id]);
menu->Remove(items_decrease[id]);
menu->Remove(items_set_number_of_copies[id]);
}
}
else {
if (menu->FindItem(_L("Add instance")) == wxNOT_FOUND)
{
// Prepend items to the menu, if those aren't not there
menu->Prepend(items_set_number_of_copies[id]);
menu->Prepend(items_decrease[id]);
menu->Prepend(items_increase[id]);
}
}
}
}
if (q != nullptr && menu) {
#ifdef __linux__
// For some reason on Linux the menu isn't displayed if position is specified
// (even though the position is sane).
q->PopupMenu(menu);
#else
q->PopupMenu(menu, (int)evt.data.first.x(), (int)evt.data.first.y());
#endif
}
}
void Plater::priv::on_wipetower_moved(Vec3dEvent &evt)
{
DynamicPrintConfig cfg;
cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = evt.data(0);
cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = evt.data(1);
2018-11-19 12:17:14 +00:00
wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
}
void Plater::priv::on_wipetower_rotated(Vec3dEvent& evt)
{
DynamicPrintConfig cfg;
cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = evt.data(0);
cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = evt.data(1);
cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = Geometry::rad2deg(evt.data(2));
wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
}
void Plater::priv::on_update_geometry(Vec3dsEvent<2>&)
{
// TODO
}
2019-08-06 16:16:02 +00:00
// Update the scene from the background processing,
// if the update message was received during mouse manipulation.
void Plater::priv::on_3dcanvas_mouse_dragging_finished(SimpleEvent&)
{
if (this->delayed_scene_refresh) {
this->delayed_scene_refresh = false;
this->update_sla_scene();
}
}
bool Plater::priv::init_object_menu()
{
items_increase.reserve(2);
items_decrease.reserve(2);
items_set_number_of_copies.reserve(2);
init_common_menu(&object_menu);
complit_init_object_menu();
init_common_menu(&sla_object_menu);
complit_init_sla_object_menu();
init_common_menu(&part_menu, true);
complit_init_part_menu();
sidebar->obj_list()->create_default_popupmenu(&default_menu);
return true;
}
void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
{
view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, show_bed, transparent_background);
}
void Plater::priv::generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
{
thumbnails.clear();
for (const Vec2d& size : sizes)
{
thumbnails.push_back(ThumbnailData());
Point isize(size); // round to ints
generate_thumbnail(thumbnails.back(), isize.x(), isize.y(), printable_only, parts_only, show_bed, transparent_background);
if (!thumbnails.back().is_valid())
thumbnails.pop_back();
}
}
void Plater::priv::msw_rescale_object_menu()
{
for (MenuWithSeparators* menu : { &object_menu, &sla_object_menu, &part_menu, &default_menu })
msw_rescale_menu(dynamic_cast<wxMenu*>(menu));
}
wxString Plater::priv::get_project_filename(const wxString& extension) const
{
return m_project_filename.empty() ? "" : m_project_filename + extension;
}
void Plater::priv::set_project_filename(const wxString& filename)
{
boost::filesystem::path full_path = into_path(filename);
boost::filesystem::path ext = full_path.extension();
if (boost::iequals(ext.string(), ".amf")) {
// Remove the first extension.
full_path.replace_extension("");
// It may be ".zip.amf".
if (boost::iequals(full_path.extension().string(), ".zip"))
// Remove the 2nd extension.
full_path.replace_extension("");
} else {
// Remove just one extension.
full_path.replace_extension("");
}
m_project_filename = from_path(full_path);
wxGetApp().mainframe->update_title();
if (!filename.empty())
wxGetApp().mainframe->add_to_recent_projects(filename);
}
bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/)
{
if (is_part) {
append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"),
[this](wxCommandEvent&) { q->remove_selected(); }, "delete", nullptr, [this]() { return can_delete(); }, q);
2019-04-04 13:13:43 +00:00
append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected volumes from disk"),
[this](wxCommandEvent&) { q->reload_from_disk(); }, "", menu, [this]() { return can_reload_from_disk(); }, q);
2019-04-04 13:13:43 +00:00
sidebar->obj_list()->append_menu_item_export_stl(menu);
}
else {
wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _L("Add instance") + "\t+", _L("Add one more instance of the selected object"),
[this](wxCommandEvent&) { q->increase_instances(); }, "add_copies", nullptr, [this]() { return can_increase_instances(); }, q);
wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _L("Remove instance") + "\t-", _L("Remove one instance of the selected object"),
[this](wxCommandEvent&) { q->decrease_instances(); }, "remove_copies", nullptr, [this]() { return can_decrease_instances(); }, q);
wxMenuItem* item_set_number_of_copies = append_menu_item(menu, wxID_ANY, _L("Set number of instances") + dots, _L("Change the number of instances of the selected object"),
[this](wxCommandEvent&) { q->set_number_of_copies(); }, "number_of_copies", nullptr, [this]() { return can_increase_instances(); }, q);
items_increase.push_back(item_increase);
items_decrease.push_back(item_decrease);
items_set_number_of_copies.push_back(item_set_number_of_copies);
// Delete menu was moved to be after +/- instace to make it more difficult to be selected by mistake.
append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"),
[this](wxCommandEvent&) { q->remove_selected(); }, "delete", nullptr, [this]() { return can_delete(); }, q);
menu->AppendSeparator();
sidebar->obj_list()->append_menu_item_instance_to_object(menu, q);
2018-12-07 17:20:22 +00:00
menu->AppendSeparator();
wxMenuItem* menu_item_printable = sidebar->obj_list()->append_menu_item_printable(menu, q);
menu->AppendSeparator();
append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected object from disk"),
[this](wxCommandEvent&) { reload_from_disk(); }, "", nullptr, [this]() { return can_reload_from_disk(); }, q);
2018-12-07 17:20:22 +00:00
append_menu_item(menu, wxID_ANY, _L("Export as STL") + dots, _L("Export the selected object as STL file"),
[this](wxCommandEvent&) { q->export_stl(false, true); }, "", nullptr,
[this]() {
const Selection& selection = get_selection();
return selection.is_single_full_instance() || selection.is_single_full_object();
}, q);
2019-04-04 13:13:43 +00:00
menu->AppendSeparator();
q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) {
const Selection& selection = get_selection();
int instance_idx = selection.get_instance_idx();
evt.Enable(selection.is_single_full_instance() || selection.is_single_full_object());
if (instance_idx != -1)
{
evt.Check(model.objects[selection.get_object_idx()]->instances[instance_idx]->printable);
view3D->set_as_dirty();
}
}, menu_item_printable->GetId());
}
sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu);
sidebar->obj_list()->append_menu_item_scale_selection_to_fit_print_volume(menu);
wxMenu* mirror_menu = new wxMenu();
if (mirror_menu == nullptr)
return false;
append_menu_item(mirror_menu, wxID_ANY, _L("Along X axis"), _L("Mirror the selected object along the X axis"),
2019-04-12 15:10:04 +00:00
[this](wxCommandEvent&) { mirror(X); }, "mark_X", menu);
append_menu_item(mirror_menu, wxID_ANY, _L("Along Y axis"), _L("Mirror the selected object along the Y axis"),
2019-04-12 15:10:04 +00:00
[this](wxCommandEvent&) { mirror(Y); }, "mark_Y", menu);
append_menu_item(mirror_menu, wxID_ANY, _L("Along Z axis"), _L("Mirror the selected object along the Z axis"),
2019-04-12 15:10:04 +00:00
[this](wxCommandEvent&) { mirror(Z); }, "mark_Z", menu);
append_submenu(menu, mirror_menu, wxID_ANY, _L("Mirror"), _L("Mirror the selected object"), "",
[this]() { return can_mirror(); }, q);
return true;
}
bool Plater::priv::complit_init_object_menu()
{
2018-10-24 10:55:38 +00:00
wxMenu* split_menu = new wxMenu();
if (split_menu == nullptr)
return false;
append_menu_item(split_menu, wxID_ANY, _L("To objects"), _L("Split the selected object into individual objects"),
[this](wxCommandEvent&) { split_object(); }, "split_object_SMALL", &object_menu, [this]() { return can_split(); }, q);
append_menu_item(split_menu, wxID_ANY, _L("To parts"), _L("Split the selected object into individual sub-parts"),
[this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL", &object_menu, [this]() { return can_split(); }, q);
2018-10-24 10:55:38 +00:00
append_submenu(&object_menu, split_menu, wxID_ANY, _L("Split"), _L("Split the selected object"), "",
[this]() { return can_split() && wxGetApp().get_mode() > comSimple; }, q);
object_menu.AppendSeparator();
2019-05-27 11:07:37 +00:00
// Layers Editing for object
sidebar->obj_list()->append_menu_item_layers_editing(&object_menu, q);
2019-05-27 11:07:37 +00:00
object_menu.AppendSeparator();
// "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume()
return true;
}
bool Plater::priv::complit_init_sla_object_menu()
{
append_menu_item(&sla_object_menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual objects"),
[this](wxCommandEvent&) { split_object(); }, "split_object_SMALL", nullptr, [this]() { return can_split(); }, q);
sla_object_menu.AppendSeparator();
// Add the automatic rotation sub-menu
append_menu_item(
&sla_object_menu, wxID_ANY, _(L("Optimize orientation")),
_(L("Optimize the rotation of the object for better print results.")),
[this](wxCommandEvent &) {
m_ui_jobs.optimize_rotation();
});
return true;
}
bool Plater::priv::complit_init_part_menu()
{
append_menu_item(&part_menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual sub-parts"),
[this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL", nullptr, [this]() { return can_split(); }, q);
part_menu.AppendSeparator();
auto obj_list = sidebar->obj_list();
obj_list->append_menu_item_change_type(&part_menu, q);
return true;
}
void Plater::priv::set_current_canvas_as_dirty()
{
if (current_panel == view3D)
view3D->set_as_dirty();
else if (current_panel == preview)
preview->set_as_dirty();
}
GLCanvas3D* Plater::priv::get_current_canvas3D()
{
return (current_panel == view3D) ? view3D->get_canvas3d() : ((current_panel == preview) ? preview->get_canvas3d() : nullptr);
}
void Plater::priv::unbind_canvas_event_handlers()
{
if (view3D != nullptr)
view3D->get_canvas3d()->unbind_event_handlers();
if (preview != nullptr)
preview->get_canvas3d()->unbind_event_handlers();
}
void Plater::priv::reset_canvas_volumes()
{
if (view3D != nullptr)
view3D->get_canvas3d()->reset_volumes();
if (preview != nullptr)
preview->get_canvas3d()->reset_volumes();
}
bool Plater::priv::init_view_toolbar()
{
if (view_toolbar.get_items_count() > 0)
// already initialized
return true;
2018-12-17 09:55:14 +00:00
BackgroundTexture::Metadata background_data;
background_data.filename = "toolbar_background.png";
background_data.left = 16;
background_data.top = 16;
background_data.right = 16;
background_data.bottom = 16;
if (!view_toolbar.init(background_data))
return false;
2018-12-17 09:55:14 +00:00
view_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left);
view_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Bottom);
2018-12-17 09:55:14 +00:00
view_toolbar.set_border(5.0f);
view_toolbar.set_gap_size(1.0f);
GLToolbarItem::Data item;
item.name = "3D";
item.icon_filename = "editor.svg";
item.tooltip = _utf8(L("3D editor view")) + " [" + GUI::shortkey_ctrl_prefix() + "5]";
2018-12-17 09:55:14 +00:00
item.sprite_id = 0;
item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); };
2018-12-17 09:55:14 +00:00
if (!view_toolbar.add_item(item))
return false;
2018-12-17 09:55:14 +00:00
item.name = "Preview";
item.icon_filename = "preview.svg";
item.tooltip = _utf8(L("Preview")) + " [" + GUI::shortkey_ctrl_prefix() + "6]";
2018-12-17 09:55:14 +00:00
item.sprite_id = 1;
item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); };
2018-12-17 09:55:14 +00:00
if (!view_toolbar.add_item(item))
return false;
2018-12-17 09:55:14 +00:00
view_toolbar.select_item("3D");
view_toolbar.set_enabled(true);
return true;
}
bool Plater::priv::can_set_instance_to_object() const
{
const int obj_idx = get_selected_object_idx();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1);
}
bool Plater::priv::can_split() const
{
return sidebar->obj_list()->is_splittable();
}
bool Plater::priv::layers_height_allowed() const
{
2019-04-05 07:51:58 +00:00
if (printer_technology != ptFFF)
return false;
int obj_idx = get_selected_object_idx();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed();
}
bool Plater::priv::can_mirror() const
{
return get_selection().is_from_single_instance();
}
bool Plater::priv::can_reload_from_disk() const
{
// struct to hold selected ModelVolumes by their indices
struct SelectedVolume
{
int object_idx;
int volume_idx;
// operators needed by std::algorithms
bool operator < (const SelectedVolume& other) const { return (object_idx < other.object_idx) || ((object_idx == other.object_idx) && (volume_idx < other.volume_idx)); }
bool operator == (const SelectedVolume& other) const { return (object_idx == other.object_idx) && (volume_idx == other.volume_idx); }
};
std::vector<SelectedVolume> selected_volumes;
const Selection& selection = get_selection();
// collects selected ModelVolumes
const std::set<unsigned int>& selected_volumes_idxs = selection.get_volume_idxs();
for (unsigned int idx : selected_volumes_idxs)
{
const GLVolume* v = selection.get_volume(idx);
int v_idx = v->volume_idx();
if (v_idx >= 0)
{
int o_idx = v->object_idx();
if ((0 <= o_idx) && (o_idx < (int)model.objects.size()))
selected_volumes.push_back({ o_idx, v_idx });
}
}
std::sort(selected_volumes.begin(), selected_volumes.end());
selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end());
// collects paths of files to load
std::vector<fs::path> paths;
for (const SelectedVolume& v : selected_volumes)
{
const ModelObject* object = model.objects[v.object_idx];
const ModelVolume* volume = object->volumes[v.volume_idx];
if (!volume->source.input_file.empty())
paths.push_back(volume->source.input_file);
else if (!object->input_file.empty() && !volume->name.empty())
paths.push_back(volume->name);
}
std::sort(paths.begin(), paths.end());
paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
return !paths.empty();
}
2019-07-24 12:02:36 +00:00
void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model)
{
2019-07-24 12:02:36 +00:00
bool new_shape = bed.set_shape(shape, custom_texture, custom_model);
if (new_shape)
{
if (view3D) view3D->bed_shape_changed();
if (preview) preview->bed_shape_changed();
}
}
bool Plater::priv::can_delete() const
2018-10-24 10:55:38 +00:00
{
2019-06-28 15:03:50 +00:00
return !get_selection().is_empty() && !get_selection().is_wipe_tower() && !m_ui_jobs.is_any_running();
2018-10-24 10:55:38 +00:00
}
bool Plater::priv::can_delete_all() const
{
return !model.objects.empty();
}
bool Plater::priv::can_fix_through_netfabb() const
{
int obj_idx = get_selected_object_idx();
if (obj_idx < 0)
return false;
return model.objects[obj_idx]->get_mesh_errors_count() > 0;
}
bool Plater::priv::can_increase_instances() const
{
if (m_ui_jobs.is_any_running()) {
return false;
}
int obj_idx = get_selected_object_idx();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size());
}
bool Plater::priv::can_decrease_instances() const
{
if (m_ui_jobs.is_any_running()) {
return false;
}
2018-10-22 13:18:56 +00:00
int obj_idx = get_selected_object_idx();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1);
}
bool Plater::priv::can_split_to_objects() const
{
return can_split();
}
bool Plater::priv::can_split_to_volumes() const
{
return (printer_technology != ptSLA) && can_split();
}
bool Plater::priv::can_arrange() const
{
return !model.objects.empty() && !m_ui_jobs.is_any_running();
}
bool Plater::priv::can_layers_editing() const
{
return layers_height_allowed();
}
void Plater::priv::update_object_menu()
{
sidebar->obj_list()->append_menu_items_add_volume(&object_menu);
}
void Plater::priv::show_action_buttons(const bool ready_to_slice) const
{
// Cache this value, so that the callbacks from the RemovableDriveManager may repeat that value when calling show_action_buttons().
this->ready_to_slice = ready_to_slice;
wxWindowUpdateLocker noUpdater(sidebar);
const auto prin_host_opt = config->option<ConfigOptionString>("print_host");
const bool send_gcode_shown = prin_host_opt != nullptr && !prin_host_opt->value.empty();
2019-12-10 16:31:27 +00:00
2019-08-06 16:16:02 +00:00
// when a background processing is ON, export_btn and/or send_btn are showing
if (wxGetApp().app_config->get("background_processing") == "1")
{
RemovableDriveManager::RemovableDrivesStatus removable_media_status = wxGetApp().removable_drive_manager()->status();
if (sidebar->show_reslice(false) |
sidebar->show_export(true) |
sidebar->show_send(send_gcode_shown) |
sidebar->show_export_removable(removable_media_status.has_removable_drives) |
sidebar->show_disconnect(removable_media_status.has_eject))
2019-08-06 16:16:02 +00:00
sidebar->Layout();
}
else
{
RemovableDriveManager::RemovableDrivesStatus removable_media_status;
if (! ready_to_slice)
removable_media_status = wxGetApp().removable_drive_manager()->status();
if (sidebar->show_reslice(ready_to_slice) |
sidebar->show_export(!ready_to_slice) |
sidebar->show_send(send_gcode_shown && !ready_to_slice) |
sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives) |
sidebar->show_disconnect(!ready_to_slice && removable_media_status.has_eject))
2019-08-06 16:16:02 +00:00
sidebar->Layout();
}
}
void Plater::priv::enter_gizmos_stack()
{
2019-08-06 16:16:02 +00:00
assert(m_undo_redo_stack_active == &m_undo_redo_stack_main);
if (m_undo_redo_stack_active == &m_undo_redo_stack_main) {
m_undo_redo_stack_active = &m_undo_redo_stack_gizmos;
assert(m_undo_redo_stack_active->empty());
// Take the initial snapshot of the gizmos.
// Not localized on purpose, the text will never be shown to the user.
this->take_snapshot(std::string("Gizmos-Initial"));
}
}
void Plater::priv::leave_gizmos_stack()
{
2019-08-06 16:16:02 +00:00
assert(m_undo_redo_stack_active == &m_undo_redo_stack_gizmos);
if (m_undo_redo_stack_active == &m_undo_redo_stack_gizmos) {
assert(! m_undo_redo_stack_active->empty());
m_undo_redo_stack_active->clear();
m_undo_redo_stack_active = &m_undo_redo_stack_main;
}
}
2019-07-09 18:45:00 +00:00
int Plater::priv::get_active_snapshot_index()
{
const size_t active_snapshot_time = this->undo_redo_stack().active_snapshot_time();
const std::vector<UndoRedo::Snapshot>& ss_stack = this->undo_redo_stack().snapshots();
2019-07-09 18:45:00 +00:00
const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time));
return it - ss_stack.begin();
}
void Plater::priv::take_snapshot(const std::string& snapshot_name)
{
2019-08-06 16:16:02 +00:00
if (this->m_prevent_snapshots > 0)
return;
assert(this->m_prevent_snapshots >= 0);
UndoRedo::SnapshotData snapshot_data;
snapshot_data.printer_technology = this->printer_technology;
if (this->view3D->is_layers_editing_enabled())
2019-08-06 16:16:02 +00:00
snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE;
if (this->sidebar->obj_list()->is_selected(itSettings)) {
snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR;
snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx();
}
else if (this->sidebar->obj_list()->is_selected(itLayer)) {
snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR;
snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx();
}
else if (this->sidebar->obj_list()->is_selected(itLayerRoot))
snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR;
// If SLA gizmo is active, ask it if it wants to trigger support generation
// on loading this snapshot.
if (view3D->get_canvas3d()->get_gizmos_manager().wants_reslice_supports_on_undo())
snapshot_data.flags |= UndoRedo::SnapshotData::RECALCULATE_SLA_SUPPORTS;
//FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config.
// This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config.
if (this->printer_technology == ptFFF) {
const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y"));
model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle");
}
this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data);
this->undo_redo_stack().release_least_recently_used();
// Save the last active preset name of a particular printer technology.
((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name();
2019-08-06 16:16:02 +00:00
BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info();
}
void Plater::priv::undo()
{
2019-08-06 16:16:02 +00:00
const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack().snapshots();
auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time()));
if (-- it_current != snapshots.begin())
this->undo_redo_to(it_current);
}
void Plater::priv::redo()
2019-08-06 16:16:02 +00:00
{
const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack().snapshots();
auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time()));
if (++ it_current != snapshots.end())
this->undo_redo_to(it_current);
}
void Plater::priv::undo_redo_to(size_t time_to_load)
2019-07-09 18:45:00 +00:00
{
2019-08-06 16:16:02 +00:00
const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack().snapshots();
auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(time_to_load));
assert(it_current != snapshots.end());
this->undo_redo_to(it_current);
2019-07-09 18:45:00 +00:00
}
void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator it_snapshot)
{
// Make sure that no updating function calls take_snapshot until we are done.
SuppressSnapshots snapshot_supressor(q);
2019-08-06 16:16:02 +00:00
bool temp_snapshot_was_taken = this->undo_redo_stack().temp_snapshot_active();
PrinterTechnology new_printer_technology = it_snapshot->snapshot_data.printer_technology;
bool printer_technology_changed = this->printer_technology != new_printer_technology;
if (printer_technology_changed) {
// Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type.
std::string s_pt = (it_snapshot->snapshot_data.printer_technology == ptFFF) ? "FFF" : "SLA";
if (! wxGetApp().check_unsaved_changes(format_wxstr(_L(
"%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt)))
2019-08-06 16:16:02 +00:00
// Don't switch the profiles.
return;
}
// Save the last active preset name of a particular printer technology.
((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name();
//FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config.
// This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config.
if (this->printer_technology == ptFFF) {
const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y"));
model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle");
}
const int layer_range_idx = it_snapshot->snapshot_data.layer_range_idx;
// Flags made of Snapshot::Flags enum values.
unsigned int new_flags = it_snapshot->snapshot_data.flags;
2019-08-06 16:16:02 +00:00
UndoRedo::SnapshotData top_snapshot_data;
top_snapshot_data.printer_technology = this->printer_technology;
if (this->view3D->is_layers_editing_enabled())
2019-08-06 16:16:02 +00:00
top_snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE;
if (this->sidebar->obj_list()->is_selected(itSettings)) {
2019-08-06 16:16:02 +00:00
top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR;
top_snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx();
}
else if (this->sidebar->obj_list()->is_selected(itLayer)) {
top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR;
top_snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx();
}
else if (this->sidebar->obj_list()->is_selected(itLayerRoot))
top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR;
2019-08-06 16:16:02 +00:00
bool new_variable_layer_editing_active = (new_flags & UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE) != 0;
bool new_selected_settings_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR) != 0;
bool new_selected_layer_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR) != 0;
bool new_selected_layerroot_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR) != 0;
if (this->view3D->get_canvas3d()->get_gizmos_manager().wants_reslice_supports_on_undo())
top_snapshot_data.flags |= UndoRedo::SnapshotData::RECALCULATE_SLA_SUPPORTS;
2019-08-06 16:16:02 +00:00
// Disable layer editing before the Undo / Redo jump.
if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled())
view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting"));
// Make a copy of the snapshot, undo/redo could invalidate the iterator
const UndoRedo::Snapshot snapshot_copy = *it_snapshot;
// Do the jump in time.
if (it_snapshot->timestamp < this->undo_redo_stack().active_snapshot_time() ?
2019-08-06 16:16:02 +00:00
this->undo_redo_stack().undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), top_snapshot_data, it_snapshot->timestamp) :
this->undo_redo_stack().redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), it_snapshot->timestamp)) {
if (printer_technology_changed) {
// Switch to the other printer technology. Switch to the last printer active for that particular technology.
AppConfig *app_config = wxGetApp().app_config;
app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name);
wxGetApp().preset_bundle->load_presets(*app_config);
// load_current_presets() calls Tab::load_current_preset() -> TabPrint::update() -> Object_list::update_and_show_object_settings_item(),
// but the Object list still keeps pointer to the old Model. Avoid a crash by removing selection first.
this->sidebar->obj_list()->unselect_objects();
2019-08-06 16:16:02 +00:00
// Load the currently selected preset into the GUI, update the preset selection box.
// This also switches the printer technology based on the printer technology of the active printer profile.
wxGetApp().load_current_presets();
}
//FIXME updating the Print config from the Wipe tower config values at the ModelWipeTower.
// This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config.
if (this->printer_technology == ptFFF) {
const DynamicPrintConfig &current_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
Vec2d current_position(current_config.opt_float("wipe_tower_x"), current_config.opt_float("wipe_tower_y"));
double current_rotation = current_config.opt_float("wipe_tower_rotation_angle");
if (current_position != model.wipe_tower.position || current_rotation != model.wipe_tower.rotation) {
DynamicPrintConfig new_config;
new_config.set_key_value("wipe_tower_x", new ConfigOptionFloat(model.wipe_tower.position.x()));
new_config.set_key_value("wipe_tower_y", new ConfigOptionFloat(model.wipe_tower.position.y()));
new_config.set_key_value("wipe_tower_rotation_angle", new ConfigOptionFloat(model.wipe_tower.rotation));
Tab *tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT);
tab_print->load_config(new_config);
tab_print->update_dirty();
}
}
// set selection mode for ObjectList on sidebar
this->sidebar->obj_list()->set_selection_mode(new_selected_settings_on_sidebar ? ObjectList::SELECTION_MODE::smSettings :
new_selected_layer_on_sidebar ? ObjectList::SELECTION_MODE::smLayer :
2019-08-06 16:16:02 +00:00
new_selected_layerroot_on_sidebar ? ObjectList::SELECTION_MODE::smLayerRoot :
ObjectList::SELECTION_MODE::smUndef);
if (new_selected_settings_on_sidebar || new_selected_layer_on_sidebar)
this->sidebar->obj_list()->set_selected_layers_range_idx(layer_range_idx);
this->update_after_undo_redo(snapshot_copy, temp_snapshot_was_taken);
2019-08-06 16:16:02 +00:00
// Enable layer editing after the Undo / Redo jump.
if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active)
view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting"));
}
2019-07-09 18:45:00 +00:00
}
void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool /* temp_snapshot_was_taken */)
{
2019-08-06 16:16:02 +00:00
this->view3D->get_canvas3d()->get_selection().clear();
// Update volumes from the deserializd model, always stop / update the background processing (for both the SLA and FFF technologies).
this->update((unsigned int)UpdateParams::FORCE_BACKGROUND_PROCESSING_UPDATE | (unsigned int)UpdateParams::POSTPONE_VALIDATION_ERROR_MESSAGE);
2019-08-06 16:16:02 +00:00
// Release old snapshots if the memory allocated is excessive. This may remove the top most snapshot if jumping to the very first snapshot.
//if (temp_snapshot_was_taken)
// Release the old snapshots always, as it may have happened, that some of the triangle meshes got deserialized from the snapshot, while some
// triangle meshes may have gotten released from the scene or the background processing, therefore now being calculated into the Undo / Redo stack size.
this->undo_redo_stack().release_least_recently_used();
//YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time)
this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack().selection_deserialized().mode), this->undo_redo_stack().selection_deserialized().volumes_and_instances);
this->view3D->get_canvas3d()->get_gizmos_manager().update_after_undo_redo(snapshot);
2019-07-04 15:33:19 +00:00
wxGetApp().obj_list()->update_after_undo_redo();
if (wxGetApp().get_mode() == comSimple && model_has_advanced_features(this->model)) {
2019-08-06 16:16:02 +00:00
// If the user jumped to a snapshot that require user interface with advanced features, switch to the advanced mode without asking.
// There is a little risk of surprising the user, as he already must have had the advanced or expert mode active for such a snapshot to be taken.
Slic3r::GUI::wxGetApp().save_mode(comAdvanced);
view3D->set_as_dirty();
}
// this->update() above was called with POSTPONE_VALIDATION_ERROR_MESSAGE, so that if an error message was generated when updating the back end, it would not open immediately,
// but it would be saved to be show later. Let's do it now. We do not want to display the message box earlier, because on Windows & OSX the message box takes over the message
// queue pump, which in turn executes the rendering function before a full update after the Undo / Redo jump.
this->show_delayed_error_message();
2019-08-06 16:16:02 +00:00
//FIXME what about the state of the manipulators?
//FIXME what about the focus? Cursor in the side panel?
BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot reloaded. Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info();
}
void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const
{
switch (btn_type)
{
case ActionButtonType::abReslice: p->btn_reslice->SetLabelText(label); break;
case ActionButtonType::abExport: p->btn_export_gcode->SetLabelText(label); break;
2019-12-10 16:31:27 +00:00
case ActionButtonType::abSendGCode: /*p->btn_send_gcode->SetLabelText(label);*/ break;
}
}
2018-09-17 10:15:11 +00:00
// Plater / Public
Plater::Plater(wxWindow *parent, MainFrame *main_frame)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(76 * wxGetApp().em_unit(), 49 * wxGetApp().em_unit()))
, p(new priv(this, main_frame))
2018-09-17 10:15:11 +00:00
{
// Initialization performed in the private c-tor
}
Plater::~Plater()
{
}
Sidebar& Plater::sidebar() { return *p->sidebar; }
Model& Plater::model() { return p->model; }
const Print& Plater::fff_print() const { return p->fff_print; }
Print& Plater::fff_print() { return p->fff_print; }
const SLAPrint& Plater::sla_print() const { return p->sla_print; }
SLAPrint& Plater::sla_print() { return p->sla_print; }
2018-09-17 10:15:11 +00:00
void Plater::new_project()
{
p->select_view_3D("3D");
wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL));
}
void Plater::load_project()
{
// Ask user for a project file name.
wxString input_file;
wxGetApp().load_project(this, input_file);
// And finally load the new project.
load_project(input_file);
}
void Plater::load_project(const wxString& filename)
{
if (filename.empty())
return;
// Take the Undo / Redo snapshot.
Plater::TakeSnapshot snapshot(this, _L("Load Project") + ": " + wxString::FromUTF8(into_path(filename).stem().string().c_str()));
p->reset();
std::vector<fs::path> input_paths;
input_paths.push_back(into_path(filename));
std::vector<size_t> res = load_files(input_paths);
// if res is empty no data has been loaded
if (!res.empty())
p->set_project_filename(filename);
}
void Plater::add_model(bool imperial_units/* = false*/)
{
wxArrayString input_files;
wxGetApp().import_model(this, input_files);
if (input_files.empty())
return;
std::vector<fs::path> paths;
for (const auto &file : input_files)
paths.push_back(into_path(file));
2019-08-06 16:16:02 +00:00
wxString snapshot_label;
assert(! paths.empty());
if (paths.size() == 1) {
snapshot_label = _L("Import Object");
2019-08-06 16:16:02 +00:00
snapshot_label += ": ";
snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
} else {
snapshot_label = _L("Import Objects");
2019-08-06 16:16:02 +00:00
snapshot_label += ": ";
snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
for (size_t i = 1; i < paths.size(); ++ i) {
snapshot_label += ", ";
snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str());
}
}
Plater::TakeSnapshot snapshot(this, snapshot_label);
load_files(paths, true, false, imperial_units);
}
void Plater::import_sl1_archive()
{
2020-04-23 17:45:55 +00:00
p->m_ui_jobs.import_sla_arch();
}
void Plater::extract_config_from_project()
{
wxString input_file;
wxGetApp().load_project(this, input_file);
if (input_file.empty())
return;
std::vector<fs::path> input_paths;
2019-01-02 14:11:05 +00:00
input_paths.push_back(into_path(input_file));
load_files(input_paths, false, true);
}
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); }
2018-09-17 10:15:11 +00:00
// To be called when providing a list of files to the GUI slic3r on command line.
std::vector<size_t> Plater::load_files(const std::vector<std::string>& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/)
2019-08-06 16:16:02 +00:00
{
std::vector<fs::path> paths;
paths.reserve(input_files.size());
for (const std::string& path : input_files)
paths.emplace_back(path);
return p->load_files(paths, load_model, load_config, imperial_units);
}
void Plater::update() { p->update(); }
2018-10-17 10:59:58 +00:00
void Plater::stop_jobs() { p->m_ui_jobs.stop_all(); }
void Plater::update_ui_from_settings() { p->update_ui_from_settings(); }
2018-10-17 10:59:58 +00:00
void Plater::select_view(const std::string& direction) { p->select_view(direction); }
void Plater::select_view_3D(const std::string& name) { p->select_view_3D(name); }
bool Plater::is_preview_shown() const { return p->is_preview_shown(); }
bool Plater::is_preview_loaded() const { return p->is_preview_loaded(); }
bool Plater::is_view3D_shown() const { return p->is_view3D_shown(); }
bool Plater::are_view3D_labels_shown() const { return p->are_view3D_labels_shown(); }
void Plater::show_view3D_labels(bool show) { p->show_view3D_labels(show); }
bool Plater::is_sidebar_collapsed() const { return p->is_sidebar_collapsed(); }
2020-04-29 13:58:57 +00:00
void Plater::collapse_sidebar(bool show) { p->collapse_sidebar(show); }
#if ENABLE_SLOPE_RENDERING
bool Plater::is_view3D_slope_shown() const { return p->is_view3D_slope_shown(); }
void Plater::show_view3D_slope(bool show) { p->show_view3D_slope(show); }
bool Plater::is_view3D_layers_editing_enabled() const { return p->is_view3D_layers_editing_enabled(); }
#endif // ENABLE_SLOPE_RENDERING
2018-11-21 14:28:35 +00:00
void Plater::select_all() { p->select_all(); }
void Plater::deselect_all() { p->deselect_all(); }
2018-11-21 14:28:35 +00:00
2018-10-08 17:14:55 +00:00
void Plater::remove(size_t obj_idx) { p->remove(obj_idx); }
2018-11-22 10:31:53 +00:00
void Plater::reset() { p->reset(); }
void Plater::reset_with_confirm()
{
if (wxMessageDialog((wxWindow*)this, _L("All objects will be removed, continue?"), wxString(SLIC3R_APP_NAME) + " - " + _L("Delete all"), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES)
reset();
}
2018-11-22 10:31:53 +00:00
void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_model(obj_idx); }
2018-10-04 09:12:55 +00:00
void Plater::remove_selected()
{
Plater::TakeSnapshot snapshot(this, _L("Delete Selected Objects"));
this->p->view3D->delete_selected();
2018-10-04 09:12:55 +00:00
}
void Plater::increase_instances(size_t num)
{
if (! can_increase_instances()) { return; }
Plater::TakeSnapshot snapshot(this, _L("Increase Instances"));
int obj_idx = p->get_selected_object_idx();
ModelObject* model_object = p->model.objects[obj_idx];
ModelInstance* model_instance = model_object->instances.back();
bool was_one_instance = model_object->instances.size()==1;
2019-08-06 16:16:02 +00:00
double offset_base = canvas3D()->get_size_proportional_to_max_bed_size(0.05);
double offset = offset_base;
for (size_t i = 0; i < num; i++, offset += offset_base) {
Vec3d offset_vec = model_instance->get_offset() + Vec3d(offset, offset, 0.0);
model_object->add_instance(offset_vec, model_instance->get_scaling_factor(), model_instance->get_rotation(), model_instance->get_mirror());
// p->print.get_object(obj_idx)->add_copy(Slic3r::to_2d(offset_vec));
}
if (p->get_config("autocenter") == "1")
arrange();
p->update();
p->get_selection().add_instance(obj_idx, (int)model_object->instances.size() - 1);
sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num);
p->selection_changed();
this->p->schedule_background_process();
}
void Plater::decrease_instances(size_t num)
{
if (! can_decrease_instances()) { return; }
Plater::TakeSnapshot snapshot(this, _L("Decrease Instances"));
int obj_idx = p->get_selected_object_idx();
ModelObject* model_object = p->model.objects[obj_idx];
if (model_object->instances.size() > num) {
for (size_t i = 0; i < num; ++ i)
model_object->delete_last_instance();
p->update();
// Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model.
sidebar().obj_list()->decrease_object_instances(obj_idx, num);
}
else {
remove(obj_idx);
}
if (!model_object->instances.empty())
p->get_selection().add_instance(obj_idx, (int)model_object->instances.size() - 1);
p->selection_changed();
this->p->schedule_background_process();
}
void Plater::set_number_of_copies(/*size_t num*/)
{
int obj_idx = p->get_selected_object_idx();
if (obj_idx == -1)
return;
ModelObject* model_object = p->model.objects[obj_idx];
const int num = wxGetNumberFromUser( " ", _L("Enter the number of copies:"),
_L("Copies of the selected object"), model_object->instances.size(), 0, 1000, this );
if (num < 0)
return;
Plater::TakeSnapshot snapshot(this, wxString::Format(_L("Set numbers of copies to %d"), num));
2019-07-04 15:33:19 +00:00
int diff = num - (int)model_object->instances.size();
if (diff > 0)
increase_instances(diff);
else if (diff < 0)
decrease_instances(-diff);
}
2018-10-04 09:12:55 +00:00
2018-11-21 14:47:41 +00:00
bool Plater::is_selection_empty() const
{
return p->get_selection().is_empty() || p->get_selection().is_wipe_tower();
2018-11-21 14:47:41 +00:00
}
void Plater::scale_selection_to_fit_print_volume()
{
p->scale_selection_to_fit_print_volume();
}
2018-11-26 09:56:07 +00:00
void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower)
2018-10-18 13:13:38 +00:00
{
wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds");
auto *object = p->model.objects[obj_idx];
wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds");
if (!keep_upper && !keep_lower) {
return;
}
Plater::TakeSnapshot snapshot(this, _L("Cut by Plane"));
2019-07-04 15:33:19 +00:00
wxBusyCursor wait;
2018-11-26 09:56:07 +00:00
const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower);
2018-10-18 13:13:38 +00:00
remove(obj_idx);
p->load_model_objects(new_objects);
2019-12-19 09:06:46 +00:00
Selection& selection = p->get_selection();
size_t last_id = p->model.objects.size() - 1;
for (size_t i = 0; i < new_objects.size(); ++i)
{
selection.add_object((unsigned int)(last_id - i), i == 0);
}
2018-10-18 13:13:38 +00:00
}
void Plater::export_gcode(bool prefer_removable)
2018-10-04 09:12:55 +00:00
{
if (p->model.objects.empty())
return;
2018-09-17 10:15:11 +00:00
// If possible, remove accents from accented latin characters.
// This function is useful for generating file names to be processed by legacy firmwares.
fs::path default_output_file;
try {
2019-08-06 16:16:02 +00:00
// Update the background processing, so that the placeholder parser will get the correct values for the ouput file template.
// Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed.
unsigned int state = this->p->update_restart_background_process(false, false);
if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)
return;
default_output_file = this->p->background_process.output_filepath_for_project(into_path(get_project_filename(".3mf")));
}
catch (const std::exception &ex) {
show_error(this, ex.what());
return;
2018-09-17 10:15:11 +00:00
}
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
AppConfig &appconfig = *wxGetApp().app_config;
RemovableDriveManager &removable_drive_manager = *wxGetApp().removable_drive_manager();
// Get a last save path, either to removable media or to an internal media.
std::string start_dir = appconfig.get_last_output_dir(default_output_file.parent_path().string(), prefer_removable);
if (prefer_removable) {
// Returns a path to a removable media if it exists, prefering start_dir. Update the internal removable drives database.
start_dir = removable_drive_manager.get_removable_drive_path(start_dir);
if (start_dir.empty())
// Direct user to the last internal media.
start_dir = appconfig.get_last_output_dir(default_output_file.parent_path().string(), false);
2019-11-27 12:30:45 +00:00
}
fs::path output_path;
{
wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 file as:"),
start_dir,
from_path(default_output_file.filename()),
GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()),
wxFD_SAVE | wxFD_OVERWRITE_PROMPT
);
if (dlg.ShowModal() == wxID_OK)
output_path = into_path(dlg.GetPath());
}
if (! output_path.empty()) {
bool path_on_removable_media = removable_drive_manager.set_and_verify_last_save_path(output_path.string());
p->export_gcode(output_path, path_on_removable_media, PrintHostJob());
// Storing a path to AppConfig either as path to removable media or a path to internal media.
// is_path_on_removable_drive() is called with the "true" parameter to update its internal database as the user may have shuffled the external drives
// while the dialog was open.
appconfig.update_last_output_dir(output_path.parent_path().string(), path_on_removable_media);
p->writing_to_removable_device = path_on_removable_media;
2019-12-12 09:48:33 +00:00
}
2018-09-17 10:15:11 +00:00
}
2019-05-02 11:46:39 +00:00
void Plater::export_stl(bool extended, bool selection_only)
2018-10-04 09:12:55 +00:00
{
if (p->model.objects.empty()) { return; }
2018-10-04 09:12:55 +00:00
wxString path = p->get_export_file(FT_STL);
if (path.empty()) { return; }
2019-01-02 14:11:05 +00:00
const std::string path_u8 = into_u8(path);
wxBusyCursor wait;
const auto &selection = p->get_selection();
const auto obj_idx = selection.get_object_idx();
if (selection_only && (obj_idx == -1 || selection.is_wipe_tower()))
return;
2019-04-04 13:13:43 +00:00
TriangleMesh mesh;
if (p->printer_technology == ptFFF) {
if (selection_only) {
const ModelObject* model_object = p->model.objects[obj_idx];
if (selection.get_mode() == Selection::Instance)
{
if (selection.is_single_full_object())
mesh = model_object->mesh();
else
mesh = model_object->full_raw_mesh();
}
2019-04-05 09:30:49 +00:00
else
{
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
mesh = model_object->volumes[volume->volume_idx()]->mesh();
mesh.transform(volume->get_volume_transformation().get_matrix());
mesh.translate(-model_object->origin_translation.cast<float>());
}
2019-04-05 09:30:49 +00:00
}
else {
mesh = p->model.mesh();
2019-04-04 13:13:43 +00:00
}
}
else
2019-05-02 11:46:39 +00:00
{
// This is SLA mode, all objects have only one volume.
// However, we must have a look at the backend to load
// hollowed mesh and/or supports
const PrintObjects& objects = p->sla_print.objects();
for (const SLAPrintObject* object : objects)
2019-05-02 11:46:39 +00:00
{
const ModelObject* model_object = object->model_object();
if (selection_only) {
if (model_object->id() != p->model.objects[obj_idx]->id())
continue;
}
Transform3d mesh_trafo_inv = object->trafo().inverse();
bool is_left_handed = object->is_left_handed();
TriangleMesh pad_mesh;
bool has_pad_mesh = extended && object->has_mesh(slaposPad);
if (has_pad_mesh)
2019-05-02 11:46:39 +00:00
{
pad_mesh = object->get_mesh(slaposPad);
pad_mesh.transform(mesh_trafo_inv);
}
2019-05-02 11:46:39 +00:00
TriangleMesh supports_mesh;
bool has_supports_mesh = extended && object->has_mesh(slaposSupportTree);
if (has_supports_mesh)
{
supports_mesh = object->get_mesh(slaposSupportTree);
supports_mesh.transform(mesh_trafo_inv);
}
const std::vector<SLAPrintObject::Instance>& obj_instances = object->instances();
for (const SLAPrintObject::Instance& obj_instance : obj_instances)
{
auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(),
[&obj_instance](const ModelInstance *mi) { return mi->id() == obj_instance.instance_id; });
assert(it != model_object->instances.end());
2019-05-02 11:46:39 +00:00
if (it != model_object->instances.end())
2019-05-02 11:46:39 +00:00
{
bool one_inst_only = selection_only && ! selection.is_single_full_object();
2019-05-02 11:46:39 +00:00
int instance_idx = it - model_object->instances.begin();
const Transform3d& inst_transform = one_inst_only
? Transform3d::Identity()
: object->model_object()->instances[instance_idx]->get_transformation().get_matrix();
2019-05-02 11:46:39 +00:00
if (has_pad_mesh)
{
TriangleMesh inst_pad_mesh = pad_mesh;
inst_pad_mesh.transform(inst_transform, is_left_handed);
mesh.merge(inst_pad_mesh);
}
2019-05-02 11:46:39 +00:00
if (has_supports_mesh)
{
TriangleMesh inst_supports_mesh = supports_mesh;
inst_supports_mesh.transform(inst_transform, is_left_handed);
mesh.merge(inst_supports_mesh);
2019-05-02 11:46:39 +00:00
}
TriangleMesh inst_object_mesh = object->get_mesh_to_print();
2020-02-13 12:49:13 +00:00
inst_object_mesh.transform(mesh_trafo_inv);
inst_object_mesh.transform(inst_transform, is_left_handed);
mesh.merge(inst_object_mesh);
if (one_inst_only)
break;
2019-05-02 11:46:39 +00:00
}
}
}
}
2019-01-02 14:11:05 +00:00
Slic3r::store_stl(path_u8.c_str(), &mesh, true);
p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path));
2018-10-04 09:12:55 +00:00
}
void Plater::export_amf()
{
if (p->model.objects.empty()) { return; }
wxString path = p->get_export_file(FT_AMF);
if (path.empty()) { return; }
2019-01-02 14:11:05 +00:00
const std::string path_u8 = into_u8(path);
wxBusyCursor wait;
bool export_config = true;
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1";
if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames)) {
// Success
p->statusbar()->set_status_text(format_wxstr(_L("AMF file exported to %s"), path));
} else {
// Failure
p->statusbar()->set_status_text(format_wxstr(_L("Error exporting AMF file %s"), path));
}
2018-10-04 09:12:55 +00:00
}
void Plater::export_3mf(const boost::filesystem::path& output_path)
2018-10-04 09:12:55 +00:00
{
if (p->model.objects.empty()) { return; }
wxString path;
bool export_config = true;
if (output_path.empty())
{
path = p->get_export_file(FT_3MF);
if (path.empty()) { return; }
}
else
2019-01-02 14:11:05 +00:00
path = from_path(output_path);
if (!path.Lower().EndsWith(".3mf"))
return;
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
2019-01-02 14:11:05 +00:00
const std::string path_u8 = into_u8(path);
wxBusyCursor wait;
bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1";
ThumbnailData thumbnail_data;
p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true);
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data)) {
// Success
p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path));
p->set_project_filename(path);
}
else {
// Failure
p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path));
}
2018-10-04 09:12:55 +00:00
}
void Plater::reload_from_disk()
{
p->reload_from_disk();
}
void Plater::reload_all_from_disk()
{
p->reload_all_from_disk();
}
bool Plater::has_toolpaths_to_export() const
{
return p->preview->get_canvas3d()->has_toolpaths_to_export();
}
void Plater::export_toolpaths_to_obj() const
{
if ((printer_technology() != ptFFF) || !is_preview_loaded())
return;
wxString path = p->get_export_file(FT_OBJ);
if (path.empty())
return;
wxBusyCursor wait;
p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str());
}
2018-09-17 10:15:11 +00:00
void Plater::reslice()
{
// Stop arrange and (or) optimize rotation tasks.
this->stop_jobs();
if (printer_technology() == ptSLA) {
for (auto& object : model().objects)
if (object->sla_points_status == sla::PointsStatus::NoPoints)
object->sla_points_status = sla::PointsStatus::Generating;
}
2019-08-06 16:16:02 +00:00
//FIXME Don't reslice if export of G-code or sending to OctoPrint is running.
// bitmask of UpdateBackgroundProcessReturnState
unsigned int state = this->p->update_background_process(true);
if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
this->p->view3D->reload_scene(false);
// If the SLA processing of just a single object's supports is running, restart slicing for the whole object.
this->p->background_process.set_task(PrintBase::TaskParams());
// Only restarts if the state is valid.
this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART);
if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
return;
if (p->background_process.running())
{
if (wxGetApp().get_mode() == comSimple)
p->sidebar->set_btn_label(ActionButtonType::abReslice, _L("Slicing") + dots);
else
{
p->sidebar->set_btn_label(ActionButtonType::abReslice, _L("Slice now"));
p->show_action_buttons(false);
}
}
else if (!p->background_process.empty() && !p->background_process.idle())
p->show_action_buttons(true);
// update type of preview
p->preview->update_view_type(true);
2018-09-17 10:15:11 +00:00
}
void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages)
{
reslice_SLA_until_step(slaposPad, object, postpone_error_messages);
}
void Plater::reslice_SLA_hollowing(const ModelObject &object, bool postpone_error_messages)
{
reslice_SLA_until_step(slaposDrillHoles, object, postpone_error_messages);
}
void Plater::reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject &object, bool postpone_error_messages)
{
//FIXME Don't reslice if export of G-code or sending to OctoPrint is running.
// bitmask of UpdateBackgroundProcessReturnState
unsigned int state = this->p->update_background_process(true, postpone_error_messages);
if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
this->p->view3D->reload_scene(false);
if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID))
// Nothing to do on empty input or invalid configuration.
return;
// Limit calculation to the single object only.
PrintBase::TaskParams task;
task.single_model_object = object.id();
// If the background processing is not enabled, calculate supports just for the single instance.
// Otherwise calculate everything, but start with the provided object.
2019-08-06 16:16:02 +00:00
if (!this->p->background_processing_enabled()) {
task.single_model_instance_only = true;
task.to_object_step = step;
2019-08-06 16:16:02 +00:00
}
this->p->background_process.set_task(task);
// and let the background processing start.
this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART);
}
2018-10-04 09:12:55 +00:00
void Plater::send_gcode()
{
2018-12-11 09:33:11 +00:00
if (p->model.objects.empty()) { return; }
PrintHostJob upload_job(p->config);
if (upload_job.empty()) { return; }
// Obtain default output path
fs::path default_output_file;
try {
2019-08-06 16:16:02 +00:00
// Update the background processing, so that the placeholder parser will get the correct values for the ouput file template.
// Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed.
unsigned int state = this->p->update_restart_background_process(false, false);
if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)
return;
default_output_file = this->p->background_process.output_filepath_for_project(into_path(get_project_filename(".3mf")));
}
catch (const std::exception &ex) {
2018-12-11 09:33:11 +00:00
show_error(this, ex.what());
return;
}
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
PrintHostSendDialog dlg(default_output_file, upload_job.printhost->can_start_print());
2018-12-11 09:33:11 +00:00
if (dlg.ShowModal() == wxID_OK) {
upload_job.upload_data.upload_path = dlg.filename();
upload_job.upload_data.start_print = dlg.start_print();
p->export_gcode(fs::path(), false, std::move(upload_job));
2018-12-11 09:33:11 +00:00
}
2018-10-04 09:12:55 +00:00
}
// Called when the Eject button is pressed.
2019-12-11 11:28:51 +00:00
void Plater::eject_drive()
{
wxBusyCursor wait;
wxGetApp().removable_drive_manager()->eject_drive();
2019-12-11 11:28:51 +00:00
}
void Plater::take_snapshot(const std::string &snapshot_name) { p->take_snapshot(snapshot_name); }
void Plater::take_snapshot(const wxString &snapshot_name) { p->take_snapshot(snapshot_name); }
void Plater::suppress_snapshots() { p->suppress_snapshots(); }
void Plater::allow_snapshots() { p->allow_snapshots(); }
void Plater::undo() { p->undo(); }
void Plater::redo() { p->redo(); }
2019-07-09 18:45:00 +00:00
void Plater::undo_to(int selection)
{
if (selection == 0) {
p->undo();
return;
}
2019-08-06 16:16:02 +00:00
2019-07-09 18:45:00 +00:00
const int idx = p->get_active_snapshot_index() - selection - 1;
p->undo_redo_to(p->undo_redo_stack().snapshots()[idx].timestamp);
2019-07-09 18:45:00 +00:00
}
void Plater::redo_to(int selection)
{
if (selection == 0) {
p->redo();
return;
}
2019-08-06 16:16:02 +00:00
const int idx = p->get_active_snapshot_index() + selection + 1;
p->undo_redo_to(p->undo_redo_stack().snapshots()[idx].timestamp);
2019-07-09 18:45:00 +00:00
}
bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** out_text)
{
const std::vector<UndoRedo::Snapshot>& ss_stack = p->undo_redo_stack().snapshots();
2019-07-09 18:45:00 +00:00
const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -(++idx) : idx);
2019-10-29 09:40:34 +00:00
if (0 < idx_in_ss_stack && (size_t)idx_in_ss_stack < ss_stack.size() - 1) {
*out_text = ss_stack[idx_in_ss_stack].name.c_str();
return true;
}
return false;
}
void Plater::undo_redo_topmost_string_getter(const bool is_undo, std::string& out_text)
{
const std::vector<UndoRedo::Snapshot>& ss_stack = p->undo_redo_stack().snapshots();
const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -1 : 0);
2019-10-29 09:40:34 +00:00
if (0 < idx_in_ss_stack && (size_t)idx_in_ss_stack < ss_stack.size() - 1) {
out_text = ss_stack[idx_in_ss_stack].name;
return;
}
out_text = "";
}
bool Plater::search_string_getter(int idx, const char** label, const char** tooltip)
{
const Search::OptionsSearcher& search_list = p->sidebar->get_searcher();
if (0 <= idx && (size_t)idx < search_list.size()) {
search_list[idx].get_marked_label_and_tooltip(label, tooltip);
return true;
}
return false;
}
void Plater::on_extruders_change(size_t num_extruders)
{
auto& choices = sidebar().combos_filament();
if (num_extruders == choices.size())
return;
wxWindowUpdateLocker noUpdates_scrolled_panel(&sidebar()/*.scrolled_panel()*/);
size_t i = choices.size();
while ( i < num_extruders )
{
PresetComboBox* choice/*{ nullptr }*/;
sidebar().init_filament_combo(&choice, i);
choices.push_back(choice);
// initialize selection
2019-12-03 03:48:01 +00:00
wxGetApp().preset_bundle->update_plater_filament_ui(i, choice);
++i;
}
// remove unused choices if any
sidebar().remove_unused_filament_combos(num_extruders);
sidebar().Layout();
sidebar().scrolled_panel()->Refresh();
}
void Plater::on_config_change(const DynamicPrintConfig &config)
{
2018-10-15 08:53:47 +00:00
bool update_scheduled = false;
2019-02-04 09:06:15 +00:00
bool bed_shape_changed = false;
for (auto opt_key : p->config->diff(config)) {
if (opt_key == "filament_colour")
{
update_scheduled = true; // update should be scheduled (for update 3DScene) #2738
/* There is a case, when we use filament_color instead of extruder_color (when extruder_color == "").
* Thus plater config option "filament_colour" should be filled with filament_presets values.
* Otherwise, on 3dScene will be used last edited filament color for all volumes with extruder_color == "".
*/
const std::vector<std::string> filament_presets = wxGetApp().preset_bundle->filament_presets;
if (filament_presets.size() > 1 &&
p->config->option<ConfigOptionStrings>(opt_key)->values.size() != config.option<ConfigOptionStrings>(opt_key)->values.size())
{
const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
std::vector<std::string> filament_colors;
filament_colors.reserve(filament_presets.size());
for (const std::string& filament_preset : filament_presets)
filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0));
p->config->option<ConfigOptionStrings>(opt_key)->values = filament_colors;
2019-10-01 16:19:28 +00:00
p->sidebar->obj_list()->update_extruder_colors();
continue;
}
}
p->config->set_key_value(opt_key, config.option(opt_key)->clone());
if (opt_key == "printer_technology") {
this->set_printer_technology(config.opt_enum<PrinterTechnology>(opt_key));
// print technology is changed, so we should to update a search list
p->sidebar->update_searcher();
}
2019-07-24 12:02:36 +00:00
else if ((opt_key == "bed_shape") || (opt_key == "bed_custom_texture") || (opt_key == "bed_custom_model")) {
2019-02-04 09:06:15 +00:00
bed_shape_changed = true;
2018-10-15 08:53:47 +00:00
update_scheduled = true;
2019-08-06 16:16:02 +00:00
}
2018-12-06 14:40:41 +00:00
else if (boost::starts_with(opt_key, "wipe_tower") ||
// opt_key == "filament_minimal_purge_on_wipe_tower" // ? #ys_FIXME
opt_key == "single_extruder_multi_material") {
2018-10-15 08:53:47 +00:00
update_scheduled = true;
2019-08-06 16:16:02 +00:00
}
2018-10-15 08:53:47 +00:00
else if(opt_key == "variable_layer_height") {
if (p->config->opt_bool("variable_layer_height") != true) {
p->view3D->enable_layers_editing(false);
p->view3D->set_as_dirty();
2018-10-15 08:53:47 +00:00
}
}
2018-10-15 08:53:47 +00:00
else if(opt_key == "extruder_colour") {
update_scheduled = true;
p->preview->set_number_extruders(p->config->option<ConfigOptionStrings>(opt_key)->values.size());
2019-10-01 16:19:28 +00:00
p->sidebar->obj_list()->update_extruder_colors();
2018-10-15 08:53:47 +00:00
} else if(opt_key == "max_print_height") {
update_scheduled = true;
}
else if (opt_key == "printer_model") {
2018-10-15 08:53:47 +00:00
// update to force bed selection(for texturing)
2019-02-04 09:06:15 +00:00
bed_shape_changed = true;
2018-10-15 08:53:47 +00:00
update_scheduled = true;
}
2018-10-15 08:53:47 +00:00
}
2019-02-04 09:06:15 +00:00
if (bed_shape_changed)
p->set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values,
2019-07-24 12:02:36 +00:00
p->config->option<ConfigOptionString>("bed_custom_texture")->value,
p->config->option<ConfigOptionString>("bed_custom_model")->value);
2019-02-04 09:06:15 +00:00
2019-08-06 16:16:02 +00:00
if (update_scheduled)
2018-10-15 08:53:47 +00:00
update();
if (p->main_frame->is_loaded())
this->p->schedule_background_process();
}
void Plater::set_bed_shape() const
{
p->set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values,
p->config->option<ConfigOptionString>("bed_custom_texture")->value,
p->config->option<ConfigOptionString>("bed_custom_model")->value);
}
void Plater::force_filament_colors_update()
{
bool update_scheduled = false;
DynamicPrintConfig* config = p->config;
const std::vector<std::string> filament_presets = wxGetApp().preset_bundle->filament_presets;
if (filament_presets.size() > 1 &&
p->config->option<ConfigOptionStrings>("filament_colour")->values.size() == filament_presets.size())
{
const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
std::vector<std::string> filament_colors;
filament_colors.reserve(filament_presets.size());
for (const std::string& filament_preset : filament_presets)
filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0));
if (config->option<ConfigOptionStrings>("filament_colour")->values != filament_colors) {
config->option<ConfigOptionStrings>("filament_colour")->values = filament_colors;
update_scheduled = true;
}
}
2019-10-01 16:19:28 +00:00
if (update_scheduled) {
update();
2019-10-01 16:19:28 +00:00
p->sidebar->obj_list()->update_extruder_colors();
}
if (p->main_frame->is_loaded())
this->p->schedule_background_process();
}
void Plater::force_print_bed_update()
{
// Fill in the printer model key with something which cannot possibly be valid, so that Plater::on_config_change() will update the print bed
// once a new Printer profile config is loaded.
p->config->opt_string("printer_model", true) = "\x01\x00\x01";
}
void Plater::on_activate()
{
#ifdef __linux__
wxWindow *focus_window = wxWindow::FindFocus();
// Activating the main frame, and no window has keyboard focus.
// Set the keyboard focus to the visible Canvas3D.
if (this->p->view3D->IsShown() && (!focus_window || focus_window == this->p->view3D->get_wxglcanvas()))
this->p->view3D->get_wxglcanvas()->SetFocus();
else if (this->p->preview->IsShown() && (!focus_window || focus_window == this->p->view3D->get_wxglcanvas()))
this->p->preview->get_wxglcanvas()->SetFocus();
#endif
this->p->show_delayed_error_message();
}
// Get vector of extruder colors considering filament color, if extruder color is undefined.
2019-09-30 12:03:50 +00:00
std::vector<std::string> Plater::get_extruder_colors_from_plater_config() const
{
const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
std::vector<std::string> extruder_colors;
if (!config->has("extruder_colour")) // in case of a SLA print
return extruder_colors;
extruder_colors = (config->option<ConfigOptionStrings>("extruder_colour"))->values;
if (!wxGetApp().plater())
return extruder_colors;
2019-09-30 12:03:50 +00:00
const std::vector<std::string>& filament_colours = (p->config->option<ConfigOptionStrings>("filament_colour"))->values;
for (size_t i = 0; i < extruder_colors.size(); ++i)
if (extruder_colors[i] == "" && i < filament_colours.size())
extruder_colors[i] = filament_colours[i];
return extruder_colors;
}
/* Get vector of colors used for rendering of a Preview scene in "Color print" mode
* It consists of extruder colors and colors, saved in model.custom_gcode_per_print_z
*/
std::vector<std::string> Plater::get_colors_for_color_print() const
{
std::vector<std::string> colors = get_extruder_colors_from_plater_config();
colors.reserve(colors.size() + p->model.custom_gcode_per_print_z.gcodes.size());
for (const CustomGCode::Item& code : p->model.custom_gcode_per_print_z.gcodes)
if (code.gcode == ColorChangeCode)
colors.emplace_back(code.color);
return colors;
}
wxString Plater::get_project_filename(const wxString& extension) const
{
return p->get_project_filename(extension);
}
void Plater::set_project_filename(const wxString& filename)
{
return p->set_project_filename(filename);
}
bool Plater::is_export_gcode_scheduled() const
{
return p->background_process.is_export_scheduled();
}
const Selection &Plater::get_selection() const
{
return p->get_selection();
}
2018-11-01 11:33:56 +00:00
int Plater::get_selected_object_idx()
{
return p->get_selected_object_idx();
}
bool Plater::is_single_full_object_selection() const
{
return p->get_selection().is_single_full_object();
}
GLCanvas3D* Plater::canvas3D()
{
return p->view3D->get_canvas3d();
}
GLCanvas3D* Plater::get_current_canvas3D()
{
return p->get_current_canvas3D();
}
BoundingBoxf Plater::bed_shape_bb() const
{
return p->bed_shape_bb();
}
void Plater::arrange()
{
p->m_ui_jobs.arrange();
}
void Plater::set_current_canvas_as_dirty()
{
p->set_current_canvas_as_dirty();
}
void Plater::unbind_canvas_event_handlers()
{
p->unbind_canvas_event_handlers();
}
void Plater::reset_canvas_volumes()
{
p->reset_canvas_volumes();
}
PrinterTechnology Plater::printer_technology() const
{
return p->printer_technology;
}
const DynamicPrintConfig * Plater::config() const { return p->config; }
void Plater::set_printer_technology(PrinterTechnology printer_technology)
{
p->printer_technology = printer_technology;
if (p->background_process.select_technology(printer_technology)) {
// Update the active presets.
}
2019-08-06 16:16:02 +00:00
//FIXME for SLA synchronize
//p->background_process.apply(Model)!
p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export");
p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer");
if (wxGetApp().mainframe)
wxGetApp().mainframe->update_menubar();
p->update_main_toolbar_tooltips();
p->sidebar->get_searcher().set_printer_technology(printer_technology);
}
2018-10-31 14:41:27 +00:00
void Plater::changed_object(int obj_idx)
{
if (obj_idx < 0)
return;
// recenter and re - align to Z = 0
auto model_object = p->model.objects[obj_idx];
model_object->ensure_on_bed();
if (this->p->printer_technology == ptSLA) {
// Update the SLAPrint from the current Model, so that the reload_scene()
// pulls the correct data, update the 3D scene.
this->p->update_restart_background_process(true, false);
}
else
p->view3D->reload_scene(false);
2018-11-07 14:29:13 +00:00
// update print
this->p->schedule_background_process();
}
void Plater::changed_objects(const std::vector<size_t>& object_idxs)
{
if (object_idxs.empty())
return;
for (size_t obj_idx : object_idxs)
{
if (obj_idx < p->model.objects.size())
// recenter and re - align to Z = 0
p->model.objects[obj_idx]->ensure_on_bed();
}
if (this->p->printer_technology == ptSLA) {
// Update the SLAPrint from the current Model, so that the reload_scene()
// pulls the correct data, update the 3D scene.
this->p->update_restart_background_process(true, false);
}
else
p->view3D->reload_scene(false);
// update print
this->p->schedule_background_process();
}
2019-06-26 07:48:52 +00:00
void Plater::schedule_background_process(bool schedule/* = true*/)
{
2019-06-26 07:48:52 +00:00
if (schedule)
this->p->schedule_background_process();
this->p->suppressed_backround_processing_update = false;
}
bool Plater::is_background_process_update_scheduled() const
2019-06-26 07:48:52 +00:00
{
return this->p->background_process_timer.IsRunning();
}
void Plater::suppress_background_process(const bool stop_background_process)
{
if (stop_background_process)
this->p->background_process_timer.Stop();
this->p->suppressed_backround_processing_update = true;
}
void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) { p->fix_through_netfabb(obj_idx, vol_idx); }
2018-09-17 10:15:11 +00:00
void Plater::update_object_menu() { p->update_object_menu(); }
void Plater::show_action_buttons(const bool ready_to_slice) const { p->show_action_buttons(ready_to_slice); }
void Plater::copy_selection_to_clipboard()
{
if (can_copy_to_clipboard())
p->view3D->get_canvas3d()->get_selection().copy_to_clipboard();
}
void Plater::paste_from_clipboard()
{
2019-07-04 15:33:19 +00:00
if (!can_paste_from_clipboard())
return;
Plater::TakeSnapshot snapshot(this, _L("Paste From Clipboard"));
2019-07-04 15:33:19 +00:00
p->view3D->get_canvas3d()->get_selection().paste_from_clipboard();
}
void Plater::search(bool plater_is_active)
{
if (plater_is_active) {
// plater should be focused for correct navigation inside search window
this->SetFocus();
wxKeyEvent evt;
#ifdef __APPLE__
evt.m_keyCode = 'f';
#else /* __APPLE__ */
evt.m_keyCode = WXK_CONTROL_F;
#endif /* __APPLE__ */
evt.SetControlDown(true);
canvas3D()->on_char(evt);
}
else
{
wxPoint pos = this->ClientToScreen(wxPoint(0, 0));
pos.x += em_unit(this) * 40;
pos.y += em_unit(this) * 4;
p->sidebar->get_searcher().search_dialog->Popup(pos);
}
}
void Plater::msw_rescale()
{
p->preview->msw_rescale();
p->view3D->get_canvas3d()->msw_rescale();
p->sidebar->msw_rescale();
p->msw_rescale_object_menu();
Layout();
GetParent()->Layout();
}
bool Plater::init_view_toolbar()
{
return p->init_view_toolbar();
}
const Camera& Plater::get_camera() const
{
return p->camera;
}
Camera& Plater::get_camera()
{
return p->camera;
}
const Bed3D& Plater::get_bed() const
{
return p->bed;
}
Bed3D& Plater::get_bed()
{
return p->bed;
}
const GLToolbar& Plater::get_view_toolbar() const
{
return p->view_toolbar;
}
GLToolbar& Plater::get_view_toolbar()
{
return p->view_toolbar;
}
const Mouse3DController& Plater::get_mouse3d_controller() const
{
return p->mouse3d_controller;
}
Mouse3DController& Plater::get_mouse3d_controller()
{
return p->mouse3d_controller;
}
bool Plater::can_delete() const { return p->can_delete(); }
bool Plater::can_delete_all() const { return p->can_delete_all(); }
bool Plater::can_increase_instances() const { return p->can_increase_instances(); }
bool Plater::can_decrease_instances() const { return p->can_decrease_instances(); }
bool Plater::can_set_instance_to_object() const { return p->can_set_instance_to_object(); }
bool Plater::can_fix_through_netfabb() const { return p->can_fix_through_netfabb(); }
bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); }
bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); }
bool Plater::can_arrange() const { return p->can_arrange(); }
bool Plater::can_layers_editing() const { return p->can_layers_editing(); }
bool Plater::can_paste_from_clipboard() const
{
const Selection& selection = p->view3D->get_canvas3d()->get_selection();
const Selection::Clipboard& clipboard = selection.get_clipboard();
if (clipboard.is_empty())
return false;
if ((wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) && !clipboard.is_sla_compliant())
return false;
Selection::EMode mode = clipboard.get_mode();
if ((mode == Selection::Volume) && !selection.is_from_single_instance())
return false;
if ((mode == Selection::Instance) && (selection.get_mode() != Selection::Instance))
return false;
return true;
}
bool Plater::can_copy_to_clipboard() const
{
if (is_selection_empty())
return false;
const Selection& selection = p->view3D->get_canvas3d()->get_selection();
if ((wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) && !selection.is_sla_compliant())
return false;
return true;
}
bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot(); }
bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); }
bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); }
const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); }
void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); }
void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); }
bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); }
2019-07-08 08:57:35 +00:00
// Wrapper around wxWindow::PopupMenu to suppress error messages popping out while tracking the popup menu.
bool Plater::PopupMenu(wxMenu *menu, const wxPoint& pos)
{
// Don't want to wake up and trigger reslicing while tracking the pop-up menu.
SuppressBackgroundProcessingUpdate sbpu;
// When tracking a pop-up menu, postpone error messages from the slicing result.
m_tracking_popup_menu = true;
bool out = this->wxPanel::PopupMenu(menu, pos);
m_tracking_popup_menu = false;
if (! m_tracking_popup_menu_error_message.empty()) {
// Don't know whether the CallAfter is necessary, but it should not hurt.
// The menus likely sends out some commands, so we may be safer if the dialog is shown after the menu command is processed.
wxString message = std::move(m_tracking_popup_menu_error_message);
wxTheApp->CallAfter([message, this]() { show_error(this, message); });
m_tracking_popup_menu_error_message.clear();
}
return out;
}
2019-07-08 08:57:35 +00:00
2019-06-26 07:48:52 +00:00
SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() :
m_was_scheduled(wxGetApp().plater()->is_background_process_update_scheduled())
2019-06-26 07:48:52 +00:00
{
wxGetApp().plater()->suppress_background_process(m_was_scheduled);
2019-06-26 07:48:52 +00:00
}
SuppressBackgroundProcessingUpdate::~SuppressBackgroundProcessingUpdate()
{
wxGetApp().plater()->schedule_background_process(m_was_scheduled);
2019-06-26 07:48:52 +00:00
}
2018-09-17 10:15:11 +00:00
}} // namespace Slic3r::GUI