2018-09-17 12:15:11 +02:00
#include "Plater.hpp"
#include <cstddef>
#include <algorithm>
2019-06-26 17:09:26 +02:00
#include <numeric>
2018-09-17 12:15:11 +02:00
#include <vector>
#include <string>
#include <regex>
2019-05-13 18:58:56 +02:00
#include <future>
2019-05-17 16:27:00 +02:00
#include <boost/algorithm/string.hpp>
2018-10-04 11:12:55 +02:00
#include <boost/optional.hpp>
2018-09-17 12:15:11 +02:00
#include <boost/filesystem/path.hpp>
2019-09-19 09:09:11 +02:00
#include <boost/filesystem/operations.hpp>
2019-06-18 17:10:13 +02:00
#include <boost/log/trivial.hpp>
2018-09-17 12:15:11 +02: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 16:48:08 +02:00
#include <wx/wupdlock.h>
2018-10-10 13:53:45 +02:00
#include <wx/colordlg.h>
2018-10-18 15:13:38 +02:00
#include <wx/numdlg.h>
#include <wx/debug.h>
2018-09-17 12:15:11 +02:00
2018-10-09 09:35:19 +02:00
#include "libslic3r/libslic3r.h"
2018-10-04 11:12:55 +02:00
#include "libslic3r/Format/STL.hpp"
2018-10-08 19:55:30 +02:00
#include "libslic3r/Format/AMF.hpp"
#include "libslic3r/Format/3mf.hpp"
2018-11-26 14:41:58 +01:00
#include "libslic3r/GCode/PreviewData.hpp"
2019-10-24 12:09:09 +02:00
#if ENABLE_THUMBNAIL_GENERATOR
#include "libslic3r/GCode/ThumbnailData.hpp"
#endif // ENABLE_THUMBNAIL_GENERATOR
2018-11-26 14:41:58 +01:00
#include "libslic3r/Model.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/SLA/SLARotfinder.hpp"
#include "libslic3r/Utils.hpp"
2019-06-06 14:27:07 +02:00
//#include "libslic3r/ClipperUtils.hpp"
// #include "libnest2d/optimizers/nlopt/genetic.hpp"
// #include "libnest2d/backends/clipper/geometries.hpp"
// #include "libnest2d/utils/rotcalipers.hpp"
#include "libslic3r/MinAreaBoundingBox.hpp"
2019-04-04 16:03:23 +02:00
2018-09-17 12:15:11 +02:00
#include "GUI.hpp"
#include "GUI_App.hpp"
2018-10-04 16:43:10 +02:00
#include "GUI_ObjectList.hpp"
#include "GUI_ObjectManipulation.hpp"
2019-05-28 16:38:04 +02:00
#include "GUI_ObjectLayers.hpp"
2018-10-04 11:12:55 +02:00
#include "GUI_Utils.hpp"
2018-10-17 12:17:25 +02:00
#include "wxExtensions.hpp"
2018-09-17 12:15:11 +02:00
#include "MainFrame.hpp"
#include "3DScene.hpp"
2018-10-03 11:34:39 +02:00
#include "GLCanvas3D.hpp"
2019-03-19 13:30:21 +01:00
#include "Selection.hpp"
2018-09-17 12:15:11 +02:00
#include "GLToolbar.hpp"
#include "GUI_Preview.hpp"
2019-02-19 15:15:27 +01:00
#include "3DBed.hpp"
2019-03-06 15:46:19 +01:00
#include "Camera.hpp"
2019-09-27 14:52:19 +02:00
#include "Mouse3DController.hpp"
2018-09-17 12:15:11 +02:00
#include "Tab.hpp"
#include "PresetBundle.hpp"
#include "BackgroundSlicingProcess.hpp"
#include "ProgressStatusBar.hpp"
2018-12-11 10:33:11 +01:00
#include "PrintHostDialogs.hpp"
2019-03-22 15:45:51 +01:00
#include "ConfigWizard.hpp"
2018-11-26 14:41:58 +01:00
#include "../Utils/ASCIIFolding.hpp"
2018-12-11 10:33:11 +01:00
#include "../Utils/PrintHost.hpp"
2018-11-07 13:40:24 +01:00
#include "../Utils/FixModelByWin10.hpp"
2019-07-02 16:42:23 +02:00
#include "../Utils/UndoRedo.hpp"
2019-11-07 12:57:40 +01:00
#include "../Utils/Thread.hpp"
2019-11-27 13:30:45 +01:00
#include "RemovableDriveManager.hpp"
2018-09-17 12:15:11 +02:00
#include <wx/glcanvas.h> // Needs to be last because reasons :-/
2018-10-03 15:14:52 +02:00
#include "WipeTowerDialog.hpp"
2018-09-17 12:15:11 +02:00
2018-10-04 11:12:55 +02:00
using boost :: optional ;
2018-09-17 12:15:11 +02:00
namespace fs = boost :: filesystem ;
using Slic3r :: _3DScene ;
using Slic3r :: Preset ;
2018-12-11 10:33:11 +01:00
using Slic3r :: PrintHostJob ;
2018-09-17 12:15:11 +02:00
2019-10-24 10:06:31 +02:00
#if ENABLE_THUMBNAIL_GENERATOR
static const std :: pair < unsigned int , unsigned int > THUMBNAIL_SIZE_3MF = { 256 , 256 };
#endif // ENABLE_THUMBNAIL_GENERATOR
2018-09-17 12:15:11 +02:00
namespace Slic3r {
namespace GUI {
2018-11-26 14:41:58 +01:00
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 12:15:11 +02: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 23:29:15 +02:00
wxStaticBitmap * manifold_warning_icon ;
2018-09-17 12:15:11 +02:00
wxStaticText * info_size ;
wxStaticText * info_volume ;
wxStaticText * info_facets ;
wxStaticText * info_materials ;
wxStaticText * info_manifold ;
2019-02-19 12:14:13 +01:00
wxStaticText * label_volume ;
wxStaticText * label_materials ;
2019-02-21 15:46:04 +01:00
std :: vector < wxStaticText *> sla_hidden_items ;
2019-02-19 12:14:13 +01:00
2018-11-01 12:33:56 +01:00
bool showing_manifold_warning_icon ;
void show_sizer ( bool show );
2019-04-25 01:45:00 +02:00
void msw_rescale ();
2018-09-17 12:15:11 +02:00
};
ObjectInfo :: ObjectInfo ( wxWindow * parent ) :
wxStaticBoxSizer ( new wxStaticBox ( parent , wxID_ANY , _ ( L ( "Info" ))), wxVERTICAL )
{
2018-10-01 15:09:31 +02:00
GetStaticBox () -> SetFont ( wxGetApp (). bold_font ());
2018-09-17 12:15:11 +02:00
2019-04-13 23:46:52 +02:00
auto * grid_sizer = new wxFlexGridSizer ( 4 , 5 , 15 );
2018-09-17 12:15:11 +02:00
grid_sizer -> SetFlexibleDirection ( wxHORIZONTAL );
2019-04-13 23:46:52 +02:00
// grid_sizer->AddGrowableCol(1, 1);
// grid_sizer->AddGrowableCol(3, 1);
2018-09-17 12:15:11 +02:00
auto init_info_label = [ parent , grid_sizer ]( wxStaticText ** info_label , wxString text_label ) {
2019-01-21 12:34:28 +01:00
auto * text = new wxStaticText ( parent , wxID_ANY , text_label + ":" );
2018-10-01 15:09:31 +02:00
text -> SetFont ( wxGetApp (). small_font ());
2018-09-17 12:15:11 +02:00
* info_label = new wxStaticText ( parent , wxID_ANY , "" );
2018-10-01 15:09:31 +02:00
( * info_label ) -> SetFont ( wxGetApp (). small_font ());
2018-09-17 12:15:11 +02:00
grid_sizer -> Add ( text , 0 );
grid_sizer -> Add ( * info_label , 0 );
2019-02-19 12:14:13 +01:00
return text ;
2018-09-17 12:15:11 +02:00
};
2019-01-21 12:34:28 +01:00
init_info_label ( & info_size , _ ( L ( "Size" )));
2019-02-19 12:14:13 +01:00
label_volume = init_info_label ( & info_volume , _ ( L ( "Volume" )));
2019-01-21 12:34:28 +01:00
init_info_label ( & info_facets , _ ( L ( "Facets" )));
2019-02-19 12:14:13 +01:00
label_materials = init_info_label ( & info_materials , _ ( L ( "Materials" )));
2018-11-01 12:33:56 +01:00
Add ( grid_sizer , 0 , wxEXPAND );
2018-09-17 12:15:11 +02:00
2019-02-19 12:14:13 +01:00
auto * info_manifold_text = new wxStaticText ( parent , wxID_ANY , _ ( L ( "Manifold" )) + ":" );
2018-10-01 15:09:31 +02:00
info_manifold_text -> SetFont ( wxGetApp (). small_font ());
2018-09-17 12:15:11 +02:00
info_manifold = new wxStaticText ( parent , wxID_ANY , "" );
2018-10-01 15:09:31 +02:00
info_manifold -> SetFont ( wxGetApp (). small_font ());
2019-04-12 17:10:04 +02:00
manifold_warning_icon = new wxStaticBitmap ( parent , wxID_ANY , create_scaled_bitmap ( parent , "exclamation" ));
2018-09-17 12:15:11 +02: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 12:33:56 +01:00
Add ( sizer_manifold , 0 , wxEXPAND | wxTOP , 4 );
2019-02-19 12:14:13 +01:00
2019-02-21 15:46:04 +01:00
sla_hidden_items = { label_volume , info_volume , label_materials , info_materials };
2018-11-01 12:33:56 +01:00
}
2018-09-17 12:15:11 +02:00
2018-11-01 12:33:56 +01:00
void ObjectInfo :: show_sizer ( bool show )
{
Show ( show );
if ( show )
manifold_warning_icon -> Show ( showing_manifold_warning_icon && show );
2018-09-17 12:15:11 +02:00
}
2019-04-25 01:45:00 +02:00
void ObjectInfo :: msw_rescale ()
2019-04-13 23:46:52 +02:00
{
2019-04-16 10:05:45 +02:00
manifold_warning_icon -> SetBitmap ( create_scaled_bitmap ( nullptr , "exclamation" ));
2019-04-13 23:46:52 +02:00
}
2019-11-05 11:37:40 +01:00
enum SlicedInfoIdx
2018-10-31 12:26:57 +01:00
{
siFilament_m ,
siFilament_mm3 ,
siFilament_g ,
2019-02-12 16:34:42 +01:00
siMateril_unit ,
2018-10-31 12:26:57 +01:00
siCost ,
2018-11-20 10:33:54 +01:00
siEstimatedTime ,
2018-10-31 12:26:57 +01:00
siWTNumbetOfToolchanges ,
siCount
};
2018-09-17 12:15:11 +02:00
class SlicedInfo : public wxStaticBoxSizer
{
public :
SlicedInfo ( wxWindow * parent );
2019-11-05 11:37:40 +01:00
void SetTextAndShow ( SlicedInfoIdx idx , const wxString & text , const wxString & new_label = "" );
2018-09-17 12:15:11 +02:00
private :
2018-10-31 12:26:57 +01:00
std :: vector < std :: pair < wxStaticText * , wxStaticText *>> info_vec ;
2018-09-17 12:15:11 +02:00
};
SlicedInfo :: SlicedInfo ( wxWindow * parent ) :
wxStaticBoxSizer ( new wxStaticBox ( parent , wxID_ANY , _ ( L ( "Sliced Info" ))), wxVERTICAL )
{
2018-10-01 15:09:31 +02:00
GetStaticBox () -> SetFont ( wxGetApp (). bold_font ());
2018-09-17 12:15:11 +02:00
2018-11-20 10:33:54 +01:00
auto * grid_sizer = new wxFlexGridSizer ( 2 , 5 , 15 );
grid_sizer -> SetFlexibleDirection ( wxVERTICAL );
2018-09-17 12:15:11 +02:00
2018-10-31 12:26:57 +01:00
info_vec . reserve ( siCount );
auto init_info_label = [ this , parent , grid_sizer ]( wxString text_label ) {
2018-09-17 12:15:11 +02:00
auto * text = new wxStaticText ( parent , wxID_ANY , text_label );
2018-10-01 15:09:31 +02:00
text -> SetFont ( wxGetApp (). small_font ());
2018-10-31 12:26:57 +01:00
auto info_label = new wxStaticText ( parent , wxID_ANY , "N/A" );
2018-10-01 15:09:31 +02:00
info_label -> SetFont ( wxGetApp (). small_font ());
2018-09-17 12:15:11 +02:00
grid_sizer -> Add ( text , 0 );
grid_sizer -> Add ( info_label , 0 );
2018-10-31 12:26:57 +01:00
info_vec . push_back ( std :: pair < wxStaticText * , wxStaticText *> ( text , info_label ));
2018-09-17 12:15:11 +02:00
};
2018-10-31 12:26:57 +01:00
init_info_label ( _ ( L ( "Used Filament (m)" )));
init_info_label ( _ ( L ( "Used Filament (mm³)" )));
init_info_label ( _ ( L ( "Used Filament (g)" )));
2019-02-12 16:34:42 +01:00
init_info_label ( _ ( L ( "Used Material (unit)" )));
2019-09-26 16:33:55 +02:00
init_info_label ( _ ( L ( "Cost (money)" )));
2018-11-20 10:33:54 +01:00
init_info_label ( _ ( L ( "Estimated printing time" )));
2018-10-31 12:26:57 +01:00
init_info_label ( _ ( L ( "Number of tool changes" )));
2018-09-17 12:15:11 +02:00
Add ( grid_sizer , 0 , wxEXPAND );
2018-10-31 12:26:57 +01:00
this -> Show ( false );
}
2019-11-05 11:37:40 +01:00
void SlicedInfo :: SetTextAndShow ( SlicedInfoIdx idx , const wxString & text , const wxString & new_label /*=""*/ )
2018-10-31 12:26:57 +01:00
{
const bool show = text != "N/A" ;
if ( show )
info_vec [ idx ]. second -> SetLabelText ( text );
2018-11-20 10:33:54 +01:00
if ( ! new_label . IsEmpty ())
info_vec [ idx ]. first -> SetLabelText ( new_label );
2018-10-31 12:26:57 +01:00
info_vec [ idx ]. first -> Show ( show );
info_vec [ idx ]. second -> Show ( show );
2018-09-17 12:15:11 +02:00
}
PresetComboBox :: PresetComboBox ( wxWindow * parent , Preset :: Type preset_type ) :
2019-02-04 10:35:16 +01:00
wxBitmapComboBox ( parent , wxID_ANY , wxEmptyString , wxDefaultPosition , wxSize ( 15 * wxGetApp (). em_unit (), - 1 ), 0 , nullptr , wxCB_READONLY ),
2018-09-17 12:15:11 +02:00
preset_type ( preset_type ),
2019-04-23 16:33:06 +02:00
last_selected ( wxNOT_FOUND ),
m_em_unit ( wxGetApp (). em_unit ())
2018-09-17 12:15:11 +02:00
{
2019-07-10 15:55:53 +02:00
SetFont ( wxGetApp (). normal_font ());
2018-09-17 12:15:11 +02:00
Bind ( wxEVT_COMBOBOX , [ this ]( wxCommandEvent & evt ) {
auto selected_item = this -> GetSelection ();
auto marker = reinterpret_cast < Marker > ( this -> GetClientData ( selected_item ));
2019-06-04 18:01:41 +02:00
if ( marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX ) {
2018-09-17 12:15:11 +02:00
this -> SetSelection ( this -> last_selected );
evt . StopPropagation ();
2019-06-04 18:01:41 +02:00
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 18:16:02 +02:00
} else if ( this -> last_selected != selected_item ||
2018-12-21 10:58:00 +01:00
wxGetApp (). get_tab ( this -> preset_type ) -> get_presets () -> current_is_dirty () ) {
2018-09-17 12:15:11 +02:00
this -> last_selected = selected_item ;
evt . SetInt ( this -> preset_type );
2018-10-09 12:41:05 +02:00
evt . Skip ();
2018-09-17 12:15:11 +02:00
} else {
evt . StopPropagation ();
}
});
2019-08-06 18:16:02 +02:00
2018-10-10 13:53:45 +02:00
if ( preset_type == Slic3r :: Preset :: TYPE_FILAMENT )
{
Bind ( wxEVT_LEFT_DOWN , [ this ]( wxMouseEvent & event ) {
2019-05-23 10:55:57 +02:00
int shifl_Left = 0 ;
float scale = m_em_unit * 0.1f ;
#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 = int ( scale * ( 24 + 4 ) + 0.5 );
int mouse_pos = event . GetLogicalPosition ( wxClientDC ( this )). x ;
// if (extruder_idx < 0 || event.GetLogicalPosition(wxClientDC(this)).x > 24) {
if ( extruder_idx < 0 || mouse_pos < shifl_Left || mouse_pos > icon_right_pos ) {
2018-10-10 13:53:45 +02:00
// Let the combo box process the mouse click.
event . Skip ();
return ;
}
2019-08-06 18:16:02 +02:00
2018-10-10 13:53:45 +02:00
// Swallow the mouse click and open the color picker.
2019-05-23 12:39:55 +02:00
// 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 ())
2019-08-20 15:26:57 +02:00
clr = wxColour ( 0 , 0 , 0 ); // Don't set alfa to transparence
2019-05-23 12:39:55 +02:00
2018-10-10 13:53:45 +02:00
auto data = new wxColourData ();
data -> SetChooseFull ( 1 );
2019-05-23 12:39:55 +02:00
data -> SetColour ( clr );
2018-10-10 13:53:45 +02:00
2019-08-15 12:52:56 +02:00
wxColourDialog dialog ( this , data );
dialog . CenterOnParent ();
if ( dialog . ShowModal () == wxID_OK )
2019-05-23 12:39:55 +02:00
{
2019-08-15 12:52:56 +02:00
colors -> values [ extruder_idx ] = dialog . GetColourData (). GetColour (). GetAsString ( wxC2S_HTML_SYNTAX );
2018-10-10 13:53:45 +02:00
2019-08-06 18:16:02 +02:00
DynamicPrintConfig cfg_new = * cfg ;
2019-05-23 12:39:55 +02:00
cfg_new . set_key_value ( "extruder_colour" , colors );
2018-10-10 13:53:45 +02:00
2019-05-23 12:39:55 +02:00
wxGetApp (). get_tab ( Preset :: TYPE_PRINTER ) -> load_config ( cfg_new );
2019-04-23 16:33:06 +02:00
wxGetApp (). preset_bundle -> update_platter_filament_ui ( extruder_idx , this );
2019-05-23 12:39:55 +02:00
wxGetApp (). plater () -> on_config_change ( cfg_new );
2018-10-10 13:53:45 +02:00
}
});
}
2019-03-18 15:10:40 +01:00
2019-04-25 01:45:00 +02:00
edit_btn = new ScalableButton ( parent , wxID_ANY , "cog" );
2019-03-20 10:14:49 +01:00
edit_btn -> SetToolTip ( _ ( L ( "Click to edit preset" )));
2019-03-18 15:10:40 +01:00
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 () -> ChangeSelection ( page_id );
2019-08-06 18:16:02 +02: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 14:36:32 +01:00
*/
2019-08-06 18:16:02 +02:00
if ( preset_type == Preset :: TYPE_FILAMENT && wxGetApp (). extruders_edited_cnt () > 1 )
2019-03-19 14:36:32 +01:00
{
const std :: string & selected_preset = GetString ( GetSelection ()). ToUTF8 (). data ();
2019-03-18 15:10:40 +01:00
2019-08-06 18:16:02 +02:00
// Call select_preset() only if there is new preset and not just modified
2019-03-20 10:14:49 +01:00
if ( ! boost :: algorithm :: ends_with ( selected_preset , Preset :: suffix_modified ()) )
2019-11-25 15:57:36 +01:00
{
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 14:36:32 +01:00
}
2019-03-18 15:10:40 +01:00
}));
2018-09-17 12:15:11 +02:00
}
2019-03-18 15:10:40 +01:00
PresetComboBox ::~ PresetComboBox ()
{
if ( edit_btn )
edit_btn -> Destroy ();
}
2018-09-17 12:15:11 +02:00
2019-03-22 15:45:51 +01:00
void PresetComboBox :: set_label_marker ( int item , LabelItemType label_item_type )
2018-10-09 12:41:05 +02:00
{
2019-03-22 15:45:51 +01:00
this -> SetClientData ( item , ( void * ) label_item_type );
2018-10-09 12:41:05 +02:00
}
2018-12-03 15:16:33 +01:00
void PresetComboBox :: check_selection ()
{
2019-03-23 10:04:44 +01:00
this -> last_selected = GetSelection ();
2018-12-03 15:16:33 +01:00
}
2019-04-25 01:45:00 +02:00
void PresetComboBox :: msw_rescale ()
2019-04-13 23:46:52 +02:00
{
2019-04-23 16:33:06 +02:00
m_em_unit = wxGetApp (). em_unit ();
2019-04-25 01:45:00 +02:00
edit_btn -> msw_rescale ();
2019-04-13 23:46:52 +02:00
}
2018-10-04 16:43:10 +02:00
// Frequently changed parameters
class FreqChangedParams : public OG_Settings
{
double m_brim_width = 0.0 ;
wxButton * m_wiping_dialog_button { nullptr };
2019-01-15 09:31:53 +01:00
wxSizer * m_sizer { nullptr };
std :: shared_ptr < ConfigOptionsGroup > m_og_sla ;
2019-07-10 17:50:24 +02:00
std :: vector < ScalableButton *> m_empty_buttons ;
2018-10-04 16:43:10 +02:00
public :
2019-07-10 15:55:53 +02:00
FreqChangedParams ( wxWindow * parent );
2018-10-04 16:43:10 +02:00
~ FreqChangedParams () {}
wxButton * get_wiping_dialog_button () { return m_wiping_dialog_button ; }
2019-01-15 09:31:53 +01:00
wxSizer * get_sizer () override ;
ConfigOptionsGroup * get_og ( const bool is_fff );
void Show ( const bool is_fff );
2019-07-10 17:50:24 +02:00
void msw_rescale ();
2018-10-04 16:43:10 +02:00
};
2019-07-10 17:50:24 +02:00
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 15:55:53 +02:00
FreqChangedParams :: FreqChangedParams ( wxWindow * parent ) :
2018-10-04 16:43:10 +02:00
OG_Settings ( parent , false )
{
DynamicPrintConfig * config = & wxGetApp (). preset_bundle -> prints . get_edited_preset (). config ;
2019-01-15 09:31:53 +01:00
// Frequently changed parameters for FFF_technology
2018-10-04 16:43:10 +02:00
m_og -> set_config ( config );
2019-07-10 15:55:53 +02:00
m_og -> hide_labels ();
2018-10-04 16:43:10 +02:00
2018-10-31 12:56:08 +01:00
m_og -> m_on_change = [ config , this ]( t_config_option_key opt_key , boost :: any value ) {
2019-01-15 09:31:53 +01:00
Tab * tab_print = wxGetApp (). get_tab ( Preset :: TYPE_PRINT );
if ( ! tab_print ) return ;
2018-10-04 16:43:10 +02:00
2018-10-31 12:56:08 +01:00
if ( opt_key == "fill_density" ) {
2018-10-04 16:43:10 +02:00
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 12:56:08 +01:00
if ( opt_key == "brim" ) {
2018-10-04 16:43:10 +02:00
double new_val ;
double brim_width = config -> opt_float ( "brim_width" );
if ( boost :: any_cast < bool > ( value ) == true )
{
2018-12-11 16:33:43 +01:00
new_val = m_brim_width == 0.0 ? 5 :
2018-10-04 16:43:10 +02:00
m_brim_width < 0.0 ? m_brim_width * ( - 1 ) :
m_brim_width ;
}
2018-12-11 16:33:43 +01:00
else {
2018-10-04 16:43:10 +02:00
m_brim_width = brim_width * ( - 1 );
new_val = 0 ;
}
new_conf . set_key_value ( "brim_width" , new ConfigOptionFloat ( new_val ));
}
2019-05-03 18:01:39 +02:00
else {
assert ( opt_key == "support" );
2018-10-04 16:43:10 +02:00
const wxString & selection = boost :: any_cast < wxString > ( value );
2019-05-03 18:01:39 +02:00
PrinterTechnology printer_technology = wxGetApp (). preset_bundle -> printers . get_edited_preset (). printer_technology ();
2018-10-04 16:43:10 +02:00
auto support_material = selection == _ ( "None" ) ? false : true ;
new_conf . set_key_value ( "support_material" , new ConfigOptionBool ( support_material ));
2019-05-03 18:01:39 +02:00
if ( selection == _ ( "Everywhere" )) {
2018-10-04 16:43:10 +02:00
new_conf . set_key_value ( "support_material_buildplate_only" , new ConfigOptionBool ( false ));
2019-05-03 18:01:39 +02:00
if ( printer_technology == ptFFF )
new_conf . set_key_value ( "support_material_auto" , new ConfigOptionBool ( true ));
} else if ( selection == _ ( "Support on build plate only" )) {
2018-10-04 16:43:10 +02:00
new_conf . set_key_value ( "support_material_buildplate_only" , new ConfigOptionBool ( true ));
2019-05-03 18:01:39 +02:00
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 ));
}
2018-10-04 16:43:10 +02:00
}
tab_print -> load_config ( new_conf );
}
tab_print -> update_dirty ();
};
2019-08-06 18:16:02 +02:00
2019-02-07 14:44:05 +01:00
Line line = Line { "" , "" };
2018-10-04 16:43:10 +02:00
2019-03-11 16:00:13 +01:00
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" ));
2019-05-03 18:01:39 +02:00
support_def . enum_labels . push_back ( L ( "For support enforcers only" ));
2019-03-11 16:00:13 +01:00
support_def . enum_labels . push_back ( L ( "Everywhere" ));
2019-05-03 18:01:39 +02:00
support_def . set_default_value ( new ConfigOptionStrings { "None" });
2019-03-11 16:00:13 +01:00
Option option = Option ( support_def , "support" );
2018-10-04 16:43:10 +02:00
option . opt . full_width = true ;
2019-02-07 14:44:05 +01:00
line . append_option ( option );
2019-07-10 17:50:24 +02:00
/* 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 14:44:05 +01:00
m_og -> append_line ( line );
2019-08-06 18:16:02 +02:00
2019-02-07 14:44:05 +01:00
line = Line { "" , "" };
option = m_og -> get_option ( "fill_density" );
option . opt . label = L ( "Infill" );
2019-05-13 18:21:17 +02:00
option . opt . width = 7 /*6*/ ;
2019-07-13 10:37:21 +02:00
option . opt . sidetext = " " ;
2019-02-07 14:44:05 +01:00
line . append_option ( option );
2018-10-04 16:43:10 +02:00
m_brim_width = config -> opt_float ( "brim_width" );
2019-03-11 16:00:13 +01:00
ConfigOptionDef def ;
2018-10-04 16:43:10 +02:00
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 = "" ;
2019-05-03 18:01:39 +02:00
def . set_default_value ( new ConfigOptionBool { m_brim_width > 0.0 ? true : false });
2018-10-04 16:43:10 +02:00
option = Option ( def , "brim" );
2019-07-13 10:37:21 +02:00
option . opt . sidetext = "" ;
2019-02-07 14:44:05 +01:00
line . append_option ( option );
2018-10-04 16:43:10 +02:00
2019-04-26 10:16:08 +02:00
auto wiping_dialog_btn = [ this ]( wxWindow * parent ) {
2018-10-04 16:43:10 +02:00
m_wiping_dialog_button = new wxButton ( parent , wxID_ANY , _ ( L ( "Purging volumes" )) + dots , wxDefaultPosition , wxDefaultSize , wxBU_EXACTFIT );
2019-07-10 15:55:53 +02:00
m_wiping_dialog_button -> SetFont ( wxGetApp (). normal_font ());
2018-10-04 16:43:10 +02:00
auto sizer = new wxBoxSizer ( wxHORIZONTAL );
2019-07-10 17:50:24 +02:00
sizer -> Add ( m_wiping_dialog_button , 0 , wxALIGN_CENTER_VERTICAL );
2018-10-04 16:43:10 +02:00
m_wiping_dialog_button -> Bind ( wxEVT_BUTTON , ([ parent ]( wxCommandEvent & e )
{
2019-04-26 10:16:08 +02:00
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 ;
2018-10-04 16:43:10 +02:00
2019-09-30 14:03:50 +02:00
const std :: vector < std :: string > extruder_colours = wxGetApp (). plater () -> get_extruder_colors_from_plater_config ();
2019-04-26 10:16:08 +02:00
WipingDialog dlg ( parent , cast < float > ( init_matrix ), cast < float > ( init_extruders ), extruder_colours );
2018-10-04 16:43:10 +02:00
if ( dlg . ShowModal () == wxID_OK ) {
std :: vector < float > matrix = dlg . get_matrix ();
std :: vector < float > extruders = dlg . get_extruders ();
2019-04-26 10:16:08 +02:00
( 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 14:44:05 +01:00
wxPostEvent ( parent , SimpleEvent ( EVT_SCHEDULE_BACKGROUND_PROCESS , parent ));
2018-10-04 16:43:10 +02:00
}
}));
2019-07-10 17:50:24 +02:00
2019-08-06 18:16:02 +02:00
auto btn = new ScalableButton ( parent , wxID_ANY , "mirroring_transparent.png" , wxEmptyString ,
2019-07-10 17:50:24 +02:00
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 );
2018-10-04 16:43:10 +02:00
return sizer ;
};
2019-02-07 14:44:05 +01:00
line . append_widget ( wiping_dialog_btn );
2019-01-15 09:31:53 +01:00
2019-02-07 14:44:05 +01:00
m_og -> append_line ( line );
2019-01-15 09:31:53 +01:00
2019-03-11 16:00:13 +01:00
2019-01-15 09:31:53 +01:00
// Frequently changed parameters for SLA_technology
m_og_sla = std :: make_shared < ConfigOptionsGroup > ( parent , "" );
2019-07-10 15:55:53 +02:00
m_og_sla -> hide_labels ();
2019-01-15 09:31:53 +01:00
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 18:16:02 +02: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" ))
2019-08-28 11:32:49 +02:00
new_conf . set_key_value ( "pad_around_object" , new ConfigOptionBool ( false ));
2019-08-06 18:16:02 +02:00
else if ( selection == _ ( "Around object" ))
2019-08-28 11:32:49 +02:00
new_conf . set_key_value ( "pad_around_object" , new ConfigOptionBool ( true ));
2019-03-11 16:00:13 +01:00
}
2019-05-03 18:01:39 +02:00
else
2019-03-11 16:00:13 +01:00
{
2019-05-03 18:01:39 +02:00
assert ( opt_key == "support" );
2019-03-11 16:00:13 +01:00
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-01-15 09:31:53 +01:00
2019-08-06 18:16:02 +02:00
tab -> load_config ( new_conf );
2019-01-15 09:31:53 +01:00
tab -> update_dirty ();
2019-03-11 16:00:13 +01:00
};
2019-02-07 14:44:05 +01:00
line = Line { "" , "" };
2019-05-03 18:01:39 +02:00
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" );
2019-03-11 16:00:13 +01:00
option . opt . full_width = true ;
2019-08-06 18:16:02 +02:00
line . append_option ( option );
2019-07-10 17:50:24 +02:00
line . append_widget ( empty_widget );
2019-03-11 16:00:13 +01:00
m_og_sla -> append_line ( line );
line = Line { "" , "" };
2019-02-07 14:44:05 +01:00
2019-08-06 18:16:02 +02: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 14:44:05 +01:00
line . append_option ( option );
2019-08-06 18:16:02 +02:00
line . append_widget ( empty_widget );
2019-02-07 14:44:05 +01:00
m_og_sla -> append_line ( line );
2019-01-15 09:31:53 +01:00
m_sizer = new wxBoxSizer ( wxVERTICAL );
m_sizer -> Add ( m_og -> sizer , 0 , wxEXPAND );
2019-02-07 14:44:05 +01:00
m_sizer -> Add ( m_og_sla -> sizer , 0 , wxEXPAND );
2018-10-04 16:43:10 +02:00
}
2019-01-15 09:31:53 +01:00
wxSizer * FreqChangedParams :: get_sizer ()
2018-10-24 12:57:23 +02:00
{
2019-01-15 09:31:53 +01:00
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 );
2018-10-24 12:57:23 +02:00
2019-08-06 18:16:02 +02:00
// correct showing of the FreqChangedParams sizer when m_wiping_dialog_button is hidden
2019-01-15 09:31:53 +01:00
if ( is_fff && ! is_wdb_shown )
2018-10-24 12:57:23 +02:00
m_wiping_dialog_button -> Hide ();
}
2019-01-15 09:31:53 +01:00
ConfigOptionsGroup * FreqChangedParams :: get_og ( const bool is_fff )
{
return is_fff ? m_og . get () : m_og_sla . get ();
}
2018-09-17 12:15:11 +02:00
// Sidebar / private
2019-03-01 11:00:34 +01:00
enum class ActionButtonType : int {
abReslice ,
abExport ,
abSendGCode
};
2018-09-17 12:15:11 +02:00
struct Sidebar :: priv
{
2018-10-04 11:12:55 +02:00
Plater * plater ;
2018-09-17 12:15:11 +02:00
wxScrolledWindow * scrolled ;
2019-03-13 13:13:18 +01:00
wxPanel * presets_panel ; // Used for MSW better layouts
2018-09-17 12:15:11 +02:00
2019-04-25 01:45:00 +02:00
ModeSizer * mode_sizer ;
2018-09-17 12:15:11 +02:00
wxFlexGridSizer * sizer_presets ;
PresetComboBox * combo_print ;
std :: vector < PresetComboBox *> combos_filament ;
wxBoxSizer * sizer_filaments ;
2018-11-16 17:36:23 +01:00
PresetComboBox * combo_sla_print ;
2018-09-17 12:15:11 +02:00
PresetComboBox * combo_sla_material ;
PresetComboBox * combo_printer ;
wxBoxSizer * sizer_params ;
2019-06-27 15:16:36 +02:00
FreqChangedParams * frequently_changed_parameters { nullptr };
ObjectList * object_list { nullptr };
ObjectManipulation * object_manipulation { nullptr };
ObjectSettings * object_settings { nullptr };
2019-06-29 12:38:03 +02:00
ObjectLayers * object_layers { nullptr };
2018-09-17 12:15:11 +02:00
ObjectInfo * object_info ;
SlicedInfo * sliced_info ;
wxButton * btn_export_gcode ;
wxButton * btn_reslice ;
wxButton * btn_send_gcode ;
2019-12-10 17:31:27 +01:00
ScalableButton * btn_disconnect ;
2018-10-04 11:12:55 +02:00
priv ( Plater * plater ) : plater ( plater ) {}
2019-06-27 15:16:36 +02:00
~ priv ();
2018-10-09 08:30:48 +02:00
2018-10-10 13:53:45 +02:00
void show_preset_comboboxes ();
2018-09-17 12:15:11 +02:00
};
2019-06-27 15:16:36 +02: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 ;
2019-06-29 12:38:03 +02:00
if ( object_layers != nullptr )
delete object_layers ;
2019-06-27 15:16:36 +02:00
}
2018-10-10 13:53:45 +02:00
void Sidebar :: priv :: show_preset_comboboxes ()
{
2019-02-22 09:38:56 +01:00
const bool showSLA = wxGetApp (). preset_bundle -> printers . get_edited_preset (). printer_technology () == ptSLA ;
2018-10-10 13:53:45 +02:00
2018-11-15 16:22:25 +01:00
for ( size_t i = 0 ; i < 4 ; ++ i )
sizer_presets -> Show ( i , ! showSLA );
2018-10-10 13:53:45 +02:00
2018-11-16 17:36:23 +01:00
for ( size_t i = 4 ; i < 8 ; ++ i ) {
2018-11-09 18:39:07 +01:00
if ( sizer_presets -> IsShown ( i ) != showSLA )
sizer_presets -> Show ( i , showSLA );
}
2018-10-10 13:53:45 +02:00
2018-11-15 16:22:25 +01:00
frequently_changed_parameters -> Show ( ! showSLA );
2018-10-10 13:53:45 +02:00
2018-11-15 11:15:24 +01:00
scrolled -> GetParent () -> Layout ();
scrolled -> Refresh ();
2018-10-10 13:53:45 +02:00
}
2018-09-17 12:15:11 +02:00
// Sidebar / public
2018-10-04 11:12:55 +02:00
Sidebar :: Sidebar ( Plater * parent )
2019-04-13 23:46:52 +02:00
: wxPanel ( parent , wxID_ANY , wxDefaultPosition , wxSize ( 40 * wxGetApp (). em_unit (), - 1 )), p ( new priv ( parent ))
2018-09-17 12:15:11 +02:00
{
2019-08-23 16:04:58 +02:00
p -> scrolled = new wxScrolledWindow ( this );
p -> scrolled -> SetScrollbars ( 0 , 100 , 1 , 2 );
2018-11-09 18:39:07 +01:00
2019-03-12 11:39:16 +01:00
2018-11-09 18:39:07 +01:00
// Sizer in the scrolled area
auto * scrolled_sizer = new wxBoxSizer ( wxVERTICAL );
p -> scrolled -> SetSizer ( scrolled_sizer );
2018-09-17 12:15:11 +02:00
2019-01-10 11:05:58 +01:00
// Sizer with buttons for mode changing
2019-07-25 16:23:32 +02:00
p -> mode_sizer = new ModeSizer ( p -> scrolled );
2019-01-10 11:05:58 +01:00
2018-09-17 12:15:11 +02:00
// The preset chooser
2019-02-07 14:44:05 +01:00
p -> sizer_presets = new wxFlexGridSizer ( 10 , 1 , 1 , 2 );
p -> sizer_presets -> AddGrowableCol ( 0 , 1 );
2018-10-10 13:53:45 +02:00
p -> sizer_presets -> SetFlexibleDirection ( wxBOTH );
2019-03-13 13:13:18 +01:00
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 13:37:36 +01:00
p -> presets_panel = p -> scrolled ;
2019-03-13 13:13:18 +01:00
#endif //__WINDOWS__
2018-09-17 12:15:11 +02:00
p -> sizer_filaments = new wxBoxSizer ( wxVERTICAL );
auto init_combo = [ this ]( PresetComboBox ** combo , wxString label , Preset :: Type preset_type , bool filament ) {
2019-03-13 13:13:18 +01:00
auto * text = new wxStaticText ( p -> presets_panel , wxID_ANY , label + " :" );
2018-10-01 15:09:31 +02:00
text -> SetFont ( wxGetApp (). small_font ());
2019-03-13 13:13:18 +01:00
* combo = new PresetComboBox ( p -> presets_panel , preset_type );
2018-09-17 12:15:11 +02:00
2019-03-18 15:10:40 +01:00
auto combo_and_btn_sizer = new wxBoxSizer ( wxHORIZONTAL );
combo_and_btn_sizer -> Add ( * combo , 1 , wxEXPAND );
if (( * combo ) -> edit_btn )
2019-08-06 18:16:02 +02:00
combo_and_btn_sizer -> Add (( * combo ) -> edit_btn , 0 , wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT ,
2019-05-17 15:49:37 +02:00
int ( 0.3 * wxGetApp (). em_unit ()));
2019-03-18 15:10:40 +01:00
2018-09-17 12:15:11 +02:00
auto * sizer_presets = this -> p -> sizer_presets ;
auto * sizer_filaments = this -> p -> sizer_filaments ;
2019-02-07 14:44:05 +01:00
sizer_presets -> Add ( text , 0 , wxALIGN_LEFT | wxEXPAND | wxRIGHT , 4 );
2018-09-17 12:15:11 +02:00
if ( ! filament ) {
2019-03-18 15:10:40 +01:00
sizer_presets -> Add ( combo_and_btn_sizer , 0 , wxEXPAND | wxBOTTOM , 1 );
2018-09-17 12:15:11 +02:00
} else {
2019-03-18 15:10:40 +01:00
sizer_filaments -> Add ( combo_and_btn_sizer , 0 , wxEXPAND | wxBOTTOM , 1 );
2018-10-10 13:53:45 +02:00
( * combo ) -> set_extruder_idx ( 0 );
2018-09-17 12:15:11 +02:00
sizer_presets -> Add ( sizer_filaments , 1 , wxEXPAND );
}
};
p -> combos_filament . push_back ( nullptr );
2019-05-09 17:18:03 +02:00
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 12:15:11 +02:00
2019-02-07 14:44:05 +01:00
const int margin_5 = int ( 0.5 * wxGetApp (). em_unit ()); // 5;
2018-10-04 16:43:10 +02:00
p -> sizer_params = new wxBoxSizer ( wxVERTICAL );
2018-10-01 15:09:31 +02:00
2018-09-17 12:15:11 +02:00
// Frequently changed parameters
2019-07-10 15:55:53 +02: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 18:16:02 +02:00
2018-10-03 15:14:52 +02:00
// Object List
2018-10-04 16:43:10 +02:00
p -> object_list = new ObjectList ( p -> scrolled );
2019-02-07 14:44:05 +01:00
p -> sizer_params -> Add ( p -> object_list -> get_sizer (), 1 , wxEXPAND );
2019-08-06 18:16:02 +02:00
2018-11-09 18:39:07 +01:00
// Object Manipulations
2018-10-04 16:43:10 +02:00
p -> object_manipulation = new ObjectManipulation ( p -> scrolled );
2018-11-09 18:39:07 +01:00
p -> object_manipulation -> Hide ();
2019-02-07 14:44:05 +01:00
p -> sizer_params -> Add ( p -> object_manipulation -> get_sizer (), 0 , wxEXPAND | wxTOP , margin_5 );
2018-09-17 12:15:11 +02:00
2018-11-09 18:39:07 +01:00
// Frequently Object Settings
p -> object_settings = new ObjectSettings ( p -> scrolled );
p -> object_settings -> Hide ();
2019-02-07 14:44:05 +01:00
p -> sizer_params -> Add ( p -> object_settings -> get_sizer (), 0 , wxEXPAND | wxTOP , margin_5 );
2019-08-06 18:16:02 +02:00
2019-05-28 16:38:04 +02: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-11-09 18:39:07 +01:00
2018-09-17 12:15:11 +02:00
// Info boxes
2018-10-01 15:09:31 +02:00
p -> object_info = new ObjectInfo ( p -> scrolled );
p -> sliced_info = new SlicedInfo ( p -> scrolled );
2018-09-17 12:15:11 +02:00
// Sizer in the scrolled area
2019-02-07 14:44:05 +01:00
scrolled_sizer -> Add ( p -> mode_sizer , 0 , wxALIGN_CENTER_HORIZONTAL /*RIGHT | wxBOTTOM | wxRIGHT, 5*/ );
2019-03-13 13:13:18 +01:00
is_msw ?
2019-08-06 18:16:02 +02:00
scrolled_sizer -> Add ( p -> presets_panel , 0 , wxEXPAND | wxLEFT , margin_5 ) :
2019-03-13 13:13:18 +01:00
scrolled_sizer -> Add ( p -> sizer_presets , 0 , wxEXPAND | wxLEFT , margin_5 );
2019-02-07 14:44:05 +01: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 12:15:11 +02:00
// Buttons underneath the scrolled area
2019-04-13 23:46:52 +02:00
2019-12-10 17:31:27 +01:00
auto init_btn = [ this ]( wxButton ** btn , wxString label , const std :: string icon_name = "" , wxString tooltip = wxEmptyString ) {
2019-08-06 18:16:02 +02:00
* btn = new wxButton ( this , wxID_ANY , label , wxDefaultPosition ,
2019-05-03 13:09:42 +02:00
wxDefaultSize , wxBU_EXACTFIT );
2019-04-13 23:46:52 +02:00
( * btn ) -> SetFont ( wxGetApp (). bold_font ());
2019-12-10 17:31:27 +01:00
( * btn ) -> SetToolTip ( tooltip );
if ( ! icon_name . empty ())
( * btn ) -> SetBitmap ( create_scaled_bitmap ( this , icon_name ));
2019-04-13 23:46:52 +02:00
};
2019-12-10 17:31:27 +01:00
init_btn ( & p -> btn_send_gcode , /*_(L("Send to printer"))*/ "" , "export_gcode" , _ ( L ( "Send to printer" )));
2019-04-13 23:46:52 +02:00
p -> btn_send_gcode -> Hide ();
init_btn ( & p -> btn_export_gcode , _ ( L ( "Export G-code" )) + dots );
init_btn ( & p -> btn_reslice , _ ( L ( "Slice now" )));
2019-12-10 17:31:27 +01:00
p -> btn_disconnect = new ScalableButton ( this , wxID_ANY , "revert_all_" , "" ,
wxDefaultSize , wxDefaultPosition , wxBU_EXACTFIT );
p -> btn_disconnect -> Hide ();
p -> btn_disconnect -> SetToolTip ( _ ( L ( "Remove device" )));
2018-12-04 10:28:05 +01:00
enable_buttons ( false );
2018-09-17 12:15:11 +02:00
auto * btns_sizer = new wxBoxSizer ( wxVERTICAL );
2019-12-10 17:31:27 +01:00
auto * complect_btns_sizer = new wxBoxSizer ( wxHORIZONTAL );
complect_btns_sizer -> Add ( p -> btn_export_gcode , 1 , wxEXPAND );
complect_btns_sizer -> Add ( p -> btn_send_gcode , 0 , wxEXPAND );
complect_btns_sizer -> Add ( p -> btn_disconnect );
2019-02-07 14:44:05 +01:00
btns_sizer -> Add ( p -> btn_reslice , 0 , wxEXPAND | wxTOP , margin_5 );
2019-12-10 17:31:27 +01:00
btns_sizer -> Add ( complect_btns_sizer , 0 , wxEXPAND | wxTOP , margin_5 );
// btns_sizer->Add(p->btn_send_gcode, 0, wxEXPAND | wxTOP, margin_5);
// btns_sizer->Add(p->btn_export_gcode, 0, wxEXPAND | wxTOP, margin_5);
2018-09-17 12:15:11 +02:00
auto * sizer = new wxBoxSizer ( wxVERTICAL );
2019-02-11 15:36:05 +01:00
sizer -> Add ( p -> scrolled , 1 , wxEXPAND );
2019-02-07 14:44:05 +01:00
sizer -> Add ( btns_sizer , 0 , wxEXPAND | wxLEFT , margin_5 );
2018-09-17 12:15:11 +02:00
SetSizer ( sizer );
2018-10-04 11:12:55 +02:00
// Events
p -> btn_export_gcode -> Bind ( wxEVT_BUTTON , [ this ]( wxCommandEvent & ) { p -> plater -> export_gcode (); });
2019-03-01 12:03:14 +01:00
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 ();
2019-03-12 09:41:57 +01:00
else
p -> plater -> reslice ();
2019-08-06 18:16:02 +02:00
p -> plater -> select_view_3D ( "Preview" );
2019-03-01 12:03:14 +01:00
});
2018-10-04 11:12:55 +02:00
p -> btn_send_gcode -> Bind ( wxEVT_BUTTON , [ this ]( wxCommandEvent & ) { p -> plater -> send_gcode (); });
2019-12-10 17:31:27 +01:00
p -> btn_disconnect -> Bind ( wxEVT_BUTTON , [ this ]( wxCommandEvent & ) {
// #dk_FIXME
2019-12-11 12:28:51 +01:00
p -> plater -> eject_drive ();
2019-12-10 17:31:27 +01:00
});
2018-09-17 12:15:11 +02:00
}
Sidebar ::~ Sidebar () {}
2018-10-10 13:53:45 +02:00
void Sidebar :: init_filament_combo ( PresetComboBox ** combo , const int extr_idx ) {
2019-03-13 13:13:18 +01:00
* combo = new PresetComboBox ( p -> presets_panel , Slic3r :: Preset :: TYPE_FILAMENT );
2018-10-10 13:53:45 +02:00
// # copy icons from first choice
// $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets;
( * combo ) -> set_extruder_idx ( extr_idx );
2019-03-18 15:10:40 +01:00
auto combo_and_btn_sizer = new wxBoxSizer ( wxHORIZONTAL );
combo_and_btn_sizer -> Add ( * combo , 1 , wxEXPAND );
2019-05-17 16:05:32 +02:00
combo_and_btn_sizer -> Add (( * combo ) -> edit_btn , 0 , wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT ,
2019-05-17 15:49:37 +02:00
int ( 0.3 * wxGetApp (). em_unit ()));
2019-03-18 15:10:40 +01:00
2018-10-10 13:53:45 +02:00
auto /***/ sizer_filaments = this -> p -> sizer_filaments ;
2019-03-18 15:10:40 +01:00
sizer_filaments -> Add ( combo_and_btn_sizer , 1 , wxEXPAND | wxBOTTOM , 1 );
2018-10-10 13:53:45 +02:00
}
2019-09-06 17:46:55 +02:00
void Sidebar :: remove_unused_filament_combos ( const size_t current_extruder_count )
2018-10-10 13:53:45 +02:00
{
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 09:56:32 +02: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 )
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 ( print_tech == ptFFF ) {
for ( size_t i = 0 ; i < p -> combos_filament . size (); ++ i )
2019-04-23 16:33:06 +02:00
preset_bundle . update_platter_filament_ui ( i , p -> combos_filament [ i ]);
2019-04-10 09:56:32 +02:00
}
}
2018-09-17 12:15:11 +02:00
void Sidebar :: update_presets ( Preset :: Type preset_type )
{
2019-08-06 18:16:02 +02:00
PresetBundle & preset_bundle = * wxGetApp (). preset_bundle ;
2019-02-22 09:38:56 +01:00
const auto print_tech = preset_bundle . printers . get_edited_preset (). printer_technology ();
2018-10-31 16:22:36 +01:00
2018-09-17 12:15:11 +02:00
switch ( preset_type ) {
2019-08-06 18:16:02 +02:00
case Preset :: TYPE_FILAMENT :
2019-01-15 15:54:20 +01:00
{
2019-09-06 17:46:55 +02:00
const size_t extruder_cnt = print_tech != ptFFF ? 1 :
2019-01-15 15:54:20 +01:00
dynamic_cast < ConfigOptionFloats *> ( preset_bundle . printers . get_edited_preset (). config . option ( "nozzle_diameter" )) -> values . size ();
2019-09-06 17:46:55 +02:00
const size_t filament_cnt = p -> combos_filament . size () > extruder_cnt ? extruder_cnt : p -> combos_filament . size ();
2019-01-15 15:54:20 +01:00
if ( filament_cnt == 1 ) {
2018-09-17 12:15:11 +02:00
// Single filament printer, synchronize the filament presets.
2019-05-22 11:31:36 +02:00
const std :: string & name = preset_bundle . filaments . get_selected_preset_name ();
2019-01-15 15:54:20 +01:00
preset_bundle . set_filament_preset ( 0 , name );
2018-09-17 12:15:11 +02:00
}
2019-01-15 15:54:20 +01:00
for ( size_t i = 0 ; i < filament_cnt ; i ++ ) {
2019-04-23 16:33:06 +02:00
preset_bundle . update_platter_filament_ui ( i , p -> combos_filament [ i ]);
2018-09-17 12:15:11 +02:00
}
2018-10-03 16:27:02 +02:00
2018-09-17 12:15:11 +02:00
break ;
2019-01-15 15:54:20 +01:00
}
2018-09-17 12:15:11 +02:00
case Preset :: TYPE_PRINT :
2019-08-06 18:16:02 +02:00
preset_bundle . prints . update_platter_ui ( p -> combo_print );
2018-09-17 12:15:11 +02:00
break ;
2018-11-16 17:36:23 +01:00
case Preset :: TYPE_SLA_PRINT :
2019-08-06 18:16:02 +02:00
preset_bundle . sla_prints . update_platter_ui ( p -> combo_sla_print );
2018-11-16 17:36:23 +01:00
break ;
2018-09-17 12:15:11 +02:00
case Preset :: TYPE_SLA_MATERIAL :
2019-08-06 18:16:02 +02:00
preset_bundle . sla_materials . update_platter_ui ( p -> combo_sla_material );
2018-09-17 12:15:11 +02:00
break ;
2019-08-06 18:16:02 +02:00
case Preset :: TYPE_PRINTER :
{
2019-04-10 09:56:32 +02:00
update_all_preset_comboboxes ();
2019-08-06 18:16:02 +02:00
p -> show_preset_comboboxes ();
break ;
}
2018-09-17 12:15:11 +02:00
default : break ;
}
// Synchronize config.ini with the current selections.
2018-10-03 16:27:02 +02:00
wxGetApp (). preset_bundle -> export_selections ( * wxGetApp (). app_config );
2018-09-17 12:15:11 +02:00
}
2019-03-01 12:03:14 +01:00
void Sidebar :: update_mode_sizer () const
2019-01-10 11:05:58 +01:00
{
2019-03-01 12:03:14 +01:00
p -> mode_sizer -> SetMode ( m_mode );
}
void Sidebar :: update_reslice_btn_tooltip () const
{
2019-03-26 14:00:53 +01:00
wxString tooltip = wxString ( "Slice" ) + " [" + GUI :: shortkey_ctrl_prefix () + "R]" ;
if ( m_mode != comSimple )
2019-08-06 18:16:02 +02:00
tooltip += wxString ( " \n " ) + _ ( L ( "Hold Shift to Slice & Export G-code" ));
2019-03-01 12:03:14 +01:00
p -> btn_reslice -> SetToolTip ( tooltip );
2019-01-10 11:05:58 +01:00
}
2019-08-06 18:16:02 +02:00
void Sidebar :: msw_rescale ()
2019-04-13 23:46:52 +02:00
{
SetMinSize ( wxSize ( 40 * wxGetApp (). em_unit (), - 1 ));
2019-04-25 01:45:00 +02:00
p -> mode_sizer -> msw_rescale ();
2019-04-13 23:46:52 +02:00
2019-04-23 16:33:06 +02:00
// Rescale preset comboboxes in respect to the current em_unit ...
2019-04-13 23:46:52 +02:00
for ( PresetComboBox * combo : std :: vector < PresetComboBox *> { p -> combo_print ,
p -> combo_sla_print ,
p -> combo_sla_material ,
p -> combo_printer } )
2019-04-25 01:45:00 +02:00
combo -> msw_rescale ();
2019-04-13 23:46:52 +02:00
for ( PresetComboBox * combo : p -> combos_filament )
2019-04-25 01:45:00 +02:00
combo -> msw_rescale ();
2019-04-13 23:46:52 +02:00
2019-04-23 16:33:06 +02:00
// ... then refill them and set min size to correct layout of the sidebar
update_all_preset_comboboxes ();
2019-07-10 17:50:24 +02:00
p -> frequently_changed_parameters -> msw_rescale ();
2019-04-25 01:45:00 +02:00
p -> object_list -> msw_rescale ();
2019-04-29 15:27:59 +02:00
p -> object_manipulation -> msw_rescale ();
2019-04-25 01:45:00 +02:00
p -> object_settings -> msw_rescale ();
2019-05-28 16:38:04 +02:00
p -> object_layers -> msw_rescale ();
2019-04-13 23:46:52 +02:00
2019-04-25 01:45:00 +02:00
p -> object_info -> msw_rescale ();
2019-04-13 23:46:52 +02:00
p -> scrolled -> Layout ();
}
2018-10-04 16:43:10 +02:00
ObjectManipulation * Sidebar :: obj_manipul ()
{
return p -> object_manipulation ;
}
2018-10-05 23:29:15 +02:00
ObjectList * Sidebar :: obj_list ()
2018-10-03 15:14:52 +02:00
{
2018-10-05 23:29:15 +02:00
return p -> object_list ;
2018-10-03 15:14:52 +02:00
}
2018-11-09 18:39:07 +01:00
ObjectSettings * Sidebar :: obj_settings ()
{
return p -> object_settings ;
}
2019-05-28 16:38:04 +02:00
ObjectLayers * Sidebar :: obj_layers ()
{
return p -> object_layers ;
}
2018-11-09 18:39:07 +01:00
wxScrolledWindow * Sidebar :: scrolled_panel ()
{
return p -> scrolled ;
}
2019-03-13 13:13:18 +01:00
wxPanel * Sidebar :: presets_panel ()
{
return p -> presets_panel ;
}
2019-01-15 09:31:53 +01:00
ConfigOptionsGroup * Sidebar :: og_freq_chng_params ( const bool is_fff )
2018-10-05 23:29:15 +02:00
{
2019-01-15 09:31:53 +01:00
return p -> frequently_changed_parameters -> get_og ( is_fff );
2018-10-03 15:14:52 +02:00
}
wxButton * Sidebar :: get_wiping_dialog_button ()
{
2018-10-04 16:43:10 +02:00
return p -> frequently_changed_parameters -> get_wiping_dialog_button ();
}
2019-09-06 17:46:55 +02:00
void Sidebar :: update_objects_list_extruder_column ( size_t extruders_count )
2018-10-04 16:43:10 +02:00
{
p -> object_list -> update_objects_list_extruder_column ( extruders_count );
}
2018-11-13 12:19:56 +01:00
void Sidebar :: show_info_sizer ()
2018-10-05 23:29:15 +02:00
{
2018-11-13 12:19:56 +01:00
if ( ! p -> plater -> is_single_full_object_selection () ||
2019-01-10 11:05:58 +01:00
m_mode < comExpert ||
2018-11-14 16:24:36 +01:00
p -> plater -> model (). objects . empty ()) {
2018-11-01 12:33:56 +01:00
p -> object_info -> Show ( false );
return ;
}
2018-11-09 18:39:07 +01:00
int obj_idx = p -> plater -> get_selected_object_idx ();
2018-11-14 16:24:36 +01:00
const ModelObject * model_object = p -> plater -> model (). objects [ obj_idx ];
2018-11-13 15:24:05 +01:00
// hack to avoid crash when deleting the last object on the bed
if ( model_object -> volumes . empty ())
{
p -> object_info -> Show ( false );
return ;
}
2018-11-15 11:15:24 +01:00
auto size = model_object -> bounding_box (). size ();
2018-11-01 12:33:56 +01:00
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 ())));
2019-04-24 16:04:47 +02:00
const auto & stats = model_object -> get_object_stl_stats (); //model_object->volumes.front()->mesh.stl.stats;
2019-03-04 15:29:07 +01:00
p -> object_info -> info_volume -> SetLabel ( wxString :: Format ( "%.2f" , stats . volume ));
2018-11-01 12:33:56 +01:00
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 );
2019-08-06 18:16:02 +02:00
2019-01-21 12:34:28 +01:00
tooltip += ": \n " + wxString :: Format ( _ ( L ( "%d degenerate facets, %d edges fixed, %d facets removed, "
2018-11-01 12:33:56 +01:00
"%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 );
2019-08-06 18:16:02 +02:00
}
2018-11-01 12:33:56 +01:00
else {
2019-05-04 02:07:07 +02:00
p -> object_info -> info_manifold -> SetLabel ( _ ( L ( "Yes" )));
2018-11-01 12:33:56 +01: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 );
2019-02-19 12:14:13 +01:00
if ( p -> plater -> printer_technology () == ptSLA ) {
2019-02-21 15:46:04 +01:00
for ( auto item : p -> object_info -> sla_hidden_items )
2019-02-19 12:14:13 +01:00
item -> Show ( false );
}
2018-10-31 12:26:57 +01:00
}
2019-09-26 16:33:55 +02:00
void Sidebar :: update_sliced_info_sizer ()
2018-10-31 12:26:57 +01:00
{
2019-09-26 16:33:55 +02:00
if ( p -> sliced_info -> IsShown ( size_t ( 0 )))
{
2019-02-12 16:34:42 +01:00
if ( p -> plater -> printer_technology () == ptSLA )
{
const SLAPrintStatistics & ps = p -> plater -> sla_print (). print_statistics ();
2019-02-18 12:28:58 +01:00
wxString new_label = _ ( L ( "Used Material (ml)" )) + " :" ;
2019-02-13 15:35:41 +01:00
const bool is_supports = ps . support_used_material > 0.0 ;
if ( is_supports )
new_label += wxString :: Format ( " \n - %s \n - %s" , _ ( L ( "object(s)" )), _ ( L ( "supports and pad" )));
wxString info_text = is_supports ?
2019-02-18 12:28:58 +01: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 13:18:47 +01:00
wxString :: Format ( "%.2f" , ( ps . objects_used_material + ps . support_used_material ) / 1000 );
2019-02-13 15:35:41 +01:00
p -> sliced_info -> SetTextAndShow ( siMateril_unit , info_text , new_label );
2019-09-26 16:33:55 +02:00
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 ( "%.2f" , material_cost * ( ps . objects_used_material + ps . support_used_material ) / 1000 );
}
p -> sliced_info -> SetTextAndShow ( siCost , str_total_cost );
2019-09-11 12:13:59 +02:00
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" )) + " :" );
2019-02-12 16:34:42 +01:00
// 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 18:16:02 +02:00
{
2019-02-21 15:46:04 +01:00
const PrintStatistics & ps = p -> plater -> fff_print (). print_statistics ();
const bool is_wipe_tower = ps . total_wipe_tower_filament > 0 ;
2018-10-31 12:26:57 +01:00
2019-02-21 15:46:04 +01:00
wxString new_label = _ ( L ( "Used Filament (m)" ));
if ( is_wipe_tower )
new_label += wxString :: Format ( " : \n - %s \n - %s" , _ ( L ( "objects" )), _ ( L ( "wipe tower" )));
2018-11-20 10:33:54 +01:00
2019-02-21 15:46:04 +01:00
wxString info_text = is_wipe_tower ?
wxString :: Format ( "%.2f \n %.2f \n %.2f" , ps . total_used_filament / 1000 ,
2019-08-06 18:16:02 +02:00
( ps . total_used_filament - ps . total_wipe_tower_filament ) / 1000 ,
2019-02-21 15:46:04 +01:00
ps . total_wipe_tower_filament / 1000 ) :
wxString :: Format ( "%.2f" , ps . total_used_filament / 1000 );
p -> sliced_info -> SetTextAndShow ( siFilament_m , info_text , new_label );
2018-11-20 10:33:54 +01:00
2019-02-21 15:46:04 +01:00
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 ));
2018-10-31 12:26:57 +01:00
2018-11-20 10:33:54 +01:00
2019-02-21 15:46:04 +01:00
new_label = _ ( L ( "Cost" ));
if ( is_wipe_tower )
new_label += wxString :: Format ( " : \n - %s \n - %s" , _ ( L ( "objects" )), _ ( L ( "wipe tower" )));
2019-08-06 18:16:02 +02:00
2019-02-21 15:46:04 +01:00
info_text = is_wipe_tower ?
wxString :: Format ( "%.2f \n %.2f \n %.2f" , ps . total_cost ,
2019-08-06 18:16:02 +02:00
( ps . total_cost - ps . total_wipe_tower_cost ),
2019-02-21 15:46:04 +01:00
ps . total_wipe_tower_cost ) :
wxString :: Format ( "%.2f" , ps . total_cost );
p -> sliced_info -> SetTextAndShow ( siCost , info_text , new_label );
2018-11-20 10:33:54 +01:00
2019-02-21 15:46:04 +01:00
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 = "" ;
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 );
2019-07-09 15:08:34 +02:00
for ( int i = ( int ) ps . estimated_normal_color_print_times . size () - 1 ; i >= 0 ; -- i )
2019-07-08 08:40:20 +02:00
{
2019-08-28 15:12:55 +02:00
new_label += wxString :: Format ( " \n - %s%d" , _ ( L ( "Color" )) + " " , i + 1 );
2019-07-08 08:40:20 +02:00
info_text += wxString :: Format ( " \n %s" , ps . estimated_normal_color_print_times [ i ]);
}
2019-02-21 15:46:04 +01:00
}
if ( ps . estimated_silent_print_time != "N/A" ) {
2019-05-09 15:39:15 +02:00
new_label += wxString :: Format ( " \n - %s" , _ ( L ( "stealth mode" )));
2019-02-21 15:46:04 +01:00
info_text += wxString :: Format ( " \n %s" , ps . estimated_silent_print_time );
2019-08-02 12:05:02 +02:00
for ( int i = ( int ) ps . estimated_silent_color_print_times . size () - 1 ; i >= 0 ; -- i )
2019-07-08 08:40:20 +02:00
{
2019-08-28 15:12:55 +02:00
new_label += wxString :: Format ( " \n - %s%d" , _ ( L ( "Color" )) + " " , i + 1 );
2019-08-02 12:05:02 +02:00
info_text += wxString :: Format ( " \n %s" , ps . estimated_silent_color_print_times [ i ]);
2019-07-08 08:40:20 +02:00
}
2019-02-21 15:46:04 +01:00
}
p -> sliced_info -> SetTextAndShow ( siEstimatedTime , info_text , new_label );
2018-11-20 10:33:54 +01:00
}
2018-10-31 12:26:57 +01:00
2019-02-21 15:46:04 +01:00
// if there is a wipe tower, insert number of toolchanges info into the array:
2019-11-05 11:37:40 +01:00
p -> sliced_info -> SetTextAndShow ( siWTNumbetOfToolchanges , is_wipe_tower ? wxString :: Format ( "%.d" , ps . total_toolchanges ) : "N/A" );
2019-02-12 16:34:42 +01:00
2019-02-21 15:46:04 +01:00
// Hide non-FFF sliced info parameters
p -> sliced_info -> SetTextAndShow ( siMateril_unit , "N/A" );
2019-08-06 18:16:02 +02:00
}
2018-10-31 12:26:57 +01:00
}
2019-09-26 16:33:55 +02: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 ();
2018-10-31 12:26:57 +01:00
2018-11-13 12:19:56 +01:00
Layout ();
2018-11-15 11:15:24 +01:00
p -> scrolled -> Refresh ();
2018-10-05 23:29:15 +02:00
}
2018-10-08 19:14:55 +02:00
void Sidebar :: enable_buttons ( bool enable )
{
p -> btn_reslice -> Enable ( enable );
p -> btn_export_gcode -> Enable ( enable );
p -> btn_send_gcode -> Enable ( enable );
2019-12-10 17:31:27 +01:00
p -> btn_disconnect -> Enable ( enable );
2018-10-08 19:14:55 +02:00
}
2019-04-24 19:03:05 +02: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 ); }
2019-12-10 17:31:27 +01:00
bool Sidebar :: show_disconnect ( bool show ) const { return p -> btn_disconnect -> Show ( show ); }
2018-10-15 10:53:47 +02:00
2018-10-09 12:41:05 +02:00
bool Sidebar :: is_multifilament ()
{
2018-11-01 15:58:14 +01:00
return p -> combos_filament . size () > 1 ;
2018-10-09 12:41:05 +02:00
}
2018-10-10 13:53:45 +02:00
2019-03-01 12:03:14 +01:00
void Sidebar :: update_mode ()
{
m_mode = wxGetApp (). get_mode ();
update_reslice_btn_tooltip ();
update_mode_sizer ();
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 18:16:02 +02:00
2019-03-01 12:03:14 +01:00
Layout ();
}
2018-10-10 13:53:45 +02:00
std :: vector < PresetComboBox *>& Sidebar :: combos_filament ()
{
return p -> combos_filament ;
}
2018-09-17 12:15:11 +02:00
// Plater::DropTarget
class PlaterDropTarget : public wxFileDropTarget
{
public :
2019-08-06 18:16:02 +02:00
PlaterDropTarget ( Plater * plater ) : plater ( plater ) { this -> SetDefaultAction ( wxDragCopy ); }
2018-09-17 12:15:11 +02:00
virtual bool OnDropFiles ( wxCoord x , wxCoord y , const wxArrayString & filenames );
private :
Plater * plater ;
2018-10-08 19:14:55 +02:00
static const std :: regex pattern_drop ;
2018-09-17 12:15:11 +02:00
};
2018-10-22 11:52:13 +02:00
const std :: regex PlaterDropTarget :: pattern_drop ( ".*[.](stl|obj|amf|3mf|prusa)" , std :: regex :: icase );
2018-10-08 19:14:55 +02:00
2018-09-17 12:15:11 +02:00
bool PlaterDropTarget :: OnDropFiles ( wxCoord x , wxCoord y , const wxArrayString & filenames )
{
2018-10-08 19:14:55 +02:00
std :: vector < fs :: path > paths ;
for ( const auto & filename : filenames ) {
2019-01-02 15:11:05 +01:00
fs :: path path ( into_path ( filename ));
2018-10-08 19:14:55 +02:00
if ( std :: regex_match ( path . string (), pattern_drop )) {
paths . push_back ( std :: move ( path ));
} else {
return false ;
}
}
2019-08-06 18:16:02 +02:00
wxString snapshot_label ;
assert ( ! paths . empty ());
if ( paths . size () == 1 ) {
snapshot_label = _ ( L ( "Load File" ));
snapshot_label += ": " ;
snapshot_label += wxString :: FromUTF8 ( paths . front (). filename (). string (). c_str ());
} else {
snapshot_label = _ ( L ( "Load Files" ));
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 );
2019-07-17 15:48:53 +02:00
2019-05-15 14:09:16 +02:00
// 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)
2019-09-02 15:15:28 +02:00
std :: vector < size_t > res = plater -> load_files ( paths );
2019-05-15 14:09:16 +02:00
// 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
2019-09-02 15:15:28 +02:00
// if res is empty no data has been loaded
if ( ! res . empty () && plater -> get_project_filename (). empty ())
2019-05-15 14:09:16 +02:00
{
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 19:14:55 +02:00
return true ;
2018-09-17 12:15:11 +02:00
}
// Plater / private
struct Plater :: priv
{
// PIMPL back pointer ("Q-Pointer")
Plater * q ;
MainFrame * main_frame ;
2018-10-17 12:17:25 +02:00
// Object popup menu
2019-04-25 01:45:00 +02:00
MenuWithSeparators object_menu ;
2018-12-07 17:50:48 +01:00
// Part popup menu
2019-04-25 01:45:00 +02:00
MenuWithSeparators part_menu ;
2018-12-07 17:50:48 +01:00
// SLA-Object popup menu
2019-04-25 01:45:00 +02:00
MenuWithSeparators sla_object_menu ;
2019-09-20 09:57:27 +02:00
// Default popup menu (when nothing is selected on 3DScene)
MenuWithSeparators default_menu ;
2019-01-25 10:34:32 +01:00
2019-03-19 09:51:50 +01:00
// 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 12:15:11 +02:00
// Data
2018-12-05 15:22:03 +01:00
Slic3r :: DynamicPrintConfig * config ; // FIXME: leak?
2018-12-03 13:14:28 +01:00
Slic3r :: Print fff_print ;
2019-08-06 18:16:02 +02:00
Slic3r :: SLAPrint sla_print ;
2018-11-13 17:45:44 +01:00
Slic3r :: Model model ;
PrinterTechnology printer_technology = ptFFF ;
Slic3r :: GCodePreviewData gcode_preview_data ;
2019-07-26 13:44:33 +02:00
2018-09-17 12:15:11 +02:00
// GUI elements
2019-03-05 10:54:03 +01:00
wxSizer * panel_sizer { nullptr };
wxPanel * current_panel { nullptr };
2018-12-04 13:55:25 +01:00
std :: vector < wxPanel *> panels ;
2018-09-17 12:15:11 +02:00
Sidebar * sidebar ;
2019-02-19 15:15:27 +01:00
Bed3D bed ;
2019-03-06 15:46:19 +01:00
Camera camera ;
2019-09-27 14:52:19 +02:00
Mouse3DController mouse3d_controller ;
2018-12-04 13:55:25 +01:00
View3D * view3D ;
2018-12-17 10:55:14 +01:00
GLToolbar view_toolbar ;
2018-10-04 16:43:10 +02:00
Preview * preview ;
2018-11-15 15:27:39 +01:00
2018-10-17 11:12:38 +02:00
BackgroundSlicingProcess background_process ;
2019-06-26 09:48:52 +02:00
bool suppressed_backround_processing_update { false };
2019-08-06 18:16:02 +02:00
2019-07-16 18:33:42 +02:00
// Cache the wti info
class WipeTower : public GLCanvas3D :: WipeTowerInfo {
2019-07-17 13:32:31 +02:00
using ArrangePolygon = arrangement :: ArrangePolygon ;
2019-07-16 18:33:42 +02:00
friend priv ;
public :
2019-08-06 18:16:02 +02:00
2019-07-17 13:32:31 +02:00
void apply_arrange_result ( const Vec2crd & tr , double rotation )
{
m_pos = unscaled ( tr ); m_rotation = rotation ;
2019-07-16 18:33:42 +02:00
apply_wipe_tower ();
}
2019-08-06 18:16:02 +02:00
2019-07-17 13:32:31 +02:00
ArrangePolygon get_arrange_polygon () const
2019-07-16 18:33:42 +02:00
{
Polygon p ({
{ coord_t ( 0 ), coord_t ( 0 )},
{ scaled ( m_bb_size ( X )), coord_t ( 0 )},
{ scaled ( m_bb_size )},
{ coord_t ( 0 ), scaled ( m_bb_size ( Y ))},
{ coord_t ( 0 ), coord_t ( 0 )},
});
2019-08-06 18:16:02 +02:00
2019-07-17 13:32:31 +02:00
ArrangePolygon ret ;
2019-07-16 18:33:42 +02:00
ret . poly . contour = std :: move ( p );
2019-07-17 13:32:31 +02:00
ret . translation = scaled ( m_pos );
2019-07-16 18:33:42 +02:00
ret . rotation = m_rotation ;
return ret ;
}
2019-07-17 13:32:31 +02:00
} wipetower ;
2019-08-06 18:16:02 +02:00
2019-07-17 13:32:31 +02:00
WipeTower & updated_wipe_tower () {
2019-07-16 18:33:42 +02:00
auto wti = view3D -> get_canvas3d () -> get_wipe_tower_info ();
2019-07-17 13:32:31 +02:00
wipetower . m_pos = wti . pos ();
wipetower . m_rotation = wti . rotation ();
wipetower . m_bb_size = wti . bb_size ();
return wipetower ;
2019-07-16 18:33:42 +02:00
}
2019-08-06 18:16:02 +02:00
2019-05-14 10:34:11 +02:00
// A class to handle UI jobs like arranging and optimizing rotation.
// These are not instant jobs, the user has to be informed about their
2019-08-06 18:16:02 +02:00
// state in the status progress indicator. On the other hand they are
2019-05-14 10:34:11 +02:00
// separated from the background slicing process. Ideally, these jobs should
// run when the background process is not running.
//
// TODO: A mechanism would be useful for blocking the plater interactions:
// objects would be frozen for the user. In case of arrange, an animation
// could be shown, or with the optimize orientations, partial results
// could be displayed.
2019-07-15 17:30:44 +02:00
class Job : public wxEvtHandler
{
int m_range = 100 ;
2019-11-07 12:09:04 +01:00
boost :: thread m_thread ;
2019-07-15 17:30:44 +02:00
priv * m_plater = nullptr ;
std :: atomic < bool > m_running { false }, m_canceled { false };
bool m_finalized = false ;
void run ()
{
m_running . store ( true );
process ();
m_running . store ( false );
2019-05-13 18:58:56 +02:00
// ensure to call the last status to finalize the job
update_status ( status_range (), "" );
}
2019-07-15 17:30:44 +02:00
2019-05-13 18:58:56 +02:00
protected :
// status range for a particular job
virtual int status_range () const { return 100 ; }
2019-07-15 17:30:44 +02:00
2019-05-14 10:34:11 +02:00
// status update, to be used from the work thread (process() method)
2019-07-15 17:30:44 +02:00
void update_status ( int st , const wxString & msg = "" )
{
auto evt = new wxThreadEvent ();
evt -> SetInt ( st );
evt -> SetString ( msg );
wxQueueEvent ( this , evt );
2019-05-13 18:58:56 +02:00
}
2019-07-15 17:30:44 +02:00
2019-07-19 18:10:10 +02:00
priv & plater () { return * m_plater ; }
const priv & plater () const { return * m_plater ; }
bool was_canceled () const { return m_canceled . load (); }
2019-07-15 17:30:44 +02:00
2019-06-18 16:24:30 +02:00
// Launched just before start(), a job can use it to prepare internals
virtual void prepare () {}
2019-07-15 17:30:44 +02:00
// Launched when the job is finished. It refreshes the 3Dscene by def.
virtual void finalize ()
{
2019-06-18 16:24:30 +02:00
// Do a full refresh of scene tree, including regenerating
// all the GLVolumes. FIXME The update function shall just
// reload the modified matrices.
2019-11-07 12:09:04 +01:00
if ( ! was_canceled ())
plater (). update ( unsigned ( UpdateParams :: FORCE_FULL_SCREEN_REFRESH ));
2019-06-18 16:24:30 +02:00
}
2019-07-15 17:30:44 +02:00
2019-05-13 18:58:56 +02:00
public :
2019-07-15 17:30:44 +02:00
Job ( priv * _plater ) : m_plater ( _plater )
2019-05-14 10:34:11 +02:00
{
2019-07-15 17:30:44 +02:00
Bind ( wxEVT_THREAD , [ this ]( const wxThreadEvent & evt ) {
2019-05-13 18:58:56 +02:00
auto msg = evt . GetString ();
2019-07-15 17:30:44 +02:00
if ( ! msg . empty ())
plater (). statusbar () -> set_status_text ( msg );
if ( m_finalized ) return ;
2019-05-13 18:58:56 +02:00
plater (). statusbar () -> set_progress ( evt . GetInt ());
2019-07-15 17:30:44 +02:00
if ( evt . GetInt () == status_range ()) {
2019-05-13 18:58:56 +02:00
// set back the original range and cancel callback
plater (). statusbar () -> set_range ( m_range );
plater (). statusbar () -> set_cancel_callback ();
wxEndBusyCursor ();
2019-07-15 17:30:44 +02:00
2019-06-18 16:24:30 +02:00
finalize ();
2019-07-15 17:30:44 +02:00
2019-05-13 18:58:56 +02:00
// dont do finalization again for the same process
2019-06-18 16:24:30 +02:00
m_finalized = true ;
2019-05-13 18:58:56 +02:00
}
});
}
2019-07-15 17:30:44 +02:00
Job ( const Job & ) = delete ;
2019-11-07 12:09:04 +01:00
Job ( Job && ) = delete ;
2019-07-15 17:30:44 +02:00
Job & operator = ( const Job & ) = delete ;
2019-11-07 12:09:04 +01:00
Job & operator = ( Job && ) = delete ;
2019-07-15 17:30:44 +02:00
2019-05-13 18:58:56 +02:00
virtual void process () = 0 ;
2019-07-15 17:30:44 +02:00
void start ()
{ // Start the job. No effect if the job is already running
if ( ! m_running . load ()) {
prepare ();
2019-05-14 10:34:11 +02:00
// Save the current status indicatior range and push the new one
2019-05-13 18:58:56 +02:00
m_range = plater (). statusbar () -> get_range ();
plater (). statusbar () -> set_range ( status_range ());
2019-07-15 17:30:44 +02:00
2019-05-14 10:34:11 +02:00
// init cancellation flag and set the cancel callback
m_canceled . store ( false );
2019-07-15 17:30:44 +02:00
plater (). statusbar () -> set_cancel_callback (
[ this ]() { m_canceled . store ( true ); });
2019-06-18 16:24:30 +02:00
m_finalized = false ;
2019-07-15 17:30:44 +02:00
2019-05-14 10:34:11 +02:00
// Changing cursor to busy
2019-05-13 18:58:56 +02:00
wxBeginBusyCursor ();
2019-07-15 17:30:44 +02:00
try { // Execute the job
2019-11-07 12:57:40 +01:00
m_thread = create_thread ([ this ] { this -> run (); });
2019-07-15 17:30:44 +02:00
} catch ( std :: exception & ) {
update_status ( status_range (),
_ ( L ( "ERROR: not enough resources to "
"execute a new job." )));
2019-05-14 10:34:11 +02:00
}
2019-07-15 17:30:44 +02:00
2019-05-14 10:34:11 +02:00
// The state changes will be undone when the process hits the
// last status value, in the status update handler (see ctor)
2019-05-13 18:58:56 +02:00
}
}
2019-07-15 17:30:44 +02:00
// To wait for the running job and join the threads. False is
// returned if the timeout has been reached and the job is still
// running. Call cancel() before this fn if you want to explicitly
// end the job.
2019-11-07 12:09:04 +01:00
bool join ( int timeout_ms = 0 )
2019-07-15 17:30:44 +02:00
{
2019-11-07 12:09:04 +01:00
if ( ! m_thread . joinable ()) return true ;
2019-07-15 17:30:44 +02:00
if ( timeout_ms <= 0 )
2019-11-07 12:09:04 +01:00
m_thread . join ();
else if ( ! m_thread . try_join_for ( boost :: chrono :: milliseconds ( timeout_ms )))
2019-05-13 18:58:56 +02:00
return false ;
2019-11-07 12:09:04 +01:00
2019-05-13 18:58:56 +02:00
return true ;
}
2019-07-15 17:30:44 +02:00
2019-05-13 18:58:56 +02:00
bool is_running () const { return m_running . load (); }
void cancel () { m_canceled . store ( true ); }
};
2019-07-15 17:30:44 +02:00
2019-06-18 16:24:30 +02:00
enum class Jobs : size_t {
Arrange ,
Rotoptimize
};
2019-08-06 18:16:02 +02:00
2019-07-15 17:30:44 +02:00
class ArrangeJob : public Job
{
2019-07-16 18:33:42 +02:00
using ArrangePolygon = arrangement :: ArrangePolygon ;
using ArrangePolygons = arrangement :: ArrangePolygons ;
2019-08-06 18:16:02 +02:00
2019-07-15 17:30:44 +02:00
// The gap between logical beds in the x axis expressed in ratio of
// the current bed width.
static const constexpr double LOGICAL_BED_GAP = 1. / 5. ;
2019-08-06 18:16:02 +02:00
2019-07-16 18:33:42 +02:00
ArrangePolygons m_selected , m_unselected ;
2019-08-06 18:16:02 +02:00
2019-07-19 18:10:10 +02:00
// clear m_selected and m_unselected, reserve space for next usage
void clear_input () {
const Model & model = plater (). model ;
2019-08-06 18:16:02 +02:00
2019-07-15 17:30:44 +02:00
size_t count = 0 ; // To know how much space to reserve
for ( auto obj : model . objects ) count += obj -> instances . size ();
2019-10-07 17:16:40 +02:00
m_selected . clear ();
m_unselected . clear ();
2019-07-15 17:30:44 +02:00
m_selected . reserve ( count + 1 /* for optional wti */ );
m_unselected . reserve ( count + 1 /* for optional wti */ );
2019-07-19 18:10:10 +02:00
}
2019-08-06 18:16:02 +02:00
2019-07-19 18:10:10 +02:00
// Stride between logical beds
coord_t bed_stride () const {
2019-07-15 17:30:44 +02:00
double bedwidth = plater (). bed_shape_bb (). size (). x ();
2019-07-19 18:10:10 +02:00
return scaled (( 1. + LOGICAL_BED_GAP ) * bedwidth );
}
2019-08-06 18:16:02 +02:00
2019-07-19 18:10:10 +02:00
// Set up arrange polygon for a ModelInstance and Wipe tower
template < class T > ArrangePolygon get_arrange_poly ( T * obj ) const {
ArrangePolygon ap = obj -> get_arrange_polygon ();
2019-10-07 17:16:40 +02:00
ap . priority = 0 ;
ap . bed_idx = ap . translation . x () / bed_stride ();
ap . setter = [ obj , this ]( const ArrangePolygon & p ) {
2019-07-19 18:10:10 +02:00
if ( p . is_arranged ()) {
2019-10-07 17:16:40 +02:00
auto t = p . translation ;
t . x () += p . bed_idx * bed_stride ();
2019-07-19 18:10:10 +02:00
obj -> apply_arrange_result ( t , p . rotation );
}
};
return ap ;
}
2019-08-06 18:16:02 +02:00
2019-07-19 18:10:10 +02:00
// Prepare all objects on the bed regardless of the selection
void prepare_all () {
clear_input ();
2019-08-06 18:16:02 +02:00
2019-07-19 18:10:10 +02:00
for ( ModelObject * obj : plater (). model . objects )
for ( ModelInstance * mi : obj -> instances )
m_selected . emplace_back ( get_arrange_poly ( mi ));
2019-08-06 18:16:02 +02:00
2019-07-19 18:10:10 +02:00
auto & wti = plater (). updated_wipe_tower ();
if ( wti ) m_selected . emplace_back ( get_arrange_poly ( & wti ));
}
2019-08-06 18:16:02 +02:00
2019-07-19 18:10:10 +02:00
// Prepare the selected and unselected items separately. If nothing is
// selected, behaves as if everything would be selected.
void prepare_selected () {
clear_input ();
2019-08-06 18:16:02 +02:00
2019-07-19 18:10:10 +02:00
Model & model = plater (). model ;
coord_t stride = bed_stride ();
2019-08-06 18:16:02 +02:00
2019-07-19 18:10:10 +02:00
std :: vector < const Selection :: InstanceIdxsList *>
obj_sel ( model . objects . size (), nullptr );
2019-08-06 18:16:02 +02:00
2019-07-19 18:10:10 +02:00
for ( auto & s : plater (). get_selection (). get_content ())
2019-10-07 17:16:40 +02:00
if ( s . first < int ( obj_sel . size ()))
obj_sel [ size_t ( s . first )] = & s . second ;
2019-08-06 18:16:02 +02:00
2019-07-16 18:33:42 +02:00
// Go through the objects and check if inside the selection
for ( size_t oidx = 0 ; oidx < model . objects . size (); ++ oidx ) {
2019-07-19 18:10:10 +02:00
const Selection :: InstanceIdxsList * instlist = obj_sel [ oidx ];
2019-07-16 18:33:42 +02:00
ModelObject * mo = model . objects [ oidx ];
2019-08-06 18:16:02 +02:00
2019-07-16 18:33:42 +02:00
std :: vector < bool > inst_sel ( mo -> instances . size (), false );
2019-08-06 18:16:02 +02:00
2019-07-19 18:10:10 +02:00
if ( instlist )
2019-10-07 17:16:40 +02:00
for ( auto inst_id : * instlist )
inst_sel [ size_t ( inst_id )] = true ;
2019-08-06 18:16:02 +02:00
2019-07-16 18:33:42 +02:00
for ( size_t i = 0 ; i < inst_sel . size (); ++ i ) {
2019-07-19 18:10:10 +02:00
ArrangePolygon && ap = get_arrange_poly ( mo -> instances [ i ]);
2019-08-06 18:16:02 +02:00
2019-07-16 18:33:42 +02:00
inst_sel [ i ] ?
m_selected . emplace_back ( std :: move ( ap )) :
m_unselected . emplace_back ( std :: move ( ap ));
}
}
2019-08-06 18:16:02 +02:00
2019-07-17 13:32:31 +02:00
auto & wti = plater (). updated_wipe_tower ();
2019-07-16 18:33:42 +02:00
if ( wti ) {
2019-07-19 18:10:10 +02:00
ArrangePolygon && ap = get_arrange_poly ( & wti );
2019-08-06 18:16:02 +02:00
2019-07-19 18:10:10 +02:00
plater (). get_selection (). is_wipe_tower () ?
2019-07-16 18:33:42 +02:00
m_selected . emplace_back ( std :: move ( ap )) :
m_unselected . emplace_back ( std :: move ( ap ));
}
2019-08-06 18:16:02 +02:00
2019-07-16 18:33:42 +02:00
// If the selection was empty arrange everything
if ( m_selected . empty ()) m_selected . swap ( m_unselected );
2019-08-06 18:16:02 +02:00
2019-07-17 13:32:31 +02:00
// The strides have to be removed from the fixed items. For the
2019-07-18 17:31:11 +02:00
// arrangeable (selected) items bed_idx is ignored and the
2019-07-17 13:32:31 +02:00
// translation is irrelevant.
for ( auto & p : m_unselected ) p . translation ( X ) -= p . bed_idx * stride ;
2019-07-15 17:30:44 +02:00
}
2019-08-06 18:16:02 +02:00
2019-07-19 18:10:10 +02:00
protected :
2019-08-06 18:16:02 +02:00
2019-07-19 18:10:10 +02:00
void prepare () override
{
wxGetKeyState ( WXK_SHIFT ) ? prepare_selected () : prepare_all ();
}
2019-08-06 18:16:02 +02:00
2019-07-15 17:30:44 +02:00
public :
using Job :: Job ;
2019-08-06 18:16:02 +02:00
2019-07-16 18:33:42 +02:00
int status_range () const override { return int ( m_selected . size ()); }
2019-08-06 18:16:02 +02:00
2019-07-15 17:30:44 +02:00
void process () override ;
2019-08-06 18:16:02 +02:00
2019-07-15 17:30:44 +02:00
void finalize () override {
2019-07-19 18:16:36 +02:00
// Ignore the arrange result if aborted.
if ( was_canceled ()) return ;
2019-08-06 18:16:02 +02:00
2019-07-16 18:33:42 +02:00
// Apply the arrange result to all selected objects
for ( ArrangePolygon & ap : m_selected ) ap . apply ();
2019-07-15 18:18:34 +02:00
2019-08-23 15:53:45 +02:00
plater (). update ();
2019-07-15 17:30:44 +02:00
}
};
2019-08-06 18:16:02 +02:00
2019-07-15 17:30:44 +02:00
class RotoptimizeJob : public Job
{
public :
using Job :: Job ;
void process () override ;
};
2019-08-06 18:16:02 +02:00
2019-06-18 16:24:30 +02: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.
class ExclusiveJobGroup {
2019-08-06 18:16:02 +02:00
2019-06-18 16:24:30 +02:00
static const int ABORT_WAIT_MAX_MS = 10000 ;
2019-08-06 18:16:02 +02:00
2019-06-18 16:24:30 +02:00
priv * m_plater ;
2019-07-15 17:30:44 +02:00
ArrangeJob arrange_job { m_plater };
RotoptimizeJob rotoptimize_job { m_plater };
2019-06-18 16:24:30 +02:00
2019-06-19 10:18:51 +02:00
// To create a new job, just define a new subclass of Job, implement
// the process and the optional prepare() and finalize() methods
// Register the instance of the class in the m_jobs container
2019-08-06 18:16:02 +02:00
// if it cannot run concurrently with other jobs in this group
2019-06-19 10:18:51 +02:00
2019-07-01 18:22:07 +02:00
std :: vector < std :: reference_wrapper < Job >> m_jobs { arrange_job ,
rotoptimize_job };
2019-06-18 16:24:30 +02:00
2019-05-13 18:58:56 +02:00
public :
2019-07-01 18:22:07 +02:00
ExclusiveJobGroup ( priv * _plater ) : m_plater ( _plater ) {}
2019-06-19 10:18:51 +02:00
2019-06-18 16:24:30 +02:00
void start ( Jobs jid ) {
m_plater -> background_process . stop ();
stop_all ();
m_jobs [ size_t ( jid )]. get (). start ();
}
2019-08-06 18:16:02 +02:00
2019-06-18 16:24:30 +02:00
void cancel_all () { for ( Job & j : m_jobs ) j . cancel (); }
void join_all ( int wait_ms = 0 )
{
std :: vector < bool > aborted ( m_jobs . size (), false );
2019-08-06 18:16:02 +02:00
2019-06-18 16:24:30 +02:00
for ( size_t jid = 0 ; jid < m_jobs . size (); ++ jid )
aborted [ jid ] = m_jobs [ jid ]. get (). join ( wait_ms );
if ( ! all_of ( aborted ))
BOOST_LOG_TRIVIAL ( error ) << "Could not abort a job!" ;
}
2019-08-06 18:16:02 +02:00
2019-06-18 16:24:30 +02:00
void stop_all () { cancel_all (); join_all ( ABORT_WAIT_MAX_MS ); }
2019-08-06 18:16:02 +02:00
2019-06-18 16:24:30 +02:00
const Job & get ( Jobs jobid ) const { return m_jobs [ size_t ( jobid )]; }
bool is_any_running () const
{
return std :: any_of ( m_jobs . begin (),
m_jobs . end (),
[]( const Job & j ) { return j . is_running (); });
}
2019-08-06 18:16:02 +02:00
2019-06-18 16:24:30 +02:00
} m_ui_jobs { this };
2018-11-23 12:47:32 +01:00
bool delayed_scene_refresh ;
2019-02-19 14:57:59 +01:00
std :: string delayed_error_message ;
2018-11-08 16:01:21 +01:00
2018-10-17 11:12:38 +02:00
wxTimer background_process_timer ;
2018-09-17 12:15:11 +02:00
2019-03-01 11:00:34 +01:00
std :: string label_btn_export ;
2019-03-12 09:41:57 +01:00
std :: string label_btn_send ;
2019-03-01 11:00:34 +01:00
2018-09-17 12:15:11 +02:00
static const std :: regex pattern_bundle ;
static const std :: regex pattern_3mf ;
static const std :: regex pattern_zip_amf ;
2019-01-03 12:04:14 +01:00
static const std :: regex pattern_any_amf ;
2019-01-18 16:01:43 +01:00
static const std :: regex pattern_prusa ;
2018-09-17 12:15:11 +02:00
priv ( Plater * q , MainFrame * main_frame );
2019-06-27 15:23:03 +02:00
~ priv ();
2018-09-17 12:15:11 +02:00
2019-08-23 15:53:45 +02: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 12:59:58 +02:00
void select_view ( const std :: string & direction );
2018-12-04 13:55:25 +01:00
void select_view_3D ( const std :: string & name );
2018-12-21 10:43:22 +01:00
void select_next_view_3D ();
2019-08-20 09:01:09 +02:00
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 ; }
2019-12-10 10:56:21 +01:00
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
bool init_view_toolbar ();
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
2019-03-27 14:16:38 +01:00
void reset_all_gizmos ();
2018-09-17 12:15:11 +02:00
void update_ui_from_settings ();
ProgressStatusBar * statusbar ();
2018-10-01 16:48:08 +02:00
std :: string get_config ( const std :: string & key ) const ;
2018-10-03 16:18:23 +02:00
BoundingBoxf bed_shape_bb () const ;
BoundingBox scaled_bed_shape_bb () const ;
2019-07-01 18:22:07 +02:00
arrangement :: BedShapeHint get_bed_shape_hint () const ;
2019-08-06 18:16:02 +02:00
2019-07-15 18:18:34 +02:00
void find_new_position ( const ModelInstancePtrs & instances , coord_t min_d );
2018-11-16 09:26:41 +01:00
std :: vector < size_t > load_files ( const std :: vector < fs :: path >& input_files , bool load_model , bool load_config );
2018-10-01 16:48:08 +02:00
std :: vector < size_t > load_model_objects ( const ModelObjectPtrs & model_objects );
2019-04-04 11:31:26 +02:00
wxString get_export_file ( GUI :: FileType file_type );
2018-10-04 11:12:55 +02:00
2019-03-19 13:30:21 +01:00
const Selection & get_selection () const ;
Selection & get_selection ();
2018-10-17 09:30:07 +02:00
int get_selected_object_idx () const ;
2018-12-06 14:49:14 +01:00
int get_selected_volume_idx () const ;
2018-10-04 11:12:55 +02:00
void selection_changed ();
2018-10-08 19:14:55 +02:00
void object_list_changed ();
2018-10-04 11:12:55 +02:00
2018-11-21 15:28:35 +01:00
void select_all ();
2019-05-14 11:57:39 +02:00
void deselect_all ();
2018-10-08 19:14:55 +02:00
void remove ( size_t obj_idx );
2018-11-13 14:17:35 +01:00
void delete_object_from_model ( size_t obj_idx );
2018-10-04 11:12:55 +02:00
void reset ();
2018-10-18 15:09:41 +02:00
void mirror ( Axis axis );
2018-10-09 09:35:19 +02:00
void arrange ();
2018-11-22 16:04:21 +01:00
void sla_optimize_rotation ();
2018-10-09 09:35:19 +02:00
void split_object ();
2018-10-24 12:55:38 +02:00
void split_volume ();
2019-05-22 14:42:38 +02:00
void scale_selection_to_fit_print_volume ();
2019-07-02 16:42:23 +02:00
2019-07-26 13:44:33 +02:00
// 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 18:16:02 +02: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 20:45:00 +02:00
int get_active_snapshot_index ();
2019-07-26 13:44:33 +02:00
2019-07-04 10:45:41 +02:00
void undo ();
void redo ();
2019-07-18 17:41:47 +02:00
void undo_redo_to ( size_t time_to_load );
2019-07-11 18:42:02 +02:00
void suppress_snapshots () { this -> m_prevent_snapshots ++ ; }
void allow_snapshots () { this -> m_prevent_snapshots -- ; }
2019-07-02 16:42:23 +02:00
2019-05-22 14:42:38 +02:00
bool background_processing_enabled () const { return this -> get_config ( "background_processing" ) == "1" ; }
2018-12-03 13:14:28 +01:00
void update_print_volume_state ();
2018-10-09 09:35:19 +02:00
void schedule_background_process ();
2018-11-13 17:45:44 +01:00
// 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,
2018-11-23 12:47:32 +01:00
// that a scene needs to be refreshed (you should call _3DScene::reload_scene(canvas3Dwidget, false))
2019-08-06 18:16:02 +02:00
UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE = 2 ,
2018-11-13 17:45:44 +01:00
// 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 ,
2018-12-22 10:02:42 +01:00
// 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 ,
2018-11-13 17:45:44 +01:00
};
// returns bit mask of UpdateBackgroundProcessReturnState
2019-08-23 15:53:45 +02:00
unsigned int update_background_process ( bool force_validation = false , bool postpone_error_messages = false );
2018-12-22 10:02:42 +01:00
// Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
bool restart_background_process ( unsigned int state );
2019-08-06 18:16:02 +02:00
// returns bit mask of UpdateBackgroundProcessReturnState
unsigned int update_restart_background_process ( bool force_scene_update , bool force_preview_update );
2019-08-23 15:53:45 +02:00
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 );
}
}
2018-12-11 10:33:11 +01:00
void export_gcode ( fs :: path output_path , PrintHostJob upload_job );
2018-10-09 09:35:19 +02:00
void reload_from_disk ();
2019-01-30 16:27:07 +01:00
void fix_through_netfabb ( const int obj_idx , const int vol_idx = - 1 );
2018-09-17 12:15:11 +02:00
2018-12-04 13:55:25 +01:00
void set_current_panel ( wxPanel * panel );
2019-01-03 12:59:06 +01:00
2018-10-08 19:55:30 +02:00
void on_select_preset ( wxCommandEvent & );
2018-11-20 13:22:26 +01:00
void on_slicing_update ( SlicingStatusEvent & );
void on_slicing_completed ( wxCommandEvent & );
2018-10-08 19:55:30 +02:00
void on_process_completed ( wxCommandEvent & );
2018-09-17 12:15:11 +02:00
void on_layer_editing_toggled ( bool enable );
2018-10-08 19:55:30 +02:00
2018-10-03 11:34:39 +02:00
void on_action_add ( SimpleEvent & );
2018-10-24 12:55:38 +02:00
void on_action_split_objects ( SimpleEvent & );
void on_action_split_volumes ( SimpleEvent & );
2018-10-04 11:12:55 +02:00
void on_action_layersediting ( SimpleEvent & );
2018-10-03 14:25:35 +02:00
2018-10-16 16:04:19 +02:00
void on_object_select ( SimpleEvent & );
2019-09-20 09:57:27 +02:00
void on_right_click ( RBtnEvent & );
2018-10-08 19:55:30 +02:00
void on_wipetower_moved ( Vec3dEvent & );
2019-04-26 15:34:26 +02:00
void on_wipetower_rotated ( Vec3dEvent & );
2018-10-08 19:55:30 +02:00
void on_update_geometry ( Vec3dsEvent < 2 >& );
2018-11-23 12:47:32 +01:00
void on_3dcanvas_mouse_dragging_finished ( SimpleEvent & );
2018-10-17 12:17:25 +02:00
2018-12-07 17:50:48 +01:00
void update_object_menu ();
2019-03-01 11:00:34 +01:00
void show_action_buttons ( const bool is_ready_to_slice ) const ;
2018-12-07 17:50:48 +01:00
2019-02-19 15:15:27 +01:00
// 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 14:02:36 +02:00
void set_bed_shape ( const Pointfs & shape , const std :: string & custom_texture , const std :: string & custom_model );
2019-02-19 15:15:27 +01:00
2019-03-14 13:54:05 +01:00
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 ;
2019-04-24 16:04:47 +02:00
bool can_fix_through_netfabb () const ;
2019-05-13 14:27:51 +02:00
bool can_set_instance_to_object () const ;
bool can_mirror () const ;
2019-09-19 09:09:11 +02:00
bool can_reload_from_disk () const ;
2019-03-14 13:54:05 +01:00
2019-10-23 13:31:24 +02:00
#if ENABLE_THUMBNAIL_GENERATOR
2019-11-28 14:18:24 +01:00
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 );
2019-10-23 13:31:24 +02:00
#endif // ENABLE_THUMBNAIL_GENERATOR
2019-05-03 09:44:19 +02:00
void msw_rescale_object_menu ();
2019-05-15 14:09:16 +02:00
// 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 ;
2019-05-14 15:00:24 +02:00
void set_project_filename ( const wxString & filename );
2018-10-17 12:17:25 +02:00
private :
bool init_object_menu ();
2018-12-07 17:50:48 +01:00
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 ();
2019-12-10 10:56:21 +01:00
#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
2018-12-06 10:38:19 +01:00
void init_view_toolbar ();
2019-12-10 10:56:21 +01:00
#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
2018-10-18 09:27:37 +02:00
2018-12-17 15:44:30 +01:00
bool can_split () const ;
2018-10-18 09:27:37 +02:00
bool layers_height_allowed () const ;
2018-11-23 12:47:32 +01:00
2018-12-06 15:40:41 +01:00
void update_fff_scene ();
2018-11-23 12:47:32 +01:00
void update_sla_scene ();
2019-08-06 18:16:02 +02:00
void undo_redo_to ( std :: vector < UndoRedo :: Snapshot >:: const_iterator it_snapshot );
2019-08-22 16:44:49 +02:00
void update_after_undo_redo ( const UndoRedo :: Snapshot & snapshot , bool temp_snapshot_was_taken = false );
2019-05-14 15:00:24 +02:00
2019-05-15 14:09:16 +02:00
// path to project file stored with no extension
2019-07-26 13:44:33 +02:00
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 18:16:02 +02:00
int m_prevent_snapshots = 0 ; /* Used for avoid of excess "snapshoting".
2019-07-26 13:44:33 +02:00
* Like for "delete selected" or "set numbers of copies"
2019-08-06 18:16:02 +02:00
* we should call tack_snapshot just ones
2019-07-26 13:44:33 +02:00
* instead of calls for each action separately
* */
2019-07-18 17:41:47 +02:00
std :: string m_last_fff_printer_profile_name ;
std :: string m_last_sla_printer_profile_name ;
2018-09-17 12:15:11 +02:00
};
2018-10-22 11:52:13 +02: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 12:04:14 +01:00
const std :: regex Plater :: priv :: pattern_any_amf ( ".*[.](amf|amf[.]xml|zip[.]amf)" , std :: regex :: icase );
2019-01-18 16:01:43 +01:00
const std :: regex Plater :: priv :: pattern_prusa ( ".*prusa" , std :: regex :: icase );
2019-01-03 12:04:14 +01:00
2018-10-18 15:13:38 +02:00
Plater :: priv :: priv ( Plater * q , MainFrame * main_frame )
: q ( q )
, main_frame ( main_frame )
, config ( Slic3r :: DynamicPrintConfig :: new_from_defaults_keys ({
2019-07-18 14:39:19 +02:00
"bed_shape" , "bed_custom_texture" , "bed_custom_model" , "complete_objects" , "duplicate_distance" , "extruder_clearance_radius" , "skirts" , "skirt_distance" ,
2018-09-17 12:15:11 +02: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 10:06:51 +01:00
"extruder_colour" , "filament_colour" , "max_print_height" , "printer_model" , "printer_technology" ,
2019-01-21 17:02:16 +01:00
// 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" , "bottom_solid_layers" , "solid_infill_extruder" ,
"support_material" , "support_material_extruder" , "support_material_interface_extruder" , "support_material_contact_distance" , "raft_layers"
2018-10-18 15:13:38 +02:00
}))
, sidebar ( new Sidebar ( q ))
2018-11-23 12:47:32 +01:00
, delayed_scene_refresh ( false )
2019-02-26 12:56:13 +01:00
, view_toolbar ( GLToolbar :: Radio , "View" )
2019-05-14 15:00:24 +02:00
, m_project_filename ( wxEmptyString )
2018-09-17 12:15:11 +02:00
{
2019-08-06 18:16:02 +02:00
this -> q -> SetFont ( Slic3r :: GUI :: wxGetApp (). normal_font ());
2019-05-06 18:28:23 +02:00
2018-12-03 13:14:28 +01:00
background_process . set_fff_print ( & fff_print );
2019-08-06 18:16:02 +02:00
background_process . set_sla_print ( & sla_print );
2018-09-17 12:15:11 +02:00
background_process . set_gcode_preview_data ( & gcode_preview_data );
2019-10-23 13:31:24 +02:00
#if ENABLE_THUMBNAIL_GENERATOR
2019-11-28 14:18:24 +01:00
background_process . set_thumbnail_cb ([ this ]( ThumbnailsList & thumbnails , const Vec2ds & sizes , bool printable_only , bool parts_only , bool show_bed , bool transparent_background )
2019-11-22 12:39:03 +01:00
{
2019-11-28 14:18:24 +01:00
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 );
2019-11-22 12:39:03 +01:00
});
std :: future < void > result = task . get_future ();
2019-11-28 14:18:24 +01:00
wxTheApp -> CallAfter ([ & ]() { task ( thumbnails , sizes , printable_only , parts_only , show_bed , transparent_background ); });
2019-11-22 12:39:03 +01:00
result . wait ();
});
2019-10-23 13:31:24 +02:00
#endif // ENABLE_THUMBNAIL_GENERATOR
2018-11-20 13:22:26 +01:00
background_process . set_slicing_completed_event ( EVT_SLICING_COMPLETED );
2018-09-17 12:15:11 +02:00
background_process . set_finished_event ( EVT_PROCESS_COMPLETED );
2019-08-06 18:16:02 +02:00
// Default printer technology for default config.
2018-11-13 17:45:44 +01:00
background_process . select_technology ( this -> printer_technology );
2018-10-23 15:27:31 +02:00
// Register progress callback from the Print class to the Platter.
2018-11-13 11:53:54 +01:00
2018-11-20 16:27:30 +01:00
auto statuscb = [ this ]( const Slic3r :: PrintBase :: SlicingStatus & status ) {
2018-11-20 13:22:26 +01:00
wxQueueEvent ( this -> q , new Slic3r :: SlicingStatusEvent ( EVT_SLICING_UPDATE , 0 , status ));
2018-11-13 11:53:54 +01:00
};
2018-12-03 13:14:28 +01:00
fff_print . set_status_callback ( statuscb );
2018-11-13 11:53:54 +01:00
sla_print . set_status_callback ( statuscb );
2018-11-20 13:22:26 +01:00
this -> q -> Bind ( EVT_SLICING_UPDATE , & priv :: on_slicing_update , this );
2018-09-17 12:15:11 +02:00
2019-03-07 11:49:00 +01:00
view3D = new View3D ( q , bed , camera , view_toolbar , & model , config , & background_process );
2019-04-02 13:47:49 +02:00
preview = new Preview ( q , bed , camera , view_toolbar , & model , config , & background_process , & gcode_preview_data , [ this ](){ schedule_background_process (); });
2019-02-19 15:15:27 +01:00
2018-12-04 13:55:25 +01:00
panels . push_back ( view3D );
panels . push_back ( preview );
2018-09-17 12:15:11 +02:00
2018-10-23 15:27:31 +02:00
this -> background_process_timer . SetOwner ( this -> q , 0 );
2019-06-26 09:48:52 +02: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 12:15:11 +02:00
update ();
auto * hsizer = new wxBoxSizer ( wxHORIZONTAL );
2018-12-04 13:55:25 +01:00
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 12:15:11 +02:00
hsizer -> Add ( sidebar , 0 , wxEXPAND | wxLEFT | wxRIGHT , 0 );
q -> SetSizer ( hsizer );
2018-10-17 12:17:25 +02:00
init_object_menu ();
2018-09-17 12:15:11 +02:00
// Events:
// Preset change event
sidebar -> Bind ( wxEVT_COMBOBOX , & priv :: on_select_preset , this );
2018-11-01 12:33:56 +01:00
sidebar -> Bind ( EVT_OBJ_LIST_OBJECT_SELECT , [ this ]( wxEvent & ) { priv :: selection_changed (); });
2018-12-04 11:14:39 +01:00
sidebar -> Bind ( EVT_SCHEDULE_BACKGROUND_PROCESS , [ this ]( SimpleEvent & ) { this -> schedule_background_process (); });
2018-11-01 12:33:56 +01:00
2018-12-04 13:55:25 +01: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 & ) { arrange (); });
2019-02-03 14:06:13 +01:00
view3D_canvas -> Bind ( EVT_GLCANVAS_SELECT_ALL , [ this ]( SimpleEvent & ) { this -> q -> select_all (); });
2018-12-19 14:01:13 +01:00
view3D_canvas -> Bind ( EVT_GLCANVAS_QUESTION_MARK , [ this ]( SimpleEvent & ) { wxGetApp (). keyboard_shortcuts (); });
2019-08-06 18:16:02 +02:00
view3D_canvas -> Bind ( EVT_GLCANVAS_INCREASE_INSTANCES , [ this ]( Event < int > & evt )
2018-12-19 12:07:45 +01:00
{ if ( evt . data == 1 ) this -> q -> increase_instances (); else if ( this -> can_decrease_instances ()) this -> q -> decrease_instances (); });
2018-12-04 13:55:25 +01:00
view3D_canvas -> Bind ( EVT_GLCANVAS_INSTANCE_MOVED , [ this ]( SimpleEvent & ) { update (); });
view3D_canvas -> Bind ( EVT_GLCANVAS_WIPETOWER_MOVED , & priv :: on_wipetower_moved , this );
2019-04-26 15:34:26 +02:00
view3D_canvas -> Bind ( EVT_GLCANVAS_WIPETOWER_ROTATED , & priv :: on_wipetower_rotated , this );
2019-01-03 11:24:03 +01:00
view3D_canvas -> Bind ( EVT_GLCANVAS_INSTANCE_ROTATED , [ this ]( SimpleEvent & ) { update (); });
view3D_canvas -> Bind ( EVT_GLCANVAS_INSTANCE_SCALED , [ this ]( SimpleEvent & ) { update (); });
2018-12-04 13:55:25 +01:00
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 11:54:18 +01:00
view3D_canvas -> Bind ( EVT_GLCANVAS_TAB , [ this ]( SimpleEvent & ) { select_next_view_3D (); });
2019-03-27 14:16:38 +01:00
view3D_canvas -> Bind ( EVT_GLCANVAS_RESETGIZMOS , [ this ]( SimpleEvent & ) { reset_all_gizmos (); });
2019-07-04 10:45:41 +02:00
view3D_canvas -> Bind ( EVT_GLCANVAS_UNDO , [ this ]( SimpleEvent & ) { this -> undo (); });
view3D_canvas -> Bind ( EVT_GLCANVAS_REDO , [ this ]( SimpleEvent & ) { this -> redo (); });
2019-11-07 15:55:45 +01:00
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
2019-11-29 11:01:51 +01:00
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 ); });
2019-11-07 15:55:45 +01:00
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
2019-04-26 15:34:26 +02:00
2018-12-04 13:55:25 +01:00
// 3DScene/Toolbar:
view3D_canvas -> Bind ( EVT_GLTOOLBAR_ADD , & priv :: on_action_add , this );
view3D_canvas -> Bind ( EVT_GLTOOLBAR_DELETE , [ q ]( SimpleEvent & ) { q -> remove_selected (); });
2019-05-02 13:52:13 +02:00
view3D_canvas -> Bind ( EVT_GLTOOLBAR_DELETE_ALL , [ q ]( SimpleEvent & ) { q -> reset_with_confirm (); });
2018-12-04 13:55:25 +01:00
view3D_canvas -> Bind ( EVT_GLTOOLBAR_ARRANGE , [ this ]( SimpleEvent & ) { arrange (); });
2019-04-11 13:20:34 +02:00
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 (); });
2018-12-04 13:55:25 +01:00
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 );
2019-12-10 10:56:21 +01:00
#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
2018-12-06 10:38:19 +01:00
view3D_canvas -> Bind ( EVT_GLCANVAS_INIT , [ this ]( SimpleEvent & ) { init_view_toolbar (); });
2019-12-10 10:56:21 +01:00
#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
2019-07-18 14:39:19 +02:00
view3D_canvas -> Bind ( EVT_GLCANVAS_UPDATE_BED_SHAPE , [ this ]( SimpleEvent & )
{
set_bed_shape ( config -> option < ConfigOptionPoints > ( "bed_shape" ) -> values ,
2019-07-24 14:02:36 +02:00
config -> option < ConfigOptionString > ( "bed_custom_texture" ) -> value ,
config -> option < ConfigOptionString > ( "bed_custom_model" ) -> value );
2019-07-18 14:39:19 +02:00
});
2018-10-04 11:12:55 +02:00
2018-10-08 19:55:30 +02:00
// Preview events:
2018-12-22 10:02:42 +01:00
preview -> get_wxglcanvas () -> Bind ( EVT_GLCANVAS_QUESTION_MARK , [ this ]( SimpleEvent & ) { wxGetApp (). keyboard_shortcuts (); });
2019-07-18 14:39:19 +02:00
preview -> get_wxglcanvas () -> Bind ( EVT_GLCANVAS_UPDATE_BED_SHAPE , [ this ]( SimpleEvent & )
{
set_bed_shape ( config -> option < ConfigOptionPoints > ( "bed_shape" ) -> values ,
2019-07-24 14:02:36 +02:00
config -> option < ConfigOptionString > ( "bed_custom_texture" ) -> value ,
config -> option < ConfigOptionString > ( "bed_custom_model" ) -> value );
2019-07-18 14:39:19 +02:00
});
2019-02-21 11:54:18 +01:00
preview -> get_wxglcanvas () -> Bind ( EVT_GLCANVAS_TAB , [ this ]( SimpleEvent & ) { select_next_view_3D (); });
2019-06-13 16:10:33 +02:00
preview -> get_wxglcanvas () -> Bind ( EVT_GLCANVAS_MOVE_DOUBLE_SLIDER , [ this ]( wxKeyEvent & evt ) { preview -> move_double_slider ( evt ); });
2019-06-18 11:40:26 +02:00
preview -> get_wxglcanvas () -> Bind ( EVT_GLCANVAS_EDIT_COLOR_CHANGE , [ this ]( wxKeyEvent & evt ) { preview -> edit_double_slider ( evt ); });
2019-01-03 12:59:06 +01:00
2018-11-20 13:22:26 +01:00
q -> Bind ( EVT_SLICING_COMPLETED , & priv :: on_slicing_completed , this );
2018-09-17 12:15:11 +02:00
q -> Bind ( EVT_PROCESS_COMPLETED , & priv :: on_process_completed , this );
2018-12-06 10:38:19 +01:00
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 12:15:11 +02:00
// Drop target:
q -> SetDropTarget ( new PlaterDropTarget ( q )); // if my understanding is right, wxWindow takes the owenership
update_ui_from_settings ();
q -> Layout ();
2018-12-20 12:51:42 +01:00
set_current_panel ( view3D );
2019-06-20 10:02:52 +02:00
// updates camera type from .ini file
2019-06-24 15:55:14 +02:00
camera . set_type ( get_config ( "use_perspective_camera" ));
2019-07-02 16:42:23 +02:00
2019-09-27 14:52:19 +02:00
mouse3d_controller . init ();
2019-07-05 19:06:19 +02:00
// Initialize the Undo / Redo stack with a first snapshot.
2019-08-06 18:16:02 +02:00
this -> take_snapshot ( _ ( L ( "New Project" )));
2018-09-17 12:15:11 +02:00
}
2019-06-27 15:23:03 +02:00
Plater :: priv ::~ priv ()
{
2019-09-27 14:52:19 +02:00
mouse3d_controller . shutdown ();
2019-06-27 15:23:03 +02:00
if ( config != nullptr )
delete config ;
}
2019-08-23 15:53:45 +02:00
void Plater :: priv :: update ( unsigned int flags )
2018-09-17 12:15:11 +02:00
{
2019-06-19 11:05:34 +02:00
// the following line, when enabled, causes flickering on NVIDIA graphics cards
// wxWindowUpdateLocker freeze_guard(q);
2018-11-21 13:32:24 +01:00
if ( get_config ( "autocenter" ) == "1" ) {
2018-10-01 16:48:08 +02: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 16:18:23 +02:00
const Vec2d & bed_center = bed_shape_bb (). center ();
2018-10-01 16:48:08 +02:00
model . center_instances_around_point ( bed_center );
}
2018-12-22 10:02:42 +01:00
unsigned int update_status = 0 ;
2019-08-23 15:53:45 +02:00
if ( this -> printer_technology == ptSLA || ( flags & ( unsigned int ) UpdateParams :: FORCE_BACKGROUND_PROCESSING_UPDATE ))
2018-11-13 17:45:44 +01:00
// Update the SLAPrint from the current Model, so that the reload_scene()
// pulls the correct data.
2019-08-23 15:53:45 +02:00
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 18:16:02 +02:00
this -> preview -> reload_print ();
2018-12-22 10:02:42 +01:00
if ( this -> printer_technology == ptSLA )
this -> restart_background_process ( update_status );
else
this -> schedule_background_process ();
2018-09-17 12:15:11 +02:00
}
2018-12-04 13:55:25 +01: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 );
}
2018-12-21 10:43:22 +01:00
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 12:59:58 +02:00
2019-03-27 14:16:38 +01:00
void Plater :: priv :: reset_all_gizmos ()
{
view3D -> get_canvas3d () -> reset_all_gizmos ();
}
2018-12-04 11:14:39 +01:00
// Called after the Preferences dialog is closed and the program settings are saved.
// Update the UI based on the current preferences.
2018-09-17 12:15:11 +02:00
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;
// }
2019-01-24 13:16:46 +01:00
view3D -> get_canvas3d () -> update_ui_from_settings ();
preview -> get_canvas3d () -> update_ui_from_settings ();
2018-09-17 12:15:11 +02:00
}
2018-10-23 15:27:31 +02:00
ProgressStatusBar * Plater :: priv :: statusbar ()
2018-09-17 12:15:11 +02:00
{
2019-05-13 18:58:56 +02:00
return main_frame -> m_statusbar . get ();
2018-09-17 12:15:11 +02:00
}
2018-10-01 16:48:08 +02:00
std :: string Plater :: priv :: get_config ( const std :: string & key ) const
{
return wxGetApp (). app_config -> get ( key );
}
2018-10-03 16:18:23 +02: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 16:48:08 +02: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 ();
}
2018-11-16 09:26:41 +01:00
std :: vector < size_t > Plater :: priv :: load_files ( const std :: vector < fs :: path >& input_files , bool load_model , bool load_config )
2018-09-17 12:15:11 +02:00
{
2018-10-04 11:12:55 +02:00
if ( input_files . empty ()) { return std :: vector < size_t > (); }
2018-09-17 12:15:11 +02:00
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 ;
}
}
}
2019-01-21 12:34:28 +01:00
const auto loading = _ ( L ( "Loading" )) + dots ;
2018-09-17 12:15:11 +02:00
wxProgressDialog dlg ( loading , loading );
dlg . Pulse ();
2018-11-16 09:26:41 +01:00
auto * new_model = ( ! load_model || one_by_one ) ? nullptr : new Slic3r :: Model ();
2018-10-01 16:48:08 +02:00
std :: vector < size_t > obj_idxs ;
2018-09-17 12:15:11 +02:00
for ( size_t i = 0 ; i < input_files . size (); i ++ ) {
const auto & path = input_files [ i ];
const auto filename = path . filename ();
2019-12-04 11:12:28 +01:00
const auto dlg_info = wxString :: Format ( _ ( L ( "Processing input file %s" )), from_path ( filename )) + " \n " ;
2018-09-17 12:15:11 +02: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 12:04:14 +01:00
const bool type_any_amf = ! type_3mf && std :: regex_match ( path . string (), pattern_any_amf );
2019-01-18 16:01:43 +01:00
const bool type_prusa = std :: regex_match ( path . string (), pattern_prusa );
2018-09-17 12:15:11 +02:00
Slic3r :: Model model ;
2019-05-22 15:30:35 +02:00
bool is_project_file = type_prusa ;
2018-09-17 12:15:11 +02:00
try {
if ( type_3mf || type_zip_amf ) {
DynamicPrintConfig config ;
2018-11-06 19:09:54 +01:00
{
DynamicPrintConfig config_loaded ;
2019-08-23 13:12:31 +02:00
model = Slic3r :: Model :: read_from_archive ( path . string (), & config_loaded , false , load_config );
2018-11-16 09:26:41 +01:00
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 18:16:02 +02:00
PrinterTechnology printer_technology = Preset :: printer_technology ( config_loaded );
2019-06-17 17:04:19 +02:00
// 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 ,
2019-08-27 11:39:51 +02:00
_ ( L ( "You can't load SLA project if there is at least one multi-part object on the bed" )) + " \n\n " +
2019-06-17 17:04:19 +02:00
_ ( L ( "Please check your object list before preset changing." )),
_ ( L ( "Attention!" )));
return obj_idxs ;
}
}
2019-08-06 18:16:02 +02:00
config . apply ( printer_technology == ptFFF ?
static_cast < const ConfigBase &> ( FullPrintConfig :: defaults ()) :
2018-11-06 19:09:54 +01:00
static_cast < const ConfigBase &> ( SLAFullPrintConfig :: defaults ()));
// and place the loaded config over the base.
config += std :: move ( config_loaded );
}
2019-11-11 16:01:34 +01:00
this -> model . custom_gcode_per_height = model . custom_gcode_per_height ;
2018-11-06 19:09:54 +01:00
}
2018-12-03 15:06:02 +01:00
2018-11-16 09:26:41 +01: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 ();
2019-05-22 15:30:35 +02:00
is_project_file = true ;
2018-11-16 09:26:41 +01:00
}
wxGetApp (). app_config -> update_config_dir ( path . parent_path (). string ());
}
}
else {
2019-08-23 13:12:31 +02:00
model = Slic3r :: Model :: read_from_file ( path . string (), nullptr , false , load_config );
2018-10-19 11:02:50 +02:00
for ( auto obj : model . objects )
if ( obj -> name . empty ())
obj -> name = fs :: path ( obj -> input_file ). filename (). string ();
2018-09-17 12:15:11 +02:00
}
2018-12-03 13:14:28 +01:00
} catch ( const std :: exception & e ) {
2018-09-17 12:15:11 +02:00
GUI :: show_error ( q , e . what ());
continue ;
}
2018-11-16 09:26:41 +01:00
if ( load_model )
{
// The model should now be initialized
2018-09-17 12:15:11 +02:00
2019-05-22 15:30:35 +02:00
if ( ! is_project_file ) {
2019-01-18 12:52:09 +01:00
if ( model . looks_like_multipart_object ()) {
2019-09-03 10:27:16 +02:00
wxMessageDialog msg_dlg ( q , _ ( L (
2019-12-04 11:12:28 +01:00
"This file contains several objects positioned at multiple heights. \n "
2019-01-21 12:34:28 +01:00
"Instead of considering them as multiple objects, should I consider \n "
2019-12-04 11:12:28 +01:00
"this file as a single object having multiple parts?" )) + " \n " ,
_ ( L ( "Multi-part object detected" )), wxICON_WARNING | wxYES | wxNO );
2019-09-03 10:27:16 +02:00
if ( msg_dlg . ShowModal () == wxID_YES ) {
2019-01-21 14:43:57 +01:00
model . convert_multipart_object ( nozzle_dmrs -> values . size ());
}
2019-01-18 12:52:09 +01:00
}
2018-09-17 12:15:11 +02:00
}
2019-07-18 17:41:47 +02:00
else if (( wxGetApp (). get_mode () == comSimple ) && ( type_3mf || type_any_amf ) && model_has_advanced_features ( model )) {
2019-12-04 11:12:28 +01:00
wxMessageDialog msg_dlg ( q , _ ( L ( "This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?" )) + " \n " ,
2019-07-18 17:41:47 +02:00
_ ( L ( "Detected advanced data" )), wxICON_WARNING | wxYES | wxNO );
2019-09-03 10:27:16 +02:00
if ( msg_dlg . ShowModal () == wxID_YES )
2019-04-04 12:02:13 +02:00
{
2019-07-18 17:41:47 +02:00
Slic3r :: GUI :: wxGetApp (). save_mode ( comAdvanced );
view3D -> set_as_dirty ();
2019-04-04 12:02:13 +02:00
}
2019-07-18 17:41:47 +02:00
else
return obj_idxs ;
2019-04-04 12:02:13 +02:00
}
2018-09-17 12:15:11 +02:00
2019-07-18 17:41:47 +02:00
for ( ModelObject * model_object : model . objects ) {
2019-09-17 12:50:54 +02:00
if ( ! type_3mf && ! type_zip_amf )
model_object -> center_around_origin ( false );
2019-07-18 17:41:47 +02:00
model_object -> ensure_on_bed ();
}
2018-09-17 12:15:11 +02:00
2018-12-10 13:39:56 +01: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 18:16:02 +02:00
Slic3r :: GUI :: show_error ( nullptr ,
wxString :: Format ( _ ( L ( "You can't to add the object(s) from %s because of one or some of them is(are) multi-part" )),
2019-01-02 15:11:05 +01:00
from_path ( filename )));
2019-04-04 12:02:13 +02:00
return obj_idxs ;
2018-12-10 13:39:56 +01:00
}
}
2018-11-16 09:26:41 +01:00
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 12:15:11 +02:00
}
}
}
2019-08-05 17:49:21 +02:00
if ( new_model != nullptr && new_model -> objects . size () > 1 ) {
2019-09-03 10:27:16 +02:00
wxMessageDialog msg_dlg ( q , _ ( L (
2018-09-17 12:15:11 +02:00
"Multiple objects were loaded for a multi-material printer. \n "
"Instead of considering them as multiple objects, should I consider \n "
2019-12-04 11:12:28 +01:00
"these files to represent a single object having multiple parts?" )) + " \n " ,
_ ( L ( "Multi-part object detected" )), wxICON_WARNING | wxYES | wxNO );
2019-09-03 10:27:16 +02:00
if ( msg_dlg . ShowModal () == wxID_YES ) {
2018-09-17 12:15:11 +02:00
new_model -> convert_multipart_object ( nozzle_dmrs -> values . size ());
}
2018-10-19 11:02:50 +02:00
auto loaded_idxs = load_model_objects ( new_model -> objects );
2018-10-01 16:48:08 +02:00
obj_idxs . insert ( obj_idxs . end (), loaded_idxs . begin (), loaded_idxs . end ());
2018-09-17 12:15:11 +02:00
}
2018-11-16 09:26:41 +01: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 15:06:02 +01:00
2019-01-03 09:12:50 +01:00
// automatic selection of added objects
if ( ! obj_idxs . empty () && ( view3D != nullptr ))
{
2019-08-06 14:54:38 +02:00
// update printable state for new volumes on canvas3D
wxGetApp (). plater () -> canvas3D () -> update_instance_printable_state_for_objects ( obj_idxs );
2019-03-19 13:30:21 +01:00
Selection & selection = view3D -> get_canvas3d () -> get_selection ();
2019-01-03 09:12:50 +01:00
selection . clear ();
for ( size_t idx : obj_idxs )
{
selection . add_object (( unsigned int ) idx , false );
}
2019-09-09 15:55:04 +02:00
if ( view3D -> get_canvas3d () -> get_gizmos_manager (). is_running ())
// 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 ();
2019-01-03 09:12:50 +01:00
}
2018-10-01 16:48:08 +02:00
return obj_idxs ;
2018-09-17 12:15:11 +02:00
}
2019-01-28 10:56:02 +01:00
// #define AUTOPLACEMENT_ON_LOAD
2018-10-04 11:12:55 +02:00
std :: vector < size_t > Plater :: priv :: load_model_objects ( const ModelObjectPtrs & model_objects )
2018-09-17 12:15:11 +02:00
{
2018-10-03 16:18:23 +02:00
const BoundingBoxf bed_shape = bed_shape_bb ();
2018-12-19 09:54:15 +01:00
const Vec3d bed_size = Slic3r :: to_3d ( bed_shape . size (). cast < double > (), 1.0 ) - 2.0 * Vec3d :: Ones ();
2018-09-17 12:15:11 +02:00
2019-01-28 10:56:02 +01:00
#ifndef AUTOPLACEMENT_ON_LOAD
bool need_arrange = false ;
#endif /* AUTOPLACEMENT_ON_LOAD */
2018-09-17 12:15:11 +02:00
bool scaled_down = false ;
2018-10-01 16:48:08 +02:00
std :: vector < size_t > obj_idxs ;
2018-10-19 11:02:50 +02:00
unsigned int obj_count = model . objects . size ();
2018-09-17 12:15:11 +02:00
2019-01-28 10:56:02 +01:00
#ifdef AUTOPLACEMENT_ON_LOAD
2019-01-22 17:50:33 +01:00
ModelInstancePtrs new_instances ;
2019-01-28 10:56:02 +01:00
#endif /* AUTOPLACEMENT_ON_LOAD */
2018-10-01 16:48:08 +02:00
for ( ModelObject * model_object : model_objects ) {
2018-09-17 12:15:11 +02:00
auto * object = model . add_object ( * model_object );
std :: string object_name = object -> name . empty () ? fs :: path ( object -> input_file ). filename (). string () : object -> name ;
2018-10-17 09:30:07 +02:00
obj_idxs . push_back ( obj_count ++ );
2018-09-17 12:15:11 +02:00
2018-10-04 11:12:55 +02:00
if ( model_object -> instances . empty ()) {
2019-01-28 10:56:02 +01:00
#ifdef AUTOPLACEMENT_ON_LOAD
2019-01-22 17:50:33 +01:00
object -> center_around_origin ();
new_instances . emplace_back ( object -> add_instance ());
2019-01-28 10:56:02 +01:00
#else /* AUTOPLACEMENT_ON_LOAD */
2019-03-25 12:07:43 +01:00
// if object has no defined position(s) we need to rearrange everything after loading
need_arrange = true ;
2019-08-06 18:16:02 +02: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 ();
2019-01-28 10:56:02 +01:00
instance -> set_offset ( Slic3r :: to_3d ( bed_shape . center (). cast < double > (), - object -> origin_translation ( 2 )));
#endif /* AUTOPLACEMENT_ON_LOAD */
2018-09-17 12:15:11 +02:00
}
2018-10-01 16:48:08 +02: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 18:16:02 +02:00
double inv = 1. / max_ratio ;
2019-07-02 16:42:23 +02:00
object -> scale_mesh_after_creation ( Vec3d ( inv , inv , inv ));
2018-12-19 09:54:15 +01:00
object -> origin_translation = Vec3d :: Zero ();
object -> center_around_origin ();
2018-10-01 16:48:08 +02:00
scaled_down = true ;
} else if ( max_ratio > 5 ) {
2019-01-25 13:30:01 +01:00
const Vec3d inverse = 1.0 / max_ratio * Vec3d :: Ones ();
for ( ModelInstance * instance : object -> instances ) {
2018-10-01 16:48:08 +02:00
instance -> set_scaling_factor ( inverse );
}
2019-01-25 13:30:01 +01:00
scaled_down = true ;
2018-10-01 16:48:08 +02:00
}
2018-10-10 14:43:07 +02:00
2018-10-30 16:03:03 +01:00
object -> ensure_on_bed ();
2018-09-17 12:15:11 +02:00
}
2019-01-28 10:56:02 +01:00
#ifdef AUTOPLACEMENT_ON_LOAD
2019-01-23 13:12:44 +01:00
// FIXME distance should be a config value /////////////////////////////////
2019-01-22 17:50:33 +01:00
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 18:16:02 +02:00
2019-06-27 21:13:44 +02:00
std :: pair < bool , GLCanvas3D :: WipeTowerInfo > wti = view3D -> get_canvas3d () -> get_wipe_tower_info ();
2019-08-06 18:16:02 +02:00
2019-04-29 14:32:02 +02: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 );
2019-01-28 10:56:02 +01:00
#endif /* AUTOPLACEMENT_ON_LOAD */
2019-01-22 17:50:33 +01:00
2018-10-01 16:48:08 +02: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?" )));
}
2019-09-23 15:35:49 +02:00
for ( const size_t idx : obj_idxs ) {
wxGetApp (). obj_list () -> add_object_to_list ( idx );
2018-10-01 16:48:08 +02:00
}
update ();
2018-10-11 13:22:36 +02:00
object_list_changed ();
2018-10-17 11:12:38 +02:00
this -> schedule_background_process ();
2018-10-01 16:48:08 +02:00
return obj_idxs ;
2018-09-17 12:15:11 +02:00
}
2019-04-04 11:31:26 +02:00
wxString Plater :: priv :: get_export_file ( GUI :: FileType file_type )
2018-10-04 11:12:55 +02:00
{
wxString wildcard ;
switch ( file_type ) {
case FT_STL :
case FT_AMF :
case FT_3MF :
2018-10-19 16:52:41 +02:00
case FT_GCODE :
2019-08-20 09:01:09 +02:00
case FT_OBJ :
2018-12-06 17:32:49 +01:00
wildcard = file_wildcards ( file_type );
2018-10-04 11:12:55 +02:00
break ;
default :
2018-12-06 17:32:49 +01:00
wildcard = file_wildcards ( FT_MODEL );
2018-10-04 11:12:55 +02:00
break ;
}
2018-12-03 13:14:28 +01:00
// Update printbility state of each of the ModelInstances.
this -> update_print_volume_state ();
2019-04-04 14:00:31 +02:00
const Selection & selection = get_selection ();
int obj_idx = selection . get_object_idx ();
fs :: path output_file ;
2019-05-14 15:00:24 +02:00
if ( file_type == FT_3MF )
// for 3mf take the path from the project filename, if any
2019-05-15 14:09:16 +02:00
output_file = into_path ( get_project_filename ( ".3mf" ));
2019-04-04 14:00:31 +02:00
if ( output_file . empty ())
2019-05-14 15:00:24 +02:00
{
// 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 ();
2019-09-19 14:01:17 +02:00
if ( output_file . empty () && ! model . objects . empty ())
// Find the file name of the first object.
output_file = this -> model . objects [ 0 ] -> get_export_filename ();
2019-05-14 15:00:24 +02:00
}
2018-10-04 11:12:55 +02:00
2019-04-04 11:31:26 +02:00
wxString dlg_title ;
2018-10-04 11:12:55 +02:00
switch ( file_type ) {
2019-04-04 11:31:26 +02:00
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 ;
}
2019-08-20 09:01:09 +02:00
case FT_OBJ :
{
output_file . replace_extension ( "obj" );
dlg_title = _ ( L ( "Export OBJ file:" ));
break ;
}
2018-10-04 11:12:55 +02:00
default : break ;
}
2019-08-15 12:52:56 +02:00
wxFileDialog dlg ( q , dlg_title ,
2019-04-04 11:31:26 +02:00
from_path ( output_file . parent_path ()), from_path ( output_file . filename ()),
wildcard , wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
2018-10-04 11:12:55 +02:00
2019-08-15 12:52:56 +02:00
if ( dlg . ShowModal () != wxID_OK )
2019-04-04 11:31:26 +02:00
return wxEmptyString ;
2018-10-04 11:12:55 +02:00
2019-08-15 12:52:56 +02:00
wxString out_path = dlg . GetPath ();
2019-04-04 11:31:26 +02:00
fs :: path path ( into_path ( out_path ));
2018-10-04 11:12:55 +02:00
wxGetApp (). app_config -> update_last_output_dir ( path . parent_path (). string ());
2019-04-04 11:31:26 +02:00
return out_path ;
2018-10-04 11:12:55 +02:00
}
2019-03-19 13:30:21 +01:00
const Selection & Plater :: priv :: get_selection () const
2018-10-17 09:30:07 +02:00
{
2018-12-04 13:55:25 +01:00
return view3D -> get_canvas3d () -> get_selection ();
2018-10-17 09:30:07 +02:00
}
2019-03-19 13:30:21 +01:00
Selection & Plater :: priv :: get_selection ()
2018-10-17 09:30:07 +02:00
{
2018-12-04 13:55:25 +01:00
return view3D -> get_canvas3d () -> get_selection ();
2018-10-17 09:30:07 +02:00
}
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 11:12:55 +02:00
2018-12-06 14:49:14 +01:00
int Plater :: priv :: get_selected_volume_idx () const
{
auto & selection = get_selection ();
int idx = selection . get_object_idx ();
2019-08-06 18:16:02 +02:00
if (( 0 > idx ) || ( idx > 1000 ))
2018-12-06 14:49:14 +01:00
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 11:12:55 +02:00
void Plater :: priv :: selection_changed ()
{
2019-01-24 15:50:06 +01:00
// 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 08:37:06 +01:00
if ( ! enable_layer_editing && view3D -> is_layers_editing_enabled ()) {
SimpleEvent evt ( EVT_GLTOOLBAR_LAYERSEDITING );
on_action_layersediting ( evt );
}
2019-01-24 15:50:06 +01:00
2018-12-04 13:55:25 +01:00
// 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 11:12:55 +02:00
}
2018-10-08 19:14:55 +02:00
void Plater :: priv :: object_list_changed ()
2018-10-04 11:12:55 +02:00
{
2018-10-23 15:27:31 +02:00
const bool export_in_progress = this -> background_process . is_export_scheduled (); // || ! send_gcode_file.empty());
2018-10-08 19:14:55 +02:00
// XXX: is this right?
2018-12-04 13:55:25 +01:00
const bool model_fits = view3D -> check_volumes_outside_state () == ModelInstance :: PVS_Inside ;
2018-10-08 19:14:55 +02:00
2018-10-18 15:09:41 +02:00
sidebar -> enable_buttons ( ! model . objects . empty () && ! export_in_progress && model_fits );
2018-10-08 19:14:55 +02:00
}
2018-11-21 15:28:35 +01:00
void Plater :: priv :: select_all ()
{
2019-07-04 17:33:19 +02:00
// this->take_snapshot(_(L("Select All")));
2018-12-04 13:55:25 +01:00
view3D -> select_all ();
2018-12-03 13:29:07 +01:00
this -> sidebar -> obj_list () -> update_selections ();
2018-11-21 15:28:35 +01:00
}
2019-05-14 11:57:39 +02:00
void Plater :: priv :: deselect_all ()
{
2019-07-04 17:33:19 +02:00
// this->take_snapshot(_(L("Deselect All")));
2019-05-14 11:57:39 +02:00
view3D -> deselect_all ();
}
2018-10-08 19:14:55 +02:00
void Plater :: priv :: remove ( size_t obj_idx )
{
// Prevent toolpaths preview from rendering while we modify the Print object
preview -> set_enabled ( false );
2018-12-04 13:55:25 +01:00
if ( view3D -> is_layers_editing_enabled ())
view3D -> enable_layers_editing ( false );
2018-10-22 15:18:56 +02:00
2018-10-08 19:14:55 +02:00
model . delete_object ( obj_idx );
update ();
2019-05-14 16:37:32 +02:00
// 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 );
2019-05-08 10:37:45 +02:00
object_list_changed ();
2018-10-04 11:12:55 +02:00
}
2018-11-13 14:17:35 +01:00
void Plater :: priv :: delete_object_from_model ( size_t obj_idx )
{
2019-08-06 18:16:02 +02:00
wxString snapshot_label = _ ( L ( "Delete Object" ));
if ( ! model . objects [ obj_idx ] -> name . empty ())
snapshot_label += ": " + wxString :: FromUTF8 ( model . objects [ obj_idx ] -> name . c_str ());
Plater :: TakeSnapshot snapshot ( q , snapshot_label );
2018-11-13 14:17:35 +01:00
model . delete_object ( obj_idx );
2018-11-14 11:22:13 +01:00
update ();
2019-05-08 10:37:45 +02:00
object_list_changed ();
2018-11-13 14:17:35 +01:00
}
2018-10-04 11:12:55 +02:00
void Plater :: priv :: reset ()
{
2019-08-06 18:16:02 +02:00
Plater :: TakeSnapshot snapshot ( q , _ ( L ( "Reset Project" )));
2019-07-02 16:42:23 +02:00
2019-05-14 15:00:24 +02:00
set_project_filename ( wxEmptyString );
2018-11-15 15:27:39 +01:00
2018-10-04 11:12:55 +02:00
// Prevent toolpaths preview from rendering while we modify the Print object
preview -> set_enabled ( false );
2018-12-04 13:55:25 +01:00
if ( view3D -> is_layers_editing_enabled ())
view3D -> enable_layers_editing ( false );
2018-10-22 15:18:56 +02:00
2018-11-08 14:23:17 +01:00
// Stop and reset the Print content.
this -> background_process . reset ();
2018-10-04 11:12:55 +02:00
model . clear_objects ();
update ();
2019-05-14 16:37:32 +02:00
// 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 ();
2019-05-08 10:37:45 +02:00
object_list_changed ();
2018-11-28 16:02:29 +01:00
2019-01-30 10:00:38 +01:00
// 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 );
2018-11-28 16:02:29 +01:00
2019-10-10 16:03:58 +02:00
model . custom_gcode_per_height . clear ();
2018-10-04 11:12:55 +02:00
}
2018-10-18 15:09:41 +02:00
void Plater :: priv :: mirror ( Axis axis )
2018-10-09 09:35:19 +02:00
{
2018-12-04 13:55:25 +01:00
view3D -> mirror_selection ( axis );
2018-10-09 09:35:19 +02:00
}
void Plater :: priv :: arrange ()
{
2019-07-04 17:33:19 +02:00
this -> take_snapshot ( _ ( L ( "Arrange" )));
2019-06-18 16:24:30 +02:00
m_ui_jobs . start ( Jobs :: Arrange );
2019-05-13 18:58:56 +02:00
}
2018-11-12 14:52:52 +01:00
2019-05-13 18:58:56 +02:00
// 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 () {
2019-07-04 17:33:19 +02:00
this -> take_snapshot ( _ ( L ( "Optimize Rotation" )));
2019-06-18 16:24:30 +02:00
m_ui_jobs . start ( Jobs :: Rotoptimize );
2019-05-13 18:58:56 +02:00
}
2019-02-03 14:06:13 +01:00
2019-07-01 18:22:07 +02:00
arrangement :: BedShapeHint Plater :: priv :: get_bed_shape_hint () const {
2019-08-06 18:16:02 +02:00
2019-06-27 21:13:44 +02:00
const auto * bed_shape_opt = config -> opt < ConfigOptionPoints > ( "bed_shape" );
assert ( bed_shape_opt );
2019-08-06 18:16:02 +02:00
2019-07-15 17:30:44 +02:00
if ( ! bed_shape_opt ) return {};
2019-08-06 18:16:02 +02:00
2019-07-15 17:30:44 +02:00
auto & bedpoints = bed_shape_opt -> values ;
Polyline bedpoly ; bedpoly . points . reserve ( bedpoints . size ());
for ( auto & v : bedpoints ) bedpoly . append ( scaled ( v ));
2019-08-06 18:16:02 +02:00
2019-07-15 17:30:44 +02:00
return arrangement :: BedShapeHint ( bedpoly );
2019-06-27 21:13:44 +02:00
}
2019-04-17 14:56:00 +02:00
2019-07-16 18:33:42 +02:00
void Plater :: priv :: find_new_position ( const ModelInstancePtrs & instances ,
coord_t min_d )
2019-07-15 18:18:34 +02:00
{
arrangement :: ArrangePolygons movable , fixed ;
2019-08-06 18:16:02 +02:00
2019-07-15 18:18:34 +02:00
for ( const ModelObject * mo : model . objects )
for ( const ModelInstance * inst : mo -> instances ) {
auto it = std :: find ( instances . begin (), instances . end (), inst );
auto arrpoly = inst -> get_arrange_polygon ();
2019-08-06 18:16:02 +02:00
2019-07-15 18:18:34 +02:00
if ( it == instances . end ())
fixed . emplace_back ( std :: move ( arrpoly ));
else
movable . emplace_back ( std :: move ( arrpoly ));
}
2019-08-06 18:16:02 +02:00
2019-07-17 13:32:31 +02:00
if ( updated_wipe_tower ())
fixed . emplace_back ( wipetower . get_arrange_polygon ());
2019-08-06 18:16:02 +02:00
2019-07-15 18:18:34 +02:00
arrangement :: arrange ( movable , fixed , min_d , get_bed_shape_hint ());
2019-08-06 18:16:02 +02:00
2019-07-15 18:18:34 +02:00
for ( size_t i = 0 ; i < instances . size (); ++ i )
if ( movable [ i ]. bed_idx == 0 )
2019-07-17 13:32:31 +02:00
instances [ i ] -> apply_arrange_result ( movable [ i ]. translation ,
movable [ i ]. rotation );
2019-07-15 18:18:34 +02:00
}
2019-07-15 17:30:44 +02:00
void Plater :: priv :: ArrangeJob :: process () {
2019-06-18 16:24:30 +02:00
static const auto arrangestr = _ ( L ( "Arranging" ));
2019-08-06 18:16:02 +02:00
2018-11-12 14:52:52 +01:00
// 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.
2019-06-18 16:24:30 +02:00
double dist = 6 ; // PrintConfig::min_object_distance(config);
if ( plater (). printer_technology == ptFFF ) {
2019-07-15 17:30:44 +02:00
dist = PrintConfig :: min_object_distance ( plater (). config );
2019-02-08 12:30:10 +01:00
}
2019-08-06 18:16:02 +02:00
2019-07-16 18:33:42 +02:00
coord_t min_d = scaled ( dist );
auto count = unsigned ( m_selected . size ());
2019-07-15 17:30:44 +02:00
arrangement :: BedShapeHint bedshape = plater (). get_bed_shape_hint ();
2019-08-06 18:16:02 +02:00
2018-11-12 14:52:52 +01:00
try {
2019-07-16 18:33:42 +02:00
arrangement :: arrange ( m_selected , m_unselected , min_d , bedshape ,
2019-07-15 17:30:44 +02:00
[ this , count ]( unsigned st ) {
2019-10-07 17:16:40 +02:00
if ( st > 0 ) // will not finalize after last one
update_status ( int ( count - st ), arrangestr );
2019-07-15 17:30:44 +02:00
},
[ this ]() { return was_canceled (); });
2019-06-18 16:24:30 +02:00
} catch ( std :: exception & /*e*/ ) {
GUI :: show_error ( plater (). q ,
2019-07-15 17:30:44 +02:00
_ ( L ( "Could not arrange model objects! "
"Some geometries may be invalid." )));
2018-11-12 14:52:52 +01:00
}
2019-08-06 18:16:02 +02:00
2019-07-01 18:22:07 +02:00
// finalize just here.
update_status ( int ( count ),
2019-06-18 16:24:30 +02:00
was_canceled () ? _ ( L ( "Arranging canceled." ))
: _ ( L ( "Arranging done." )));
2018-10-09 09:35:19 +02:00
}
2019-07-15 17:30:44 +02:00
void Plater :: priv :: RotoptimizeJob :: process ()
2019-05-13 18:58:56 +02:00
{
int obj_idx = plater (). get_selected_object_idx ();
2019-04-17 14:56:00 +02:00
if ( obj_idx < 0 ) { return ; }
2019-04-04 16:03:23 +02:00
2019-06-18 16:24:30 +02:00
ModelObject * o = plater (). model . objects [ size_t ( obj_idx )];
2018-11-22 16:04:21 +01:00
auto r = sla :: find_best_rotation (
2019-06-18 16:24:30 +02:00
* o ,
.005f ,
[ this ]( unsigned s ) {
if ( s < 100 )
update_status ( int ( s ),
_ ( L ( "Searching for optimal orientation" )));
},
[ this ]() { return was_canceled (); });
2018-11-22 16:04:21 +01:00
2019-04-04 16:03:23 +02:00
double mindist = 6.0 ; // FIXME
2019-08-06 18:16:02 +02:00
2019-06-19 10:43:10 +02:00
if ( ! was_canceled ()) {
for ( ModelInstance * oi : o -> instances ) {
2019-06-18 16:24:30 +02:00
oi -> set_rotation ({ r [ X ], r [ Y ], r [ Z ]});
2019-08-06 18:16:02 +02:00
2019-06-19 10:43:10 +02:00
auto trmatrix = oi -> get_transformation (). get_matrix ();
Polygon trchull = o -> convex_hull_2d ( trmatrix );
2019-08-06 18:16:02 +02:00
2019-06-19 10:43:10 +02:00
MinAreaBoundigBox rotbb ( trchull , MinAreaBoundigBox :: pcConvex );
double r = rotbb . angle_to_X ();
2019-08-06 18:16:02 +02:00
2019-06-19 10:43:10 +02:00
// The box should be landscape
if ( rotbb . width () < rotbb . height ()) r += PI / 2 ;
2019-08-06 18:16:02 +02:00
2019-06-19 10:43:10 +02:00
Vec3d rt = oi -> get_rotation (); rt ( Z ) += r ;
2019-08-06 18:16:02 +02:00
2019-06-19 10:43:10 +02:00
oi -> set_rotation ( rt );
2019-06-18 16:24:30 +02:00
}
2019-08-06 18:16:02 +02:00
2019-07-15 18:18:34 +02:00
plater (). find_new_position ( o -> instances , scaled ( mindist ));
2019-04-04 16:03:23 +02:00
2019-06-19 10:43:10 +02:00
// Correct the z offset of the object which was corrupted be
// the rotation
o -> ensure_on_bed ();
2019-04-04 16:03:23 +02:00
}
2019-06-19 10:43:10 +02:00
update_status ( 100 ,
was_canceled () ? _ ( L ( "Orientation search canceled." ))
: _ ( L ( "Orientation found." )));
2018-11-22 16:04:21 +01:00
}
2018-10-09 09:35:19 +02:00
void Plater :: priv :: split_object ()
{
2018-10-19 15:27:19 +02:00
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 ;
}
2019-02-05 20:23:24 +01:00
wxBusyCursor wait ;
2018-10-19 15:27:19 +02: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." )));
else
{
2019-08-06 18:16:02 +02:00
Plater :: TakeSnapshot snapshot ( q , _ ( L ( "Split to Objects" )));
2019-07-04 17:33:19 +02:00
2018-10-24 12:55:38 +02:00
unsigned int counter = 1 ;
2018-10-19 15:27:19 +02:00
for ( ModelObject * m : new_objects )
2018-10-24 12:55:38 +02:00
m -> name = current_model_object -> name + "_" + std :: to_string ( counter ++ );
2018-10-19 15:27:19 +02: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
2018-10-31 11:24:56 +01:00
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 15:27:19 +02:00
}
2018-10-09 09:35:19 +02:00
}
2018-10-24 12:55:38 +02:00
void Plater :: priv :: split_volume ()
{
2018-12-06 14:49:14 +01:00
wxGetApp (). obj_list () -> split ();
2018-10-24 12:55:38 +02:00
}
2019-05-22 14:42:38 +02:00
void Plater :: priv :: scale_selection_to_fit_print_volume ()
{
this -> view3D -> get_canvas3d () -> get_selection (). scale_to_fit_print_volume ( * config );
}
2018-10-09 09:35:19 +02:00
void Plater :: priv :: schedule_background_process ()
{
2019-02-22 11:59:40 +01:00
delayed_error_message . clear ();
2018-10-17 11:12:38 +02:00
// Trigger the timer event after 0.5s
this -> background_process_timer . Start ( 500 , wxTIMER_ONE_SHOT );
2019-01-21 17:02:16 +01:00
// 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 );
2018-10-09 09:35:19 +02:00
}
2018-12-03 13:14:28 +01:00
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 );
}
2018-11-13 17:45:44 +01:00
// Update background processing thread from the current config and Model.
// Returns a bitmask of UpdateBackgroundProcessReturnState.
2019-08-23 15:53:45 +02:00
unsigned int Plater :: priv :: update_background_process ( bool force_validation , bool postpone_error_messages )
2018-10-09 09:35:19 +02:00
{
2018-11-13 17:45:44 +01:00
// bitmap of enum UpdateBackgroundProcessReturnState
unsigned int return_state = 0 ;
2019-08-06 18:16:02 +02:00
// If the update_background_process() was not called by the timer, kill the timer,
2018-12-22 10:02:42 +01:00
// so the update_restart_background_process() will not be called again in vain.
2018-11-13 17:45:44 +01:00
this -> background_process_timer . Stop ();
2018-12-03 13:14:28 +01:00
// Update the "out of print bed" state of ModelInstances.
this -> update_print_volume_state ();
2018-10-17 11:12:38 +02:00
// Apply new config to the possibly running background task.
2018-12-22 10:02:42 +01:00
bool was_running = this -> background_process . running ();
2019-10-15 15:42:30 +02:00
this -> background_process . set_force_update_print_regions ( force_validation );
2018-12-03 13:14:28 +01:00
Print :: ApplyStatus invalidated = this -> background_process . apply ( this -> q -> model (), wxGetApp (). preset_bundle -> full_config ());
2018-11-08 16:01:21 +01:00
2018-10-17 11:12:38 +02:00
// Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile.
2018-12-04 13:55:25 +01:00
if ( view3D -> is_layers_editing_enabled ())
view3D -> get_wxglcanvas () -> Refresh ();
2018-10-23 22:53:43 +02:00
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.
2018-10-31 12:26:57 +01:00
this -> sidebar -> show_sliced_info_sizer ( false );
2018-10-23 22:53:43 +02:00
// Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared.
// Otherwise they will be just refreshed.
2019-08-06 18:16:02 +02: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 ;
2018-10-17 11:12:38 +02:00
}
2018-11-13 17:45:44 +01:00
2019-01-10 16:06:24 +01:00
if (( invalidated != Print :: APPLY_STATUS_UNCHANGED || force_validation ) && ! this -> background_process . empty ()) {
2019-08-23 15:53:45 +02:00
// 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.
2018-11-13 17:45:44 +01:00
std :: string err = this -> background_process . validate ();
if ( err . empty ()) {
2019-08-06 18:16:02 +02:00
if ( invalidated != Print :: APPLY_STATUS_UNCHANGED && this -> background_processing_enabled ())
2018-11-13 17:45:44 +01:00
return_state |= UPDATE_BACKGROUND_PROCESS_RESTART ;
} else {
// The print is not valid.
2019-02-19 14:57:59 +01:00
// 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 );
2019-08-23 15:53:45 +02:00
if ( ! postpone_error_messages && top_level_wnd && top_level_wnd -> IsActive ()) {
2019-02-19 14:57:59 +01:00
// 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 );
}
2018-11-13 17:45:44 +01:00
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID ;
}
2019-08-23 15:53:45 +02:00
} else if ( ! this -> delayed_error_message . empty ()) {
// Reusing the old state.
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID ;
2018-11-13 17:45:44 +01:00
}
2018-12-22 10:02:42 +01:00
2019-01-10 16:06:24 +01:00
if ( invalidated != Print :: APPLY_STATUS_UNCHANGED && was_running && ! this -> background_process . running () &&
2018-12-22 10:02:42 +01:00
( return_state & UPDATE_BACKGROUND_PROCESS_RESTART ) == 0 ) {
2019-08-06 18:16:02 +02: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 ());
}
2018-12-22 10:02:42 +01:00
2019-03-01 11:00:34 +01:00
if (( return_state & UPDATE_BACKGROUND_PROCESS_INVALID ) != 0 )
{
2019-03-12 09:41:57 +01:00
// Validation of the background data failed.
2019-03-01 11:00:34 +01:00
const wxString invalid_str = _ ( L ( "Invalid data" ));
for ( auto btn : { ActionButtonType :: abReslice , ActionButtonType :: abSendGCode , ActionButtonType :: abExport })
sidebar -> set_btn_label ( btn , invalid_str );
}
else
{
2019-03-12 09:41:57 +01:00
// Background data is valid.
2019-03-01 11:00:34 +01:00
if (( return_state & UPDATE_BACKGROUND_PROCESS_RESTART ) != 0 ||
( return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE ) != 0 )
2019-05-04 02:07:07 +02:00
this -> statusbar () -> set_status_text ( _ ( L ( "Ready to slice" )));
2019-03-01 11:00:34 +01:00
sidebar -> set_btn_label ( ActionButtonType :: abExport , _ ( label_btn_export ));
2019-03-12 09:41:57 +01:00
sidebar -> set_btn_label ( ActionButtonType :: abSendGCode , _ ( label_btn_send ));
2019-08-06 18:16:02 +02:00
const wxString slice_string = background_process . running () && wxGetApp (). get_mode () == comSimple ?
2019-03-01 11:00:34 +01:00
_ ( 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 18:16:02 +02:00
else if ( ! background_process . empty () &&
2019-03-11 10:23:59 +01:00
! background_process . running ()) /* Do not update buttons if background process is running
2019-08-06 18:16:02 +02:00
* This condition is important for SLA mode especially,
* when this function is called several times during calculations
2019-03-11 10:23:59 +01:00
* */
2019-03-01 11:00:34 +01:00
show_action_buttons ( true );
}
2018-11-13 17:45:44 +01:00
return return_state ;
2018-10-09 09:35:19 +02:00
}
2018-12-22 10:02:42 +01:00
// Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
bool Plater :: priv :: restart_background_process ( unsigned int state )
{
2019-06-18 16:24:30 +02:00
if ( m_ui_jobs . is_any_running ()) {
2019-04-17 14:56:00 +02:00
// Avoid a race condition
return false ;
}
2019-08-06 18:16:02 +02:00
if ( ! this -> background_process . empty () &&
( state & priv :: UPDATE_BACKGROUND_PROCESS_INVALID ) == 0 &&
( (( state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART ) != 0 && ! this -> background_process . finished ()) ||
2018-12-22 10:02:42 +01:00
( 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 ]() {
2019-05-04 02:07:07 +02:00
this -> statusbar () -> set_status_text ( _ ( L ( "Cancelling" )));
2018-12-22 10:02:42 +01:00
this -> background_process . stop ();
});
return true ;
}
}
return false ;
}
2018-12-11 10:33:11 +01:00
void Plater :: priv :: export_gcode ( fs :: path output_path , PrintHostJob upload_job )
{
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." )));
return ;
}
// bitmask of UpdateBackgroundProcessReturnState
2019-01-10 16:06:24 +01:00
unsigned int state = update_background_process ( true );
2018-12-11 10:33:11 +01:00
if ( state & priv :: UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE )
view3D -> reload_scene ( false );
2019-01-03 12:59:06 +01:00
2018-12-11 10:33:11 +01:00
if (( state & priv :: UPDATE_BACKGROUND_PROCESS_INVALID ) != 0 )
return ;
if ( ! output_path . empty ()) {
background_process . schedule_export ( output_path . string ());
} else {
background_process . schedule_upload ( std :: move ( upload_job ));
}
2019-02-25 10:21:12 +01:00
// 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 ());
2018-12-22 10:02:42 +01:00
this -> restart_background_process ( priv :: UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT );
2018-12-11 10:33:11 +01:00
}
2019-02-03 10:41:14 +01:00
unsigned int Plater :: priv :: update_restart_background_process ( bool force_update_scene , bool force_update_preview )
2018-10-09 09:35:19 +02:00
{
2018-11-13 17:45:44 +01:00
// bitmask of UpdateBackgroundProcessReturnState
2019-01-10 16:06:24 +01:00
unsigned int state = this -> update_background_process ( false );
2018-12-22 10:02:42 +01:00
if ( force_update_scene || ( state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE ) != 0 )
2018-12-04 13:55:25 +01:00
view3D -> reload_scene ( false );
2019-01-03 12:59:06 +01:00
2018-12-22 10:02:42 +01:00
if ( force_update_preview )
this -> preview -> reload_print ();
this -> restart_background_process ( state );
2019-08-06 18:16:02 +02:00
return state ;
2018-10-09 09:35:19 +02:00
}
2018-12-06 15:40:41 +01: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 18:16:02 +02:00
view3D -> reload_scene ( true );
2018-12-06 15:40:41 +01:00
}
2018-11-23 12:47:32 +01: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 ;
2018-12-22 10:02:42 +01:00
this -> update_restart_background_process ( true , true );
2018-11-23 12:47:32 +01:00
}
2018-10-09 09:35:19 +02:00
void Plater :: priv :: reload_from_disk ()
{
2019-09-19 09:09:11 +02:00
Plater :: TakeSnapshot snapshot ( q , _ ( L ( "Reload from disk" )));
2019-07-05 19:46:48 +02:00
2019-09-19 09:09:11 +02:00
const Selection & selection = get_selection ();
2018-10-09 09:35:19 +02:00
2019-09-19 09:09:11 +02:00
if ( selection . is_wipe_tower ())
2019-09-06 14:36:47 +02:00
return ;
2018-12-05 15:22:03 +01:00
2019-09-19 09:09:11 +02:00
// struct to hold selected ModelVolumes by their indices
struct SelectedVolume
2019-09-11 15:02:57 +02:00
{
2019-09-19 09:09:11 +02:00
int object_idx ;
int volume_idx ;
2019-09-10 11:16:59 +02:00
2019-09-19 09:09:11 +02:00
// 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 ();
2019-11-25 14:42:36 +01:00
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 });
}
2019-09-19 09:09:11 +02:00
}
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 ;
2019-12-10 14:42:28 +01:00
#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
std :: vector < fs :: path > missing_input_paths ;
#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
2019-09-19 09:09:11 +02:00
for ( const SelectedVolume & v : selected_volumes )
{
2019-12-10 14:42:28 +01:00
#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
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 );
}
#else
2019-09-19 09:09:11 +02:00
const ModelVolume * volume = model . objects [ v . object_idx ] -> volumes [ v . volume_idx ];
if ( ! volume -> source . input_file . empty () && boost :: filesystem :: exists ( volume -> source . input_file ))
input_paths . push_back ( volume -> source . input_file );
2019-12-10 14:42:28 +01:00
#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
2019-09-19 09:09:11 +02:00
}
2019-12-10 14:42:28 +01:00
#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
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
std :: string search = missing_input_paths . back (). string ();
2019-12-13 12:41:46 +01:00
wxFileDialog dialog ( q , _ ( L ( "Please select the file to reload:" )), "" , from_u8 ( fs :: path ( search ). filename (). string ()), file_wildcards ( FT_MODEL ), wxFD_OPEN | wxFD_FILE_MUST_EXIST );
2019-12-10 14:42:28 +01:00
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 :: iends_with ( search , sel_filename ))
{
input_paths . push_back ( sel_filename_path );
missing_input_paths . pop_back ();
std :: string 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
std :: string repathed_filename = sel_path + "/" + it -> filename (). string ();
if ( fs :: exists ( repathed_filename ))
{
input_paths . push_back ( repathed_filename );
it = missing_input_paths . erase ( it );
}
else
++ it ;
}
}
}
#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
2019-09-19 09:09:11 +02:00
std :: sort ( input_paths . begin (), input_paths . end ());
input_paths . erase ( std :: unique ( input_paths . begin (), input_paths . end ()), input_paths . end ());
// load one file at a time
for ( size_t i = 0 ; i < input_paths . size (); ++ i )
{
const auto & path = input_paths [ i ]. string ();
Model new_model ;
try
2019-09-11 15:02:57 +02:00
{
2019-09-19 09:09:11 +02:00
new_model = Model :: read_from_file ( path , nullptr , true , false );
for ( ModelObject * model_object : new_model . objects )
2019-09-11 15:02:57 +02:00
{
2019-09-19 09:09:11 +02:00
model_object -> center_around_origin ();
model_object -> ensure_on_bed ();
2018-12-05 15:22:03 +01:00
}
}
2019-09-19 09:09:11 +02:00
catch ( std :: exception & )
{
// error while loading
return ;
}
2018-12-05 15:22:03 +01:00
2019-09-19 09:09:11 +02:00
// update the selected volumes whose source is the current file
for ( const SelectedVolume & old_v : selected_volumes )
{
ModelObject * old_model_object = model . objects [ old_v . object_idx ];
ModelVolume * old_volume = old_model_object -> volumes [ old_v . volume_idx ];
int new_volume_idx = old_volume -> source . volume_idx ;
int new_object_idx = old_volume -> source . object_idx ;
if ( old_volume -> source . input_file == path )
{
2019-12-10 14:42:28 +01:00
assert ( new_object_idx < ( int ) new_model . objects . size ());
ModelObject * new_model_object = new_model . objects [ new_object_idx ];
if ( new_volume_idx < ( int ) new_model_object -> volumes . size ())
2019-09-19 09:09:11 +02:00
{
2019-12-10 14:42:28 +01:00
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 ());
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 [ old_v . volume_idx ], old_model_object -> volumes . back ());
old_model_object -> delete_volume ( old_model_object -> volumes . size () - 1 );
2019-09-19 09:09:11 +02:00
}
}
}
2018-12-05 15:22:03 +01:00
}
2019-09-19 09:09:11 +02:00
model . adjust_min_z ();
2019-09-11 15:31:30 +02:00
2019-09-19 09:09:11 +02:00
// update 3D scene
update ();
2019-09-06 13:11:20 +02:00
2019-09-10 11:16:59 +02: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 );
}
2018-10-09 09:35:19 +02:00
}
2019-01-30 16:27:07 +01:00
void Plater :: priv :: fix_through_netfabb ( const int obj_idx , const int vol_idx /* = -1*/ )
2018-10-09 09:35:19 +02:00
{
2018-11-07 13:40:24 +01:00
if ( obj_idx < 0 )
return ;
2019-07-04 17:33:19 +02:00
2019-08-06 18:16:02 +02:00
Plater :: TakeSnapshot snapshot ( q , _ ( L ( "Fix Throught NetFabb" )));
2019-07-04 17:33:19 +02:00
2019-02-03 22:14:34 +01:00
fix_model_by_win10_sdk_gui ( * model . objects [ obj_idx ], vol_idx );
this -> update ();
2019-05-08 10:37:45 +02:00
this -> object_list_changed ();
2019-02-03 22:14:34 +01:00
this -> schedule_background_process ();
2018-10-09 09:35:19 +02:00
}
2018-12-04 13:55:25 +01:00
void Plater :: priv :: set_current_panel ( wxPanel * panel )
{
if ( std :: find ( panels . begin (), panels . end (), panel ) == panels . end ())
return ;
2019-03-08 08:29:54 +01:00
#ifdef __WXMAC__
2019-03-07 15:44:25 +01:00
bool force_render = ( current_panel != nullptr );
2019-03-08 08:29:54 +01:00
#endif // __WXMAC__
2019-03-07 15:44:25 +01:00
2018-12-04 13:55:25 +01:00
if ( current_panel == panel )
return ;
current_panel = panel ;
2018-12-06 13:44:38 +01:00
// to reduce flickering when changing view, first set as visible the new current panel
2018-12-04 13:55:25 +01:00
for ( wxPanel * p : panels )
{
2018-12-06 13:44:38 +01:00
if ( p == current_panel )
2019-03-07 15:44:25 +01:00
{
2019-03-08 08:29:54 +01:00
#ifdef __WXMAC__
// On Mac we need also to force a render to avoid flickering when changing view
2019-03-07 15:44:25 +01:00
if ( force_render )
{
if ( p == view3D )
dynamic_cast < View3D *> ( p ) -> get_canvas3d () -> render ();
else if ( p == preview )
dynamic_cast < Preview *> ( p ) -> get_canvas3d () -> render ();
}
2019-03-08 08:29:54 +01:00
#endif // __WXMAC__
2018-12-06 13:44:38 +01:00
p -> Show ();
2019-03-07 15:44:25 +01:00
}
2018-12-06 13:44:38 +01:00
}
// then set to invisible the other
for ( wxPanel * p : panels )
{
if ( p != current_panel )
p -> Hide ();
2018-12-04 13:55:25 +01:00
}
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.
2018-12-22 10:02:42 +01:00
this -> update_restart_background_process ( true , false );
} else
view3D -> reload_scene ( true );
2018-12-04 13:55:25 +01:00
}
2019-09-27 14:52:19 +02:00
2018-12-04 13:55:25 +01:00
// 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 ();
2018-12-21 10:43:22 +01:00
view_toolbar . select_item ( "3D" );
2018-12-04 13:55:25 +01:00
}
else if ( current_panel == preview )
{
2019-08-29 15:38:21 +02:00
// 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 ();
2019-02-28 15:27:03 +01:00
// keeps current gcode preview, if any
2019-03-18 14:07:55 +01:00
preview -> reload_print ( true );
2019-09-27 14:52:19 +02:00
2018-12-04 13:55:25 +01:00
preview -> set_canvas_as_dirty ();
2018-12-21 10:43:22 +01:00
view_toolbar . select_item ( "Preview" );
2018-12-04 13:55:25 +01:00
}
2018-12-21 11:39:37 +01:00
current_panel -> SetFocusFromKbd ();
2018-12-04 13:55:25 +01:00
}
2018-09-17 12:15:11 +02:00
void Plater :: priv :: on_select_preset ( wxCommandEvent & evt )
{
auto preset_type = static_cast < Preset :: Type > ( evt . GetInt ());
2018-10-10 13:53:45 +02:00
auto * combo = static_cast < PresetComboBox *> ( evt . GetEventObject ());
2018-09-17 12:15:11 +02:00
2018-10-10 13:53:45 +02:00
auto idx = combo -> get_extruder_idx ();
2018-10-09 12:41:05 +02:00
2019-08-06 18:16:02 +02:00
//! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox,
2018-10-15 11:39:48 +02:00
//! but the OSX version derived from wxOwnerDrawnCombo.
2019-08-06 18:16:02 +02:00
//! So, to get selected string we do
//! combo->GetString(combo->GetSelection())
//! instead of
//! combo->GetStringSelection().ToUTF8().data());
2018-10-15 11:39:48 +02:00
2019-03-13 13:13:18 +01:00
const std :: string & selected_string = combo -> GetString ( combo -> GetSelection ()). ToUTF8 (). data ();
2019-11-29 11:02:30 +01:00
const std :: string preset_name = wxGetApp (). preset_bundle -> get_preset_name_by_alias ( preset_type , selected_string );
2018-10-15 11:39:48 +02:00
2018-09-17 12:15:11 +02:00
if ( preset_type == Preset :: TYPE_FILAMENT ) {
2019-11-29 11:02:30 +01:00
wxGetApp (). preset_bundle -> set_filament_preset ( idx , preset_name );
2018-09-17 12:15:11 +02:00
}
// TODO: ?
2018-10-09 12:41:05 +02:00
if ( preset_type == Preset :: TYPE_FILAMENT && sidebar -> is_multifilament ()) {
2018-10-10 13:53:45 +02:00
// Only update the platter UI for the 2nd and other filaments.
2019-04-23 16:33:06 +02:00
wxGetApp (). preset_bundle -> update_platter_filament_ui ( idx , combo );
2019-08-06 18:16:02 +02:00
}
2018-10-10 13:53:45 +02:00
else {
2019-03-13 13:13:18 +01:00
wxWindowUpdateLocker noUpdates ( sidebar -> presets_panel ());
2019-11-29 11:02:30 +01:00
wxGetApp (). get_tab ( preset_type ) -> select_preset ( preset_name );
2018-09-17 12:15:11 +02:00
}
2018-10-15 11:39:48 +02:00
// update plater with new config
2018-10-17 11:12:38 +02:00
wxGetApp (). plater () -> on_config_change ( wxGetApp (). preset_bundle -> full_config ());
2019-06-10 10:48:43 +02:00
/* Settings list can be changed after printer preset changing, so
* update all settings items for all item had it.
2019-08-06 18:16:02 +02:00
* Furthermore, Layers editing is implemented only for FFF printers
2019-06-10 10:48:43 +02:00
* and for SLA presets they should be deleted
*/
2018-11-29 15:00:23 +01:00
if ( preset_type == Preset :: TYPE_PRINTER )
2019-06-10 10:48:43 +02:00
wxGetApp (). obj_list () -> update_object_list_by_printer_technology ();
2018-09-17 12:15:11 +02:00
}
2018-11-20 13:22:26 +01:00
void Plater :: priv :: on_slicing_update ( SlicingStatusEvent & evt )
2018-10-09 09:35:19 +02:00
{
2019-03-14 15:11:27 +01:00
if ( evt . status . percent >= - 1 ) {
2019-06-18 16:24:30 +02:00
if ( m_ui_jobs . is_any_running ()) {
2019-04-17 14:56:00 +02:00
// Avoid a race condition
return ;
}
2019-03-14 15:11:27 +01:00
this -> statusbar () -> set_progress ( evt . status . percent );
2019-05-04 02:07:07 +02:00
this -> statusbar () -> set_status_text ( _ ( evt . status . text ) + wxString :: FromUTF8 ( "…" ));
2019-03-14 15:11:27 +01:00
}
2019-09-25 12:06:38 +02:00
if ( evt . status . flags & ( PrintBase :: SlicingStatus :: RELOAD_SCENE | PrintBase :: SlicingStatus :: RELOAD_SLA_SUPPORT_POINTS )) {
2018-11-20 13:22:26 +01:00
switch ( this -> printer_technology ) {
case ptFFF :
2018-12-06 15:40:41 +01:00
this -> update_fff_scene ();
2018-11-20 13:22:26 +01:00
break ;
case ptSLA :
2019-04-10 13:36:15 +02:00
// If RELOAD_SLA_SUPPORT_POINTS, then the SLA gizmo is updated (reload_scene calls update_gizmos_data)
2018-12-04 13:55:25 +01:00
if ( view3D -> is_dragging ())
2018-11-23 12:47:32 +01:00
delayed_scene_refresh = true ;
else
this -> update_sla_scene ();
2018-11-20 13:22:26 +01:00
break ;
2019-09-03 10:27:16 +02:00
default : break ;
2018-11-20 13:22:26 +01:00
}
2019-04-10 13:36:15 +02:00
} 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.
2019-03-14 15:11:27 +01:00
this -> preview -> reload_print ();
}
2018-10-09 09:35:19 +02:00
}
2018-11-20 13:22:26 +01:00
void Plater :: priv :: on_slicing_completed ( wxCommandEvent & )
2018-09-17 12:15:11 +02:00
{
2018-11-20 13:22:26 +01:00
switch ( this -> printer_technology ) {
case ptFFF :
2018-12-06 15:40:41 +01:00
this -> update_fff_scene ();
2018-11-20 13:22:26 +01:00
break ;
case ptSLA :
2018-12-04 13:55:25 +01:00
if ( view3D -> is_dragging ())
2018-11-23 12:47:32 +01:00
delayed_scene_refresh = true ;
else
this -> update_sla_scene ();
2018-11-20 13:22:26 +01:00
break ;
2019-09-03 10:27:16 +02:00
default : break ;
2018-11-20 13:22:26 +01:00
}
2018-09-17 12:15:11 +02:00
}
2018-10-23 15:27:31 +02:00
void Plater :: priv :: on_process_completed ( wxCommandEvent & evt )
2018-09-17 12:15:11 +02:00
{
2018-10-18 18:06:40 +02: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 ();
2018-10-23 15:27:31 +02:00
this -> statusbar () -> reset_cancel_callback ();
this -> statusbar () -> stop_busy ();
2019-08-06 18:16:02 +02:00
2019-03-11 09:48:24 +01:00
const bool canceled = evt . GetInt () < 0 ;
2019-08-06 18:16:02 +02:00
const bool error = evt . GetInt () == 0 ;
2019-03-11 09:48:24 +01:00
const bool success = evt . GetInt () > 0 ;
2018-10-23 15:27:31 +02:00
// Reset the "export G-code path" name, so that the automatic background processing will be enabled again.
this -> background_process . reset_export ();
2019-03-11 09:48:24 +01:00
if ( error ) {
2018-10-23 15:27:31 +02:00
wxString message = evt . GetString ();
if ( message . IsEmpty ())
message = _ ( L ( "Export failed" ));
2019-03-11 09:48:24 +01:00
show_error ( q , message );
2018-10-23 15:27:31 +02:00
this -> statusbar () -> set_status_text ( message );
2018-10-18 18:06:40 +02:00
}
2019-08-06 18:16:02 +02:00
if ( canceled )
this -> statusbar () -> set_status_text ( _ ( L ( "Cancelled" )));
2018-10-18 18:06:40 +02:00
2018-10-31 12:26:57 +01:00
this -> sidebar -> show_sliced_info_sizer ( success );
2018-10-18 18:06:40 +02:00
2018-12-04 11:14:39 +01: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 18:16:02 +02:00
2018-10-18 18:06:40 +02:00
// refresh preview
2018-11-13 17:45:44 +01:00
switch ( this -> printer_technology ) {
case ptFFF :
2018-12-06 15:40:41 +01:00
this -> update_fff_scene ();
2018-11-13 17:45:44 +01:00
break ;
case ptSLA :
2018-12-04 13:55:25 +01:00
if ( view3D -> is_dragging ())
2018-11-23 12:47:32 +01:00
delayed_scene_refresh = true ;
else
this -> update_sla_scene ();
2018-11-13 17:45:44 +01:00
break ;
2019-09-03 10:27:16 +02:00
default : break ;
2018-11-12 17:35:57 +01:00
}
2019-03-01 11:00:34 +01:00
2019-03-08 15:40:28 +01:00
if ( canceled ) {
if ( wxGetApp (). get_mode () == comSimple )
sidebar -> set_btn_label ( ActionButtonType :: abReslice , "Slice now" );
show_action_buttons ( true );
}
else if ( wxGetApp (). get_mode () == comSimple )
2019-03-01 11:00:34 +01:00
show_action_buttons ( false );
2018-09-17 12:15:11 +02:00
}
void Plater :: priv :: on_layer_editing_toggled ( bool enable )
{
2018-12-04 13:55:25 +01:00
view3D -> enable_layers_editing ( enable );
view3D -> set_as_dirty ();
2018-09-17 12:15:11 +02:00
}
2018-10-03 11:34:39 +02:00
void Plater :: priv :: on_action_add ( SimpleEvent & )
2018-09-17 12:15:11 +02:00
{
2018-10-17 12:17:25 +02:00
if ( q != nullptr )
2018-11-15 15:27:39 +01:00
q -> add_model ();
2018-09-17 12:15:11 +02:00
}
2018-10-24 12:55:38 +02:00
void Plater :: priv :: on_action_split_objects ( SimpleEvent & )
2018-10-04 11:12:55 +02:00
{
2018-10-19 15:27:19 +02:00
split_object ();
2018-10-04 11:12:55 +02:00
}
2018-10-24 12:55:38 +02:00
void Plater :: priv :: on_action_split_volumes ( SimpleEvent & )
{
split_volume ();
}
2018-10-04 11:12:55 +02:00
void Plater :: priv :: on_action_layersediting ( SimpleEvent & )
{
2019-03-14 13:54:05 +01:00
view3D -> enable_layers_editing ( ! view3D -> is_layers_editing_enabled ());
2018-10-04 11:12:55 +02:00
}
2018-10-16 16:04:19 +02:00
void Plater :: priv :: on_object_select ( SimpleEvent & evt )
{
2019-07-04 17:33:19 +02:00
// this->take_snapshot(_(L("Object Selection")));
2018-10-16 16:04:19 +02:00
wxGetApp (). obj_list () -> update_selections ();
2018-12-17 15:44:30 +01:00
selection_changed ();
2018-10-16 16:04:19 +02:00
}
2018-10-09 17:14:59 +02:00
2019-09-20 09:57:27 +02:00
void Plater :: priv :: on_right_click ( RBtnEvent & evt )
2018-10-08 19:55:30 +02:00
{
2018-10-17 12:17:25 +02:00
int obj_idx = get_selected_object_idx ();
2019-09-20 09:57:27 +02:00
wxMenu * menu = nullptr ;
2019-12-11 16:07:00 +01:00
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
return ;
}
2019-09-20 09:57:27 +02:00
else
2019-03-28 16:28:34 +01:00
{
2019-09-20 09:57:27 +02:00
// If in 3DScene is(are) selected volume(s), but right button was clicked on empty space
if ( evt . data . second )
return ;
2019-12-11 16:07:00 +01:00
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 ;
}
2019-09-20 09:57:27 +02:00
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 ]);
}
2019-03-28 16:28:34 +01:00
}
2019-09-20 09:57:27 +02:00
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 ]);
}
2019-03-28 16:28:34 +01:00
}
2019-03-19 09:51:50 +01:00
}
}
2019-09-20 09:57:27 +02:00
if ( q != nullptr && menu ) {
2019-02-25 10:35:16 +01:00
#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
2019-09-20 09:57:27 +02:00
q -> PopupMenu ( menu , ( int ) evt . data . first . x (), ( int ) evt . data . first . y ());
2019-02-25 10:35:16 +01:00
#endif
}
2018-10-08 19:55:30 +02:00
}
2018-10-09 17:14:59 +02:00
void Plater :: priv :: on_wipetower_moved ( Vec3dEvent & evt )
2018-10-08 19:55:30 +02:00
{
2018-10-09 17:14:59 +02:00
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 13:17:14 +01:00
wxGetApp (). get_tab ( Preset :: TYPE_PRINT ) -> load_config ( cfg );
2018-10-08 19:55:30 +02:00
}
2019-04-26 15:34:26 +02:00
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 );
}
2018-10-08 19:55:30 +02:00
void Plater :: priv :: on_update_geometry ( Vec3dsEvent < 2 >& )
{
// TODO
}
2019-08-06 18:16:02 +02:00
// Update the scene from the background processing,
2018-11-23 12:47:32 +01:00
// 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 ();
}
}
2018-10-17 12:17:25 +02:00
bool Plater :: priv :: init_object_menu ()
{
2019-03-19 09:51:50 +01:00
items_increase . reserve ( 2 );
items_decrease . reserve ( 2 );
items_set_number_of_copies . reserve ( 2 );
2018-12-07 17:50:48 +01:00
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 ();
2019-09-20 09:57:27 +02:00
sidebar -> obj_list () -> create_default_popupmenu ( & default_menu );
2018-12-07 17:50:48 +01:00
return true ;
}
2019-10-23 13:31:24 +02:00
#if ENABLE_THUMBNAIL_GENERATOR
2019-11-28 14:18:24 +01:00
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 )
2019-10-23 13:31:24 +02:00
{
2019-11-28 14:18:24 +01:00
view3D -> get_canvas3d () -> render_thumbnail ( data , w , h , printable_only , parts_only , show_bed , transparent_background );
2019-10-23 13:31:24 +02:00
}
2019-11-22 12:39:03 +01:00
2019-11-28 14:18:24 +01:00
void Plater :: priv :: generate_thumbnails ( ThumbnailsList & thumbnails , const Vec2ds & sizes , bool printable_only , bool parts_only , bool show_bed , bool transparent_background )
2019-11-22 12:39:03 +01:00
{
thumbnails . clear ();
for ( const Vec2d & size : sizes )
{
thumbnails . push_back ( ThumbnailData ());
Point isize ( size ); // round to ints
2019-11-28 14:18:24 +01:00
generate_thumbnail ( thumbnails . back (), isize . x (), isize . y (), printable_only , parts_only , show_bed , transparent_background );
2019-11-22 12:39:03 +01:00
if ( ! thumbnails . back (). is_valid ())
thumbnails . pop_back ();
}
}
2019-10-23 13:31:24 +02:00
#endif // ENABLE_THUMBNAIL_GENERATOR
2019-05-03 09:44:19 +02:00
void Plater :: priv :: msw_rescale_object_menu ()
{
2019-09-20 09:57:27 +02:00
for ( MenuWithSeparators * menu : { & object_menu , & sla_object_menu , & part_menu , & default_menu })
2019-05-03 09:44:19 +02:00
msw_rescale_menu ( dynamic_cast < wxMenu *> ( menu ));
}
2019-05-15 14:09:16 +02:00
wxString Plater :: priv :: get_project_filename ( const wxString & extension ) const
2019-05-14 15:00:24 +02:00
{
2019-05-15 14:55:51 +02:00
return m_project_filename . empty () ? "" : m_project_filename + extension ;
2019-05-14 15:00:24 +02:00
}
void Plater :: priv :: set_project_filename ( const wxString & filename )
{
2019-05-15 14:09:16 +02:00
boost :: filesystem :: path full_path = into_path ( filename );
2019-05-17 16:27:00 +02:00
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.
2019-05-15 14:09:16 +02:00
full_path . replace_extension ( "" );
}
2019-05-15 10:23:02 +02:00
m_project_filename = from_path ( full_path );
2019-05-14 15:00:24 +02:00
wxGetApp (). mainframe -> update_title ();
2019-07-12 15:36:01 +02:00
if ( ! filename . empty ())
wxGetApp (). mainframe -> add_to_recent_projects ( filename );
2019-05-14 15:00:24 +02:00
}
2018-12-07 17:50:48 +01:00
bool Plater :: priv :: init_common_menu ( wxMenu * menu , const bool is_part /* = false*/ )
{
2019-03-08 17:00:56 +01:00
if ( is_part ) {
2019-05-13 14:27:51 +02:00
append_menu_item ( menu , wxID_ANY , _ ( L ( "Delete" )) + " \t Del" , _ ( L ( "Remove the selected object" )),
[ this ]( wxCommandEvent & ) { q -> remove_selected (); }, "delete" , nullptr , [ this ]() { return can_delete (); }, q );
2019-04-04 15:13:43 +02:00
2019-09-19 09:09:11 +02: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 15:13:43 +02:00
sidebar -> obj_list () -> append_menu_item_export_stl ( menu );
}
else {
2019-08-28 15:35:01 +02:00
wxMenuItem * item_increase = append_menu_item ( menu , wxID_ANY , _ ( L ( "Add instance" )) + " \t +" , _ ( L ( "Add one more instance of the selected object" )),
2019-05-13 14:27:51 +02:00
[ this ]( wxCommandEvent & ) { q -> increase_instances (); }, "add_copies" , nullptr , [ this ]() { return can_increase_instances (); }, q );
2019-08-28 15:35:01 +02:00
wxMenuItem * item_decrease = append_menu_item ( menu , wxID_ANY , _ ( L ( "Remove instance" )) + " \t -" , _ ( L ( "Remove one instance of the selected object" )),
2019-05-13 14:27:51 +02:00
[ this ]( wxCommandEvent & ) { q -> decrease_instances (); }, "remove_copies" , nullptr , [ this ]() { return can_decrease_instances (); }, q );
2019-08-28 15:35:01 +02:00
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" )),
2019-05-13 14:27:51 +02:00
[ this ]( wxCommandEvent & ) { q -> set_number_of_copies (); }, "number_of_copies" , nullptr , [ this ]() { return can_increase_instances (); }, q );
2019-03-19 09:51:50 +01:00
items_increase . push_back ( item_increase );
items_decrease . push_back ( item_decrease );
items_set_number_of_copies . push_back ( item_set_number_of_copies );
2019-03-08 17:00:56 +01:00
// Delete menu was moved to be after +/- instace to make it more difficult to be selected by mistake.
2019-05-13 14:27:51 +02:00
append_menu_item ( menu , wxID_ANY , _ ( L ( "Delete" )) + " \t Del" , _ ( L ( "Remove the selected object" )),
[ this ]( wxCommandEvent & ) { q -> remove_selected (); }, "delete" , nullptr , [ this ]() { return can_delete (); }, q );
2019-01-22 16:40:10 +01:00
menu -> AppendSeparator ();
2019-05-13 14:27:51 +02:00
sidebar -> obj_list () -> append_menu_item_instance_to_object ( menu , q );
2018-12-07 18:20:22 +01:00
menu -> AppendSeparator ();
2019-07-31 10:12:13 +02:00
wxMenuItem * menu_item_printable = sidebar -> obj_list () -> append_menu_item_printable ( menu , q );
menu -> AppendSeparator ();
2019-09-19 09:09:11 +02:00
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 18:20:22 +01:00
2019-04-05 11:30:49 +02:00
append_menu_item ( menu , wxID_ANY , _ ( L ( "Export as STL" )) + dots , _ ( L ( "Export the selected object as STL file" )),
2019-12-11 16:07:00 +01:00
[ 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 15:13:43 +02:00
menu -> AppendSeparator ();
2019-07-31 10:12:13 +02:00
q -> Bind ( wxEVT_UPDATE_UI , [ this ]( wxUpdateUIEvent & evt ) {
const Selection & selection = get_selection ();
int instance_idx = selection . get_instance_idx ();
2019-12-11 16:07:00 +01:00
evt . Enable ( selection . is_single_full_instance () || selection . is_single_full_object ());
2019-07-31 10:12:13 +02:00
if ( instance_idx != - 1 )
2019-07-31 11:01:50 +02:00
{
2019-07-31 10:12:13 +02:00
evt . Check ( model . objects [ selection . get_object_idx ()] -> instances [ instance_idx ] -> printable );
2019-07-31 11:01:50 +02:00
view3D -> set_as_dirty ();
}
2019-07-31 10:12:13 +02:00
}, menu_item_printable -> GetId ());
2018-12-07 17:50:48 +01:00
}
2018-10-17 14:26:13 +02:00
2019-05-13 14:27:51 +02:00
sidebar -> obj_list () -> append_menu_item_fix_through_netfabb ( menu );
2019-01-30 16:27:07 +01:00
2019-05-23 09:20:11 +02:00
sidebar -> obj_list () -> append_menu_item_scale_selection_to_fit_print_volume ( menu );
2019-05-22 14:42:38 +02:00
2018-10-18 14:42:21 +02:00
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 17:10:04 +02:00
[ this ]( wxCommandEvent & ) { mirror ( X ); }, "mark_X" , menu );
2018-10-18 14:42:21 +02:00
append_menu_item ( mirror_menu , wxID_ANY , _ ( L ( "Along Y axis" )), _ ( L ( "Mirror the selected object along the Y axis" )),
2019-04-12 17:10:04 +02:00
[ this ]( wxCommandEvent & ) { mirror ( Y ); }, "mark_Y" , menu );
2018-10-18 14:42:21 +02:00
append_menu_item ( mirror_menu , wxID_ANY , _ ( L ( "Along Z axis" )), _ ( L ( "Mirror the selected object along the Z axis" )),
2019-04-12 17:10:04 +02:00
[ this ]( wxCommandEvent & ) { mirror ( Z ); }, "mark_Z" , menu );
2018-10-18 14:42:21 +02:00
2019-05-13 14:27:51 +02:00
append_submenu ( menu , mirror_menu , wxID_ANY , _ ( L ( "Mirror" )), _ ( L ( "Mirror the selected object" )), "" ,
[ this ]() { return can_mirror (); }, q );
2018-12-07 17:50:48 +01:00
return true ;
}
bool Plater :: priv :: complit_init_object_menu ()
{
2018-10-24 12:55:38 +02:00
wxMenu * split_menu = new wxMenu ();
if ( split_menu == nullptr )
return false ;
2019-05-13 14:27:51 +02:00
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 12:55:38 +02:00
2019-08-06 18:16:02 +02:00
append_submenu ( & object_menu , split_menu , wxID_ANY , _ ( L ( "Split" )), _ ( L ( "Split the selected object" )), "" ,
2019-05-13 14:27:51 +02:00
[ this ]() { return can_split () && wxGetApp (). get_mode () > comSimple ; }, q );
2018-12-07 17:50:48 +01:00
object_menu . AppendSeparator ();
2018-11-22 16:04:21 +01:00
2019-05-27 13:07:37 +02:00
// Layers Editing for object
2019-12-11 16:07:00 +01:00
sidebar -> obj_list () -> append_menu_item_layers_editing ( & object_menu , q );
2019-05-27 13:07:37 +02:00
object_menu . AppendSeparator ();
2019-01-25 10:34:32 +01:00
// "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume()
2018-11-22 16:04:21 +01:00
2018-12-07 17:50:48 +01:00
return true ;
}
bool Plater :: priv :: complit_init_sla_object_menu ()
{
2019-05-13 14:27:51 +02:00
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 );
2018-12-07 17:50:48 +01:00
2019-01-25 10:34:32 +01:00
sla_object_menu . AppendSeparator ();
2018-12-07 17:50:48 +01:00
// 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 & ) { sla_optimize_rotation (); });
return true ;
}
bool Plater :: priv :: complit_init_part_menu ()
{
2019-05-13 14:27:51 +02:00
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 );
2018-12-07 17:50:48 +01:00
2019-01-25 10:34:32 +01:00
part_menu . AppendSeparator ();
2018-12-07 17:50:48 +01:00
auto obj_list = sidebar -> obj_list ();
obj_list -> append_menu_item_change_type ( & part_menu );
2018-10-17 12:17:25 +02:00
return true ;
}
2018-10-08 19:55:30 +02:00
2019-12-10 10:56:21 +01:00
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
bool Plater :: priv :: init_view_toolbar ()
#else
2018-12-06 10:38:19 +01:00
void Plater :: priv :: init_view_toolbar ()
2019-12-10 10:56:21 +01:00
#endif //!ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
2018-12-06 10:38:19 +01:00
{
2019-12-10 10:56:21 +01:00
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
if ( view_toolbar . get_items_count () > 0 )
// already initialized
return true ;
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
2018-12-17 10:55:14 +01: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 ;
2019-02-26 09:56:23 +01:00
if ( ! view_toolbar . init ( background_data ))
2019-12-10 10:56:21 +01:00
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
return false ;
#else
2018-12-06 10:38:19 +01:00
return ;
2019-12-10 10:56:21 +01:00
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
2018-12-06 10:38:19 +01:00
2019-07-20 12:02:29 +02:00
view_toolbar . set_horizontal_orientation ( GLToolbar :: Layout :: HO_Left );
view_toolbar . set_vertical_orientation ( GLToolbar :: Layout :: VO_Bottom );
2018-12-17 10:55:14 +01:00
view_toolbar . set_border ( 5.0f );
view_toolbar . set_gap_size ( 1.0f );
GLToolbarItem :: Data item ;
item . name = "3D" ;
2019-03-04 14:21:52 +01:00
item . icon_filename = "editor.svg" ;
2019-05-09 17:18:03 +02:00
item . tooltip = _utf8 ( L ( "3D editor view" )) + " [" + GUI :: shortkey_ctrl_prefix () + "5]" ;
2018-12-17 10:55:14 +01:00
item . sprite_id = 0 ;
2019-07-12 09:26:19 +02:00
item . left . action_callback = [ this ]() { if ( this -> q != nullptr ) wxPostEvent ( this -> q , SimpleEvent ( EVT_GLVIEWTOOLBAR_3D )); };
2018-12-17 10:55:14 +01:00
if ( ! view_toolbar . add_item ( item ))
2019-12-10 10:56:21 +01:00
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
return false ;
#else
2018-12-17 10:55:14 +01:00
return ;
2019-12-10 10:56:21 +01:00
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
2018-12-17 10:55:14 +01:00
item . name = "Preview" ;
2019-03-04 14:21:52 +01:00
item . icon_filename = "preview.svg" ;
2019-05-09 17:18:03 +02:00
item . tooltip = _utf8 ( L ( "Preview" )) + " [" + GUI :: shortkey_ctrl_prefix () + "6]" ;
2018-12-17 10:55:14 +01:00
item . sprite_id = 1 ;
2019-07-12 09:26:19 +02:00
item . left . action_callback = [ this ]() { if ( this -> q != nullptr ) wxPostEvent ( this -> q , SimpleEvent ( EVT_GLVIEWTOOLBAR_PREVIEW )); };
2018-12-17 10:55:14 +01:00
if ( ! view_toolbar . add_item ( item ))
2019-12-10 10:56:21 +01:00
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
return false ;
#else
2018-12-17 10:55:14 +01:00
return ;
2019-12-10 10:56:21 +01:00
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
2018-12-17 10:55:14 +01:00
view_toolbar . select_item ( "3D" );
view_toolbar . set_enabled ( true );
2019-12-10 10:56:21 +01:00
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
return true ;
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
2018-12-06 10:38:19 +01:00
}
2019-01-22 16:40:10 +01:00
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 );
}
2018-12-17 15:44:30 +01:00
bool Plater :: priv :: can_split () const
{
return sidebar -> obj_list () -> is_splittable ();
}
2018-10-18 09:27:37 +02:00
bool Plater :: priv :: layers_height_allowed () const
{
2019-04-05 09:51:58 +02:00
if ( printer_technology != ptFFF )
return false ;
2018-10-22 15:18:56 +02:00
int obj_idx = get_selected_object_idx ();
2018-12-04 13:55:25 +01:00
return ( 0 <= obj_idx ) && ( obj_idx < ( int ) model . objects . size ()) && config -> opt_bool ( "variable_layer_height" ) && view3D -> is_layers_editing_allowed ();
2018-10-18 09:27:37 +02:00
}
2018-10-18 15:09:41 +02:00
bool Plater :: priv :: can_mirror () const
{
return get_selection (). is_from_single_instance ();
}
2018-10-08 19:55:30 +02:00
2019-09-19 09:09:11 +02:00
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 ();
2019-10-10 14:44:52 +02:00
if ( v_idx >= 0 )
selected_volumes . push_back ({ v -> object_idx (), v_idx });
2019-09-19 09:09:11 +02:00
}
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 ModelVolume * volume = model . objects [ v . object_idx ] -> volumes [ v . volume_idx ];
2019-12-10 14:42:28 +01:00
#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
if ( ! volume -> source . input_file . empty ())
#else
2019-09-19 09:09:11 +02:00
if ( ! volume -> source . input_file . empty () && boost :: filesystem :: exists ( volume -> source . input_file ))
2019-12-10 14:42:28 +01:00
#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
2019-09-19 09:09:11 +02:00
paths . push_back ( volume -> source . input_file );
}
std :: sort ( paths . begin (), paths . end ());
paths . erase ( std :: unique ( paths . begin (), paths . end ()), paths . end ());
return ! paths . empty ();
}
2019-07-24 14:02:36 +02:00
void Plater :: priv :: set_bed_shape ( const Pointfs & shape , const std :: string & custom_texture , const std :: string & custom_model )
2019-02-19 15:15:27 +01:00
{
2019-07-24 14:02:36 +02:00
bool new_shape = bed . set_shape ( shape , custom_texture , custom_model );
2019-02-19 15:15:27 +01:00
if ( new_shape )
{
if ( view3D ) view3D -> bed_shape_changed ();
if ( preview ) preview -> bed_shape_changed ();
}
}
2019-03-14 13:54:05 +01:00
bool Plater :: priv :: can_delete () const
{
2019-06-28 17:03:50 +02:00
return ! get_selection (). is_empty () && ! get_selection (). is_wipe_tower () && ! m_ui_jobs . is_any_running ();
2019-03-14 13:54:05 +01:00
}
bool Plater :: priv :: can_delete_all () const
{
return ! model . objects . empty ();
}
2019-04-24 16:04:47 +02:00
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 ;
}
2019-03-14 13:54:05 +01:00
bool Plater :: priv :: can_increase_instances () const
{
2019-06-18 16:24:30 +02:00
if ( m_ui_jobs . is_any_running ()) {
2019-04-17 14:56:00 +02:00
return false ;
}
2019-03-14 13:54:05 +01:00
int obj_idx = get_selected_object_idx ();
return ( 0 <= obj_idx ) && ( obj_idx < ( int ) model . objects . size ());
}
bool Plater :: priv :: can_decrease_instances () const
{
2019-06-18 16:24:30 +02:00
if ( m_ui_jobs . is_any_running ()) {
2019-04-17 14:56:00 +02:00
return false ;
}
2019-03-14 13:54:05 +01: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
{
2019-06-18 16:24:30 +02:00
return ! model . objects . empty () && ! m_ui_jobs . is_any_running ();
2019-03-14 13:54:05 +01:00
}
bool Plater :: priv :: can_layers_editing () const
{
return layers_height_allowed ();
}
2018-12-07 17:50:48 +01:00
void Plater :: priv :: update_object_menu ()
{
2019-01-25 13:16:32 +01:00
sidebar -> obj_list () -> append_menu_items_add_volume ( & object_menu );
2018-12-07 17:50:48 +01:00
}
2019-08-06 18:16:02 +02:00
void Plater :: priv :: show_action_buttons ( const bool is_ready_to_slice ) const
2019-03-01 11:00:34 +01:00
{
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 17:31:27 +01:00
2019-12-11 12:28:51 +01:00
const bool disconnect_shown = ! ( RemovableDriveManager :: get_instance (). is_last_drive_removed ()); // #dk_FIXME
2019-03-01 11:00:34 +01:00
2019-08-06 18:16:02 +02:00
// when a background processing is ON, export_btn and/or send_btn are showing
2019-03-01 11:00:34 +01:00
if ( wxGetApp (). app_config -> get ( "background_processing" ) == "1" )
{
2019-04-24 19:03:05 +02:00
if ( sidebar -> show_reslice ( false ) |
2019-08-06 18:16:02 +02:00
sidebar -> show_export ( true ) |
2019-12-10 17:31:27 +01:00
sidebar -> show_send ( send_gcode_shown ) |
sidebar -> show_disconnect ( disconnect_shown ))
2019-08-06 18:16:02 +02:00
sidebar -> Layout ();
}
2019-03-01 11:00:34 +01:00
else
{
2019-08-06 18:16:02 +02:00
if ( sidebar -> show_reslice ( is_ready_to_slice ) |
sidebar -> show_export ( ! is_ready_to_slice ) |
2019-12-10 17:31:27 +01:00
sidebar -> show_send ( send_gcode_shown && ! is_ready_to_slice ) |
sidebar -> show_disconnect ( disconnect_shown && ! is_ready_to_slice ))
2019-08-06 18:16:02 +02:00
sidebar -> Layout ();
}
2019-03-01 11:00:34 +01:00
}
2019-07-26 13:44:33 +02:00
void Plater :: priv :: enter_gizmos_stack ()
{
2019-08-06 18:16:02 +02: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" ));
}
2019-07-26 13:44:33 +02:00
}
void Plater :: priv :: leave_gizmos_stack ()
{
2019-08-06 18:16:02 +02: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-26 13:44:33 +02:00
}
2019-07-09 20:45:00 +02:00
int Plater :: priv :: get_active_snapshot_index ()
{
2019-07-26 13:44:33 +02:00
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 20:45:00 +02:00
const auto it = std :: lower_bound ( ss_stack . begin (), ss_stack . end (), UndoRedo :: Snapshot ( active_snapshot_time ));
return it - ss_stack . begin ();
}
2019-07-19 10:29:06 +02:00
void Plater :: priv :: take_snapshot ( const std :: string & snapshot_name )
{
2019-08-06 18:16:02 +02:00
if ( this -> m_prevent_snapshots > 0 )
2019-07-19 10:29:06 +02:00
return ;
assert ( this -> m_prevent_snapshots >= 0 );
2019-07-22 16:52:47 +02:00
UndoRedo :: SnapshotData snapshot_data ;
snapshot_data . printer_technology = this -> printer_technology ;
2019-07-19 10:29:06 +02:00
if ( this -> view3D -> is_layers_editing_enabled ())
2019-08-06 18:16:02 +02:00
snapshot_data . flags |= UndoRedo :: SnapshotData :: VARIABLE_LAYER_EDITING_ACTIVE ;
2019-07-23 18:17:57 +02:00
if ( this -> sidebar -> obj_list () -> is_selected ( itSettings )) {
snapshot_data . flags |= UndoRedo :: SnapshotData :: SELECTED_SETTINGS_ON_SIDEBAR ;
2019-07-24 12:32:38 +02:00
snapshot_data . layer_range_idx = this -> sidebar -> obj_list () -> get_selected_layers_range_idx ();
2019-07-23 18:17:57 +02:00
}
else if ( this -> sidebar -> obj_list () -> is_selected ( itLayer )) {
snapshot_data . flags |= UndoRedo :: SnapshotData :: SELECTED_LAYER_ON_SIDEBAR ;
2019-07-24 12:32:38 +02:00
snapshot_data . layer_range_idx = this -> sidebar -> obj_list () -> get_selected_layers_range_idx ();
2019-07-23 18:17:57 +02:00
}
else if ( this -> sidebar -> obj_list () -> is_selected ( itLayerRoot ))
snapshot_data . flags |= UndoRedo :: SnapshotData :: SELECTED_LAYERROOT_ON_SIDEBAR ;
2019-08-22 16:44:49 +02:00
// 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 ;
2019-07-19 15:29:04 +02:00
//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 ) {
2019-07-19 15:47:10 +02:00
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" );
}
2019-07-26 13:44:33 +02:00
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 ();
2019-07-19 10:29:06 +02:00
// 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 18:16:02 +02: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 ();
2019-07-19 10:29:06 +02:00
}
2019-07-04 10:45:41 +02:00
void Plater :: priv :: undo ()
{
2019-08-06 18:16:02 +02: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 );
2019-07-04 10:45:41 +02:00
}
void Plater :: priv :: redo ()
2019-08-06 18:16:02 +02: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 );
2019-07-04 14:35:04 +02:00
}
2019-07-18 17:41:47 +02:00
void Plater :: priv :: undo_redo_to ( size_t time_to_load )
2019-07-09 20:45:00 +02:00
{
2019-08-06 18:16:02 +02: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 20:45:00 +02:00
}
2019-07-18 17:41:47 +02:00
void Plater :: priv :: undo_redo_to ( std :: vector < UndoRedo :: Snapshot >:: const_iterator it_snapshot )
{
2019-08-23 01:36:56 +02:00
// Make sure that no updating function calls take_snapshot until we are done.
SuppressSnapshots snapshot_supressor ( q );
2019-08-06 18:16:02 +02: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 ( from_u8 (( boost :: format ( _utf8 (
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 ). str ())))
// Don't switch the profiles.
return ;
}
2019-07-18 17:41:47 +02:00
// 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-07-19 15:29:04 +02:00
//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 ) {
2019-07-19 15:47:10 +02:00
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" );
}
2019-07-25 17:24:00 +02:00
const int layer_range_idx = it_snapshot -> snapshot_data . layer_range_idx ;
2019-07-19 10:29:06 +02:00
// Flags made of Snapshot::Flags enum values.
2019-07-22 16:52:47 +02:00
unsigned int new_flags = it_snapshot -> snapshot_data . flags ;
2019-08-06 18:16:02 +02:00
UndoRedo :: SnapshotData top_snapshot_data ;
2019-07-22 16:52:47 +02:00
top_snapshot_data . printer_technology = this -> printer_technology ;
2019-07-19 10:29:06 +02:00
if ( this -> view3D -> is_layers_editing_enabled ())
2019-08-06 18:16:02 +02:00
top_snapshot_data . flags |= UndoRedo :: SnapshotData :: VARIABLE_LAYER_EDITING_ACTIVE ;
2019-07-23 18:17:57 +02:00
if ( this -> sidebar -> obj_list () -> is_selected ( itSettings )) {
2019-08-06 18:16:02 +02:00
top_snapshot_data . flags |= UndoRedo :: SnapshotData :: SELECTED_SETTINGS_ON_SIDEBAR ;
2019-07-24 12:32:38 +02:00
top_snapshot_data . layer_range_idx = this -> sidebar -> obj_list () -> get_selected_layers_range_idx ();
2019-07-23 18:17:57 +02:00
}
else if ( this -> sidebar -> obj_list () -> is_selected ( itLayer )) {
top_snapshot_data . flags |= UndoRedo :: SnapshotData :: SELECTED_LAYER_ON_SIDEBAR ;
2019-07-24 12:32:38 +02:00
top_snapshot_data . layer_range_idx = this -> sidebar -> obj_list () -> get_selected_layers_range_idx ();
2019-07-23 18:17:57 +02:00
}
else if ( this -> sidebar -> obj_list () -> is_selected ( itLayerRoot ))
top_snapshot_data . flags |= UndoRedo :: SnapshotData :: SELECTED_LAYERROOT_ON_SIDEBAR ;
2019-08-06 18:16:02 +02:00
bool new_variable_layer_editing_active = ( new_flags & UndoRedo :: SnapshotData :: VARIABLE_LAYER_EDITING_ACTIVE ) != 0 ;
2019-07-23 18:17:57 +02:00
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 ;
2019-08-22 16:44:49 +02:00
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 18:16:02 +02:00
// Disable layer editing before the Undo / Redo jump.
2019-07-19 15:36:55 +02:00
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" ));
2019-11-27 11:08:10 +01:00
2019-08-22 16:44:49 +02:00
// Make a copy of the snapshot, undo/redo could invalidate the iterator
const UndoRedo :: Snapshot snapshot_copy = * it_snapshot ;
2019-07-18 17:41:47 +02:00
// Do the jump in time.
2019-07-26 13:44:33 +02:00
if ( it_snapshot -> timestamp < this -> undo_redo_stack (). active_snapshot_time () ?
2019-08-06 18:16:02 +02: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 );
2019-08-27 12:15:29 +02:00
// 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 18:16:02 +02: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 ();
2019-07-18 17:41:47 +02:00
}
2019-07-19 15:47:10 +02:00
//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 ();
}
}
2019-07-23 18:17:57 +02:00
// 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 18:16:02 +02:00
new_selected_layerroot_on_sidebar ? ObjectList :: SELECTION_MODE :: smLayerRoot :
2019-07-23 18:17:57 +02:00
ObjectList :: SELECTION_MODE :: smUndef );
if ( new_selected_settings_on_sidebar || new_selected_layer_on_sidebar )
2019-07-25 17:24:00 +02:00
this -> sidebar -> obj_list () -> set_selected_layers_range_idx ( layer_range_idx );
2019-07-23 18:17:57 +02:00
2019-08-22 16:44:49 +02:00
this -> update_after_undo_redo ( snapshot_copy , temp_snapshot_was_taken );
2019-08-06 18:16:02 +02:00
// Enable layer editing after the Undo / Redo jump.
if ( ! view3D -> is_layers_editing_enabled () && this -> layers_height_allowed () && new_variable_layer_editing_active )
2019-07-19 15:36:55 +02:00
view3D -> get_canvas3d () -> force_main_toolbar_left_action ( view3D -> get_canvas3d () -> get_main_toolbar_item_id ( "layersediting" ));
}
2019-07-09 20:45:00 +02:00
}
2019-08-22 16:44:49 +02:00
void Plater :: priv :: update_after_undo_redo ( const UndoRedo :: Snapshot & snapshot , bool /* temp_snapshot_was_taken */ )
2019-07-04 14:35:04 +02:00
{
2019-08-06 18:16:02 +02: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).
2019-08-23 15:53:45 +02:00
this -> update (( unsigned int ) UpdateParams :: FORCE_BACKGROUND_PROCESSING_UPDATE | ( unsigned int ) UpdateParams :: POSTPONE_VALIDATION_ERROR_MESSAGE );
2019-08-06 18:16:02 +02: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)
2019-07-26 13:44:33 +02:00
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 );
2019-08-22 16:44:49 +02:00
this -> view3D -> get_canvas3d () -> get_gizmos_manager (). update_after_undo_redo ( snapshot );
2019-07-04 17:33:19 +02:00
2019-07-11 18:42:02 +02:00
wxGetApp (). obj_list () -> update_after_undo_redo ();
2019-07-18 17:41:47 +02:00
if ( wxGetApp (). get_mode () == comSimple && model_has_advanced_features ( this -> model )) {
2019-08-06 18:16:02 +02: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.
2019-07-18 17:41:47 +02:00
Slic3r :: GUI :: wxGetApp (). save_mode ( comAdvanced );
view3D -> set_as_dirty ();
}
2019-08-23 15:53:45 +02:00
// 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 18:16:02 +02:00
//FIXME what about the state of the manipulators?
//FIXME what about the focus? Cursor in the side panel?
2019-07-17 15:48:53 +02:00
2019-07-26 13:44:33 +02:00
BOOST_LOG_TRIVIAL ( info ) << "Undo / Redo snapshot reloaded. Undo / Redo stack memory: " << Slic3r :: format_memsize_MB ( this -> undo_redo_stack (). memsize ()) << log_memory_info ();
2019-07-04 10:45:41 +02:00
}
2019-03-01 11:00:34 +01:00
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 17:31:27 +01:00
case ActionButtonType :: abSendGCode : /*p->btn_send_gcode->SetLabelText(label);*/ break ;
2019-03-01 11:00:34 +01:00
}
}
2018-09-17 12:15:11 +02:00
// 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 ()
{
}
2018-12-03 13:14:28 +01:00
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 12:15:11 +02:00
2019-05-14 10:30:14 +02:00
void Plater :: new_project ()
{
2019-06-18 14:45:10 +02:00
p -> select_view_3D ( "3D" );
2019-05-14 15:00:24 +02:00
wxPostEvent ( p -> view3D -> get_wxglcanvas (), SimpleEvent ( EVT_GLTOOLBAR_DELETE_ALL ));
2019-05-14 10:30:14 +02:00
}
2018-11-15 15:27:39 +01:00
void Plater :: load_project ()
{
2019-07-17 15:48:53 +02:00
// Ask user for a project file name.
2018-11-15 15:27:39 +01:00
wxString input_file ;
wxGetApp (). load_project ( this , input_file );
2019-07-17 15:48:53 +02:00
// And finally load the new project.
2019-07-12 15:36:01 +02:00
load_project ( input_file );
}
2018-11-15 15:27:39 +01:00
2019-07-12 15:36:01 +02:00
void Plater :: load_project ( const wxString & filename )
{
if ( filename . empty ())
2018-11-15 15:27:39 +01:00
return ;
2019-07-23 12:57:58 +02:00
// Take the Undo / Redo snapshot.
Plater :: TakeSnapshot snapshot ( this , _ ( L ( "Load Project" )) + ": " + wxString :: FromUTF8 ( into_path ( filename ). stem (). string (). c_str ()));
2018-11-15 15:27:39 +01:00
p -> reset ();
std :: vector < fs :: path > input_paths ;
2019-07-12 15:36:01 +02:00
input_paths . push_back ( into_path ( filename ));
2019-09-02 15:15:28 +02:00
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 );
2018-11-15 15:27:39 +01:00
}
void Plater :: add_model ()
2018-10-17 12:17:25 +02:00
{
wxArrayString input_files ;
2018-11-15 15:27:39 +01:00
wxGetApp (). import_model ( this , input_files );
if ( input_files . empty ())
return ;
2018-10-17 12:17:25 +02:00
2019-07-17 15:48:53 +02:00
std :: vector < fs :: path > paths ;
for ( const auto & file : input_files )
paths . push_back ( into_path ( file ));
2019-07-04 17:33:19 +02:00
2019-08-06 18:16:02 +02:00
wxString snapshot_label ;
assert ( ! paths . empty ());
if ( paths . size () == 1 ) {
snapshot_label = _ ( L ( "Import Object" ));
snapshot_label += ": " ;
snapshot_label += wxString :: FromUTF8 ( paths . front (). filename (). string (). c_str ());
} else {
snapshot_label = _ ( L ( "Import Objects" ));
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 ());
}
}
2019-07-17 15:48:53 +02:00
2019-08-06 18:16:02 +02:00
Plater :: TakeSnapshot snapshot ( this , snapshot_label );
2019-07-17 15:48:53 +02:00
load_files ( paths , true , false );
2018-10-17 12:17:25 +02:00
}
2018-11-16 09:26:41 +01:00
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 15:11:05 +01:00
input_paths . push_back ( into_path ( input_file ));
2018-11-16 09:26:41 +01:00
load_files ( input_paths , false , true );
}
2019-09-02 15:15:28 +02:00
std :: vector < size_t > Plater :: load_files ( const std :: vector < fs :: path >& input_files , bool load_model , bool load_config ) { return p -> load_files ( input_files , load_model , load_config ); }
2018-09-17 12:15:11 +02:00
2019-01-09 10:43:17 +01:00
// To be called when providing a list of files to the GUI slic3r on command line.
2019-09-02 15:15:28 +02:00
std :: vector < size_t > Plater :: load_files ( const std :: vector < std :: string >& input_files , bool load_model , bool load_config )
2019-08-06 18:16:02 +02:00
{
2019-01-09 10:43:17 +01:00
std :: vector < fs :: path > paths ;
paths . reserve ( input_files . size ());
2019-09-02 15:15:28 +02:00
for ( const std :: string & path : input_files )
2019-01-09 10:43:17 +01:00
paths . emplace_back ( path );
2019-09-02 15:15:28 +02:00
return p -> load_files ( paths , load_model , load_config );
2019-01-09 10:43:17 +01:00
}
2018-11-21 13:32:24 +01:00
void Plater :: update () { p -> update (); }
2018-10-17 12:59:58 +02:00
2019-06-18 16:24:30 +02:00
void Plater :: stop_jobs () { p -> m_ui_jobs . stop_all (); }
2019-05-13 18:58:56 +02:00
2018-12-04 11:14:39 +01:00
void Plater :: update_ui_from_settings () { p -> update_ui_from_settings (); }
2018-10-17 12:59:58 +02:00
void Plater :: select_view ( const std :: string & direction ) { p -> select_view ( direction ); }
2018-12-04 13:55:25 +01:00
void Plater :: select_view_3D ( const std :: string & name ) { p -> select_view_3D ( name ); }
2019-08-20 09:01:09 +02:00
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 (); }
2018-11-21 15:28:35 +01:00
void Plater :: select_all () { p -> select_all (); }
2019-05-14 11:57:39 +02:00
void Plater :: deselect_all () { p -> deselect_all (); }
2018-11-21 15:28:35 +01:00
2018-10-08 19:14:55 +02:00
void Plater :: remove ( size_t obj_idx ) { p -> remove ( obj_idx ); }
2018-11-22 11:31:53 +01:00
void Plater :: reset () { p -> reset (); }
2019-05-02 13:52:13 +02:00
void Plater :: reset_with_confirm ()
{
2019-08-06 18:16:02 +02:00
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 )
2019-05-02 13:52:13 +02:00
reset ();
}
2018-11-22 11:31:53 +01:00
2018-11-13 14:17:35 +01:00
void Plater :: delete_object_from_model ( size_t obj_idx ) { p -> delete_object_from_model ( obj_idx ); }
2018-10-04 11:12:55 +02:00
void Plater :: remove_selected ()
{
2019-08-06 18:16:02 +02:00
Plater :: TakeSnapshot snapshot ( this , _ ( L ( "Delete Selected Objects" )));
2018-12-04 13:55:25 +01:00
this -> p -> view3D -> delete_selected ();
2018-10-04 11:12:55 +02:00
}
2018-10-18 15:09:41 +02:00
void Plater :: increase_instances ( size_t num )
2018-10-11 10:03:38 +02:00
{
2019-04-17 14:56:00 +02:00
if ( ! can_increase_instances ()) { return ; }
2019-08-06 18:16:02 +02:00
Plater :: TakeSnapshot snapshot ( this , _ ( L ( "Increase Instances" )));
2019-07-02 16:42:23 +02:00
2018-10-17 09:30:07 +02:00
int obj_idx = p -> get_selected_object_idx ();
ModelObject * model_object = p -> model . objects [ obj_idx ];
ModelInstance * model_instance = model_object -> instances . back ();
2018-10-11 10:03:38 +02:00
2018-11-07 13:40:24 +01:00
bool was_one_instance = model_object -> instances . size () == 1 ;
2019-08-06 18:16:02 +02:00
2019-04-12 15:31:33 +02: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 ) {
2018-10-11 10:03:38 +02:00
Vec3d offset_vec = model_instance -> get_offset () + Vec3d ( offset , offset , 0.0 );
2019-01-03 15:04:29 +01:00
model_object -> add_instance ( offset_vec , model_instance -> get_scaling_factor (), model_instance -> get_rotation (), model_instance -> get_mirror ());
2018-10-23 15:27:31 +02:00
// p->print.get_object(obj_idx)->add_copy(Slic3r::to_2d(offset_vec));
2018-10-11 10:03:38 +02:00
}
2018-11-07 13:40:24 +01:00
sidebar (). obj_list () -> increase_object_instances ( obj_idx , was_one_instance ? num + 1 : num );
2018-10-11 10:03:38 +02:00
2019-09-24 09:11:27 +02:00
if ( p -> get_config ( "autocenter" ) == "1" )
2018-10-11 10:03:38 +02:00
p -> arrange ();
2019-09-24 09:11:27 +02:00
p -> update ();
2018-10-11 10:03:38 +02:00
2018-10-23 09:19:41 +02:00
p -> get_selection (). add_instance ( obj_idx , ( int ) model_object -> instances . size () - 1 );
2018-10-11 10:03:38 +02:00
p -> selection_changed ();
2018-10-17 11:12:38 +02:00
this -> p -> schedule_background_process ();
2018-10-11 10:03:38 +02:00
}
2018-10-18 15:09:41 +02:00
void Plater :: decrease_instances ( size_t num )
2018-10-11 10:03:38 +02:00
{
2019-04-17 14:56:00 +02:00
if ( ! can_decrease_instances ()) { return ; }
2019-08-06 18:16:02 +02:00
Plater :: TakeSnapshot snapshot ( this , _ ( L ( "Decrease Instances" )));
2019-07-02 16:42:23 +02:00
2018-10-17 09:30:07 +02:00
int obj_idx = p -> get_selected_object_idx ();
ModelObject * model_object = p -> model . objects [ obj_idx ];
2018-10-11 10:03:38 +02:00
if ( model_object -> instances . size () > num ) {
2018-11-08 14:23:17 +01:00
for ( size_t i = 0 ; i < num ; ++ i )
2018-10-11 10:03:38 +02:00
model_object -> delete_last_instance ();
2019-05-14 16:37:32 +02:00
p -> update ();
// Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model.
2018-10-18 11:41:29 +02:00
sidebar (). obj_list () -> decrease_object_instances ( obj_idx , num );
2018-10-17 09:30:07 +02:00
}
else {
remove ( obj_idx );
2018-10-11 10:03:38 +02:00
}
2018-10-23 09:19:41 +02:00
if ( ! model_object -> instances . empty ())
p -> get_selection (). add_instance ( obj_idx , ( int ) model_object -> instances . size () - 1 );
2018-10-17 09:40:04 +02:00
p -> selection_changed ();
2018-10-23 15:27:31 +02:00
this -> p -> schedule_background_process ();
2018-10-11 10:03:38 +02:00
}
2018-10-25 21:13:45 +02:00
void Plater :: set_number_of_copies ( /*size_t num*/ )
2018-10-11 10:03:38 +02:00
{
2018-10-17 09:30:07 +02:00
int obj_idx = p -> get_selected_object_idx ();
if ( obj_idx == - 1 )
return ;
ModelObject * model_object = p -> model . objects [ obj_idx ];
2018-10-11 10:03:38 +02:00
2019-09-20 16:45:43 +02:00
const int num = wxGetNumberFromUser ( " " , _ ( "Enter the number of copies:" ),
2018-10-25 21:13:45 +02:00
_ ( "Copies of the selected object" ), model_object -> instances . size (), 0 , 1000 , this );
if ( num < 0 )
return ;
2019-08-06 18:16:02 +02:00
Plater :: TakeSnapshot snapshot ( this , wxString :: Format ( _ ( L ( "Set numbers of copies to %d" )), num ));
2019-07-04 17:33:19 +02:00
2019-09-20 16:45:43 +02:00
int diff = num - ( int ) model_object -> instances . size ();
2018-10-18 15:09:41 +02:00
if ( diff > 0 )
increase_instances ( diff );
else if ( diff < 0 )
decrease_instances ( - diff );
2018-10-11 10:03:38 +02:00
}
2018-10-04 11:12:55 +02:00
2018-11-21 15:47:41 +01:00
bool Plater :: is_selection_empty () const
{
2019-04-10 12:16:17 +02:00
return p -> get_selection (). is_empty () || p -> get_selection (). is_wipe_tower ();
2018-11-21 15:47:41 +01:00
}
2019-05-22 14:42:38 +02:00
void Plater :: scale_selection_to_fit_print_volume ()
{
p -> scale_selection_to_fit_print_volume ();
}
2018-11-26 10:56:07 +01: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 15:13:38 +02: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" );
2019-01-18 15:02:07 +01:00
if ( ! keep_upper && ! keep_lower ) {
return ;
}
2019-08-06 18:16:02 +02:00
Plater :: TakeSnapshot snapshot ( this , _ ( L ( "Cut by Plane" )));
2019-07-04 17:33:19 +02:00
2019-02-05 20:23:24 +01:00
wxBusyCursor wait ;
2018-11-26 10:56:07 +01:00
const auto new_objects = object -> cut ( instance_idx , z , keep_upper , keep_lower , rotate_lower );
2018-10-18 15:13:38 +02:00
remove ( obj_idx );
p -> load_model_objects ( new_objects );
}
2019-02-03 10:41:14 +01:00
void Plater :: export_gcode ()
2018-10-04 11:12:55 +02:00
{
2018-10-23 15:27:31 +02:00
if ( p -> model . objects . empty ())
return ;
2018-09-17 12:15:11 +02:00
2019-02-03 10:41:14 +01: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 18:16:02 +02: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 ;
2019-05-17 16:27:00 +02:00
default_output_file = this -> p -> background_process . output_filepath_for_project ( into_path ( get_project_filename ( ".3mf" )));
2019-05-15 14:09:16 +02:00
}
catch ( const std :: exception & ex ) {
2019-02-03 10:41:14 +01:00
show_error ( this , ex . what ());
return ;
2018-09-17 12:15:11 +02:00
}
2019-02-03 10:41:14 +01:00
default_output_file = fs :: path ( Slic3r :: fold_utf8_to_ascii ( default_output_file . string ()));
auto start_dir = wxGetApp (). app_config -> get_last_output_dir ( default_output_file . parent_path (). string ());
2019-11-28 13:50:58 +01:00
if ( GUI :: RemovableDriveManager :: get_instance (). update ())
2019-11-27 13:30:45 +01:00
{
2019-11-28 13:50:58 +01:00
if ( ! RemovableDriveManager :: get_instance (). is_path_on_removable_drive ( start_dir ))
2019-11-27 13:30:45 +01:00
{
2019-12-11 14:53:28 +01:00
start_dir = RemovableDriveManager :: get_instance (). get_drive_path ();
2019-11-27 13:30:45 +01:00
}
}
2019-03-01 10:09:20 +01:00
wxFileDialog dlg ( this , ( printer_technology () == ptFFF ) ? _ ( L ( "Save G-code file as:" )) : _ ( L ( "Save SL1 file as:" )),
2019-02-03 10:41:14 +01:00
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
);
fs :: path output_path ;
if ( dlg . ShowModal () == wxID_OK ) {
fs :: path path = into_path ( dlg . GetPath ());
wxGetApp (). app_config -> update_last_output_dir ( path . parent_path (). string ());
output_path = std :: move ( path );
2019-12-11 15:02:20 +01:00
RemovableDriveManager :: get_instance (). update ( 0 , true );
RemovableDriveManager :: get_instance (). set_last_save_path ( output_path . string ());
2019-02-03 10:41:14 +01:00
}
if ( ! output_path . empty ())
2018-12-11 10:33:11 +01:00
p -> export_gcode ( std :: move ( output_path ), PrintHostJob ());
2019-12-05 14:07:02 +01:00
2018-09-17 12:15:11 +02:00
}
2019-05-02 13:46:39 +02:00
void Plater :: export_stl ( bool extended , bool selection_only )
2018-10-04 11:12:55 +02:00
{
2018-10-17 09:30:07 +02:00
if ( p -> model . objects . empty ()) { return ; }
2018-10-04 11:12:55 +02:00
2019-04-04 11:31:26 +02:00
wxString path = p -> get_export_file ( FT_STL );
if ( path . empty ()) { return ; }
2019-01-02 15:11:05 +01:00
const std :: string path_u8 = into_u8 ( path );
2018-12-05 15:22:03 +01:00
2019-02-22 10:11:57 +01:00
wxBusyCursor wait ;
2018-12-05 15:22:03 +01:00
TriangleMesh mesh ;
if ( selection_only ) {
const auto & selection = p -> get_selection ();
if ( selection . is_wipe_tower ()) { return ; }
const auto obj_idx = selection . get_object_idx ();
if ( obj_idx == - 1 ) { return ; }
2019-04-04 15:13:43 +02:00
2019-04-05 10:08:34 +02:00
const ModelObject * model_object = p -> model . objects [ obj_idx ];
2019-04-04 15:13:43 +02:00
if ( selection . get_mode () == Selection :: Instance )
2019-04-05 11:30:49 +02:00
{
if ( selection . is_single_full_object ())
mesh = model_object -> mesh ();
else
mesh = model_object -> full_raw_mesh ();
}
2019-04-04 15:13:43 +02:00
else
{
const GLVolume * volume = selection . get_volume ( * selection . get_volume_idxs (). begin ());
2019-06-11 17:08:47 +02:00
mesh = model_object -> volumes [ volume -> volume_idx ()] -> mesh ();
2019-04-04 15:13:43 +02:00
mesh . transform ( volume -> get_volume_transformation (). get_matrix ());
2019-04-05 10:08:34 +02:00
mesh . translate ( - model_object -> origin_translation . cast < float > ());
2019-04-04 15:13:43 +02:00
}
}
2019-04-05 10:08:34 +02:00
else
2019-05-02 13:46:39 +02:00
{
2019-01-02 18:36:48 +01:00
mesh = p -> model . mesh ();
2018-12-05 15:22:03 +01:00
2019-05-02 13:46:39 +02:00
if ( extended && ( p -> printer_technology == ptSLA ))
{
const PrintObjects & objects = p -> sla_print . objects ();
for ( const SLAPrintObject * object : objects )
{
const ModelObject * model_object = object -> model_object ();
Transform3d mesh_trafo_inv = object -> trafo (). inverse ();
bool is_left_handed = object -> is_left_handed ();
TriangleMesh pad_mesh ;
2019-09-24 15:15:49 +02:00
bool has_pad_mesh = object -> has_mesh ( slaposPad );
2019-05-02 13:46:39 +02:00
if ( has_pad_mesh )
{
2019-09-24 15:15:49 +02:00
pad_mesh = object -> get_mesh ( slaposPad );
2019-05-02 13:46:39 +02:00
pad_mesh . transform ( mesh_trafo_inv );
}
TriangleMesh supports_mesh ;
bool has_supports_mesh = 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 ());
if ( it != model_object -> instances . end ())
{
int instance_idx = it - model_object -> instances . begin ();
const Transform3d & inst_transform = object -> model_object () -> instances [ instance_idx ] -> get_transformation (). get_matrix ();
if ( has_pad_mesh )
{
TriangleMesh inst_pad_mesh = pad_mesh ;
inst_pad_mesh . transform ( inst_transform , is_left_handed );
mesh . merge ( inst_pad_mesh );
}
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-01-02 15:11:05 +01:00
Slic3r :: store_stl ( path_u8 . c_str (), & mesh , true );
2018-10-04 11:12:55 +02:00
p -> statusbar () -> set_status_text ( wxString :: Format ( _ ( L ( "STL file exported to %s" )), path ));
}
void Plater :: export_amf ()
{
2018-10-17 09:30:07 +02:00
if ( p -> model . objects . empty ()) { return ; }
2018-10-08 19:55:30 +02:00
2019-04-04 11:31:26 +02:00
wxString path = p -> get_export_file ( FT_AMF );
if ( path . empty ()) { return ; }
2019-01-02 15:11:05 +01:00
const std :: string path_u8 = into_u8 ( path );
2018-10-08 19:55:30 +02:00
2019-02-05 20:23:24 +01:00
wxBusyCursor wait ;
2019-04-04 11:31:26 +02:00
bool export_config = true ;
DynamicPrintConfig cfg = wxGetApp (). preset_bundle -> full_config_secure ();
if ( Slic3r :: store_amf ( path_u8 . c_str (), & p -> model , export_config ? & cfg : nullptr )) {
2018-10-08 19:55:30 +02:00
// 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 ));
}
2018-10-04 11:12:55 +02:00
}
2018-11-15 15:27:39 +01:00
void Plater :: export_3mf ( const boost :: filesystem :: path & output_path )
2018-10-04 11:12:55 +02:00
{
2018-10-17 09:30:07 +02:00
if ( p -> model . objects . empty ()) { return ; }
2018-10-08 19:55:30 +02:00
2018-11-15 15:27:39 +01:00
wxString path ;
bool export_config = true ;
if ( output_path . empty ())
{
2019-04-04 11:31:26 +02:00
path = p -> get_export_file ( FT_3MF );
if ( path . empty ()) { return ; }
2018-11-15 15:27:39 +01:00
}
else
2019-01-02 15:11:05 +01:00
path = from_path ( output_path );
2018-10-08 19:55:30 +02:00
2018-11-15 15:27:39 +01:00
if ( ! path . Lower (). EndsWith ( ".3mf" ))
return ;
2018-10-08 19:55:30 +02:00
2019-06-18 16:24:30 +02:00
DynamicPrintConfig cfg = wxGetApp (). preset_bundle -> full_config_secure ();
2019-01-02 15:11:05 +01:00
const std :: string path_u8 = into_u8 ( path );
2019-02-05 20:23:24 +01:00
wxBusyCursor wait ;
2019-10-23 13:31:24 +02:00
#if ENABLE_THUMBNAIL_GENERATOR
2019-10-24 15:56:10 +02:00
ThumbnailData thumbnail_data ;
2019-11-28 14:18:24 +01:00
p -> generate_thumbnail ( thumbnail_data , THUMBNAIL_SIZE_3MF . first , THUMBNAIL_SIZE_3MF . second , false , true , true , true );
2019-10-24 15:56:10 +02:00
if ( Slic3r :: store_3mf ( path_u8 . c_str (), & p -> model , export_config ? & cfg : nullptr , & thumbnail_data )) {
2019-10-23 13:31:24 +02:00
#else
2019-01-02 15:11:05 +01:00
if ( Slic3r :: store_3mf ( path_u8 . c_str (), & p -> model , export_config ? & cfg : nullptr )) {
2019-10-23 13:31:24 +02:00
#endif // ENABLE_THUMBNAIL_GENERATOR
2018-10-08 19:55:30 +02:00
// Success
p -> statusbar () -> set_status_text ( wxString :: Format ( _ ( L ( "3MF file exported to %s" )), path ));
2019-05-14 15:00:24 +02:00
p -> set_project_filename ( path );
}
else {
2018-10-08 19:55:30 +02:00
// Failure
p -> statusbar () -> set_status_text ( wxString :: Format ( _ ( L ( "Error exporting 3MF file %s" )), path ));
}
2018-10-04 11:12:55 +02:00
}
2019-09-19 09:09:11 +02:00
void Plater :: reload_from_disk ()
{
p -> reload_from_disk ();
}
2019-08-20 11:33:58 +02:00
bool Plater :: has_toolpaths_to_export () const
{
return p -> preview -> get_canvas3d () -> has_toolpaths_to_export ();
}
void Plater :: export_toolpaths_to_obj () const
2019-08-20 09:01:09 +02:00
{
if (( printer_technology () != ptFFF ) || ! is_preview_loaded ())
return ;
wxString path = p -> get_export_file ( FT_OBJ );
if ( path . empty ())
return ;
2019-08-20 11:33:58 +02:00
2019-08-20 09:01:09 +02:00
wxBusyCursor wait ;
p -> preview -> get_canvas3d () -> export_toolpaths_to_obj ( into_u8 ( path ). c_str ());
}
2018-09-17 12:15:11 +02:00
void Plater :: reslice ()
{
2019-05-14 10:34:11 +02:00
// Stop arrange and (or) optimize rotation tasks.
this -> stop_jobs ();
2019-07-31 13:00:35 +02:00
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 18:16:02 +02:00
2018-11-13 17:45:44 +01:00
//FIXME Don't reslice if export of G-code or sending to OctoPrint is running.
// bitmask of UpdateBackgroundProcessReturnState
2019-01-10 16:06:24 +01:00
unsigned int state = this -> p -> update_background_process ( true );
2018-11-13 17:45:44 +01:00
if ( state & priv :: UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE )
2018-12-04 13:55:25 +01:00
this -> p -> view3D -> reload_scene ( false );
2019-02-25 10:21:12 +01:00
// 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 ());
2018-12-22 10:02:42 +01:00
// Only restarts if the state is valid.
this -> p -> restart_background_process ( state | priv :: UPDATE_BACKGROUND_PROCESS_FORCE_RESTART );
2019-03-01 11:00:34 +01:00
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 );
2019-07-02 13:13:17 +02:00
// update type of preview
2019-10-16 09:45:11 +02:00
p -> preview -> update_view_type ( true );
2018-09-17 12:15:11 +02:00
}
2019-08-23 15:53:45 +02:00
void Plater :: reslice_SLA_supports ( const ModelObject & object , bool postpone_error_messages )
2019-02-21 11:40:56 +01:00
{
//FIXME Don't reslice if export of G-code or sending to OctoPrint is running.
// bitmask of UpdateBackgroundProcessReturnState
2019-08-23 15:53:45 +02:00
unsigned int state = this -> p -> update_background_process ( true , postpone_error_messages );
2019-02-21 11:40:56 +01:00
if ( state & priv :: UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE )
this -> p -> view3D -> reload_scene ( false );
2019-06-18 16:24:30 +02:00
if ( this -> p -> background_process . empty () || ( state & priv :: UPDATE_BACKGROUND_PROCESS_INVALID ))
2019-02-21 11:40:56 +01:00
// 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 18:16:02 +02:00
if ( ! this -> p -> background_processing_enabled ()) {
task . single_model_instance_only = true ;
2019-09-24 15:15:49 +02:00
task . to_object_step = slaposPad ;
2019-08-06 18:16:02 +02:00
}
2019-02-21 11:40:56 +01: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 11:12:55 +02:00
void Plater :: send_gcode ()
{
2018-12-11 10:33:11 +01: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 18:16:02 +02: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 ;
2019-05-17 16:27:00 +02:00
default_output_file = this -> p -> background_process . output_filepath_for_project ( into_path ( get_project_filename ( ".3mf" )));
2019-05-15 14:09:16 +02:00
}
catch ( const std :: exception & ex ) {
2018-12-11 10:33:11 +01:00
show_error ( this , ex . what ());
return ;
}
default_output_file = fs :: path ( Slic3r :: fold_utf8_to_ascii ( default_output_file . string ()));
2019-03-04 16:50:43 +01:00
PrintHostSendDialog dlg ( default_output_file , upload_job . printhost -> can_start_print ());
2018-12-11 10:33:11 +01: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 (), std :: move ( upload_job ));
}
2018-10-04 11:12:55 +02:00
}
2019-12-11 12:28:51 +01:00
void Plater :: eject_drive ()
{
2019-12-11 14:53:28 +01:00
if ( GUI :: RemovableDriveManager :: get_instance (). update ( 0 , true ))
2019-12-11 12:28:51 +01:00
{
RemovableDriveManager :: get_instance (). erase_callbacks ();
RemovableDriveManager :: get_instance (). add_callback ( std :: bind ( & Plater :: drive_ejected_callback , this ));
2019-12-11 14:53:28 +01:00
RemovableDriveManager :: get_instance (). eject_drive ( RemovableDriveManager :: get_instance (). get_last_save_path ());
2019-12-11 12:28:51 +01:00
}
}
void Plater :: drive_ejected_callback ()
{
p -> show_action_buttons ( false );
}
2019-07-04 10:45:41 +02: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 ); }
2019-07-11 16:00:01 +02:00
void Plater :: suppress_snapshots () { p -> suppress_snapshots (); }
void Plater :: allow_snapshots () { p -> allow_snapshots (); }
2019-07-04 10:45:41 +02:00
void Plater :: undo () { p -> undo (); }
void Plater :: redo () { p -> redo (); }
2019-07-09 20:45:00 +02:00
void Plater :: undo_to ( int selection )
{
if ( selection == 0 ) {
p -> undo ();
return ;
}
2019-08-06 18:16:02 +02:00
2019-07-09 20:45:00 +02:00
const int idx = p -> get_active_snapshot_index () - selection - 1 ;
2019-07-26 13:44:33 +02:00
p -> undo_redo_to ( p -> undo_redo_stack (). snapshots ()[ idx ]. timestamp );
2019-07-09 20:45:00 +02:00
}
void Plater :: redo_to ( int selection )
{
if ( selection == 0 ) {
p -> redo ();
return ;
}
2019-08-06 18:16:02 +02:00
2019-07-10 10:15:07 +02:00
const int idx = p -> get_active_snapshot_index () + selection + 1 ;
2019-07-26 13:44:33 +02:00
p -> undo_redo_to ( p -> undo_redo_stack (). snapshots ()[ idx ]. timestamp );
2019-07-09 20:45:00 +02:00
}
2019-07-09 15:27:28 +02:00
bool Plater :: undo_redo_string_getter ( const bool is_undo , int idx , const char ** out_text )
{
2019-07-26 13:44:33 +02:00
const std :: vector < UndoRedo :: Snapshot >& ss_stack = p -> undo_redo_stack (). snapshots ();
2019-07-09 20:45:00 +02:00
const int idx_in_ss_stack = p -> get_active_snapshot_index () + ( is_undo ? - ( ++ idx ) : idx );
2019-07-09 15:27:28 +02:00
2019-10-29 10:40:34 +01:00
if ( 0 < idx_in_ss_stack && ( size_t ) idx_in_ss_stack < ss_stack . size () - 1 ) {
2019-07-09 15:27:28 +02:00
* out_text = ss_stack [ idx_in_ss_stack ]. name . c_str ();
return true ;
}
return false ;
}
2019-07-02 16:42:23 +02:00
2019-07-20 14:03:34 +02:00
void Plater :: undo_redo_topmost_string_getter ( const bool is_undo , std :: string & out_text )
{
2019-07-26 13:44:33 +02:00
const std :: vector < UndoRedo :: Snapshot >& ss_stack = p -> undo_redo_stack (). snapshots ();
2019-07-20 14:03:34 +02:00
const int idx_in_ss_stack = p -> get_active_snapshot_index () + ( is_undo ? - 1 : 0 );
2019-10-29 10:40:34 +01:00
if ( 0 < idx_in_ss_stack && ( size_t ) idx_in_ss_stack < ss_stack . size () - 1 ) {
2019-07-20 14:03:34 +02:00
out_text = ss_stack [ idx_in_ss_stack ]. name ;
return ;
}
2019-08-06 10:01:10 +02:00
out_text = "" ;
2019-07-20 14:03:34 +02:00
}
2019-09-06 17:46:55 +02:00
void Plater :: on_extruders_change ( size_t num_extruders )
2018-10-10 13:53:45 +02:00
{
auto & choices = sidebar (). combos_filament ();
2019-03-13 13:13:18 +01:00
if ( num_extruders == choices . size ())
return ;
2018-11-15 11:15:24 +01:00
wxWindowUpdateLocker noUpdates_scrolled_panel ( & sidebar () /*.scrolled_panel()*/ );
2018-11-09 18:39:07 +01:00
2019-09-06 17:46:55 +02:00
size_t i = choices . size ();
2018-10-10 13:53:45 +02:00
while ( i < num_extruders )
{
PresetComboBox * choice /*{ nullptr }*/ ;
sidebar (). init_filament_combo ( & choice , i );
choices . push_back ( choice );
// initialize selection
2019-04-23 16:33:06 +02:00
wxGetApp (). preset_bundle -> update_platter_filament_ui ( i , choice );
2018-10-10 13:53:45 +02:00
++ i ;
}
// remove unused choices if any
sidebar (). remove_unused_filament_combos ( num_extruders );
2018-11-15 11:15:24 +01:00
sidebar (). Layout ();
sidebar (). scrolled_panel () -> Refresh ();
2018-10-10 13:53:45 +02:00
}
2018-10-17 11:12:38 +02:00
void Plater :: on_config_change ( const DynamicPrintConfig & config )
2018-10-10 13:53:45 +02:00
{
2018-10-15 10:53:47 +02:00
bool update_scheduled = false ;
2019-02-04 10:06:15 +01:00
bool bed_shape_changed = false ;
2018-10-17 11:12:38 +02:00
for ( auto opt_key : p -> config -> diff ( config )) {
2019-08-19 17:19:21 +02:00
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 18:19:28 +02:00
p -> sidebar -> obj_list () -> update_extruder_colors ();
2019-08-19 17:19:21 +02:00
continue ;
}
}
2018-10-17 11:12:38 +02:00
p -> config -> set_key_value ( opt_key , config . option ( opt_key ) -> clone ());
2018-12-03 13:14:28 +01:00
if ( opt_key == "printer_technology" )
this -> set_printer_technology ( config . opt_enum < PrinterTechnology > ( opt_key ));
2019-07-24 14:02:36 +02:00
else if (( opt_key == "bed_shape" ) || ( opt_key == "bed_custom_texture" ) || ( opt_key == "bed_custom_model" )) {
2019-02-04 10:06:15 +01:00
bed_shape_changed = true ;
2018-10-15 10:53:47 +02:00
update_scheduled = true ;
2019-08-06 18:16:02 +02:00
}
2018-12-06 15:40:41 +01: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 10:53:47 +02:00
update_scheduled = true ;
2019-08-06 18:16:02 +02:00
}
2018-10-15 10:53:47 +02:00
else if ( opt_key == "variable_layer_height" ) {
if ( p -> config -> opt_bool ( "variable_layer_height" ) != true ) {
2018-12-04 13:55:25 +01:00
p -> view3D -> enable_layers_editing ( false );
p -> view3D -> set_as_dirty ();
2018-10-15 10:53:47 +02:00
}
2019-03-14 13:54:05 +01:00
}
2018-10-15 10:53:47 +02: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 18:19:28 +02:00
p -> sidebar -> obj_list () -> update_extruder_colors ();
2018-10-15 10:53:47 +02:00
} else if ( opt_key == "max_print_height" ) {
update_scheduled = true ;
2019-01-17 13:21:33 +01:00
}
else if ( opt_key == "printer_model" ) {
2018-10-15 10:53:47 +02:00
// update to force bed selection(for texturing)
2019-02-04 10:06:15 +01:00
bed_shape_changed = true ;
2018-10-15 10:53:47 +02:00
update_scheduled = true ;
2019-01-17 13:21:33 +01:00
}
2018-10-15 10:53:47 +02:00
}
2018-12-11 10:33:11 +01:00
{
const auto prin_host_opt = p -> config -> option < ConfigOptionString > ( "print_host" );
p -> sidebar -> show_send ( prin_host_opt != nullptr && ! prin_host_opt -> value . empty ());
}
2019-02-04 10:06:15 +01:00
if ( bed_shape_changed )
2019-07-18 14:39:19 +02:00
p -> set_bed_shape ( p -> config -> option < ConfigOptionPoints > ( "bed_shape" ) -> values ,
2019-07-24 14:02:36 +02:00
p -> config -> option < ConfigOptionString > ( "bed_custom_texture" ) -> value ,
p -> config -> option < ConfigOptionString > ( "bed_custom_model" ) -> value );
2019-02-04 10:06:15 +01:00
2019-08-06 18:16:02 +02:00
if ( update_scheduled )
2018-10-15 10:53:47 +02:00
update ();
2018-10-17 11:12:38 +02:00
if ( p -> main_frame -> is_loaded ())
this -> p -> schedule_background_process ();
2018-10-10 13:53:45 +02:00
}
2019-09-30 13:59:26 +02:00
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 18:19:28 +02:00
if ( update_scheduled ) {
2019-09-30 13:59:26 +02:00
update ();
2019-10-01 18:19:28 +02:00
p -> sidebar -> obj_list () -> update_extruder_colors ();
}
2019-09-30 13:59:26 +02:00
if ( p -> main_frame -> is_loaded ())
this -> p -> schedule_background_process ();
}
2019-02-19 14:57:59 +01:00
void Plater :: on_activate ()
{
2019-02-19 15:54:43 +01:00
#ifdef __linux__
2019-02-19 14:57:59 +01:00
wxWindow * focus_window = wxWindow :: FindFocus ();
2019-02-19 15:54:43 +01:00
// 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
2019-08-23 15:53:45 +02:00
this -> p -> show_delayed_error_message ();
2019-02-19 14:57:59 +01:00
}
2019-09-18 11:47:19 +02:00
const DynamicPrintConfig * Plater :: get_plater_config () const
{
return p -> config ;
}
2019-09-30 14:03:50 +02: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 ;
2019-10-07 12:36:16 +02:00
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 ;
2019-10-18 12:35:35 +02:00
if ( ! wxGetApp (). plater ())
return extruder_colors ;
2019-09-30 14:03:50 +02: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 ;
}
2019-11-07 13:51:54 +01:00
std :: vector < std :: string > Plater :: get_colors_for_color_print () const
{
std :: vector < std :: string > colors = get_extruder_colors_from_plater_config ();
for ( const Model :: CustomGCode & code : p -> model . custom_gcode_per_height )
2019-11-11 09:38:45 +01:00
if ( code . gcode == ColorChangeCode )
2019-11-07 13:51:54 +01:00
colors . push_back ( code . color );
return colors ;
}
2019-05-15 14:09:16 +02:00
wxString Plater :: get_project_filename ( const wxString & extension ) const
2018-11-15 15:27:39 +01:00
{
2019-05-15 14:09:16 +02:00
return p -> get_project_filename ( extension );
}
void Plater :: set_project_filename ( const wxString & filename )
{
return p -> set_project_filename ( filename );
2018-11-15 15:27:39 +01:00
}
bool Plater :: is_export_gcode_scheduled () const
{
return p -> background_process . is_export_scheduled ();
}
2018-11-01 12:33:56 +01:00
int Plater :: get_selected_object_idx ()
{
return p -> get_selected_object_idx ();
}
2018-11-13 15:24:05 +01:00
bool Plater :: is_single_full_object_selection () const
2018-11-09 18:39:07 +01:00
{
return p -> get_selection (). is_single_full_object ();
}
2018-11-23 12:47:32 +01:00
GLCanvas3D * Plater :: canvas3D ()
2018-10-11 15:57:09 +02:00
{
2018-12-04 13:55:25 +01:00
return p -> view3D -> get_canvas3d ();
2018-10-11 15:57:09 +02:00
}
2019-09-19 12:30:16 +02:00
BoundingBoxf Plater :: bed_shape_bb () const
{
return p -> bed_shape_bb ();
}
2018-11-12 17:35:57 +01:00
PrinterTechnology Plater :: printer_technology () const
{
2018-11-13 17:45:44 +01:00
return p -> printer_technology ;
2018-11-12 17:35:57 +01:00
}
2018-12-03 13:14:28 +01:00
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 18:16:02 +02:00
//FIXME for SLA synchronize
2018-12-03 13:14:28 +01:00
//p->background_process.apply(Model)!
2019-03-01 11:00:34 +01:00
2019-03-12 09:41:57 +01:00
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" );
2019-04-30 13:51:36 +02:00
if ( wxGetApp (). mainframe )
wxGetApp (). mainframe -> update_menubar ();
2018-12-03 13:14:28 +01:00
}
2018-10-31 15:41:27 +01:00
void Plater :: changed_object ( int obj_idx )
2018-10-08 16:27:38 +02:00
{
if ( obj_idx < 0 )
return ;
2019-04-22 01:51:10 +02:00
// 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 );
2018-10-08 16:27:38 +02:00
}
2019-04-22 01:51:10 +02:00
else
p -> view3D -> reload_scene ( false );
2018-10-08 16:27:38 +02:00
2018-11-07 15:29:13 +01:00
// update print
2018-12-03 13:14:28 +01:00
this -> p -> schedule_background_process ();
2018-10-08 16:27:38 +02:00
}
2019-04-15 15:40:40 +02:00
void Plater :: changed_objects ( const std :: vector < size_t >& object_idxs )
{
if ( object_idxs . empty ())
return ;
2019-09-06 17:46:55 +02:00
for ( size_t obj_idx : object_idxs )
2019-04-22 01:51:10 +02:00
{
if ( obj_idx < p -> model . objects . size ())
// recenter and re - align to Z = 0
p -> model . objects [ obj_idx ] -> ensure_on_bed ();
2019-04-15 15:40:40 +02:00
}
2019-04-22 01:51:10 +02:00
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 );
2019-04-15 15:40:40 +02:00
// update print
this -> p -> schedule_background_process ();
}
2019-06-26 09:48:52 +02:00
void Plater :: schedule_background_process ( bool schedule /* = true*/ )
2019-04-12 18:29:47 +02:00
{
2019-06-26 09:48:52 +02:00
if ( schedule )
this -> p -> schedule_background_process ();
this -> p -> suppressed_backround_processing_update = false ;
}
2019-08-06 18:16:02 +02:00
bool Plater :: is_background_process_running () const
2019-06-26 09:48:52 +02: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 ;
2019-04-12 18:29:47 +02:00
}
2019-01-30 16:27:07 +01:00
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 12:15:11 +02:00
2018-12-07 17:50:48 +01:00
void Plater :: update_object_menu () { p -> update_object_menu (); }
2019-04-10 08:40:58 +02:00
void Plater :: copy_selection_to_clipboard ()
{
2019-06-24 13:21:05 +02:00
if ( can_copy_to_clipboard ())
p -> view3D -> get_canvas3d () -> get_selection (). copy_to_clipboard ();
2019-04-10 08:40:58 +02:00
}
void Plater :: paste_from_clipboard ()
{
2019-07-04 17:33:19 +02:00
if ( ! can_paste_from_clipboard ())
return ;
2019-07-26 13:06:49 +02:00
Plater :: TakeSnapshot snapshot ( this , _ ( L ( "Paste From Clipboard" )));
2019-07-04 17:33:19 +02:00
p -> view3D -> get_canvas3d () -> get_selection (). paste_from_clipboard ();
2019-04-10 08:40:58 +02:00
}
2019-04-25 01:45:00 +02:00
void Plater :: msw_rescale ()
2019-04-16 10:05:45 +02:00
{
2019-04-25 01:45:00 +02:00
p -> preview -> msw_rescale ();
2019-04-16 12:11:48 +02:00
2019-04-25 01:45:00 +02:00
p -> view3D -> get_canvas3d () -> msw_rescale ();
2019-04-16 10:05:45 +02:00
2019-04-25 01:45:00 +02:00
p -> sidebar -> msw_rescale ();
2019-04-16 10:05:45 +02:00
2019-05-03 09:44:19 +02:00
p -> msw_rescale_object_menu ();
2019-04-16 10:05:45 +02:00
Layout ();
GetParent () -> Layout ();
}
2019-12-10 10:56:21 +01:00
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
bool Plater :: init_view_toolbar ()
{
return p -> init_view_toolbar ();
}
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
2019-07-02 15:49:18 +02:00
const Camera & Plater :: get_camera () const
{
return p -> camera ;
}
2019-09-27 14:52:19 +02:00
const Mouse3DController & Plater :: get_mouse3d_controller () const
{
return p -> mouse3d_controller ;
}
Mouse3DController & Plater :: get_mouse3d_controller ()
{
return p -> mouse3d_controller ;
}
2019-03-14 13:54:05 +01:00
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 (); }
2019-05-13 14:27:51 +02:00
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 (); }
2019-03-14 13:54:05 +01:00
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 (); }
2019-06-24 13:21:05 +02:00
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 ;
}
2019-03-14 13:54:05 +01:00
2019-07-26 13:44:33 +02:00
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 (); }
2019-09-19 09:09:11 +02:00
bool Plater :: can_reload_from_disk () const { return p -> can_reload_from_disk (); }
2019-07-26 13:44:33 +02:00
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 (); }
2019-07-08 10:57:35 +02:00
2019-06-26 09:48:52 +02:00
SuppressBackgroundProcessingUpdate :: SuppressBackgroundProcessingUpdate () :
m_was_running ( wxGetApp (). plater () -> is_background_process_running ())
{
wxGetApp (). plater () -> suppress_background_process ( m_was_running );
}
SuppressBackgroundProcessingUpdate ::~ SuppressBackgroundProcessingUpdate ()
{
wxGetApp (). plater () -> schedule_background_process ( m_was_running );
}
2018-09-17 12:15:11 +02:00
}} // namespace Slic3r::GUI