PrusaSlicer-NonPlainar/src/slic3r/GUI/Plater.cpp
2018-11-26 10:41:16 +01:00

2819 lines
100 KiB
C++

#include "Plater.hpp"
#include <cstddef>
#include <algorithm>
#include <vector>
#include <string>
#include <regex>
#include <boost/optional.hpp>
#include <boost/filesystem/path.hpp>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/notebook.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>
#include <wx/wupdlock.h>
#include <wx/colordlg.h>
#include <wx/numdlg.h>
#include <wx/debug.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/ModelArrange.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/GCode/PreviewData.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Format/STL.hpp"
#include "libslic3r/Format/AMF.hpp"
#include "libslic3r/Format/3mf.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
#include "GUI_ObjectManipulation.hpp"
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
#include "MainFrame.hpp"
#include "3DScene.hpp"
#include "GLCanvas3D.hpp"
#include "GLToolbar.hpp"
#include "GUI_Preview.hpp"
#include "Tab.hpp"
#include "PresetBundle.hpp"
#include "BackgroundSlicingProcess.hpp"
#include "ProgressStatusBar.hpp"
#include "slic3r/Utils/ASCIIFolding.hpp"
#include "../Utils/FixModelByWin10.hpp"
#include "SLA/SLARotfinder.hpp"
#include <wx/glcanvas.h> // Needs to be last because reasons :-/
#include "WipeTowerDialog.hpp"
using boost::optional;
namespace fs = boost::filesystem;
using Slic3r::_3DScene;
using Slic3r::Preset;
namespace Slic3r {
namespace GUI {
wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent);
wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent);
wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent);
// 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);
wxStaticBitmap *manifold_warning_icon;
wxStaticText *info_size;
wxStaticText *info_volume;
wxStaticText *info_facets;
wxStaticText *info_materials;
wxStaticText *info_manifold;
bool showing_manifold_warning_icon;
void show_sizer(bool show);
};
ObjectInfo::ObjectInfo(wxWindow *parent) :
wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _(L("Info"))), wxVERTICAL)
{
GetStaticBox()->SetFont(wxGetApp().bold_font());
auto *grid_sizer = new wxFlexGridSizer(4, 5, 5);
grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
grid_sizer->AddGrowableCol(1, 1);
grid_sizer->AddGrowableCol(3, 1);
auto init_info_label = [parent, grid_sizer](wxStaticText **info_label, wxString text_label) {
auto *text = new wxStaticText(parent, wxID_ANY, text_label);
text->SetFont(wxGetApp().small_font());
*info_label = new wxStaticText(parent, wxID_ANY, "");
(*info_label)->SetFont(wxGetApp().small_font());
grid_sizer->Add(text, 0);
grid_sizer->Add(*info_label, 0);
};
init_info_label(&info_size, _(L("Size:")));
init_info_label(&info_volume, _(L("Volume:")));
init_info_label(&info_facets, _(L("Facets:")));
init_info_label(&info_materials, _(L("Materials:")));
Add(grid_sizer, 0, wxEXPAND);
auto *info_manifold_text = new wxStaticText(parent, wxID_ANY, _(L("Manifold:")));
info_manifold_text->SetFont(wxGetApp().small_font());
info_manifold = new wxStaticText(parent, wxID_ANY, "");
info_manifold->SetFont(wxGetApp().small_font());
wxBitmap bitmap(GUI::from_u8(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG);
manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, bitmap);
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);
Add(sizer_manifold, 0, wxEXPAND | wxTOP, 4);
}
void ObjectInfo::show_sizer(bool show)
{
Show(show);
if (show)
manifold_warning_icon->Show(showing_manifold_warning_icon && show);
}
enum SlisedInfoIdx
{
siFilament_m,
siFilament_mm3,
siFilament_g,
siCost,
siEstimatedTime,
siWTNumbetOfToolchanges,
siCount
};
class SlicedInfo : public wxStaticBoxSizer
{
public:
SlicedInfo(wxWindow *parent);
void SetTextAndShow(SlisedInfoIdx idx, const wxString& text, const wxString& new_label="");
private:
std::vector<std::pair<wxStaticText*, wxStaticText*>> info_vec;
};
SlicedInfo::SlicedInfo(wxWindow *parent) :
wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _(L("Sliced Info"))), wxVERTICAL)
{
GetStaticBox()->SetFont(wxGetApp().bold_font());
auto *grid_sizer = new wxFlexGridSizer(2, 5, 15);
grid_sizer->SetFlexibleDirection(wxVERTICAL);
info_vec.reserve(siCount);
auto init_info_label = [this, parent, grid_sizer](wxString text_label) {
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());
grid_sizer->Add(text, 0);
grid_sizer->Add(info_label, 0);
info_vec.push_back(std::pair<wxStaticText*, wxStaticText*>(text, info_label));
};
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("Cost")));
init_info_label(_(L("Estimated printing time")));
init_info_label(_(L("Number of tool changes")));
Add(grid_sizer, 0, wxEXPAND);
this->Show(false);
}
void SlicedInfo::SetTextAndShow(SlisedInfoIdx 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);
}
PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) :
wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY),
preset_type(preset_type),
last_selected(wxNOT_FOUND)
{
Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) {
auto selected_item = this->GetSelection();
auto marker = reinterpret_cast<Marker>(this->GetClientData(selected_item));
if (marker == LABEL_ITEM_MARKER) {
this->SetSelection(this->last_selected);
evt.StopPropagation();
} else if (this->last_selected != selected_item) {
this->last_selected = selected_item;
evt.SetInt(this->preset_type);
evt.Skip();
} else {
evt.StopPropagation();
}
});
if (preset_type == Slic3r::Preset::TYPE_FILAMENT)
{
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) {
if (extruder_idx < 0 || event.GetLogicalPosition(wxClientDC(this)).x > 24) {
// Let the combo box process the mouse click.
event.Skip();
return;
}
// Swallow the mouse click and open the color picker.
auto data = new wxColourData();
data->SetChooseFull(1);
auto dialog = new wxColourDialog(wxGetApp().mainframe, data);
if (dialog->ShowModal() == wxID_OK) {
DynamicPrintConfig cfg = *wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config();
//FIXME this is too expensive to call full_config to get just the extruder color!
auto colors = static_cast<ConfigOptionStrings*>(wxGetApp().preset_bundle->full_config().option("extruder_colour")->clone());
colors->values[extruder_idx] = dialog->GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX);
cfg.set_key_value("extruder_colour", colors);
wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg);
wxGetApp().preset_bundle->update_platter_filament_ui(extruder_idx, this);
wxGetApp().plater()->on_config_change(cfg);
}
dialog->Destroy();
});
}
}
PresetComboBox::~PresetComboBox() {}
void PresetComboBox::set_label_marker(int item)
{
this->SetClientData(item, (void*)LABEL_ITEM_MARKER);
}
// Frequently changed parameters
class FreqChangedParams : public OG_Settings
{
double m_brim_width = 0.0;
wxButton* m_wiping_dialog_button{ nullptr };
public:
FreqChangedParams(wxWindow* parent, const int label_width);
~FreqChangedParams() {}
wxButton* get_wiping_dialog_button() { return m_wiping_dialog_button; }
void Show(const bool show);
};
FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) :
OG_Settings(parent, false)
{
DynamicPrintConfig* config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
m_og->set_config(config);
m_og->label_width = label_width;
m_og->m_on_change = [config, this](t_config_option_key opt_key, boost::any value) {
TabPrint* tab_print = nullptr;
for (size_t i = 0; i < wxGetApp().tab_panel()->GetPageCount(); ++i) {
Tab *tab = dynamic_cast<Tab*>(wxGetApp().tab_panel()->GetPage(i));
if (!tab)
continue;
if (tab->name() == "print") {
tab_print = static_cast<TabPrint*>(tab);
break;
}
}
if (tab_print == nullptr)
return;
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;
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 ? 10 :
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{ //(opt_key == "support")
const wxString& selection = boost::any_cast<wxString>(value);
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));
else if (selection == _("Support on build plate only"))
new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(true));
}
tab_print->load_config(new_conf);
}
tab_print->update_dirty();
};
Option option = m_og->get_option("fill_density");
option.opt.sidetext = "";
option.opt.full_width = true;
m_og->append_single_option_line(option);
ConfigOptionDef def;
def.label = L("Support");
def.type = coStrings;
def.gui_type = "select_open";
def.tooltip = L("Select what kind of support do you need");
def.enum_labels.push_back(L("None"));
def.enum_labels.push_back(L("Support on build plate only"));
def.enum_labels.push_back(L("Everywhere"));
std::string selection = !config->opt_bool("support_material") ?
"None" :
config->opt_bool("support_material_buildplate_only") ?
"Support on build plate only" :
"Everywhere";
def.default_value = new ConfigOptionStrings{ selection };
option = Option(def, "support");
option.opt.full_width = true;
m_og->append_single_option_line(option);
m_brim_width = config->opt_float("brim_width");
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.default_value = new ConfigOptionBool{ m_brim_width > 0.0 ? true : false };
option = Option(def, "brim");
m_og->append_single_option_line(option);
Line line = { "", "" };
line.widget = [config, this](wxWindow* parent) {
m_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(m_wiping_dialog_button);
m_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e)
{
auto &config = wxGetApp().preset_bundle->project_config;
const std::vector<double> &init_matrix = (config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values;
const std::vector<double> &init_extruders = (config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values;
WipingDialog dlg(parent, cast<float>(init_matrix), cast<float>(init_extruders));
if (dlg.ShowModal() == wxID_OK) {
std::vector<float> matrix = dlg.get_matrix();
std::vector<float> extruders = dlg.get_extruders();
(config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values = std::vector<double>(matrix.begin(), matrix.end());
(config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values = std::vector<double>(extruders.begin(), extruders.end());
g_on_request_update_callback.call();
}
}));
return sizer;
};
m_og->append_line(line);
}
void FreqChangedParams::Show(const bool show)
{
bool is_wdb_shown = m_wiping_dialog_button->IsShown();
m_og->Show(show);
// correct showing of the FreqChangedParams sizer when m_wiping_dialog_button is hidden
if (show && !is_wdb_shown)
m_wiping_dialog_button->Hide();
}
// Sidebar / private
struct Sidebar::priv
{
Plater *plater;
wxScrolledWindow *scrolled;
wxFlexGridSizer *sizer_presets;
PresetComboBox *combo_print;
std::vector<PresetComboBox*> combos_filament;
wxBoxSizer *sizer_filaments;
PresetComboBox *combo_sla_print;
PresetComboBox *combo_sla_material;
PresetComboBox *combo_printer;
wxBoxSizer *sizer_params;
FreqChangedParams *frequently_changed_parameters;
ObjectList *object_list;
ObjectManipulation *object_manipulation;
ObjectSettings *object_settings;
ObjectInfo *object_info;
SlicedInfo *sliced_info;
wxButton *btn_export_gcode;
wxButton *btn_reslice;
// wxButton *btn_print; // XXX: remove
wxButton *btn_send_gcode;
priv(Plater *plater) : plater(plater) {}
void show_preset_comboboxes();
};
void Sidebar::priv::show_preset_comboboxes()
{
const bool showSLA = plater->printer_technology() == ptSLA;
wxWindowUpdateLocker noUpdates_scrolled(scrolled->GetParent());
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();
}
// Sidebar / public
Sidebar::Sidebar(Plater *parent)
: wxPanel(parent), p(new priv(parent))
{
p->scrolled = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(400, -1));
p->scrolled->SetScrollbars(0, 1, 1, 1);
// Sizer in the scrolled area
auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL);
p->scrolled->SetSizer(scrolled_sizer);
// The preset chooser
p->sizer_presets = new wxFlexGridSizer(5, 2, 1, 2);
p->sizer_presets->AddGrowableCol(1, 1);
p->sizer_presets->SetFlexibleDirection(wxBOTH);
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->scrolled, wxID_ANY, label);
text->SetFont(wxGetApp().small_font());
*combo = new PresetComboBox(p->scrolled, preset_type);
auto *sizer_presets = this->p->sizer_presets;
auto *sizer_filaments = this->p->sizer_filaments;
sizer_presets->Add(text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
if (! filament) {
sizer_presets->Add(*combo, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 1);
} else {
sizer_filaments->Add(*combo, 1, wxEXPAND | wxBOTTOM, 1);
(*combo)->set_extruder_idx(0);
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")), 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);
// calculate width of the preset labels
p->sizer_presets->Layout();
const wxArrayInt& ar = p->sizer_presets->GetColWidths();
int label_width = ar.IsEmpty() ? 100 : ar.front()-4;
p->sizer_params = new wxBoxSizer(wxVERTICAL);
// Frequently changed parameters
p->frequently_changed_parameters = new FreqChangedParams(p->scrolled, label_width);
p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxBOTTOM | wxLEFT, 2);
// Object List
p->object_list = new ObjectList(p->scrolled);
p->sizer_params->Add(p->object_list->get_sizer(), 1, wxEXPAND | wxTOP, 20);
// Object Manipulations
p->object_manipulation = new ObjectManipulation(p->scrolled);
p->object_manipulation->Hide();
p->sizer_params->Add(p->object_manipulation->get_sizer(), 0, wxEXPAND | wxLEFT | wxTOP, 20);
// Frequently Object Settings
p->object_settings = new ObjectSettings(p->scrolled);
p->object_settings->Hide();
p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxLEFT | wxTOP, 20);
// Buttons in the scrolled area
wxBitmap arrow_up(GUI::from_u8(Slic3r::var("brick_go.png")), wxBITMAP_TYPE_PNG);
p->btn_send_gcode = new wxButton(p->scrolled, wxID_ANY, _(L("Send to printer")));
p->btn_send_gcode->SetBitmap(arrow_up);
p->btn_send_gcode->Hide();
auto *btns_sizer_scrolled = new wxBoxSizer(wxHORIZONTAL);
btns_sizer_scrolled->Add(p->btn_send_gcode);
// Info boxes
p->object_info = new ObjectInfo(p->scrolled);
p->sliced_info = new SlicedInfo(p->scrolled);
// Sizer in the scrolled area
scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, 2);
scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND);
scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, 20);
scrolled_sizer->Add(btns_sizer_scrolled, 0, wxEXPAND, 0);
scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND | wxTOP | wxLEFT, 20);
// Buttons underneath the scrolled area
p->btn_export_gcode = new wxButton(this, wxID_ANY, _(L("Export G-code…")));
p->btn_export_gcode->SetFont(wxGetApp().bold_font());
p->btn_reslice = new wxButton(this, wxID_ANY, _(L("Slice now")));
p->btn_reslice->SetFont(wxGetApp().bold_font());
auto *btns_sizer = new wxBoxSizer(wxVERTICAL);
btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, 5);
btns_sizer->Add(p->btn_export_gcode, 0, wxEXPAND | wxTOP, 5);
auto *sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(p->scrolled, 1, wxEXPAND | wxTOP, 5);
sizer->Add(btns_sizer, 0, wxEXPAND | wxLEFT, 20);
SetSizer(sizer);
// Events
p->btn_export_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(); });
p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->reslice(); });
p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); });
}
Sidebar::~Sidebar() {}
void Sidebar::init_filament_combo(PresetComboBox **combo, const int extr_idx) {
*combo = new PresetComboBox(p->scrolled, Slic3r::Preset::TYPE_FILAMENT);
// # copy icons from first choice
// $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets;
(*combo)->set_extruder_idx(extr_idx);
auto /***/sizer_filaments = this->p->sizer_filaments;
sizer_filaments->Add(*combo, 1, wxEXPAND | wxBOTTOM, 1);
}
void Sidebar::remove_unused_filament_combos(const int 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();
}
}
void Sidebar::update_presets(Preset::Type preset_type)
{
PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
switch (preset_type) {
case Preset::TYPE_FILAMENT:
if (p->combos_filament.size() == 1) {
// 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);
}
for (size_t i = 0; i < p->combos_filament.size(); i++) {
preset_bundle.update_platter_filament_ui(i, p->combos_filament[i]);
}
break;
case Preset::TYPE_PRINT:
preset_bundle.prints.update_platter_ui(p->combo_print);
break;
case Preset::TYPE_SLA_PRINT:
preset_bundle.sla_prints.update_platter_ui(p->combo_sla_print);
break;
case Preset::TYPE_SLA_MATERIAL:
preset_bundle.sla_materials.update_platter_ui(p->combo_sla_material);
break;
case Preset::TYPE_PRINTER:
{
// Update the print choosers to only contain the compatible presets, update the dirty flags.
if (p->plater->printer_technology() == ptFFF)
preset_bundle.prints.update_platter_ui(p->combo_print);
else {
preset_bundle.sla_prints.update_platter_ui(p->combo_sla_print);
preset_bundle.sla_materials.update_platter_ui(p->combo_sla_material);
}
// Update the printer choosers, update the dirty flags.
preset_bundle.printers.update_platter_ui(p->combo_printer);
// Update the filament choosers to only contain the compatible presets, update the color preview,
// update the dirty flags.
if (p->plater->printer_technology() == ptFFF) {
for (size_t i = 0; i < p->combos_filament.size(); ++ i)
preset_bundle.update_platter_filament_ui(i, p->combos_filament[i]);
}
p->show_preset_comboboxes();
break;
}
default: break;
}
// Synchronize config.ini with the current selections.
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
}
ObjectManipulation* Sidebar::obj_manipul()
{
return p->object_manipulation;
}
ObjectList* Sidebar::obj_list()
{
return p->object_list;
}
ObjectSettings* Sidebar::obj_settings()
{
return p->object_settings;
}
wxScrolledWindow* Sidebar::scrolled_panel()
{
return p->scrolled;
}
ConfigOptionsGroup* Sidebar::og_freq_chng_params()
{
return p->frequently_changed_parameters->get_og();
}
wxButton* Sidebar::get_wiping_dialog_button()
{
return p->frequently_changed_parameters->get_wiping_dialog_button();
}
void Sidebar::update_objects_list_extruder_column(int extruders_count)
{
p->object_list->update_objects_list_extruder_column(extruders_count);
}
void Sidebar::show_info_sizer()
{
if (!p->plater->is_single_full_object_selection() ||
m_mode < ConfigMenuModeExpert ||
p->plater->model().objects.empty()) {
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;
}
const ModelInstance* model_instance = !model_object->instances.empty() ? model_object->instances.front() : nullptr;
auto size = model_object->bounding_box().size();
p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0), size(1), size(2)));
p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast<int>(model_object->materials_count())));
auto& stats = model_object->volumes.front()->mesh.stl.stats;
auto sf = model_instance->get_scaling_factor();
p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume * sf(0) * sf(1) * sf(2)));
p->object_info->info_facets->SetLabel(wxString::Format(_(L("%d (%d shells)")), static_cast<int>(model_object->facets_count()), stats.number_of_parts));
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);
p->object_info->info_manifold->SetLabel(tooltip);
tooltip += wxString::Format(_(L(":\n%d degenerate facets, %d edges fixed, %d facets removed, "
"%d facets added, %d facets reversed, %d backwards edges")),
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);
}
else {
p->object_info->info_manifold->SetLabel(L("Yes"));
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);
}
void Sidebar::show_sliced_info_sizer(const bool show)
{
wxWindowUpdateLocker freeze_guard(this);
p->sliced_info->Show(show);
if (show) {
const PrintStatistics& ps = p->plater->print().print_statistics();
const bool is_wipe_tower = ps.total_wipe_tower_filament > 0;
wxString new_label = _(L("Used Filament (m)"));
if (is_wipe_tower)
new_label += wxString::Format(" :\n - %s\n - %s", _(L("objects")), _(L("wipe tower")));
wxString info_text = is_wipe_tower ?
wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / 1000,
(ps.total_used_filament - ps.total_wipe_tower_filament) / 1000,
ps.total_wipe_tower_filament / 1000) :
wxString::Format("%.2f", ps.total_used_filament / 1000);
p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label);
p->sliced_info->SetTextAndShow(siFilament_mm3, wxString::Format("%.2f", ps.total_extruded_volume));
p->sliced_info->SetTextAndShow(siFilament_g, wxString::Format("%.2f", ps.total_weight));
new_label = _(L("Cost"));
if (is_wipe_tower)
new_label += wxString::Format(" :\n - %s\n - %s", _(L("objects")), _(L("wipe tower")));
info_text = is_wipe_tower ?
wxString::Format("%.2f \n%.2f \n%.2f", ps.total_cost,
(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 = "Estimated printing time :";
info_text = "";
if (ps.estimated_normal_print_time != "N/A") {
new_label += wxString::Format("\n - %s", _(L("normal mode")));
info_text += wxString::Format("\n%s", ps.estimated_normal_print_time);
}
if (ps.estimated_silent_print_time != "N/A") {
new_label += wxString::Format("\n - %s", _(L("silent mode")));
info_text += wxString::Format("\n%s", ps.estimated_silent_print_time);
}
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", p->plater->print().wipe_tower_data().number_of_toolchanges) : "N/A");
}
Layout();
p->scrolled->Refresh();
}
void Sidebar::show_buttons(const bool show)
{
p->btn_reslice->Show(show);
for (size_t i = 0; i < wxGetApp().tab_panel()->GetPageCount(); ++i) {
TabPrinter *tab = dynamic_cast<TabPrinter*>(wxGetApp().tab_panel()->GetPage(i));
if (!tab)
continue;
if (p->plater->printer_technology() == ptFFF) {
p->btn_send_gcode->Show(show && !tab->m_config->opt_string("print_host").empty());
}
break;
}
}
void Sidebar::enable_buttons(bool enable)
{
p->btn_reslice->Enable(enable);
p->btn_export_gcode->Enable(enable);
p->btn_send_gcode->Enable(enable);
}
void Sidebar::show_button(ButtonAction but_action, bool show)
{
switch (but_action)
{
case baReslice:
p->btn_reslice->Show(show);
break;
case baExportGcode:
p->btn_export_gcode->Show(show);
break;
case baSendGcode:
p->btn_send_gcode->Show(show);
break;
default:
break;
}
}
bool Sidebar::is_multifilament()
{
return p->combos_filament.size() > 1;
}
std::vector<PresetComboBox*>& Sidebar::combos_filament()
{
return p->combos_filament;
}
// Plater::DropTarget
class PlaterDropTarget : public wxFileDropTarget
{
public:
PlaterDropTarget(Plater *plater) : plater(plater) {}
virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames);
private:
Plater *plater;
static const std::regex pattern_drop;
};
const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)", std::regex::icase);
bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames)
{
std::vector<fs::path> paths;
for (const auto &filename : filenames) {
fs::path path(filename);
if (std::regex_match(path.string(), pattern_drop)) {
paths.push_back(std::move(path));
} else {
return false;
}
}
plater->load_files(paths);
return true;
}
// Plater / private
struct Plater::priv
{
// PIMPL back pointer ("Q-Pointer")
Plater *q;
MainFrame *main_frame;
// Object popup menu
wxMenu object_menu;
wxMenuItem* item_sla_autorot = nullptr;
// Data
Slic3r::DynamicPrintConfig *config;
Slic3r::Print print;
Slic3r::SLAPrint sla_print;
Slic3r::Model model;
PrinterTechnology printer_technology = ptFFF;
Slic3r::GCodePreviewData gcode_preview_data;
// GUI elements
wxNotebook *notebook;
Sidebar *sidebar;
wxPanel *panel3d;
wxGLCanvas *canvas3Dwidget; // TODO: Use GLCanvas3D when we can
GLCanvas3D *canvas3D;
Preview *preview;
#if ENABLE_NEW_MENU_LAYOUT
wxString project_filename;
#endif // ENABLE_NEW_MENU_LAYOUT
BackgroundSlicingProcess background_process;
std::atomic<bool> arranging;
std::atomic<bool> rotoptimizing;
bool delayed_scene_refresh;
wxTimer background_process_timer;
static const std::regex pattern_bundle;
static const std::regex pattern_3mf;
static const std::regex pattern_zip_amf;
priv(Plater *q, MainFrame *main_frame);
void update(bool force_full_scene_refresh = false);
void select_view(const std::string& direction);
void update_ui_from_settings();
ProgressStatusBar* statusbar();
std::string get_config(const std::string &key) const;
BoundingBoxf bed_shape_bb() const;
BoundingBox scaled_bed_shape_bb() const;
#if ENABLE_NEW_MENU_LAYOUT
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config);
#else
std::vector<size_t> load_files(const std::vector<fs::path> &input_files);
#endif // ENABLE_NEW_MENU_LAYOUT
std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
std::unique_ptr<CheckboxFileDialog> get_export_file(GUI::FileType file_type);
const GLCanvas3D::Selection& get_selection() const;
GLCanvas3D::Selection& get_selection();
int get_selected_object_idx() const;
void selection_changed();
void object_list_changed();
void select_all();
void remove(size_t obj_idx);
void delete_object_from_model(size_t obj_idx);
void reset();
void mirror(Axis axis);
void arrange();
void sla_optimize_rotation();
void split_object();
void split_volume();
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))
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,
};
// returns bit mask of UpdateBackgroundProcessReturnState
unsigned int update_background_process();
void async_apply_config();
void reload_from_disk();
void export_object_stl();
void fix_through_netfabb(const int obj_idx);
void on_notebook_changed(wxBookCtrlEvent&);
void on_select_preset(wxCommandEvent&);
void on_slicing_update(SlicingStatusEvent&);
void on_slicing_completed(wxCommandEvent&);
void on_process_completed(wxCommandEvent&);
void on_layer_editing_toggled(bool enable);
void on_schedule_background_process(SimpleEvent&);
void on_action_add(SimpleEvent&);
void on_action_split_objects(SimpleEvent&);
void on_action_split_volumes(SimpleEvent&);
void on_action_layersediting(SimpleEvent&);
void on_object_select(SimpleEvent&);
void on_viewport_changed(SimpleEvent&);
void on_right_click(Vec2dEvent&);
void on_model_update(SimpleEvent&);
void on_wipetower_moved(Vec3dEvent&);
void on_enable_action_buttons(Event<bool>&);
void on_update_geometry(Vec3dsEvent<2>&);
void on_3dcanvas_mouse_dragging_finished(SimpleEvent&);
private:
bool init_object_menu();
bool can_delete_object() const;
bool can_increase_instances() const;
bool can_decrease_instances() const;
bool can_split_to_objects() const;
bool can_split_to_volumes() const;
bool layers_height_allowed() const;
bool can_delete_all() const;
bool can_arrange() const;
bool can_mirror() const;
void update_sla_scene();
};
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);
Plater::priv::priv(Plater *q, MainFrame *main_frame)
: q(q)
, main_frame(main_frame)
, config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({
"bed_shape", "complete_objects", "extruder_clearance_radius", "skirts", "skirt_distance",
"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",
"extruder_colour", "filament_colour", "max_print_height", "printer_model", "printer_technology"
}))
, notebook(new wxNotebook(q, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_BOTTOM))
, sidebar(new Sidebar(q))
, panel3d(new wxPanel(notebook, wxID_ANY))
, canvas3Dwidget(GLCanvas3DManager::create_wxglcanvas(panel3d))
, canvas3D(nullptr)
, delayed_scene_refresh(false)
#if ENABLE_NEW_MENU_LAYOUT
, project_filename(wxEmptyString)
#endif // ENABLE_NEW_MENU_LAYOUT
{
arranging.store(false);
background_process.set_fff_print(&print);
background_process.set_sla_print(&sla_print);
background_process.set_gcode_preview_data(&gcode_preview_data);
background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
background_process.set_finished_event(EVT_PROCESS_COMPLETED);
// Default printer technology for default config.
background_process.select_technology(this->printer_technology);
// Register progress callback from the Print class to the Platter.
auto statuscb = [this](const Slic3r::PrintBase::SlicingStatus &status) {
wxQueueEvent(this->q, new Slic3r::SlicingStatusEvent(EVT_SLICING_UPDATE, 0, status));
};
print.set_status_callback(statuscb);
sla_print.set_status_callback(statuscb);
this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this);
_3DScene::add_canvas(canvas3Dwidget);
this->canvas3D = _3DScene::get_canvas(this->canvas3Dwidget);
this->canvas3D->allow_multisample(GLCanvas3DManager::can_multisample());
auto *panel3dsizer = new wxBoxSizer(wxVERTICAL);
panel3dsizer->Add(canvas3Dwidget, 1, wxEXPAND);
auto *panel_gizmo_widgets = new wxPanel(panel3d, wxID_ANY);
panel_gizmo_widgets->SetSizer(new wxBoxSizer(wxVERTICAL));
panel3dsizer->Add(panel_gizmo_widgets, 0, wxEXPAND);
panel3d->SetSizer(panel3dsizer);
notebook->AddPage(panel3d, _(L("3D")));
preview = new GUI::Preview(notebook, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); });
canvas3D->set_external_gizmo_widgets_parent(panel_gizmo_widgets);
// XXX: If have OpenGL
this->canvas3D->enable_picking(true);
this->canvas3D->enable_moving(true);
// XXX: more config from 3D.pm
this->canvas3D->set_model(&model);
this->canvas3D->set_process(&background_process);
this->canvas3D->set_config(config);
this->canvas3D->enable_gizmos(true);
this->canvas3D->enable_toolbar(true);
this->canvas3D->enable_shader(true);
this->canvas3D->enable_force_zoom_to_bed(true);
this->background_process_timer.SetOwner(this->q, 0);
this->q->Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->async_apply_config(); });
auto *bed_shape = config->opt<ConfigOptionPoints>("bed_shape");
this->canvas3D->set_bed_shape(bed_shape->values);
this->canvas3D->zoom_to_bed();
preview->set_bed_shape(bed_shape->values);
update();
auto *hsizer = new wxBoxSizer(wxHORIZONTAL);
hsizer->Add(notebook, 1, wxEXPAND | wxTOP, 1);
hsizer->Add(sidebar, 0, wxEXPAND | wxLEFT | wxRIGHT, 0);
q->SetSizer(hsizer);
init_object_menu();
// Events:
// Notebook page change event
notebook->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &priv::on_notebook_changed, this);
// Preset change event
sidebar->Bind(wxEVT_COMBOBOX, &priv::on_select_preset, this);
sidebar->Bind(EVT_OBJ_LIST_OBJECT_SELECT, [this](wxEvent&) { priv::selection_changed(); });
// 3DScene events:
canvas3Dwidget->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, &priv::on_schedule_background_process, this);
canvas3Dwidget->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this);
canvas3Dwidget->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this);
canvas3Dwidget->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this);
canvas3Dwidget->Bind(EVT_GLCANVAS_MODEL_UPDATE, &priv::on_model_update, this);
canvas3Dwidget->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); });
canvas3Dwidget->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); });
canvas3Dwidget->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [q](Event<int> &evt) { evt.data == 1 ? q->increase_instances() : q->decrease_instances(); });
canvas3Dwidget->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
canvas3Dwidget->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this);
canvas3Dwidget->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, &priv::on_enable_action_buttons, this);
canvas3Dwidget->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this);
canvas3Dwidget->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this);
// 3DScene/Toolbar:
canvas3Dwidget->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
canvas3Dwidget->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); } );
canvas3Dwidget->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { reset(); });
canvas3Dwidget->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); });
canvas3Dwidget->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); });
canvas3Dwidget->Bind(EVT_GLTOOLBAR_FEWER, [q](SimpleEvent&) { q->decrease_instances(); });
canvas3Dwidget->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this);
canvas3Dwidget->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this);
canvas3Dwidget->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this);
// Preview events:
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this);
q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this);
// Drop target:
q->SetDropTarget(new PlaterDropTarget(q)); // if my understanding is right, wxWindow takes the owenership
update_ui_from_settings();
q->Layout();
}
void Plater::priv::update(bool force_full_scene_refresh)
{
wxWindowUpdateLocker freeze_guard(q);
if (get_config("autocenter") == "1") {
// 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();
const Vec2d& bed_center = bed_shape_bb().center();
model.center_instances_around_point(bed_center);
}
if (this->printer_technology == ptSLA) {
// Update the SLAPrint from the current Model, so that the reload_scene()
// pulls the correct data.
this->update_background_process();
}
this->canvas3D->reload_scene(false, force_full_scene_refresh);
preview->reset_gcode_preview_data();
preview->reload_print();
this->schedule_background_process();
}
void Plater::priv::select_view(const std::string& direction)
{
int page_id = notebook->GetSelection();
if (page_id != wxNOT_FOUND)
{
const wxString& page_text = notebook->GetPageText(page_id);
if (page_text == _(L("3D")))
this->canvas3D->select_view(direction);
else if (page_text == _(L("Preview")))
preview->select_view(direction);
}
}
void Plater::priv::update_ui_from_settings()
{
// TODO: (?)
// my ($self) = @_;
// if (defined($self->{btn_reslice}) && $self->{buttons_sizer}->IsShown($self->{btn_reslice}) != (! wxTheApp->{app_config}->get("background_processing"))) {
// $self->{buttons_sizer}->Show($self->{btn_reslice}, ! wxTheApp->{app_config}->get("background_processing"));
// $self->{buttons_sizer}->Layout;
// }
}
ProgressStatusBar* Plater::priv::statusbar()
{
return main_frame->m_statusbar;
}
std::string Plater::priv::get_config(const std::string &key) const
{
return wxGetApp().app_config->get(key);
}
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
{
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();
}
#if ENABLE_NEW_MENU_LAYOUT
std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config)
#else
std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path> &input_files)
#endif // ENABLE_NEW_MENU_LAYOUT
{
if (input_files.empty()) { return std::vector<size_t>(); }
auto *nozzle_dmrs = config->opt<ConfigOptionFloats>("nozzle_diameter");
bool one_by_one = input_files.size() == 1 || nozzle_dmrs->values.size() <= 1;
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…"));
wxProgressDialog dlg(loading, loading);
dlg.Pulse();
#if ENABLE_NEW_MENU_LAYOUT
auto *new_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model();
#else
auto *new_model = one_by_one ? nullptr : new Slic3r::Model();
#endif // ENABLE_NEW_MENU_LAYOUT
std::vector<size_t> obj_idxs;
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 = wxString::Format(_(L("Processing input file %s\n")), filename.string());
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);
Slic3r::Model model;
try {
if (type_3mf || type_zip_amf) {
DynamicPrintConfig config;
{
DynamicPrintConfig config_loaded;
model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, false);
#if ENABLE_NEW_MENU_LAYOUT
if (load_config && !config_loaded.empty()) {
#else
if (!config_loaded.empty()) {
#endif // ENABLE_NEW_MENU_LAYOUT
// Based on the printer technology field found in the loaded config, select the base for the config,
PrinterTechnology printer_technology = Preset::printer_technology(config_loaded);
config.apply(printer_technology == ptFFF ?
static_cast<const ConfigBase&>(FullPrintConfig::defaults()) :
static_cast<const ConfigBase&>(SLAFullPrintConfig::defaults()));
// and place the loaded config over the base.
config += std::move(config_loaded);
}
}
#if ENABLE_NEW_MENU_LAYOUT
if (load_config)
{
#endif // ENABLE_NEW_MENU_LAYOUT
if (!config.empty()) {
Preset::normalize(config);
wxGetApp().preset_bundle->load_config_model(filename.string(), std::move(config));
wxGetApp().load_current_presets();
}
wxGetApp().app_config->update_config_dir(path.parent_path().string());
#if ENABLE_NEW_MENU_LAYOUT
}
#endif // ENABLE_NEW_MENU_LAYOUT
}
else {
model = Slic3r::Model::read_from_file(path.string(), nullptr, false);
for (auto obj : model.objects)
if (obj->name.empty())
obj->name = fs::path(obj->input_file).filename().string();
}
}
catch (const std::runtime_error &e) {
GUI::show_error(q, e.what());
continue;
}
#if ENABLE_NEW_MENU_LAYOUT
if (load_model)
{
#endif // ENABLE_NEW_MENU_LAYOUT
// The model should now be initialized
if (model.looks_like_multipart_object()) {
wxMessageDialog dlg(q, _(L(
"This file contains several objects positioned at multiple heights. "
"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 (dlg.ShowModal() == wxID_YES) {
model.convert_multipart_object(nozzle_dmrs->values.size());
}
}
if (type_3mf) {
for (ModelObject* model_object : model.objects) {
model_object->center_around_origin();
model_object->ensure_on_bed();
}
}
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);
}
}
#if ENABLE_NEW_MENU_LAYOUT
}
#endif // ENABLE_NEW_MENU_LAYOUT
}
if (new_model != nullptr) {
wxMessageDialog dlg(q, _(L(
"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 (dlg.ShowModal() == wxID_YES) {
new_model->convert_multipart_object(nozzle_dmrs->values.size());
}
auto loaded_idxs = load_model_objects(new_model->objects);
obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
}
#if ENABLE_NEW_MENU_LAYOUT
if (load_model)
{
#endif // ENABLE_NEW_MENU_LAYOUT
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")));
#if ENABLE_NEW_MENU_LAYOUT
}
#endif // ENABLE_NEW_MENU_LAYOUT
return obj_idxs;
}
std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &model_objects)
{
const BoundingBoxf bed_shape = bed_shape_bb();
#if !ENABLE_MODELVOLUME_TRANSFORM
const Vec3d bed_center = Slic3r::to_3d(bed_shape.center().cast<double>(), 0.0);
#endif // !ENABLE_MODELVOLUME_TRANSFORM
const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast<double>(), 1.0);
bool need_arrange = false;
bool scaled_down = false;
std::vector<size_t> obj_idxs;
unsigned int obj_count = model.objects.size();
for (ModelObject *model_object : model_objects) {
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++);
if (model_object->instances.empty()) {
// if object has no defined position(s) we need to rearrange everything after loading
need_arrange = true;
// add a default instance and center object around origin
object->center_around_origin(); // also aligns object to Z = 0
ModelInstance* instance = object->add_instance();
#if ENABLE_MODELVOLUME_TRANSFORM
instance->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -object->origin_translation(2)));
#else
instance->set_offset(bed_center);
#endif // ENABLE_MODELVOLUME_TRANSFORM
}
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
// const Vec3d inverse = ratio.cwiseInverse();
// object->scale(inverse);
object->scale(ratio.cwiseInverse());
scaled_down = true;
} else if (max_ratio > 5) {
const Vec3d inverse = ratio.cwiseInverse();
for (ModelInstance *instance : model_object->instances) {
instance->set_scaling_factor(inverse);
}
}
object->ensure_on_bed();
// print.auto_assign_extruders(object);
// print.add_model_object(object);
}
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?")));
}
for (const size_t idx : obj_idxs) {
wxGetApp().obj_list()->add_object_to_list(idx);
}
update();
#if !ENABLE_MODIFIED_CAMERA_TARGET
this->canvas3D->zoom_to_volumes();
#endif // !ENABLE_MODIFIED_CAMERA_TARGET
object_list_changed();
this->schedule_background_process();
return obj_idxs;
}
std::unique_ptr<CheckboxFileDialog> Plater::priv::get_export_file(GUI::FileType file_type)
{
wxString wildcard;
switch (file_type) {
case FT_STL:
case FT_AMF:
case FT_3MF:
case FT_GCODE:
wildcard = file_wildcards[file_type];
break;
// FT_GCODE
default:
wildcard = file_wildcards[FT_MODEL];
break;
}
fs::path output_file(print.output_filepath(std::string()));
switch (file_type) {
case FT_STL: output_file.replace_extension("stl"); break;
case FT_AMF: output_file.replace_extension("zip.amf"); break; // XXX: Problem on OS X with double extension?
case FT_3MF: output_file.replace_extension("3mf"); break;
default: break;
}
wxGetApp().preset_bundle->export_selections(print.placeholder_parser());
auto dlg = Slic3r::make_unique<CheckboxFileDialog>(q,
((file_type == FT_AMF) || (file_type == FT_3MF)) ? _(L("Export print config")) : "",
true,
_(L("Save file as:")),
output_file.parent_path().string(),
output_file.filename().string(),
wildcard,
wxFD_SAVE | wxFD_OVERWRITE_PROMPT
);
if (dlg->ShowModal() != wxID_OK) {
return nullptr;
}
fs::path path(dlg->GetPath());
wxGetApp().app_config->update_last_output_dir(path.parent_path().string());
return dlg;
}
const GLCanvas3D::Selection& Plater::priv::get_selection() const
{
return canvas3D->get_selection();
}
GLCanvas3D::Selection& Plater::priv::get_selection()
{
return 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;
}
void Plater::priv::selection_changed()
{
this->canvas3D->enable_toolbar_item("delete", can_delete_object());
this->canvas3D->enable_toolbar_item("more", can_increase_instances());
this->canvas3D->enable_toolbar_item("fewer", can_decrease_instances());
this->canvas3D->enable_toolbar_item("splitobjects", can_split_to_objects());
this->canvas3D->enable_toolbar_item("splitvolumes", can_split_to_volumes());
this->canvas3D->enable_toolbar_item("layersediting", layers_height_allowed());
// forces a frame render to update the view (to avoid a missed update if, for example, the context menu appears)
this->canvas3D->render();
}
void Plater::priv::object_list_changed()
{
// Enable/disable buttons depending on whether there are any objects on the platter.
this->canvas3D->enable_toolbar_item("deleteall", can_delete_all());
this->canvas3D->enable_toolbar_item("arrange", can_arrange());
const bool export_in_progress = this->background_process.is_export_scheduled(); // || ! send_gcode_file.empty());
// XXX: is this right?
const bool model_fits = this->canvas3D->check_volumes_outside_state(config) == ModelInstance::PVS_Inside;
sidebar->enable_buttons(!model.objects.empty() && !export_in_progress && model_fits);
}
void Plater::priv::select_all()
{
this->canvas3D->select_all();
}
void Plater::priv::remove(size_t obj_idx)
{
// Prevent toolpaths preview from rendering while we modify the Print object
preview->set_enabled(false);
if (this->canvas3D->is_layers_editing_enabled())
this->canvas3D->enable_layers_editing(false);
model.delete_object(obj_idx);
// print.delete_object(obj_idx);
// Delete object from Sidebar list
sidebar->obj_list()->delete_object_from_list(obj_idx);
object_list_changed();
update();
}
void Plater::priv::delete_object_from_model(size_t obj_idx)
{
model.delete_object(obj_idx);
object_list_changed();
update();
}
void Plater::priv::reset()
{
#if ENABLE_NEW_MENU_LAYOUT
project_filename.Clear();
#endif // ENABLE_NEW_MENU_LAYOUT
// Prevent toolpaths preview from rendering while we modify the Print object
preview->set_enabled(false);
if (this->canvas3D->is_layers_editing_enabled())
this->canvas3D->enable_layers_editing(false);
// Stop and reset the Print content.
this->background_process.reset();
model.clear_objects();
// Delete all objects from list on c++ side
sidebar->obj_list()->delete_all_objects_from_list();
object_list_changed();
update();
}
void Plater::priv::mirror(Axis axis)
{
this->canvas3D->mirror_selection(axis);
}
void Plater::priv::arrange()
{
// don't do anything if currently arranging. Then this is a re-entrance
if(arranging.load()) return;
// Guard the arrange process
arranging.store(true);
// Disable the arrange button (to prevent reentrancies, we will call wxYied)
this->canvas3D->enable_toolbar_item("arrange", can_arrange());
this->background_process.stop();
unsigned count = 0;
for(auto obj : model.objects) count += obj->instances.size();
auto prev_range = statusbar()->get_range();
statusbar()->set_range(count);
auto statusfn = [this, count] (unsigned st, const std::string& msg) {
/* // In case we would run the arrange asynchronously
wxCommandEvent event(EVT_PROGRESS_BAR);
event.SetInt(st);
event.SetString(msg);
wxQueueEvent(this->q, event.Clone()); */
statusbar()->set_progress(count - st);
statusbar()->set_status_text(msg);
// ok, this is dangerous, but we are protected by the atomic flag
// 'arranging' and the arrange button is also disabled.
// This call is needed for the cancel button to work.
wxYieldIfNeeded();
};
statusbar()->set_cancel_callback([this, statusfn](){
arranging.store(false);
statusfn(0, L("Arranging canceled"));
});
static const std::string arrangestr = L("Arranging");
// FIXME: I don't know how to obtain the minimum distance, it depends
// on printer technology. I guess the following should work but it crashes.
double dist = 6; //PrintConfig::min_object_distance(config);
auto min_obj_distance = static_cast<coord_t>(dist/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)));
statusfn(0, arrangestr);
try {
arr::BedShapeHint hint;
// TODO: from Sasha from GUI or
hint.type = arr::BedShapeType::WHO_KNOWS;
arr::arrange(model,
min_obj_distance,
bed,
hint,
false, // create many piles not just one pile
[statusfn](unsigned st) { statusfn(st, arrangestr); },
[this] () { return !arranging.load(); });
} catch(std::exception& /*e*/) {
GUI::show_error(this->q, L("Could not arrange model objects! "
"Some geometries may be invalid."));
}
statusfn(0, L("Arranging done."));
statusbar()->set_range(prev_range);
statusbar()->set_cancel_callback(); // remove cancel button
arranging.store(false);
// We enable back the arrange button
this->canvas3D->enable_toolbar_item("arrange", can_arrange());
// Do a full refresh of scene tree, including regenerating all the GLVolumes.
//FIXME The update function shall just reload the modified matrices.
update(true);
}
// This method will find an optimal orientation for the currently selected item
// Very similar in nature to the arrange method above...
void Plater::priv::sla_optimize_rotation() {
// TODO: we should decide whether to allow arrange when the search is
// running we should probably disable explicit slicing and background
// processing
if(rotoptimizing.load()) return;
rotoptimizing.store(true);
int obj_idx = get_selected_object_idx();
ModelObject * o = model.objects[obj_idx];
background_process.stop();
auto prev_range = statusbar()->get_range();
statusbar()->set_range(100);
auto stfn = [this] (unsigned st, const std::string& msg) {
statusbar()->set_progress(st);
statusbar()->set_status_text(msg);
// could be problematic, but we need the cancel button.
wxYieldIfNeeded();
};
statusbar()->set_cancel_callback([this, stfn](){
rotoptimizing.store(false);
stfn(0, L("Orientation search canceled"));
});
auto r = sla::find_best_rotation(
*o, .1f,
[stfn](unsigned s) { stfn(s, L("Searching for optimal orientation")); },
[this](){ return !rotoptimizing.load(); }
);
if(rotoptimizing.load()) // wasn't canceled
for(ModelInstance * oi : o->instances) oi->set_rotation({r[X], r[Y], r[Z]});
stfn(0, L("Orientation found."));
statusbar()->set_range(prev_range);
statusbar()->set_cancel_callback();
rotoptimizing.store(false);
update(true);
}
void Plater::priv::split_object()
{
int obj_idx = get_selected_object_idx();
if (obj_idx == -1)
return;
// 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.")));
return;
}
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.")));
else
{
unsigned int counter = 1;
for (ModelObject* m : new_objects)
m->name = current_model_object->name + "_" + std::to_string(counter++);
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);
}
}
}
void Plater::priv::split_volume()
{
wxGetApp().obj_list()->split(false);
}
void Plater::priv::schedule_background_process()
{
// Trigger the timer event after 0.5s
this->background_process_timer.Start(500, wxTIMER_ONE_SHOT);
}
// Update background processing thread from the current config and Model.
// Returns a bitmask of UpdateBackgroundProcessReturnState.
unsigned int Plater::priv::update_background_process()
{
// bitmap of enum UpdateBackgroundProcessReturnState
unsigned int return_state = 0;
// If the async_apply_config() was not called by the timer, kill the timer, so the async_apply_config()
// will not be called again in vain.
this->background_process_timer.Stop();
DynamicPrintConfig config = wxGetApp().preset_bundle->full_config();
BoundingBox bed_box_2D = get_extents(Polygon::new_scale(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_(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);
// Apply new config to the possibly running background task.
Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), std::move(config));
// Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile.
if (this->canvas3D->is_layers_editing_enabled())
this->canvas3Dwidget->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.
this->gcode_preview_data.reset();
switch (this->printer_technology) {
case ptFFF:
if (this->preview != nullptr)
// If the preview is not visible, the following line just invalidates the preview,
// but the G-code paths are calculated first once the preview is made visible.
this->preview->reload_print();
// We also need to reload 3D scene because of the wipe tower preview box
if (this->config->opt_bool("wipe_tower")) {
// std::vector<int> selections = this->collect_selections();
// this->canvas3D->set_objects_selections(selections);
return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE;
}
break;
case ptSLA:
return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE;
break;
}
}
if (! this->background_process.empty()) {
std::string err = this->background_process.validate();
if (err.empty()) {
if (invalidated != Print::APPLY_STATUS_UNCHANGED)
return_state |= UPDATE_BACKGROUND_PROCESS_RESTART;
} else {
// The print is not valid.
GUI::show_error(this->q, _(err));
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
}
}
return return_state;
}
void Plater::priv::async_apply_config()
{
// bitmask of UpdateBackgroundProcessReturnState
unsigned int state = this->update_background_process();
if (state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
this->canvas3D->reload_scene(false);
if ((state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 && this->get_config("background_processing") == "1") {
// 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();
});
}
}
void Plater::priv::update_sla_scene()
{
// Update the SLAPrint from the current Model, so that the reload_scene()
// pulls the correct data.
if (this->update_background_process() & UPDATE_BACKGROUND_PROCESS_RESTART)
this->schedule_background_process();
this->canvas3D->reload_scene(true);
delayed_scene_refresh = false;
}
void Plater::priv::reload_from_disk()
{
// TODO
}
void Plater::priv::export_object_stl()
{
// TODO
}
void Plater::priv::fix_through_netfabb(const int obj_idx)
{
if (obj_idx < 0)
return;
const auto model_object = model.objects[obj_idx];
Model model_fixed;// = new Model();
fix_model_by_win10_sdk_gui(*model_object, print, model_fixed);
auto new_obj_idxs = load_model_objects(model_fixed.objects);
if (new_obj_idxs.empty())
return;
for(auto new_obj_idx : new_obj_idxs) {
auto o = model.objects[new_obj_idx];
o->clear_instances();
for (auto instance: model_object->instances)
o->add_instance(*instance);
// o->invalidate_bounding_box();
if (o->volumes.size() == model_object->volumes.size()) {
for (int i = 0; i < o->volumes.size(); i++) {
o->volumes[i]->config.apply(model_object->volumes[i]->config);
}
}
// FIXME restore volumes and their configs, layer_height_ranges, layer_height_profile, layer_height_profile_valid,
}
remove(obj_idx);
}
void Plater::priv::on_notebook_changed(wxBookCtrlEvent&)
{
const auto current_id = notebook->GetCurrentPage()->GetId();
if (current_id == panel3d->GetId()) {
if (this->canvas3D->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.
if (this->update_background_process() & UPDATE_BACKGROUND_PROCESS_RESTART)
this->schedule_background_process();
}
this->canvas3D->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)
this->canvas3D->set_as_dirty();
} else if (current_id == preview->GetId()) {
preview->reload_print();
preview->set_canvas_as_dirty();
}
}
void Plater::priv::on_select_preset(wxCommandEvent &evt)
{
auto preset_type = static_cast<Preset::Type>(evt.GetInt());
auto *combo = static_cast<PresetComboBox*>(evt.GetEventObject());
auto idx = combo->get_extruder_idx();
//! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox,
//! but the OSX version derived from wxOwnerDrawnCombo.
//! So, to get selected string we do
//! combo->GetString(combo->GetSelection())
//! instead of
//! combo->GetStringSelection().ToStdString());
std::string selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data();
if (preset_type == Preset::TYPE_FILAMENT) {
wxGetApp().preset_bundle->set_filament_preset(idx, selected_string);
}
// TODO: ?
if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) {
// Only update the platter UI for the 2nd and other filaments.
wxGetApp().preset_bundle->update_platter_filament_ui(idx, combo);
}
else {
for (Tab* tab : wxGetApp().tabs_list) {
if (tab->type() == preset_type) {
tab->select_preset(selected_string);
break;
}
}
}
// update plater with new config
wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config());
}
void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
{
this->statusbar()->set_progress(evt.status.percent);
this->statusbar()->set_status_text(_(L(evt.status.text)) + wxString::FromUTF8(""));
if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SCENE) {
switch (this->printer_technology) {
case ptFFF:
if (this->preview != nullptr)
this->preview->reload_print();
break;
case ptSLA:
if (this->canvas3D->is_dragging())
delayed_scene_refresh = true;
else
this->update_sla_scene();
break;
}
}
}
void Plater::priv::on_slicing_completed(wxCommandEvent &)
{
switch (this->printer_technology) {
case ptFFF:
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:
// auto selections = collect_selections();
// this->canvas3D->set_objects_selections(selections);
// this->canvas3D->reload_scene(true);
break;
case ptSLA:
if (this->canvas3D->is_dragging())
delayed_scene_refresh = true;
else
this->update_sla_scene();
break;
}
}
void Plater::priv::on_process_completed(wxCommandEvent &evt)
{
// 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();
bool canceled = evt.GetInt() < 0;
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 (! success) {
wxString message = evt.GetString();
if (message.IsEmpty())
message = _(L("Export failed"));
this->statusbar()->set_status_text(message);
}
if (canceled)
this->statusbar()->set_status_text(L("Cancelled"));
this->sidebar->show_sliced_info_sizer(success);
// this updates buttons status
//$self->object_list_changed;
// refresh preview
switch (this->printer_technology) {
case ptFFF:
if (this->preview != nullptr)
this->preview->reload_print();
break;
case ptSLA:
if (this->canvas3D->is_dragging())
delayed_scene_refresh = true;
else
this->update_sla_scene();
break;
}
}
void Plater::priv::on_layer_editing_toggled(bool enable)
{
this->canvas3D->enable_layers_editing(enable);
if (enable && ! this->canvas3D->is_layers_editing_enabled()) {
// Initialization of the OpenGL shaders failed. Disable the tool.
this->canvas3D->enable_toolbar_item("layersediting", false);
}
canvas3Dwidget->Refresh();
canvas3Dwidget->Update();
}
void Plater::priv::on_schedule_background_process(SimpleEvent&)
{
schedule_background_process();
}
void Plater::priv::on_action_add(SimpleEvent&)
{
if (q != nullptr)
#if ENABLE_NEW_MENU_LAYOUT
q->add_model();
#else
q->add();
#endif // ENABLE_NEW_MENU_LAYOUT
}
void Plater::priv::on_action_split_objects(SimpleEvent&)
{
split_object();
}
void Plater::priv::on_action_split_volumes(SimpleEvent&)
{
split_volume();
}
void Plater::priv::on_action_layersediting(SimpleEvent&)
{
bool enable = ! this->canvas3D->is_layers_editing_enabled();
this->canvas3D->enable_layers_editing(enable);
if (enable && ! this->canvas3D->is_layers_editing_enabled())
this->canvas3D->enable_toolbar_item("layersediting", false);
}
void Plater::priv::on_object_select(SimpleEvent& evt)
{
selection_changed();
wxGetApp().obj_list()->update_selections();
}
void Plater::priv::on_viewport_changed(SimpleEvent& evt)
{
wxObject* o = evt.GetEventObject();
if (o == preview->get_wxglcanvas())
preview->set_viewport_into_scene(canvas3D);
else if (o == canvas3Dwidget)
preview->set_viewport_from_scene(canvas3D);
}
void Plater::priv::on_right_click(Vec2dEvent& evt)
{
int obj_idx = get_selected_object_idx();
if (obj_idx == -1)
return;
if (q != nullptr)
q->PopupMenu(&object_menu, (int)evt.data.x(), (int)evt.data.y());
}
void Plater::priv::on_model_update(SimpleEvent&)
{
// TODO
}
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);
wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
}
void Plater::priv::on_enable_action_buttons(Event<bool>&)
{
// TODO
}
void Plater::priv::on_update_geometry(Vec3dsEvent<2>&)
{
// TODO
}
// 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()
{
wxMenuItem* item_delete = append_menu_item(&object_menu, wxID_ANY, _(L("Delete\tDel")), _(L("Remove the selected object")),
[this](wxCommandEvent&) { q->remove_selected(); }, "brick_delete.png");
wxMenuItem* item_increase = append_menu_item(&object_menu, wxID_ANY, _(L("Increase copies\t+")), _(L("Place one more copy of the selected object")),
[this](wxCommandEvent&) { q->increase_instances(); }, "add.png");
wxMenuItem* item_decrease = append_menu_item(&object_menu, wxID_ANY, _(L("Decrease copies\t-")), _(L("Remove one copy of the selected object")),
[this](wxCommandEvent&) { q->decrease_instances(); }, "delete.png");
wxMenuItem* item_set_number_of_copies = append_menu_item(&object_menu, wxID_ANY, _(L("Set number of copies…")), _(L("Change the number of copies of the selected object")),
[this](wxCommandEvent&) { q->set_number_of_copies(); }, "textfield.png");
object_menu.AppendSeparator();
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")),
[this](wxCommandEvent&) { mirror(X); }, "bullet_red.png", &object_menu);
append_menu_item(mirror_menu, wxID_ANY, _(L("Along Y axis")), _(L("Mirror the selected object along the Y axis")),
[this](wxCommandEvent&) { mirror(Y); }, "bullet_green.png", &object_menu);
append_menu_item(mirror_menu, wxID_ANY, _(L("Along Z axis")), _(L("Mirror the selected object along the Z axis")),
[this](wxCommandEvent&) { mirror(Z); }, "bullet_blue.png", &object_menu);
wxMenuItem* item_mirror = append_submenu(&object_menu, mirror_menu, wxID_ANY, _(L("Mirror")), _(L("Mirror the selected object")));
wxMenu* split_menu = new wxMenu();
if (split_menu == nullptr)
return false;
wxMenuItem* item_split_objects = append_menu_item(split_menu, wxID_ANY, _(L("To objects")), _(L("Split the selected object into individual objects")),
[this](wxCommandEvent&) { split_object(); }, "shape_ungroup_o.png", &object_menu);
wxMenuItem* item_split_volumes = append_menu_item(split_menu, wxID_ANY, _(L("To parts")), _(L("Split the selected object into individual sub-parts")),
[this](wxCommandEvent&) { split_volume(); }, "shape_ungroup_p.png", &object_menu);
wxMenuItem* item_split = append_submenu(&object_menu, split_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object")), "shape_ungroup.png");
// Add the automatic rotation sub-menu
item_sla_autorot = append_menu_item(&object_menu, wxID_ANY, _(L("Optimize orientation")), _(L("Optimize the rotation of the object for better print results.")),
[this](wxCommandEvent&) { sla_optimize_rotation(); });
if(printer_technology == ptFFF) item_sla_autorot = object_menu.Remove(item_sla_autorot);
// ui updates needs to be binded to the parent panel
if (q != nullptr)
{
q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_mirror()); }, item_mirror->GetId());
q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete_object()); }, item_delete->GetId());
q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_increase_instances()); }, item_increase->GetId());
q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_decrease_instances()); }, item_decrease->GetId());
q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_increase_instances()); }, item_set_number_of_copies->GetId());
q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split_to_objects() || can_split_to_volumes()); }, item_split->GetId());
q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split_to_objects()); }, item_split_objects->GetId());
q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split_to_volumes()); }, item_split_volumes->GetId());
}
return true;
}
bool Plater::priv::can_delete_object() const
{
int obj_idx = get_selected_object_idx();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size());
}
bool Plater::priv::can_increase_instances() const
{
int obj_idx = get_selected_object_idx();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size());
}
bool Plater::priv::can_decrease_instances() 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_to_objects() const
{
int obj_idx = get_selected_object_idx();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && !model.objects[obj_idx]->is_multiparts();
}
bool Plater::priv::can_split_to_volumes() const
{
int obj_idx = get_selected_object_idx();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && !model.objects[obj_idx]->is_multiparts();
}
bool Plater::priv::layers_height_allowed() const
{
int obj_idx = get_selected_object_idx();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && config->opt_bool("variable_layer_height") && this->canvas3D->is_layers_editing_allowed();
}
bool Plater::priv::can_delete_all() const
{
return !model.objects.empty();
}
bool Plater::priv::can_arrange() const
{
return !model.objects.empty() && !arranging.load();
}
bool Plater::priv::can_mirror() const
{
return get_selection().is_from_single_instance();
}
// Plater / Public
Plater::Plater(wxWindow *parent, MainFrame *main_frame)
: wxPanel(parent), p(new priv(this, main_frame))
{
// Initialization performed in the private c-tor
}
Plater::~Plater()
{
_3DScene::remove_canvas(p->canvas3Dwidget);
p->canvas3D = nullptr;
}
Sidebar& Plater::sidebar() { return *p->sidebar; }
Model& Plater::model() { return p->model; }
Print& Plater::print() { return p->print; }
#if ENABLE_NEW_MENU_LAYOUT
void Plater::load_project()
{
wxString input_file;
wxGetApp().load_project(this, input_file);
if (input_file.empty())
return;
p->reset();
p->project_filename = input_file;
std::vector<fs::path> input_paths;
input_paths.push_back(input_file.wx_str());
load_files(input_paths);
}
void Plater::add_model()
#else
void Plater::add()
#endif // ENABLE_NEW_MENU_LAYOUT
{
wxArrayString input_files;
#if ENABLE_NEW_MENU_LAYOUT
wxGetApp().import_model(this, input_files);
#else
wxGetApp().open_model(this, input_files);
#endif // ENABLE_NEW_MENU_LAYOUT
#if ENABLE_NEW_MENU_LAYOUT
if (input_files.empty())
return;
#endif // ENABLE_NEW_MENU_LAYOUT
std::vector<fs::path> input_paths;
for (const auto &file : input_files) {
input_paths.push_back(file.wx_str());
}
#if ENABLE_NEW_MENU_LAYOUT
load_files(input_paths, true, false);
#else
load_files(input_paths);
#endif // ENABLE_NEW_MENU_LAYOUT
}
#if ENABLE_NEW_MENU_LAYOUT
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;
input_paths.push_back(input_file.wx_str());
load_files(input_paths, false, true);
}
#endif // ENABLE_NEW_MENU_LAYOUT
#if ENABLE_NEW_MENU_LAYOUT
void Plater::load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config) { p->load_files(input_files, load_model, load_config); }
#else
void Plater::load_files(const std::vector<fs::path> &input_files) { p->load_files(input_files); }
#endif // ENABLE_NEW_MENU_LAYOUT
void Plater::update() { p->update(); }
void Plater::select_view(const std::string& direction) { p->select_view(direction); }
void Plater::select_all() { p->select_all(); }
void Plater::remove(size_t obj_idx) { p->remove(obj_idx); }
void Plater::reset() { p->reset(); }
void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_model(obj_idx); }
void Plater::remove_selected()
{
this->p->canvas3D->delete_selected();
}
void Plater::increase_instances(size_t num)
{
int obj_idx = p->get_selected_object_idx();
if (obj_idx == -1)
return;
ModelObject* model_object = p->model.objects[obj_idx];
ModelInstance* model_instance = model_object->instances.back();
bool was_one_instance = model_object->instances.size()==1;
float offset = 10.0;
for (size_t i = 0; i < num; i++, offset += 10.0) {
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());
// p->print.get_object(obj_idx)->add_copy(Slic3r::to_2d(offset_vec));
}
sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num);
if (p->get_config("autocenter") == "1") {
p->arrange();
} else {
p->update();
}
p->get_selection().add_instance(obj_idx, (int)model_object->instances.size() - 1);
p->selection_changed();
this->p->schedule_background_process();
}
void Plater::decrease_instances(size_t num)
{
int obj_idx = p->get_selected_object_idx();
if (obj_idx == -1)
return;
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();
sidebar().obj_list()->decrease_object_instances(obj_idx, num);
}
else {
remove(obj_idx);
}
p->update();
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 auto num = wxGetNumberFromUser( " ", _("Enter the number of copies:"),
_("Copies of the selected object"), model_object->instances.size(), 0, 1000, this );
if (num < 0)
return;
int diff = (int)num - (int)model_object->instances.size();
if (diff > 0)
increase_instances(diff);
else if (diff < 0)
decrease_instances(-diff);
}
bool Plater::is_selection_empty() const
{
return p->get_selection().is_empty();
}
void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z)
{
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");
const auto new_objects = object->cut(instance_idx, z);
remove(obj_idx);
p->load_model_objects(new_objects);
p->arrange();
}
void Plater::export_gcode(fs::path output_path)
{
if (p->model.objects.empty())
return;
if (this->p->background_process.is_export_scheduled()) {
GUI::show_error(this, _(L("Another export job is currently running.")));
return;
}
std::string err = wxGetApp().preset_bundle->full_config().validate();
if (err.empty())
err = p->background_process.validate();
if (! err.empty()) {
// The config is not valid
GUI::show_error(this, _(err));
return;
}
std::string final_path;
if(printer_technology() == ptFFF) { // TODO: custom button for SLA export
// Copy the names of active presets into the placeholder parser.
wxGetApp().preset_bundle->export_selections(p->print.placeholder_parser());
// select output file
if (output_path.empty()) {
// XXX: take output path from CLI opts? Ancient Slic3r versions used to do that...
// If possible, remove accents from accented latin characters.
// This function is useful for generating file names to be processed by legacy firmwares.
auto default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(
p->print.output_filepath(output_path.string())
// FIXME: ^ errors to handle?
));
auto start_dir = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string());
wxFileDialog dlg(this, _(L("Save G-code file as:")),
start_dir,
default_output_file.filename().string(),
GUI::file_wildcards[FT_GCODE],
wxFD_SAVE | wxFD_OVERWRITE_PROMPT
);
if (dlg.ShowModal() == wxID_OK) {
fs::path path(dlg.GetPath());
wxGetApp().app_config->update_last_output_dir(path.parent_path().string());
output_path = path;
}
}
final_path = p->print.output_filepath(output_path.string());
} else {
wxFileDialog dlg(this, _(L("Save Zip file as:")),
wxGetApp().app_config->get_last_output_dir(""),
"out.zip",
GUI::file_wildcards[FT_PNGZIP],
wxFD_SAVE | wxFD_OVERWRITE_PROMPT
);
if (dlg.ShowModal() == wxID_OK) {
fs::path path(dlg.GetPath());
wxGetApp().app_config->update_last_output_dir(path.parent_path().string());
output_path = path;
}
final_path = output_path.string();
}
if (! output_path.empty()) {
this->p->background_process.schedule_export(final_path);
this->p->background_process.start();
}
}
void Plater::export_stl()
{
if (p->model.objects.empty()) { return; }
auto dialog = p->get_export_file(FT_STL);
if (! dialog) { return; }
// Store a binary STL
wxString path = dialog->GetPath();
auto path_cstr = path.c_str();
auto mesh = p->model.mesh();
Slic3r::store_stl(path_cstr, &mesh, true);
p->statusbar()->set_status_text(wxString::Format(_(L("STL file exported to %s")), path));
}
void Plater::export_amf()
{
if (p->model.objects.empty()) { return; }
auto dialog = p->get_export_file(FT_AMF);
if (! dialog) { return; }
wxString path = dialog->GetPath();
auto path_cstr = path.c_str();
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config();
if (Slic3r::store_amf(path_cstr, &p->model, dialog->get_checkbox_value() ? &cfg : nullptr)) {
// Success
p->statusbar()->set_status_text(wxString::Format(_(L("AMF file exported to %s")), path));
} else {
// Failure
p->statusbar()->set_status_text(wxString::Format(_(L("Error exporting AMF file %s")), path));
}
}
#if ENABLE_NEW_MENU_LAYOUT
void Plater::export_3mf(const boost::filesystem::path& output_path)
#else
void Plater::export_3mf()
#endif // ENABLE_NEW_MENU_LAYOUT
{
if (p->model.objects.empty()) { return; }
#if ENABLE_NEW_MENU_LAYOUT
wxString path;
bool export_config = true;
if (output_path.empty())
{
#endif // ENABLE_NEW_MENU_LAYOUT
auto dialog = p->get_export_file(FT_3MF);
if (!dialog) { return; }
#if ENABLE_NEW_MENU_LAYOUT
path = dialog->GetPath();
export_config = dialog->get_checkbox_value();
}
else
path = output_path.string();
if (!path.Lower().EndsWith(".3mf"))
return;
#else
wxString path = dialog->GetPath();
auto path_cstr = path.c_str();
#endif // ENABLE_NEW_MENU_LAYOUT
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config();
#if ENABLE_NEW_MENU_LAYOUT
if (Slic3r::store_3mf(path.c_str(), &p->model, export_config ? &cfg : nullptr)) {
#else
if (Slic3r::store_3mf(path_cstr, &p->model, dialog->get_checkbox_value() ? &cfg : nullptr)) {
#endif // ENABLE_NEW_MENU_LAYOUT
// Success
p->statusbar()->set_status_text(wxString::Format(_(L("3MF file exported to %s")), path));
} else {
// Failure
p->statusbar()->set_status_text(wxString::Format(_(L("Error exporting 3MF file %s")), path));
}
}
void Plater::reslice()
{
//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();
if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
this->p->canvas3D->reload_scene(false);
if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0 && !this->p->background_process.running()) {
// The print is valid and it can be started.
// Copy the names of active presets into the placeholder parser.
//FIXME how to generate a file name for the SLA printers?
wxGetApp().preset_bundle->export_selections(this->print().placeholder_parser());
if (this->p->background_process.start())
this->p->statusbar()->set_cancel_callback([this]() {
this->p->statusbar()->set_status_text(L("Cancelling"));
this->p->background_process.stop();
});
}
}
void Plater::send_gcode()
{
// p->send_gcode_file = export_gcode();
}
void Plater::on_extruders_change(int num_extruders)
{
auto& choices = sidebar().combos_filament();
wxWindowUpdateLocker noUpdates_scrolled_panel(&sidebar()/*.scrolled_panel()*/);
// sidebar().scrolled_panel()->Freeze();
int i = choices.size();
while ( i < num_extruders )
{
PresetComboBox* choice/*{ nullptr }*/;
sidebar().init_filament_combo(&choice, i);
choices.push_back(choice);
// initialize selection
wxGetApp().preset_bundle->update_platter_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)
{
bool update_scheduled = false;
for (auto opt_key : p->config->diff(config)) {
p->config->set_key_value(opt_key, config.option(opt_key)->clone());
if (opt_key == "printer_technology") {
p->printer_technology = config.opt_enum<PrinterTechnology>(opt_key);
p->background_process.select_technology(this->printer_technology());
//FIXME for SLA synchronize
//p->background_process.apply(Model)!
}
else if (opt_key == "bed_shape") {
this->p->canvas3D->set_bed_shape(p->config->option<ConfigOptionPoints>(opt_key)->values);
if (p->preview) p->preview->set_bed_shape(p->config->option<ConfigOptionPoints>(opt_key)->values);
update_scheduled = true;
}
else if(opt_key == "wipe_tower" /*|| opt_key == "filament_minimal_purge_on_wipe_tower"*/ || // ? #ys_FIXME
opt_key == "single_extruder_multi_material") {
update_scheduled = true;
}
// else if(opt_key == "serial_port") {
// sidebar()->p->btn_print->Show(config.get("serial_port")); // ???: btn_print is removed
// Layout();
// }
else if (opt_key == "print_host") {
sidebar().show_button(baReslice, !p->config->option<ConfigOptionString>(opt_key)->value.empty());
Layout();
}
else if(opt_key == "variable_layer_height") {
if (p->config->opt_bool("variable_layer_height") != true) {
p->canvas3D->enable_toolbar_item("layersediting", false);
p->canvas3D->enable_layers_editing(0);
p->canvas3Dwidget->Refresh();
p->canvas3Dwidget->Update();
}
else if (p->canvas3D->is_layers_editing_allowed()) {
p->canvas3D->enable_toolbar_item("layersediting", true);
}
}
else if(opt_key == "extruder_colour") {
update_scheduled = true;
p->preview->set_number_extruders(p->config->option<ConfigOptionStrings>(opt_key)->values.size());
} else if(opt_key == "max_print_height") {
update_scheduled = true;
} else if(opt_key == "printer_model") {
// update to force bed selection(for texturing)
p->canvas3D->set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values);
if (p->preview) p->preview->set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values);
update_scheduled = true;
}
}
bool attached = false;
for(const wxMenuItem * m : p->object_menu.GetMenuItems())
if(m == p->item_sla_autorot) { attached = true; break; }
switch(printer_technology()) {
case ptFFF: {
// hide sla auto rotation menuitem
if(attached) p->item_sla_autorot = p->object_menu.Remove(p->item_sla_autorot);
std::cout << "sla autorot menu should be removed" << std::endl;
}
case ptSLA: {
// show sla auto rotation menuitem
if(!attached) p->object_menu.Append(p->item_sla_autorot);
}
}
if (update_scheduled)
update();
if (p->main_frame->is_loaded())
this->p->schedule_background_process();
}
#if ENABLE_NEW_MENU_LAYOUT
const wxString& Plater::get_project_filename() const
{
return p->project_filename;
}
bool Plater::is_export_gcode_scheduled() const
{
return p->background_process.is_export_scheduled();
}
#endif // ENABLE_NEW_MENU_LAYOUT
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->canvas3D;
}
PrinterTechnology Plater::printer_technology() const
{
return p->printer_technology;
}
void Plater::changed_object(int obj_idx)
{
if (obj_idx < 0)
return;
auto list = wxGetApp().obj_list();
wxASSERT(list != nullptr);
if (list == nullptr)
return;
if (list->is_parts_changed()) {
// recenter and re - align to Z = 0
auto model_object = p->model.objects[obj_idx];
#if !ENABLE_MODELVOLUME_TRANSFORM
model_object->center_around_origin();
#endif // !ENABLE_MODELVOLUME_TRANSFORM
model_object->ensure_on_bed();
p->canvas3D->reload_scene(false);
}
// update print
if (list->is_parts_changed() || list->is_part_settings_changed()) {
this->p->schedule_background_process();
#if !ENABLE_MODIFIED_CAMERA_TARGET
p->canvas3D->zoom_to_volumes();
#endif // !ENABLE_MODIFIED_CAMERA_TARGET
}
else {
this->p->schedule_background_process();
}
}
void Plater::fix_through_netfabb(const int obj_idx) { p->fix_through_netfabb(obj_idx); }
}} // namespace Slic3r::GUI