2019-05-23 09:20:11 +02:00
#include "libslic3r/libslic3r.h"
2020-06-16 16:58:41 +02:00
#include "libslic3r/PresetBundle.hpp"
2018-10-04 16:43:10 +02:00
#include "GUI_ObjectList.hpp"
2021-03-15 10:04:45 +01:00
#include "GUI_Factories.hpp"
2018-10-05 23:29:15 +02:00
#include "GUI_ObjectManipulation.hpp"
2019-05-28 16:38:04 +02:00
#include "GUI_ObjectLayers.hpp"
2018-10-04 16:43:10 +02:00
#include "GUI_App.hpp"
2018-11-26 14:41:58 +01:00
#include "I18N.hpp"
2020-05-26 17:42:57 +02:00
#include "Plater.hpp"
2021-04-06 13:17:29 +02:00
#if ENABLE_PROJECT_DIRTY_STATE
#include "MainFrame.hpp"
#endif // ENABLE_PROJECT_DIRTY_STATE
2018-10-04 16:43:10 +02:00
#include "OptionsGroup.hpp"
#include "Tab.hpp"
#include "wxExtensions.hpp"
2018-11-26 14:41:58 +01:00
#include "libslic3r/Model.hpp"
2018-10-11 15:57:09 +02:00
#include "GLCanvas3D.hpp"
2019-03-19 13:30:21 +01:00
#include "Selection.hpp"
2021-02-04 10:02:25 +01:00
#include "format.hpp"
2018-10-04 16:43:10 +02:00
#include <boost/algorithm/string.hpp>
2021-03-05 11:27:25 +01:00
#include <wx/progdlg.h>
2021-05-12 17:04:36 +02:00
#include <wx/numformatter.h>
2021-03-05 11:27:25 +01:00
2018-10-04 16:43:10 +02:00
#include "slic3r/Utils/FixModelByWin10.hpp"
2019-08-22 11:47:58 +02:00
#ifdef __WXMSW__
#include "wx/uiaction.h"
#endif /* __WXMSW__ */
2018-10-04 16:43:10 +02:00
namespace Slic3r
{
namespace GUI
{
2018-11-01 14:02:38 +01:00
wxDEFINE_EVENT ( EVT_OBJ_LIST_OBJECT_SELECT , SimpleEvent );
2018-11-01 12:33:56 +01:00
2019-03-05 15:33:38 +01:00
static PrinterTechnology printer_technology ()
{
return wxGetApp (). preset_bundle -> printers . get_selected_preset (). printer_technology ();
}
2019-12-11 16:07:00 +01:00
static const Selection & scene_selection ()
{
return wxGetApp (). plater () -> canvas3D () -> get_selection ();
}
2019-03-14 13:15:04 +01:00
// Config from current edited printer preset
2019-03-14 15:45:52 +01:00
static DynamicPrintConfig & printer_config ()
2019-03-14 13:15:04 +01:00
{
return wxGetApp (). preset_bundle -> printers . get_edited_preset (). config ;
}
2019-03-14 15:45:52 +01:00
static int extruders_count ()
2019-03-14 13:15:04 +01:00
{
2021-02-22 09:54:46 +01:00
return wxGetApp (). extruders_edited_cnt ();
2019-03-14 13:15:04 +01:00
}
2019-07-11 16:00:01 +02:00
static void take_snapshot ( const wxString & snapshot_name )
2019-07-04 17:33:19 +02:00
{
2020-02-21 10:02:41 +01:00
Plater * plater = wxGetApp (). plater ();
if ( plater )
plater -> take_snapshot ( snapshot_name );
2019-07-04 17:33:19 +02:00
}
2018-10-04 16:43:10 +02:00
ObjectList :: ObjectList ( wxWindow * parent ) :
2021-03-15 10:04:45 +01:00
wxDataViewCtrl ( parent , wxID_ANY , wxDefaultPosition , wxDefaultSize , wxDV_MULTIPLE )
2018-10-04 16:43:10 +02:00
{
// create control
create_objects_ctrl ();
// describe control behavior
2019-04-02 16:33:52 +02:00
Bind ( wxEVT_DATAVIEW_SELECTION_CHANGED , [ this ]( wxDataViewEvent & event ) {
2020-03-10 12:03:08 +01:00
// detect the current mouse position here, to pass it to list_manipulation() method
// if we detect it later, the user may have moved the mouse pointer while calculations are performed, and this would mess-up the HitTest() call performed into list_manipulation()
// see: https://github.com/prusa3d/PrusaSlicer/issues/3802
2021-01-29 16:16:23 +01:00
#ifndef __WXOSX__
2020-03-14 07:00:09 +01:00
const wxPoint mouse_pos = this -> get_mouse_position_in_control ();
2021-01-29 16:16:23 +01:00
#endif
2020-03-10 12:03:08 +01:00
2019-01-31 14:12:07 +01:00
#ifndef __APPLE__
2020-03-16 21:32:13 +01:00
// On Windows and Linux:
// It's not invoked KillFocus event for "temporary" panels (like "Manipulation panel", "Settings", "Layer ranges"),
// if we change selection in object list.
// see https://github.com/prusa3d/PrusaSlicer/issues/3303
// But, if we call SetFocus() for ObjectList it will cause an invoking of a KillFocus event for "temporary" panels
this -> SetFocus ();
2019-04-09 15:43:16 +02:00
#else
2019-04-08 11:06:15 +02:00
// To avoid selection update from SetSelection() and UnselectAll() under osx
if ( m_prevent_list_events )
return ;
2019-01-31 14:12:07 +01:00
#endif // __APPLE__
2019-04-04 15:07:54 +02:00
/* For multiple selection with pressed SHIFT,
* event.GetItem() returns value of a first item in selection list
* instead of real last clicked item.
* So, let check last selected item in such strange way
*/
2019-08-22 11:47:58 +02:00
#ifdef __WXMSW__
// Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected.
int new_selected_column = - 1 ;
2019-09-02 09:41:12 +02:00
#endif //__WXMSW__
2019-04-04 15:07:54 +02:00
if ( wxGetKeyState ( WXK_SHIFT ))
{
wxDataViewItemArray sels ;
GetSelections ( sels );
2019-09-28 09:57:26 +02:00
if ( ! sels . empty () && sels . front () == m_last_selected_item )
2019-04-04 15:07:54 +02:00
m_last_selected_item = sels . back ();
else
m_last_selected_item = event . GetItem ();
}
2019-08-22 11:47:58 +02:00
else {
wxDataViewItem new_selected_item = event . GetItem ();
#ifdef __WXMSW__
// Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected.
wxDataViewItem item ;
wxDataViewColumn * col ;
2020-03-14 07:00:09 +01:00
this -> HitTest ( this -> get_mouse_position_in_control (), item , col );
2019-08-22 11:47:58 +02:00
new_selected_column = ( col == nullptr ) ? - 1 : ( int ) col -> GetModelColumn ();
if ( new_selected_item == m_last_selected_item && m_last_selected_column != - 1 && m_last_selected_column != new_selected_column ) {
// Mouse clicked on another column of the active row. Simulate keyboard enter to enter the editing mode of the current column.
wxUIActionSimulator sim ;
sim . Char ( WXK_RETURN );
}
2019-09-02 09:41:12 +02:00
#endif //__WXMSW__
2019-08-22 11:47:58 +02:00
m_last_selected_item = new_selected_item ;
}
#ifdef __WXMSW__
m_last_selected_column = new_selected_column ;
2019-09-02 09:41:12 +02:00
#endif //__WXMSW__
2019-04-04 15:07:54 +02:00
2018-10-11 15:57:09 +02:00
selection_changed ();
2018-10-04 16:43:10 +02:00
#ifndef __WXMSW__
2020-03-14 07:00:09 +01:00
set_tooltip_for_item ( this -> get_mouse_position_in_control ());
2019-08-20 13:01:01 +02:00
#endif //__WXMSW__
#ifndef __WXOSX__
2020-03-10 12:03:08 +01:00
list_manipulation ( mouse_pos );
2019-08-20 13:01:01 +02:00
#endif //__WXOSX__
2018-10-04 16:43:10 +02:00
});
2019-05-16 10:37:06 +02:00
#ifdef __WXOSX__
// Key events are not correctly processed by the wxDataViewCtrl on OSX.
// Our patched wxWidgets process the keyboard accelerators.
// On the other hand, using accelerators will break in-place editing on Windows & Linux/GTK (there is no in-place editing working on OSX for wxDataViewCtrl for now).
// Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this);
{
// Accelerators
2021-05-11 11:02:12 +02:00
wxAcceleratorEntry entries [ 33 ];
entries [ 0 ]. Set ( wxACCEL_CTRL , ( int ) 'C' , wxID_COPY );
entries [ 1 ]. Set ( wxACCEL_CTRL , ( int ) 'X' , wxID_CUT );
entries [ 2 ]. Set ( wxACCEL_CTRL , ( int ) 'V' , wxID_PASTE );
entries [ 3 ]. Set ( wxACCEL_CTRL , ( int ) 'A' , wxID_SELECTALL );
entries [ 4 ]. Set ( wxACCEL_CTRL , ( int ) 'Z' , wxID_UNDO );
entries [ 5 ]. Set ( wxACCEL_CTRL , ( int ) 'Y' , wxID_REDO );
2019-07-05 20:27:44 +02:00
entries [ 6 ]. Set ( wxACCEL_NORMAL , WXK_DELETE , wxID_DELETE );
2021-05-11 11:02:12 +02:00
entries [ 7 ]. Set ( wxACCEL_NORMAL , WXK_BACK , wxID_DELETE );
entries [ 8 ]. Set ( wxACCEL_NORMAL , int ( '+' ), wxID_ADD );
entries [ 9 ]. Set ( wxACCEL_NORMAL , WXK_NUMPAD_ADD , wxID_ADD );
entries [ 10 ]. Set ( wxACCEL_NORMAL , int ( '-' ), wxID_REMOVE );
entries [ 11 ]. Set ( wxACCEL_NORMAL , WXK_NUMPAD_SUBTRACT , wxID_REMOVE );
entries [ 12 ]. Set ( wxACCEL_NORMAL , int ( 'p' ), wxID_PRINT );
int numbers_cnt = 1 ;
for ( auto char_number : { '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' }) {
entries [ 12 + numbers_cnt ]. Set ( wxACCEL_NORMAL , int ( char_number ), wxID_LAST + numbers_cnt );
entries [ 22 + numbers_cnt ]. Set ( wxACCEL_NORMAL , WXK_NUMPAD0 + numbers_cnt - 1 , wxID_LAST + numbers_cnt );
numbers_cnt ++ ;
}
wxAcceleratorTable accel ( 33 , entries );
2019-05-16 10:37:06 +02:00
SetAcceleratorTable ( accel );
2019-06-11 10:11:42 +02:00
this -> Bind ( wxEVT_MENU , [ this ]( wxCommandEvent & evt ) { this -> copy (); }, wxID_COPY );
this -> Bind ( wxEVT_MENU , [ this ]( wxCommandEvent & evt ) { this -> paste (); }, wxID_PASTE );
this -> Bind ( wxEVT_MENU , [ this ]( wxCommandEvent & evt ) { this -> select_item_all_children (); }, wxID_SELECTALL );
this -> Bind ( wxEVT_MENU , [ this ]( wxCommandEvent & evt ) { this -> remove (); }, wxID_DELETE );
2019-07-05 20:27:44 +02:00
this -> Bind ( wxEVT_MENU , [ this ]( wxCommandEvent & evt ) { this -> undo (); }, wxID_UNDO );
this -> Bind ( wxEVT_MENU , [ this ]( wxCommandEvent & evt ) { this -> redo (); }, wxID_REDO );
2020-12-08 10:49:24 +01:00
this -> Bind ( wxEVT_MENU , [ this ]( wxCommandEvent & evt ) { this -> increase_instances (); }, wxID_ADD );
this -> Bind ( wxEVT_MENU , [ this ]( wxCommandEvent & evt ) { this -> decrease_instances (); }, wxID_REMOVE );
2021-05-11 11:02:12 +02:00
this -> Bind ( wxEVT_MENU , [ this ]( wxCommandEvent & evt ) { this -> toggle_printable_state (); }, wxID_PRINT );
for ( int i = 0 ; i < 10 ; i ++ )
this -> Bind ( wxEVT_MENU , [ this , i ]( wxCommandEvent & evt ) {
if ( extruders_count () > 1 && i <= extruders_count ())
this -> set_extruder_for_selected_items ( i );
}, wxID_LAST + i + 1 );
2019-05-16 10:37:06 +02:00
}
2019-09-02 09:41:12 +02:00
#else //__WXOSX__
2019-05-16 10:37:06 +02:00
Bind ( wxEVT_CHAR , [ this ]( wxKeyEvent & event ) { key_event ( event ); }); // doesn't work on OSX
#endif
2018-10-04 16:43:10 +02:00
#ifdef __WXMSW__
2018-10-08 16:27:38 +02:00
GetMainWindow () -> Bind ( wxEVT_MOTION , [ this ]( wxMouseEvent & event ) {
2020-03-14 07:00:09 +01:00
set_tooltip_for_item ( this -> get_mouse_position_in_control ());
2018-10-04 16:43:10 +02:00
event . Skip ();
});
#endif //__WXMSW__
2018-12-11 09:37:58 +01:00
Bind ( wxEVT_DATAVIEW_ITEM_CONTEXT_MENU , & ObjectList :: OnContextMenu , this );
Bind ( wxEVT_DATAVIEW_ITEM_BEGIN_DRAG , & ObjectList :: OnBeginDrag , this );
Bind ( wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE , & ObjectList :: OnDropPossible , this );
Bind ( wxEVT_DATAVIEW_ITEM_DROP , & ObjectList :: OnDrop , this );
2019-08-22 11:47:58 +02:00
#ifdef __WXMSW__
Bind ( wxEVT_DATAVIEW_ITEM_EDITING_STARTED , & ObjectList :: OnEditingStarted , this );
#endif /* __WXMSW__ */
Bind ( wxEVT_DATAVIEW_ITEM_EDITING_DONE , & ObjectList :: OnEditingDone , this );
2018-12-12 14:35:18 +01:00
2018-12-11 09:37:58 +01:00
Bind ( wxEVT_DATAVIEW_ITEM_VALUE_CHANGED , & ObjectList :: ItemValueChanged , this );
2018-11-12 13:47:24 +01:00
2018-12-10 13:39:56 +01:00
Bind ( wxCUSTOMEVT_LAST_VOLUME_IS_DELETED , [ this ]( wxCommandEvent & e ) { last_volume_is_deleted ( e . GetInt ()); });
2018-12-12 08:40:10 +01:00
2019-08-06 19:53:20 +02:00
Bind ( wxEVT_SIZE , ([ this ]( wxSizeEvent & e ) {
#ifdef __WXGTK__
// On GTK, the EnsureVisible call is postponed to Idle processing (see wxDataViewCtrl::m_ensureVisibleDefered).
// So the postponed EnsureVisible() call is planned for an item, which may not exist at the Idle processing time, if this wxEVT_SIZE
// event is succeeded by a delete of the currently active item. We are trying our luck by postponing the wxEVT_SIZE triggered EnsureVisible(),
// which seems to be working as of now.
2020-01-27 14:31:41 +01:00
this -> CallAfter ([ this ](){ ensure_current_item_visible (); });
2019-08-06 19:53:20 +02:00
#else
2020-01-27 14:31:41 +01:00
ensure_current_item_visible ();
2019-08-06 19:53:20 +02:00
#endif
e . Skip ();
}));
2018-10-05 23:29:15 +02:00
}
ObjectList ::~ ObjectList ()
{
2018-10-04 16:43:10 +02:00
}
2020-12-08 10:11:11 +01:00
void ObjectList :: set_min_height ()
{
2020-12-29 19:10:14 +01:00
if ( m_items_count == size_t ( - 1 ))
m_items_count = 7 ;
int list_min_height = lround ( 2.25 * ( m_items_count + 1 ) * wxGetApp (). em_unit ()); // +1 is for height of control header
this -> SetMinSize ( wxSize ( 1 , list_min_height ));
}
void ObjectList :: update_min_height ()
{
wxDataViewItemArray all_items ;
m_objects_model -> GetAllChildren ( wxDataViewItem ( nullptr ), all_items );
size_t items_cnt = all_items . Count ();
if ( items_cnt < 7 )
items_cnt = 7 ;
else if ( items_cnt >= 15 )
items_cnt = 15 ;
if ( m_items_count == items_cnt )
return ;
m_items_count = items_cnt ;
set_min_height ();
2020-12-08 10:11:11 +01:00
}
2018-10-04 16:43:10 +02:00
void ObjectList :: create_objects_ctrl ()
{
2019-03-26 18:01:44 +01:00
/* Temporary workaround for the correct behavior of the Scrolled sidebar panel:
* 1. set a height of the list to some big value
2020-12-29 19:10:14 +01:00
* 2. change it to the normal(meaningful) min value after first whole Mainframe updating/layouting
2019-03-26 18:01:44 +01:00
*/
SetMinSize ( wxSize ( - 1 , 3000 ));
2018-10-04 16:43:10 +02:00
m_sizer = new wxBoxSizer ( wxVERTICAL );
2019-02-07 14:44:05 +01:00
m_sizer -> Add ( this , 1 , wxGROW );
2018-10-04 16:43:10 +02:00
2019-04-25 01:45:00 +02:00
m_objects_model = new ObjectDataViewModel ;
2018-10-08 16:27:38 +02:00
AssociateModel ( m_objects_model );
2018-11-12 13:47:24 +01:00
m_objects_model -> SetAssociatedControl ( this );
2018-10-04 16:43:10 +02:00
#if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE
2018-10-08 16:27:38 +02:00
EnableDragSource ( wxDF_UNICODETEXT );
EnableDropTarget ( wxDF_UNICODETEXT );
2018-10-04 16:43:10 +02:00
#endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE
2019-09-20 11:30:29 +02:00
const int em = wxGetApp (). em_unit ();
2019-08-01 14:58:04 +02:00
// column ItemName(Icon+Text) of the view control:
2018-10-04 16:43:10 +02:00
// And Icon can be consisting of several bitmaps
2020-08-06 15:54:12 +02:00
BitmapTextRenderer * bmp_text_renderer = new BitmapTextRenderer ();
2020-08-05 20:26:40 +02:00
bmp_text_renderer -> set_can_create_editor_ctrl_function ([ this ]() {
return m_objects_model -> GetItemType ( GetSelection ()) & ( itVolume | itObject );
});
AppendColumn ( new wxDataViewColumn ( _L ( "Name" ), bmp_text_renderer ,
2019-09-20 11:30:29 +02:00
colName , 20 * em , wxALIGN_LEFT , wxDATAVIEW_COL_RESIZABLE ));
2018-10-04 16:43:10 +02:00
2019-08-01 14:58:04 +02:00
// column PrintableProperty (Icon) of the view control:
2019-09-20 11:30:29 +02:00
AppendBitmapColumn ( " " , colPrint , wxDATAVIEW_CELL_INERT , 3 * em ,
2019-08-01 14:58:04 +02:00
wxALIGN_CENTER_HORIZONTAL , wxDATAVIEW_COL_RESIZABLE );
// column Extruder of the view control:
2020-08-05 20:26:40 +02:00
BitmapChoiceRenderer * bmp_choice_renderer = new BitmapChoiceRenderer ();
bmp_choice_renderer -> set_can_create_editor_ctrl_function ([ this ]() {
return m_objects_model -> GetItemType ( GetSelection ()) & ( itVolume | itLayer | itObject );
});
AppendColumn ( new wxDataViewColumn ( _L ( "Extruder" ), bmp_choice_renderer ,
2019-10-01 18:19:28 +02:00
colExtruder , 8 * em , wxALIGN_CENTER_HORIZONTAL , wxDATAVIEW_COL_RESIZABLE ));
2018-10-04 16:43:10 +02:00
2019-08-01 14:58:04 +02:00
// column ItemEditing of the view control:
2020-08-05 20:26:40 +02:00
AppendBitmapColumn ( _L ( "Editing" ), colEditing , wxDATAVIEW_CELL_INERT , 3 * em ,
2018-10-04 16:43:10 +02:00
wxALIGN_CENTER_HORIZONTAL , wxDATAVIEW_COL_RESIZABLE );
2019-09-20 11:30:29 +02:00
2019-09-23 14:24:53 +02:00
// For some reason under OSX on 4K(5K) monitors in wxDataViewColumn constructor doesn't set width of column.
// Therefore, force set column width.
2019-09-20 11:30:29 +02:00
if ( wxOSX )
{
GetColumn ( colName ) -> SetWidth ( 20 * em );
GetColumn ( colPrint ) -> SetWidth ( 3 * em );
GetColumn ( colExtruder ) -> SetWidth ( 8 * em );
2020-03-02 11:33:09 +01:00
GetColumn ( colEditing ) -> SetWidth ( 7 * em );
2019-09-20 11:30:29 +02:00
}
2018-10-04 16:43:10 +02:00
}
2019-09-02 09:41:12 +02:00
void ObjectList :: get_selected_item_indexes ( int & obj_idx , int & vol_idx , const wxDataViewItem & input_item /* = wxDataViewItem(nullptr)*/ )
2019-04-24 13:33:05 +02:00
{
2019-09-02 09:41:12 +02:00
const wxDataViewItem item = input_item == wxDataViewItem ( nullptr ) ? GetSelection () : input_item ;
2019-04-24 13:33:05 +02:00
if ( ! item )
{
obj_idx = vol_idx = - 1 ;
return ;
}
const ItemType type = m_objects_model -> GetItemType ( item );
obj_idx = type & itObject ? m_objects_model -> GetIdByItem ( item ) :
type & itVolume ? m_objects_model -> GetIdByItem ( m_objects_model -> GetTopParent ( item )) : - 1 ;
vol_idx = type & itVolume ? m_objects_model -> GetVolumeIdByItem ( item ) : - 1 ;
}
2020-05-20 20:07:31 +02:00
void ObjectList :: get_selection_indexes ( std :: vector < int >& obj_idxs , std :: vector < int >& vol_idxs )
{
wxDataViewItemArray sels ;
GetSelections ( sels );
assert ( ! sels . IsEmpty ());
2021-03-15 10:04:45 +01:00
if ( m_objects_model -> GetItemType ( sels [ 0 ]) & itVolume ||
( sels . Count () == 1 && m_objects_model -> GetItemType ( m_objects_model -> GetParent ( sels [ 0 ])) & itVolume ) ) {
2020-05-20 20:07:31 +02:00
for ( wxDataViewItem item : sels ) {
obj_idxs . emplace_back ( m_objects_model -> GetIdByItem ( m_objects_model -> GetTopParent ( item )));
2021-03-15 10:04:45 +01:00
if ( sels . Count () == 1 && m_objects_model -> GetItemType ( m_objects_model -> GetParent ( item )) & itVolume )
item = m_objects_model -> GetParent ( item );
2020-05-20 20:07:31 +02:00
assert ( m_objects_model -> GetItemType ( item ) & itVolume );
vol_idxs . emplace_back ( m_objects_model -> GetVolumeIdByItem ( item ));
}
}
else {
for ( wxDataViewItem item : sels ) {
const ItemType type = m_objects_model -> GetItemType ( item );
obj_idxs . emplace_back ( type & itObject ? m_objects_model -> GetIdByItem ( item ) :
m_objects_model -> GetIdByItem ( m_objects_model -> GetTopParent ( item )));
}
}
std :: sort ( obj_idxs . begin (), obj_idxs . end (), std :: greater < int > ());
obj_idxs . erase ( std :: unique ( obj_idxs . begin (), obj_idxs . end ()), obj_idxs . end ());
}
2019-04-24 13:33:05 +02:00
int ObjectList :: get_mesh_errors_count ( const int obj_idx , const int vol_idx /*= -1*/ ) const
{
if ( obj_idx < 0 )
return 0 ;
2019-04-24 16:04:47 +02:00
return ( * m_objects )[ obj_idx ] -> get_mesh_errors_count ( vol_idx );
2019-04-24 13:33:05 +02:00
}
wxString ObjectList :: get_mesh_errors_list ( const int obj_idx , const int vol_idx /*= -1*/ ) const
{
const int errors = get_mesh_errors_count ( obj_idx , vol_idx );
if ( errors == 0 )
return "" ; // hide tooltip
// Create tooltip string, if there are errors
2021-02-03 21:30:38 +01:00
wxString tooltip = format_wxstr ( _L_PLURAL ( "Auto-repaired %1$d error" , "Auto-repaired %1$d errors" , errors ), errors ) + ": \n " ;
2019-04-24 13:33:05 +02:00
2019-04-24 16:04:47 +02:00
const stl_stats & stats = vol_idx == - 1 ?
( * m_objects )[ obj_idx ] -> get_object_stl_stats () :
2019-06-11 17:08:47 +02:00
( * m_objects )[ obj_idx ] -> volumes [ vol_idx ] -> mesh (). stl . stats ;
2019-04-24 13:33:05 +02:00
2021-02-03 21:30:38 +01:00
if ( stats . degenerate_facets > 0 )
tooltip += " \t " + format_wxstr ( _L_PLURAL ( "%1$d degenerate facet" , "%1$d degenerate facets" , stats . degenerate_facets ), stats . degenerate_facets ) + " \n " ;
if ( stats . edges_fixed > 0 )
tooltip += " \t " + format_wxstr ( _L_PLURAL ( "%1$d edge fixed" , "%1$d edges fixed" , stats . edges_fixed ), stats . edges_fixed ) + " \n " ;
if ( stats . facets_removed > 0 )
tooltip += " \t " + format_wxstr ( _L_PLURAL ( "%1$d facet removed" , "%1$d facets removed" , stats . facets_removed ), stats . facets_removed ) + " \n " ;
if ( stats . facets_added > 0 )
tooltip += " \t " + format_wxstr ( _L_PLURAL ( "%1$d facet added" , "%1$d facets added" , stats . facets_added ), stats . facets_added ) + " \n " ;
if ( stats . facets_reversed > 0 )
tooltip += " \t " + format_wxstr ( _L_PLURAL ( "%1$d facet reversed" , "%1$d facets reversed" , stats . facets_reversed ), stats . facets_reversed ) + " \n " ;
if ( stats . backwards_edges > 0 )
tooltip += " \t " + format_wxstr ( _L_PLURAL ( "%1$d backwards edge" , "%1$d backwards edges" , stats . backwards_edges ), stats . backwards_edges ) + " \n " ;
2019-04-24 13:33:05 +02:00
if ( is_windows10 ())
2021-02-03 21:30:38 +01:00
tooltip += _L ( "Right button click the icon to fix STL through Netfabb" );
2019-04-24 13:33:05 +02:00
return tooltip ;
}
wxString ObjectList :: get_mesh_errors_list ()
{
if ( ! GetSelection ())
return "" ;
int obj_idx , vol_idx ;
get_selected_item_indexes ( obj_idx , vol_idx );
return get_mesh_errors_list ( obj_idx , vol_idx );
}
2018-10-04 16:43:10 +02:00
void ObjectList :: set_tooltip_for_item ( const wxPoint & pt )
{
wxDataViewItem item ;
wxDataViewColumn * col ;
2018-10-08 16:27:38 +02:00
HitTest ( pt , item , col );
2018-10-04 16:43:10 +02:00
2019-04-30 16:59:40 +02:00
/* GetMainWindow() return window, associated with wxDataViewCtrl.
* And for this window we should to set tooltips.
* Just this->SetToolTip(tooltip) => has no effect.
*/
2019-08-20 13:01:01 +02:00
if ( ! item || GetSelectedItemsCount () > 1 )
2019-05-20 13:52:58 +02:00
{
GetMainWindow () -> SetToolTip ( "" ); // hide tooltip
return ;
}
2019-08-20 13:01:01 +02:00
wxString tooltip = "" ;
if ( col -> GetTitle () == _ ( L ( "Editing" )))
#ifdef __WXOSX__
tooltip = _ ( L ( "Right button click the icon to change the object settings" ));
#else
tooltip = _ ( L ( "Click the icon to change the object settings" ));
2019-04-30 15:46:25 +02:00
#endif //__WXMSW__
2019-08-20 13:01:01 +02:00
else if ( col -> GetTitle () == " " )
#ifdef __WXOSX__
tooltip = _ ( L ( "Right button click the icon to change the object printable property" ));
#else
tooltip = _ ( L ( "Click the icon to change the object printable property" ));
#endif //__WXMSW__
else if ( col -> GetTitle () == _ ( "Name" ) && ( pt . x >= 2 * wxGetApp (). em_unit () && pt . x <= 4 * wxGetApp (). em_unit ()))
{
2019-04-24 13:33:05 +02:00
int obj_idx , vol_idx ;
get_selected_item_indexes ( obj_idx , vol_idx , item );
2019-08-20 13:01:01 +02:00
tooltip = get_mesh_errors_list ( obj_idx , vol_idx );
2018-10-05 23:29:15 +02:00
}
2019-08-20 13:01:01 +02:00
GetMainWindow () -> SetToolTip ( tooltip );
2018-10-04 16:43:10 +02:00
}
2018-10-12 12:00:37 +02:00
int ObjectList :: get_selected_obj_idx () const
{
2018-10-31 15:41:27 +01:00
if ( GetSelectedItemsCount () == 1 )
return m_objects_model -> GetIdByItem ( m_objects_model -> GetTopParent ( GetSelection ()));
2018-10-12 12:00:37 +02:00
return - 1 ;
}
2020-09-24 15:34:13 +02:00
ModelConfig & ObjectList :: get_item_config ( const wxDataViewItem & item ) const
2019-03-28 16:28:34 +01:00
{
assert ( item );
const ItemType type = m_objects_model -> GetItemType ( item );
2019-07-22 09:41:34 +02:00
const int obj_idx = m_objects_model -> GetObjectIdByItem ( item );
2019-03-28 16:28:34 +01:00
const int vol_idx = type & itVolume ? m_objects_model -> GetVolumeIdByItem ( item ) : - 1 ;
assert ( obj_idx >= 0 || (( type & itVolume ) && vol_idx >= 0 ));
2019-05-23 15:12:19 +02:00
return type & itVolume ? ( * m_objects )[ obj_idx ] -> volumes [ vol_idx ] -> config :
2019-06-06 14:14:29 +02:00
type & itLayer ? ( * m_objects )[ obj_idx ] -> layer_config_ranges [ m_objects_model -> GetLayerRangeByItem ( item )] :
2019-05-23 15:12:19 +02:00
( * m_objects )[ obj_idx ] -> config ;
2019-03-28 16:28:34 +01:00
}
2019-09-06 17:46:55 +02:00
void ObjectList :: update_extruder_values_for_items ( const size_t max_extruder )
2018-12-10 17:00:28 +01:00
{
2019-09-06 17:46:55 +02:00
for ( size_t i = 0 ; i < m_objects -> size (); ++ i )
2018-12-10 17:00:28 +01:00
{
wxDataViewItem item = m_objects_model -> GetItemById ( i );
if ( ! item ) continue ;
auto object = ( * m_objects )[ i ];
wxString extruder ;
if ( ! object -> config . has ( "extruder" ) ||
2020-09-24 15:34:13 +02:00
size_t ( object -> config . extruder ()) > max_extruder )
2019-05-10 12:54:20 +02:00
extruder = _ ( L ( "default" ));
2018-12-10 17:00:28 +01:00
else
2020-09-24 15:34:13 +02:00
extruder = wxString :: Format ( "%d" , object -> config . extruder ());
2018-12-10 17:00:28 +01:00
2019-10-01 18:19:28 +02:00
m_objects_model -> SetExtruder ( extruder , item );
2018-12-10 17:00:28 +01:00
if ( object -> volumes . size () > 1 ) {
2019-09-06 17:46:55 +02:00
for ( size_t id = 0 ; id < object -> volumes . size (); id ++ ) {
2018-12-10 17:00:28 +01:00
item = m_objects_model -> GetItemByVolumeId ( i , id );
if ( ! item ) continue ;
if ( ! object -> volumes [ id ] -> config . has ( "extruder" ) ||
2020-09-24 15:34:13 +02:00
size_t ( object -> volumes [ id ] -> config . extruder ()) > max_extruder )
2019-05-10 12:54:20 +02:00
extruder = _ ( L ( "default" ));
2018-12-10 17:00:28 +01:00
else
2020-09-24 15:34:13 +02:00
extruder = wxString :: Format ( "%d" , object -> volumes [ id ] -> config . extruder ());
2018-12-10 17:00:28 +01:00
2019-10-01 18:19:28 +02:00
m_objects_model -> SetExtruder ( extruder , item );
2018-12-10 17:00:28 +01:00
}
}
}
}
2019-09-06 17:46:55 +02:00
void ObjectList :: update_objects_list_extruder_column ( size_t extruders_count )
2018-10-04 16:43:10 +02:00
{
2019-03-05 10:46:57 +01:00
if ( printer_technology () == ptSLA )
2018-10-04 16:43:10 +02:00
extruders_count = 1 ;
2018-12-10 17:00:28 +01:00
m_prevent_update_extruder_in_config = true ;
if ( m_objects && extruders_count > 1 )
update_extruder_values_for_items ( extruders_count );
2019-10-01 18:19:28 +02:00
update_extruder_colors ();
2018-10-04 16:43:10 +02:00
// set show/hide for this column
set_extruder_column_hidden ( extruders_count <= 1 );
2018-12-04 14:27:39 +01:00
//a workaround for a wrong last column width updating under OSX
2019-08-01 14:58:04 +02:00
GetColumn ( colEditing ) -> SetWidth ( 25 );
2018-12-10 17:00:28 +01:00
m_prevent_update_extruder_in_config = false ;
2018-10-04 16:43:10 +02:00
}
2019-10-01 18:19:28 +02:00
void ObjectList :: update_extruder_colors ()
{
m_objects_model -> UpdateColumValues ( colExtruder );
}
2018-12-11 09:37:58 +01:00
void ObjectList :: set_extruder_column_hidden ( const bool hide ) const
2018-10-05 23:29:15 +02:00
{
2019-08-01 14:58:04 +02:00
GetColumn ( colExtruder ) -> SetHidden ( hide );
2018-10-05 23:29:15 +02:00
}
2018-10-04 16:43:10 +02:00
2018-12-10 17:00:28 +01:00
void ObjectList :: update_extruder_in_config ( const wxDataViewItem & item )
2018-10-05 23:29:15 +02:00
{
2018-12-10 17:00:28 +01:00
if ( m_prevent_update_extruder_in_config )
return ;
2019-06-06 14:14:29 +02:00
const ItemType item_type = m_objects_model -> GetItemType ( item );
if ( item_type & itObject ) {
2018-12-10 14:13:01 +01:00
const int obj_idx = m_objects_model -> GetIdByItem ( item );
m_config = & ( * m_objects )[ obj_idx ] -> config ;
}
else {
2019-06-06 14:14:29 +02:00
const int obj_idx = m_objects_model -> GetIdByItem ( m_objects_model -> GetTopParent ( item ));
if ( item_type & itVolume )
{
2018-12-10 14:13:01 +01:00
const int volume_id = m_objects_model -> GetVolumeIdByItem ( item );
if ( obj_idx < 0 || volume_id < 0 )
return ;
m_config = & ( * m_objects )[ obj_idx ] -> volumes [ volume_id ] -> config ;
2019-06-06 14:14:29 +02:00
}
else if ( item_type & itLayer )
m_config = & get_item_config ( item );
2018-12-10 14:13:01 +01:00
}
2019-10-01 18:19:28 +02:00
if ( ! m_config )
2018-10-05 23:29:15 +02:00
return ;
2019-12-04 15:12:00 +01:00
take_snapshot ( _ ( L ( "Change Extruder" )));
2019-10-01 18:19:28 +02:00
const int extruder = m_objects_model -> GetExtruderNumber ( item );
2018-10-05 23:29:15 +02:00
m_config -> set_key_value ( "extruder" , new ConfigOptionInt ( extruder ));
2018-10-15 10:53:47 +02:00
// update scene
wxGetApp (). plater () -> update ();
2018-10-05 23:29:15 +02:00
}
2019-01-30 11:35:37 +01:00
void ObjectList :: update_name_in_model ( const wxDataViewItem & item ) const
2018-12-12 14:35:18 +01:00
{
const int obj_idx = m_objects_model -> GetObjectIdByItem ( item );
if ( obj_idx < 0 ) return ;
2019-07-22 13:38:53 +02:00
const int volume_id = m_objects_model -> GetVolumeIdByItem ( item );
2018-12-12 14:35:18 +01:00
2019-08-01 15:38:00 +02:00
take_snapshot ( volume_id < 0 ? _ ( L ( "Rename Object" )) : _ ( L ( "Rename Sub-object" )));
2019-07-22 13:38:53 +02:00
if ( m_objects_model -> GetItemType ( item ) & itObject ) {
2019-01-03 14:34:53 +01:00
( * m_objects )[ obj_idx ] -> name = m_objects_model -> GetName ( item ). ToUTF8 (). data ();
2018-12-12 14:35:18 +01:00
return ;
}
if ( volume_id < 0 ) return ;
2019-01-03 14:34:53 +01:00
( * m_objects )[ obj_idx ] -> volumes [ volume_id ] -> name = m_objects_model -> GetName ( item ). ToUTF8 (). data ();
2018-12-12 14:35:18 +01:00
}
2018-10-11 15:57:09 +02:00
void ObjectList :: selection_changed ()
2018-10-05 23:29:15 +02:00
{
if ( m_prevent_list_events ) return ;
2018-10-11 15:57:09 +02:00
fix_multiselection_conflicts ();
// update object selection on Plater
2019-03-04 09:41:25 +01:00
if ( ! m_prevent_canvas_selection_update )
update_selections_on_canvas ();
2018-10-11 15:57:09 +02:00
2018-11-01 12:33:56 +01:00
// to update the toolbar and info sizer
if ( ! GetSelection () || m_objects_model -> GetItemType ( GetSelection ()) == itObject ) {
auto event = SimpleEvent ( EVT_OBJ_LIST_OBJECT_SELECT );
event . SetEventObject ( this );
wxPostEvent ( this , event );
}
2019-07-02 15:26:11 +02:00
if ( const wxDataViewItem item = GetSelection ())
{
const ItemType type = m_objects_model -> GetItemType ( item );
// to correct visual hints for layers editing on the Scene
if ( type & ( itLayer | itLayerRoot )) {
wxGetApp (). obj_layers () -> reset_selection ();
if ( type & itLayerRoot )
wxGetApp (). plater () -> canvas3D () -> handle_sidebar_focus_event ( "" , false );
else {
wxGetApp (). obj_layers () -> set_selectable_range ( m_objects_model -> GetLayerRangeByItem ( item ));
wxGetApp (). obj_layers () -> update_scene_from_editor_selection ();
}
}
}
2018-10-05 23:29:15 +02:00
part_selection_changed ();
}
2020-06-01 11:11:38 +02:00
void ObjectList :: copy_layers_to_clipboard ()
2019-06-10 15:22:09 +02:00
{
wxDataViewItemArray sel_layers ;
GetSelections ( sel_layers );
2020-06-01 11:11:38 +02:00
const int obj_idx = m_objects_model -> GetObjectIdByItem ( sel_layers . front ());
2019-06-10 15:22:09 +02:00
if ( obj_idx < 0 || ( int ) m_objects -> size () <= obj_idx )
return ;
const t_layer_config_ranges & ranges = object ( obj_idx ) -> layer_config_ranges ;
2020-06-01 11:11:38 +02:00
t_layer_config_ranges & cache_ranges = m_clipboard . get_ranges_cache ();
if ( sel_layers . Count () == 1 && m_objects_model -> GetItemType ( sel_layers . front ()) & itLayerRoot )
{
cache_ranges . clear ();
cache_ranges = ranges ;
return ;
}
2019-06-10 15:22:09 +02:00
2021-01-29 16:16:23 +01:00
for ( const auto & layer_item : sel_layers )
2019-06-10 15:22:09 +02:00
if ( m_objects_model -> GetItemType ( layer_item ) & itLayer ) {
auto range = m_objects_model -> GetLayerRangeByItem ( layer_item );
auto it = ranges . find ( range );
if ( it != ranges . end ())
2020-06-01 11:11:38 +02:00
cache_ranges [ it -> first ] = it -> second ;
2019-06-10 15:22:09 +02:00
}
}
void ObjectList :: paste_layers_into_list ()
{
const int obj_idx = m_objects_model -> GetObjectIdByItem ( GetSelection ());
2020-06-01 11:11:38 +02:00
t_layer_config_ranges & cache_ranges = m_clipboard . get_ranges_cache ();
2019-06-10 15:22:09 +02:00
if ( obj_idx < 0 || ( int ) m_objects -> size () <= obj_idx ||
2020-06-01 11:11:38 +02:00
cache_ranges . empty () || printer_technology () == ptSLA )
2019-06-10 15:22:09 +02:00
return ;
const wxDataViewItem object_item = m_objects_model -> GetItemById ( obj_idx );
wxDataViewItem layers_item = m_objects_model -> GetLayerRootItem ( object_item );
if ( layers_item )
m_objects_model -> Delete ( layers_item );
t_layer_config_ranges & ranges = object ( obj_idx ) -> layer_config_ranges ;
// and create Layer item(s) according to the layer_config_ranges
2021-01-29 16:16:23 +01:00
for ( const auto & range : cache_ranges )
2019-06-10 15:22:09 +02:00
ranges . emplace ( range );
layers_item = add_layer_root_item ( object_item );
changed_object ( obj_idx );
select_item ( layers_item );
#ifndef __WXOSX__
selection_changed ();
#endif //no __WXOSX__
}
2020-06-01 11:11:38 +02:00
void ObjectList :: copy_settings_to_clipboard ()
{
wxDataViewItem item = GetSelection ();
assert ( item . IsOk ());
if ( m_objects_model -> GetItemType ( item ) & itSettings )
item = m_objects_model -> GetParent ( item );
2020-09-24 15:34:13 +02:00
m_clipboard . get_config_cache () = get_item_config ( item ). get ();
2020-06-01 11:11:38 +02:00
}
void ObjectList :: paste_settings_into_list ()
{
wxDataViewItem item = GetSelection ();
assert ( item . IsOk ());
if ( m_objects_model -> GetItemType ( item ) & itSettings )
item = m_objects_model -> GetParent ( item );
ItemType item_type = m_objects_model -> GetItemType ( item );
if ( ! ( item_type & ( itObject | itVolume | itLayer )))
return ;
DynamicPrintConfig & config_cache = m_clipboard . get_config_cache ();
assert ( ! config_cache . empty ());
auto keys = config_cache . keys ();
2021-03-15 10:04:45 +01:00
auto part_options = SettingsFactory :: get_options ( true );
2020-06-01 11:11:38 +02:00
for ( const std :: string & opt_key : keys ) {
if ( item_type & ( itVolume | itLayer ) &&
std :: find ( part_options . begin (), part_options . end (), opt_key ) == part_options . end ())
continue ; // we can't to add object specific options for the part's(itVolume | itLayer) config
const ConfigOption * option = config_cache . option ( opt_key );
if ( option )
m_config -> set_key_value ( opt_key , option -> clone ());
}
// Add settings item for object/sub-object and show them
2020-09-24 15:34:13 +02:00
show_settings ( add_settings_item ( item , & m_config -> get ()));
2020-06-01 11:11:38 +02:00
}
2019-04-10 08:40:58 +02:00
void ObjectList :: paste_volumes_into_list ( int obj_idx , const ModelVolumePtrs & volumes )
{
if (( obj_idx < 0 ) || (( int ) m_objects -> size () <= obj_idx ))
return ;
if ( volumes . empty ())
return ;
const auto object_item = m_objects_model -> GetItemById ( obj_idx );
wxDataViewItemArray items ;
for ( const ModelVolume * volume : volumes )
{
2019-05-15 17:16:31 +02:00
const wxDataViewItem & vol_item = m_objects_model -> AddVolumeChild ( object_item , wxString :: FromUTF8 ( volume -> name . c_str ()), volume -> type (),
2019-04-29 15:27:59 +02:00
volume -> get_mesh_errors_count () > 0 ,
2020-09-24 15:34:13 +02:00
volume -> config . has ( "extruder" ) ? volume -> config . extruder () : 0 );
add_settings_item ( vol_item , & volume -> config . get ());
2019-04-10 08:40:58 +02:00
items . Add ( vol_item );
}
2019-04-22 01:51:10 +02:00
changed_object ( obj_idx );
2019-04-10 08:40:58 +02:00
2019-04-12 11:28:24 +02:00
if ( items . size () > 1 )
{
m_selection_mode = smVolume ;
2019-09-02 09:41:12 +02:00
m_last_selected_item = wxDataViewItem ( nullptr );
2019-04-12 11:28:24 +02:00
}
2019-04-10 08:40:58 +02:00
select_items ( items );
selection_changed ();
}
2019-04-10 15:55:32 +02:00
void ObjectList :: paste_objects_into_list ( const std :: vector < size_t >& object_idxs )
2019-04-10 08:40:58 +02:00
{
2019-04-10 15:55:32 +02:00
if ( object_idxs . empty ())
return ;
wxDataViewItemArray items ;
for ( const size_t object : object_idxs )
{
add_object_to_list ( object );
items . Add ( m_objects_model -> GetItemById ( object ));
}
2019-04-15 15:40:40 +02:00
wxGetApp (). plater () -> changed_objects ( object_idxs );
2019-04-10 15:55:32 +02:00
select_items ( items );
selection_changed ();
2019-04-10 08:40:58 +02:00
}
2019-05-16 10:37:06 +02:00
#ifdef __WXOSX__
/*
2018-12-11 09:37:58 +01:00
void ObjectList::OnChar(wxKeyEvent& event)
{
2018-12-12 08:40:10 +01:00
if (event.GetKeyCode() == WXK_BACK){
2018-12-11 09:37:58 +01:00
remove();
}
else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_SHIFT))
select_item_all_children();
2018-12-12 08:40:10 +01:00
event.Skip();
2018-12-11 09:37:58 +01:00
}
2019-05-16 10:37:06 +02:00
*/
#endif /* __WXOSX__ */
2018-12-11 09:37:58 +01:00
2020-03-10 12:03:08 +01:00
void ObjectList :: OnContextMenu ( wxDataViewEvent & evt )
2019-08-20 13:01:01 +02:00
{
2020-03-10 12:03:08 +01:00
// The mouse position returned by get_mouse_position_in_control() here is the one at the time the mouse button is released (mouse up event)
2020-03-14 07:00:09 +01:00
wxPoint mouse_pos = this -> get_mouse_position_in_control ();
2020-03-10 12:03:08 +01:00
2019-10-09 13:39:24 +02:00
// Do not show the context menu if the user pressed the right mouse button on the 3D scene and released it on the objects list
GLCanvas3D * canvas = wxGetApp (). plater () -> canvas3D ();
bool evt_context_menu = ( canvas != nullptr ) ? ! canvas -> is_mouse_dragging () : true ;
if ( ! evt_context_menu )
canvas -> mouse_up_cleanup ();
2020-03-10 12:03:08 +01:00
list_manipulation ( mouse_pos , evt_context_menu );
2019-08-20 13:01:01 +02:00
}
2020-03-10 12:03:08 +01:00
void ObjectList :: list_manipulation ( const wxPoint & mouse_pos , bool evt_context_menu /* = false*/ )
2018-10-05 23:29:15 +02:00
{
2020-03-13 23:22:09 +01:00
// Interesting fact: when mouse_pos.x < 0, HitTest(mouse_pos, item, col) returns item = null, but column = last column.
// So, when mouse was moved to scene immediately after clicking in ObjectList, in the scene will be shown context menu for the Editing column.
// see: https://github.com/prusa3d/PrusaSlicer/issues/3802
if ( mouse_pos . x < 0 )
return ;
2018-10-05 23:29:15 +02:00
wxDataViewItem item ;
2019-09-06 14:50:44 +02:00
wxDataViewColumn * col = nullptr ;
2020-03-10 12:03:08 +01:00
HitTest ( mouse_pos , item , col );
2019-08-05 11:02:56 +02:00
2019-10-02 11:53:50 +02:00
if ( m_extruder_editor )
m_extruder_editor -> Hide ();
2019-09-19 14:11:56 +02:00
/* Note: Under OSX right click doesn't send "selection changed" event.
* It means that Selection() will be return still previously selected item.
* Thus under OSX we should force UnselectAll(), when item and col are nullptr,
* and select new item otherwise.
*/
2019-09-19 12:30:16 +02:00
if ( ! item ) {
2019-09-23 10:17:53 +02:00
if ( col == nullptr ) {
2019-12-10 17:25:30 +01:00
if ( wxOSX && ! multiple_selection ())
2019-09-23 10:17:53 +02:00
UnselectAll ();
2019-09-24 14:32:59 +02:00
else if ( ! evt_context_menu )
// Case, when last item was deleted and under GTK was called wxEVT_DATAVIEW_SELECTION_CHANGED,
// which invoked next list_manipulation(false)
2019-09-23 10:17:53 +02:00
return ;
}
2019-09-19 12:30:16 +02:00
if ( evt_context_menu ) {
show_context_menu ( evt_context_menu );
return ;
}
2019-08-05 11:02:56 +02:00
}
2019-09-19 14:11:56 +02:00
if ( wxOSX && item && col ) {
2019-12-10 17:25:30 +01:00
wxDataViewItemArray sels ;
GetSelections ( sels );
2019-09-19 14:11:56 +02:00
UnselectAll ();
2019-12-10 17:25:30 +01:00
if ( sels . Count () > 1 )
SetSelections ( sels );
else
Select ( item );
2019-09-19 14:11:56 +02:00
}
2020-03-11 13:58:31 +01:00
if ( col != nullptr )
2018-12-12 13:18:38 +01:00
{
2020-03-11 13:58:31 +01:00
const wxString title = col -> GetTitle ();
if ( title == " " )
2021-03-24 20:24:51 +01:00
toggle_printable_state ();
2020-03-11 13:58:31 +01:00
else if ( title == _ ( "Editing" ))
show_context_menu ( evt_context_menu );
else if ( title == _ ( "Name" ))
{
if ( wxOSX )
show_context_menu ( evt_context_menu ); // return context menu under OSX (related to #2909)
2019-04-29 15:27:59 +02:00
2020-03-11 13:58:31 +01:00
if ( is_windows10 ())
{
int obj_idx , vol_idx ;
get_selected_item_indexes ( obj_idx , vol_idx , item );
2019-09-20 11:30:29 +02:00
2020-03-11 13:58:31 +01:00
if ( get_mesh_errors_count ( obj_idx , vol_idx ) > 0 &&
mouse_pos . x > 2 * wxGetApp (). em_unit () && mouse_pos . x < 4 * wxGetApp (). em_unit ())
fix_through_netfabb ();
}
}
// workaround for extruder editing under OSX
else if ( wxOSX && evt_context_menu && title == _ ( "Extruder" ))
extruder_editing ();
}
2019-03-14 13:15:04 +01:00
2018-10-05 23:29:15 +02:00
#ifndef __WXMSW__
2018-10-08 16:27:38 +02:00
GetMainWindow () -> SetToolTip ( "" ); // hide tooltip
2018-10-05 23:29:15 +02:00
#endif //__WXMSW__
}
2019-09-19 12:30:16 +02:00
void ObjectList :: show_context_menu ( const bool evt_context_menu )
2018-10-05 23:29:15 +02:00
{
2021-03-15 10:04:45 +01:00
wxMenu * menu { nullptr };
Plater * plater = wxGetApp (). plater ();
2019-03-14 13:15:04 +01:00
if ( multiple_selection ())
2019-01-23 16:01:37 +01:00
{
2019-03-14 13:15:04 +01:00
if ( selected_instances_of_same_object ())
2021-03-15 10:04:45 +01:00
menu = plater -> instance_menu ();
2019-03-14 13:15:04 +01:00
else
2021-03-15 10:04:45 +01:00
menu = plater -> multi_selection_menu ();
2019-01-23 16:01:37 +01:00
}
2021-03-15 10:04:45 +01:00
else {
const auto item = GetSelection ();
if ( item )
{
const ItemType type = m_objects_model -> GetItemType ( item );
if ( ! ( type & ( itObject | itVolume | itLayer | itInstance )))
return ;
2019-01-23 16:01:37 +01:00
2021-03-15 10:04:45 +01:00
menu = type & itInstance ? plater -> instance_menu () :
type & itLayer ? plater -> layer_menu () :
m_objects_model -> GetParent ( item ) != wxDataViewItem ( nullptr ) ? plater -> part_menu () :
printer_technology () == ptFFF ? plater -> object_menu () : plater -> sla_object_menu ();
}
else if ( evt_context_menu )
menu = plater -> default_menu ();
2018-10-05 23:29:15 +02:00
}
2019-09-19 12:30:16 +02:00
if ( menu )
2021-03-15 10:04:45 +01:00
plater -> PopupMenu ( menu );
2018-10-05 23:29:15 +02:00
}
2019-10-02 11:53:50 +02:00
void ObjectList :: extruder_editing ()
{
wxDataViewItem item = GetSelection ();
if ( ! item || ! ( m_objects_model -> GetItemType ( item ) & ( itVolume | itObject )))
return ;
2019-10-02 16:14:40 +02:00
const int column_width = GetColumn ( colExtruder ) -> GetWidth () + wxSystemSettings :: GetMetric ( wxSYS_VSCROLL_X ) + 5 ;
2019-10-02 11:53:50 +02:00
2020-03-14 07:00:09 +01:00
wxPoint pos = this -> get_mouse_position_in_control ();
2019-10-02 15:02:13 +02:00
wxSize size = wxSize ( column_width , - 1 );
2019-10-02 16:14:40 +02:00
pos . x = GetColumn ( colName ) -> GetWidth () + GetColumn ( colPrint ) -> GetWidth () + 5 ;
2019-10-02 15:02:13 +02:00
pos . y -= GetTextExtent ( "m" ). y ;
2019-10-02 11:53:50 +02:00
2019-10-18 12:35:35 +02:00
apply_extruder_selector ( & m_extruder_editor , this , L ( "default" ), pos , size );
2019-10-02 11:53:50 +02:00
m_extruder_editor -> SetSelection ( m_objects_model -> GetExtruderNumber ( item ));
2019-11-08 11:24:57 +01:00
m_extruder_editor -> Show ();
2019-10-02 11:53:50 +02:00
2019-10-02 15:02:13 +02:00
auto set_extruder = [ this ]()
2019-10-02 11:53:50 +02:00
{
2019-10-02 15:02:13 +02:00
wxDataViewItem item = GetSelection ();
if ( ! item ) return ;
2019-10-02 11:53:50 +02:00
const int selection = m_extruder_editor -> GetSelection ();
if ( selection >= 0 )
m_objects_model -> SetExtruder ( m_extruder_editor -> GetString ( selection ), item );
m_extruder_editor -> Hide ();
2019-11-12 10:28:01 +01:00
update_extruder_in_config ( item );
2019-10-02 11:53:50 +02:00
};
// to avoid event propagation to other sidebar items
m_extruder_editor -> Bind ( wxEVT_COMBOBOX , [ set_extruder ]( wxCommandEvent & evt )
{
set_extruder ();
evt . StopPropagation ();
});
}
2019-06-11 10:11:42 +02:00
void ObjectList :: copy ()
{
2020-06-01 11:11:38 +02:00
wxPostEvent (( wxEvtHandler * ) wxGetApp (). plater () -> canvas3D () -> get_wxglcanvas (), SimpleEvent ( EVT_GLTOOLBAR_COPY ));
2019-06-11 10:11:42 +02:00
}
void ObjectList :: paste ()
{
2020-06-01 11:11:38 +02:00
wxPostEvent (( wxEvtHandler * ) wxGetApp (). plater () -> canvas3D () -> get_wxglcanvas (), SimpleEvent ( EVT_GLTOOLBAR_PASTE ));
}
bool ObjectList :: copy_to_clipboard ()
{
wxDataViewItemArray sels ;
GetSelections ( sels );
2020-12-22 10:42:47 +01:00
if ( sels . IsEmpty ())
return false ;
2020-06-01 11:11:38 +02:00
ItemType type = m_objects_model -> GetItemType ( sels . front ());
if ( ! ( type & ( itSettings | itLayer | itLayerRoot ))) {
m_clipboard . reset ();
return false ;
}
if ( type & itSettings )
copy_settings_to_clipboard ();
if ( type & ( itLayer | itLayerRoot ))
copy_layers_to_clipboard ();
m_clipboard . set_type ( type );
return true ;
}
bool ObjectList :: paste_from_clipboard ()
{
if ( ! ( m_clipboard . get_type () & ( itSettings | itLayer | itLayerRoot ))) {
m_clipboard . reset ();
return false ;
}
if ( m_clipboard . get_type () & itSettings )
paste_settings_into_list ();
if ( m_clipboard . get_type () & ( itLayer | itLayerRoot ))
paste_layers_into_list ();
return true ;
2019-06-11 10:11:42 +02:00
}
2019-07-05 20:27:44 +02:00
void ObjectList :: undo ()
{
wxGetApp (). plater () -> undo ();
}
void ObjectList :: redo ()
{
wxGetApp (). plater () -> redo ();
}
2020-12-08 08:30:15 +01:00
void ObjectList :: increase_instances ()
{
wxGetApp (). plater () -> increase_instances ( 1 );
}
void ObjectList :: decrease_instances ()
{
wxGetApp (). plater () -> decrease_instances ( 1 );
}
2019-05-16 10:37:06 +02:00
#ifndef __WXOSX__
2018-10-11 15:57:09 +02:00
void ObjectList :: key_event ( wxKeyEvent & event )
2018-10-05 23:29:15 +02:00
{
if ( event . GetKeyCode () == WXK_TAB )
2018-10-08 16:27:38 +02:00
Navigate ( event . ShiftDown () ? wxNavigationKeyEvent :: IsBackward : wxNavigationKeyEvent :: IsForward );
2018-10-05 23:29:15 +02:00
else if ( event . GetKeyCode () == WXK_DELETE
#ifdef __WXOSX__
|| event . GetKeyCode () == WXK_BACK
#endif //__WXOSX__
2018-10-31 12:56:08 +01:00
) {
2018-10-05 23:29:15 +02:00
remove ();
}
2020-02-03 15:47:09 +01:00
else if ( event . GetKeyCode () == WXK_F5 )
wxGetApp (). plater () -> reload_all_from_disk ();
2019-04-04 15:07:54 +02:00
else if ( wxGetKeyState ( wxKeyCode ( 'A' )) && wxGetKeyState ( WXK_CONTROL /*WXK_SHIFT*/ ))
2018-11-08 15:45:55 +01:00
select_item_all_children ();
2019-06-11 10:11:42 +02:00
else if ( wxGetKeyState ( wxKeyCode ( 'C' )) && wxGetKeyState ( WXK_CONTROL ))
copy ();
2019-04-16 13:47:37 +02:00
else if ( wxGetKeyState ( wxKeyCode ( 'V' )) && wxGetKeyState ( WXK_CONTROL ))
2019-06-11 10:11:42 +02:00
paste ();
2019-07-05 20:27:44 +02:00
else if ( wxGetKeyState ( wxKeyCode ( 'Y' )) && wxGetKeyState ( WXK_CONTROL ))
redo ();
else if ( wxGetKeyState ( wxKeyCode ( 'Z' )) && wxGetKeyState ( WXK_CONTROL ))
undo ();
2020-12-08 08:30:15 +01:00
else if ( event . GetUnicodeKey () == '+' )
increase_instances ();
else if ( event . GetUnicodeKey () == '-' )
decrease_instances ();
2021-05-11 11:02:12 +02:00
else if ( event . GetUnicodeKey () == 'p' )
toggle_printable_state ();
else if ( extruders_count () > 1 ) {
std :: vector < wxChar > numbers = { '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' };
wxChar key_char = event . GetUnicodeKey ();
if ( std :: find ( numbers . begin (), numbers . end (), key_char ) != numbers . end ()) {
long extruder_number ;
if ( wxNumberFormatter :: FromString ( wxString ( key_char ), & extruder_number ) &&
extruders_count () >= extruder_number )
set_extruder_for_selected_items ( int ( extruder_number ));
}
else
event . Skip ();
}
2018-10-05 23:29:15 +02:00
else
event . Skip ();
}
2019-05-16 10:37:06 +02:00
#endif /* __WXOSX__ */
2018-10-05 23:29:15 +02:00
2018-12-11 09:37:58 +01:00
void ObjectList :: OnBeginDrag ( wxDataViewEvent & event )
2018-10-05 23:29:15 +02:00
{
2018-12-12 10:34:34 +01:00
const wxDataViewItem item ( event . GetItem ());
2018-10-05 23:29:15 +02:00
2019-01-23 16:01:37 +01:00
const bool mult_sel = multiple_selection ();
2019-09-02 09:41:12 +02:00
if (( mult_sel && ! selected_instances_of_same_object ()) ||
2020-01-30 14:42:59 +01:00
( ! mult_sel && ( GetSelection () != item )) ) {
2019-01-22 16:40:10 +01:00
event . Veto ();
return ;
}
const ItemType & type = m_objects_model -> GetItemType ( item );
2020-01-30 14:42:59 +01:00
if ( ! ( type & ( itVolume | itObject | itInstance ))) {
2018-10-05 23:29:15 +02:00
event . Veto ();
return ;
}
2019-01-23 16:01:37 +01:00
if ( mult_sel )
{
m_dragged_data . init ( m_objects_model -> GetObjectIdByItem ( item ), type );
std :: set < int >& sub_obj_idxs = m_dragged_data . inst_idxs ();
wxDataViewItemArray sels ;
GetSelections ( sels );
for ( auto sel : sels )
sub_obj_idxs . insert ( m_objects_model -> GetInstanceIdByItem ( sel ));
}
2020-01-30 14:42:59 +01:00
else if ( type & itObject )
m_dragged_data . init ( m_objects_model -> GetIdByItem ( item ), type );
else
2019-01-23 16:01:37 +01:00
m_dragged_data . init ( m_objects_model -> GetObjectIdByItem ( item ),
2020-01-30 14:42:59 +01:00
type & itVolume ? m_objects_model -> GetVolumeIdByItem ( item ) :
2019-01-22 16:40:10 +01:00
m_objects_model -> GetInstanceIdByItem ( item ),
2020-01-30 14:42:59 +01:00
type );
2018-12-13 13:19:11 +01:00
2018-10-05 23:29:15 +02:00
/* Under MSW or OSX, DnD moves an item to the place of another selected item
* But under GTK, DnD moves an item between another two items.
* And as a result - call EVT_CHANGE_SELECTION to unselect all items.
2018-12-11 09:37:58 +01:00
* To prevent such behavior use m_prevent_list_events
2018-10-05 23:29:15 +02:00
**/
m_prevent_list_events = true ; //it's needed for GTK
2021-01-09 14:31:21 +01:00
/* Under GTK, DnD requires to the wxTextDataObject been initialized with some valid value,
2018-12-13 13:19:11 +01:00
* so set some nonempty string
*/
wxTextDataObject * obj = new wxTextDataObject ;
obj -> SetText ( "Some text" ); //it's needed for GTK
2018-12-12 08:40:10 +01:00
2018-12-13 13:19:11 +01:00
event . SetDataObject ( obj );
2018-12-12 08:40:10 +01:00
event . SetDragFlags ( wxDrag_DefaultMove ); // allows both copy and move;
2018-10-05 23:29:15 +02:00
}
2019-01-22 16:40:10 +01:00
bool ObjectList :: can_drop ( const wxDataViewItem & item ) const
{
2020-01-30 14:42:59 +01:00
// move instance(s) or object on "empty place" of ObjectList
if ( ( m_dragged_data . type () & ( itInstance | itObject )) && ! item . IsOk () )
return true ;
// type of moved item should be the same as a "destination" item
if ( ! item . IsOk () || ! ( m_dragged_data . type () & ( itVolume | itObject )) ||
m_objects_model -> GetItemType ( item ) != m_dragged_data . type () )
return false ;
// move volumes inside one object only
2021-05-26 15:36:02 +02:00
if ( m_dragged_data . type () & itVolume ) {
if ( m_dragged_data . obj_idx () != m_objects_model -> GetObjectIdByItem ( item ))
return false ;
wxDataViewItem dragged_item = m_objects_model -> GetItemByVolumeId ( m_dragged_data . obj_idx (), m_dragged_data . sub_obj_idx ());
if ( ! dragged_item )
return false ;
ModelVolumeType item_v_type = m_objects_model -> GetVolumeType ( item );
ModelVolumeType dragged_item_v_type = m_objects_model -> GetVolumeType ( dragged_item );
if ( dragged_item_v_type == item_v_type && dragged_item_v_type != ModelVolumeType :: MODEL_PART )
return true ;
if ( wxGetApp (). app_config -> get ( "order_volumes" ) == "1" || // we can't reorder volumes outside of types
item_v_type >= ModelVolumeType :: SUPPORT_BLOCKER ) // support blockers/enforcers can't change its place
return false ;
bool only_one_solid_part = true ;
auto & volumes = ( * m_objects )[ m_dragged_data . obj_idx ()] -> volumes ;
for ( size_t cnt , id = cnt = 0 ; id < volumes . size () && cnt < 2 ; id ++ )
if ( volumes [ id ] -> type () == ModelVolumeType :: MODEL_PART ) {
if ( ++ cnt > 1 )
only_one_solid_part = false ;
}
if ( dragged_item_v_type == ModelVolumeType :: MODEL_PART ) {
if ( only_one_solid_part )
return false ;
return ( m_objects_model -> GetVolumeIdByItem ( item ) == 0 ||
( m_dragged_data . sub_obj_idx () == 0 && volumes [ 1 ] -> type () == ModelVolumeType :: MODEL_PART ) ||
( m_dragged_data . sub_obj_idx () != 0 && volumes [ 0 ] -> type () == ModelVolumeType :: MODEL_PART ));
}
if (( dragged_item_v_type == ModelVolumeType :: NEGATIVE_VOLUME || dragged_item_v_type == ModelVolumeType :: PARAMETER_MODIFIER )) {
if ( only_one_solid_part )
return false ;
return m_objects_model -> GetVolumeIdByItem ( item ) != 0 ;
}
return false ;
}
2020-01-30 14:42:59 +01:00
return true ;
2019-01-22 16:40:10 +01:00
}
2018-12-11 09:37:58 +01:00
void ObjectList :: OnDropPossible ( wxDataViewEvent & event )
2018-10-05 23:29:15 +02:00
{
2019-01-22 16:40:10 +01:00
const wxDataViewItem & item = event . GetItem ();
2018-10-05 23:29:15 +02:00
2021-01-07 09:56:43 +01:00
if ( ! can_drop ( item )) {
2018-10-05 23:29:15 +02:00
event . Veto ();
2021-01-07 09:56:43 +01:00
m_prevent_list_events = false ;
}
2018-10-05 23:29:15 +02:00
}
2018-12-11 09:37:58 +01:00
void ObjectList :: OnDrop ( wxDataViewEvent & event )
2018-10-05 23:29:15 +02:00
{
2019-01-22 16:40:10 +01:00
const wxDataViewItem & item = event . GetItem ();
2018-10-05 23:29:15 +02:00
2019-01-22 16:40:10 +01:00
if ( ! can_drop ( item ))
{
2018-10-05 23:29:15 +02:00
event . Veto ();
2018-12-12 08:40:10 +01:00
m_dragged_data . clear ();
2018-10-05 23:29:15 +02:00
return ;
}
2019-01-22 16:40:10 +01:00
if ( m_dragged_data . type () == itInstance )
{
2019-07-22 13:38:53 +02:00
Plater :: TakeSnapshot snapshot ( wxGetApp (). plater (), _ ( L ( "Instances to Separated Objects" )));
2019-01-23 16:01:37 +01:00
instances_to_separated_object ( m_dragged_data . obj_idx (), m_dragged_data . inst_idxs ());
2019-01-22 16:40:10 +01:00
m_dragged_data . clear ();
return ;
}
2019-08-29 11:16:14 +02:00
take_snapshot ( _ (( m_dragged_data . type () == itVolume ) ? L ( "Volumes in Object reordered" ) : L ( "Object reordered" )));
2019-07-04 17:33:19 +02:00
2020-01-30 14:42:59 +01:00
if ( m_dragged_data . type () & itVolume )
{
int from_volume_id = m_dragged_data . sub_obj_idx ();
int to_volume_id = m_objects_model -> GetVolumeIdByItem ( item );
int delta = to_volume_id < from_volume_id ? - 1 : 1 ;
2018-10-05 23:29:15 +02:00
2020-01-30 14:42:59 +01:00
auto & volumes = ( * m_objects )[ m_dragged_data . obj_idx ()] -> volumes ;
int cnt = 0 ;
for ( int id = from_volume_id ; cnt < abs ( from_volume_id - to_volume_id ); id += delta , cnt ++ )
std :: swap ( volumes [ id ], volumes [ id + delta ]);
select_item ( m_objects_model -> ReorganizeChildren ( from_volume_id , to_volume_id , m_objects_model -> GetParent ( item )));
}
else if ( m_dragged_data . type () & itObject )
{
int from_obj_id = m_dragged_data . obj_idx ();
int to_obj_id = item . IsOk () ? m_objects_model -> GetIdByItem ( item ) : (( int ) m_objects -> size () - 1 );
int delta = to_obj_id < from_obj_id ? - 1 : 1 ;
int cnt = 0 ;
for ( int id = from_obj_id ; cnt < abs ( from_obj_id - to_obj_id ); id += delta , cnt ++ )
std :: swap (( * m_objects )[ id ], ( * m_objects )[ id + delta ]);
select_item ( m_objects_model -> ReorganizeObjects ( from_obj_id , to_obj_id ));
}
2018-10-11 15:57:09 +02:00
2019-04-22 01:51:10 +02:00
changed_object ( m_dragged_data . obj_idx ());
2018-10-05 23:29:15 +02:00
2018-12-12 08:40:10 +01:00
m_dragged_data . clear ();
2020-02-06 15:19:53 +01:00
wxGetApp (). plater () -> set_current_canvas_as_dirty ();
2018-10-05 23:29:15 +02:00
}
2021-03-15 10:04:45 +01:00
void ObjectList :: add_category_to_settings_from_selection ( const std :: vector < std :: pair < std :: string , bool > >& category_options , wxDataViewItem item )
2018-10-05 23:29:15 +02:00
{
2021-03-15 10:04:45 +01:00
if ( category_options . empty ())
return ;
2018-10-05 23:29:15 +02:00
2019-08-01 15:38:00 +02:00
const ItemType item_type = m_objects_model -> GetItemType ( item );
2019-07-22 09:41:34 +02:00
if ( ! m_config )
m_config = & get_item_config ( item );
assert ( m_config );
2018-10-05 23:29:15 +02:00
auto opt_keys = m_config -> keys ();
2021-03-15 10:04:45 +01:00
const wxString snapshot_text = item_type & itLayer ? _L ( "Add Settings for Layers" ) :
item_type & itVolume ? _L ( "Add Settings for Sub-object" ) :
_L ( "Add Settings for Object" );
2019-08-01 15:38:00 +02:00
take_snapshot ( snapshot_text );
2019-07-10 11:28:11 +02:00
2019-03-05 10:46:57 +01:00
const DynamicPrintConfig & from_config = printer_technology () == ptFFF ?
wxGetApp (). preset_bundle -> prints . get_edited_preset (). config :
wxGetApp (). preset_bundle -> sla_prints . get_edited_preset (). config ;
2019-01-28 12:13:53 +01:00
2021-03-15 10:04:45 +01:00
for ( auto & opt : category_options ) {
auto & opt_key = opt . first ;
if ( find ( opt_keys . begin (), opt_keys . end (), opt_key ) != opt_keys . end () && ! opt . second )
2018-10-05 23:29:15 +02:00
m_config -> erase ( opt_key );
2021-03-15 10:04:45 +01:00
if ( find ( opt_keys . begin (), opt_keys . end (), opt_key ) == opt_keys . end () && opt . second ) {
2019-01-28 12:13:53 +01:00
const ConfigOption * option = from_config . option ( opt_key );
if ( ! option ) {
// if current option doesn't exist in prints.get_edited_preset(),
2019-01-30 14:45:18 +01:00
// get it from default config values
option = DynamicPrintConfig :: new_from_defaults_keys ({ opt_key }) -> option ( opt_key );
2019-01-28 12:13:53 +01:00
}
m_config -> set_key_value ( opt_key , option -> clone ());
}
2018-10-05 23:29:15 +02:00
}
2019-07-11 23:46:23 +02:00
// Add settings item for object/sub-object and show them
2019-08-01 15:38:00 +02:00
if ( ! ( item_type & ( itObject | itVolume | itLayer )))
2019-07-22 09:41:34 +02:00
item = m_objects_model -> GetTopParent ( item );
2020-09-24 15:34:13 +02:00
show_settings ( add_settings_item ( item , & m_config -> get ()));
2019-01-25 16:39:50 +01:00
}
2021-03-15 10:04:45 +01:00
void ObjectList :: add_category_to_settings_from_frequent ( const std :: vector < std :: string >& options , wxDataViewItem item )
2019-01-25 16:39:50 +01:00
{
2019-12-12 15:54:53 +01:00
const ItemType item_type = m_objects_model -> GetItemType ( item );
2019-08-01 15:38:00 +02:00
2019-07-22 09:41:34 +02:00
if ( ! m_config )
m_config = & get_item_config ( item );
2019-05-03 16:05:49 +02:00
assert ( m_config );
2019-01-25 16:39:50 +01:00
auto opt_keys = m_config -> keys ();
2021-03-15 10:04:45 +01:00
const wxString snapshot_text = item_type & itLayer ? _L ( "Add Settings Bundle for Height range" ) :
item_type & itVolume ? _L ( "Add Settings Bundle for Sub-object" ) :
_L ( "Add Settings Bundle for Object" );
2019-08-01 15:38:00 +02:00
take_snapshot ( snapshot_text );
2019-07-10 11:28:11 +02:00
2019-01-28 12:13:53 +01:00
const DynamicPrintConfig & from_config = wxGetApp (). preset_bundle -> prints . get_edited_preset (). config ;
2019-01-25 16:39:50 +01:00
for ( auto & opt_key : options )
{
2019-01-28 12:13:53 +01:00
if ( find ( opt_keys . begin (), opt_keys . end (), opt_key ) == opt_keys . end ()) {
const ConfigOption * option = from_config . option ( opt_key );
if ( ! option ) {
// if current option doesn't exist in prints.get_edited_preset(),
2019-01-30 14:45:18 +01:00
// get it from default config values
option = DynamicPrintConfig :: new_from_defaults_keys ({ opt_key }) -> option ( opt_key );
2019-01-28 12:13:53 +01:00
}
m_config -> set_key_value ( opt_key , option -> clone ());
}
2019-01-25 16:39:50 +01:00
}
2019-07-11 23:46:23 +02:00
// Add settings item for object/sub-object and show them
2019-08-01 15:38:00 +02:00
if ( ! ( item_type & ( itObject | itVolume | itLayer )))
2019-07-22 09:41:34 +02:00
item = m_objects_model -> GetTopParent ( item );
2020-09-24 15:34:13 +02:00
show_settings ( add_settings_item ( item , & m_config -> get ()));
2019-01-25 16:39:50 +01:00
}
2019-07-11 23:46:23 +02:00
void ObjectList :: show_settings ( const wxDataViewItem settings_item )
2019-01-25 16:39:50 +01:00
{
2019-07-11 23:46:23 +02:00
if ( ! settings_item )
return ;
select_item ( settings_item );
// update object selection on Plater
if ( ! m_prevent_canvas_selection_update )
update_selections_on_canvas ();
2018-10-05 23:29:15 +02:00
}
2019-12-11 16:07:00 +01:00
bool ObjectList :: is_instance_or_object_selected ()
{
const Selection & selection = scene_selection ();
return selection . is_single_full_instance () || selection . is_single_full_object ();
}
2019-02-22 12:12:10 +01:00
void ObjectList :: load_subobject ( ModelVolumeType type )
2018-10-05 23:29:15 +02:00
{
2019-06-17 15:33:58 +02:00
wxDataViewItem item = GetSelection ();
// we can add volumes for Object or Instance
if ( ! item || ! ( m_objects_model -> GetItemType ( item ) & ( itObject | itInstance )))
2018-10-05 23:29:15 +02:00
return ;
2019-06-17 15:33:58 +02:00
const int obj_idx = m_objects_model -> GetObjectIdByItem ( item );
2018-10-05 23:29:15 +02:00
if ( obj_idx < 0 ) return ;
2019-04-29 15:27:59 +02:00
2019-06-17 15:33:58 +02:00
// Get object item, if Instance is selected
if ( m_objects_model -> GetItemType ( item ) & itInstance )
item = m_objects_model -> GetItemById ( obj_idx );
2021-03-04 15:09:50 +01:00
take_snapshot ( _L ( "Load Part" ));
2019-07-04 17:33:19 +02:00
2019-04-29 15:27:59 +02:00
std :: vector < std :: pair < wxString , bool >> volumes_info ;
load_part (( * m_objects )[ obj_idx ], volumes_info , type );
2019-04-22 01:51:10 +02:00
changed_object ( obj_idx );
2019-08-16 13:14:51 +02:00
if ( type == ModelVolumeType :: MODEL_PART )
// update printable state on canvas
wxGetApp (). plater () -> canvas3D () -> update_instance_printable_state_for_object (( size_t ) obj_idx );
2018-10-05 23:29:15 +02:00
2019-04-29 15:27:59 +02:00
wxDataViewItem sel_item ;
for ( const auto & volume : volumes_info )
sel_item = m_objects_model -> AddVolumeChild ( item , volume . first , type , volume . second );
if ( sel_item )
select_item ( sel_item );
2019-09-17 15:30:54 +02:00
selection_changed ();
2018-10-05 23:29:15 +02:00
}
void ObjectList :: load_part ( ModelObject * model_object ,
2019-04-29 15:27:59 +02:00
std :: vector < std :: pair < wxString , bool >> & volumes_info ,
2019-02-22 12:12:10 +01:00
ModelVolumeType type )
2018-10-05 23:29:15 +02:00
{
wxWindow * parent = wxGetApp (). tab_panel () -> GetPage ( 0 );
wxArrayString input_files ;
2018-11-15 15:27:39 +01:00
wxGetApp (). import_model ( parent , input_files );
2021-03-04 15:09:50 +01:00
wxProgressDialog dlg ( _L ( "Loading" ) + dots , "" , 100 , wxGetApp (). plater (), wxPD_AUTO_HIDE );
wxBusyCursor busy ;
2019-09-02 09:41:12 +02:00
for ( size_t i = 0 ; i < input_files . size (); ++ i ) {
2019-01-03 14:34:53 +01:00
std :: string input_file = input_files . Item ( i ). ToUTF8 (). data ();
2018-10-05 23:29:15 +02:00
2021-03-04 15:09:50 +01:00
dlg . Update ( static_cast < int > ( 100.0f * static_cast < float > ( i ) / static_cast < float > ( input_files . size ())),
_L ( "Loading file" ) + ": " + from_path ( boost :: filesystem :: path ( input_file ). filename ()));
dlg . Fit ();
2018-10-05 23:29:15 +02:00
Model model ;
try {
model = Model :: read_from_file ( input_file );
}
catch ( std :: exception & e ) {
2021-03-04 15:09:50 +01:00
auto msg = _L ( "Error!" ) + " " + input_file + " : " + e . what () + "." ;
2018-10-05 23:29:15 +02:00
show_error ( parent , msg );
exit ( 1 );
}
for ( auto object : model . objects ) {
Vec3d delta = Vec3d :: Zero ();
2021-03-04 15:09:50 +01:00
if ( model_object -> origin_translation != Vec3d :: Zero ()) {
2018-10-05 23:29:15 +02:00
object -> center_around_origin ();
delta = model_object -> origin_translation - object -> origin_translation ;
}
for ( auto volume : object -> volumes ) {
2018-11-12 08:54:22 +01:00
volume -> translate ( delta );
2021-05-26 15:36:02 +02:00
auto new_volume = model_object -> add_volume ( * volume , type );
2018-10-05 23:29:15 +02:00
new_volume -> name = boost :: filesystem :: path ( input_file ). filename (). string ();
2019-04-29 15:27:59 +02:00
volumes_info . push_back ( std :: make_pair ( from_u8 ( new_volume -> name ), new_volume -> get_mesh_errors_count () > 0 ));
2018-10-05 23:29:15 +02:00
// set a default extruder value, since user can't add it manually
new_volume -> config . set_key_value ( "extruder" , new ConfigOptionInt ( 0 ));
}
}
}
}
2019-09-19 12:30:16 +02:00
static TriangleMesh create_mesh ( const std :: string & type_name , const BoundingBoxf3 & bb )
{
TriangleMesh mesh ;
const double side = wxGetApp (). plater () -> canvas3D () -> get_size_proportional_to_max_bed_size ( 0.1 );
if ( type_name == "Box" )
// Sitting on the print bed, left front front corner at (0, 0).
mesh = make_cube ( side , side , side );
else if ( type_name == "Cylinder" )
// Centered around 0, sitting on the print bed.
// The cylinder has the same volume as the box above.
mesh = make_cylinder ( 0.564 * side , side );
else if ( type_name == "Sphere" )
// Centered around 0, half the sphere below the print bed, half above.
// The sphere has the same volume as the box above.
mesh = make_sphere ( 0.62 * side , PI / 18 );
else if ( type_name == "Slab" )
// Sitting on the print bed, left front front corner at (0, 0).
mesh = make_cube ( bb . size (). x () * 1.5 , bb . size (). y () * 1.5 , bb . size (). z () * 0.5 );
mesh . repair ();
return mesh ;
}
2019-02-22 12:12:10 +01:00
void ObjectList :: load_generic_subobject ( const std :: string & type_name , const ModelVolumeType type )
2018-10-05 23:29:15 +02:00
{
2019-09-19 12:30:16 +02:00
if ( type == ModelVolumeType :: INVALID ) {
load_shape_object ( type_name );
return ;
}
2019-09-02 09:41:12 +02:00
const int obj_idx = get_selected_obj_idx ();
2019-02-28 11:20:01 +01:00
if ( obj_idx < 0 )
return ;
2019-12-11 16:07:00 +01:00
const Selection & selection = scene_selection ();
2019-02-28 11:20:01 +01:00
assert ( obj_idx == selection . get_object_idx ());
2019-03-07 10:08:21 +01:00
/** Any changes of the Object's composition is duplicated for all Object's Instances
* So, It's enough to take a bounding box of a first selected Instance and calculate Part(generic_subobject) position
*/
int instance_idx = * selection . get_instance_idxs (). begin ();
2019-02-28 11:20:01 +01:00
assert ( instance_idx != - 1 );
if ( instance_idx == - 1 )
return ;
2019-07-04 17:33:19 +02:00
take_snapshot ( _ ( L ( "Add Generic Subobject" )));
2019-02-28 11:20:01 +01:00
// Selected object
ModelObject & model_object = * ( * m_objects )[ obj_idx ];
// Bounding box of the selected instance in world coordinate system including the translation, without modifiers.
BoundingBoxf3 instance_bb = model_object . instance_bounding_box ( instance_idx );
2018-10-05 23:29:15 +02:00
2019-09-19 12:30:16 +02:00
TriangleMesh mesh = create_mesh ( type_name , instance_bb );
2018-11-12 08:54:22 +01:00
2019-02-28 11:20:01 +01:00
// Mesh will be centered when loading.
2021-05-26 15:36:02 +02:00
ModelVolume * new_volume = model_object . add_volume ( std :: move ( mesh ), type );
2018-10-05 23:29:15 +02:00
2019-01-14 13:29:06 +01:00
if ( instance_idx != - 1 )
{
2019-02-28 11:20:01 +01:00
// First (any) GLVolume of the selected instance. They all share the same instance matrix.
2019-01-14 13:29:06 +01:00
const GLVolume * v = selection . get_volume ( * selection . get_volume_idxs (). begin ());
2019-02-28 11:20:01 +01:00
// Transform the new modifier to be aligned with the print bed.
2019-06-11 17:08:47 +02:00
const BoundingBoxf3 mesh_bb = new_volume -> mesh (). bounding_box ();
2019-06-24 12:26:11 +02:00
new_volume -> set_transformation ( Geometry :: Transformation :: volume_to_bed_transformation ( v -> get_instance_transformation (), mesh_bb ));
2019-02-28 11:20:01 +01:00
// Set the modifier position.
auto offset = ( type_name == "Slab" ) ?
// Slab: Lift to print bed
Vec3d ( 0. , 0. , 0.5 * mesh_bb . size (). z () + instance_bb . min . z () - v -> get_instance_offset (). z ()) :
// Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed.
Vec3d ( instance_bb . max ( 0 ), instance_bb . min ( 1 ), instance_bb . min ( 2 )) + 0.5 * mesh_bb . size () - v -> get_instance_offset ();
new_volume -> set_offset ( v -> get_instance_transformation (). get_matrix ( true ). inverse () * offset );
2019-01-14 13:29:06 +01:00
}
2019-09-19 12:30:16 +02:00
const wxString name = _ ( L ( "Generic" )) + "-" + _ ( type_name );
2019-02-22 14:33:01 +01:00
new_volume -> name = into_u8 ( name );
2018-10-05 23:29:15 +02:00
// set a default extruder value, since user can't add it manually
new_volume -> config . set_key_value ( "extruder" , new ConfigOptionInt ( 0 ));
2019-04-22 01:51:10 +02:00
changed_object ( obj_idx );
2019-08-16 13:14:51 +02:00
if ( type == ModelVolumeType :: MODEL_PART )
// update printable state on canvas
wxGetApp (). plater () -> canvas3D () -> update_instance_printable_state_for_object (( size_t ) obj_idx );
2018-10-05 23:29:15 +02:00
2019-01-09 10:30:29 +01:00
const auto object_item = m_objects_model -> GetTopParent ( GetSelection ());
2020-12-04 14:19:08 +01:00
select_item ([ this , object_item , name , type , new_volume ]() { return m_objects_model -> AddVolumeChild ( object_item , name , type ,
new_volume -> get_mesh_errors_count () > 0 ); });
2018-10-11 15:57:09 +02:00
selection_changed ();
2018-10-05 23:29:15 +02:00
}
2019-09-19 12:30:16 +02:00
void ObjectList :: load_shape_object ( const std :: string & type_name )
{
const Selection & selection = wxGetApp (). plater () -> canvas3D () -> get_selection ();
assert ( selection . get_object_idx () == - 1 ); // Add nothing is something is selected on 3DScene
if ( selection . get_object_idx () != - 1 )
return ;
const int obj_idx = m_objects -> size ();
if ( obj_idx < 0 )
return ;
2021-04-06 13:17:29 +02:00
take_snapshot ( _L ( "Add Shape" ));
2019-09-19 12:30:16 +02:00
// Create mesh
BoundingBoxf3 bb ;
TriangleMesh mesh = create_mesh ( type_name , bb );
2021-04-06 13:17:29 +02:00
load_mesh_object ( mesh , _L ( "Shape" ) + "-" + _ ( type_name ));
#if ENABLE_PROJECT_DIRTY_STATE
wxGetApp (). mainframe -> update_title ();
#endif // ENABLE_PROJECT_DIRTY_STATE
2020-04-23 19:12:07 +02:00
}
2019-09-19 12:30:16 +02:00
2020-04-29 16:26:39 +02:00
void ObjectList :: load_mesh_object ( const TriangleMesh & mesh , const wxString & name , bool center )
2020-04-23 19:12:07 +02:00
{
2019-09-19 12:30:16 +02:00
// Add mesh to model as a new object
Model & model = wxGetApp (). plater () -> model ();
#ifdef _DEBUG
check_model_ids_validity ( model );
#endif /* _DEBUG */
2020-04-23 19:12:07 +02:00
2019-09-19 12:30:16 +02:00
std :: vector < size_t > object_idxs ;
2020-04-29 16:26:39 +02:00
auto bb = mesh . bounding_box ();
2019-09-19 12:30:16 +02:00
ModelObject * new_object = model . add_object ();
new_object -> name = into_u8 ( name );
new_object -> add_instance (); // each object should have at list one instance
2020-04-23 19:12:07 +02:00
2019-09-19 12:30:16 +02:00
ModelVolume * new_volume = new_object -> add_volume ( mesh );
new_volume -> name = into_u8 ( name );
// set a default extruder value, since user can't add it manually
new_volume -> config . set_key_value ( "extruder" , new ConfigOptionInt ( 0 ));
new_object -> invalidate_bounding_box ();
2020-04-29 16:26:39 +02:00
new_object -> translate ( - bb . center ());
if ( center ) {
const BoundingBoxf bed_shape = wxGetApp (). plater () -> bed_shape_bb ();
new_object -> instances [ 0 ] -> set_offset ( Slic3r :: to_3d ( bed_shape . center (). cast < double > (), - new_object -> origin_translation ( 2 )));
} else {
new_object -> instances [ 0 ] -> set_offset ( bb . center ());
}
2019-09-25 11:25:14 +02:00
new_object -> ensure_on_bed ();
2020-04-29 16:26:39 +02:00
2019-09-19 12:30:16 +02:00
object_idxs . push_back ( model . objects . size () - 1 );
#ifdef _DEBUG
check_model_ids_validity ( model );
#endif /* _DEBUG */
2020-04-23 19:12:07 +02:00
2019-09-19 12:30:16 +02:00
paste_objects_into_list ( object_idxs );
#ifdef _DEBUG
check_model_ids_validity ( model );
#endif /* _DEBUG */
}
2018-11-13 14:17:35 +01:00
void ObjectList :: del_object ( const int obj_idx )
{
wxGetApp (). plater () -> delete_object_from_model ( obj_idx );
}
2018-10-05 23:29:15 +02:00
// Delete subobject
2018-10-12 12:00:37 +02:00
void ObjectList :: del_subobject_item ( wxDataViewItem & item )
2018-10-05 23:29:15 +02:00
{
if ( ! item ) return ;
2018-10-16 11:08:37 +02:00
int obj_idx , idx ;
ItemType type ;
2018-10-12 12:00:37 +02:00
2018-10-16 11:08:37 +02:00
m_objects_model -> GetItemInfo ( item , type , obj_idx , idx );
2019-07-23 17:09:19 +02:00
if ( type == itUndef )
2018-10-05 23:29:15 +02:00
return ;
2021-04-19 11:51:56 +02:00
wxDataViewItem parent = m_objects_model -> GetParent ( item );
2019-06-06 14:14:29 +02:00
if ( type & itSettings )
2021-04-19 11:51:56 +02:00
del_settings_from_config ( parent );
2019-06-06 14:14:29 +02:00
else if ( type & itInstanceRoot && obj_idx != - 1 )
2018-10-16 11:08:37 +02:00
del_instances_from_object ( obj_idx );
2019-06-06 14:14:29 +02:00
else if ( type & itLayerRoot && obj_idx != - 1 )
2019-05-30 16:53:17 +02:00
del_layers_from_object ( obj_idx );
2019-06-06 14:14:29 +02:00
else if ( type & itLayer && obj_idx != - 1 )
del_layer_from_object ( obj_idx , m_objects_model -> GetLayerRangeByItem ( item ));
2021-04-19 11:51:56 +02:00
else if ( type & itInfo && obj_idx != - 1 ) {
Unselect ( item );
Select ( parent );
}
2018-10-16 11:08:37 +02:00
else if ( idx == - 1 )
return ;
else if ( ! del_subobject_from_object ( obj_idx , idx , type ))
2018-10-05 23:29:15 +02:00
return ;
2019-04-29 15:27:59 +02:00
// If last volume item with warning was deleted, unmark object item
2019-06-06 14:14:29 +02:00
if ( type & itVolume && ( * m_objects )[ obj_idx ] -> get_mesh_errors_count () == 0 )
2021-04-19 11:51:56 +02:00
m_objects_model -> DeleteWarningIcon ( parent );
2019-04-29 15:27:59 +02:00
2018-10-12 12:00:37 +02:00
m_objects_model -> Delete ( item );
2021-04-14 07:38:50 +02:00
update_info_items ( obj_idx );
2018-10-05 23:29:15 +02:00
}
2019-06-06 14:14:29 +02:00
void ObjectList :: del_settings_from_config ( const wxDataViewItem & parent_item )
2018-10-05 23:29:15 +02:00
{
2019-06-06 14:14:29 +02:00
const bool is_layer_settings = m_objects_model -> GetItemType ( parent_item ) == itLayer ;
2019-09-02 09:41:12 +02:00
const size_t opt_cnt = m_config -> keys (). size ();
if (( opt_cnt == 1 && m_config -> has ( "extruder" )) ||
( is_layer_settings && opt_cnt == 2 && m_config -> has ( "extruder" ) && m_config -> has ( "layer_height" )))
2018-10-05 23:29:15 +02:00
return ;
2019-06-06 14:14:29 +02:00
2019-07-11 18:42:02 +02:00
take_snapshot ( _ ( L ( "Delete Settings" )));
2020-09-24 15:34:13 +02:00
int extruder = m_config -> has ( "extruder" ) ? m_config -> extruder () : - 1 ;
2018-10-05 23:29:15 +02:00
2019-06-06 14:14:29 +02:00
coordf_t layer_height = 0.0 ;
if ( is_layer_settings )
layer_height = m_config -> opt_float ( "layer_height" );
2018-10-05 23:29:15 +02:00
m_config -> clear ();
if ( extruder >= 0 )
m_config -> set_key_value ( "extruder" , new ConfigOptionInt ( extruder ));
2019-06-06 14:14:29 +02:00
if ( is_layer_settings )
m_config -> set_key_value ( "layer_height" , new ConfigOptionFloat ( layer_height ));
2020-12-29 14:51:19 +01:00
changed_object ();
2018-10-05 23:29:15 +02:00
}
2018-10-16 11:08:37 +02:00
void ObjectList :: del_instances_from_object ( const int obj_idx )
2018-10-05 23:29:15 +02:00
{
2018-10-19 11:02:50 +02:00
auto & instances = ( * m_objects )[ obj_idx ] -> instances ;
2018-10-16 11:08:37 +02:00
if ( instances . size () <= 1 )
return ;
2018-10-05 23:29:15 +02:00
2019-07-04 17:33:19 +02:00
take_snapshot ( _ ( L ( "Delete All Instances from Object" )));
2018-10-16 11:08:37 +02:00
while ( instances . size () > 1 )
instances . pop_back ();
( * m_objects )[ obj_idx ] -> invalidate_bounding_box (); // ? #ys_FIXME
2019-04-22 01:51:10 +02:00
changed_object ( obj_idx );
2018-10-16 11:08:37 +02:00
}
2019-06-06 14:14:29 +02:00
void ObjectList :: del_layer_from_object ( const int obj_idx , const t_layer_height_range & layer_range )
{
const auto del_range = object ( obj_idx ) -> layer_config_ranges . find ( layer_range );
if ( del_range == object ( obj_idx ) -> layer_config_ranges . end ())
return ;
2019-07-11 18:42:02 +02:00
2019-08-27 11:39:51 +02:00
take_snapshot ( _ ( L ( "Delete Height Range" )));
2019-06-06 14:14:29 +02:00
object ( obj_idx ) -> layer_config_ranges . erase ( del_range );
changed_object ( obj_idx );
}
2019-05-30 16:53:17 +02:00
void ObjectList :: del_layers_from_object ( const int obj_idx )
{
2019-06-03 15:35:21 +02:00
object ( obj_idx ) -> layer_config_ranges . clear ();
2019-05-30 16:53:17 +02:00
changed_object ( obj_idx );
}
2018-10-16 11:08:37 +02:00
bool ObjectList :: del_subobject_from_object ( const int obj_idx , const int idx , const int type )
{
2020-07-24 11:21:49 +02:00
assert ( idx >= 0 );
if ( obj_idx == 1000 || idx < 0 )
// Cannot delete a wipe tower or volume with negative id
2018-12-18 19:12:59 +01:00
return false ;
2019-03-13 16:40:11 +01:00
ModelObject * object = ( * m_objects )[ obj_idx ];
2018-10-16 11:08:37 +02:00
if ( type == itVolume ) {
2019-03-13 16:40:11 +01:00
const auto volume = object -> volumes [ idx ];
2018-10-16 11:08:37 +02:00
// if user is deleting the last solid part, throw error
int solid_cnt = 0 ;
2019-03-13 16:40:11 +01:00
for ( auto vol : object -> volumes )
2018-10-16 11:08:37 +02:00
if ( vol -> is_model_part ())
++ solid_cnt ;
if ( volume -> is_model_part () && solid_cnt == 1 ) {
2019-08-02 17:53:12 +02:00
Slic3r :: GUI :: show_error ( nullptr , _ ( L ( "From Object List You can't delete the last solid part from object." )));
2018-10-16 11:08:37 +02:00
return false ;
}
2019-07-04 17:33:19 +02:00
take_snapshot ( _ ( L ( "Delete Subobject" )));
2019-03-13 16:40:11 +01:00
object -> delete_volume ( idx );
if ( object -> volumes . size () == 1 )
{
const auto last_volume = object -> volumes [ 0 ];
if ( ! last_volume -> config . empty ()) {
object -> config . apply ( last_volume -> config );
last_volume -> config . clear ();
2021-02-22 18:50:01 +01:00
// update extruder color in ObjectList
wxDataViewItem obj_item = m_objects_model -> GetItemById ( obj_idx );
if ( obj_item ) {
wxString extruder = object -> config . has ( "extruder" ) ? wxString :: Format ( "%d" , object -> config . extruder ()) : _L ( "default" );
m_objects_model -> SetExtruder ( extruder , obj_item );
}
2021-03-15 10:04:45 +01:00
// add settings to the object, if it has them
add_settings_item ( obj_item , & object -> config . get ());
2019-03-13 16:40:11 +01:00
}
}
2018-10-05 23:29:15 +02:00
}
2018-10-18 10:40:26 +02:00
else if ( type == itInstance ) {
2019-03-13 16:40:11 +01:00
if ( object -> instances . size () == 1 ) {
2019-08-05 14:54:29 +02:00
Slic3r :: GUI :: show_error ( nullptr , _ ( L ( "Last instance of an object cannot be deleted." )));
2018-10-18 10:40:26 +02:00
return false ;
}
2019-07-04 17:33:19 +02:00
take_snapshot ( _ ( L ( "Delete Instance" )));
2019-03-13 16:40:11 +01:00
object -> delete_instance ( idx );
2018-10-18 10:40:26 +02:00
}
2018-10-16 11:08:37 +02:00
else
return false ;
2018-10-05 23:29:15 +02:00
2019-04-22 01:51:10 +02:00
changed_object ( obj_idx );
2018-10-05 23:29:15 +02:00
return true ;
}
2018-12-06 14:49:14 +01:00
void ObjectList :: split ()
2018-10-05 23:29:15 +02:00
{
2018-10-08 16:27:38 +02:00
const auto item = GetSelection ();
2018-11-06 09:06:26 +01:00
const int obj_idx = get_selected_obj_idx ();
if ( ! item || obj_idx < 0 )
2018-10-05 23:29:15 +02:00
return ;
2018-11-06 09:06:26 +01:00
2018-10-05 23:29:15 +02:00
ModelVolume * volume ;
2018-12-06 14:49:14 +01:00
if ( ! get_volume_by_item ( item , volume )) return ;
2019-03-14 13:15:04 +01:00
DynamicPrintConfig & config = printer_config ();
2018-11-08 20:44:07 +01:00
const ConfigOption * nozzle_dmtrs_opt = config . option ( "nozzle_diameter" , false );
const auto nozzle_dmrs_cnt = ( nozzle_dmtrs_opt == nullptr ) ? size_t ( 1 ) : dynamic_cast < const ConfigOptionFloats *> ( nozzle_dmtrs_opt ) -> values . size ();
2019-08-22 15:42:23 +02:00
if ( ! volume -> is_splittable ()) {
2018-10-05 23:29:15 +02:00
wxMessageBox ( _ ( L ( "The selected object couldn't be split because it contains only one part." )));
return ;
}
2019-07-04 17:33:19 +02:00
take_snapshot ( _ ( L ( "Split to Parts" )));
2019-08-22 15:42:23 +02:00
volume -> split ( nozzle_dmrs_cnt );
2019-02-05 20:23:24 +01:00
wxBusyCursor wait ;
2018-11-06 09:06:26 +01:00
auto model_object = ( * m_objects )[ obj_idx ];
2018-10-05 23:29:15 +02:00
2018-11-06 09:06:26 +01:00
auto parent = m_objects_model -> GetTopParent ( item );
if ( parent )
m_objects_model -> DeleteVolumeChildren ( parent );
else
parent = item ;
2018-10-05 23:29:15 +02:00
2019-04-29 15:27:59 +02:00
for ( const ModelVolume * volume : model_object -> volumes ) {
const wxDataViewItem & vol_item = m_objects_model -> AddVolumeChild ( parent , from_u8 ( volume -> name ),
volume -> is_modifier () ? ModelVolumeType :: PARAMETER_MODIFIER : ModelVolumeType :: MODEL_PART ,
volume -> get_mesh_errors_count () > 0 ,
2020-09-24 15:34:13 +02:00
volume -> config . has ( "extruder" ) ? volume -> config . extruder () : 0 ,
2019-04-11 11:09:32 +02:00
false );
2018-11-06 09:06:26 +01:00
// add settings to the part, if it has those
2020-09-24 15:34:13 +02:00
add_settings_item ( vol_item , & volume -> config . get ());
2018-10-05 23:29:15 +02:00
}
2020-02-07 09:49:48 +01:00
model_object -> input_file . clear ();
2018-11-06 15:34:44 +01:00
if ( parent == item )
Expand ( parent );
2019-04-22 01:51:10 +02:00
changed_object ( obj_idx );
2018-10-05 23:29:15 +02:00
}
2020-05-27 10:55:48 +02:00
void ObjectList :: merge ( bool to_multipart_object )
{
// merge selected objects to the multipart object
if ( to_multipart_object )
{
2020-05-27 22:29:18 +02:00
auto get_object_idxs = [ this ]( std :: vector < int >& obj_idxs , wxDataViewItemArray & sels )
{
// check selections and split instances to the separated objects...
bool instance_selection = false ;
for ( wxDataViewItem item : sels )
if ( m_objects_model -> GetItemType ( item ) & itInstance ) {
instance_selection = true ;
break ;
}
if ( ! instance_selection )
{
for ( wxDataViewItem item : sels ) {
assert ( m_objects_model -> GetItemType ( item ) & itObject );
obj_idxs . emplace_back ( m_objects_model -> GetIdByItem ( item ));
}
return ;
}
// map of obj_idx -> set of selected instance_idxs
std :: map < int , std :: set < int >> sel_map ;
std :: set < int > empty_set ;
for ( wxDataViewItem item : sels ) {
if ( m_objects_model -> GetItemType ( item ) & itObject )
{
int obj_idx = m_objects_model -> GetIdByItem ( item );
int inst_cnt = ( * m_objects )[ obj_idx ] -> instances . size ();
if ( inst_cnt == 1 )
sel_map . emplace ( obj_idx , empty_set );
else
for ( int i = 0 ; i < inst_cnt ; i ++ )
sel_map [ obj_idx ]. emplace ( i );
continue ;
}
int obj_idx = m_objects_model -> GetIdByItem ( m_objects_model -> GetTopParent ( item ));
sel_map [ obj_idx ]. emplace ( m_objects_model -> GetInstanceIdByItem ( item ));
}
// all objects, created from the instances will be added to the end of list
int new_objects_cnt = 0 ; // count of this new objects
for ( auto map_item : sel_map )
{
int obj_idx = map_item . first ;
// object with just 1 instance
if ( map_item . second . empty ()) {
obj_idxs . emplace_back ( obj_idx );
continue ;
}
// object with selected all instances
if (( * m_objects )[ map_item . first ] -> instances . size () == map_item . second . size ()) {
instances_to_separated_objects ( obj_idx );
// first instance stay on its own place and another all add to the end of list :
obj_idxs . emplace_back ( obj_idx );
new_objects_cnt += map_item . second . size () - 1 ;
continue ;
}
// object with selected some of instances
instances_to_separated_object ( obj_idx , map_item . second );
if ( map_item . second . size () == 1 )
new_objects_cnt += 1 ;
else { // we should split to separate instances last object
instances_to_separated_objects ( m_objects -> size () - 1 );
// all instances will stay at the end of list :
new_objects_cnt += map_item . second . size ();
}
}
// all instatnces are extracted to the separate objects and should be selected
m_prevent_list_events = true ;
sels . Clear ();
for ( int obj_idx : obj_idxs )
sels . Add ( m_objects_model -> GetItemById ( obj_idx ));
int obj_cnt = m_objects -> size ();
for ( int obj_idx = obj_cnt - new_objects_cnt ; obj_idx < obj_cnt ; obj_idx ++ ) {
sels . Add ( m_objects_model -> GetItemById ( obj_idx ));
obj_idxs . emplace_back ( obj_idx );
}
UnselectAll ();
SetSelections ( sels );
assert ( ! sels . IsEmpty ());
m_prevent_list_events = false ;
};
2020-05-27 10:55:48 +02:00
std :: vector < int > obj_idxs ;
wxDataViewItemArray sels ;
GetSelections ( sels );
assert ( ! sels . IsEmpty ());
Plater :: TakeSnapshot snapshot ( wxGetApp (). plater (), _L ( "Merge" ));
2020-05-27 22:29:18 +02:00
get_object_idxs ( obj_idxs , sels );
// resulted objects merge to the one
2020-05-27 10:55:48 +02:00
Model * model = ( * m_objects )[ 0 ] -> get_model ();
ModelObject * new_object = model -> add_object ();
new_object -> name = _u8L ( "Merged" );
2020-09-24 15:34:13 +02:00
ModelConfig & config = new_object -> config ;
2020-05-27 10:55:48 +02:00
for ( int obj_idx : obj_idxs )
{
ModelObject * object = ( * m_objects )[ obj_idx ];
2020-06-04 23:17:21 +02:00
const Geometry :: Transformation & transformation = object -> instances [ 0 ] -> get_transformation ();
Vec3d scale = transformation . get_scaling_factor ();
Vec3d mirror = transformation . get_mirror ();
Vec3d rotation = transformation . get_rotation ();
if ( object -> id () == ( * m_objects )[ obj_idxs . front ()] -> id ())
new_object -> add_instance ();
Transform3d volume_offset_correction = new_object -> instances [ 0 ] -> get_transformation (). get_matrix (). inverse () * transformation . get_matrix ();
// merge volumes
for ( const ModelVolume * volume : object -> volumes ) {
ModelVolume * new_volume = new_object -> add_volume ( * volume );
//set rotation
Vec3d vol_rot = new_volume -> get_rotation () + rotation ;
new_volume -> set_rotation ( vol_rot );
// set scale
Vec3d vol_sc_fact = new_volume -> get_scaling_factor (). cwiseProduct ( scale );
new_volume -> set_scaling_factor ( vol_sc_fact );
// set mirror
Vec3d vol_mirror = new_volume -> get_mirror (). cwiseProduct ( mirror );
new_volume -> set_mirror ( vol_mirror );
// set offset
Vec3d vol_offset = volume_offset_correction * new_volume -> get_offset ();
new_volume -> set_offset ( vol_offset );
}
// merge settings
2020-09-24 15:34:13 +02:00
auto new_opt_keys = config . keys ();
const ModelConfig & from_config = object -> config ;
2020-05-27 10:55:48 +02:00
auto opt_keys = from_config . keys ();
for ( auto & opt_key : opt_keys ) {
if ( find ( new_opt_keys . begin (), new_opt_keys . end (), opt_key ) == new_opt_keys . end ()) {
const ConfigOption * option = from_config . option ( opt_key );
if ( ! option ) {
// if current option doesn't exist in prints.get_edited_preset(),
// get it from default config values
option = DynamicPrintConfig :: new_from_defaults_keys ({ opt_key }) -> option ( opt_key );
}
2020-09-24 15:34:13 +02:00
config . set_key_value ( opt_key , option -> clone ());
2020-05-27 10:55:48 +02:00
}
}
// save extruder value if it was set
if ( object -> volumes . size () == 1 && find ( opt_keys . begin (), opt_keys . end (), "extruder" ) != opt_keys . end ()) {
ModelVolume * volume = new_object -> volumes . back ();
const ConfigOption * option = from_config . option ( "extruder" );
2020-06-04 23:17:21 +02:00
if ( option )
2020-05-27 10:55:48 +02:00
volume -> config . set_key_value ( "extruder" , option -> clone ());
}
// merge layers
for ( const auto & range : object -> layer_config_ranges )
new_object -> layer_config_ranges . emplace ( range );
}
// remove selected objects
remove ();
2020-05-27 22:29:18 +02:00
2020-05-27 10:55:48 +02:00
// Add new object(merged) to the object_list
add_object_to_list ( m_objects -> size () - 1 );
2020-05-27 22:29:18 +02:00
select_item ( m_objects_model -> GetItemById ( m_objects -> size () - 1 ));
update_selections_on_canvas ();
2020-05-27 10:55:48 +02:00
}
// merge all parts to the one single object
// all part's settings will be lost
else
{
wxDataViewItem item = GetSelection ();
if ( ! item )
return ;
const int obj_idx = m_objects_model -> GetIdByItem ( item );
if ( obj_idx == - 1 )
return ;
Plater :: TakeSnapshot snapshot ( wxGetApp (). plater (), _L ( "Merge all parts to the one single object" ));
ModelObject * model_object = ( * m_objects )[ obj_idx ];
model_object -> merge ();
m_objects_model -> DeleteVolumeChildren ( item );
changed_object ( obj_idx );
}
}
2019-05-27 13:07:37 +02:00
void ObjectList :: layers_editing ()
{
2019-12-11 16:07:00 +01:00
const Selection & selection = scene_selection ();
const int obj_idx = selection . get_object_idx ();
wxDataViewItem item = obj_idx >= 0 && GetSelectedItemsCount () > 1 && selection . is_single_full_object () ?
m_objects_model -> GetItemById ( obj_idx ) :
GetSelection ();
if ( ! item )
2019-05-27 13:07:37 +02:00
return ;
2019-05-31 15:29:09 +02:00
const wxDataViewItem obj_item = m_objects_model -> GetTopParent ( item );
2019-05-31 10:54:52 +02:00
wxDataViewItem layers_item = m_objects_model -> GetLayerRootItem ( obj_item );
2019-05-31 15:29:09 +02:00
// if it doesn't exist now
2019-05-27 13:07:37 +02:00
if ( ! layers_item . IsOk ())
2019-05-28 16:38:04 +02:00
{
2019-06-03 15:35:21 +02:00
t_layer_config_ranges & ranges = object ( obj_idx ) -> layer_config_ranges ;
2019-06-10 10:48:43 +02:00
// set some default value
2019-07-11 18:42:02 +02:00
if ( ranges . empty ()) {
take_snapshot ( _ ( L ( "Add Layers" )));
2020-09-24 15:34:13 +02:00
ranges [{ 0.0f , 2.0f }]. assign_config ( get_default_layer_config ( obj_idx ));
2019-07-11 18:42:02 +02:00
}
2019-06-03 15:35:21 +02:00
2019-06-10 10:48:43 +02:00
// create layer root item
layers_item = add_layer_root_item ( obj_item );
2019-05-30 14:41:16 +02:00
}
2019-06-10 10:48:43 +02:00
if ( ! layers_item . IsOk ())
return ;
2019-05-30 14:41:16 +02:00
2019-07-04 14:25:40 +02:00
// to correct visual hints for layers editing on the Scene, reset previous selection
wxGetApp (). obj_layers () -> reset_selection ();
wxGetApp (). plater () -> canvas3D () -> handle_sidebar_focus_event ( "" , false );
2019-05-31 15:29:09 +02:00
// select LayerRoor item and expand
2019-05-27 13:07:37 +02:00
select_item ( layers_item );
2019-05-31 15:29:09 +02:00
Expand ( layers_item );
2019-05-27 13:07:37 +02:00
}
2019-06-10 10:48:43 +02:00
wxDataViewItem ObjectList :: add_layer_root_item ( const wxDataViewItem obj_item )
{
const int obj_idx = m_objects_model -> GetIdByItem ( obj_item );
if ( obj_idx < 0 ||
object ( obj_idx ) -> layer_config_ranges . empty () ||
printer_technology () == ptSLA )
2019-09-02 09:41:12 +02:00
return wxDataViewItem ( nullptr );
2019-06-10 10:48:43 +02:00
// create LayerRoot item
wxDataViewItem layers_item = m_objects_model -> AddLayersRoot ( obj_item );
// and create Layer item(s) according to the layer_config_ranges
2019-09-02 09:41:12 +02:00
for ( const auto & range : object ( obj_idx ) -> layer_config_ranges )
2019-06-10 10:48:43 +02:00
add_layer_item ( range . first , layers_item );
2019-07-11 18:42:02 +02:00
Expand ( layers_item );
2019-06-10 10:48:43 +02:00
return layers_item ;
}
2019-06-05 16:47:09 +02:00
DynamicPrintConfig ObjectList :: get_default_layer_config ( const int obj_idx )
{
DynamicPrintConfig config ;
coordf_t layer_height = object ( obj_idx ) -> config . has ( "layer_height" ) ?
object ( obj_idx ) -> config . opt_float ( "layer_height" ) :
wxGetApp (). preset_bundle -> prints . get_edited_preset (). config . opt_float ( "layer_height" );
config . set_key_value ( "layer_height" , new ConfigOptionFloat ( layer_height ));
config . set_key_value ( "extruder" , new ConfigOptionInt ( 0 ));
return config ;
}
2018-12-06 14:49:14 +01:00
bool ObjectList :: get_volume_by_item ( const wxDataViewItem & item , ModelVolume *& volume )
2018-10-05 23:29:15 +02:00
{
2018-11-06 09:06:26 +01:00
auto obj_idx = get_selected_obj_idx ();
if ( ! item || obj_idx < 0 )
2018-10-05 23:29:15 +02:00
return false ;
const auto volume_id = m_objects_model -> GetVolumeIdByItem ( item );
2018-12-06 14:49:14 +01:00
const bool split_part = m_objects_model -> GetItemType ( item ) == itVolume ;
2018-11-06 09:06:26 +01:00
// object is selected
2018-10-05 23:29:15 +02:00
if ( volume_id < 0 ) {
2018-11-06 09:06:26 +01:00
if ( split_part || ( * m_objects )[ obj_idx ] -> volumes . size () > 1 )
return false ;
volume = ( * m_objects )[ obj_idx ] -> volumes [ 0 ];
2018-10-05 23:29:15 +02:00
}
2018-11-06 09:06:26 +01:00
// volume is selected
2018-10-05 23:29:15 +02:00
else
2018-11-06 09:06:26 +01:00
volume = ( * m_objects )[ obj_idx ] -> volumes [ volume_id ];
return true ;
2018-10-05 23:29:15 +02:00
}
2021-03-15 10:04:45 +01:00
bool ObjectList :: is_splittable ( bool to_objects )
2018-10-05 23:29:15 +02:00
{
2018-10-08 16:27:38 +02:00
const wxDataViewItem item = GetSelection ();
2018-10-05 23:29:15 +02:00
if ( ! item ) return false ;
2021-03-15 10:04:45 +01:00
if ( to_objects )
{
ItemType type = m_objects_model -> GetItemType ( item );
if ( type == itVolume )
return false ;
if ( type == itObject || m_objects_model -> GetItemType ( m_objects_model -> GetParent ( item )) == itObject ) {
auto obj_idx = get_selected_obj_idx ();
if ( obj_idx < 0 )
return false ;
if (( * m_objects )[ obj_idx ] -> volumes . size () > 1 )
return true ;
return ( * m_objects )[ obj_idx ] -> volumes [ 0 ] -> is_splittable ();
}
return false ;
}
2018-10-05 23:29:15 +02:00
ModelVolume * volume ;
2018-12-06 14:49:14 +01:00
if ( ! get_volume_by_item ( item , volume ) || ! volume )
2018-10-05 23:29:15 +02:00
return false ;
2019-03-13 14:04:59 +01:00
return volume -> is_splittable ();
2018-10-05 23:29:15 +02:00
}
2019-01-23 16:01:37 +01:00
bool ObjectList :: selected_instances_of_same_object ()
{
wxDataViewItemArray sels ;
GetSelections ( sels );
const int obj_idx = m_objects_model -> GetObjectIdByItem ( sels . front ());
for ( auto item : sels ) {
if ( ! ( m_objects_model -> GetItemType ( item ) & itInstance ) ||
obj_idx != m_objects_model -> GetObjectIdByItem ( item ))
return false ;
}
return true ;
}
bool ObjectList :: can_split_instances ()
{
2019-12-11 16:07:00 +01:00
const Selection & selection = scene_selection ();
2019-01-23 16:01:37 +01:00
return selection . is_multiple_full_instance () || selection . is_single_full_instance ();
}
2020-05-27 10:55:48 +02:00
bool ObjectList :: can_merge_to_multipart_object () const
{
2020-05-28 14:04:15 +02:00
if ( printer_technology () == ptSLA )
return false ;
2020-05-27 10:55:48 +02:00
wxDataViewItemArray sels ;
GetSelections ( sels );
if ( sels . IsEmpty ())
return false ;
// should be selected just objects
for ( wxDataViewItem item : sels )
2020-05-27 22:29:18 +02:00
if ( ! ( m_objects_model -> GetItemType ( item ) & ( itObject | itInstance )))
2020-05-27 10:55:48 +02:00
return false ;
return true ;
}
bool ObjectList :: can_merge_to_single_object () const
{
int obj_idx = get_selected_obj_idx ();
if ( obj_idx < 0 )
return false ;
// selected object should be multipart
return ( * m_objects )[ obj_idx ] -> volumes . size () > 1 ;
}
2019-04-22 01:51:10 +02:00
// NO_PARAMETERS function call means that changed object index will be determine from Selection()
void ObjectList :: changed_object ( const int obj_idx /* = -1*/ ) const
2018-10-31 15:41:27 +01:00
{
2019-04-22 01:51:10 +02:00
wxGetApp (). plater () -> changed_object ( obj_idx < 0 ? get_selected_obj_idx () : obj_idx );
2018-10-05 23:29:15 +02:00
}
void ObjectList :: part_selection_changed ()
{
2019-10-10 16:13:07 +02:00
if ( m_extruder_editor ) m_extruder_editor -> Hide ();
2018-10-05 23:29:15 +02:00
int obj_idx = - 1 ;
2019-04-24 13:33:05 +02:00
int volume_id = - 1 ;
2018-10-05 23:29:15 +02:00
m_config = nullptr ;
2018-11-09 18:39:07 +01:00
wxString og_name = wxEmptyString ;
2018-11-08 15:45:55 +01:00
2018-11-09 18:39:07 +01:00
bool update_and_show_manipulations = false ;
bool update_and_show_settings = false ;
2019-05-28 16:38:04 +02:00
bool update_and_show_layers = false ;
2018-11-09 18:39:07 +01:00
2019-03-29 14:36:09 +01:00
const auto item = GetSelection ();
2021-04-28 11:07:15 +02:00
if ( multiple_selection () || ( item && m_objects_model -> GetItemType ( item ) == itInstanceRoot )) {
og_name = _L ( "Group manipulation" );
2019-03-29 14:36:09 +01:00
2019-12-11 16:07:00 +01:00
const Selection & selection = scene_selection ();
2019-03-29 14:36:09 +01:00
// don't show manipulation panel for case of all Object's parts selection
update_and_show_manipulations = ! selection . is_single_full_instance ();
2018-11-09 18:39:07 +01:00
}
2021-04-28 11:07:15 +02:00
else {
if ( item ) {
2021-04-14 07:38:50 +02:00
const ItemType type = m_objects_model -> GetItemType ( item );
const wxDataViewItem parent = m_objects_model -> GetParent ( item );
const ItemType parent_type = m_objects_model -> GetItemType ( parent );
obj_idx = m_objects_model -> GetObjectIdByItem ( item );
if ( parent == wxDataViewItem ( nullptr )
|| type == itInfo ) {
2021-04-28 11:07:15 +02:00
og_name = _L ( "Object manipulation" );
2018-11-08 15:45:55 +01:00
m_config = & ( * m_objects )[ obj_idx ] -> config ;
2018-11-09 18:39:07 +01:00
update_and_show_manipulations = true ;
2021-04-14 07:38:50 +02:00
if ( type == itInfo ) {
InfoItemType info_type = m_objects_model -> GetInfoItemType ( item );
2021-04-19 15:42:57 +02:00
if ( info_type != InfoItemType :: VariableLayerHeight ) {
GLGizmosManager :: EType gizmo_type =
info_type == InfoItemType :: CustomSupports ? GLGizmosManager :: EType :: FdmSupports
: GLGizmosManager :: EType :: Seam ;
GLGizmosManager & gizmos_mgr = wxGetApp (). plater () -> canvas3D () -> get_gizmos_manager ();
if ( gizmos_mgr . get_current_type () != gizmo_type )
gizmos_mgr . open_gizmo ( gizmo_type );
} else
wxGetApp (). plater () -> toggle_layers_editing ( true );
2021-04-14 07:38:50 +02:00
}
2018-11-08 15:45:55 +01:00
}
else {
2019-05-28 16:38:04 +02:00
if ( type & itSettings ) {
2019-06-05 16:47:09 +02:00
if ( parent_type & itObject ) {
2021-04-28 11:07:15 +02:00
og_name = _L ( "Object Settings to modify" );
2018-11-08 15:45:55 +01:00
m_config = & ( * m_objects )[ obj_idx ] -> config ;
}
2019-06-05 16:47:09 +02:00
else if ( parent_type & itVolume ) {
2021-04-28 11:07:15 +02:00
og_name = _L ( "Part Settings to modify" );
2019-06-05 16:47:09 +02:00
volume_id = m_objects_model -> GetVolumeIdByItem ( parent );
2018-11-08 15:45:55 +01:00
m_config = & ( * m_objects )[ obj_idx ] -> volumes [ volume_id ] -> config ;
}
2019-06-05 16:47:09 +02:00
else if ( parent_type & itLayer ) {
2021-04-28 11:07:15 +02:00
og_name = _L ( "Layer range Settings to modify" );
2019-06-06 14:14:29 +02:00
m_config = & get_item_config ( parent );
2019-06-05 16:47:09 +02:00
}
2018-11-09 18:39:07 +01:00
update_and_show_settings = true ;
2018-10-05 23:29:15 +02:00
}
2019-05-28 16:38:04 +02:00
else if ( type & itVolume ) {
2021-04-28 11:07:15 +02:00
og_name = _L ( "Part manipulation" );
2019-04-24 13:33:05 +02:00
volume_id = m_objects_model -> GetVolumeIdByItem ( item );
2018-10-05 23:29:15 +02:00
m_config = & ( * m_objects )[ obj_idx ] -> volumes [ volume_id ] -> config ;
2018-11-09 18:39:07 +01:00
update_and_show_manipulations = true ;
2018-10-05 23:29:15 +02:00
}
2019-05-28 16:38:04 +02:00
else if ( type & itInstance ) {
2021-04-28 11:07:15 +02:00
og_name = _L ( "Instance manipulation" );
2018-11-09 18:39:07 +01:00
update_and_show_manipulations = true ;
2019-01-09 10:30:29 +01:00
// fill m_config by object's values
2019-06-05 16:47:09 +02:00
m_config = & ( * m_objects )[ obj_idx ] -> config ;
2018-11-08 15:45:55 +01:00
}
2019-05-28 16:38:04 +02:00
else if ( type & ( itLayerRoot | itLayer )) {
2021-04-28 11:07:15 +02:00
og_name = type & itLayerRoot ? _L ( "Height ranges" ) : _L ( "Settings for height range" );
2019-05-28 16:38:04 +02:00
update_and_show_layers = true ;
2019-06-05 16:47:09 +02:00
2019-06-06 14:14:29 +02:00
if ( type & itLayer )
m_config = & get_item_config ( item );
2018-11-08 15:45:55 +01:00
}
2018-10-05 23:29:15 +02:00
}
2018-11-08 15:45:55 +01:00
}
2018-10-05 23:29:15 +02:00
}
m_selected_object_id = obj_idx ;
2018-11-09 18:39:07 +01:00
if ( update_and_show_manipulations ) {
wxGetApp (). obj_manipul () -> get_og () -> set_name ( " " + og_name + " " );
2019-04-24 13:33:05 +02:00
if ( item ) {
2019-09-18 15:10:36 +02:00
// wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(item));
wxGetApp (). obj_manipul () -> update_item_name ( m_objects_model -> GetName ( item ));
2019-05-02 09:54:18 +02:00
wxGetApp (). obj_manipul () -> update_warning_icon_state ( get_mesh_errors_list ( obj_idx , volume_id ));
2019-04-24 13:33:05 +02:00
}
2018-11-09 18:39:07 +01:00
}
if ( update_and_show_settings )
wxGetApp (). obj_settings () -> get_og () -> set_name ( " " + og_name + " " );
2019-06-10 10:48:43 +02:00
if ( printer_technology () == ptSLA )
update_and_show_layers = false ;
else if ( update_and_show_layers )
2019-05-28 16:38:04 +02:00
wxGetApp (). obj_layers () -> get_og () -> set_name ( " " + og_name + " " );
2020-12-29 19:10:14 +01:00
update_min_height ();
2018-11-13 12:19:56 +01:00
Sidebar & panel = wxGetApp (). sidebar ();
panel . Freeze ();
2018-11-09 18:39:07 +01:00
2019-07-02 10:34:30 +02:00
wxGetApp (). plater () -> canvas3D () -> handle_sidebar_focus_event ( "" , false );
2018-11-09 18:39:07 +01:00
wxGetApp (). obj_manipul () -> UpdateAndShow ( update_and_show_manipulations );
wxGetApp (). obj_settings () -> UpdateAndShow ( update_and_show_settings );
2019-05-28 16:38:04 +02:00
wxGetApp (). obj_layers () -> UpdateAndShow ( update_and_show_layers );
2018-11-13 12:19:56 +01:00
wxGetApp (). sidebar (). show_info_sizer ();
2018-11-09 18:39:07 +01:00
2018-11-13 12:19:56 +01:00
panel . Layout ();
panel . Thaw ();
2018-10-05 23:29:15 +02:00
}
2019-07-12 13:55:46 +02:00
// Add new SettingsItem for parent_item if it doesn't exist, or just update a digest according to new config
2019-07-11 23:46:23 +02:00
wxDataViewItem ObjectList :: add_settings_item ( wxDataViewItem parent_item , const DynamicPrintConfig * config )
{
2019-09-02 09:41:12 +02:00
wxDataViewItem ret = wxDataViewItem ( nullptr );
2019-07-11 23:46:23 +02:00
if ( ! parent_item )
return ret ;
2019-07-25 17:24:00 +02:00
const bool is_object_settings = m_objects_model -> GetItemType ( parent_item ) == itObject ;
2021-03-15 10:04:45 +01:00
if ( ! is_object_settings ) {
ModelVolumeType volume_type = m_objects_model -> GetVolumeType ( parent_item );
2021-05-24 17:30:07 +02:00
if ( volume_type == ModelVolumeType :: NEGATIVE_VOLUME || volume_type == ModelVolumeType :: SUPPORT_BLOCKER || volume_type == ModelVolumeType :: SUPPORT_ENFORCER )
2021-03-15 10:04:45 +01:00
return ret ;
}
SettingsFactory :: Bundle cat_options = SettingsFactory :: get_bundle ( config , is_object_settings );
2019-07-11 23:46:23 +02:00
if ( cat_options . empty ())
return ret ;
std :: vector < std :: string > categories ;
categories . reserve ( cat_options . size ());
for ( auto & cat : cat_options )
categories . push_back ( cat . first );
if ( m_objects_model -> GetItemType ( parent_item ) & itInstance )
parent_item = m_objects_model -> GetTopParent ( parent_item );
ret = m_objects_model -> IsSettingsItem ( parent_item ) ? parent_item : m_objects_model -> GetSettingsItem ( parent_item );
if ( ! ret ) ret = m_objects_model -> AddSettingsChild ( parent_item );
m_objects_model -> UpdateSettingsDigest ( ret , categories );
Expand ( parent_item );
return ret ;
}
2021-04-14 07:38:50 +02:00
void ObjectList :: update_info_items ( size_t obj_idx )
{
const ModelObject * model_object = ( * m_objects )[ obj_idx ];
wxDataViewItem item_obj = m_objects_model -> GetItemById ( obj_idx );
assert ( item_obj . IsOk ());
2021-04-19 15:42:57 +02:00
for ( InfoItemType type : { InfoItemType :: CustomSupports ,
InfoItemType :: CustomSeam ,
InfoItemType :: VariableLayerHeight }) {
2021-04-14 07:38:50 +02:00
wxDataViewItem item = m_objects_model -> GetInfoItemByType ( item_obj , type );
bool shows = item . IsOk ();
2021-04-19 15:42:57 +02:00
bool should_show = false ;
switch ( type ) {
case InfoItemType :: CustomSupports :
case InfoItemType :: CustomSeam :
should_show = printer_technology () == ptFFF
&& std :: any_of ( model_object -> volumes . begin (), model_object -> volumes . end (),
[ type ]( const ModelVolume * mv ) {
return ! ( type == InfoItemType :: CustomSupports
? mv -> supported_facets . empty ()
: mv -> seam_facets . empty ());
});
break ;
case InfoItemType :: VariableLayerHeight :
should_show = printer_technology () == ptFFF
&& ! model_object -> layer_height_profile . empty ();
break ;
2021-05-18 12:58:14 +02:00
default : break ;
2021-04-19 15:42:57 +02:00
}
2021-04-14 07:38:50 +02:00
if ( ! shows && should_show ) {
m_objects_model -> AddInfoChild ( item_obj , type );
Expand ( item_obj );
}
else if ( shows && ! should_show ) {
Unselect ( item );
m_objects_model -> Delete ( item );
Select ( item_obj );
}
}
}
2019-07-05 19:06:19 +02:00
void ObjectList :: add_object_to_list ( size_t obj_idx , bool call_selection_changed )
2018-10-05 23:29:15 +02:00
{
2018-10-08 16:27:38 +02:00
auto model_object = ( * m_objects )[ obj_idx ];
2019-04-24 13:33:05 +02:00
const wxString & item_name = from_u8 ( model_object -> name );
2018-12-11 08:53:18 +01:00
const auto item = m_objects_model -> Add ( item_name ,
2020-09-24 15:34:13 +02:00
model_object -> config . has ( "extruder" ) ? model_object -> config . extruder () : 0 ,
2019-04-29 15:27:59 +02:00
get_mesh_errors_count ( obj_idx ) > 0 );
2018-10-05 23:29:15 +02:00
2021-04-14 07:38:50 +02:00
update_info_items ( obj_idx );
2018-11-06 09:06:26 +01:00
// add volumes to the object
2018-10-05 23:29:15 +02:00
if ( model_object -> volumes . size () > 1 ) {
2019-04-29 15:27:59 +02:00
for ( const ModelVolume * volume : model_object -> volumes ) {
const wxDataViewItem & vol_item = m_objects_model -> AddVolumeChild ( item ,
from_u8 ( volume -> name ),
volume -> type (),
volume -> get_mesh_errors_count () > 0 ,
2020-09-24 15:34:13 +02:00
volume -> config . has ( "extruder" ) ? volume -> config . extruder () : 0 ,
2018-12-10 10:38:32 +01:00
false );
2020-09-24 15:34:13 +02:00
add_settings_item ( vol_item , & volume -> config . get ());
2018-12-10 10:38:32 +01:00
}
2018-10-08 16:27:38 +02:00
Expand ( item );
2018-10-05 23:29:15 +02:00
}
2018-11-06 09:06:26 +01:00
// add instances to the object, if it has those
2018-11-01 16:22:16 +01:00
if ( model_object -> instances . size () > 1 )
2019-08-01 14:58:04 +02:00
{
std :: vector < bool > print_idicator ( model_object -> instances . size ());
2019-09-02 09:41:12 +02:00
for ( size_t i = 0 ; i < model_object -> instances . size (); ++ i )
2019-08-20 15:26:57 +02:00
print_idicator [ i ] = model_object -> instances [ i ] -> printable ;
2019-08-01 14:58:04 +02:00
2019-08-05 14:57:30 +02:00
const wxDataViewItem object_item = m_objects_model -> GetItemById ( obj_idx );
m_objects_model -> AddInstanceChild ( object_item , print_idicator );
Expand ( m_objects_model -> GetInstanceRootItem ( object_item ));
2019-08-01 14:58:04 +02:00
}
2019-08-05 08:44:55 +02:00
else
2019-08-20 15:26:57 +02:00
m_objects_model -> SetPrintableState ( model_object -> instances [ 0 ] -> printable ? piPrintable : piUnprintable , obj_idx );
2018-11-01 16:22:16 +01:00
2018-11-06 09:06:26 +01:00
// add settings to the object, if it has those
2020-09-24 15:34:13 +02:00
add_settings_item ( item , & model_object -> config . get ());
2018-11-06 09:06:26 +01:00
2019-06-10 15:22:09 +02:00
// Add layers if it has
add_layer_root_item ( item );
2018-10-05 23:29:15 +02:00
#ifndef __WXOSX__
2019-07-05 19:06:19 +02:00
if ( call_selection_changed )
selection_changed ();
2018-10-05 23:29:15 +02:00
#endif //__WXMSW__
}
void ObjectList :: delete_object_from_list ()
{
2018-10-08 16:27:38 +02:00
auto item = GetSelection ();
2018-10-12 12:00:37 +02:00
if ( ! item )
2018-10-05 23:29:15 +02:00
return ;
2019-09-02 09:41:12 +02:00
if ( m_objects_model -> GetParent ( item ) == wxDataViewItem ( nullptr ))
2020-12-04 14:19:08 +01:00
select_item ([ this , item ]() { return m_objects_model -> Delete ( item ); });
2018-10-12 12:00:37 +02:00
else
2020-12-04 14:19:08 +01:00
select_item ([ this , item ]() { return m_objects_model -> Delete ( m_objects_model -> GetParent ( item )); });
2018-10-12 12:00:37 +02:00
}
2018-10-05 23:29:15 +02:00
2018-10-12 12:00:37 +02:00
void ObjectList :: delete_object_from_list ( const size_t obj_idx )
{
2020-12-04 14:19:08 +01:00
select_item ([ this , obj_idx ]() { return m_objects_model -> Delete ( m_objects_model -> GetItemById ( obj_idx )); });
2018-10-12 12:00:37 +02:00
}
void ObjectList :: delete_volume_from_list ( const size_t obj_idx , const size_t vol_idx )
{
2020-12-04 14:19:08 +01:00
select_item ([ this , obj_idx , vol_idx ]() { return m_objects_model -> Delete ( m_objects_model -> GetItemByVolumeId ( obj_idx , vol_idx )); });
2018-10-05 23:29:15 +02:00
}
2018-11-13 13:34:31 +01:00
void ObjectList :: delete_instance_from_list ( const size_t obj_idx , const size_t inst_idx )
{
2020-12-04 14:19:08 +01:00
select_item ([ this , obj_idx , inst_idx ]() { return m_objects_model -> Delete ( m_objects_model -> GetItemByInstanceId ( obj_idx , inst_idx )); });
2018-11-13 13:34:31 +01:00
}
2018-11-13 14:17:35 +01:00
void ObjectList :: delete_from_model_and_list ( const ItemType type , const int obj_idx , const int sub_obj_idx )
{
if ( ! ( type & ( itObject | itVolume | itInstance )) )
return ;
2019-07-04 17:33:19 +02:00
take_snapshot ( _ ( L ( "Delete Selected Item" )));
2018-11-13 14:17:35 +01:00
if ( type & itObject ) {
del_object ( obj_idx );
delete_object_from_list ( obj_idx );
}
else {
del_subobject_from_object ( obj_idx , sub_obj_idx , type );
type == itVolume ? delete_volume_from_list ( obj_idx , sub_obj_idx ) :
delete_instance_from_list ( obj_idx , sub_obj_idx );
}
}
2018-11-14 08:53:56 +01:00
void ObjectList :: delete_from_model_and_list ( const std :: vector < ItemForDelete >& items_for_delete )
2018-11-13 14:17:35 +01:00
{
2018-11-14 11:22:13 +01:00
if ( items_for_delete . empty ())
return ;
2020-12-04 14:19:08 +01:00
m_prevent_list_events = true ;
2018-11-14 11:22:13 +01:00
for ( std :: vector < ItemForDelete >:: const_reverse_iterator item = items_for_delete . rbegin (); item != items_for_delete . rend (); ++ item )
2018-11-13 14:17:35 +01:00
{
2018-11-14 11:22:13 +01:00
if ( ! ( item -> type & ( itObject | itVolume | itInstance )))
2018-11-13 14:17:35 +01:00
continue ;
2018-11-14 11:22:13 +01:00
if ( item -> type & itObject ) {
del_object ( item -> obj_idx );
m_objects_model -> Delete ( m_objects_model -> GetItemById ( item -> obj_idx ));
2018-11-13 14:17:35 +01:00
}
else {
2018-11-23 11:54:06 +01:00
if ( ! del_subobject_from_object ( item -> obj_idx , item -> sub_obj_idx , item -> type ))
continue ;
2018-11-14 11:22:13 +01:00
if ( item -> type & itVolume )
2018-11-21 12:27:20 +01:00
{
2018-11-14 11:22:13 +01:00
m_objects_model -> Delete ( m_objects_model -> GetItemByVolumeId ( item -> obj_idx , item -> sub_obj_idx ));
2019-03-14 09:22:15 +01:00
if (( * m_objects )[ item -> obj_idx ] -> volumes . size () == 1 &&
( * m_objects )[ item -> obj_idx ] -> config . has ( "extruder" ))
2019-03-13 16:40:11 +01:00
{
2020-09-24 15:34:13 +02:00
const wxString extruder = wxString :: Format ( "%d" , ( * m_objects )[ item -> obj_idx ] -> config . extruder ());
2019-10-01 18:19:28 +02:00
m_objects_model -> SetExtruder ( extruder , m_objects_model -> GetItemById ( item -> obj_idx ));
2019-03-13 16:40:11 +01:00
}
2018-11-23 12:47:32 +01:00
wxGetApp (). plater () -> canvas3D () -> ensure_on_bed ( item -> obj_idx );
2018-11-21 12:27:20 +01:00
}
2018-11-13 14:17:35 +01:00
else
2018-11-14 11:22:13 +01:00
m_objects_model -> Delete ( m_objects_model -> GetItemByInstanceId ( item -> obj_idx , item -> sub_obj_idx ));
2018-11-13 14:17:35 +01:00
}
}
2020-12-04 14:19:08 +01:00
m_prevent_list_events = true ;
2018-11-13 14:17:35 +01:00
part_selection_changed ();
}
2018-10-05 23:29:15 +02:00
void ObjectList :: delete_all_objects_from_list ()
{
2019-05-20 18:49:32 +02:00
m_prevent_list_events = true ;
this -> UnselectAll ();
2018-10-05 23:29:15 +02:00
m_objects_model -> DeleteAll ();
2019-05-20 18:49:32 +02:00
m_prevent_list_events = false ;
2018-10-05 23:29:15 +02:00
part_selection_changed ();
}
2018-10-18 10:40:26 +02:00
void ObjectList :: increase_object_instances ( const size_t obj_idx , const size_t num )
2018-10-05 23:29:15 +02:00
{
2020-12-04 14:19:08 +01:00
select_item ([ this , obj_idx , num ]() { return m_objects_model -> AddInstanceChild ( m_objects_model -> GetItemById ( obj_idx ), num ); });
2020-04-29 13:35:35 +02:00
selection_changed ();
2018-10-18 10:40:26 +02:00
}
void ObjectList :: decrease_object_instances ( const size_t obj_idx , const size_t num )
{
2020-12-04 14:19:08 +01:00
select_item ([ this , obj_idx , num ]() { return m_objects_model -> DeleteLastInstance ( m_objects_model -> GetItemById ( obj_idx ), num ); });
2018-10-05 23:29:15 +02:00
}
void ObjectList :: unselect_objects ()
{
2018-10-08 16:27:38 +02:00
if ( ! GetSelection ())
2018-10-05 23:29:15 +02:00
return ;
m_prevent_list_events = true ;
2018-10-08 16:27:38 +02:00
UnselectAll ();
2018-10-05 23:29:15 +02:00
part_selection_changed ();
m_prevent_list_events = false ;
}
2021-05-04 14:19:38 +02:00
void ObjectList :: select_object_item ( bool is_msr_gizmo )
2018-10-05 23:29:15 +02:00
{
2021-05-04 14:19:38 +02:00
if ( wxDataViewItem item = GetSelection ()) {
ItemType type = m_objects_model -> GetItemType ( item );
2021-05-18 12:58:14 +02:00
bool is_volume_item = type == itVolume || ( type == itSettings && m_objects_model -> GetItemType ( m_objects_model -> GetParent ( item )) == itVolume );
if (( is_msr_gizmo && is_volume_item ) || type == itObject )
2021-05-04 14:19:38 +02:00
return ;
2018-10-05 23:29:15 +02:00
2021-05-04 14:19:38 +02:00
if ( wxDataViewItem obj_item = m_objects_model -> GetTopParent ( item )) {
m_prevent_list_events = true ;
UnselectAll ();
Select ( obj_item );
part_selection_changed ();
m_prevent_list_events = false ;
}
2018-10-05 23:29:15 +02:00
}
}
2020-12-04 14:19:08 +01:00
static void update_selection ( wxDataViewItemArray & sels , ObjectList :: SELECTION_MODE mode , ObjectDataViewModel * model )
{
if ( mode == ObjectList :: smInstance )
{
for ( auto & item : sels )
{
ItemType type = model -> GetItemType ( item );
if ( type == itObject )
continue ;
if ( type == itInstanceRoot ) {
wxDataViewItem obj_item = model -> GetParent ( item );
sels . Remove ( item );
sels . Add ( obj_item );
update_selection ( sels , mode , model );
return ;
}
if ( type == itInstance )
{
wxDataViewItemArray instances ;
model -> GetChildren ( model -> GetParent ( item ), instances );
assert ( instances . Count () > 0 );
size_t selected_instances_cnt = 0 ;
for ( auto & inst : instances ) {
if ( sels . Index ( inst ) == wxNOT_FOUND )
break ;
selected_instances_cnt ++ ;
}
if ( selected_instances_cnt == instances . Count ())
{
wxDataViewItem obj_item = model -> GetTopParent ( item );
for ( auto & inst : instances )
sels . Remove ( inst );
sels . Add ( obj_item );
update_selection ( sels , mode , model );
return ;
}
}
else
return ;
}
}
}
2018-10-05 23:29:15 +02:00
void ObjectList :: remove ()
{
2018-10-12 12:00:37 +02:00
if ( GetSelectedItemsCount () == 0 )
2018-10-05 23:29:15 +02:00
return ;
2019-08-23 12:29:47 +02:00
auto delete_item = [ this ]( wxDataViewItem item )
2018-10-12 12:00:37 +02:00
{
2019-08-23 12:29:47 +02:00
wxDataViewItem parent = m_objects_model -> GetParent ( item );
2019-09-04 15:15:35 +02:00
ItemType type = m_objects_model -> GetItemType ( item );
if ( type & itObject )
2018-11-23 11:54:06 +01:00
delete_from_model_and_list ( itObject , m_objects_model -> GetIdByItem ( item ), - 1 );
2019-03-21 09:04:11 +01:00
else {
2019-09-04 15:15:35 +02:00
if ( type & ( itLayer | itInstance )) {
// In case there is just one layer or two instances and we delete it, del_subobject_item will
// also remove the parent item. Selection should therefore pass to the top parent (object).
2019-06-17 13:09:11 +02:00
wxDataViewItemArray children ;
2019-09-04 15:15:35 +02:00
if ( m_objects_model -> GetChildren ( parent , children ) == ( type & itLayer ? 1 : 2 ))
2019-06-17 13:09:11 +02:00
parent = m_objects_model -> GetTopParent ( item );
2019-05-30 16:53:17 +02:00
}
2019-09-04 15:15:35 +02:00
2018-10-12 12:00:37 +02:00
del_subobject_item ( item );
2019-03-21 09:04:11 +01:00
}
2019-08-23 12:29:47 +02:00
return parent ;
};
wxDataViewItemArray sels ;
GetSelections ( sels );
2019-09-02 09:41:12 +02:00
wxDataViewItem parent = wxDataViewItem ( nullptr );
2019-08-23 12:29:47 +02:00
if ( sels . Count () == 1 )
parent = delete_item ( GetSelection ());
else
{
2020-12-04 14:19:08 +01:00
SELECTION_MODE sels_mode = m_selection_mode ;
UnselectAll ();
update_selection ( sels , sels_mode , m_objects_model );
2019-08-23 12:29:47 +02:00
Plater :: TakeSnapshot snapshot = Plater :: TakeSnapshot ( wxGetApp (). plater (), _ ( L ( "Delete Selected" )));
for ( auto & item : sels )
{
if ( m_objects_model -> InvalidItem ( item )) // item can be deleted for this moment (like last 2 Instances or Volumes)
continue ;
parent = delete_item ( item );
}
2018-10-05 23:29:15 +02:00
}
2019-06-17 13:09:11 +02:00
2019-08-23 12:29:47 +02:00
if ( parent && ! m_objects_model -> InvalidItem ( parent )) {
2019-06-17 13:09:11 +02:00
select_item ( parent );
2019-08-23 12:29:47 +02:00
update_selections_on_canvas ();
}
2018-10-05 23:29:15 +02:00
}
2019-06-06 14:14:29 +02:00
void ObjectList :: del_layer_range ( const t_layer_height_range & range )
2019-05-31 10:54:52 +02:00
{
const int obj_idx = get_selected_obj_idx ();
if ( obj_idx < 0 ) return ;
2019-06-05 11:03:46 +02:00
t_layer_config_ranges & ranges = object ( obj_idx ) -> layer_config_ranges ;
2019-05-31 10:54:52 +02:00
wxDataViewItem selectable_item = GetSelection ();
if ( ranges . size () == 1 )
selectable_item = m_objects_model -> GetParent ( selectable_item );
2019-06-06 14:14:29 +02:00
wxDataViewItem layer_item = m_objects_model -> GetItemByLayerRange ( obj_idx , range );
2019-05-31 10:54:52 +02:00
del_subobject_item ( layer_item );
select_item ( selectable_item );
}
2019-08-06 19:01:59 +02:00
static double get_min_layer_height ( const int extruder_idx )
2019-06-12 16:28:25 +02:00
{
const DynamicPrintConfig & config = wxGetApp (). preset_bundle -> printers . get_edited_preset (). config ;
2020-03-14 09:43:55 +01:00
return config . opt_float ( "min_layer_height" , std :: max ( 0 , extruder_idx - 1 ));
2019-06-12 16:28:25 +02:00
}
2019-08-06 19:01:59 +02:00
static double get_max_layer_height ( const int extruder_idx )
2019-06-12 16:28:25 +02:00
{
const DynamicPrintConfig & config = wxGetApp (). preset_bundle -> printers . get_edited_preset (). config ;
2020-03-14 09:43:55 +01:00
int extruder_idx_zero_based = std :: max ( 0 , extruder_idx - 1 );
2019-09-10 12:34:03 +02:00
double max_layer_height = config . opt_float ( "max_layer_height" , extruder_idx_zero_based );
// In case max_layer_height is set to zero, it should default to 75 % of nozzle diameter:
if ( max_layer_height < EPSILON )
max_layer_height = 0.75 * config . opt_float ( "nozzle_diameter" , extruder_idx_zero_based );
return max_layer_height ;
2019-06-12 16:28:25 +02:00
}
2020-03-14 09:43:55 +01:00
// When editing this function, please synchronize the conditions with can_add_new_range_after_current().
2020-03-11 09:10:25 +01:00
void ObjectList :: add_layer_range_after_current ( const t_layer_height_range current_range )
2019-05-31 15:29:09 +02:00
{
const int obj_idx = get_selected_obj_idx ();
2020-03-14 09:43:55 +01:00
assert ( obj_idx >= 0 );
2020-03-11 09:10:25 +01:00
if ( obj_idx < 0 )
// This should not happen.
return ;
2019-05-31 15:29:09 +02:00
2019-06-06 14:14:29 +02:00
const wxDataViewItem layers_item = GetSelection ();
2019-05-31 15:29:09 +02:00
2020-09-24 15:34:13 +02:00
auto & ranges = object ( obj_idx ) -> layer_config_ranges ;
2020-03-11 09:10:25 +01:00
auto it_range = ranges . find ( current_range );
assert ( it_range != ranges . end ());
if ( it_range == ranges . end ())
// This shoudl not happen.
return ;
2019-05-31 15:29:09 +02:00
2020-03-11 09:10:25 +01:00
auto it_next_range = it_range ;
bool changed = false ;
if ( ++ it_next_range == ranges . end ())
2019-05-31 15:29:09 +02:00
{
2020-03-11 09:10:25 +01:00
// Adding a new layer height range after the last one.
2019-08-27 11:39:51 +02:00
take_snapshot ( _ ( L ( "Add Height Range" )));
2020-03-11 09:10:25 +01:00
changed = true ;
2019-07-11 18:42:02 +02:00
2020-03-11 09:10:25 +01:00
const t_layer_height_range new_range = { current_range . second , current_range . second + 2. };
2020-09-24 15:34:13 +02:00
ranges [ new_range ]. assign_config ( get_default_layer_config ( obj_idx ));
2019-05-31 15:29:09 +02:00
add_layer_item ( new_range , layers_item );
}
2020-03-11 09:10:25 +01:00
else if ( const std :: pair < coordf_t , coordf_t > & next_range = it_next_range -> first ; current_range . second <= next_range . first )
2019-05-31 15:29:09 +02:00
{
2019-06-06 14:14:29 +02:00
const int layer_idx = m_objects_model -> GetItemIdByLayerRange ( obj_idx , next_range );
2020-03-11 09:10:25 +01:00
assert ( layer_idx >= 0 );
if ( layer_idx >= 0 )
2019-05-31 15:29:09 +02:00
{
2020-03-11 09:10:25 +01:00
if ( current_range . second == next_range . first )
{
2020-03-14 09:43:55 +01:00
// Splitting the next layer height range to two.
2020-03-11 09:10:25 +01:00
const auto old_config = ranges . at ( next_range );
2020-03-14 09:43:55 +01:00
const coordf_t delta = next_range . second - next_range . first ;
// Layer height of the current layer.
const coordf_t old_min_layer_height = get_min_layer_height ( old_config . opt_int ( "extruder" ));
// Layer height of the layer to be inserted.
const coordf_t new_min_layer_height = get_min_layer_height ( 0 );
if ( delta >= old_min_layer_height + new_min_layer_height - EPSILON ) {
const coordf_t middle_layer_z = ( new_min_layer_height > 0.5 * delta ) ?
next_range . second - new_min_layer_height :
next_range . first + std :: max ( old_min_layer_height , 0.5 * delta );
t_layer_height_range new_range = { middle_layer_z , next_range . second };
2019-06-12 16:28:25 +02:00
2020-03-11 09:10:25 +01:00
Plater :: TakeSnapshot snapshot ( wxGetApp (). plater (), _ ( L ( "Add Height Range" )));
changed = true ;
2019-05-31 15:29:09 +02:00
2020-03-11 09:10:25 +01:00
// create new 2 layers instead of deleted one
// delete old layer
2019-05-31 15:29:09 +02:00
2020-03-11 09:10:25 +01:00
wxDataViewItem layer_item = m_objects_model -> GetItemByLayerRange ( obj_idx , next_range );
del_subobject_item ( layer_item );
2019-07-11 18:42:02 +02:00
2020-03-11 09:10:25 +01:00
ranges [ new_range ] = old_config ;
add_layer_item ( new_range , layers_item , layer_idx );
2019-07-11 18:42:02 +02:00
2020-03-14 09:43:55 +01:00
new_range = { current_range . second , middle_layer_z };
2020-09-24 15:34:13 +02:00
ranges [ new_range ]. assign_config ( get_default_layer_config ( obj_idx ));
2020-03-11 09:10:25 +01:00
add_layer_item ( new_range , layers_item , layer_idx );
}
}
2020-03-14 09:43:55 +01:00
else if ( next_range . first - current_range . second >= get_min_layer_height ( 0 ) - EPSILON )
2020-03-11 09:10:25 +01:00
{
// Filling in a gap between the current and a new layer height range with a new one.
take_snapshot ( _ ( L ( "Add Height Range" )));
changed = true ;
2019-05-31 15:29:09 +02:00
2020-03-11 09:10:25 +01:00
const t_layer_height_range new_range = { current_range . second , next_range . first };
2020-09-24 15:34:13 +02:00
ranges [ new_range ]. assign_config ( get_default_layer_config ( obj_idx ));
2020-03-11 09:10:25 +01:00
add_layer_item ( new_range , layers_item , layer_idx );
}
2019-05-31 15:29:09 +02:00
}
}
2020-03-11 09:10:25 +01:00
if ( changed )
changed_object ( obj_idx );
2019-05-31 15:29:09 +02:00
2020-03-11 09:10:25 +01:00
// The layer range panel is updated even if this function does not change the layer ranges, as the panel update
// may have been postponed from the "kill focus" event of a text field, if the focus was lost for the "add layer" button.
2019-05-31 15:29:09 +02:00
// select item to update layers sizer
select_item ( layers_item );
}
2020-03-14 09:43:55 +01:00
// Returning an empty string means that the layer could be added after the current layer.
// Otherwise an error tooltip is returned.
// When editing this function, please synchronize the conditions with add_layer_range_after_current().
2020-03-12 14:50:20 +01:00
wxString ObjectList :: can_add_new_range_after_current ( const t_layer_height_range current_range )
{
const int obj_idx = get_selected_obj_idx ();
2020-03-14 09:43:55 +01:00
assert ( obj_idx >= 0 );
2020-03-12 14:50:20 +01:00
if ( obj_idx < 0 )
// This should not happen.
2020-03-14 09:43:55 +01:00
return "ObjectList assert" ;
2020-03-12 14:50:20 +01:00
2020-09-24 15:34:13 +02:00
auto & ranges = object ( obj_idx ) -> layer_config_ranges ;
2020-03-12 14:50:20 +01:00
auto it_range = ranges . find ( current_range );
assert ( it_range != ranges . end ());
if ( it_range == ranges . end ())
// This shoudl not happen.
2020-03-14 09:43:55 +01:00
return "ObjectList assert" ;
2020-03-12 14:50:20 +01:00
auto it_next_range = it_range ;
2020-03-14 09:43:55 +01:00
if ( ++ it_next_range == ranges . end ())
// Adding a layer after the last layer is always possible.
return "" ;
2020-03-12 14:50:20 +01:00
if ( const std :: pair < coordf_t , coordf_t >& next_range = it_next_range -> first ; current_range . second <= next_range . first )
{
2020-03-14 09:43:55 +01:00
if ( current_range . second == next_range . first ) {
if ( next_range . second - next_range . first < get_min_layer_height ( it_next_range -> second . opt_int ( "extruder" )) + get_min_layer_height ( 0 ) - EPSILON )
return _ ( L ( "Cannot insert a new layer range after the current layer range. \n "
"The next layer range is too thin to be split to two \n "
"without violating the minimum layer height." ));
} else if ( next_range . first - current_range . second < get_min_layer_height ( 0 ) - EPSILON ) {
return _ ( L ( "Cannot insert a new layer range between the current and the next layer range. \n "
"The gap between the current layer range and the next layer range \n "
"is thinner than the minimum layer height allowed." ));
}
} else
return _ ( L ( "Cannot insert a new layer range after the current layer range. \n "
"Current layer range overlaps with the next layer range." ));
2020-03-12 14:50:20 +01:00
2020-03-14 09:43:55 +01:00
// All right, new layer height range could be inserted.
return "" ;
2020-03-12 14:50:20 +01:00
}
2019-05-31 15:29:09 +02:00
void ObjectList :: add_layer_item ( const t_layer_height_range & range ,
const wxDataViewItem layers_item ,
const int layer_idx /* = -1*/ )
{
2019-06-10 10:48:43 +02:00
const int obj_idx = m_objects_model -> GetObjectIdByItem ( layers_item );
2019-06-05 16:47:09 +02:00
if ( obj_idx < 0 ) return ;
2020-09-24 15:34:13 +02:00
const DynamicPrintConfig & config = object ( obj_idx ) -> layer_config_ranges [ range ]. get ();
2019-06-07 11:32:46 +02:00
if ( ! config . has ( "extruder" ))
return ;
const auto layer_item = m_objects_model -> AddLayersChild ( layers_item ,
range ,
config . opt_int ( "extruder" ),
layer_idx );
2019-07-11 23:46:23 +02:00
add_settings_item ( layer_item , & config );
2019-05-31 15:29:09 +02:00
}
2019-06-12 16:28:25 +02:00
bool ObjectList :: edit_layer_range ( const t_layer_height_range & range , coordf_t layer_height )
2019-05-31 10:54:52 +02:00
{
2020-03-16 21:32:13 +01:00
// Use m_selected_object_id instead of get_selected_obj_idx()
// because of get_selected_obj_idx() return obj_idx for currently selected item.
// But edit_layer_range(...) function can be called, when Selection in ObjectList could be changed
const int obj_idx = m_selected_object_id ;
2019-06-12 16:28:25 +02:00
if ( obj_idx < 0 )
return false ;
2019-05-31 10:54:52 +02:00
2020-09-24 15:34:13 +02:00
ModelConfig * config = & object ( obj_idx ) -> layer_config_ranges [ range ];
2019-06-12 16:28:25 +02:00
if ( fabs ( layer_height - config -> opt_float ( "layer_height" )) < EPSILON )
return false ;
2019-06-04 15:22:29 +02:00
2019-06-12 16:28:25 +02:00
const int extruder_idx = config -> opt_int ( "extruder" );
if ( layer_height >= get_min_layer_height ( extruder_idx ) &&
layer_height <= get_max_layer_height ( extruder_idx ))
{
config -> set_key_value ( "layer_height" , new ConfigOptionFloat ( layer_height ));
2020-01-03 16:09:16 +01:00
changed_object ( obj_idx );
2019-06-12 16:28:25 +02:00
return true ;
}
return false ;
2019-05-31 10:54:52 +02:00
}
2020-03-11 09:10:25 +01:00
bool ObjectList :: edit_layer_range ( const t_layer_height_range & range , const t_layer_height_range & new_range , bool dont_update_ui )
2019-06-04 17:30:44 +02:00
{
2020-03-16 21:32:13 +01:00
// Use m_selected_object_id instead of get_selected_obj_idx()
// because of get_selected_obj_idx() return obj_idx for currently selected item.
// But edit_layer_range(...) function can be called, when Selection in ObjectList could be changed
const int obj_idx = m_selected_object_id ;
2019-06-12 16:28:25 +02:00
if ( obj_idx < 0 ) return false ;
2019-06-04 17:30:44 +02:00
2021-04-28 11:07:15 +02:00
take_snapshot ( _L ( "Edit Height Range" ));
2019-07-11 18:42:02 +02:00
2019-06-17 13:46:56 +02:00
const ItemType sel_type = m_objects_model -> GetItemType ( GetSelection ());
2020-09-24 15:34:13 +02:00
auto & ranges = object ( obj_idx ) -> layer_config_ranges ;
2019-06-04 17:30:44 +02:00
2020-09-24 15:34:13 +02:00
{
ModelConfig config = std :: move ( ranges [ range ]);
ranges . erase ( range );
ranges [ new_range ] = std :: move ( config );
}
2019-06-04 17:30:44 +02:00
2020-01-03 16:09:16 +01:00
changed_object ( obj_idx );
2020-03-11 09:10:25 +01:00
2019-06-04 17:30:44 +02:00
wxDataViewItem root_item = m_objects_model -> GetLayerRootItem ( m_objects_model -> GetItemById ( obj_idx ));
2019-08-09 17:47:35 +02:00
// To avoid update selection after deleting of a selected item (under GTK)
// set m_prevent_list_events to true
m_prevent_list_events = true ;
2019-06-04 17:30:44 +02:00
m_objects_model -> DeleteChildren ( root_item );
2020-03-11 09:10:25 +01:00
if ( root_item . IsOk ()) {
2019-06-04 17:30:44 +02:00
// create Layer item(s) according to the layer_config_ranges
2019-09-02 09:41:12 +02:00
for ( const auto & r : ranges )
2019-06-04 17:30:44 +02:00
add_layer_item ( r . first , root_item );
2020-03-11 09:10:25 +01:00
}
2020-03-16 21:32:13 +01:00
// if this function was invoked from wxEVT_CHANGE_SELECTION selected item could be other than itLayer or itLayerRoot
if ( ! dont_update_ui && ( sel_type & ( itLayer | itLayerRoot )))
2020-03-12 14:50:20 +01:00
select_item ( sel_type & itLayer ? m_objects_model -> GetItemByLayerRange ( obj_idx , new_range ) : root_item );
2019-06-04 17:30:44 +02:00
Expand ( root_item );
2019-06-12 16:28:25 +02:00
2020-03-16 21:32:13 +01:00
m_prevent_list_events = false ;
2019-06-12 16:28:25 +02:00
return true ;
2018-10-05 23:29:15 +02:00
}
void ObjectList :: init_objects ()
{
2019-07-18 17:41:47 +02:00
m_objects = & wxGetApp (). model (). objects ;
2018-10-05 23:29:15 +02:00
}
2018-10-04 16:43:10 +02:00
2018-10-10 16:22:20 +02:00
bool ObjectList :: multiple_selection () const
{
2018-10-11 15:57:09 +02:00
return GetSelectedItemsCount () > 1 ;
}
2019-03-29 14:36:09 +01:00
bool ObjectList :: is_selected ( const ItemType type ) const
{
const wxDataViewItem & item = GetSelection ();
if ( item )
return m_objects_model -> GetItemType ( item ) == type ;
return false ;
}
2019-07-24 12:32:38 +02:00
int ObjectList :: get_selected_layers_range_idx () const
2019-07-23 18:17:57 +02:00
{
const wxDataViewItem & item = GetSelection ();
if ( ! item )
2019-07-24 12:32:38 +02:00
return - 1 ;
2019-07-23 18:17:57 +02:00
const ItemType type = m_objects_model -> GetItemType ( item );
if ( type & itSettings && m_objects_model -> GetItemType ( m_objects_model -> GetParent ( item )) != itLayer )
2019-07-24 12:32:38 +02:00
return - 1 ;
2019-07-23 18:17:57 +02:00
2019-07-24 12:32:38 +02:00
return m_objects_model -> GetLayerIdByItem ( type & itLayer ? item : m_objects_model -> GetParent ( item ));
2019-07-23 18:17:57 +02:00
}
2018-10-11 15:57:09 +02:00
void ObjectList :: update_selections ()
{
2019-12-11 16:07:00 +01:00
const Selection & selection = scene_selection ();
2018-10-11 15:57:09 +02:00
wxDataViewItemArray sels ;
2019-07-23 18:17:57 +02:00
if ( ( m_selection_mode & ( smSettings | smLayer | smLayerRoot ) ) == 0 )
2019-07-23 16:17:37 +02:00
m_selection_mode = smInstance ;
2019-04-02 16:33:52 +02:00
2021-03-15 10:04:45 +01:00
// We doesn't update selection if itSettings | itLayerRoot | itLayer Item for the current object/part is selected
2019-05-27 16:13:24 +02:00
if ( GetSelectedItemsCount () == 1 && m_objects_model -> GetItemType ( GetSelection ()) & ( itSettings | itLayerRoot | itLayer ))
2018-12-18 15:29:09 +01:00
{
const auto item = GetSelection ();
2019-06-13 15:35:10 +02:00
if ( selection . is_single_full_object ()) {
2021-04-30 16:54:19 +02:00
if ( m_objects_model -> GetItemType ( m_objects_model -> GetParent ( item )) & itObject &&
m_objects_model -> GetObjectIdByItem ( item ) == selection . get_object_idx () )
2019-06-13 15:35:10 +02:00
return ;
sels . Add ( m_objects_model -> GetItemById ( selection . get_object_idx ()));
}
2020-12-30 12:44:07 +01:00
else if ( selection . is_single_volume () || selection . is_any_modifier ()) {
2018-12-18 15:29:09 +01:00
const auto gl_vol = selection . get_volume ( * selection . get_volume_idxs (). begin ());
if ( m_objects_model -> GetVolumeIdByItem ( m_objects_model -> GetParent ( item )) == gl_vol -> volume_idx ())
return ;
}
2019-05-03 16:05:49 +02:00
// but if there is selected only one of several instances by context menu,
// then select this instance in ObjectList
2020-12-30 12:44:07 +01:00
else if ( selection . is_single_full_instance ())
2019-05-03 16:05:49 +02:00
sels . Add ( m_objects_model -> GetItemByInstanceId ( selection . get_object_idx (), selection . get_instance_idx ()));
2020-12-30 12:44:07 +01:00
// Can be the case, when we have selected itSettings | itLayerRoot | itLayer in the ObjectList and selected object/instance in the Scene
// and then select some object/instance in 3DScene using Ctrt+left click
// see https://github.com/prusa3d/PrusaSlicer/issues/5517
else {
// Unselect all items in ObjectList
m_last_selected_item = wxDataViewItem ( nullptr );
m_prevent_list_events = true ;
UnselectAll ();
m_prevent_list_events = false ;
// call this function again to update selection from the canvas
update_selections ();
return ;
}
2018-12-18 15:29:09 +01:00
}
2019-03-29 14:36:09 +01:00
else if ( selection . is_single_full_object () || selection . is_multiple_full_object ())
2018-11-08 15:45:55 +01:00
{
2019-03-29 14:36:09 +01:00
const Selection :: ObjectIdxsToInstanceIdxsMap & objects_content = selection . get_content ();
2019-12-12 13:42:13 +01:00
// it's impossible to select Settings, Layer or LayerRoot for several objects
if ( ! selection . is_multiple_full_object () && ( m_selection_mode & ( smSettings | smLayer | smLayerRoot )))
2019-07-23 16:17:37 +02:00
{
2019-07-23 18:17:57 +02:00
auto obj_idx = objects_content . begin () -> first ;
wxDataViewItem obj_item = m_objects_model -> GetItemById ( obj_idx );
if ( m_selection_mode & smSettings )
{
2019-07-24 12:32:38 +02:00
if ( m_selected_layers_range_idx < 0 )
2019-07-23 18:17:57 +02:00
sels . Add ( m_objects_model -> GetSettingsItem ( obj_item ));
else
2019-07-24 12:32:38 +02:00
sels . Add ( m_objects_model -> GetSettingsItem ( m_objects_model -> GetItemByLayerId ( obj_idx , m_selected_layers_range_idx )));
2019-07-23 18:17:57 +02:00
}
else if ( m_selection_mode & smLayerRoot )
sels . Add ( m_objects_model -> GetLayerRootItem ( obj_item ));
2019-08-23 12:29:47 +02:00
else if ( m_selection_mode & smLayer ) {
if ( m_selected_layers_range_idx >= 0 )
sels . Add ( m_objects_model -> GetItemByLayerId ( obj_idx , m_selected_layers_range_idx ));
else
sels . Add ( obj_item );
}
2019-07-23 16:17:37 +02:00
}
else {
2019-03-29 14:36:09 +01:00
for ( const auto & object : objects_content ) {
if ( object . second . size () == 1 ) // object with 1 instance
sels . Add ( m_objects_model -> GetItemById ( object . first ));
else if ( object . second . size () > 1 ) // object with several instances
{
wxDataViewItemArray current_sels ;
GetSelections ( current_sels );
const wxDataViewItem frst_inst_item = m_objects_model -> GetItemByInstanceId ( object . first , 0 );
bool root_is_selected = false ;
for ( const auto & item : current_sels )
2019-04-02 16:33:52 +02:00
if ( item == m_objects_model -> GetParent ( frst_inst_item ) ||
item == m_objects_model -> GetTopParent ( frst_inst_item )) {
2019-03-29 14:36:09 +01:00
root_is_selected = true ;
2019-04-02 16:33:52 +02:00
sels . Add ( item );
2019-03-29 14:36:09 +01:00
break ;
}
if ( root_is_selected )
continue ;
const Selection :: InstanceIdxsList & instances = object . second ;
for ( const auto & inst : instances )
sels . Add ( m_objects_model -> GetItemByInstanceId ( object . first , inst ));
}
2019-07-23 16:17:37 +02:00
} }
2018-11-08 15:45:55 +01:00
}
2019-04-09 15:42:00 +02:00
else if ( selection . is_any_volume () || selection . is_any_modifier ())
2019-03-29 14:36:09 +01:00
{
2019-07-23 16:17:37 +02:00
if ( m_selection_mode & smSettings )
{
const auto idx = * selection . get_volume_idxs (). begin ();
const auto gl_vol = selection . get_volume ( idx );
if ( gl_vol -> volume_idx () >= 0 ) {
// Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids
// are not associated with ModelVolumes, but they are temporarily generated by the backend
// (for example, SLA supports or SLA pad).
wxDataViewItem vol_item = m_objects_model -> GetItemByVolumeId ( gl_vol -> object_idx (), gl_vol -> volume_idx ());
sels . Add ( m_objects_model -> GetSettingsItem ( vol_item ));
}
}
else {
2018-11-05 13:53:30 +01:00
for ( auto idx : selection . get_volume_idxs ()) {
const auto gl_vol = selection . get_volume ( idx );
2019-03-29 14:36:09 +01:00
if ( gl_vol -> volume_idx () >= 0 )
2018-11-17 17:23:56 +01:00
// Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids
// are not associated with ModelVolumes, but they are temporarily generated by the backend
// (for example, SLA supports or SLA pad).
2018-11-08 15:45:55 +01:00
sels . Add ( m_objects_model -> GetItemByVolumeId ( gl_vol -> object_idx (), gl_vol -> volume_idx ()));
2018-11-05 13:53:30 +01:00
}
2019-07-23 16:17:37 +02:00
m_selection_mode = smVolume ; }
2018-10-11 15:57:09 +02:00
}
2019-03-29 14:36:09 +01:00
else if ( selection . is_single_full_instance () || selection . is_multiple_full_instance ())
{
2018-11-05 13:53:30 +01:00
for ( auto idx : selection . get_instance_idxs ()) {
sels . Add ( m_objects_model -> GetItemByInstanceId ( selection . get_object_idx (), idx ));
}
}
2018-11-30 11:21:25 +01:00
else if ( selection . is_mixed ())
{
2019-04-02 16:33:52 +02:00
const Selection :: ObjectIdxsToInstanceIdxsMap & objects_content_list = selection . get_content ();
2018-11-30 11:21:25 +01:00
for ( auto idx : selection . get_volume_idxs ()) {
const auto gl_vol = selection . get_volume ( idx );
const auto & glv_obj_idx = gl_vol -> object_idx ();
const auto & glv_ins_idx = gl_vol -> instance_idx ();
bool is_selected = false ;
for ( auto obj_ins : objects_content_list ) {
if ( obj_ins . first == glv_obj_idx ) {
2020-02-14 13:27:01 +01:00
if ( obj_ins . second . find ( glv_ins_idx ) != obj_ins . second . end () &&
! selection . is_from_single_instance () ) // a case when volumes of different types are selected
{
2018-11-30 11:21:25 +01:00
if ( glv_ins_idx == 0 && ( * m_objects )[ glv_obj_idx ] -> instances . size () == 1 )
sels . Add ( m_objects_model -> GetItemById ( glv_obj_idx ));
else
sels . Add ( m_objects_model -> GetItemByInstanceId ( glv_obj_idx , glv_ins_idx ));
is_selected = true ;
break ;
}
}
}
if ( is_selected )
continue ;
const auto & glv_vol_idx = gl_vol -> volume_idx ();
if ( glv_vol_idx == 0 && ( * m_objects )[ glv_obj_idx ] -> volumes . size () == 1 )
sels . Add ( m_objects_model -> GetItemById ( glv_obj_idx ));
else
sels . Add ( m_objects_model -> GetItemByVolumeId ( glv_obj_idx , glv_vol_idx ));
}
}
2019-04-02 16:33:52 +02:00
2019-07-23 16:17:37 +02:00
if ( sels . size () == 0 || m_selection_mode & smSettings )
2019-04-02 16:33:52 +02:00
m_selection_mode = smUndef ;
2018-11-05 13:53:30 +01:00
2018-10-11 15:57:09 +02:00
select_items ( sels );
2018-12-13 13:05:47 +01:00
2019-03-07 15:26:19 +01:00
// Scroll selected Item in the middle of an object list
2020-01-27 14:31:41 +01:00
ensure_current_item_visible ();
2018-10-11 15:57:09 +02:00
}
void ObjectList :: update_selections_on_canvas ()
{
2019-03-19 13:30:21 +01:00
Selection & selection = wxGetApp (). plater () -> canvas3D () -> get_selection ();
2018-10-11 15:57:09 +02:00
const int sel_cnt = GetSelectedItemsCount ();
if ( sel_cnt == 0 ) {
2019-07-23 15:14:08 +02:00
selection . remove_all ();
2018-12-03 13:29:07 +01:00
wxGetApp (). plater () -> canvas3D () -> update_gizmos_on_off_state ();
2018-10-11 15:57:09 +02:00
return ;
}
2019-07-23 15:14:08 +02:00
std :: vector < unsigned int > volume_idxs ;
Selection :: EMode mode = Selection :: Volume ;
2019-07-31 11:52:24 +02:00
bool single_selection = sel_cnt == 1 ;
auto add_to_selection = [ this , & volume_idxs , & single_selection ]( const wxDataViewItem & item , const Selection & selection , int instance_idx , Selection :: EMode & mode )
2019-03-19 13:30:21 +01:00
{
2019-04-02 16:33:52 +02:00
const ItemType & type = m_objects_model -> GetItemType ( item );
2019-06-10 15:22:09 +02:00
const int obj_idx = m_objects_model -> GetObjectIdByItem ( item );
2018-10-11 15:57:09 +02:00
2019-04-02 16:33:52 +02:00
if ( type == itVolume ) {
2018-10-18 10:40:26 +02:00
const int vol_idx = m_objects_model -> GetVolumeIdByItem ( item );
2019-07-23 15:14:08 +02:00
std :: vector < unsigned int > idxs = selection . get_volume_idxs_from_volume ( obj_idx , std :: max ( instance_idx , 0 ), vol_idx );
volume_idxs . insert ( volume_idxs . end (), idxs . begin (), idxs . end ());
2018-10-18 10:40:26 +02:00
}
2019-04-02 16:33:52 +02:00
else if ( type == itInstance ) {
2018-10-18 10:40:26 +02:00
const int inst_idx = m_objects_model -> GetInstanceIdByItem ( item );
2019-07-23 15:14:08 +02:00
mode = Selection :: Instance ;
std :: vector < unsigned int > idxs = selection . get_volume_idxs_from_instance ( obj_idx , inst_idx );
volume_idxs . insert ( volume_idxs . end (), idxs . begin (), idxs . end ());
2018-10-18 10:40:26 +02:00
}
2019-06-10 15:22:09 +02:00
else
2019-07-23 15:14:08 +02:00
{
mode = Selection :: Instance ;
2019-07-31 15:30:03 +02:00
single_selection &= ( obj_idx != selection . get_object_idx ());
2019-07-23 15:14:08 +02:00
std :: vector < unsigned int > idxs = selection . get_volume_idxs_from_object ( obj_idx );
volume_idxs . insert ( volume_idxs . end (), idxs . begin (), idxs . end ());
}
2018-10-11 15:57:09 +02:00
};
2019-04-04 09:35:13 +02:00
// stores current instance idx before to clear the selection
int instance_idx = selection . get_instance_idx ();
2018-10-11 15:57:09 +02:00
if ( sel_cnt == 1 ) {
wxDataViewItem item = GetSelection ();
2021-04-14 07:38:50 +02:00
if ( m_objects_model -> GetItemType ( item ) & ( itSettings | itInstanceRoot | itLayerRoot | itLayer | itInfo ))
2019-07-23 15:14:08 +02:00
add_to_selection ( m_objects_model -> GetParent ( item ), selection , instance_idx , mode );
else
add_to_selection ( item , selection , instance_idx , mode );
2018-10-11 15:57:09 +02:00
}
2019-07-23 15:14:08 +02:00
else
{
wxDataViewItemArray sels ;
GetSelections ( sels );
2019-08-06 09:41:09 +02:00
// clear selection before adding new elements
selection . clear (); //OR remove_all()?
2019-07-23 15:14:08 +02:00
for ( auto item : sels )
{
add_to_selection ( item , selection , instance_idx , mode );
}
}
if ( selection . contains_all_volumes ( volume_idxs ))
{
// remove
volume_idxs = selection . get_missing_volume_idxs_from ( volume_idxs );
if ( volume_idxs . size () > 0 )
{
2019-07-26 11:32:44 +02:00
Plater :: TakeSnapshot snapshot ( wxGetApp (). plater (), _ ( L ( "Selection-Remove from list" )));
2019-07-23 15:14:08 +02:00
selection . remove_volumes ( mode , volume_idxs );
}
}
else
{
// add
volume_idxs = selection . get_unselected_volume_idxs_from ( volume_idxs );
2019-07-26 11:32:44 +02:00
Plater :: TakeSnapshot snapshot ( wxGetApp (). plater (), _ ( L ( "Selection-Add from list" )));
2019-07-31 11:52:24 +02:00
selection . add_volumes ( mode , volume_idxs , single_selection );
2019-07-23 15:14:08 +02:00
}
2018-10-11 15:57:09 +02:00
2018-12-03 13:29:07 +01:00
wxGetApp (). plater () -> canvas3D () -> update_gizmos_on_off_state ();
2019-04-10 14:03:40 +02:00
wxGetApp (). plater () -> canvas3D () -> render ();
2018-10-11 15:57:09 +02:00
}
void ObjectList :: select_item ( const wxDataViewItem & item )
{
2019-03-18 16:18:28 +01:00
if ( ! item . IsOk ()) { return ; }
2018-10-11 15:57:09 +02:00
m_prevent_list_events = true ;
UnselectAll ();
Select ( item );
part_selection_changed ();
m_prevent_list_events = false ;
}
2020-12-04 14:19:08 +01:00
void ObjectList :: select_item ( std :: function < wxDataViewItem () > get_item )
{
if ( ! get_item )
return ;
m_prevent_list_events = true ;
wxDataViewItem item = get_item ();
if ( item . IsOk ()) {
UnselectAll ();
Select ( item );
part_selection_changed ();
}
m_prevent_list_events = false ;
}
2018-10-11 15:57:09 +02:00
void ObjectList :: select_items ( const wxDataViewItemArray & sels )
{
m_prevent_list_events = true ;
2021-02-16 21:20:06 +01:00
m_last_selected_item = sels . empty () ? wxDataViewItem ( nullptr ) : sels . back ();
2018-10-11 15:57:09 +02:00
UnselectAll ();
SetSelections ( sels );
part_selection_changed ();
m_prevent_list_events = false ;
}
2018-10-12 12:00:37 +02:00
void ObjectList :: select_all ()
{
SelectAll ();
selection_changed ();
}
2018-11-08 15:45:55 +01:00
void ObjectList :: select_item_all_children ()
{
wxDataViewItemArray sels ;
// There is no selection before OR some object is selected => select all objects
if ( ! GetSelection () || m_objects_model -> GetItemType ( GetSelection ()) == itObject ) {
2019-09-02 09:41:12 +02:00
for ( size_t i = 0 ; i < m_objects -> size (); i ++ )
2018-11-08 15:45:55 +01:00
sels . Add ( m_objects_model -> GetItemById ( i ));
2019-04-02 16:33:52 +02:00
m_selection_mode = smInstance ;
2018-11-08 15:45:55 +01:00
}
else {
const auto item = GetSelection ();
2019-06-10 15:22:09 +02:00
const ItemType item_type = m_objects_model -> GetItemType ( item );
// Some volume/layer/instance is selected => select all volumes/layers/instances inside the current object
if ( item_type & ( itVolume | itInstance | itLayer ))
2018-11-08 15:45:55 +01:00
m_objects_model -> GetChildren ( m_objects_model -> GetParent ( item ), sels );
2019-04-02 16:33:52 +02:00
2019-06-10 15:22:09 +02:00
m_selection_mode = item_type & itVolume ? smVolume :
item_type & itLayer ? smLayer : smInstance ;
2018-11-08 15:45:55 +01:00
}
SetSelections ( sels );
selection_changed ();
}
2019-04-02 16:33:52 +02:00
// update selection mode for non-multiple selection
void ObjectList :: update_selection_mode ()
{
2019-07-31 09:46:45 +02:00
m_selected_layers_range_idx =- 1 ;
2019-04-02 16:33:52 +02:00
// All items are unselected
if ( ! GetSelection ())
{
2019-09-02 09:41:12 +02:00
m_last_selected_item = wxDataViewItem ( nullptr );
2019-04-02 16:33:52 +02:00
m_selection_mode = smUndef ;
return ;
}
const ItemType type = m_objects_model -> GetItemType ( GetSelection ());
2019-06-10 15:22:09 +02:00
m_selection_mode = type & itSettings ? smUndef :
type & itLayer ? smLayer :
type & itVolume ? smVolume : smInstance ;
2019-04-02 16:33:52 +02:00
}
// check last selected item. If is it possible to select it
2019-04-04 15:07:54 +02:00
bool ObjectList :: check_last_selection ( wxString & msg_str )
2019-04-02 16:33:52 +02:00
{
if ( ! m_last_selected_item )
return true ;
2019-04-04 15:07:54 +02:00
const bool is_shift_pressed = wxGetKeyState ( WXK_SHIFT );
2019-04-02 16:33:52 +02:00
2019-06-10 15:22:09 +02:00
/* We can't mix Volumes, Layers and Objects/Instances.
2019-04-04 15:07:54 +02:00
* So, show information about it
2019-04-02 16:33:52 +02:00
*/
const ItemType type = m_objects_model -> GetItemType ( m_last_selected_item );
2019-04-04 15:07:54 +02:00
2019-06-10 15:22:09 +02:00
// check a case of a selection of the same type items from different Objects
auto impossible_multi_selection = [ type , this ]( const ItemType item_type , const SELECTION_MODE selection_mode ) {
if ( ! ( type & item_type && m_selection_mode & selection_mode ))
return false ;
2019-04-04 15:07:54 +02:00
wxDataViewItemArray sels ;
GetSelections ( sels );
2019-06-10 15:22:09 +02:00
for ( const auto & sel : sels )
if ( sel != m_last_selected_item &&
m_objects_model -> GetTopParent ( sel ) != m_objects_model -> GetTopParent ( m_last_selected_item ))
return true ;
2019-04-04 15:07:54 +02:00
2019-06-10 15:22:09 +02:00
return false ;
};
if ( impossible_multi_selection ( itVolume , smVolume ) ||
impossible_multi_selection ( itLayer , smLayer ) ||
2019-04-04 15:07:54 +02:00
type & itSettings ||
2019-09-02 09:41:12 +02:00
( type & itVolume && ! ( m_selection_mode & smVolume )) ||
( type & itLayer && ! ( m_selection_mode & smLayer )) ||
( type & itInstance && ! ( m_selection_mode & smInstance ))
2019-06-10 15:22:09 +02:00
)
2019-04-02 16:33:52 +02:00
{
2020-02-27 11:44:01 +01:00
// Inform user why selection isn't completed
2019-06-10 15:22:09 +02:00
const wxString item_type = m_selection_mode & smInstance ? _ ( L ( "Object or Instance" )) :
m_selection_mode & smVolume ? _ ( L ( "Part" )) : _ ( L ( "Layer" ));
2019-04-02 16:33:52 +02:00
2019-04-04 15:07:54 +02:00
msg_str = wxString :: Format ( _ ( L ( "Unsupported selection" )) + " \n\n " +
_ ( L ( "You started your selection with %s Item." )) + " \n " +
_ ( L ( "In this mode you can select only other %s Items%s" )),
item_type , item_type ,
m_selection_mode == smInstance ? "." :
" " + _ ( L ( "of a current Object" )));
2019-04-02 16:33:52 +02:00
2019-04-04 15:07:54 +02:00
// Unselect last selected item, if selection is without SHIFT
if ( ! is_shift_pressed ) {
Unselect ( m_last_selected_item );
show_info ( this , msg_str , _ ( L ( "Info" )));
}
return is_shift_pressed ;
2019-04-02 16:33:52 +02:00
}
return true ;
}
2018-10-11 15:57:09 +02:00
void ObjectList :: fix_multiselection_conflicts ()
{
2019-04-02 16:33:52 +02:00
if ( GetSelectedItemsCount () <= 1 ) {
update_selection_mode ();
return ;
}
2019-04-04 15:07:54 +02:00
wxString msg_string ;
if ( ! check_last_selection ( msg_string ))
2018-10-11 15:57:09 +02:00
return ;
m_prevent_list_events = true ;
wxDataViewItemArray sels ;
GetSelections ( sels );
2019-06-10 15:22:09 +02:00
if ( m_selection_mode & ( smVolume | smLayer ))
2019-04-04 15:07:54 +02:00
{
// identify correct parent of the initial selected item
const wxDataViewItem & parent = m_objects_model -> GetParent ( m_last_selected_item == sels . front () ? sels . back () : sels . front ());
2019-03-29 14:36:09 +01:00
2019-04-04 15:07:54 +02:00
sels . clear ();
wxDataViewItemArray children ; // selected volumes from current parent
m_objects_model -> GetChildren ( parent , children );
2019-03-29 14:36:09 +01:00
2019-06-10 15:22:09 +02:00
const ItemType item_type = m_selection_mode & smVolume ? itVolume : itLayer ;
2019-09-02 09:41:12 +02:00
for ( const auto & child : children )
2019-06-10 15:22:09 +02:00
if ( IsSelected ( child ) && m_objects_model -> GetItemType ( child ) & item_type )
2019-04-04 15:07:54 +02:00
sels . Add ( child );
2019-03-29 14:36:09 +01:00
2019-04-04 15:07:54 +02:00
// If some part is selected, unselect all items except of selected parts of the current object
UnselectAll ();
SetSelections ( sels );
}
else
{
2019-09-02 09:41:12 +02:00
for ( const auto & item : sels )
2019-03-29 14:36:09 +01:00
{
2019-04-04 15:07:54 +02:00
if ( ! IsSelected ( item )) // if this item is unselected now (from previous actions)
continue ;
2019-04-02 16:33:52 +02:00
2019-04-04 15:07:54 +02:00
if ( m_objects_model -> GetItemType ( item ) & itSettings ) {
Unselect ( item );
continue ;
2019-04-02 16:33:52 +02:00
}
2019-04-04 15:07:54 +02:00
const wxDataViewItem & parent = m_objects_model -> GetParent ( item );
2019-09-02 09:41:12 +02:00
if ( parent != wxDataViewItem ( nullptr ) && IsSelected ( parent ))
2019-04-04 15:07:54 +02:00
Unselect ( parent );
else
{
wxDataViewItemArray unsels ;
m_objects_model -> GetAllChildren ( item , unsels );
2019-09-02 09:41:12 +02:00
for ( const auto & unsel_item : unsels )
2019-04-04 15:07:54 +02:00
Unselect ( unsel_item );
}
2019-04-02 16:33:52 +02:00
2019-04-04 15:07:54 +02:00
if ( m_objects_model -> GetItemType ( item ) & itVolume )
Unselect ( item );
m_selection_mode = smInstance ;
2019-03-29 14:36:09 +01:00
}
2018-10-11 15:57:09 +02:00
}
2019-04-04 15:07:54 +02:00
if ( ! msg_string . IsEmpty ())
show_info ( this , msg_string , _ ( L ( "Info" )));
if ( ! IsSelected ( m_last_selected_item ))
2019-09-02 09:41:12 +02:00
m_last_selected_item = wxDataViewItem ( nullptr );
2019-04-04 15:07:54 +02:00
2018-10-11 15:57:09 +02:00
m_prevent_list_events = false ;
2018-10-10 16:22:20 +02:00
}
2018-11-02 23:27:31 +01:00
ModelVolume * ObjectList :: get_selected_model_volume ()
{
2021-03-15 10:04:45 +01:00
wxDataViewItem item = GetSelection ();
if ( ! item )
2018-11-02 23:27:31 +01:00
return nullptr ;
2021-03-15 10:04:45 +01:00
if ( m_objects_model -> GetItemType ( item ) != itVolume ) {
if ( m_objects_model -> GetItemType ( m_objects_model -> GetParent ( item )) == itVolume )
item = m_objects_model -> GetParent ( item );
else
return nullptr ;
}
2018-11-02 23:27:31 +01:00
const auto vol_idx = m_objects_model -> GetVolumeIdByItem ( item );
const auto obj_idx = get_selected_obj_idx ();
if ( vol_idx < 0 || obj_idx < 0 )
return nullptr ;
return ( * m_objects )[ obj_idx ] -> volumes [ vol_idx ];
}
void ObjectList :: change_part_type ()
{
ModelVolume * volume = get_selected_model_volume ();
if ( ! volume )
return ;
2018-12-10 10:38:32 +01:00
2019-02-22 12:12:10 +01:00
const ModelVolumeType type = volume -> type ();
if ( type == ModelVolumeType :: MODEL_PART )
2018-12-10 10:38:32 +01:00
{
const int obj_idx = get_selected_obj_idx ();
if ( obj_idx < 0 ) return ;
int model_part_cnt = 0 ;
for ( auto vol : ( * m_objects )[ obj_idx ] -> volumes ) {
2019-02-22 12:12:10 +01:00
if ( vol -> type () == ModelVolumeType :: MODEL_PART )
2018-12-10 10:38:32 +01:00
++ model_part_cnt ;
}
if ( model_part_cnt == 1 ) {
Slic3r :: GUI :: show_error ( nullptr , _ ( L ( "You can't change a type of the last solid part of the object." )));
return ;
}
}
2018-11-02 23:27:31 +01:00
2021-05-24 17:30:07 +02:00
const wxString names [] = { _L ( "Part" ), _L ( "Negative Volume" ), _L ( "Modifier" ), _L ( "Support Enforcer" ), _L ( "Support Blocker" ) };
auto new_type = ModelVolumeType ( wxGetSingleChoiceIndex ( _L ( "Type:" ), _L ( "Select type of part" ), wxArrayString ( 5 , names ), int ( type )));
2018-11-02 23:27:31 +01:00
2019-02-22 12:12:10 +01:00
if ( new_type == type || new_type == ModelVolumeType :: INVALID )
2018-11-02 23:27:31 +01:00
return ;
2019-07-22 13:38:53 +02:00
take_snapshot ( _ ( L ( "Change Part Type" )));
2019-07-04 17:33:19 +02:00
2019-02-22 12:12:10 +01:00
volume -> set_type ( new_type );
2021-03-15 10:04:45 +01:00
wxDataViewItem item = GetSelection ();
if ( m_objects_model -> GetItemType ( item ) != itVolume && m_objects_model -> GetItemType ( m_objects_model -> GetParent ( item )) == itVolume )
item = m_objects_model -> GetParent ( item );
2018-11-05 08:55:44 +01:00
m_objects_model -> SetVolumeType ( item , new_type );
2018-11-02 23:27:31 +01:00
2019-04-22 01:51:10 +02:00
changed_object ( get_selected_obj_idx ());
2018-11-05 08:55:44 +01:00
// Update settings showing, if we have it
//(we show additional settings for Part and Modifier and hide it for Support Blocker/Enforcer)
const auto settings_item = m_objects_model -> GetSettingsItem ( item );
if ( settings_item &&
2021-05-24 17:30:07 +02:00
( new_type == ModelVolumeType :: SUPPORT_ENFORCER || new_type == ModelVolumeType :: SUPPORT_BLOCKER || new_type == ModelVolumeType :: NEGATIVE_VOLUME )) {
2018-11-05 08:55:44 +01:00
m_objects_model -> Delete ( settings_item );
}
else if ( ! settings_item &&
2019-02-22 12:12:10 +01:00
( new_type == ModelVolumeType :: MODEL_PART || new_type == ModelVolumeType :: PARAMETER_MODIFIER )) {
2020-09-24 15:34:13 +02:00
add_settings_item ( item , & volume -> config . get ());
2018-11-05 08:55:44 +01:00
}
2018-11-02 23:27:31 +01:00
}
2018-11-12 13:47:24 +01:00
void ObjectList :: last_volume_is_deleted ( const int obj_idx )
{
2019-09-06 17:46:55 +02:00
if ( obj_idx < 0 || size_t ( obj_idx ) >= m_objects -> size () || ( * m_objects )[ obj_idx ] -> volumes . size () != 1 )
2018-11-12 13:47:24 +01:00
return ;
2019-07-05 19:06:19 +02:00
auto volume = ( * m_objects )[ obj_idx ] -> volumes . front ();
2018-11-12 13:47:24 +01:00
// clear volume's config values
volume -> config . clear ();
// set a default extruder value, since user can't add it manually
volume -> config . set_key_value ( "extruder" , new ConfigOptionInt ( 0 ));
}
2019-07-12 13:55:46 +02:00
void ObjectList :: update_and_show_object_settings_item ()
{
const wxDataViewItem item = GetSelection ();
if ( ! item ) return ;
const wxDataViewItem & obj_item = m_objects_model -> IsSettingsItem ( item ) ? m_objects_model -> GetParent ( item ) : item ;
2020-12-04 14:19:08 +01:00
select_item ([ this , obj_item ](){ return add_settings_item ( obj_item , & get_item_config ( obj_item ). get ()); });
2019-07-12 13:55:46 +02:00
}
2018-11-29 15:00:23 +01:00
2019-06-10 10:48:43 +02:00
// Update settings item for item had it
2019-06-10 15:22:09 +02:00
void ObjectList :: update_settings_item_and_selection ( wxDataViewItem item , wxDataViewItemArray & selections )
2019-06-10 10:48:43 +02:00
{
2019-07-12 13:55:46 +02:00
const wxDataViewItem old_settings_item = m_objects_model -> GetSettingsItem ( item );
2020-09-24 15:34:13 +02:00
const wxDataViewItem new_settings_item = add_settings_item ( item , & get_item_config ( item ). get ());
2019-06-10 10:48:43 +02:00
2019-07-12 13:55:46 +02:00
if ( ! new_settings_item && old_settings_item )
m_objects_model -> Delete ( old_settings_item );
2019-06-10 10:48:43 +02:00
2019-07-12 13:55:46 +02:00
// if ols settings item was is selected area
if ( selections . Index ( old_settings_item ) != wxNOT_FOUND )
{
// If settings item was just updated
if ( old_settings_item == new_settings_item )
{
Sidebar & panel = wxGetApp (). sidebar ();
panel . Freeze ();
// update settings list
wxGetApp (). obj_settings () -> UpdateAndShow ( true );
panel . Layout ();
panel . Thaw ();
}
else
// If settings item was deleted from the list,
// it's need to be deleted from selection array, if it was there
{
selections . Remove ( old_settings_item );
// Select item, if settings_item doesn't exist for item anymore, but was selected
if ( selections . Index ( item ) == wxNOT_FOUND ) {
selections . Add ( item );
select_item ( item ); // to correct update of the SettingsList and ManipulationPanel sizers
}
}
2019-06-10 10:48:43 +02:00
}
}
void ObjectList :: update_object_list_by_printer_technology ()
{
m_prevent_canvas_selection_update = true ;
wxDataViewItemArray sel ;
GetSelections ( sel ); // stash selection
wxDataViewItemArray object_items ;
2019-09-02 09:41:12 +02:00
m_objects_model -> GetChildren ( wxDataViewItem ( nullptr ), object_items );
2019-06-10 10:48:43 +02:00
for ( auto & object_item : object_items ) {
2021-04-14 07:38:50 +02:00
// update custom supports info
update_info_items ( m_objects_model -> GetObjectIdByItem ( object_item ));
2019-06-10 10:48:43 +02:00
// Update Settings Item for object
2019-06-10 15:22:09 +02:00
update_settings_item_and_selection ( object_item , sel );
2019-06-10 10:48:43 +02:00
// Update settings for Volumes
wxDataViewItemArray all_object_subitems ;
m_objects_model -> GetChildren ( object_item , all_object_subitems );
for ( auto item : all_object_subitems )
if ( m_objects_model -> GetItemType ( item ) & itVolume )
// update settings for volume
2019-06-10 15:22:09 +02:00
update_settings_item_and_selection ( item , sel );
2019-06-10 10:48:43 +02:00
// Update Layers Items
wxDataViewItem layers_item = m_objects_model -> GetLayerRootItem ( object_item );
if ( ! layers_item )
layers_item = add_layer_root_item ( object_item );
else if ( printer_technology () == ptSLA ) {
// If layers root item will be deleted from the list, so
// it's need to be deleted from selection array, if it was there
wxDataViewItemArray del_items ;
bool some_layers_was_selected = false ;
m_objects_model -> GetAllChildren ( layers_item , del_items );
for ( auto & del_item : del_items )
if ( sel . Index ( del_item ) != wxNOT_FOUND ) {
some_layers_was_selected = true ;
sel . Remove ( del_item );
}
if ( sel . Index ( layers_item ) != wxNOT_FOUND ) {
some_layers_was_selected = true ;
sel . Remove ( layers_item );
}
// delete all "layers" items
m_objects_model -> Delete ( layers_item );
// Select object_item, if layers_item doesn't exist for item anymore, but was some of layer items was/were selected
if ( some_layers_was_selected )
sel . Add ( object_item );
}
else {
wxDataViewItemArray all_obj_layers ;
m_objects_model -> GetChildren ( layers_item , all_obj_layers );
for ( auto item : all_obj_layers )
// update settings for layer
2019-06-10 15:22:09 +02:00
update_settings_item_and_selection ( item , sel );
2019-06-10 10:48:43 +02:00
}
}
// restore selection:
SetSelections ( sel );
m_prevent_canvas_selection_update = false ;
}
2019-01-23 16:01:37 +01:00
void ObjectList :: instances_to_separated_object ( const int obj_idx , const std :: set < int >& inst_idxs )
2019-01-22 16:40:10 +01:00
{
2019-04-05 09:04:52 +02:00
if (( * m_objects )[ obj_idx ] -> instances . size () == inst_idxs . size ())
{
instances_to_separated_objects ( obj_idx );
return ;
}
2019-01-22 16:40:10 +01:00
// create new object from selected instance
ModelObject * model_object = ( * m_objects )[ obj_idx ] -> get_model () -> add_object ( * ( * m_objects )[ obj_idx ]);
2019-09-03 10:27:16 +02:00
for ( int inst_idx = int ( model_object -> instances . size ()) - 1 ; inst_idx >= 0 ; inst_idx -- )
2019-01-22 16:40:10 +01:00
{
2019-01-23 16:01:37 +01:00
if ( find ( inst_idxs . begin (), inst_idxs . end (), inst_idx ) != inst_idxs . end ())
2019-01-22 16:40:10 +01:00
continue ;
2019-01-23 16:01:37 +01:00
model_object -> delete_instance ( inst_idx );
2019-01-22 16:40:10 +01:00
}
// Add new object to the object_list
2019-08-05 14:57:30 +02:00
const size_t new_obj_indx = static_cast < size_t > ( m_objects -> size () - 1 );
add_object_to_list ( new_obj_indx );
2019-01-22 16:40:10 +01:00
2019-01-23 16:01:37 +01:00
for ( std :: set < int >:: const_reverse_iterator it = inst_idxs . rbegin (); it != inst_idxs . rend (); ++ it )
{
// delete selected instance from the object
del_subobject_from_object ( obj_idx , * it , itInstance );
delete_instance_from_list ( obj_idx , * it );
}
2019-08-05 14:57:30 +02:00
// update printable state for new volumes on canvas3D
2019-08-16 13:14:51 +02:00
wxGetApp (). plater () -> canvas3D () -> update_instance_printable_state_for_object ( new_obj_indx );
2019-01-23 16:01:37 +01:00
}
2019-04-04 18:01:53 +02:00
void ObjectList :: instances_to_separated_objects ( const int obj_idx )
{
const int inst_cnt = ( * m_objects )[ obj_idx ] -> instances . size ();
2019-08-05 14:57:30 +02:00
std :: vector < size_t > object_idxs ;
2019-04-04 18:01:53 +02:00
for ( int i = inst_cnt - 1 ; i > 0 ; i -- )
{
// create new object from initial
ModelObject * object = ( * m_objects )[ obj_idx ] -> get_model () -> add_object ( * ( * m_objects )[ obj_idx ]);
for ( int inst_idx = object -> instances . size () - 1 ; inst_idx >= 0 ; inst_idx -- )
{
if ( inst_idx == i )
continue ;
// delete unnecessary instances
object -> delete_instance ( inst_idx );
}
// Add new object to the object_list
2019-08-05 14:57:30 +02:00
const size_t new_obj_indx = static_cast < size_t > ( m_objects -> size () - 1 );
add_object_to_list ( new_obj_indx );
object_idxs . push_back ( new_obj_indx );
2019-04-04 18:01:53 +02:00
// delete current instance from the initial object
del_subobject_from_object ( obj_idx , i , itInstance );
delete_instance_from_list ( obj_idx , i );
}
2019-08-05 14:57:30 +02:00
// update printable state for new volumes on canvas3D
wxGetApp (). plater () -> canvas3D () -> update_instance_printable_state_for_objects ( object_idxs );
2019-04-04 18:01:53 +02:00
}
2019-01-23 16:01:37 +01:00
void ObjectList :: split_instances ()
{
2019-12-11 16:07:00 +01:00
const Selection & selection = scene_selection ();
2019-01-23 16:01:37 +01:00
const int obj_idx = selection . get_object_idx ();
if ( obj_idx == - 1 )
return ;
2019-07-22 13:38:53 +02:00
Plater :: TakeSnapshot snapshot ( wxGetApp (). plater (), _ ( L ( "Instances to Separated Objects" )));
2019-04-04 18:01:53 +02:00
if ( selection . is_single_full_object ())
{
instances_to_separated_objects ( obj_idx );
return ;
}
2019-01-23 16:01:37 +01:00
const int inst_idx = selection . get_instance_idx ();
const std :: set < int > inst_idxs = inst_idx < 0 ?
selection . get_instance_idxs () :
std :: set < int > { inst_idx };
instances_to_separated_object ( obj_idx , inst_idxs );
2019-01-22 16:40:10 +01:00
}
2019-01-30 11:35:37 +01:00
void ObjectList :: rename_item ()
{
const wxDataViewItem item = GetSelection ();
if ( ! item || ! ( m_objects_model -> GetItemType ( item ) & ( itVolume | itObject )))
return ;
const wxString new_name = wxGetTextFromUser ( _ ( L ( "Enter new name" )) + ":" , _ ( L ( "Renaming" )),
m_objects_model -> GetName ( item ), this );
2019-01-30 13:26:02 +01:00
if ( new_name . IsEmpty ())
return ;
2019-01-30 11:35:37 +01:00
bool is_unusable_symbol = false ;
std :: string chosen_name = Slic3r :: normalize_utf8_nfc ( new_name . ToUTF8 ());
const char * unusable_symbols = "<>:/ \\ |?* \" " ;
for ( size_t i = 0 ; i < std :: strlen ( unusable_symbols ); i ++ ) {
if ( chosen_name . find_first_of ( unusable_symbols [ i ]) != std :: string :: npos ) {
is_unusable_symbol = true ;
}
}
if ( is_unusable_symbol ) {
show_error ( this , _ ( L ( "The supplied name is not valid;" )) + " \n " +
_ ( L ( "the following characters are not allowed:" )) + " <>:/ \\ |?* \" " );
return ;
}
// The icon can't be edited so get its old value and reuse it.
wxVariant valueOld ;
2019-08-01 14:58:04 +02:00
m_objects_model -> GetValue ( valueOld , item , colName );
2019-01-30 11:35:37 +01:00
2019-04-25 01:45:00 +02:00
DataViewBitmapText bmpText ;
2019-01-30 11:35:37 +01:00
bmpText << valueOld ;
// But replace the text with the value entered by user.
bmpText . SetText ( new_name );
wxVariant value ;
value << bmpText ;
2019-08-01 14:58:04 +02:00
m_objects_model -> SetValue ( value , item , colName );
2019-01-30 11:35:37 +01:00
m_objects_model -> ItemChanged ( item );
update_name_in_model ( item );
}
2019-04-24 13:33:05 +02:00
void ObjectList :: fix_through_netfabb ()
2019-01-30 16:27:07 +01:00
{
2019-04-24 13:33:05 +02:00
int obj_idx , vol_idx ;
get_selected_item_indexes ( obj_idx , vol_idx );
2019-03-13 15:34:27 +01:00
wxGetApp (). plater () -> fix_through_netfabb ( obj_idx , vol_idx );
2019-01-30 16:27:07 +01:00
2019-03-13 15:34:27 +01:00
update_item_error_icon ( obj_idx , vol_idx );
}
void ObjectList :: update_item_error_icon ( const int obj_idx , const int vol_idx ) const
{
const wxDataViewItem item = vol_idx < 0 ? m_objects_model -> GetItemById ( obj_idx ) :
m_objects_model -> GetItemByVolumeId ( obj_idx , vol_idx );
if ( ! item )
return ;
2019-04-29 15:27:59 +02:00
if ( get_mesh_errors_count ( obj_idx , vol_idx ) == 0 )
{
// if whole object has no errors more,
if ( get_mesh_errors_count ( obj_idx ) == 0 )
// unmark all items in the object
m_objects_model -> DeleteWarningIcon ( vol_idx >= 0 ? m_objects_model -> GetParent ( item ) : item , true );
else
// unmark fixed item only
m_objects_model -> DeleteWarningIcon ( item );
2019-03-13 15:34:27 +01:00
}
2019-01-30 16:27:07 +01:00
}
2019-04-25 01:45:00 +02:00
void ObjectList :: msw_rescale ()
2019-04-13 23:46:52 +02:00
{
2020-12-29 19:10:14 +01:00
set_min_height ();
2019-04-25 16:19:50 +02:00
const int em = wxGetApp (). em_unit ();
2019-04-13 23:46:52 +02:00
2019-09-23 14:24:53 +02:00
GetColumn ( colName ) -> SetWidth ( 20 * em );
GetColumn ( colPrint ) -> SetWidth ( 3 * em );
2019-08-01 14:58:04 +02:00
GetColumn ( colExtruder ) -> SetWidth ( 8 * em );
2019-09-23 14:24:53 +02:00
GetColumn ( colEditing ) -> SetWidth ( 3 * em );
2019-04-13 23:46:52 +02:00
2019-05-09 14:53:01 +02:00
// rescale/update existing items with bitmaps
2019-04-16 10:05:45 +02:00
m_objects_model -> Rescale ();
2019-04-13 23:46:52 +02:00
Layout ();
}
2020-05-21 17:29:00 +02:00
void ObjectList :: sys_color_changed ()
{
// update existing items with bitmaps
m_objects_model -> Rescale ();
Layout ();
}
2018-12-10 13:39:56 +01:00
void ObjectList :: ItemValueChanged ( wxDataViewEvent & event )
2018-12-04 14:27:39 +01:00
{
2019-08-01 14:58:04 +02:00
if ( event . GetColumn () == colName )
2018-12-12 14:35:18 +01:00
update_name_in_model ( event . GetItem ());
2019-08-01 14:58:04 +02:00
else if ( event . GetColumn () == colExtruder )
2018-12-12 14:35:18 +01:00
update_extruder_in_config ( event . GetItem ());
}
2019-08-22 11:47:58 +02:00
#ifdef __WXMSW__
// Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected.
// Here the last active column is forgotten, so when leaving the editing mode, the next mouse click will not enter the editing mode of the newly selected column.
void ObjectList :: OnEditingStarted ( wxDataViewEvent & event )
{
m_last_selected_column = - 1 ;
}
2019-09-02 09:41:12 +02:00
#endif //__WXMSW__
2019-08-22 11:47:58 +02:00
2018-12-12 14:35:18 +01:00
void ObjectList :: OnEditingDone ( wxDataViewEvent & event )
{
2019-08-01 14:58:04 +02:00
if ( event . GetColumn () != colName )
2018-12-12 14:35:18 +01:00
return ;
2019-08-01 14:58:04 +02:00
const auto renderer = dynamic_cast < BitmapTextRenderer *> ( GetColumn ( colName ) -> GetRenderer ());
2018-12-12 14:35:18 +01:00
if ( renderer -> WasCanceled ())
2019-05-23 12:18:28 +02:00
wxTheApp -> CallAfter ([ this ]{
show_error ( this , _ ( L ( "The supplied name is not valid;" )) + " \n " +
_ ( L ( "the following characters are not allowed:" )) + " <>:/ \\ |?* \" " );
});
2019-08-22 11:47:58 +02:00
#ifdef __WXMSW__
// Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected.
// Here the last active column is forgotten, so when leaving the editing mode, the next mouse click will not enter the editing mode of the newly selected column.
m_last_selected_column = - 1 ;
2019-09-02 09:41:12 +02:00
#endif //__WXMSW__
2020-02-06 12:06:39 +01:00
2020-02-21 10:02:41 +01:00
Plater * plater = wxGetApp (). plater ();
if ( plater )
plater -> set_current_canvas_as_dirty ();
2018-12-04 14:27:39 +01:00
}
2019-03-14 13:15:04 +01:00
void ObjectList :: set_extruder_for_selected_items ( const int extruder ) const
{
wxDataViewItemArray sels ;
GetSelections ( sels );
2019-12-04 15:12:00 +01:00
if ( ! sels . empty ())
take_snapshot ( _ ( L ( "Change Extruders" )));
2019-03-14 13:15:04 +01:00
for ( const wxDataViewItem & item : sels )
{
2020-09-24 15:34:13 +02:00
ModelConfig & config = get_item_config ( item );
2019-03-14 13:15:04 +01:00
if ( config . has ( "extruder" )) {
if ( extruder == 0 )
config . erase ( "extruder" );
else
2020-09-24 15:34:13 +02:00
config . set ( "extruder" , extruder );
2019-03-14 13:15:04 +01:00
}
else if ( extruder > 0 )
config . set_key_value ( "extruder" , new ConfigOptionInt ( extruder ));
2019-05-10 12:54:20 +02:00
const wxString extruder_str = extruder == 0 ? wxString ( _ ( L ( "default" ))) :
2020-09-24 15:34:13 +02:00
wxString :: Format ( "%d" , config . extruder ());
2019-03-28 16:28:34 +01:00
auto const type = m_objects_model -> GetItemType ( item );
/* We can change extruder for Object/Volume only.
* So, if Instance is selected, get its Object item and change it
*/
2019-10-01 18:19:28 +02:00
m_objects_model -> SetExtruder ( extruder_str , type & itInstance ? m_objects_model -> GetTopParent ( item ) : item );
2019-03-28 16:28:34 +01:00
const int obj_idx = type & itObject ? m_objects_model -> GetIdByItem ( item ) :
m_objects_model -> GetIdByItem ( m_objects_model -> GetTopParent ( item ));
2019-03-14 13:15:04 +01:00
wxGetApp (). plater () -> canvas3D () -> ensure_on_bed ( obj_idx );
}
// update scene
wxGetApp (). plater () -> update ();
}
2019-07-11 18:42:02 +02:00
void ObjectList :: update_after_undo_redo ()
2019-07-04 17:33:19 +02:00
{
m_prevent_canvas_selection_update = true ;
2019-07-17 15:48:53 +02:00
Plater :: SuppressSnapshots suppress ( wxGetApp (). plater ());
2019-07-11 18:42:02 +02:00
2019-07-05 19:06:19 +02:00
// Unselect all objects before deleting them, so that no change of selection is emitted during deletion.
2019-12-12 13:49:12 +01:00
/* To avoid execution of selection_changed()
* from wxEVT_DATAVIEW_SELECTION_CHANGED emitted from DeleteAll(),
* wrap this two functions into m_prevent_list_events *
* */
m_prevent_list_events = true ;
this -> UnselectAll ();
2019-07-04 17:33:19 +02:00
m_objects_model -> DeleteAll ();
2019-12-12 13:49:12 +01:00
m_prevent_list_events = false ;
2019-07-04 17:33:19 +02:00
size_t obj_idx = 0 ;
2019-08-07 14:28:46 +02:00
std :: vector < size_t > obj_idxs ;
obj_idxs . reserve ( m_objects -> size ());
2019-07-04 17:33:19 +02:00
while ( obj_idx < m_objects -> size ()) {
2019-07-05 19:06:19 +02:00
add_object_to_list ( obj_idx , false );
2019-08-07 14:28:46 +02:00
obj_idxs . push_back ( obj_idx );
2019-07-04 17:33:19 +02:00
++ obj_idx ;
}
2019-07-11 18:42:02 +02:00
update_selections ();
2019-07-04 17:33:19 +02:00
m_prevent_canvas_selection_update = false ;
2019-08-07 14:28:46 +02:00
// update printable states on canvas
wxGetApp (). plater () -> canvas3D () -> update_instance_printable_state_for_objects ( obj_idxs );
// update scene
wxGetApp (). plater () -> update ();
2019-07-04 17:33:19 +02:00
}
2019-08-05 08:44:55 +02:00
void ObjectList :: update_printable_state ( int obj_idx , int instance_idx )
{
ModelObject * object = ( * m_objects )[ obj_idx ];
2019-08-07 14:11:41 +02:00
const PrintIndicator printable = object -> instances [ instance_idx ] -> printable ? piPrintable : piUnprintable ;
2019-08-05 08:44:55 +02:00
if ( object -> instances . size () == 1 )
instance_idx = - 1 ;
2019-08-05 10:05:28 +02:00
m_objects_model -> SetPrintableState ( printable , obj_idx , instance_idx );
}
2021-03-24 20:24:51 +01:00
void ObjectList :: toggle_printable_state ()
2019-08-05 10:05:28 +02:00
{
2021-03-24 20:24:51 +01:00
wxDataViewItemArray sels ;
GetSelections ( sels );
2021-05-10 17:57:17 +02:00
if ( sels . IsEmpty ())
return ;
2021-03-24 20:24:51 +01:00
wxDataViewItem frst_item = sels [ 0 ];
ItemType type = m_objects_model -> GetItemType ( frst_item );
if ( ! ( type & ( itObject | itInstance )))
2019-08-05 10:05:28 +02:00
return ;
2021-03-24 20:24:51 +01:00
int obj_idx = m_objects_model -> GetObjectIdByItem ( frst_item );
int inst_idx = type == itObject ? 0 : m_objects_model -> GetInstanceIdByItem ( frst_item );
bool printable = ! object ( obj_idx ) -> instances [ inst_idx ] -> printable ;
2021-05-11 11:02:12 +02:00
const wxString snapshot_text = sels . Count () > 1 ?
( printable ? _L ( "Set Printable group" ) : _L ( "Set Unprintable group" )) :
object ( obj_idx ) -> instances . size () == 1 ?
format_wxstr ( "%1% %2%" , ( printable ? _L ( "Set Printable" ) : _L ( "Set Unprintable" )), from_u8 ( object ( obj_idx ) -> name )) :
2021-03-24 20:24:51 +01:00
( printable ? _L ( "Set Printable Instance" ) : _L ( "Set Unprintable Instance" ));
take_snapshot ( snapshot_text );
std :: vector < size_t > obj_idxs ;
for ( auto item : sels )
2019-08-07 14:11:41 +02:00
{
2021-03-24 20:24:51 +01:00
type = m_objects_model -> GetItemType ( item );
if ( ! ( type & ( itObject | itInstance )))
continue ;
2019-08-07 14:11:41 +02:00
2021-03-24 20:24:51 +01:00
obj_idx = m_objects_model -> GetObjectIdByItem ( item );
ModelObject * obj = object ( obj_idx );
2019-08-07 15:39:46 +02:00
2021-03-24 20:24:51 +01:00
obj_idxs . emplace_back ( static_cast < size_t > ( obj_idx ));
2019-08-07 15:39:46 +02:00
2021-03-24 20:24:51 +01:00
// set printable value for selected instance/instances in object
if ( type == itInstance ) {
inst_idx = m_objects_model -> GetInstanceIdByItem ( item );
obj -> instances [ m_objects_model -> GetInstanceIdByItem ( item )] -> printable = printable ;
}
else
for ( auto inst : obj -> instances )
inst -> printable = printable ;
2019-08-07 14:11:41 +02:00
// update printable state in ObjectList
2021-03-24 20:24:51 +01:00
m_objects_model -> SetObjectPrintableState ( printable ? piPrintable : piUnprintable , item );
2019-08-07 14:11:41 +02:00
}
2021-03-24 20:24:51 +01:00
sort ( obj_idxs . begin (), obj_idxs . end ());
obj_idxs . erase ( unique ( obj_idxs . begin (), obj_idxs . end ()), obj_idxs . end ());
// update printable state on canvas
wxGetApp (). plater () -> canvas3D () -> update_instance_printable_state_for_objects ( obj_idxs );
2019-08-05 10:05:28 +02:00
// update scene
wxGetApp (). plater () -> update ();
2019-08-05 08:44:55 +02:00
}
2019-07-04 20:49:46 +02:00
ModelObject * ObjectList :: object ( const int obj_idx ) const
{
if ( obj_idx < 0 )
return nullptr ;
return ( * m_objects )[ obj_idx ];
}
2018-10-04 16:43:10 +02:00
} //namespace GUI
2019-08-06 19:53:20 +02:00
} //namespace Slic3r