2019-03-19 13:30:21 +01:00
#include "libslic3r/libslic3r.h"
#include "Selection.hpp"
#include "GLCanvas3D.hpp"
#include "GUI_App.hpp"
2020-03-04 09:24:13 +01:00
#include "GUI.hpp"
2019-03-19 13:30:21 +01:00
#include "GUI_ObjectManipulation.hpp"
#include "GUI_ObjectList.hpp"
#include "Gizmos/GLGizmoBase.hpp"
2019-07-02 15:49:18 +02:00
#include "3DScene.hpp"
#include "Camera.hpp"
2019-03-19 13:30:21 +01:00
#include <GL/glew.h>
#include <boost/algorithm/string/predicate.hpp>
2020-05-12 16:15:43 +02:00
#if ENABLE_GCODE_VIEWER
#include <boost/log/trivial.hpp>
#endif // ENABLE_GCODE_VIEWER
2019-03-19 13:30:21 +01:00
static const float UNIFORM_SCALE_COLOR [ 3 ] = { 1.0f , 0.38f , 0.0f };
namespace Slic3r {
namespace GUI {
Selection :: VolumeCache :: TransformCache :: TransformCache ()
: position ( Vec3d :: Zero ())
, rotation ( Vec3d :: Zero ())
, scaling_factor ( Vec3d :: Ones ())
, mirror ( Vec3d :: Ones ())
, rotation_matrix ( Transform3d :: Identity ())
, scale_matrix ( Transform3d :: Identity ())
, mirror_matrix ( Transform3d :: Identity ())
, full_matrix ( Transform3d :: Identity ())
{
}
Selection :: VolumeCache :: TransformCache :: TransformCache ( const Geometry :: Transformation & transform )
: position ( transform . get_offset ())
, rotation ( transform . get_rotation ())
, scaling_factor ( transform . get_scaling_factor ())
, mirror ( transform . get_mirror ())
, full_matrix ( transform . get_matrix ())
{
rotation_matrix = Geometry :: assemble_transform ( Vec3d :: Zero (), rotation );
scale_matrix = Geometry :: assemble_transform ( Vec3d :: Zero (), Vec3d :: Zero (), scaling_factor );
mirror_matrix = Geometry :: assemble_transform ( Vec3d :: Zero (), Vec3d :: Zero (), Vec3d :: Ones (), mirror );
}
Selection :: VolumeCache :: VolumeCache ( const Geometry :: Transformation & volume_transform , const Geometry :: Transformation & instance_transform )
: m_volume ( volume_transform )
, m_instance ( instance_transform )
{
}
2019-06-24 13:21:05 +02:00
bool Selection :: Clipboard :: is_sla_compliant () const
{
if ( m_mode == Selection :: Volume )
return false ;
for ( const ModelObject * o : m_model . objects )
{
if ( o -> is_multiparts ())
return false ;
for ( const ModelVolume * v : o -> volumes )
{
if ( v -> is_modifier ())
return false ;
}
}
return true ;
}
2019-03-19 13:30:21 +01:00
Selection :: Selection ()
: m_volumes ( nullptr )
, m_model ( nullptr )
2019-03-28 08:44:46 +01:00
, m_enabled ( false )
2019-03-19 13:30:21 +01:00
, m_mode ( Instance )
, m_type ( Empty )
, m_valid ( false )
2020-05-12 16:15:43 +02:00
#if !ENABLE_GCODE_VIEWER
2019-03-19 13:30:21 +01:00
, m_curved_arrow ( 16 )
2020-05-12 16:15:43 +02:00
#endif // !ENABLE_GCODE_VIEWER
2019-03-19 13:30:21 +01:00
, m_scale_factor ( 1.0f )
{
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
#if ENABLE_RENDER_SELECTION_CENTER
m_quadric = :: gluNewQuadric ();
if ( m_quadric != nullptr )
:: gluQuadricDrawStyle ( m_quadric , GLU_FILL );
#endif // ENABLE_RENDER_SELECTION_CENTER
}
#if ENABLE_RENDER_SELECTION_CENTER
Selection ::~ Selection ()
{
if ( m_quadric != nullptr )
:: gluDeleteQuadric ( m_quadric );
}
#endif // ENABLE_RENDER_SELECTION_CENTER
void Selection :: set_volumes ( GLVolumePtrs * volumes )
{
m_volumes = volumes ;
2019-03-21 11:02:10 +01:00
update_valid ();
2019-03-19 13:30:21 +01:00
}
2019-08-05 14:30:32 +02:00
// Init shall be called from the OpenGL render function, so that the OpenGL context is initialized!
2019-07-01 12:28:16 +02:00
bool Selection :: init ()
2019-03-19 13:30:21 +01:00
{
2020-05-12 16:15:43 +02:00
#if ENABLE_GCODE_VIEWER
2020-05-13 09:07:06 +02:00
m_arrow . init_from ( straight_arrow ( 10.0f , 5.0f , 5.0f , 10.0f , 1.0f ));
2020-05-12 16:15:43 +02:00
m_curved_arrow . init_from ( circular_arrow ( 16 , 10.0f , 5.0f , 10.0f , 5.0f , 1.0f ));
2020-05-20 14:11:22 +02:00
#if !ENABLE_SHADERS_MANAGER
2020-05-12 16:15:43 +02:00
if ( ! m_arrows_shader . init ( "gouraud_light.vs" , "gouraud_light.fs" ))
{
BOOST_LOG_TRIVIAL ( error ) << "Unable to initialize gouraud_light shader: please, check that the files gouraud_light.vs and gouraud_light.fs are available" ;
return false ;
}
2020-05-20 14:11:22 +02:00
#endif // !ENABLE_SHADERS_MANAGER
2020-05-12 16:15:43 +02:00
#else
2020-05-13 09:07:06 +02:00
if ( ! m_arrow . init ())
return false ;
m_arrow . set_scale ( 5.0 * Vec3d :: Ones ());
2019-07-01 12:28:16 +02:00
if ( ! m_curved_arrow . init ())
2019-03-19 13:30:21 +01:00
return false ;
m_curved_arrow . set_scale ( 5.0 * Vec3d :: Ones ());
2020-05-12 16:15:43 +02:00
#endif //ENABLE_GCODE_VIEWER
2019-03-19 13:30:21 +01:00
return true ;
}
void Selection :: set_model ( Model * model )
{
m_model = model ;
2019-03-21 11:02:10 +01:00
update_valid ();
2019-03-19 13:30:21 +01:00
}
2019-04-15 10:27:40 +02:00
void Selection :: add ( unsigned int volume_idx , bool as_single_selection , bool check_for_already_contained )
2019-03-19 13:30:21 +01:00
{
if ( ! m_valid || (( unsigned int ) m_volumes -> size () <= volume_idx ))
return ;
const GLVolume * volume = ( * m_volumes )[ volume_idx ];
// wipe tower is already selected
if ( is_wipe_tower () && volume -> is_wipe_tower )
return ;
2019-04-09 15:42:00 +02:00
bool keep_instance_mode = ( m_mode == Instance ) && ! as_single_selection ;
2019-04-15 10:27:40 +02:00
bool already_contained = check_for_already_contained && contains_volume ( volume_idx );
2019-04-03 15:28:09 +02:00
2019-03-19 13:30:21 +01:00
// resets the current list if needed
2019-04-09 15:42:00 +02:00
bool needs_reset = as_single_selection && ! already_contained ;
2019-03-19 13:30:21 +01:00
needs_reset |= volume -> is_wipe_tower ;
needs_reset |= is_wipe_tower () && ! volume -> is_wipe_tower ;
2019-04-09 15:42:00 +02:00
needs_reset |= as_single_selection && ! is_any_modifier () && volume -> is_modifier ;
needs_reset |= is_any_modifier () && ! volume -> is_modifier ;
2019-03-19 13:30:21 +01:00
2019-04-09 15:42:00 +02:00
if ( ! already_contained || needs_reset )
2019-04-03 15:28:09 +02:00
{
2019-07-26 11:32:44 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( L ( "Selection-Add" )));
2019-07-23 15:14:08 +02:00
if ( needs_reset )
clear ();
2019-04-03 15:28:09 +02:00
if ( ! keep_instance_mode )
m_mode = volume -> is_modifier ? Volume : Instance ;
}
2019-04-01 13:53:48 +02:00
else
2019-04-03 15:28:09 +02:00
// keep current mode
return ;
2019-03-19 13:30:21 +01:00
switch ( m_mode )
{
case Volume :
{
2019-04-01 13:53:48 +02:00
if (( volume -> volume_idx () >= 0 ) && ( is_empty () || ( volume -> instance_idx () == get_instance_idx ())))
2019-03-21 11:02:10 +01:00
do_add_volume ( volume_idx );
2019-03-19 13:30:21 +01:00
break ;
}
case Instance :
{
2019-07-23 15:14:08 +02:00
Plater :: SuppressSnapshots suppress ( wxGetApp (). plater ());
add_instance ( volume -> object_idx (), volume -> instance_idx (), as_single_selection );
2019-03-19 13:30:21 +01:00
break ;
}
}
2019-03-21 11:02:10 +01:00
update_type ();
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
void Selection :: remove ( unsigned int volume_idx )
{
if ( ! m_valid || (( unsigned int ) m_volumes -> size () <= volume_idx ))
return ;
2019-07-23 15:14:08 +02:00
if ( ! contains_volume ( volume_idx ))
return ;
2019-07-26 11:32:44 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( L ( "Selection-Remove" )));
2019-07-23 15:14:08 +02:00
2019-03-19 13:30:21 +01:00
GLVolume * volume = ( * m_volumes )[ volume_idx ];
switch ( m_mode )
{
case Volume :
{
2019-03-21 11:02:10 +01:00
do_remove_volume ( volume_idx );
2019-03-19 13:30:21 +01:00
break ;
}
case Instance :
{
2019-03-21 11:02:10 +01:00
do_remove_instance ( volume -> object_idx (), volume -> instance_idx ());
2019-03-19 13:30:21 +01:00
break ;
}
}
2019-03-21 11:02:10 +01:00
update_type ();
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
void Selection :: add_object ( unsigned int object_idx , bool as_single_selection )
{
if ( ! m_valid )
return ;
2019-07-23 15:14:08 +02:00
std :: vector < unsigned int > volume_idxs = get_volume_idxs_from_object ( object_idx );
if (( ! as_single_selection && contains_all_volumes ( volume_idxs )) ||
( as_single_selection && matches ( volume_idxs )))
return ;
2019-07-26 11:32:44 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( L ( "Selection-Add Object" )));
2019-07-23 15:14:08 +02:00
2019-03-19 13:30:21 +01:00
// resets the current list if needed
if ( as_single_selection )
clear ();
m_mode = Instance ;
2019-07-23 15:14:08 +02:00
do_add_volumes ( volume_idxs );
2019-03-19 13:30:21 +01:00
2019-03-21 11:02:10 +01:00
update_type ();
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
void Selection :: remove_object ( unsigned int object_idx )
{
if ( ! m_valid )
return ;
2019-07-26 11:32:44 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( L ( "Selection-Remove Object" )));
2019-07-23 15:14:08 +02:00
2019-03-21 11:02:10 +01:00
do_remove_object ( object_idx );
2019-03-19 13:30:21 +01:00
2019-03-21 11:02:10 +01:00
update_type ();
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
void Selection :: add_instance ( unsigned int object_idx , unsigned int instance_idx , bool as_single_selection )
{
if ( ! m_valid )
return ;
2019-07-23 15:14:08 +02:00
std :: vector < unsigned int > volume_idxs = get_volume_idxs_from_instance ( object_idx , instance_idx );
if (( ! as_single_selection && contains_all_volumes ( volume_idxs )) ||
( as_single_selection && matches ( volume_idxs )))
return ;
2019-07-26 11:32:44 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( L ( "Selection-Add Instance" )));
2019-07-23 15:14:08 +02:00
2019-03-19 13:30:21 +01:00
// resets the current list if needed
if ( as_single_selection )
clear ();
m_mode = Instance ;
2019-07-23 15:14:08 +02:00
do_add_volumes ( volume_idxs );
2019-03-19 13:30:21 +01:00
2019-03-21 11:02:10 +01:00
update_type ();
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
void Selection :: remove_instance ( unsigned int object_idx , unsigned int instance_idx )
{
if ( ! m_valid )
return ;
2019-07-26 11:32:44 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( L ( "Selection-Remove Instance" )));
2019-07-23 15:14:08 +02:00
2019-03-21 11:02:10 +01:00
do_remove_instance ( object_idx , instance_idx );
2019-03-19 13:30:21 +01:00
2019-03-21 11:02:10 +01:00
update_type ();
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
void Selection :: add_volume ( unsigned int object_idx , unsigned int volume_idx , int instance_idx , bool as_single_selection )
{
if ( ! m_valid )
return ;
2019-07-23 15:14:08 +02:00
std :: vector < unsigned int > volume_idxs = get_volume_idxs_from_volume ( object_idx , instance_idx , volume_idx );
if (( ! as_single_selection && contains_all_volumes ( volume_idxs )) ||
( as_single_selection && matches ( volume_idxs )))
return ;
2019-03-19 13:30:21 +01:00
// resets the current list if needed
if ( as_single_selection )
clear ();
m_mode = Volume ;
2019-07-23 15:14:08 +02:00
do_add_volumes ( volume_idxs );
2019-03-19 13:30:21 +01:00
2019-03-21 11:02:10 +01:00
update_type ();
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
void Selection :: remove_volume ( unsigned int object_idx , unsigned int volume_idx )
{
if ( ! m_valid )
return ;
for ( unsigned int i = 0 ; i < ( unsigned int ) m_volumes -> size (); ++ i )
{
GLVolume * v = ( * m_volumes )[ i ];
2019-09-04 10:46:51 +02:00
if (( v -> object_idx () == ( int ) object_idx ) && ( v -> volume_idx () == ( int ) volume_idx ))
2019-03-21 11:02:10 +01:00
do_remove_volume ( i );
2019-03-19 13:30:21 +01:00
}
2019-03-21 11:02:10 +01:00
update_type ();
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
2019-07-23 15:14:08 +02:00
void Selection :: add_volumes ( EMode mode , const std :: vector < unsigned int >& volume_idxs , bool as_single_selection )
{
if ( ! m_valid )
return ;
if (( ! as_single_selection && contains_all_volumes ( volume_idxs )) ||
( as_single_selection && matches ( volume_idxs )))
return ;
// resets the current list if needed
if ( as_single_selection )
clear ();
m_mode = mode ;
for ( unsigned int i : volume_idxs )
{
if ( i < ( unsigned int ) m_volumes -> size ())
do_add_volume ( i );
}
update_type ();
this -> set_bounding_boxes_dirty ();
}
void Selection :: remove_volumes ( EMode mode , const std :: vector < unsigned int >& volume_idxs )
{
if ( ! m_valid )
return ;
m_mode = mode ;
for ( unsigned int i : volume_idxs )
{
if ( i < ( unsigned int ) m_volumes -> size ())
do_remove_volume ( i );
}
update_type ();
this -> set_bounding_boxes_dirty ();
}
2019-03-19 13:30:21 +01:00
void Selection :: add_all ()
{
if ( ! m_valid )
return ;
2019-07-23 15:14:08 +02:00
unsigned int count = 0 ;
for ( unsigned int i = 0 ; i < ( unsigned int ) m_volumes -> size (); ++ i )
{
if ( ! ( * m_volumes )[ i ] -> is_wipe_tower )
++ count ;
}
if (( unsigned int ) m_list . size () == count )
return ;
2019-07-26 11:32:44 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( L ( "Selection-Add All" )));
2019-07-23 15:14:08 +02:00
2019-03-19 13:30:21 +01:00
m_mode = Instance ;
clear ();
for ( unsigned int i = 0 ; i < ( unsigned int ) m_volumes -> size (); ++ i )
{
if ( ! ( * m_volumes )[ i ] -> is_wipe_tower )
2019-03-21 11:02:10 +01:00
do_add_volume ( i );
2019-03-19 13:30:21 +01:00
}
2019-03-21 11:02:10 +01:00
update_type ();
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
2019-07-23 15:14:08 +02:00
void Selection :: remove_all ()
{
if ( ! m_valid )
return ;
if ( is_empty ())
return ;
2019-08-01 15:25:35 +02:00
// Not taking the snapshot with non-empty Redo stack will likely be more confusing than losing the Redo stack.
// Let's wait for user feedback.
// if (!wxGetApp().plater()->can_redo())
2019-07-26 11:32:44 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( L ( "Selection-Remove All" )));
2019-07-23 15:14:08 +02:00
m_mode = Instance ;
clear ();
}
2019-07-04 17:33:19 +02:00
void Selection :: set_deserialized ( EMode mode , const std :: vector < std :: pair < size_t , size_t >> & volumes_and_instances )
2019-07-04 14:35:04 +02:00
{
if ( ! m_valid )
return ;
2019-09-24 16:01:01 +02:00
m_mode = mode ;
2019-07-04 14:35:04 +02:00
for ( unsigned int i : m_list )
( * m_volumes )[ i ] -> selected = false ;
m_list . clear ();
2019-07-04 17:33:19 +02:00
for ( unsigned int i = 0 ; i < ( unsigned int ) m_volumes -> size (); ++ i )
if ( std :: binary_search ( volumes_and_instances . begin (), volumes_and_instances . end (), ( * m_volumes )[ i ] -> geometry_id ))
2019-07-04 14:35:04 +02:00
this -> do_add_volume ( i );
update_type ();
this -> set_bounding_boxes_dirty ();
}
2019-03-19 13:30:21 +01:00
void Selection :: clear ()
{
if ( ! m_valid )
return ;
2019-06-11 09:59:33 +02:00
if ( m_list . empty ())
return ;
2019-03-19 13:30:21 +01:00
for ( unsigned int i : m_list )
{
( * m_volumes )[ i ] -> selected = false ;
}
m_list . clear ();
2019-03-21 11:02:10 +01:00
update_type ();
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
2020-03-02 10:58:46 +01:00
// this happens while the application is closing
if ( wxGetApp (). obj_manipul () == nullptr )
return ;
2019-03-19 13:30:21 +01:00
// resets the cache in the sidebar
wxGetApp (). obj_manipul () -> reset_cache ();
2019-06-27 15:57:29 +02:00
// #et_FIXME fake KillFocus from sidebar
wxGetApp (). plater () -> canvas3D () -> handle_sidebar_focus_event ( "" , false );
2019-03-19 13:30:21 +01:00
}
2019-04-26 17:28:31 +02:00
// Update the selection based on the new instance IDs.
void Selection :: instances_changed ( const std :: vector < size_t > & instance_ids_selected )
{
assert ( m_valid );
assert ( m_mode == Instance );
m_list . clear ();
for ( unsigned int volume_idx = 0 ; volume_idx < ( unsigned int ) m_volumes -> size (); ++ volume_idx ) {
const GLVolume * volume = ( * m_volumes )[ volume_idx ];
auto it = std :: lower_bound ( instance_ids_selected . begin (), instance_ids_selected . end (), volume -> geometry_id . second );
if ( it != instance_ids_selected . end () && * it == volume -> geometry_id . second )
this -> do_add_volume ( volume_idx );
}
update_type ();
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-04-26 17:28:31 +02:00
}
2019-03-19 13:30:21 +01:00
// Update the selection based on the map from old indices to new indices after m_volumes changed.
// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances.
void Selection :: volumes_changed ( const std :: vector < size_t > & map_volume_old_to_new )
{
assert ( m_valid );
2019-04-26 17:28:31 +02:00
assert ( m_mode == Volume );
2019-03-19 13:30:21 +01:00
IndicesList list_new ;
2019-04-26 17:28:31 +02:00
for ( unsigned int idx : m_list )
2019-03-19 13:30:21 +01:00
if ( map_volume_old_to_new [ idx ] != size_t ( - 1 )) {
unsigned int new_idx = ( unsigned int ) map_volume_old_to_new [ idx ];
2019-09-19 09:09:11 +02:00
( * m_volumes )[ new_idx ] -> selected = true ;
2019-03-19 13:30:21 +01:00
list_new . insert ( new_idx );
}
m_list = std :: move ( list_new );
2019-03-21 11:02:10 +01:00
update_type ();
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
bool Selection :: is_single_full_instance () const
{
if ( m_type == SingleFullInstance )
return true ;
if ( m_type == SingleFullObject )
return get_instance_idx () != - 1 ;
if ( m_list . empty () || m_volumes -> empty ())
return false ;
int object_idx = m_valid ? get_object_idx () : - 1 ;
if (( object_idx < 0 ) || (( int ) m_model -> objects . size () <= object_idx ))
return false ;
int instance_idx = ( * m_volumes )[ * m_list . begin ()] -> instance_idx ();
std :: set < int > volumes_idxs ;
for ( unsigned int i : m_list )
{
const GLVolume * v = ( * m_volumes )[ i ];
if (( object_idx != v -> object_idx ()) || ( instance_idx != v -> instance_idx ()))
return false ;
int volume_idx = v -> volume_idx ();
if ( volume_idx >= 0 )
volumes_idxs . insert ( volume_idx );
}
return m_model -> objects [ object_idx ] -> volumes . size () == volumes_idxs . size ();
}
bool Selection :: is_from_single_object () const
{
int idx = get_object_idx ();
return ( 0 <= idx ) && ( idx < 1000 );
}
2019-06-24 13:21:05 +02:00
bool Selection :: is_sla_compliant () const
{
if ( m_mode == Volume )
return false ;
for ( unsigned int i : m_list )
{
if (( * m_volumes )[ i ] -> is_modifier )
return false ;
}
return true ;
}
2019-07-23 15:14:08 +02:00
bool Selection :: contains_all_volumes ( const std :: vector < unsigned int >& volume_idxs ) const
{
for ( unsigned int i : volume_idxs )
{
if ( m_list . find ( i ) == m_list . end ())
return false ;
}
return true ;
}
bool Selection :: contains_any_volume ( const std :: vector < unsigned int >& volume_idxs ) const
{
for ( unsigned int i : volume_idxs )
{
if ( m_list . find ( i ) != m_list . end ())
return true ;
}
return false ;
}
bool Selection :: matches ( const std :: vector < unsigned int >& volume_idxs ) const
{
unsigned int count = 0 ;
for ( unsigned int i : volume_idxs )
{
if ( m_list . find ( i ) != m_list . end ())
++ count ;
else
return false ;
}
return count == ( unsigned int ) m_list . size ();
}
2019-03-19 13:30:21 +01:00
bool Selection :: requires_uniform_scale () const
{
if ( is_single_full_instance () || is_single_modifier () || is_single_volume ())
return false ;
return true ;
}
int Selection :: get_object_idx () const
{
return ( m_cache . content . size () == 1 ) ? m_cache . content . begin () -> first : - 1 ;
}
int Selection :: get_instance_idx () const
{
if ( m_cache . content . size () == 1 )
{
const InstanceIdxsList & idxs = m_cache . content . begin () -> second ;
if ( idxs . size () == 1 )
return * idxs . begin ();
}
return - 1 ;
}
const Selection :: InstanceIdxsList & Selection :: get_instance_idxs () const
{
assert ( m_cache . content . size () == 1 );
return m_cache . content . begin () -> second ;
}
const GLVolume * Selection :: get_volume ( unsigned int volume_idx ) const
{
return ( m_valid && ( volume_idx < ( unsigned int ) m_volumes -> size ())) ? ( * m_volumes )[ volume_idx ] : nullptr ;
}
const BoundingBoxf3 & Selection :: get_bounding_box () const
{
if ( m_bounding_box_dirty )
2019-03-21 11:02:10 +01:00
calc_bounding_box ();
2019-03-19 13:30:21 +01:00
return m_bounding_box ;
}
2019-05-03 12:36:26 +02:00
const BoundingBoxf3 & Selection :: get_unscaled_instance_bounding_box () const
{
if ( m_unscaled_instance_bounding_box_dirty )
calc_unscaled_instance_bounding_box ();
return m_unscaled_instance_bounding_box ;
}
2019-05-07 15:43:53 +02:00
const BoundingBoxf3 & Selection :: get_scaled_instance_bounding_box () const
{
if ( m_scaled_instance_bounding_box_dirty )
calc_scaled_instance_bounding_box ();
return m_scaled_instance_bounding_box ;
}
2019-03-19 13:30:21 +01:00
void Selection :: start_dragging ()
{
if ( ! m_valid )
return ;
2019-03-21 11:02:10 +01:00
set_caches ();
2019-03-19 13:30:21 +01:00
}
void Selection :: translate ( const Vec3d & displacement , bool local )
{
if ( ! m_valid )
return ;
2019-04-01 13:53:48 +02:00
EMode translation_type = m_mode ;
2019-03-19 13:30:21 +01:00
for ( unsigned int i : m_list )
{
if (( m_mode == Volume ) || ( * m_volumes )[ i ] -> is_wipe_tower )
{
if ( local )
( * m_volumes )[ i ] -> set_volume_offset ( m_cache . volumes_data [ i ]. get_volume_position () + displacement );
else
{
Vec3d local_displacement = ( m_cache . volumes_data [ i ]. get_instance_rotation_matrix () * m_cache . volumes_data [ i ]. get_instance_scale_matrix () * m_cache . volumes_data [ i ]. get_instance_mirror_matrix ()). inverse () * displacement ;
( * m_volumes )[ i ] -> set_volume_offset ( m_cache . volumes_data [ i ]. get_volume_position () + local_displacement );
}
}
else if ( m_mode == Instance )
2019-04-01 13:53:48 +02:00
{
2019-04-01 14:21:55 +02:00
if ( is_from_fully_selected_instance ( i ))
2019-04-01 13:53:48 +02:00
( * m_volumes )[ i ] -> set_instance_offset ( m_cache . volumes_data [ i ]. get_instance_position () + displacement );
else
{
Vec3d local_displacement = ( m_cache . volumes_data [ i ]. get_instance_rotation_matrix () * m_cache . volumes_data [ i ]. get_instance_scale_matrix () * m_cache . volumes_data [ i ]. get_instance_mirror_matrix ()). inverse () * displacement ;
( * m_volumes )[ i ] -> set_volume_offset ( m_cache . volumes_data [ i ]. get_volume_position () + local_displacement );
translation_type = Volume ;
}
}
2019-03-19 13:30:21 +01:00
}
#if !DISABLE_INSTANCES_SYNCH
2019-04-01 13:53:48 +02:00
if ( translation_type == Instance )
2019-03-21 11:02:10 +01:00
synchronize_unselected_instances ( SYNC_ROTATION_NONE );
2019-04-01 13:53:48 +02:00
else if ( translation_type == Volume )
2019-03-21 11:02:10 +01:00
synchronize_unselected_volumes ();
2019-03-19 13:30:21 +01:00
#endif // !DISABLE_INSTANCES_SYNCH
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
// Rotate an object around one of the axes. Only one rotation component is expected to be changing.
void Selection :: rotate ( const Vec3d & rotation , TransformationType transformation_type )
{
if ( ! m_valid )
return ;
// Only relative rotation values are allowed in the world coordinate system.
assert ( ! transformation_type . world () || transformation_type . relative ());
2019-04-26 15:34:26 +02:00
if ( ! is_wipe_tower ()) {
int rot_axis_max = 0 ;
if ( rotation . isApprox ( Vec3d :: Zero ()))
2019-03-19 13:30:21 +01:00
{
2019-04-26 15:34:26 +02:00
for ( unsigned int i : m_list )
2019-03-19 13:30:21 +01:00
{
2019-04-26 15:34:26 +02:00
GLVolume & volume = * ( * m_volumes )[ i ];
2019-03-19 13:30:21 +01:00
if ( m_mode == Instance )
2019-04-26 15:34:26 +02:00
{
volume . set_instance_rotation ( m_cache . volumes_data [ i ]. get_instance_rotation ());
volume . set_instance_offset ( m_cache . volumes_data [ i ]. get_instance_position ());
}
2019-03-19 13:30:21 +01:00
else if ( m_mode == Volume )
{
2019-04-26 15:34:26 +02:00
volume . set_volume_rotation ( m_cache . volumes_data [ i ]. get_volume_rotation ());
volume . set_volume_offset ( m_cache . volumes_data [ i ]. get_volume_position ());
2019-03-19 13:30:21 +01:00
}
}
}
2019-04-26 15:34:26 +02:00
else { // this is not the wipe tower
//FIXME this does not work for absolute rotations (transformation_type.absolute() is true)
rotation . cwiseAbs (). maxCoeff ( & rot_axis_max );
2019-03-19 13:30:21 +01:00
2019-05-15 15:22:18 +02:00
// if ( single instance or single volume )
// Rotate around center , if only a single object or volume
// transformation_type.set_independent();
2019-04-26 15:34:26 +02:00
// For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it.
std :: vector < int > object_instance_first ( m_model -> objects . size (), - 1 );
auto rotate_instance = [ this , & rotation , & object_instance_first , rot_axis_max , transformation_type ]( GLVolume & volume , int i ) {
int first_volume_idx = object_instance_first [ volume . object_idx ()];
if ( rot_axis_max != 2 && first_volume_idx != - 1 ) {
// Generic rotation, but no rotation around the Z axis.
// Always do a local rotation (do not consider the selection to be a rigid body).
assert ( is_approx ( rotation . z (), 0.0 ));
const GLVolume & first_volume = * ( * m_volumes )[ first_volume_idx ];
const Vec3d & rotation = first_volume . get_instance_rotation ();
double z_diff = Geometry :: rotation_diff_z ( m_cache . volumes_data [ first_volume_idx ]. get_instance_rotation (), m_cache . volumes_data [ i ]. get_instance_rotation ());
volume . set_instance_rotation ( Vec3d ( rotation ( 0 ), rotation ( 1 ), rotation ( 2 ) + z_diff ));
}
else {
// extracts rotations from the composed transformation
Vec3d new_rotation = transformation_type . world () ?
Geometry :: extract_euler_angles ( Geometry :: assemble_transform ( Vec3d :: Zero (), rotation ) * m_cache . volumes_data [ i ]. get_instance_rotation_matrix ()) :
transformation_type . absolute () ? rotation : rotation + m_cache . volumes_data [ i ]. get_instance_rotation ();
if ( rot_axis_max == 2 && transformation_type . joint ()) {
// Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis.
2019-05-15 15:22:18 +02:00
double z_diff = Geometry :: rotation_diff_z ( m_cache . volumes_data [ i ]. get_instance_rotation (), new_rotation );
volume . set_instance_offset ( m_cache . dragging_center + Eigen :: AngleAxisd ( z_diff , Vec3d :: UnitZ ()) * ( m_cache . volumes_data [ i ]. get_instance_position () - m_cache . dragging_center ));
2019-04-26 15:34:26 +02:00
}
volume . set_instance_rotation ( new_rotation );
object_instance_first [ volume . object_idx ()] = i ;
}
};
for ( unsigned int i : m_list )
{
GLVolume & volume = * ( * m_volumes )[ i ];
if ( is_single_full_instance ())
rotate_instance ( volume , i );
else if ( is_single_volume () || is_single_modifier ())
{
if ( transformation_type . independent ())
volume . set_volume_rotation ( volume . get_volume_rotation () + rotation );
else
{
Transform3d m = Geometry :: assemble_transform ( Vec3d :: Zero (), rotation );
Vec3d new_rotation = Geometry :: extract_euler_angles ( m * m_cache . volumes_data [ i ]. get_volume_rotation_matrix ());
volume . set_volume_rotation ( new_rotation );
}
}
else
{
if ( m_mode == Instance )
rotate_instance ( volume , i );
else if ( m_mode == Volume )
{
// extracts rotations from the composed transformation
Transform3d m = Geometry :: assemble_transform ( Vec3d :: Zero (), rotation );
Vec3d new_rotation = Geometry :: extract_euler_angles ( m * m_cache . volumes_data [ i ]. get_volume_rotation_matrix ());
if ( transformation_type . joint ())
{
Vec3d local_pivot = m_cache . volumes_data [ i ]. get_instance_full_matrix (). inverse () * m_cache . dragging_center ;
Vec3d offset = m * ( m_cache . volumes_data [ i ]. get_volume_position () - local_pivot );
volume . set_volume_offset ( local_pivot + offset );
}
volume . set_volume_rotation ( new_rotation );
}
}
}
}
#if !DISABLE_INSTANCES_SYNCH
if ( m_mode == Instance )
synchronize_unselected_instances (( rot_axis_max == 2 ) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL );
else if ( m_mode == Volume )
synchronize_unselected_volumes ();
#endif // !DISABLE_INSTANCES_SYNCH
}
else { // it's the wipe tower that's selected and being rotated
GLVolume & volume = * (( * m_volumes )[ * m_list . begin ()]); // the wipe tower is always alone in the selection
// make sure the wipe tower rotates around its center, not origin
// we can assume that only Z rotation changes
Vec3d center_local = volume . transformed_bounding_box (). center () - volume . get_volume_offset ();
Vec3d center_local_new = Eigen :: AngleAxisd ( rotation ( 2 ) - volume . get_volume_rotation ()( 2 ), Vec3d ( 0 , 0 , 1 )) * center_local ;
volume . set_volume_rotation ( rotation );
volume . set_volume_offset ( volume . get_volume_offset () + center_local - center_local_new );
}
2019-03-19 13:30:21 +01:00
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
void Selection :: flattening_rotate ( const Vec3d & normal )
{
// We get the normal in untransformed coordinates. We must transform it using the instance matrix, find out
// how to rotate the instance so it faces downwards and do the rotation. All that for all selected instances.
// The function assumes that is_from_single_object() holds.
if ( ! m_valid )
return ;
for ( unsigned int i : m_list )
{
Transform3d wst = m_cache . volumes_data [ i ]. get_instance_scale_matrix ();
Vec3d scaling_factor = Vec3d ( 1. / wst ( 0 , 0 ), 1. / wst ( 1 , 1 ), 1. / wst ( 2 , 2 ));
Transform3d wmt = m_cache . volumes_data [ i ]. get_instance_mirror_matrix ();
Vec3d mirror ( wmt ( 0 , 0 ), wmt ( 1 , 1 ), wmt ( 2 , 2 ));
Vec3d rotation = Geometry :: extract_euler_angles ( m_cache . volumes_data [ i ]. get_instance_rotation_matrix ());
Vec3d transformed_normal = Geometry :: assemble_transform ( Vec3d :: Zero (), rotation , scaling_factor , mirror ) * normal ;
transformed_normal . normalize ();
Vec3d axis = transformed_normal ( 2 ) > 0.999f ? Vec3d ( 1. , 0. , 0. ) : Vec3d ( transformed_normal . cross ( Vec3d ( 0. , 0. , - 1. )));
axis . normalize ();
Transform3d extra_rotation = Transform3d :: Identity ();
extra_rotation . rotate ( Eigen :: AngleAxisd ( acos ( - transformed_normal ( 2 )), axis ));
Vec3d new_rotation = Geometry :: extract_euler_angles ( extra_rotation * m_cache . volumes_data [ i ]. get_instance_rotation_matrix ());
( * m_volumes )[ i ] -> set_instance_rotation ( new_rotation );
}
#if !DISABLE_INSTANCES_SYNCH
// we want to synchronize z-rotation as well, otherwise the flattening behaves funny
// when applied on one of several identical instances
if ( m_mode == Instance )
2019-03-21 11:02:10 +01:00
synchronize_unselected_instances ( SYNC_ROTATION_FULL );
2019-03-19 13:30:21 +01:00
#endif // !DISABLE_INSTANCES_SYNCH
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
2019-04-24 11:01:59 +02:00
void Selection :: scale ( const Vec3d & scale , TransformationType transformation_type )
2019-03-19 13:30:21 +01:00
{
if ( ! m_valid )
return ;
for ( unsigned int i : m_list )
{
2019-05-03 12:36:26 +02:00
GLVolume & volume = * ( * m_volumes )[ i ];
if ( is_single_full_instance ()) {
2019-05-22 14:42:38 +02:00
if ( transformation_type . relative ())
{
Transform3d m = Geometry :: assemble_transform ( Vec3d :: Zero (), Vec3d :: Zero (), scale );
Eigen :: Matrix < double , 3 , 3 , Eigen :: DontAlign > new_matrix = ( m * m_cache . volumes_data [ i ]. get_instance_scale_matrix ()). matrix (). block ( 0 , 0 , 3 , 3 );
// extracts scaling factors from the composed transformation
Vec3d new_scale ( new_matrix . col ( 0 ). norm (), new_matrix . col ( 1 ). norm (), new_matrix . col ( 2 ). norm ());
if ( transformation_type . joint ())
volume . set_instance_offset ( m_cache . dragging_center + m * ( m_cache . volumes_data [ i ]. get_instance_position () - m_cache . dragging_center ));
volume . set_instance_scaling_factor ( new_scale );
}
else
{
if ( transformation_type . world () && ( std :: abs ( scale . x () - scale . y ()) > EPSILON || std :: abs ( scale . x () - scale . z ()) > EPSILON )) {
// Non-uniform scaling. Transform the scaling factors into the local coordinate system.
// This is only possible, if the instance rotation is mulitples of ninety degrees.
assert ( Geometry :: is_rotation_ninety_degrees ( volume . get_instance_rotation ()));
volume . set_instance_scaling_factor (( volume . get_instance_transformation (). get_matrix ( true , false , true , true ). matrix (). block < 3 , 3 > ( 0 , 0 ). transpose () * scale ). cwiseAbs ());
}
else
volume . set_instance_scaling_factor ( scale );
}
2019-05-03 12:36:26 +02:00
}
2019-03-19 13:30:21 +01:00
else if ( is_single_volume () || is_single_modifier ())
2019-05-03 12:36:26 +02:00
volume . set_volume_scaling_factor ( scale );
2019-03-19 13:30:21 +01:00
else
{
Transform3d m = Geometry :: assemble_transform ( Vec3d :: Zero (), Vec3d :: Zero (), scale );
if ( m_mode == Instance )
{
Eigen :: Matrix < double , 3 , 3 , Eigen :: DontAlign > new_matrix = ( m * m_cache . volumes_data [ i ]. get_instance_scale_matrix ()). matrix (). block ( 0 , 0 , 3 , 3 );
// extracts scaling factors from the composed transformation
Vec3d new_scale ( new_matrix . col ( 0 ). norm (), new_matrix . col ( 1 ). norm (), new_matrix . col ( 2 ). norm ());
2019-04-24 11:01:59 +02:00
if ( transformation_type . joint ())
2019-05-03 12:36:26 +02:00
volume . set_instance_offset ( m_cache . dragging_center + m * ( m_cache . volumes_data [ i ]. get_instance_position () - m_cache . dragging_center ));
2019-03-19 13:30:21 +01:00
2019-05-03 12:36:26 +02:00
volume . set_instance_scaling_factor ( new_scale );
2019-03-19 13:30:21 +01:00
}
else if ( m_mode == Volume )
{
Eigen :: Matrix < double , 3 , 3 , Eigen :: DontAlign > new_matrix = ( m * m_cache . volumes_data [ i ]. get_volume_scale_matrix ()). matrix (). block ( 0 , 0 , 3 , 3 );
// extracts scaling factors from the composed transformation
Vec3d new_scale ( new_matrix . col ( 0 ). norm (), new_matrix . col ( 1 ). norm (), new_matrix . col ( 2 ). norm ());
2019-04-24 11:01:59 +02:00
if ( transformation_type . joint ())
2019-03-19 13:30:21 +01:00
{
Vec3d offset = m * ( m_cache . volumes_data [ i ]. get_volume_position () + m_cache . volumes_data [ i ]. get_instance_position () - m_cache . dragging_center );
2019-05-03 12:36:26 +02:00
volume . set_volume_offset ( m_cache . dragging_center - m_cache . volumes_data [ i ]. get_instance_position () + offset );
2019-03-19 13:30:21 +01:00
}
2019-05-03 12:36:26 +02:00
volume . set_volume_scaling_factor ( new_scale );
2019-03-19 13:30:21 +01:00
}
}
}
#if !DISABLE_INSTANCES_SYNCH
if ( m_mode == Instance )
2019-03-21 11:02:10 +01:00
synchronize_unselected_instances ( SYNC_ROTATION_NONE );
2019-03-19 13:30:21 +01:00
else if ( m_mode == Volume )
2019-03-21 11:02:10 +01:00
synchronize_unselected_volumes ();
2019-03-19 13:30:21 +01:00
#endif // !DISABLE_INSTANCES_SYNCH
2019-03-21 11:02:10 +01:00
ensure_on_bed ();
2019-03-19 13:30:21 +01:00
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
2019-05-22 14:42:38 +02:00
void Selection :: scale_to_fit_print_volume ( const DynamicPrintConfig & config )
{
if ( is_empty () || ( m_mode == Volume ))
return ;
// adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings
Vec3d box_size = get_bounding_box (). size () + 0.01 * Vec3d :: Ones ();
const ConfigOptionPoints * opt = dynamic_cast < const ConfigOptionPoints *> ( config . option ( "bed_shape" ));
if ( opt != nullptr )
{
BoundingBox bed_box_2D = get_extents ( Polygon :: new_scale ( opt -> values ));
BoundingBoxf3 print_volume ( Vec3d ( unscale < double > ( bed_box_2D . min ( 0 )), unscale < double > ( bed_box_2D . min ( 1 )), 0.0 ), Vec3d ( unscale < double > ( bed_box_2D . max ( 0 )), unscale < double > ( bed_box_2D . max ( 1 )), config . opt_float ( "max_print_height" )));
Vec3d print_volume_size = print_volume . size ();
double sx = ( box_size ( 0 ) != 0.0 ) ? print_volume_size ( 0 ) / box_size ( 0 ) : 0.0 ;
double sy = ( box_size ( 1 ) != 0.0 ) ? print_volume_size ( 1 ) / box_size ( 1 ) : 0.0 ;
double sz = ( box_size ( 2 ) != 0.0 ) ? print_volume_size ( 2 ) / box_size ( 2 ) : 0.0 ;
if (( sx != 0.0 ) && ( sy != 0.0 ) && ( sz != 0.0 ))
{
double s = std :: min ( sx , std :: min ( sy , sz ));
if ( s != 1.0 )
{
2019-07-09 10:18:57 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( L ( "Scale To Fit" )));
2019-07-05 19:06:19 +02:00
2019-05-22 14:42:38 +02:00
TransformationType type ;
type . set_world ();
type . set_relative ();
type . set_joint ();
// apply scale
start_dragging ();
scale ( s * Vec3d :: Ones (), type );
2019-08-28 15:12:55 +02:00
wxGetApp (). plater () -> canvas3D () -> do_scale ( "" ); // avoid storing another snapshot
2019-05-22 14:42:38 +02:00
// center selection on print bed
start_dragging ();
translate ( print_volume . center () - get_bounding_box (). center ());
2019-08-28 15:12:55 +02:00
wxGetApp (). plater () -> canvas3D () -> do_move ( "" ); // avoid storing another snapshot
2019-05-22 14:42:38 +02:00
wxGetApp (). obj_manipul () -> set_dirty ();
}
}
}
}
2019-03-19 13:30:21 +01:00
void Selection :: mirror ( Axis axis )
{
if ( ! m_valid )
return ;
bool single_full_instance = is_single_full_instance ();
for ( unsigned int i : m_list )
{
if ( single_full_instance )
( * m_volumes )[ i ] -> set_instance_mirror ( axis , - ( * m_volumes )[ i ] -> get_instance_mirror ( axis ));
else if ( m_mode == Volume )
( * m_volumes )[ i ] -> set_volume_mirror ( axis , - ( * m_volumes )[ i ] -> get_volume_mirror ( axis ));
}
#if !DISABLE_INSTANCES_SYNCH
if ( m_mode == Instance )
2019-03-21 11:02:10 +01:00
synchronize_unselected_instances ( SYNC_ROTATION_NONE );
2019-03-19 13:30:21 +01:00
else if ( m_mode == Volume )
2019-03-21 11:02:10 +01:00
synchronize_unselected_volumes ();
2019-03-19 13:30:21 +01:00
#endif // !DISABLE_INSTANCES_SYNCH
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
void Selection :: translate ( unsigned int object_idx , const Vec3d & displacement )
{
if ( ! m_valid )
return ;
for ( unsigned int i : m_list )
{
GLVolume * v = ( * m_volumes )[ i ];
2019-09-04 10:46:51 +02:00
if ( v -> object_idx () == ( int ) object_idx )
2019-03-19 13:30:21 +01:00
v -> set_instance_offset ( v -> get_instance_offset () + displacement );
}
std :: set < unsigned int > done ; // prevent processing volumes twice
done . insert ( m_list . begin (), m_list . end ());
for ( unsigned int i : m_list )
{
if ( done . size () == m_volumes -> size ())
break ;
int object_idx = ( * m_volumes )[ i ] -> object_idx ();
if ( object_idx >= 1000 )
continue ;
// Process unselected volumes of the object.
for ( unsigned int j = 0 ; j < ( unsigned int ) m_volumes -> size (); ++ j )
{
if ( done . size () == m_volumes -> size ())
break ;
if ( done . find ( j ) != done . end ())
continue ;
GLVolume * v = ( * m_volumes )[ j ];
if ( v -> object_idx () != object_idx )
continue ;
v -> set_instance_offset ( v -> get_instance_offset () + displacement );
done . insert ( j );
}
}
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
void Selection :: translate ( unsigned int object_idx , unsigned int instance_idx , const Vec3d & displacement )
{
if ( ! m_valid )
return ;
for ( unsigned int i : m_list )
{
GLVolume * v = ( * m_volumes )[ i ];
2019-09-04 10:46:51 +02:00
if (( v -> object_idx () == ( int ) object_idx ) && ( v -> instance_idx () == ( int ) instance_idx ))
2019-03-19 13:30:21 +01:00
v -> set_instance_offset ( v -> get_instance_offset () + displacement );
}
std :: set < unsigned int > done ; // prevent processing volumes twice
done . insert ( m_list . begin (), m_list . end ());
for ( unsigned int i : m_list )
{
if ( done . size () == m_volumes -> size ())
break ;
int object_idx = ( * m_volumes )[ i ] -> object_idx ();
if ( object_idx >= 1000 )
continue ;
// Process unselected volumes of the object.
for ( unsigned int j = 0 ; j < ( unsigned int ) m_volumes -> size (); ++ j )
{
if ( done . size () == m_volumes -> size ())
break ;
if ( done . find ( j ) != done . end ())
continue ;
GLVolume * v = ( * m_volumes )[ j ];
2019-09-04 10:46:51 +02:00
if (( v -> object_idx () != object_idx ) || ( v -> instance_idx () != ( int ) instance_idx ))
2019-03-19 13:30:21 +01:00
continue ;
v -> set_instance_offset ( v -> get_instance_offset () + displacement );
done . insert ( j );
}
}
2019-05-03 12:36:26 +02:00
this -> set_bounding_boxes_dirty ();
2019-03-19 13:30:21 +01:00
}
void Selection :: erase ()
{
if ( ! m_valid )
return ;
if ( is_single_full_object ())
wxGetApp (). obj_list () -> delete_from_model_and_list ( ItemType :: itObject , get_object_idx (), 0 );
else if ( is_multiple_full_object ())
{
std :: vector < ItemForDelete > items ;
items . reserve ( m_cache . content . size ());
for ( ObjectIdxsToInstanceIdxsMap :: iterator it = m_cache . content . begin (); it != m_cache . content . end (); ++ it )
{
items . emplace_back ( ItemType :: itObject , it -> first , 0 );
}
wxGetApp (). obj_list () -> delete_from_model_and_list ( items );
}
else if ( is_multiple_full_instance ())
{
std :: set < std :: pair < int , int >> instances_idxs ;
for ( ObjectIdxsToInstanceIdxsMap :: iterator obj_it = m_cache . content . begin (); obj_it != m_cache . content . end (); ++ obj_it )
{
for ( InstanceIdxsList :: reverse_iterator inst_it = obj_it -> second . rbegin (); inst_it != obj_it -> second . rend (); ++ inst_it )
{
instances_idxs . insert ( std :: make_pair ( obj_it -> first , * inst_it ));
}
}
std :: vector < ItemForDelete > items ;
items . reserve ( instances_idxs . size ());
for ( const std :: pair < int , int >& i : instances_idxs )
{
items . emplace_back ( ItemType :: itInstance , i . first , i . second );
}
wxGetApp (). obj_list () -> delete_from_model_and_list ( items );
}
else if ( is_single_full_instance ())
wxGetApp (). obj_list () -> delete_from_model_and_list ( ItemType :: itInstance , get_object_idx (), get_instance_idx ());
else if ( is_mixed ())
{
std :: set < ItemForDelete > items_set ;
std :: map < int , int > volumes_in_obj ;
for ( auto i : m_list ) {
const auto gl_vol = ( * m_volumes )[ i ];
const auto glv_obj_idx = gl_vol -> object_idx ();
const auto model_object = m_model -> objects [ glv_obj_idx ];
if ( model_object -> instances . size () == 1 ) {
if ( model_object -> volumes . size () == 1 )
items_set . insert ( ItemForDelete ( ItemType :: itObject , glv_obj_idx , - 1 ));
else {
items_set . insert ( ItemForDelete ( ItemType :: itVolume , glv_obj_idx , gl_vol -> volume_idx ()));
int idx = ( volumes_in_obj . find ( glv_obj_idx ) == volumes_in_obj . end ()) ? 0 : volumes_in_obj . at ( glv_obj_idx );
volumes_in_obj [ glv_obj_idx ] = ++ idx ;
}
continue ;
}
const auto glv_ins_idx = gl_vol -> instance_idx ();
for ( auto obj_ins : m_cache . content ) {
if ( obj_ins . first == glv_obj_idx ) {
if ( obj_ins . second . find ( glv_ins_idx ) != obj_ins . second . end ()) {
if ( obj_ins . second . size () == model_object -> instances . size ())
items_set . insert ( ItemForDelete ( ItemType :: itVolume , glv_obj_idx , gl_vol -> volume_idx ()));
else
items_set . insert ( ItemForDelete ( ItemType :: itInstance , glv_obj_idx , glv_ins_idx ));
break ;
}
}
}
}
std :: vector < ItemForDelete > items ;
items . reserve ( items_set . size ());
for ( const ItemForDelete & i : items_set ) {
if ( i . type == ItemType :: itVolume ) {
const int vol_in_obj_cnt = volumes_in_obj . find ( i . obj_idx ) == volumes_in_obj . end () ? 0 : volumes_in_obj . at ( i . obj_idx );
2019-09-04 10:46:51 +02:00
if ( vol_in_obj_cnt == ( int ) m_model -> objects [ i . obj_idx ] -> volumes . size ()) {
2019-03-19 13:30:21 +01:00
if ( i . sub_obj_idx == vol_in_obj_cnt - 1 )
items . emplace_back ( ItemType :: itObject , i . obj_idx , 0 );
continue ;
}
}
items . emplace_back ( i . type , i . obj_idx , i . sub_obj_idx );
}
wxGetApp (). obj_list () -> delete_from_model_and_list ( items );
}
else
{
std :: set < std :: pair < int , int >> volumes_idxs ;
for ( unsigned int i : m_list )
{
const GLVolume * v = ( * m_volumes )[ i ];
// Only remove volumes associated with ModelVolumes from the object list.
// Temporary meshes (SLA supports or pads) are not managed by the object list.
if ( v -> volume_idx () >= 0 )
volumes_idxs . insert ( std :: make_pair ( v -> object_idx (), v -> volume_idx ()));
}
std :: vector < ItemForDelete > items ;
items . reserve ( volumes_idxs . size ());
for ( const std :: pair < int , int >& v : volumes_idxs )
{
items . emplace_back ( ItemType :: itVolume , v . first , v . second );
}
wxGetApp (). obj_list () -> delete_from_model_and_list ( items );
}
}
void Selection :: render ( float scale_factor ) const
{
if ( ! m_valid || is_empty ())
return ;
m_scale_factor = scale_factor ;
// render cumulative bounding box of selected volumes
2019-03-21 11:02:10 +01:00
render_selected_volumes ();
render_synchronized_volumes ();
2019-03-19 13:30:21 +01:00
}
#if ENABLE_RENDER_SELECTION_CENTER
2019-05-20 15:59:54 +02:00
void Selection :: render_center ( bool gizmo_is_dragging ) const
2019-03-19 13:30:21 +01:00
{
if ( ! m_valid || is_empty () || ( m_quadric == nullptr ))
return ;
2019-05-20 15:59:54 +02:00
Vec3d center = gizmo_is_dragging ? m_cache . dragging_center : get_bounding_box (). center ();
2019-03-19 13:30:21 +01:00
2019-05-20 15:59:54 +02:00
glsafe ( :: glDisable ( GL_DEPTH_TEST ));
2019-03-19 13:30:21 +01:00
2019-03-21 11:02:10 +01:00
glsafe ( :: glEnable ( GL_LIGHTING ));
2019-03-19 13:30:21 +01:00
2019-03-21 11:02:10 +01:00
glsafe ( :: glColor3f ( 1.0f , 1.0f , 1.0f ));
glsafe ( :: glPushMatrix ());
glsafe ( :: glTranslated ( center ( 0 ), center ( 1 ), center ( 2 )));
glsafe ( :: gluSphere ( m_quadric , 0.75 , 32 , 32 ));
glsafe ( :: glPopMatrix ());
2019-03-19 13:30:21 +01:00
2019-03-21 11:02:10 +01:00
glsafe ( :: glDisable ( GL_LIGHTING ));
2019-03-19 13:30:21 +01:00
}
#endif // ENABLE_RENDER_SELECTION_CENTER
2020-05-13 09:07:06 +02:00
#if ENABLE_GCODE_VIEWER
void Selection :: render_sidebar_hints ( const std :: string & sidebar_field ) const
#else
2020-05-20 17:03:53 +02:00
#if ENABLE_SHADERS_MANAGER
void Selection :: render_sidebar_hints ( const std :: string & sidebar_field ) const
#else
2019-06-27 13:42:50 +02:00
void Selection :: render_sidebar_hints ( const std :: string & sidebar_field , const Shader & shader ) const
2020-05-20 17:03:53 +02:00
#endif // ENABLE_SHADERS_MANAGER
2020-05-13 09:07:06 +02:00
#endif // ENABLE_GCODE_VIEWER
2019-03-19 13:30:21 +01:00
{
if ( sidebar_field . empty ())
return ;
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
GLShaderProgram * shader = nullptr ;
#endif // ENABLE_SHADERS_MANAGER
2019-06-27 13:42:50 +02:00
if ( ! boost :: starts_with ( sidebar_field , "layer" ))
{
2020-05-13 09:07:06 +02:00
#if ENABLE_GCODE_VIEWER
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
shader = wxGetApp (). get_shader ( "gouraud_light" );
if ( shader == nullptr )
return ;
shader -> start_using ();
#else
2020-05-13 09:07:06 +02:00
if ( ! m_arrows_shader . is_initialized ())
return ;
m_arrows_shader . start_using ();
2020-05-20 14:11:22 +02:00
#endif // ENABLE_SHADERS_MANAGER
2020-05-13 09:07:06 +02:00
glsafe ( :: glClear ( GL_DEPTH_BUFFER_BIT ));
2020-05-20 17:03:53 +02:00
#else
#if ENABLE_SHADERS_MANAGER
shader = wxGetApp (). get_shader ( "gouraud_light" );
if ( shader == nullptr )
return ;
shader -> start_using ();
2020-05-13 09:07:06 +02:00
#else
2019-06-27 13:42:50 +02:00
shader . start_using ();
glsafe ( :: glEnable ( GL_LIGHTING ));
2020-05-20 17:03:53 +02:00
#endif // ENABLE_SHADERS_MANAGER
glsafe ( :: glClear ( GL_DEPTH_BUFFER_BIT ));
2020-05-13 09:07:06 +02:00
#endif // ENABLE_GCODE_VIEWER
2019-06-27 13:42:50 +02:00
}
2019-03-19 13:30:21 +01:00
2019-06-27 13:42:50 +02:00
glsafe ( :: glEnable ( GL_DEPTH_TEST ));
2019-03-19 13:30:21 +01:00
2019-03-21 11:02:10 +01:00
glsafe ( :: glPushMatrix ());
2019-03-19 13:30:21 +01:00
2019-06-27 13:42:50 +02:00
if ( ! boost :: starts_with ( sidebar_field , "layer" ))
2019-03-19 13:30:21 +01:00
{
2019-06-27 13:42:50 +02:00
const Vec3d & center = get_bounding_box (). center ();
if ( is_single_full_instance () && ! wxGetApp (). obj_manipul () -> get_world_coordinates ())
2019-03-19 13:30:21 +01:00
{
2019-06-27 13:42:50 +02:00
glsafe ( :: glTranslated ( center ( 0 ), center ( 1 ), center ( 2 )));
if ( ! boost :: starts_with ( sidebar_field , "position" ))
2019-04-01 09:11:23 +02:00
{
2019-06-27 13:42:50 +02:00
Transform3d orient_matrix = Transform3d :: Identity ();
if ( boost :: starts_with ( sidebar_field , "scale" ))
2019-04-01 09:11:23 +02:00
orient_matrix = ( * m_volumes )[ * m_list . begin ()] -> get_instance_transformation (). get_matrix ( true , false , true , true );
2019-06-27 13:42:50 +02:00
else if ( boost :: starts_with ( sidebar_field , "rotation" ))
2019-04-01 09:11:23 +02:00
{
2019-06-27 13:42:50 +02:00
if ( boost :: ends_with ( sidebar_field , "x" ))
2019-04-01 09:11:23 +02:00
orient_matrix = ( * m_volumes )[ * m_list . begin ()] -> get_instance_transformation (). get_matrix ( true , false , true , true );
2019-06-27 13:42:50 +02:00
else if ( boost :: ends_with ( sidebar_field , "y" ))
{
const Vec3d & rotation = ( * m_volumes )[ * m_list . begin ()] -> get_instance_transformation (). get_rotation ();
if ( rotation ( 0 ) == 0.0 )
orient_matrix = ( * m_volumes )[ * m_list . begin ()] -> get_instance_transformation (). get_matrix ( true , false , true , true );
else
orient_matrix . rotate ( Eigen :: AngleAxisd ( rotation ( 2 ), Vec3d :: UnitZ ()));
}
2019-04-01 09:11:23 +02:00
}
2019-06-27 13:42:50 +02:00
glsafe ( :: glMultMatrixd ( orient_matrix . data ()));
2019-04-01 09:11:23 +02:00
}
2019-06-27 13:42:50 +02:00
}
else if ( is_single_volume () || is_single_modifier ())
{
glsafe ( :: glTranslated ( center ( 0 ), center ( 1 ), center ( 2 )));
Transform3d orient_matrix = ( * m_volumes )[ * m_list . begin ()] -> get_instance_transformation (). get_matrix ( true , false , true , true );
if ( ! boost :: starts_with ( sidebar_field , "position" ))
orient_matrix = orient_matrix * ( * m_volumes )[ * m_list . begin ()] -> get_volume_transformation (). get_matrix ( true , false , true , true );
2019-04-01 09:11:23 +02:00
2019-03-21 11:02:10 +01:00
glsafe ( :: glMultMatrixd ( orient_matrix . data ()));
2019-03-19 13:30:21 +01:00
}
2019-06-27 13:42:50 +02:00
else
2019-03-19 13:30:21 +01:00
{
2019-06-27 13:42:50 +02:00
glsafe ( :: glTranslated ( center ( 0 ), center ( 1 ), center ( 2 )));
if ( requires_local_axes ())
{
Transform3d orient_matrix = ( * m_volumes )[ * m_list . begin ()] -> get_instance_transformation (). get_matrix ( true , false , true , true );
glsafe ( :: glMultMatrixd ( orient_matrix . data ()));
}
2019-03-19 13:30:21 +01:00
}
}
if ( boost :: starts_with ( sidebar_field , "position" ))
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
2020-05-20 17:03:53 +02:00
#if ENABLE_GCODE_VIEWER
2020-05-20 14:11:22 +02:00
render_sidebar_position_hints ( sidebar_field , * shader );
#else
2019-03-21 11:02:10 +01:00
render_sidebar_position_hints ( sidebar_field );
2020-05-20 17:03:53 +02:00
#endif // ENABLE_GCODE_VIEWER
#else
render_sidebar_position_hints ( sidebar_field );
2020-05-20 14:11:22 +02:00
#endif // ENABLE_SHADERS_MANAGER
2019-03-19 13:30:21 +01:00
else if ( boost :: starts_with ( sidebar_field , "rotation" ))
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
2020-05-20 17:03:53 +02:00
#if ENABLE_GCODE_VIEWER
2020-05-20 14:11:22 +02:00
render_sidebar_rotation_hints ( sidebar_field , * shader );
#else
2019-03-21 11:02:10 +01:00
render_sidebar_rotation_hints ( sidebar_field );
2020-05-20 14:11:22 +02:00
#endif // ENABLE_GCODE_VIEWER
2020-05-20 17:03:53 +02:00
#else
render_sidebar_rotation_hints ( sidebar_field );
#endif // ENABLE_SHADERS_MANAGER
else if ( boost :: starts_with ( sidebar_field , "scale" ) || boost :: starts_with ( sidebar_field , "size" ))
#if ENABLE_SHADERS_MANAGER
#if ENABLE_GCODE_VIEWER
render_sidebar_scale_hints ( sidebar_field , * shader );
#else
render_sidebar_scale_hints ( sidebar_field );
#endif // ENABLE_GCODE_VIEWER
#else
render_sidebar_scale_hints ( sidebar_field );
#endif // ENABLE_SHADERS_MANAGER
2019-06-27 13:42:50 +02:00
else if ( boost :: starts_with ( sidebar_field , "layer" ))
render_sidebar_layers_hints ( sidebar_field );
2019-03-19 13:30:21 +01:00
2019-03-21 11:02:10 +01:00
glsafe ( :: glPopMatrix ());
2019-03-19 13:30:21 +01:00
2019-06-27 13:42:50 +02:00
if ( ! boost :: starts_with ( sidebar_field , "layer" ))
{
2020-05-13 09:07:06 +02:00
#if ENABLE_GCODE_VIEWER
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
shader -> stop_using ();
#else
2020-05-13 09:07:06 +02:00
m_arrows_shader . stop_using ();
2020-05-20 14:11:22 +02:00
#endif // ENABLE_SHADERS_MANAGER
2020-05-20 17:03:53 +02:00
#else
#if ENABLE_SHADERS_MANAGER
shader -> stop_using ();
2020-05-13 09:07:06 +02:00
#else
2019-06-27 13:42:50 +02:00
glsafe ( :: glDisable ( GL_LIGHTING ));
shader . stop_using ();
2020-05-20 17:03:53 +02:00
#endif // ENABLE_SHADERS_MANAGER
2020-05-13 09:07:06 +02:00
#endif // ENABLE_GCODE_VIEWER
2019-06-27 13:42:50 +02:00
}
2019-03-19 13:30:21 +01:00
}
bool Selection :: requires_local_axes () const
{
return ( m_mode == Volume ) && is_from_single_instance ();
}
2019-04-10 08:40:58 +02:00
void Selection :: copy_to_clipboard ()
{
if ( ! m_valid )
return ;
m_clipboard . reset ();
2019-04-10 14:03:40 +02:00
for ( const ObjectIdxsToInstanceIdxsMap :: value_type & object : m_cache . content )
2019-04-10 08:40:58 +02:00
{
2019-04-10 14:03:40 +02:00
ModelObject * src_object = m_model -> objects [ object . first ];
ModelObject * dst_object = m_clipboard . add_object ();
2019-04-12 18:29:47 +02:00
dst_object -> name = src_object -> name ;
dst_object -> input_file = src_object -> input_file ;
2019-07-03 13:43:54 +02:00
static_cast < DynamicPrintConfig &> ( dst_object -> config ) = static_cast < const DynamicPrintConfig &> ( src_object -> config );
2019-04-12 18:29:47 +02:00
dst_object -> sla_support_points = src_object -> sla_support_points ;
dst_object -> sla_points_status = src_object -> sla_points_status ;
2020-02-06 14:23:03 +01:00
dst_object -> sla_drain_holes = src_object -> sla_drain_holes ;
2019-06-05 11:50:59 +02:00
dst_object -> layer_config_ranges = src_object -> layer_config_ranges ; // #ys_FIXME_experiment
2019-04-12 18:29:47 +02:00
dst_object -> layer_height_profile = src_object -> layer_height_profile ;
dst_object -> origin_translation = src_object -> origin_translation ;
2019-04-10 14:03:40 +02:00
for ( int i : object . second )
2019-04-10 08:40:58 +02:00
{
2019-04-10 14:03:40 +02:00
dst_object -> add_instance ( * src_object -> instances [ i ]);
2019-04-10 08:40:58 +02:00
}
2019-04-10 14:03:40 +02:00
for ( unsigned int i : m_list )
{
2019-04-12 18:29:47 +02:00
// Copy the ModelVolumes only for the selected GLVolumes of the 1st selected instance.
2019-04-10 14:03:40 +02:00
const GLVolume * volume = ( * m_volumes )[ i ];
2019-04-11 11:09:32 +02:00
if (( volume -> object_idx () == object . first ) && ( volume -> instance_idx () == * object . second . begin ()))
2019-04-10 14:03:40 +02:00
{
2019-04-10 15:55:32 +02:00
int volume_idx = volume -> volume_idx ();
if (( 0 <= volume_idx ) && ( volume_idx < ( int ) src_object -> volumes . size ()))
{
2019-04-11 11:09:32 +02:00
ModelVolume * src_volume = src_object -> volumes [ volume_idx ];
2019-04-10 15:55:32 +02:00
ModelVolume * dst_volume = dst_object -> add_volume ( * src_volume );
dst_volume -> set_new_unique_id ();
2019-04-12 18:29:47 +02:00
} else {
assert ( false );
2019-04-10 15:55:32 +02:00
}
2019-04-10 14:03:40 +02:00
}
}
2019-04-10 08:40:58 +02:00
}
m_clipboard . set_mode ( m_mode );
}
void Selection :: paste_from_clipboard ()
{
2019-04-10 14:03:40 +02:00
if ( ! m_valid || m_clipboard . is_empty ())
2019-04-10 08:40:58 +02:00
return ;
2019-04-11 14:21:08 +02:00
switch ( m_clipboard . get_mode ())
{
case Volume :
{
if ( is_from_single_instance ())
paste_volumes_from_clipboard ();
2019-04-10 08:40:58 +02:00
2019-04-11 14:21:08 +02:00
break ;
}
case Instance :
{
2019-04-12 08:49:24 +02:00
if ( m_mode == Instance )
paste_objects_from_clipboard ();
2019-04-12 08:41:59 +02:00
2019-04-11 14:21:08 +02:00
break ;
}
}
2019-04-10 08:40:58 +02:00
}
2019-07-23 15:14:08 +02:00
std :: vector < unsigned int > Selection :: get_volume_idxs_from_object ( unsigned int object_idx ) const
{
std :: vector < unsigned int > idxs ;
for ( unsigned int i = 0 ; i < ( unsigned int ) m_volumes -> size (); ++ i )
{
2019-09-04 10:46:51 +02:00
if (( * m_volumes )[ i ] -> object_idx () == ( int ) object_idx )
2019-07-23 15:14:08 +02:00
idxs . push_back ( i );
}
return idxs ;
}
std :: vector < unsigned int > Selection :: get_volume_idxs_from_instance ( unsigned int object_idx , unsigned int instance_idx ) const
{
std :: vector < unsigned int > idxs ;
for ( unsigned int i = 0 ; i < ( unsigned int ) m_volumes -> size (); ++ i )
{
const GLVolume * v = ( * m_volumes )[ i ];
2019-09-04 10:46:51 +02:00
if (( v -> object_idx () == ( int ) object_idx ) && ( v -> instance_idx () == ( int ) instance_idx ))
2019-07-23 15:14:08 +02:00
idxs . push_back ( i );
}
return idxs ;
}
std :: vector < unsigned int > Selection :: get_volume_idxs_from_volume ( unsigned int object_idx , unsigned int instance_idx , unsigned int volume_idx ) const
{
std :: vector < unsigned int > idxs ;
for ( unsigned int i = 0 ; i < ( unsigned int ) m_volumes -> size (); ++ i )
{
const GLVolume * v = ( * m_volumes )[ i ];
2019-09-04 10:46:51 +02:00
if (( v -> object_idx () == ( int ) object_idx ) && ( v -> volume_idx () == ( int ) volume_idx ))
2019-07-23 15:14:08 +02:00
{
2019-09-04 10:46:51 +02:00
if ((( int ) instance_idx != - 1 ) && ( v -> instance_idx () == ( int ) instance_idx ))
2019-07-23 15:14:08 +02:00
idxs . push_back ( i );
}
}
return idxs ;
}
std :: vector < unsigned int > Selection :: get_missing_volume_idxs_from ( const std :: vector < unsigned int >& volume_idxs ) const
{
std :: vector < unsigned int > idxs ;
for ( unsigned int i : m_list )
{
std :: vector < unsigned int >:: const_iterator it = std :: find ( volume_idxs . begin (), volume_idxs . end (), i );
if ( it == volume_idxs . end ())
idxs . push_back ( i );
}
return idxs ;
}
std :: vector < unsigned int > Selection :: get_unselected_volume_idxs_from ( const std :: vector < unsigned int >& volume_idxs ) const
{
std :: vector < unsigned int > idxs ;
for ( unsigned int i : volume_idxs )
{
if ( m_list . find ( i ) == m_list . end ())
idxs . push_back ( i );
}
return idxs ;
}
2019-07-31 11:01:50 +02:00
void Selection :: toggle_instance_printable_state ()
{
int instance_idx = get_instance_idx ();
if ( instance_idx == - 1 )
return ;
int obj_idx = get_object_idx ();
if (( 0 <= obj_idx ) && ( obj_idx < ( int ) m_model -> objects . size ()))
{
ModelObject * model_object = m_model -> objects [ obj_idx ];
if (( 0 <= instance_idx ) && ( instance_idx < ( int ) model_object -> instances . size ()))
{
ModelInstance * instance = model_object -> instances [ instance_idx ];
2019-08-09 09:49:10 +02:00
const bool printable = ! instance -> printable ;
2020-03-04 09:24:13 +01:00
wxString snapshot_text = model_object -> instances . size () == 1 ? from_u8 (( boost :: format ( "%1% %2%" )
2020-03-02 12:46:40 +01:00
% ( printable ? _utf8 ( L ( "Set Printable" )) : _utf8 ( L ( "Set Unprintable" )))
% model_object -> name ). str ()) :
( printable ? _ ( L ( "Set Printable Instance" )) : _ ( L ( "Set Unprintable Instance" )));
2019-08-09 09:49:10 +02:00
wxGetApp (). plater () -> take_snapshot ( snapshot_text );
instance -> printable = printable ;
2019-07-31 11:01:50 +02:00
for ( GLVolume * volume : * m_volumes )
{
if (( volume -> object_idx () == obj_idx ) && ( volume -> instance_idx () == instance_idx ))
volume -> printable = instance -> printable ;
}
2019-08-05 08:44:55 +02:00
wxGetApp (). obj_list () -> update_printable_state ( obj_idx , instance_idx );
2019-08-30 10:33:21 +02:00
wxGetApp (). plater () -> update ();
2019-07-31 11:01:50 +02:00
}
}
}
2019-03-21 11:02:10 +01:00
void Selection :: update_valid ()
2019-03-19 13:30:21 +01:00
{
m_valid = ( m_volumes != nullptr ) && ( m_model != nullptr );
}
2019-03-21 11:02:10 +01:00
void Selection :: update_type ()
2019-03-19 13:30:21 +01:00
{
m_cache . content . clear ();
m_type = Mixed ;
for ( unsigned int i : m_list )
{
const GLVolume * volume = ( * m_volumes )[ i ];
int obj_idx = volume -> object_idx ();
int inst_idx = volume -> instance_idx ();
ObjectIdxsToInstanceIdxsMap :: iterator obj_it = m_cache . content . find ( obj_idx );
if ( obj_it == m_cache . content . end ())
obj_it = m_cache . content . insert ( ObjectIdxsToInstanceIdxsMap :: value_type ( obj_idx , InstanceIdxsList ())). first ;
obj_it -> second . insert ( inst_idx );
}
bool requires_disable = false ;
if ( ! m_valid )
m_type = Invalid ;
else
{
if ( m_list . empty ())
m_type = Empty ;
else if ( m_list . size () == 1 )
{
const GLVolume * first = ( * m_volumes )[ * m_list . begin ()];
if ( first -> is_wipe_tower )
m_type = WipeTower ;
else if ( first -> is_modifier )
{
m_type = SingleModifier ;
requires_disable = true ;
}
else
{
const ModelObject * model_object = m_model -> objects [ first -> object_idx ()];
unsigned int volumes_count = ( unsigned int ) model_object -> volumes . size ();
unsigned int instances_count = ( unsigned int ) model_object -> instances . size ();
if ( volumes_count * instances_count == 1 )
{
m_type = SingleFullObject ;
// ensures the correct mode is selected
m_mode = Instance ;
}
else if ( volumes_count == 1 ) // instances_count > 1
{
m_type = SingleFullInstance ;
// ensures the correct mode is selected
m_mode = Instance ;
}
else
{
m_type = SingleVolume ;
requires_disable = true ;
}
}
}
else
{
if ( m_cache . content . size () == 1 ) // single object
{
const ModelObject * model_object = m_model -> objects [ m_cache . content . begin () -> first ];
unsigned int model_volumes_count = ( unsigned int ) model_object -> volumes . size ();
unsigned int sla_volumes_count = 0 ;
for ( unsigned int i : m_list )
{
if (( * m_volumes )[ i ] -> volume_idx () < 0 )
++ sla_volumes_count ;
}
unsigned int volumes_count = model_volumes_count + sla_volumes_count ;
unsigned int instances_count = ( unsigned int ) model_object -> instances . size ();
unsigned int selected_instances_count = ( unsigned int ) m_cache . content . begin () -> second . size ();
if ( volumes_count * instances_count == ( unsigned int ) m_list . size ())
{
m_type = SingleFullObject ;
// ensures the correct mode is selected
m_mode = Instance ;
}
else if ( selected_instances_count == 1 )
{
if ( volumes_count == ( unsigned int ) m_list . size ())
{
m_type = SingleFullInstance ;
// ensures the correct mode is selected
m_mode = Instance ;
}
else
{
unsigned int modifiers_count = 0 ;
for ( unsigned int i : m_list )
{
if (( * m_volumes )[ i ] -> is_modifier )
++ modifiers_count ;
}
if ( modifiers_count == 0 )
m_type = MultipleVolume ;
else if ( modifiers_count == ( unsigned int ) m_list . size ())
m_type = MultipleModifier ;
2019-04-04 09:01:47 +02:00
2019-04-08 09:49:21 +02:00
requires_disable = true ;
}
2019-03-19 13:30:21 +01:00
}
else if (( selected_instances_count > 1 ) && ( selected_instances_count * volumes_count == ( unsigned int ) m_list . size ()))
{
m_type = MultipleFullInstance ;
// ensures the correct mode is selected
m_mode = Instance ;
}
}
else
{
2019-09-04 10:46:51 +02:00
unsigned int sels_cntr = 0 ;
2019-03-19 13:30:21 +01:00
for ( ObjectIdxsToInstanceIdxsMap :: iterator it = m_cache . content . begin (); it != m_cache . content . end (); ++ it )
{
const ModelObject * model_object = m_model -> objects [ it -> first ];
unsigned int volumes_count = ( unsigned int ) model_object -> volumes . size ();
unsigned int instances_count = ( unsigned int ) model_object -> instances . size ();
sels_cntr += volumes_count * instances_count ;
}
if ( sels_cntr == ( unsigned int ) m_list . size ())
{
m_type = MultipleFullObject ;
// ensures the correct mode is selected
m_mode = Instance ;
}
}
}
}
int object_idx = get_object_idx ();
int instance_idx = get_instance_idx ();
for ( GLVolume * v : * m_volumes )
{
v -> disabled = requires_disable ? ( v -> object_idx () != object_idx ) || ( v -> instance_idx () != instance_idx ) : false ;
}
#if ENABLE_SELECTION_DEBUG_OUTPUT
std :: cout << "Selection: " ;
std :: cout << "mode: " ;
switch ( m_mode )
{
case Volume :
{
std :: cout << "Volume" ;
break ;
}
case Instance :
{
std :: cout << "Instance" ;
break ;
}
}
std :: cout << " - type: " ;
switch ( m_type )
{
case Invalid :
{
std :: cout << "Invalid" << std :: endl ;
break ;
}
case Empty :
{
std :: cout << "Empty" << std :: endl ;
break ;
}
case WipeTower :
{
std :: cout << "WipeTower" << std :: endl ;
break ;
}
case SingleModifier :
{
std :: cout << "SingleModifier" << std :: endl ;
break ;
}
case MultipleModifier :
{
std :: cout << "MultipleModifier" << std :: endl ;
break ;
}
case SingleVolume :
{
std :: cout << "SingleVolume" << std :: endl ;
break ;
}
case MultipleVolume :
{
std :: cout << "MultipleVolume" << std :: endl ;
break ;
}
case SingleFullObject :
{
std :: cout << "SingleFullObject" << std :: endl ;
break ;
}
case MultipleFullObject :
{
std :: cout << "MultipleFullObject" << std :: endl ;
break ;
}
case SingleFullInstance :
{
std :: cout << "SingleFullInstance" << std :: endl ;
break ;
}
case MultipleFullInstance :
{
std :: cout << "MultipleFullInstance" << std :: endl ;
break ;
}
case Mixed :
{
std :: cout << "Mixed" << std :: endl ;
break ;
}
}
#endif // ENABLE_SELECTION_DEBUG_OUTPUT
}
2019-03-21 11:02:10 +01:00
void Selection :: set_caches ()
2019-03-19 13:30:21 +01:00
{
m_cache . volumes_data . clear ();
for ( unsigned int i = 0 ; i < ( unsigned int ) m_volumes -> size (); ++ i )
{
const GLVolume * v = ( * m_volumes )[ i ];
m_cache . volumes_data . emplace ( i , VolumeCache ( v -> get_volume_transformation (), v -> get_instance_transformation ()));
}
m_cache . dragging_center = get_bounding_box (). center ();
}
2019-03-21 11:02:10 +01:00
void Selection :: do_add_volume ( unsigned int volume_idx )
2019-03-19 13:30:21 +01:00
{
m_list . insert ( volume_idx );
( * m_volumes )[ volume_idx ] -> selected = true ;
}
2019-07-23 15:14:08 +02:00
void Selection :: do_add_volumes ( const std :: vector < unsigned int >& volume_idxs )
{
for ( unsigned int i : volume_idxs )
{
if ( i < ( unsigned int ) m_volumes -> size ())
do_add_volume ( i );
}
}
2019-03-19 13:30:21 +01:00
2019-03-21 11:02:10 +01:00
void Selection :: do_remove_volume ( unsigned int volume_idx )
2019-03-19 13:30:21 +01:00
{
IndicesList :: iterator v_it = m_list . find ( volume_idx );
if ( v_it == m_list . end ())
return ;
m_list . erase ( v_it );
( * m_volumes )[ volume_idx ] -> selected = false ;
}
2019-03-21 11:02:10 +01:00
void Selection :: do_remove_instance ( unsigned int object_idx , unsigned int instance_idx )
2019-03-19 13:30:21 +01:00
{
for ( unsigned int i = 0 ; i < ( unsigned int ) m_volumes -> size (); ++ i )
{
GLVolume * v = ( * m_volumes )[ i ];
2019-09-04 10:46:51 +02:00
if (( v -> object_idx () == ( int ) object_idx ) && ( v -> instance_idx () == ( int ) instance_idx ))
2019-03-21 11:02:10 +01:00
do_remove_volume ( i );
2019-03-19 13:30:21 +01:00
}
}
2019-03-21 11:02:10 +01:00
void Selection :: do_remove_object ( unsigned int object_idx )
2019-03-19 13:30:21 +01:00
{
for ( unsigned int i = 0 ; i < ( unsigned int ) m_volumes -> size (); ++ i )
{
GLVolume * v = ( * m_volumes )[ i ];
2019-09-04 10:46:51 +02:00
if ( v -> object_idx () == ( int ) object_idx )
2019-03-21 11:02:10 +01:00
do_remove_volume ( i );
2019-03-19 13:30:21 +01:00
}
}
2019-03-21 11:02:10 +01:00
void Selection :: calc_bounding_box () const
2019-03-19 13:30:21 +01:00
{
m_bounding_box = BoundingBoxf3 ();
if ( m_valid )
{
for ( unsigned int i : m_list )
{
m_bounding_box . merge (( * m_volumes )[ i ] -> transformed_convex_hull_bounding_box ());
}
}
2019-05-03 12:36:26 +02:00
m_bounding_box_dirty = false ;
}
void Selection :: calc_unscaled_instance_bounding_box () const
{
m_unscaled_instance_bounding_box = BoundingBoxf3 ();
2019-05-07 15:43:53 +02:00
if ( m_valid ) {
for ( unsigned int i : m_list ) {
2019-05-03 12:36:26 +02:00
const GLVolume & volume = * ( * m_volumes )[ i ];
2019-05-07 15:43:53 +02:00
if ( volume . is_modifier )
continue ;
2019-05-03 12:36:26 +02:00
Transform3d trafo = volume . get_instance_transformation (). get_matrix ( false , false , true , false ) * volume . get_volume_transformation (). get_matrix ();
trafo . translation ()( 2 ) += volume . get_sla_shift_z ();
m_unscaled_instance_bounding_box . merge ( volume . transformed_convex_hull_bounding_box ( trafo ));
}
}
m_unscaled_instance_bounding_box_dirty = false ;
2019-03-19 13:30:21 +01:00
}
2019-05-07 15:43:53 +02:00
void Selection :: calc_scaled_instance_bounding_box () const
{
m_scaled_instance_bounding_box = BoundingBoxf3 ();
if ( m_valid ) {
for ( unsigned int i : m_list ) {
const GLVolume & volume = * ( * m_volumes )[ i ];
if ( volume . is_modifier )
continue ;
Transform3d trafo = volume . get_instance_transformation (). get_matrix ( false , false , false , false ) * volume . get_volume_transformation (). get_matrix ();
trafo . translation ()( 2 ) += volume . get_sla_shift_z ();
m_scaled_instance_bounding_box . merge ( volume . transformed_convex_hull_bounding_box ( trafo ));
}
}
m_scaled_instance_bounding_box_dirty = false ;
}
2019-03-21 11:02:10 +01:00
void Selection :: render_selected_volumes () const
2019-03-19 13:30:21 +01:00
{
float color [ 3 ] = { 1.0f , 1.0f , 1.0f };
2019-03-21 11:02:10 +01:00
render_bounding_box ( get_bounding_box (), color );
2019-03-19 13:30:21 +01:00
}
2019-03-21 11:02:10 +01:00
void Selection :: render_synchronized_volumes () const
2019-03-19 13:30:21 +01:00
{
if ( m_mode == Instance )
return ;
float color [ 3 ] = { 1.0f , 1.0f , 0.0f };
for ( unsigned int i : m_list )
{
const GLVolume * volume = ( * m_volumes )[ i ];
int object_idx = volume -> object_idx ();
int volume_idx = volume -> volume_idx ();
for ( unsigned int j = 0 ; j < ( unsigned int ) m_volumes -> size (); ++ j )
{
if ( i == j )
continue ;
const GLVolume * v = ( * m_volumes )[ j ];
if (( v -> object_idx () != object_idx ) || ( v -> volume_idx () != volume_idx ))
continue ;
2019-03-21 11:02:10 +01:00
render_bounding_box ( v -> transformed_convex_hull_bounding_box (), color );
2019-03-19 13:30:21 +01:00
}
}
}
2019-03-21 11:02:10 +01:00
void Selection :: render_bounding_box ( const BoundingBoxf3 & box , float * color ) const
2019-03-19 13:30:21 +01:00
{
if ( color == nullptr )
return ;
Vec3f b_min = box . min . cast < float > ();
Vec3f b_max = box . max . cast < float > ();
Vec3f size = 0.2f * box . size (). cast < float > ();
2019-03-21 11:02:10 +01:00
glsafe ( :: glEnable ( GL_DEPTH_TEST ));
2019-03-19 13:30:21 +01:00
2019-03-21 11:02:10 +01:00
glsafe ( :: glColor3fv ( color ));
glsafe ( :: glLineWidth ( 2.0f * m_scale_factor ));
2019-03-19 13:30:21 +01:00
:: glBegin ( GL_LINES );
:: glVertex3f ( b_min ( 0 ), b_min ( 1 ), b_min ( 2 )); :: glVertex3f ( b_min ( 0 ) + size ( 0 ), b_min ( 1 ), b_min ( 2 ));
:: glVertex3f ( b_min ( 0 ), b_min ( 1 ), b_min ( 2 )); :: glVertex3f ( b_min ( 0 ), b_min ( 1 ) + size ( 1 ), b_min ( 2 ));
:: glVertex3f ( b_min ( 0 ), b_min ( 1 ), b_min ( 2 )); :: glVertex3f ( b_min ( 0 ), b_min ( 1 ), b_min ( 2 ) + size ( 2 ));
:: glVertex3f ( b_max ( 0 ), b_min ( 1 ), b_min ( 2 )); :: glVertex3f ( b_max ( 0 ) - size ( 0 ), b_min ( 1 ), b_min ( 2 ));
:: glVertex3f ( b_max ( 0 ), b_min ( 1 ), b_min ( 2 )); :: glVertex3f ( b_max ( 0 ), b_min ( 1 ) + size ( 1 ), b_min ( 2 ));
:: glVertex3f ( b_max ( 0 ), b_min ( 1 ), b_min ( 2 )); :: glVertex3f ( b_max ( 0 ), b_min ( 1 ), b_min ( 2 ) + size ( 2 ));
:: glVertex3f ( b_max ( 0 ), b_max ( 1 ), b_min ( 2 )); :: glVertex3f ( b_max ( 0 ) - size ( 0 ), b_max ( 1 ), b_min ( 2 ));
:: glVertex3f ( b_max ( 0 ), b_max ( 1 ), b_min ( 2 )); :: glVertex3f ( b_max ( 0 ), b_max ( 1 ) - size ( 1 ), b_min ( 2 ));
:: glVertex3f ( b_max ( 0 ), b_max ( 1 ), b_min ( 2 )); :: glVertex3f ( b_max ( 0 ), b_max ( 1 ), b_min ( 2 ) + size ( 2 ));
:: glVertex3f ( b_min ( 0 ), b_max ( 1 ), b_min ( 2 )); :: glVertex3f ( b_min ( 0 ) + size ( 0 ), b_max ( 1 ), b_min ( 2 ));
:: glVertex3f ( b_min ( 0 ), b_max ( 1 ), b_min ( 2 )); :: glVertex3f ( b_min ( 0 ), b_max ( 1 ) - size ( 1 ), b_min ( 2 ));
:: glVertex3f ( b_min ( 0 ), b_max ( 1 ), b_min ( 2 )); :: glVertex3f ( b_min ( 0 ), b_max ( 1 ), b_min ( 2 ) + size ( 2 ));
:: glVertex3f ( b_min ( 0 ), b_min ( 1 ), b_max ( 2 )); :: glVertex3f ( b_min ( 0 ) + size ( 0 ), b_min ( 1 ), b_max ( 2 ));
:: glVertex3f ( b_min ( 0 ), b_min ( 1 ), b_max ( 2 )); :: glVertex3f ( b_min ( 0 ), b_min ( 1 ) + size ( 1 ), b_max ( 2 ));
:: glVertex3f ( b_min ( 0 ), b_min ( 1 ), b_max ( 2 )); :: glVertex3f ( b_min ( 0 ), b_min ( 1 ), b_max ( 2 ) - size ( 2 ));
:: glVertex3f ( b_max ( 0 ), b_min ( 1 ), b_max ( 2 )); :: glVertex3f ( b_max ( 0 ) - size ( 0 ), b_min ( 1 ), b_max ( 2 ));
:: glVertex3f ( b_max ( 0 ), b_min ( 1 ), b_max ( 2 )); :: glVertex3f ( b_max ( 0 ), b_min ( 1 ) + size ( 1 ), b_max ( 2 ));
:: glVertex3f ( b_max ( 0 ), b_min ( 1 ), b_max ( 2 )); :: glVertex3f ( b_max ( 0 ), b_min ( 1 ), b_max ( 2 ) - size ( 2 ));
:: glVertex3f ( b_max ( 0 ), b_max ( 1 ), b_max ( 2 )); :: glVertex3f ( b_max ( 0 ) - size ( 0 ), b_max ( 1 ), b_max ( 2 ));
:: glVertex3f ( b_max ( 0 ), b_max ( 1 ), b_max ( 2 )); :: glVertex3f ( b_max ( 0 ), b_max ( 1 ) - size ( 1 ), b_max ( 2 ));
:: glVertex3f ( b_max ( 0 ), b_max ( 1 ), b_max ( 2 )); :: glVertex3f ( b_max ( 0 ), b_max ( 1 ), b_max ( 2 ) - size ( 2 ));
:: glVertex3f ( b_min ( 0 ), b_max ( 1 ), b_max ( 2 )); :: glVertex3f ( b_min ( 0 ) + size ( 0 ), b_max ( 1 ), b_max ( 2 ));
:: glVertex3f ( b_min ( 0 ), b_max ( 1 ), b_max ( 2 )); :: glVertex3f ( b_min ( 0 ), b_max ( 1 ) - size ( 1 ), b_max ( 2 ));
:: glVertex3f ( b_min ( 0 ), b_max ( 1 ), b_max ( 2 )); :: glVertex3f ( b_min ( 0 ), b_max ( 1 ), b_max ( 2 ) - size ( 2 ));
2019-03-21 11:02:10 +01:00
glsafe ( :: glEnd ());
2019-03-19 13:30:21 +01:00
}
2020-05-13 09:07:06 +02:00
#if ENABLE_GCODE_VIEWER
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
void Selection :: render_sidebar_position_hints ( const std :: string & sidebar_field , GLShaderProgram & shader ) const
#else
void Selection :: render_sidebar_position_hints ( const std :: string & sidebar_field ) const
#endif // ENABLE_SHADERS_MANAGER
{
#if !ENABLE_SHADERS_MANAGER
2020-05-13 09:07:06 +02:00
GLint color_id = :: glGetUniformLocation ( m_arrows_shader . get_shader_program_id (), "uniform_color" );
2020-05-20 14:11:22 +02:00
#endif // !ENABLE_SHADERS_MANAGER
2020-05-13 09:07:06 +02:00
if ( boost :: ends_with ( sidebar_field , "x" ))
{
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
shader . set_uniform ( "uniform_color" , AXES_COLOR [ 0 ], 4 );
#else
2020-05-13 09:07:06 +02:00
if ( color_id >= 0 )
glsafe ( :: glUniform4fv ( color_id , 1 , ( const GLfloat * ) AXES_COLOR [ 0 ]));
2020-05-20 14:11:22 +02:00
#endif // ENABLE_SHADERS_MANAGER
2020-05-13 09:07:06 +02:00
glsafe ( :: glRotated ( - 90.0 , 0.0 , 0.0 , 1.0 ));
m_arrow . render ();
}
else if ( boost :: ends_with ( sidebar_field , "y" ))
{
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
shader . set_uniform ( "uniform_color" , AXES_COLOR [ 1 ], 4 );
#else
2020-05-13 09:07:06 +02:00
if ( color_id >= 0 )
glsafe ( :: glUniform4fv ( color_id , 1 , ( const GLfloat * ) AXES_COLOR [ 1 ]));
2020-05-20 14:11:22 +02:00
#endif // ENABLE_SHADERS_MANAGER
2020-05-13 09:07:06 +02:00
m_arrow . render ();
}
else if ( boost :: ends_with ( sidebar_field , "z" ))
{
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
shader . set_uniform ( "uniform_color" , AXES_COLOR [ 2 ], 4 );
#else
2020-05-13 09:07:06 +02:00
if ( color_id >= 0 )
glsafe ( :: glUniform4fv ( color_id , 1 , ( const GLfloat * ) AXES_COLOR [ 2 ]));
2020-05-20 14:11:22 +02:00
#endif // ENABLE_SHADERS_MANAGER
2020-05-13 09:07:06 +02:00
glsafe ( :: glRotated ( 90.0 , 1.0 , 0.0 , 0.0 ));
m_arrow . render ();
}
2020-05-20 14:11:22 +02:00
}
2020-05-13 09:07:06 +02:00
#else
2020-05-20 17:03:53 +02:00
void Selection :: render_sidebar_position_hints ( const std :: string & sidebar_field ) const
2020-05-20 14:11:22 +02:00
{
2019-03-19 13:30:21 +01:00
if ( boost :: ends_with ( sidebar_field , "x" ))
{
2019-03-21 11:02:10 +01:00
glsafe ( :: glRotated ( - 90.0 , 0.0 , 0.0 , 1.0 ));
render_sidebar_position_hint ( X );
2019-03-19 13:30:21 +01:00
}
else if ( boost :: ends_with ( sidebar_field , "y" ))
2019-03-21 11:02:10 +01:00
render_sidebar_position_hint ( Y );
2019-03-19 13:30:21 +01:00
else if ( boost :: ends_with ( sidebar_field , "z" ))
{
2019-03-21 11:02:10 +01:00
glsafe ( :: glRotated ( 90.0 , 1.0 , 0.0 , 0.0 ));
render_sidebar_position_hint ( Z );
2019-03-19 13:30:21 +01:00
}
}
2020-05-20 14:11:22 +02:00
#endif // ENABLE_GCODE_VIEWER
2019-03-19 13:30:21 +01:00
2020-05-12 16:15:43 +02:00
#if ENABLE_GCODE_VIEWER
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
void Selection :: render_sidebar_rotation_hints ( const std :: string & sidebar_field , GLShaderProgram & shader ) const
#else
void Selection :: render_sidebar_rotation_hints ( const std :: string & sidebar_field ) const
#endif // ENABLE_SHADERS_MANAGER
{
#if !ENABLE_SHADERS_MANAGER
2020-05-12 16:15:43 +02:00
GLint color_id = :: glGetUniformLocation ( m_arrows_shader . get_shader_program_id (), "uniform_color" );
2020-05-20 14:11:22 +02:00
#endif // !ENABLE_SHADERS_MANAGER
2020-05-12 16:15:43 +02:00
if ( boost :: ends_with ( sidebar_field , "x" ))
{
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
shader . set_uniform ( "uniform_color" , AXES_COLOR [ 0 ], 4 );
#else
2020-05-12 16:15:43 +02:00
if ( color_id >= 0 )
glsafe ( :: glUniform4fv ( color_id , 1 , ( const GLfloat * ) AXES_COLOR [ 0 ]));
2020-05-20 14:11:22 +02:00
#endif // ENABLE_SHADERS_MANAGER
2020-05-12 16:15:43 +02:00
glsafe ( :: glRotated ( 90.0 , 0.0 , 1.0 , 0.0 ));
render_sidebar_rotation_hint ( X );
}
else if ( boost :: ends_with ( sidebar_field , "y" ))
{
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
shader . set_uniform ( "uniform_color" , AXES_COLOR [ 1 ], 4 );
#else
2020-05-12 16:15:43 +02:00
if ( color_id >= 0 )
glsafe ( :: glUniform4fv ( color_id , 1 , ( const GLfloat * ) AXES_COLOR [ 1 ]));
2020-05-20 14:11:22 +02:00
#endif // ENABLE_SHADERS_MANAGER
2020-05-12 16:15:43 +02:00
glsafe ( :: glRotated ( - 90.0 , 1.0 , 0.0 , 0.0 ));
render_sidebar_rotation_hint ( Y );
}
else if ( boost :: ends_with ( sidebar_field , "z" ))
{
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
shader . set_uniform ( "uniform_color" , AXES_COLOR [ 2 ], 4 );
#else
2020-05-12 16:15:43 +02:00
if ( color_id >= 0 )
glsafe ( :: glUniform4fv ( color_id , 1 , ( const GLfloat * ) AXES_COLOR [ 2 ]));
2020-05-20 14:11:22 +02:00
#endif // ENABLE_SHADERS_MANAGER
2020-05-12 16:15:43 +02:00
render_sidebar_rotation_hint ( Z );
}
2020-05-20 14:11:22 +02:00
}
2020-05-12 16:15:43 +02:00
#else
2020-05-20 14:11:22 +02:00
void Selection :: render_sidebar_rotation_hints ( const std :: string & sidebar_field ) const
{
2019-03-19 13:30:21 +01:00
if ( boost :: ends_with ( sidebar_field , "x" ))
{
2019-03-21 11:02:10 +01:00
glsafe ( :: glRotated ( 90.0 , 0.0 , 1.0 , 0.0 ));
render_sidebar_rotation_hint ( X );
2019-03-19 13:30:21 +01:00
}
else if ( boost :: ends_with ( sidebar_field , "y" ))
{
2019-03-21 11:02:10 +01:00
glsafe ( :: glRotated ( - 90.0 , 1.0 , 0.0 , 0.0 ));
render_sidebar_rotation_hint ( Y );
2019-03-19 13:30:21 +01:00
}
else if ( boost :: ends_with ( sidebar_field , "z" ))
2019-03-21 11:02:10 +01:00
render_sidebar_rotation_hint ( Z );
2019-03-19 13:30:21 +01:00
}
2020-05-20 14:11:22 +02:00
#endif // ENABLE_GCODE_VIEWER
2019-03-19 13:30:21 +01:00
2020-05-20 14:11:22 +02:00
#if ENABLE_GCODE_VIEWER
#if ENABLE_SHADERS_MANAGER
void Selection :: render_sidebar_scale_hints ( const std :: string & sidebar_field , GLShaderProgram & shader ) const
#else
2019-03-21 11:02:10 +01:00
void Selection :: render_sidebar_scale_hints ( const std :: string & sidebar_field ) const
2020-05-20 14:11:22 +02:00
#endif // ENABLE_SHADERS_MANAGER
2019-03-19 13:30:21 +01:00
{
bool uniform_scale = requires_uniform_scale () || wxGetApp (). obj_manipul () -> get_uniform_scaling ();
if ( boost :: ends_with ( sidebar_field , "x" ) || uniform_scale )
{
2019-03-21 11:02:10 +01:00
glsafe ( :: glPushMatrix ());
glsafe ( :: glRotated ( - 90.0 , 0.0 , 0.0 , 1.0 ));
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
render_sidebar_scale_hint ( X , shader );
2020-05-13 09:07:06 +02:00
#else
2020-05-20 14:11:22 +02:00
render_sidebar_scale_hint ( X );
#endif // ENABLE_SHADERS_MANAGER
glsafe ( :: glPopMatrix ());
}
if ( boost :: ends_with ( sidebar_field , "y" ) || uniform_scale )
{
glsafe ( :: glPushMatrix ());
#if ENABLE_SHADERS_MANAGER
render_sidebar_scale_hint ( Y , shader );
#else
render_sidebar_scale_hint ( Y );
#endif // ENABLE_SHADERS_MANAGER
glsafe ( :: glPopMatrix ());
}
if ( boost :: ends_with ( sidebar_field , "z" ) || uniform_scale )
{
glsafe ( :: glPushMatrix ());
glsafe ( :: glRotated ( 90.0 , 1.0 , 0.0 , 0.0 ));
#if ENABLE_SHADERS_MANAGER
render_sidebar_scale_hint ( Z , shader );
#else
render_sidebar_scale_hint ( Z );
#endif // ENABLE_SHADERS_MANAGER
glsafe ( :: glPopMatrix ());
}
}
#else
void Selection :: render_sidebar_scale_hints ( const std :: string & sidebar_field ) const
{
bool uniform_scale = requires_uniform_scale () || wxGetApp (). obj_manipul () -> get_uniform_scaling ();
2020-05-13 09:07:06 +02:00
if ( boost :: ends_with ( sidebar_field , "x" ) || uniform_scale )
{
glsafe ( :: glPushMatrix ());
glsafe ( :: glRotated ( - 90.0 , 0.0 , 0.0 , 1.0 ));
render_sidebar_scale_hint ( X );
glsafe ( :: glPopMatrix ());
}
if ( boost :: ends_with ( sidebar_field , "y" ) || uniform_scale )
{
glsafe ( :: glPushMatrix ());
render_sidebar_scale_hint ( Y );
glsafe ( :: glPopMatrix ());
}
if ( boost :: ends_with ( sidebar_field , "z" ) || uniform_scale )
{
glsafe ( :: glPushMatrix ());
glsafe ( :: glRotated ( 90.0 , 1.0 , 0.0 , 0.0 ));
render_sidebar_scale_hint ( Z );
glsafe ( :: glPopMatrix ());
}
2019-03-19 13:30:21 +01:00
}
2020-05-20 14:11:22 +02:00
#endif // ENABLE_GCODE_VIEWER
2019-03-19 13:30:21 +01:00
2019-06-27 13:42:50 +02:00
void Selection :: render_sidebar_layers_hints ( const std :: string & sidebar_field ) const
{
static const double Margin = 10.0 ;
std :: string field = sidebar_field ;
// extract max_z
std :: string :: size_type pos = field . rfind ( "_" );
if ( pos == std :: string :: npos )
return ;
double max_z = std :: stod ( field . substr ( pos + 1 ));
// extract min_z
field = field . substr ( 0 , pos );
pos = field . rfind ( "_" );
if ( pos == std :: string :: npos )
return ;
double min_z = std :: stod ( field . substr ( pos + 1 ));
// extract type
field = field . substr ( 0 , pos );
pos = field . rfind ( "_" );
if ( pos == std :: string :: npos )
return ;
int type = std :: stoi ( field . substr ( pos + 1 ));
const BoundingBoxf3 & box = get_bounding_box ();
const float min_x = box . min ( 0 ) - Margin ;
const float max_x = box . max ( 0 ) + Margin ;
const float min_y = box . min ( 1 ) - Margin ;
const float max_y = box . max ( 1 ) + Margin ;
2019-07-02 15:49:18 +02:00
// view dependend order of rendering to keep correct transparency
2020-01-15 12:49:34 +01:00
bool camera_on_top = wxGetApp (). plater () -> get_camera (). is_looking_downward ();
2019-07-02 15:49:18 +02:00
float z1 = camera_on_top ? min_z : max_z ;
float z2 = camera_on_top ? max_z : min_z ;
2019-06-27 13:42:50 +02:00
glsafe ( :: glEnable ( GL_DEPTH_TEST ));
glsafe ( :: glDisable ( GL_CULL_FACE ));
glsafe ( :: glEnable ( GL_BLEND ));
glsafe ( :: glBlendFunc ( GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA ));
:: glBegin ( GL_QUADS );
2019-07-02 15:49:18 +02:00
if (( camera_on_top && ( type == 1 )) || ( ! camera_on_top && ( type == 2 )))
2019-06-27 13:42:50 +02:00
:: glColor4f ( 1.0f , 0.38f , 0.0f , 1.0f );
else
:: glColor4f ( 0.8f , 0.8f , 0.8f , 0.5f );
2019-07-02 15:49:18 +02:00
:: glVertex3f ( min_x , min_y , z1 );
:: glVertex3f ( max_x , min_y , z1 );
:: glVertex3f ( max_x , max_y , z1 );
:: glVertex3f ( min_x , max_y , z1 );
2019-06-27 13:42:50 +02:00
glsafe ( :: glEnd ());
:: glBegin ( GL_QUADS );
2019-07-02 15:49:18 +02:00
if (( camera_on_top && ( type == 2 )) || ( ! camera_on_top && ( type == 1 )))
2019-06-27 13:42:50 +02:00
:: glColor4f ( 1.0f , 0.38f , 0.0f , 1.0f );
else
:: glColor4f ( 0.8f , 0.8f , 0.8f , 0.5f );
2019-07-02 15:49:18 +02:00
:: glVertex3f ( min_x , min_y , z2 );
:: glVertex3f ( max_x , min_y , z2 );
:: glVertex3f ( max_x , max_y , z2 );
:: glVertex3f ( min_x , max_y , z2 );
2019-06-27 13:42:50 +02:00
glsafe ( :: glEnd ());
glsafe ( :: glEnable ( GL_CULL_FACE ));
glsafe ( :: glDisable ( GL_BLEND ));
}
2020-05-13 09:07:06 +02:00
#if !ENABLE_GCODE_VIEWER
2019-03-21 11:02:10 +01:00
void Selection :: render_sidebar_position_hint ( Axis axis ) const
2019-03-19 13:30:21 +01:00
{
m_arrow . set_color ( AXES_COLOR [ axis ], 3 );
m_arrow . render ();
}
2020-05-13 09:07:06 +02:00
#endif // !ENABLE_GCODE_VIEWER
2019-03-19 13:30:21 +01:00
2019-03-21 11:02:10 +01:00
void Selection :: render_sidebar_rotation_hint ( Axis axis ) const
2019-03-19 13:30:21 +01:00
{
2020-05-12 16:15:43 +02:00
#if !ENABLE_GCODE_VIEWER
2019-03-19 13:30:21 +01:00
m_curved_arrow . set_color ( AXES_COLOR [ axis ], 3 );
2020-05-12 16:15:43 +02:00
#endif // !ENABLE_GCODE_VIEWER
2019-03-19 13:30:21 +01:00
m_curved_arrow . render ();
2019-03-21 11:02:10 +01:00
glsafe ( :: glRotated ( 180.0 , 0.0 , 0.0 , 1.0 ));
2019-03-19 13:30:21 +01:00
m_curved_arrow . render ();
}
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
2020-05-20 17:03:53 +02:00
#if ENABLE_GCODE_VIEWER
2020-05-20 14:11:22 +02:00
void Selection :: render_sidebar_scale_hint ( Axis axis , GLShaderProgram & shader ) const
#else
2019-03-21 11:02:10 +01:00
void Selection :: render_sidebar_scale_hint ( Axis axis ) const
2020-05-20 17:03:53 +02:00
#endif // ENABLE_GCODE_VIEWER
#else
void Selection :: render_sidebar_scale_hint ( Axis axis ) const
2020-05-20 14:11:22 +02:00
#endif // ENABLE_SHADERS_MANAGER
2019-03-19 13:30:21 +01:00
{
2020-05-13 09:07:06 +02:00
#if ENABLE_GCODE_VIEWER
2020-05-20 14:11:22 +02:00
#if ENABLE_SHADERS_MANAGER
shader . set_uniform ( "uniform_color" , ( requires_uniform_scale () || wxGetApp (). obj_manipul () -> get_uniform_scaling ()) ? UNIFORM_SCALE_COLOR : AXES_COLOR [ axis ], 4 );
#else
2020-05-13 09:07:06 +02:00
GLint color_id = :: glGetUniformLocation ( m_arrows_shader . get_shader_program_id (), "uniform_color" );
if ( color_id >= 0 )
glsafe ( :: glUniform4fv ( color_id , 1 , ( requires_uniform_scale () || wxGetApp (). obj_manipul () -> get_uniform_scaling ()) ? ( const GLfloat * ) UNIFORM_SCALE_COLOR : ( const GLfloat * ) AXES_COLOR [ axis ]));
2020-05-20 14:11:22 +02:00
#endif // ENABLE_SHADERS_MANAGER
2020-05-13 09:07:06 +02:00
#else
2019-03-19 13:30:21 +01:00
m_arrow . set_color ((( requires_uniform_scale () || wxGetApp (). obj_manipul () -> get_uniform_scaling ()) ? UNIFORM_SCALE_COLOR : AXES_COLOR [ axis ]), 3 );
2020-05-13 09:07:06 +02:00
#endif // ENABLE_GCODE_VIEWER
2019-03-19 13:30:21 +01:00
2019-03-21 11:02:10 +01:00
glsafe ( :: glTranslated ( 0.0 , 5.0 , 0.0 ));
2019-03-19 13:30:21 +01:00
m_arrow . render ();
2019-03-21 11:02:10 +01:00
glsafe ( :: glTranslated ( 0.0 , - 10.0 , 0.0 ));
glsafe ( :: glRotated ( 180.0 , 0.0 , 0.0 , 1.0 ));
2019-03-19 13:30:21 +01:00
m_arrow . render ();
}
#ifndef NDEBUG
static bool is_rotation_xy_synchronized ( const Vec3d & rot_xyz_from , const Vec3d & rot_xyz_to )
{
2019-04-08 13:35:03 +02:00
Eigen :: AngleAxisd angle_axis ( Geometry :: rotation_xyz_diff ( rot_xyz_from , rot_xyz_to ));
2019-03-19 13:30:21 +01:00
Vec3d axis = angle_axis . axis ();
double angle = angle_axis . angle ();
if ( std :: abs ( angle ) < 1e-8 )
return true ;
assert ( std :: abs ( axis . x ()) < 1e-8 );
assert ( std :: abs ( axis . y ()) < 1e-8 );
assert ( std :: abs ( std :: abs ( axis . z ()) - 1. ) < 1e-8 );
return std :: abs ( axis . x ()) < 1e-8 && std :: abs ( axis . y ()) < 1e-8 && std :: abs ( std :: abs ( axis . z ()) - 1. ) < 1e-8 ;
}
static void verify_instances_rotation_synchronized ( const Model & model , const GLVolumePtrs & volumes )
{
for ( size_t idx_object = 0 ; idx_object < model . objects . size (); ++ idx_object ) {
int idx_volume_first = - 1 ;
for ( int i = 0 ; i < ( int ) volumes . size (); ++ i ) {
if ( volumes [ i ] -> object_idx () == idx_object ) {
idx_volume_first = i ;
break ;
}
}
assert ( idx_volume_first != - 1 ); // object without instances?
if ( idx_volume_first == - 1 )
continue ;
const Vec3d & rotation0 = volumes [ idx_volume_first ] -> get_instance_rotation ();
for ( int i = idx_volume_first + 1 ; i < ( int ) volumes . size (); ++ i )
if ( volumes [ i ] -> object_idx () == idx_object ) {
const Vec3d & rotation = volumes [ i ] -> get_instance_rotation ();
assert ( is_rotation_xy_synchronized ( rotation , rotation0 ));
}
}
}
#endif /* NDEBUG */
2019-03-21 11:02:10 +01:00
void Selection :: synchronize_unselected_instances ( SyncRotationType sync_rotation_type )
2019-03-19 13:30:21 +01:00
{
std :: set < unsigned int > done ; // prevent processing volumes twice
done . insert ( m_list . begin (), m_list . end ());
for ( unsigned int i : m_list )
{
if ( done . size () == m_volumes -> size ())
break ;
const GLVolume * volume = ( * m_volumes )[ i ];
int object_idx = volume -> object_idx ();
if ( object_idx >= 1000 )
continue ;
int instance_idx = volume -> instance_idx ();
const Vec3d & rotation = volume -> get_instance_rotation ();
const Vec3d & scaling_factor = volume -> get_instance_scaling_factor ();
const Vec3d & mirror = volume -> get_instance_mirror ();
// Process unselected instances.
for ( unsigned int j = 0 ; j < ( unsigned int ) m_volumes -> size (); ++ j )
{
if ( done . size () == m_volumes -> size ())
break ;
if ( done . find ( j ) != done . end ())
continue ;
GLVolume * v = ( * m_volumes )[ j ];
if (( v -> object_idx () != object_idx ) || ( v -> instance_idx () == instance_idx ))
continue ;
assert ( is_rotation_xy_synchronized ( m_cache . volumes_data [ i ]. get_instance_rotation (), m_cache . volumes_data [ j ]. get_instance_rotation ()));
switch ( sync_rotation_type ) {
case SYNC_ROTATION_NONE :
// z only rotation -> keep instance z
// The X,Y rotations should be synchronized from start to end of the rotation.
assert ( is_rotation_xy_synchronized ( rotation , v -> get_instance_rotation ()));
break ;
case SYNC_ROTATION_FULL :
// rotation comes from place on face -> force given z
v -> set_instance_rotation ( Vec3d ( rotation ( 0 ), rotation ( 1 ), rotation ( 2 )));
break ;
case SYNC_ROTATION_GENERAL :
// generic rotation -> update instance z with the delta of the rotation.
2019-04-08 13:35:03 +02:00
double z_diff = Geometry :: rotation_diff_z ( m_cache . volumes_data [ i ]. get_instance_rotation (), m_cache . volumes_data [ j ]. get_instance_rotation ());
2019-03-19 13:30:21 +01:00
v -> set_instance_rotation ( Vec3d ( rotation ( 0 ), rotation ( 1 ), rotation ( 2 ) + z_diff ));
break ;
}
v -> set_instance_scaling_factor ( scaling_factor );
v -> set_instance_mirror ( mirror );
done . insert ( j );
}
}
#ifndef NDEBUG
verify_instances_rotation_synchronized ( * m_model , * m_volumes );
#endif /* NDEBUG */
}
2019-03-21 11:02:10 +01:00
void Selection :: synchronize_unselected_volumes ()
2019-03-19 13:30:21 +01:00
{
for ( unsigned int i : m_list )
{
const GLVolume * volume = ( * m_volumes )[ i ];
int object_idx = volume -> object_idx ();
if ( object_idx >= 1000 )
continue ;
int volume_idx = volume -> volume_idx ();
const Vec3d & offset = volume -> get_volume_offset ();
const Vec3d & rotation = volume -> get_volume_rotation ();
const Vec3d & scaling_factor = volume -> get_volume_scaling_factor ();
const Vec3d & mirror = volume -> get_volume_mirror ();
// Process unselected volumes.
for ( unsigned int j = 0 ; j < ( unsigned int ) m_volumes -> size (); ++ j )
{
if ( j == i )
continue ;
GLVolume * v = ( * m_volumes )[ j ];
if (( v -> object_idx () != object_idx ) || ( v -> volume_idx () != volume_idx ))
continue ;
v -> set_volume_offset ( offset );
v -> set_volume_rotation ( rotation );
v -> set_volume_scaling_factor ( scaling_factor );
v -> set_volume_mirror ( mirror );
}
}
}
2019-03-21 11:02:10 +01:00
void Selection :: ensure_on_bed ()
2019-03-19 13:30:21 +01:00
{
typedef std :: map < std :: pair < int , int > , double > InstancesToZMap ;
InstancesToZMap instances_min_z ;
for ( GLVolume * volume : * m_volumes )
{
if ( ! volume -> is_wipe_tower && ! volume -> is_modifier )
{
double min_z = volume -> transformed_convex_hull_bounding_box (). min ( 2 );
std :: pair < int , int > instance = std :: make_pair ( volume -> object_idx (), volume -> instance_idx ());
InstancesToZMap :: iterator it = instances_min_z . find ( instance );
if ( it == instances_min_z . end ())
it = instances_min_z . insert ( InstancesToZMap :: value_type ( instance , DBL_MAX )). first ;
it -> second = std :: min ( it -> second , min_z );
}
}
for ( GLVolume * volume : * m_volumes )
{
std :: pair < int , int > instance = std :: make_pair ( volume -> object_idx (), volume -> instance_idx ());
InstancesToZMap :: iterator it = instances_min_z . find ( instance );
if ( it != instances_min_z . end ())
volume -> set_instance_offset ( Z , volume -> get_instance_offset ( Z ) - it -> second );
}
}
2019-04-01 14:21:55 +02:00
bool Selection :: is_from_fully_selected_instance ( unsigned int volume_idx ) const
2019-04-01 13:53:48 +02:00
{
struct SameInstance
{
int obj_idx ;
int inst_idx ;
GLVolumePtrs & volumes ;
SameInstance ( int obj_idx , int inst_idx , GLVolumePtrs & volumes ) : obj_idx ( obj_idx ), inst_idx ( inst_idx ), volumes ( volumes ) {}
2019-04-02 10:55:36 +02:00
bool operator () ( unsigned int i ) { return ( volumes [ i ] -> volume_idx () >= 0 ) && ( volumes [ i ] -> object_idx () == obj_idx ) && ( volumes [ i ] -> instance_idx () == inst_idx ); }
2019-04-01 13:53:48 +02:00
};
if (( unsigned int ) m_volumes -> size () <= volume_idx )
return false ;
GLVolume * volume = ( * m_volumes )[ volume_idx ];
int object_idx = volume -> object_idx ();
if (( int ) m_model -> objects . size () <= object_idx )
return false ;
unsigned int count = ( unsigned int ) std :: count_if ( m_list . begin (), m_list . end (), SameInstance ( object_idx , volume -> instance_idx (), * m_volumes ));
return count == ( unsigned int ) m_model -> objects [ object_idx ] -> volumes . size ();
}
2019-04-10 08:40:58 +02:00
void Selection :: paste_volumes_from_clipboard ()
{
2019-07-05 19:06:19 +02:00
#ifdef _DEBUG
check_model_ids_validity ( * m_model );
#endif /* _DEBUG */
2019-06-24 12:26:11 +02:00
int dst_obj_idx = get_object_idx ();
if (( dst_obj_idx < 0 ) || (( int ) m_model -> objects . size () <= dst_obj_idx ))
return ;
ModelObject * dst_object = m_model -> objects [ dst_obj_idx ];
int dst_inst_idx = get_instance_idx ();
if (( dst_inst_idx < 0 ) || (( int ) dst_object -> instances . size () <= dst_inst_idx ))
2019-04-10 08:40:58 +02:00
return ;
2019-04-10 14:03:40 +02:00
ModelObject * src_object = m_clipboard . get_object ( 0 );
if ( src_object != nullptr )
2019-04-10 08:40:58 +02:00
{
2019-06-24 12:26:11 +02:00
ModelInstance * dst_instance = dst_object -> instances [ dst_inst_idx ];
BoundingBoxf3 dst_instance_bb = dst_object -> instance_bounding_box ( dst_inst_idx );
Transform3d src_matrix = src_object -> instances [ 0 ] -> get_transformation (). get_matrix ( true );
Transform3d dst_matrix = dst_instance -> get_transformation (). get_matrix ( true );
bool from_same_object = ( src_object -> input_file == dst_object -> input_file ) && src_matrix . isApprox ( dst_matrix );
// used to keep relative position of multivolume selections when pasting from another object
BoundingBoxf3 total_bb ;
2019-04-10 14:03:40 +02:00
ModelVolumePtrs volumes ;
for ( ModelVolume * src_volume : src_object -> volumes )
{
ModelVolume * dst_volume = dst_object -> add_volume ( * src_volume );
dst_volume -> set_new_unique_id ();
2019-06-24 12:26:11 +02:00
if ( from_same_object )
{
// // if the volume comes from the same object, apply the offset in world system
// double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05);
// dst_volume->translate(dst_matrix.inverse() * Vec3d(offset, offset, 0.0));
}
else
{
// if the volume comes from another object, apply the offset as done when adding modifiers
// see ObjectList::load_generic_subobject()
total_bb . merge ( dst_volume -> mesh (). bounding_box (). transformed ( src_volume -> get_matrix ()));
}
2019-04-10 14:03:40 +02:00
volumes . push_back ( dst_volume );
2019-07-05 19:06:19 +02:00
#ifdef _DEBUG
check_model_ids_validity ( * m_model );
#endif /* _DEBUG */
2019-04-10 14:03:40 +02:00
}
2019-06-24 12:26:11 +02:00
// keeps relative position of multivolume selections
if ( ! from_same_object )
{
for ( ModelVolume * v : volumes )
{
v -> set_offset (( v -> get_offset () - total_bb . center ()) + dst_matrix . inverse () * ( Vec3d ( dst_instance_bb . max ( 0 ), dst_instance_bb . min ( 1 ), dst_instance_bb . min ( 2 )) + 0.5 * total_bb . size () - dst_instance -> get_transformation (). get_offset ()));
}
}
wxGetApp (). obj_list () -> paste_volumes_into_list ( dst_obj_idx , volumes );
2019-04-10 08:40:58 +02:00
}
2019-07-05 19:06:19 +02:00
#ifdef _DEBUG
check_model_ids_validity ( * m_model );
#endif /* _DEBUG */
2019-04-10 08:40:58 +02:00
}
2019-04-10 15:55:32 +02:00
void Selection :: paste_objects_from_clipboard ()
2019-04-10 08:40:58 +02:00
{
2019-07-05 19:06:19 +02:00
#ifdef _DEBUG
check_model_ids_validity ( * m_model );
#endif /* _DEBUG */
2019-04-10 15:55:32 +02:00
std :: vector < size_t > object_idxs ;
const ModelObjectPtrs & src_objects = m_clipboard . get_objects ();
for ( const ModelObject * src_object : src_objects )
{
ModelObject * dst_object = m_model -> add_object ( * src_object );
2019-04-12 15:31:33 +02:00
double offset = wxGetApp (). plater () -> canvas3D () -> get_size_proportional_to_max_bed_size ( 0.05 );
2019-05-20 10:33:49 +02:00
Vec3d displacement ( offset , offset , 0.0 );
for ( ModelInstance * inst : dst_object -> instances )
{
inst -> set_offset ( inst -> get_offset () + displacement );
}
2019-04-10 15:55:32 +02:00
object_idxs . push_back ( m_model -> objects . size () - 1 );
2019-07-05 19:06:19 +02:00
#ifdef _DEBUG
check_model_ids_validity ( * m_model );
#endif /* _DEBUG */
2019-04-10 15:55:32 +02:00
}
2019-04-12 11:28:24 +02:00
2019-04-10 15:55:32 +02:00
wxGetApp (). obj_list () -> paste_objects_into_list ( object_idxs );
2019-07-05 19:06:19 +02:00
#ifdef _DEBUG
check_model_ids_validity ( * m_model );
#endif /* _DEBUG */
2019-04-10 08:40:58 +02:00
}
2019-03-19 13:30:21 +01:00
} // namespace GUI
} // namespace Slic3r