2019-03-19 13:30:21 +01:00
#include "libslic3r/libslic3r.h"
2018-05-09 10:47:04 +02:00
#include "GLCanvas3D.hpp"
2018-11-13 17:45:44 +01:00
#include "admesh/stl.h"
2019-01-24 19:08:58 +01:00
#include "polypartition.h"
2018-11-26 14:41:58 +01:00
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/PrintConfig.hpp"
2019-10-24 12:09:09 +02:00
#include "libslic3r/GCode/ThumbnailData.hpp"
2018-11-26 14:41:58 +01:00
#include "libslic3r/Geometry.hpp"
2019-08-27 15:55:53 +02:00
#include "libslic3r/ExtrusionEntity.hpp"
2020-05-26 11:09:38 +02:00
#include "libslic3r/Layer.hpp"
2018-11-27 16:55:54 +01:00
#include "libslic3r/Utils.hpp"
2019-01-24 11:30:29 +01:00
#include "libslic3r/Technologies.hpp"
2019-02-05 19:45:52 +01:00
#include "libslic3r/Tesselate.hpp"
2020-06-16 16:58:41 +02:00
#include "libslic3r/PresetBundle.hpp"
2018-11-13 17:45:44 +01:00
#include "slic3r/GUI/3DScene.hpp"
2018-11-22 15:29:59 +01:00
#include "slic3r/GUI/BackgroundSlicingProcess.hpp"
2018-11-13 17:45:44 +01:00
#include "slic3r/GUI/GLShader.hpp"
#include "slic3r/GUI/GUI.hpp"
2019-04-29 14:32:02 +02:00
#include "slic3r/GUI/Tab.hpp"
2019-06-18 11:40:26 +02:00
#include "slic3r/GUI/GUI_Preview.hpp"
2020-05-06 14:38:53 +02:00
#include "slic3r/GUI/OpenGLManager.hpp"
2020-02-28 14:29:53 +01:00
#include "slic3r/GUI/3DBed.hpp"
#include "slic3r/GUI/Camera.hpp"
2020-05-26 17:42:57 +02:00
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/MainFrame.hpp"
2019-11-27 11:08:10 +01:00
2018-10-01 15:09:31 +02:00
#include "GUI_App.hpp"
2018-10-05 23:29:15 +02:00
#include "GUI_ObjectList.hpp"
2018-10-04 16:43:10 +02:00
#include "GUI_ObjectManipulation.hpp"
2019-09-27 14:52:19 +02:00
#include "Mouse3DController.hpp"
2018-11-26 14:41:58 +01:00
#include "I18N.hpp"
2020-08-03 15:36:55 +02:00
#include "NotificationManager.hpp"
2020-12-07 10:02:22 +01:00
#include "format.hpp"
2018-05-14 14:14:19 +02:00
2019-01-24 11:30:29 +01:00
#if ENABLE_RETINA_GL
#include "slic3r/Utils/RetinaHelper.hpp"
#endif
2018-05-23 15:35:11 +02:00
#include <GL/glew.h>
2018-05-24 13:46:17 +02:00
2018-05-09 10:47:04 +02:00
#include <wx/glcanvas.h>
2018-07-19 13:18:19 +02:00
#include <wx/bitmap.h>
#include <wx/dcmemory.h>
#include <wx/image.h>
#include <wx/settings.h>
2018-10-25 10:36:47 +02:00
#include <wx/tooltip.h>
2018-11-27 16:55:54 +01:00
#include <wx/debug.h>
2019-03-21 14:33:55 +01:00
#include <wx/fontutil.h>
2018-05-09 10:47:04 +02:00
2018-09-12 13:17:47 +02:00
// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx.
2018-11-13 17:45:44 +01:00
#include "libslic3r/Print.hpp"
#include "libslic3r/SLAPrint.hpp"
2018-09-12 11:59:02 +02:00
2018-11-14 08:53:56 +01:00
#include "wxExtensions.hpp"
2018-06-05 14:09:36 +02:00
#include <tbb/parallel_for.h>
#include <tbb/spin_mutex.h>
#include <boost/log/trivial.hpp>
2018-06-11 10:46:32 +02:00
#include <boost/algorithm/string/predicate.hpp>
2018-06-05 14:09:36 +02:00
2018-05-09 10:47:04 +02:00
#include <iostream>
2018-05-24 15:22:53 +02:00
#include <float.h>
2018-06-13 09:12:16 +02:00
#include <algorithm>
2019-01-24 11:30:29 +01:00
#include <cmath>
2020-01-23 15:07:31 +01:00
#include "DoubleSlider.hpp"
2018-05-09 10:47:04 +02:00
2020-01-28 13:15:21 +01:00
#include <imgui/imgui_internal.h>
2018-06-01 09:00:30 +02:00
static const float TRACKBALLSIZE = 0.8f ;
2018-05-24 13:46:17 +02:00
2018-12-12 10:38:07 +01:00
static const float DEFAULT_BG_DARK_COLOR [ 3 ] = { 0.478f , 0.478f , 0.478f };
static const float DEFAULT_BG_LIGHT_COLOR [ 3 ] = { 0.753f , 0.753f , 0.753f };
static const float ERROR_BG_DARK_COLOR [ 3 ] = { 0.478f , 0.192f , 0.039f };
static const float ERROR_BG_LIGHT_COLOR [ 3 ] = { 0.753f , 0.192f , 0.039f };
2019-03-15 12:53:15 +01:00
//static const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } };
2018-12-20 11:14:53 +01:00
2019-08-26 11:12:48 +02:00
// Number of floats
2019-08-27 15:55:53 +02:00
static const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6 ; // 3.15MB
2019-08-26 11:12:48 +02:00
// Reserve size in number of floats.
2019-08-27 15:55:53 +02:00
static const size_t VERTEX_BUFFER_RESERVE_SIZE = 131072 * 2 ; // 1.05MB
// Reserve size in number of floats, maximum sum of all preallocated buffers.
static const size_t VERTEX_BUFFER_RESERVE_SIZE_SUM_MAX = 1024 * 1024 * 128 / 4 ; // 128MB
2019-08-26 11:12:48 +02:00
2018-05-09 10:47:04 +02:00
namespace Slic3r {
namespace GUI {
2020-12-15 12:14:30 +01:00
#ifdef __WXGTK3__
// wxGTK3 seems to simulate OSX behavior in regard to HiDPI scaling support.
RetinaHelper :: RetinaHelper ( wxWindow * window ) : m_window ( window ), m_self ( nullptr ) {}
RetinaHelper ::~ RetinaHelper () {}
float RetinaHelper :: get_scale_factor () { return float ( m_window -> GetContentScaleFactor ()); }
#endif // __WXGTK3__
2018-05-24 13:46:17 +02:00
Size :: Size ()
: m_width ( 0 )
, m_height ( 0 )
{
}
2019-01-24 11:30:29 +01:00
Size :: Size ( int width , int height , float scale_factor )
2018-05-24 13:46:17 +02:00
: m_width ( width )
, m_height ( height )
2019-01-24 11:30:29 +01:00
, m_scale_factor ( scale_factor )
2018-05-24 13:46:17 +02:00
{
}
int Size :: get_width () const
{
return m_width ;
}
void Size :: set_width ( int width )
{
m_width = width ;
}
int Size :: get_height () const
{
return m_height ;
}
void Size :: set_height ( int height )
{
m_height = height ;
}
2019-01-24 11:30:29 +01:00
int Size :: get_scale_factor () const
{
return m_scale_factor ;
}
void Size :: set_scale_factor ( int scale_factor )
{
m_scale_factor = scale_factor ;
}
2018-05-18 14:08:59 +02:00
GLCanvas3D :: LayersEditing :: LayersEditing ()
2019-06-25 09:20:58 +02:00
: m_enabled ( false )
2018-05-25 14:05:08 +02:00
, m_z_texture_id ( 0 )
2019-01-21 10:06:51 +01:00
, m_model_object ( nullptr )
, m_object_max_z ( 0.f )
2019-01-21 17:02:16 +01:00
, m_slicing_parameters ( nullptr )
2019-01-21 10:06:51 +01:00
, m_layer_height_profile_modified ( false )
2019-12-13 13:43:16 +01:00
, m_adaptive_quality ( 0.5f )
2018-06-01 15:54:41 +02:00
, state ( Unknown )
, band_width ( 2.0f )
, strength ( 0.005f )
, last_object_id ( - 1 )
, last_z ( 0.0f )
2019-01-21 10:06:51 +01:00
, last_action ( LAYER_HEIGHT_EDIT_ACTION_INCREASE )
2018-05-18 14:08:59 +02:00
{
}
2018-05-24 13:46:17 +02:00
GLCanvas3D :: LayersEditing ::~ LayersEditing ()
{
2018-05-25 14:05:08 +02:00
if ( m_z_texture_id != 0 )
{
2019-03-27 14:42:09 +01:00
glsafe ( :: glDeleteTextures ( 1 , & m_z_texture_id ));
2018-05-25 14:05:08 +02:00
m_z_texture_id = 0 ;
}
2019-01-21 10:06:51 +01:00
delete m_slicing_parameters ;
2018-05-25 14:05:08 +02:00
}
2019-01-24 11:30:29 +01:00
const float GLCanvas3D :: LayersEditing :: THICKNESS_BAR_WIDTH = 70.0f ;
2020-05-20 14:11:22 +02:00
void GLCanvas3D :: LayersEditing :: init ()
{
2019-03-27 14:42:09 +01:00
glsafe ( :: glGenTextures ( 1 , ( GLuint * ) & m_z_texture_id ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , m_z_texture_id ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_S , GL_CLAMP ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_T , GL_CLAMP ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_LINEAR ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR_MIPMAP_NEAREST ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAX_LEVEL , 1 ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , 0 ));
2018-05-25 14:05:08 +02:00
}
2019-01-22 12:14:26 +01:00
void GLCanvas3D :: LayersEditing :: set_config ( const DynamicPrintConfig * config )
2020-11-18 00:06:34 +01:00
{
2019-01-22 12:14:26 +01:00
m_config = config ;
delete m_slicing_parameters ;
m_slicing_parameters = nullptr ;
m_layers_texture . valid = false ;
}
2019-01-21 10:06:51 +01:00
void GLCanvas3D :: LayersEditing :: select_object ( const Model & model , int object_id )
{
2019-01-21 17:02:16 +01:00
const ModelObject * model_object_new = ( object_id >= 0 ) ? model . objects [ object_id ] : nullptr ;
2019-04-13 14:15:54 +02:00
// Maximum height of an object changes when the object gets rotated or scaled.
// Changing maximum height of an object will invalidate the layer heigth editing profile.
// m_model_object->raw_bounding_box() is cached, therefore it is cheap even if this method is called frequently.
2019-05-04 21:40:58 +02:00
float new_max_z = ( model_object_new == nullptr ) ? 0.f : model_object_new -> raw_bounding_box (). size (). z ();
2019-04-13 14:15:54 +02:00
if ( m_model_object != model_object_new || this -> last_object_id != object_id || m_object_max_z != new_max_z ||
( model_object_new != nullptr && m_model_object -> id () != model_object_new -> id ())) {
2019-01-21 10:06:51 +01:00
m_layer_height_profile . clear ();
m_layer_height_profile_modified = false ;
2019-01-21 17:02:16 +01:00
delete m_slicing_parameters ;
2019-04-13 14:15:54 +02:00
m_slicing_parameters = nullptr ;
2019-01-22 12:14:26 +01:00
m_layers_texture . valid = false ;
2019-04-13 14:15:54 +02:00
this -> last_object_id = object_id ;
m_model_object = model_object_new ;
m_object_max_z = new_max_z ;
2019-01-21 10:06:51 +01:00
}
}
2018-05-25 14:05:08 +02:00
bool GLCanvas3D :: LayersEditing :: is_allowed () const
{
2020-05-20 14:11:22 +02:00
return wxGetApp (). get_shader ( "variable_layer_height" ) != nullptr && m_z_texture_id > 0 ;
2018-05-24 13:46:17 +02:00
}
2018-05-18 14:08:59 +02:00
bool GLCanvas3D :: LayersEditing :: is_enabled () const
{
return m_enabled ;
}
2018-05-25 14:05:08 +02:00
void GLCanvas3D :: LayersEditing :: set_enabled ( bool enabled )
{
2018-05-25 16:28:24 +02:00
m_enabled = is_allowed () && enabled ;
2018-05-25 14:05:08 +02:00
}
2020-10-02 10:26:11 +02:00
float GLCanvas3D :: LayersEditing :: s_overelay_window_width ;
2019-01-21 10:06:51 +01:00
void GLCanvas3D :: LayersEditing :: render_overlay ( const GLCanvas3D & canvas ) const
2018-05-25 15:56:14 +02:00
{
if ( ! m_enabled )
return ;
2019-11-07 15:55:45 +01:00
const Size & cnv_size = canvas . get_canvas_size ();
ImGuiWrapper & imgui = * wxGetApp (). imgui ();
2020-08-03 08:28:43 +02:00
imgui . set_next_window_pos ( static_cast < float > ( cnv_size . get_width ()) - imgui . get_style_scaling () * THICKNESS_BAR_WIDTH ,
static_cast < float > ( cnv_size . get_height ()), ImGuiCond_Always , 1.0f , 1.0f );
2019-11-07 15:55:45 +01:00
2020-07-08 13:33:50 +02:00
imgui . begin ( _L ( "Variable layer height" ), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse );
2019-11-07 15:55:45 +01:00
2020-08-03 08:56:58 +02:00
imgui . text_colored ( ImGuiWrapper :: COL_ORANGE_LIGHT , _L ( "Left mouse button:" ));
2019-11-07 15:55:45 +01:00
ImGui :: SameLine ();
2020-07-08 13:33:50 +02:00
imgui . text ( _L ( "Add detail" ));
2019-11-07 15:55:45 +01:00
2020-08-03 08:56:58 +02:00
imgui . text_colored ( ImGuiWrapper :: COL_ORANGE_LIGHT , _L ( "Right mouse button:" ));
2019-11-07 15:55:45 +01:00
ImGui :: SameLine ();
2020-07-08 13:33:50 +02:00
imgui . text ( _L ( "Remove detail" ));
2019-11-07 15:55:45 +01:00
2020-08-03 08:56:58 +02:00
imgui . text_colored ( ImGuiWrapper :: COL_ORANGE_LIGHT , _L ( "Shift + Left mouse button:" ));
2019-11-07 15:55:45 +01:00
ImGui :: SameLine ();
2020-07-08 13:33:50 +02:00
imgui . text ( _L ( "Reset to base" ));
2019-11-07 15:55:45 +01:00
2020-08-03 08:56:58 +02:00
imgui . text_colored ( ImGuiWrapper :: COL_ORANGE_LIGHT , _L ( "Shift + Right mouse button:" ));
2019-11-07 15:55:45 +01:00
ImGui :: SameLine ();
2020-07-08 13:33:50 +02:00
imgui . text ( _L ( "Smoothing" ));
2019-11-07 15:55:45 +01:00
2020-08-03 08:56:58 +02:00
imgui . text_colored ( ImGuiWrapper :: COL_ORANGE_LIGHT , _L ( "Mouse wheel:" ));
2019-11-07 15:55:45 +01:00
ImGui :: SameLine ();
2020-07-08 13:33:50 +02:00
imgui . text ( _L ( "Increase/decrease edit area" ));
2019-11-07 15:55:45 +01:00
ImGui :: Separator ();
2020-07-08 13:33:50 +02:00
if ( imgui . button ( _L ( "Adaptive" )))
2019-12-13 13:43:16 +01:00
wxPostEvent (( wxEvtHandler * ) canvas . get_wxglcanvas (), Event < float > ( EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE , m_adaptive_quality ));
2019-11-12 14:18:43 +01:00
2019-11-13 13:53:02 +01:00
ImGui :: SameLine ();
2019-11-15 15:49:07 +01:00
float text_align = ImGui :: GetCursorPosX ();
2019-12-11 15:30:25 +01:00
ImGui :: AlignTextToFramePadding ();
2020-07-08 13:33:50 +02:00
imgui . text ( _L ( "Quality / Speed" ));
2020-08-03 08:28:43 +02:00
if ( ImGui :: IsItemHovered ()) {
2019-12-11 15:30:25 +01:00
ImGui :: BeginTooltip ();
2020-07-08 13:33:50 +02:00
ImGui :: TextUnformatted ( _L ( "Higher print quality versus higher print speed." ). ToUTF8 ());
2019-12-11 15:30:25 +01:00
ImGui :: EndTooltip ();
}
2019-11-14 10:22:48 +01:00
ImGui :: SameLine ();
2019-11-15 15:49:07 +01:00
float widget_align = ImGui :: GetCursorPosX ();
2019-11-26 14:44:14 +01:00
ImGui :: PushItemWidth ( imgui . get_style_scaling () * 120.0f );
2019-12-13 13:43:16 +01:00
m_adaptive_quality = clamp ( 0.0f , 1.f , m_adaptive_quality );
ImGui :: SliderFloat ( "" , & m_adaptive_quality , 0.0f , 1.f , "%.2f" );
2019-11-14 10:22:48 +01:00
2019-11-15 15:49:07 +01:00
ImGui :: Separator ();
2020-07-08 13:33:50 +02:00
if ( imgui . button ( _L ( "Smooth" )))
2019-11-29 12:45:41 +01:00
wxPostEvent (( wxEvtHandler * ) canvas . get_wxglcanvas (), HeightProfileSmoothEvent ( EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE , m_smooth_params ));
2019-11-15 15:49:07 +01:00
ImGui :: SameLine ();
ImGui :: SetCursorPosX ( text_align );
2019-12-11 15:30:25 +01:00
ImGui :: AlignTextToFramePadding ();
2020-07-08 13:33:50 +02:00
imgui . text ( _L ( "Radius" ));
2019-11-15 15:49:07 +01:00
ImGui :: SameLine ();
ImGui :: SetCursorPosX ( widget_align );
2019-11-26 14:44:14 +01:00
ImGui :: PushItemWidth ( imgui . get_style_scaling () * 120.0f );
2019-11-20 14:06:30 +01:00
int radius = ( int ) m_smooth_params . radius ;
2019-11-15 15:49:07 +01:00
if ( ImGui :: SliderInt ( "##1" , & radius , 1 , 10 ))
2019-11-20 14:06:30 +01:00
m_smooth_params . radius = ( unsigned int ) radius ;
ImGui :: SetCursorPosX ( text_align );
2019-12-11 15:30:25 +01:00
ImGui :: AlignTextToFramePadding ();
2020-07-08 13:33:50 +02:00
imgui . text ( _L ( "Keep min" ));
2019-11-20 14:06:30 +01:00
ImGui :: SameLine ();
2019-12-06 16:20:59 +01:00
if ( ImGui :: GetCursorPosX () < widget_align ) // because of line lenght after localization
ImGui :: SetCursorPosX ( widget_align );
2019-12-11 15:30:25 +01:00
2019-11-26 14:44:14 +01:00
ImGui :: PushItemWidth ( imgui . get_style_scaling () * 120.0f );
2019-11-20 14:06:30 +01:00
imgui . checkbox ( "##2" , m_smooth_params . keep_min );
2019-11-15 15:49:07 +01:00
2019-11-14 10:22:48 +01:00
ImGui :: Separator ();
2020-07-08 13:33:50 +02:00
if ( imgui . button ( _L ( "Reset" )))
2019-11-13 13:53:02 +01:00
wxPostEvent (( wxEvtHandler * ) canvas . get_wxglcanvas (), SimpleEvent ( EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE ));
2020-10-02 10:26:11 +02:00
GLCanvas3D :: LayersEditing :: s_overelay_window_width = ImGui :: GetWindowSize (). x /*+ (float)m_layers_texture.width/4*/ ;
2019-11-07 15:55:45 +01:00
imgui . end ();
const Rect & bar_rect = get_bar_rect_viewport ( canvas );
render_active_object_annotations ( canvas , bar_rect );
render_profile ( bar_rect );
2018-05-24 13:46:17 +02:00
}
2018-05-28 14:10:02 +02:00
float GLCanvas3D :: LayersEditing :: get_cursor_z_relative ( const GLCanvas3D & canvas )
2018-05-28 13:43:29 +02:00
{
2019-04-01 11:08:26 +02:00
const Vec2d mouse_pos = canvas . get_local_mouse_position ();
2018-05-30 15:18:45 +02:00
const Rect & rect = get_bar_rect_screen ( canvas );
2018-08-17 15:53:43 +02:00
float x = ( float ) mouse_pos ( 0 );
float y = ( float ) mouse_pos ( 1 );
2018-05-30 15:18:45 +02:00
float t = rect . get_top ();
float b = rect . get_bottom ();
2018-05-28 13:43:29 +02:00
2018-05-30 15:18:45 +02:00
return (( rect . get_left () <= x ) && ( x <= rect . get_right ()) && ( t <= y ) && ( y <= b )) ?
2018-05-28 13:43:29 +02:00
// Inside the bar.
( b - y - 1.0f ) / ( b - t - 1.0f ) :
// Outside the bar.
- 1000.0f ;
}
2018-05-28 14:39:59 +02:00
bool GLCanvas3D :: LayersEditing :: bar_rect_contains ( const GLCanvas3D & canvas , float x , float y )
{
2018-05-30 15:18:45 +02:00
const Rect & rect = get_bar_rect_screen ( canvas );
2018-05-28 14:39:59 +02:00
return ( rect . get_left () <= x ) && ( x <= rect . get_right ()) && ( rect . get_top () <= y ) && ( y <= rect . get_bottom ());
}
2018-05-30 15:18:45 +02:00
Rect GLCanvas3D :: LayersEditing :: get_bar_rect_screen ( const GLCanvas3D & canvas )
{
const Size & cnv_size = canvas . get_canvas_size ();
float w = ( float ) cnv_size . get_width ();
float h = ( float ) cnv_size . get_height ();
2019-11-07 15:55:45 +01:00
return Rect ( w - thickness_bar_width ( canvas ), 0.0f , w , h );
2018-05-30 15:18:45 +02:00
}
Rect GLCanvas3D :: LayersEditing :: get_bar_rect_viewport ( const GLCanvas3D & canvas )
{
const Size & cnv_size = canvas . get_canvas_size ();
float half_w = 0.5f * ( float ) cnv_size . get_width ();
float half_h = 0.5f * ( float ) cnv_size . get_height ();
2020-03-02 10:58:46 +01:00
float inv_zoom = ( float ) wxGetApp (). plater () -> get_camera (). get_inv_zoom ();
2019-11-07 15:55:45 +01:00
return Rect (( half_w - thickness_bar_width ( canvas )) * inv_zoom , half_h * inv_zoom , half_w * inv_zoom , - half_h * inv_zoom );
2018-05-30 15:18:45 +02:00
}
2019-11-07 15:55:45 +01:00
bool GLCanvas3D :: LayersEditing :: is_initialized () const
2018-05-25 14:05:08 +02:00
{
2020-05-20 14:11:22 +02:00
return wxGetApp (). get_shader ( "variable_layer_height" ) != nullptr ;
2018-05-25 14:05:08 +02:00
}
2019-11-26 14:44:14 +01:00
std :: string GLCanvas3D :: LayersEditing :: get_tooltip ( const GLCanvas3D & canvas ) const
{
std :: string ret ;
2020-11-19 12:29:19 +01:00
if ( m_enabled && m_layer_height_profile . size () >= 4 ) {
2019-11-26 14:44:14 +01:00
float z = get_cursor_z_relative ( canvas );
2020-11-19 12:29:19 +01:00
if ( z != - 1000.0f ) {
2019-11-26 14:44:14 +01:00
z *= m_object_max_z ;
float h = 0.0f ;
2020-11-19 12:29:19 +01:00
for ( size_t i = m_layer_height_profile . size () - 2 ; i >= 2 ; i -= 2 ) {
2019-11-26 14:44:14 +01:00
float zi = m_layer_height_profile [ i ];
float zi_1 = m_layer_height_profile [ i - 2 ];
2020-11-19 12:29:19 +01:00
if ( zi_1 <= z && z <= zi ) {
2019-11-26 14:44:14 +01:00
float dz = zi - zi_1 ;
h = ( dz != 0.0f ) ? lerp ( m_layer_height_profile [ i - 1 ], m_layer_height_profile [ i + 1 ], ( z - zi_1 ) / dz ) : m_layer_height_profile [ i + 1 ];
break ;
}
}
if ( h > 0.0f )
ret = std :: to_string ( h );
}
}
return ret ;
}
2019-11-07 15:55:45 +01:00
void GLCanvas3D :: LayersEditing :: render_active_object_annotations ( const GLCanvas3D & canvas , const Rect & bar_rect ) const
2018-05-25 15:56:14 +02:00
{
2020-05-20 14:11:22 +02:00
GLShaderProgram * shader = wxGetApp (). get_shader ( "variable_layer_height" );
if ( shader == nullptr )
return ;
shader -> start_using ();
shader -> set_uniform ( "z_to_texture_row" , float ( m_layers_texture . cells - 1 ) / ( float ( m_layers_texture . width ) * m_object_max_z ));
shader -> set_uniform ( "z_texture_row_to_normalized" , 1.0f / ( float ) m_layers_texture . height );
shader -> set_uniform ( "z_cursor" , m_object_max_z * this -> get_cursor_z_relative ( canvas ));
shader -> set_uniform ( "z_cursor_band_width" , band_width );
shader -> set_uniform ( "object_max_z" , m_object_max_z );
2018-05-25 15:56:14 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glPixelStorei ( GL_UNPACK_ALIGNMENT , 1 ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , m_z_texture_id ));
2018-05-25 15:56:14 +02:00
// Render the color bar
float l = bar_rect . get_left ();
float r = bar_rect . get_right ();
float t = bar_rect . get_top ();
float b = bar_rect . get_bottom ();
:: glBegin ( GL_QUADS );
2019-01-21 14:41:59 +01:00
:: glNormal3f ( 0.0f , 0.0f , 1.0f );
2019-06-14 10:55:56 +02:00
:: glTexCoord2f ( 0.0f , 0.0f ); :: glVertex2f ( l , b );
:: glTexCoord2f ( 1.0f , 0.0f ); :: glVertex2f ( r , b );
:: glTexCoord2f ( 1.0f , 1.0f ); :: glVertex2f ( r , t );
:: glTexCoord2f ( 0.0f , 1.0f ); :: glVertex2f ( l , t );
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnd ());
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , 0 ));
2018-05-25 15:56:14 +02:00
2020-05-20 14:11:22 +02:00
shader -> stop_using ();
2018-05-25 15:56:14 +02:00
}
2019-11-07 15:55:45 +01:00
void GLCanvas3D :: LayersEditing :: render_profile ( const Rect & bar_rect ) const
2018-05-24 15:17:01 +02:00
{
2019-01-21 10:06:51 +01:00
//FIXME show some kind of legend.
2018-05-24 15:17:01 +02:00
2019-06-26 11:49:02 +02:00
if ( ! m_slicing_parameters )
return ;
2018-05-24 15:17:01 +02:00
// Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region.
2019-01-21 17:02:16 +01:00
float scale_x = bar_rect . get_width () / ( float )( 1.12 * m_slicing_parameters -> max_layer_height );
float scale_y = bar_rect . get_height () / m_object_max_z ;
float x = bar_rect . get_left () + ( float ) m_slicing_parameters -> layer_height * scale_x ;
2018-05-24 15:17:01 +02:00
// Baseline
2019-03-27 14:42:09 +01:00
glsafe ( :: glColor3f ( 0.0f , 0.0f , 0.0f ));
2018-05-24 15:17:01 +02:00
:: glBegin ( GL_LINE_STRIP );
2019-01-21 17:02:16 +01:00
:: glVertex2f ( x , bar_rect . get_bottom ());
:: glVertex2f ( x , bar_rect . get_top ());
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnd ());
2018-05-24 15:17:01 +02:00
// Curve
2019-03-27 14:42:09 +01:00
glsafe ( :: glColor3f ( 0.0f , 0.0f , 1.0f ));
2019-01-21 10:06:51 +01:00
:: glBegin ( GL_LINE_STRIP );
for ( unsigned int i = 0 ; i < m_layer_height_profile . size (); i += 2 )
2019-01-21 17:02:16 +01:00
:: glVertex2f ( bar_rect . get_left () + ( float ) m_layer_height_profile [ i + 1 ] * scale_x , bar_rect . get_bottom () + ( float ) m_layer_height_profile [ i ] * scale_y );
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnd ());
2019-01-21 10:06:51 +01:00
}
2018-05-24 15:17:01 +02:00
2019-01-21 10:06:51 +01:00
void GLCanvas3D :: LayersEditing :: render_volumes ( const GLCanvas3D & canvas , const GLVolumeCollection & volumes ) const
{
assert ( this -> is_allowed ());
assert ( this -> last_object_id != - 1 );
2020-05-20 14:11:22 +02:00
GLShaderProgram * shader = wxGetApp (). get_shader ( "variable_layer_height" );
2020-05-21 10:15:00 +02:00
if ( shader == nullptr )
return ;
2020-05-20 14:11:22 +02:00
2020-05-21 10:15:00 +02:00
GLShaderProgram * current_shader = wxGetApp (). get_current_shader ();
if ( shader -> get_id () != current_shader -> get_id ())
2020-05-20 14:11:22 +02:00
// The layer editing shader is not yet active. Activate it.
shader -> start_using ();
else
// The layer editing shader was already active.
2020-05-21 10:15:00 +02:00
current_shader = nullptr ;
2020-05-20 14:11:22 +02:00
const_cast < LayersEditing *> ( this ) -> generate_layer_height_texture ();
// Uniforms were resolved, go ahead using the layer editing shader.
shader -> set_uniform ( "z_to_texture_row" , float ( m_layers_texture . cells - 1 ) / ( float ( m_layers_texture . width ) * float ( m_object_max_z )));
shader -> set_uniform ( "z_texture_row_to_normalized" , 1.0f / float ( m_layers_texture . height ));
shader -> set_uniform ( "z_cursor" , float ( m_object_max_z ) * float ( this -> get_cursor_z_relative ( canvas )));
shader -> set_uniform ( "z_cursor_band_width" , float ( this -> band_width ));
2019-01-21 10:06:51 +01:00
2020-05-22 16:08:02 +02:00
// Initialize the layer height texture mapping.
GLsizei w = ( GLsizei ) m_layers_texture . width ;
GLsizei h = ( GLsizei ) m_layers_texture . height ;
GLsizei half_w = w / 2 ;
GLsizei half_h = h / 2 ;
glsafe ( :: glPixelStorei ( GL_UNPACK_ALIGNMENT , 1 ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , m_z_texture_id ));
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 0 , GL_RGBA , w , h , 0 , GL_RGBA , GL_UNSIGNED_BYTE , 0 ));
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 1 , GL_RGBA , half_w , half_h , 0 , GL_RGBA , GL_UNSIGNED_BYTE , 0 ));
glsafe ( :: glTexSubImage2D ( GL_TEXTURE_2D , 0 , 0 , 0 , w , h , GL_RGBA , GL_UNSIGNED_BYTE , m_layers_texture . data . data ()));
glsafe ( :: glTexSubImage2D ( GL_TEXTURE_2D , 1 , 0 , 0 , half_w , half_h , GL_RGBA , GL_UNSIGNED_BYTE , m_layers_texture . data . data () + m_layers_texture . width * m_layers_texture . height * 4 ));
for ( const GLVolume * glvolume : volumes . volumes ) {
// Render the object using the layer editing shader and texture.
if ( ! glvolume -> is_active || glvolume -> composite_id . object_id != this -> last_object_id || glvolume -> is_modifier )
continue ;
2019-01-21 10:06:51 +01:00
2020-05-22 16:08:02 +02:00
shader -> set_uniform ( "volume_world_matrix" , glvolume -> world_matrix ());
shader -> set_uniform ( "object_max_z" , GLfloat ( 0 ));
glvolume -> render ();
2020-05-20 14:11:22 +02:00
}
2020-05-22 16:08:02 +02:00
// Revert back to the previous shader.
glBindTexture ( GL_TEXTURE_2D , 0 );
if ( current_shader != nullptr )
current_shader -> start_using ();
2019-01-21 10:06:51 +01:00
}
void GLCanvas3D :: LayersEditing :: adjust_layer_height_profile ()
{
2019-01-21 17:02:16 +01:00
this -> update_slicing_parameters ();
2019-01-21 10:06:51 +01:00
PrintObject :: update_layer_height_profile ( * m_model_object , * m_slicing_parameters , m_layer_height_profile );
Slic3r :: adjust_layer_height_profile ( * m_slicing_parameters , m_layer_height_profile , this -> last_z , this -> strength , this -> band_width , this -> last_action );
m_layer_height_profile_modified = true ;
m_layers_texture . valid = false ;
}
2019-01-23 14:00:03 +01:00
void GLCanvas3D :: LayersEditing :: reset_layer_height_profile ( GLCanvas3D & canvas )
2019-01-22 12:14:26 +01:00
{
const_cast < ModelObject *> ( m_model_object ) -> layer_height_profile . clear ();
m_layer_height_profile . clear ();
m_layers_texture . valid = false ;
2019-01-23 14:00:03 +01:00
canvas . post_event ( SimpleEvent ( EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS ));
2019-01-22 12:14:26 +01:00
}
2019-12-13 13:43:16 +01:00
void GLCanvas3D :: LayersEditing :: adaptive_layer_height_profile ( GLCanvas3D & canvas , float quality_factor )
2019-11-12 14:18:43 +01:00
{
2019-11-29 14:24:24 +01:00
this -> update_slicing_parameters ();
2019-12-13 13:43:16 +01:00
m_layer_height_profile = layer_height_profile_adaptive ( * m_slicing_parameters , * m_model_object , quality_factor );
2020-09-24 15:34:13 +02:00
const_cast < ModelObject *> ( m_model_object ) -> layer_height_profile . set ( m_layer_height_profile );
2019-11-12 14:18:43 +01:00
m_layers_texture . valid = false ;
canvas . post_event ( SimpleEvent ( EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS ));
}
2019-11-15 15:49:07 +01:00
2019-11-20 14:06:30 +01:00
void GLCanvas3D :: LayersEditing :: smooth_layer_height_profile ( GLCanvas3D & canvas , const HeightProfileSmoothingParams & smoothing_params )
2019-11-15 15:49:07 +01:00
{
2019-11-29 14:24:24 +01:00
this -> update_slicing_parameters ();
2019-11-20 14:06:30 +01:00
m_layer_height_profile = smooth_height_profile ( m_layer_height_profile , * m_slicing_parameters , smoothing_params );
2020-09-24 15:34:13 +02:00
const_cast < ModelObject *> ( m_model_object ) -> layer_height_profile . set ( m_layer_height_profile );
2019-11-15 15:49:07 +01:00
m_layers_texture . valid = false ;
canvas . post_event ( SimpleEvent ( EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS ));
}
2019-11-12 14:18:43 +01:00
2019-01-21 10:06:51 +01:00
void GLCanvas3D :: LayersEditing :: generate_layer_height_texture ()
{
2019-01-21 17:02:16 +01:00
this -> update_slicing_parameters ();
// Always try to update the layer height profile.
2019-01-21 10:06:51 +01:00
bool update = ! m_layers_texture . valid ;
if ( PrintObject :: update_layer_height_profile ( * m_model_object , * m_slicing_parameters , m_layer_height_profile )) {
// Initialized to the default value.
m_layer_height_profile_modified = false ;
update = true ;
2018-05-24 15:17:01 +02:00
}
2019-01-21 10:06:51 +01:00
// Update if the layer height profile was changed, or when the texture is not valid.
if ( ! update && ! m_layers_texture . data . empty () && m_layers_texture . cells > 0 )
// Texture is valid, don't update.
return ;
if ( m_layers_texture . data . empty ()) {
m_layers_texture . width = 1024 ;
m_layers_texture . height = 1024 ;
m_layers_texture . levels = 2 ;
m_layers_texture . data . assign ( m_layers_texture . width * m_layers_texture . height * 5 , 0 );
}
bool level_of_detail_2nd_level = true ;
m_layers_texture . cells = Slic3r :: generate_layer_height_texture (
* m_slicing_parameters ,
Slic3r :: generate_object_layers ( * m_slicing_parameters , m_layer_height_profile ),
m_layers_texture . data . data (), m_layers_texture . height , m_layers_texture . width , level_of_detail_2nd_level );
m_layers_texture . valid = true ;
}
void GLCanvas3D :: LayersEditing :: accept_changes ( GLCanvas3D & canvas )
{
if ( last_object_id >= 0 ) {
if ( m_layer_height_profile_modified ) {
2019-12-06 08:59:25 +01:00
wxGetApp (). plater () -> take_snapshot ( _ ( L ( "Variable layer height - Manual edit" )));
2020-09-24 15:34:13 +02:00
const_cast < ModelObject *> ( m_model_object ) -> layer_height_profile . set ( m_layer_height_profile );
2019-01-21 10:06:51 +01:00
canvas . post_event ( SimpleEvent ( EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS ));
}
}
m_layer_height_profile_modified = false ;
2018-05-24 15:17:01 +02:00
}
2019-01-21 17:02:16 +01:00
void GLCanvas3D :: LayersEditing :: update_slicing_parameters ()
{
if ( m_slicing_parameters == nullptr ) {
m_slicing_parameters = new SlicingParameters ();
2019-04-13 14:15:54 +02:00
* m_slicing_parameters = PrintObject :: slicing_parameters ( * m_config , * m_model_object , m_object_max_z );
2019-01-21 17:02:16 +01:00
}
}
2019-01-24 11:30:29 +01:00
float GLCanvas3D :: LayersEditing :: thickness_bar_width ( const GLCanvas3D & canvas )
{
2019-02-04 21:41:10 +01:00
return
2019-01-24 11:30:29 +01:00
#if ENABLE_RETINA_GL
2019-02-04 21:41:10 +01:00
canvas . get_canvas_size (). get_scale_factor ()
2019-01-24 11:30:29 +01:00
#else
2019-02-04 21:41:10 +01:00
canvas . get_wxglcanvas () -> GetContentScaleFactor ()
2019-01-24 11:30:29 +01:00
#endif
2019-02-04 21:41:10 +01:00
* THICKNESS_BAR_WIDTH ;
2019-01-24 11:30:29 +01:00
}
2018-06-01 15:54:41 +02:00
const Point GLCanvas3D :: Mouse :: Drag :: Invalid_2D_Point ( INT_MAX , INT_MAX );
2018-08-21 17:43:05 +02:00
const Vec3d GLCanvas3D :: Mouse :: Drag :: Invalid_3D_Point ( DBL_MAX , DBL_MAX , DBL_MAX );
2019-01-14 09:29:17 +01:00
const int GLCanvas3D :: Mouse :: Drag :: MoveThresholdPx = 5 ;
2018-06-01 15:54:41 +02:00
GLCanvas3D :: Mouse :: Drag :: Drag ()
: start_position_2D ( Invalid_2D_Point )
, start_position_3D ( Invalid_3D_Point )
2018-06-21 08:37:04 +02:00
, move_volume_idx ( - 1 )
2019-01-14 09:29:17 +01:00
, move_requires_threshold ( false )
, move_start_threshold_position_2D ( Invalid_2D_Point )
2018-06-01 15:54:41 +02:00
{
}
2018-05-23 13:56:54 +02:00
GLCanvas3D :: Mouse :: Mouse ()
2018-06-01 15:54:41 +02:00
: dragging ( false )
, position ( DBL_MAX , DBL_MAX )
2018-11-15 11:38:40 +01:00
, scene_position ( DBL_MAX , DBL_MAX , DBL_MAX )
2019-04-26 14:07:46 +02:00
, ignore_left_up ( false )
2018-05-23 13:56:54 +02:00
{
}
2019-03-01 11:00:34 +01:00
const unsigned char GLCanvas3D :: WarningTexture :: Background_Color [ 3 ] = { 120 , 120 , 120 }; //{ 9, 91, 134 };
2018-07-19 13:18:19 +02:00
const unsigned char GLCanvas3D :: WarningTexture :: Opacity = 255 ;
2018-07-31 14:20:16 +02:00
GLCanvas3D :: WarningTexture :: WarningTexture ()
: GUI :: GLTexture ()
, m_original_width ( 0 )
, m_original_height ( 0 )
{
}
2019-02-20 12:09:45 +01:00
void GLCanvas3D :: WarningTexture :: activate ( WarningTexture :: Warning warning , bool state , const GLCanvas3D & canvas )
{
2020-08-25 16:28:03 +02:00
// Since we have NotificationsManager.hpp the warning textures are no loger needed.
// However i have left the infrastructure here and only commented the rendering.
// The plater warning / error notifications are added and closed from here.
std :: string text ;
bool error = false ;
switch ( warning ) {
case ObjectOutside : text = L ( "An object outside the print area was detected." ); break ;
2020-09-21 13:47:18 +02:00
case ToolpathOutside : text = L ( "A toolpath outside the print area was detected." ); error = true ; break ;
case SlaSupportsOutside : text = L ( "SLA supports outside the print area were detected." ); error = true ; break ;
2020-08-25 16:28:03 +02:00
case SomethingNotShown : text = L ( "Some objects are not visible." ); break ;
case ObjectClashed :
text = L ( "An object outside the print area was detected. \n "
"Resolve the current problem to continue slicing." );
error = true ;
break ;
}
2020-10-14 16:48:56 +02:00
auto & notification_manager = * wxGetApp (). plater () -> get_notification_manager ();
if ( state ) {
2020-08-25 16:28:03 +02:00
if ( error )
2020-11-30 13:45:17 +01:00
notification_manager . push_plater_error_notification ( text );
2020-08-25 16:28:03 +02:00
else
2020-11-30 13:45:17 +01:00
notification_manager . push_plater_warning_notification ( text );
2020-08-25 16:28:03 +02:00
} else {
if ( error )
2020-10-14 16:48:56 +02:00
notification_manager . close_plater_error_notification ( text );
2020-08-25 16:28:03 +02:00
else
2020-10-14 16:48:56 +02:00
notification_manager . close_plater_warning_notification ( text );
2020-08-25 16:28:03 +02:00
}
/*
2019-02-20 12:09:45 +01:00
auto it = std::find(m_warnings.begin(), m_warnings.end(), warning);
if (state) {
if (it != m_warnings.end()) // this warning is already set to be shown
return;
2020-08-25 16:28:03 +02:00
m_warnings.push_back(warning);
2019-02-20 12:09:45 +01:00
std::sort(m_warnings.begin(), m_warnings.end());
}
else {
if (it == m_warnings.end()) // deactivating something that is not active is an easy task
return;
m_warnings.erase(it);
2020-08-25 16:28:03 +02:00
if (m_warnings.empty()) { // nothing remains to be shown
2019-02-20 12:09:45 +01:00
reset();
2019-04-16 12:11:48 +02:00
m_msg_text = "";// save information for rescaling
2019-02-20 12:09:45 +01:00
return;
2020-08-25 16:28:03 +02:00
}
2019-02-20 12:09:45 +01:00
}
2020-08-25 16:28:03 +02:00
2019-02-20 12:09:45 +01:00
// Look at the end of our vector and generate proper texture.
std::string text;
2019-03-01 11:00:34 +01:00
bool red_colored = false;
2019-02-20 12:09:45 +01:00
switch (m_warnings.back()) {
2019-05-10 11:44:21 +02:00
case ObjectOutside : text = L("An object outside the print area was detected"); break;
case ToolpathOutside : text = L("A toolpath outside the print area was detected"); break;
2019-05-10 11:47:11 +02:00
case SlaSupportsOutside : text = L("SLA supports outside the print area were detected"); break;
2020-08-25 16:28:03 +02:00
case SomethingNotShown : text = L("Some objects are not visible when editing supports"); break;
2019-03-01 11:00:34 +01:00
case ObjectClashed: {
2019-05-10 11:44:21 +02:00
text = L("An object outside the print area was detected\n"
"Resolve the current problem to continue slicing");
2019-03-01 11:00:34 +01:00
red_colored = true;
break;
}
2019-02-20 12:09:45 +01:00
}
2019-05-28 12:53:16 +02:00
generate(text, canvas, true, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...)
2019-04-16 12:11:48 +02:00
// save information for rescaling
m_msg_text = text;
m_is_colored_red = red_colored;
2020-08-03 15:36:55 +02:00
*/
2019-02-20 12:09:45 +01:00
}
2019-03-21 14:33:55 +01:00
#ifdef __WXMSW__
static bool is_font_cleartype ( const wxFont & font )
{
// Native font description: on MSW, it is a version number plus the content of LOGFONT, separated by semicolon.
wxString font_desc = font . GetNativeFontInfoDesc ();
// Find the quality field.
wxString sep ( ";" );
size_t startpos = 0 ;
for ( size_t i = 0 ; i < 12 ; ++ i )
startpos = font_desc . find ( sep , startpos + 1 );
++ startpos ;
size_t endpos = font_desc . find ( sep , startpos );
int quality = wxAtoi ( font_desc ( startpos , endpos - startpos ));
return quality == CLEARTYPE_QUALITY ;
}
// ClearType produces renders, which are difficult to convert into an alpha blended OpenGL texture.
// Therefore it is better to disable it, though Vojtech found out, that the font returned with ClearType
// disabled is signifcantly thicker than the default ClearType font.
// This function modifies the font provided.
static void msw_disable_cleartype ( wxFont & font )
{
// Native font description: on MSW, it is a version number plus the content of LOGFONT, separated by semicolon.
wxString font_desc = font . GetNativeFontInfoDesc ();
// Find the quality field.
wxString sep ( ";" );
size_t startpos_weight = 0 ;
for ( size_t i = 0 ; i < 5 ; ++ i )
startpos_weight = font_desc . find ( sep , startpos_weight + 1 );
++ startpos_weight ;
size_t endpos_weight = font_desc . find ( sep , startpos_weight );
// Parse the weight field.
2020-02-27 17:01:50 +01:00
unsigned int weight = wxAtoi ( font_desc ( startpos_weight , endpos_weight - startpos_weight ));
2019-03-21 14:33:55 +01:00
size_t startpos = endpos_weight ;
for ( size_t i = 0 ; i < 6 ; ++ i )
startpos = font_desc . find ( sep , startpos + 1 );
++ startpos ;
size_t endpos = font_desc . find ( sep , startpos );
int quality = wxAtoi ( font_desc ( startpos , endpos - startpos ));
if ( quality == CLEARTYPE_QUALITY ) {
// Replace the weight with a smaller value to compensate the weight of non ClearType font.
wxString sweight = std :: to_string ( weight * 2 / 4 );
size_t len_weight = endpos_weight - startpos_weight ;
wxString squality = std :: to_string ( ANTIALIASED_QUALITY );
font_desc . replace ( startpos_weight , len_weight , sweight );
font_desc . replace ( startpos + sweight . size () - len_weight , endpos - startpos , squality );
font . SetNativeFontInfo ( font_desc );
wxString font_desc2 = font . GetNativeFontInfoDesc ();
}
wxString font_desc2 = font . GetNativeFontInfoDesc ();
}
#endif /* __WXMSW__ */
2019-05-28 12:53:16 +02:00
bool GLCanvas3D :: WarningTexture :: generate ( const std :: string & msg_utf8 , const GLCanvas3D & canvas , bool compress , bool red_colored /* = false*/ )
2018-07-19 13:18:19 +02:00
{
reset ();
2019-03-21 14:33:55 +01:00
if ( msg_utf8 . empty ())
2018-07-19 13:18:19 +02:00
return false ;
2019-05-09 19:24:21 +02:00
wxString msg = _ ( msg_utf8 );
2019-03-21 14:33:55 +01:00
2018-07-19 13:18:19 +02:00
wxMemoryDC memDC ;
2019-04-26 13:07:31 +02:00
#ifdef __WXMSW__
// set scaled application normal font as default font
wxFont font = wxGetApp (). normal_font ();
#else
2018-07-19 13:18:19 +02:00
// select default font
2019-01-24 11:30:29 +01:00
const float scale = canvas . get_canvas_size (). get_scale_factor ();
2020-03-25 23:52:47 +01:00
#if ENABLE_RETINA_GL
// For non-visible or non-created window getBackingScaleFactor function return 0.0 value.
// And using of the zero scale causes a crash, when we trying to draw text to the (0,0) rectangle
// https://github.com/prusa3d/PrusaSlicer/issues/3916
if ( scale <= 0.0f )
return false ;
#endif
2019-04-26 13:07:31 +02:00
wxFont font = wxSystemSettings :: GetFont ( wxSYS_DEFAULT_GUI_FONT ). Scale ( scale );
#endif
2019-04-16 12:11:48 +02:00
2018-07-31 14:20:16 +02:00
font . MakeLarger ();
2018-07-31 15:32:16 +02:00
font . MakeBold ();
2018-07-31 14:20:16 +02:00
memDC . SetFont ( font );
2018-07-19 13:18:19 +02:00
// calculates texture size
wxCoord w , h ;
2019-03-01 11:00:34 +01:00
memDC . GetMultiLineTextExtent ( msg , & w , & h );
2018-07-31 14:20:16 +02:00
m_original_width = ( int ) w ;
m_original_height = ( int ) h ;
2019-03-21 14:33:55 +01:00
m_width = ( int ) next_highest_power_of_2 (( uint32_t ) w );
m_height = ( int ) next_highest_power_of_2 (( uint32_t ) h );
2018-07-19 13:18:19 +02:00
// generates bitmap
wxBitmap bitmap ( m_width , m_height );
memDC . SelectObject ( bitmap );
2019-03-21 14:33:55 +01:00
memDC . SetBackground ( wxBrush ( * wxBLACK ));
2018-07-19 13:18:19 +02:00
memDC . Clear ();
// draw message
2019-03-21 14:33:55 +01:00
memDC . SetTextForeground ( * wxRED );
memDC . DrawLabel ( msg , wxRect ( 0 , 0 , m_original_width , m_original_height ), wxALIGN_CENTER );
2018-07-19 13:18:19 +02:00
memDC . SelectObject ( wxNullBitmap );
// Convert the bitmap into a linear data ready to be loaded into the GPU.
wxImage image = bitmap . ConvertToImage ();
// prepare buffer
std :: vector < unsigned char > data ( 4 * m_width * m_height , 0 );
2019-03-21 14:33:55 +01:00
const unsigned char * src = image . GetData ();
2020-11-19 12:29:19 +01:00
for ( int h = 0 ; h < m_height ; ++ h ) {
2019-03-21 14:33:55 +01:00
unsigned char * dst = data . data () + 4 * h * m_width ;
2020-11-19 12:29:19 +01:00
for ( int w = 0 ; w < m_width ; ++ w ) {
2019-03-21 14:33:55 +01:00
* dst ++ = 255 ;
if ( red_colored ) {
* dst ++ = 72 ; // 204
* dst ++ = 65 ; // 204
} else {
* dst ++ = 255 ;
* dst ++ = 255 ;
}
* dst ++ = ( unsigned char ) std :: min < int > ( 255 , * src );
src += 3 ;
2018-07-19 13:18:19 +02:00
}
}
// sends buffer to gpu
2019-03-27 14:42:09 +01:00
glsafe ( :: glPixelStorei ( GL_UNPACK_ALIGNMENT , 1 ));
glsafe ( :: glGenTextures ( 1 , & m_id ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , ( GLuint ) m_id ));
2019-05-28 12:53:16 +02:00
if ( compress && GLEW_EXT_texture_compression_s3tc )
2019-05-21 14:19:03 +02:00
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 0 , GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , ( GLsizei ) m_width , ( GLsizei ) m_height , 0 , GL_RGBA , GL_UNSIGNED_BYTE , ( const void * ) data . data ()));
else
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 0 , GL_RGBA , ( GLsizei ) m_width , ( GLsizei ) m_height , 0 , GL_RGBA , GL_UNSIGNED_BYTE , ( const void * ) data . data ()));
2019-03-27 14:42:09 +01:00
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_LINEAR ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAX_LEVEL , 0 ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , 0 ));
2018-07-19 13:18:19 +02:00
return true ;
}
2018-07-31 14:20:16 +02:00
void GLCanvas3D :: WarningTexture :: render ( const GLCanvas3D & canvas ) const
{
2019-02-20 12:09:45 +01:00
if ( m_warnings . empty ())
return ;
2020-11-19 12:29:19 +01:00
if ( m_id > 0 && m_original_width > 0 && m_original_height > 0 && m_width > 0 && m_height > 0 ) {
2018-07-31 14:20:16 +02:00
const Size & cnv_size = canvas . get_canvas_size ();
2020-03-02 10:58:46 +01:00
float inv_zoom = ( float ) wxGetApp (). plater () -> get_camera (). get_inv_zoom ();
2018-07-31 14:20:16 +02:00
float left = ( - 0.5f * ( float ) m_original_width ) * inv_zoom ;
float top = ( - 0.5f * ( float ) cnv_size . get_height () + ( float ) m_original_height + 2.0f ) * inv_zoom ;
float right = left + ( float ) m_original_width * inv_zoom ;
float bottom = top - ( float ) m_original_height * inv_zoom ;
float uv_left = 0.0f ;
float uv_top = 0.0f ;
float uv_right = ( float ) m_original_width / ( float ) m_width ;
float uv_bottom = ( float ) m_original_height / ( float ) m_height ;
GLTexture :: Quad_UVs uvs ;
uvs . left_top = { uv_left , uv_top };
uvs . left_bottom = { uv_left , uv_bottom };
uvs . right_bottom = { uv_right , uv_bottom };
uvs . right_top = { uv_right , uv_top };
GLTexture :: render_sub_texture ( m_id , left , right , bottom , top , uvs );
}
}
2019-04-26 13:07:31 +02:00
void GLCanvas3D :: WarningTexture :: msw_rescale ( const GLCanvas3D & canvas )
2019-04-16 12:11:48 +02:00
{
if ( m_msg_text . empty ())
return ;
2019-05-28 12:53:16 +02:00
generate ( m_msg_text , canvas , true , m_is_colored_red );
2019-04-16 12:11:48 +02:00
}
2020-02-06 15:19:53 +01:00
void GLCanvas3D :: Labels :: render ( const std :: vector < const ModelInstance *>& sorted_instances ) const
2020-01-28 13:15:21 +01:00
{
if ( ! m_enabled || ! is_shown ())
return ;
2020-03-02 10:58:46 +01:00
const Camera & camera = wxGetApp (). plater () -> get_camera ();
2020-01-28 15:57:02 +01:00
const Model * model = m_canvas . get_model ();
2020-01-28 13:15:21 +01:00
if ( model == nullptr )
return ;
Transform3d world_to_eye = camera . get_view_matrix ();
Transform3d world_to_screen = camera . get_projection_matrix () * world_to_eye ;
const std :: array < int , 4 >& viewport = camera . get_viewport ();
struct Owner
{
int obj_idx ;
int inst_idx ;
2020-02-03 11:44:26 +01:00
size_t model_instance_id ;
2020-01-28 13:15:21 +01:00
BoundingBoxf3 world_box ;
double eye_center_z ;
2020-02-03 11:44:26 +01:00
std :: string title ;
std :: string label ;
std :: string print_order ;
2020-01-28 13:15:21 +01:00
bool selected ;
};
2020-02-03 11:44:26 +01:00
// collect owners world bounding boxes and data from volumes
2020-01-28 13:15:21 +01:00
std :: vector < Owner > owners ;
2020-01-28 15:57:02 +01:00
const GLVolumeCollection & volumes = m_canvas . get_volumes ();
2020-02-03 11:44:26 +01:00
for ( const GLVolume * volume : volumes . volumes ) {
2020-01-28 13:15:21 +01:00
int obj_idx = volume -> object_idx ();
2020-02-03 11:44:26 +01:00
if ( 0 <= obj_idx && obj_idx < ( int ) model -> objects . size ()) {
2020-01-28 13:15:21 +01:00
int inst_idx = volume -> instance_idx ();
std :: vector < Owner >:: iterator it = std :: find_if ( owners . begin (), owners . end (), [ obj_idx , inst_idx ]( const Owner & owner ) {
return ( owner . obj_idx == obj_idx ) && ( owner . inst_idx == inst_idx );
});
2020-02-03 11:44:26 +01:00
if ( it != owners . end ()) {
2020-01-28 13:15:21 +01:00
it -> world_box . merge ( volume -> transformed_bounding_box ());
it -> selected &= volume -> selected ;
2020-02-03 11:44:26 +01:00
} else {
const ModelObject * model_object = model -> objects [ obj_idx ];
2020-01-28 13:15:21 +01:00
Owner owner ;
owner . obj_idx = obj_idx ;
owner . inst_idx = inst_idx ;
2020-02-03 11:44:26 +01:00
owner . model_instance_id = model_object -> instances [ inst_idx ] -> id (). id ;
2020-01-28 13:15:21 +01:00
owner . world_box = volume -> transformed_bounding_box ();
2020-02-03 11:44:26 +01:00
owner . title = "object" + std :: to_string ( obj_idx ) + "_inst##" + std :: to_string ( inst_idx );
owner . label = model_object -> name ;
if ( model_object -> instances . size () > 1 )
owner . label += " (" + std :: to_string ( inst_idx + 1 ) + ")" ;
2020-01-28 13:15:21 +01:00
owner . selected = volume -> selected ;
2020-03-13 15:57:07 +01:00
owners . emplace_back ( owner );
2020-01-28 13:15:21 +01:00
}
}
}
2020-02-03 11:44:26 +01:00
// updates print order strings
if ( sorted_instances . size () > 1 ) {
2020-03-03 14:52:16 +01:00
for ( size_t i = 0 ; i < sorted_instances . size (); ++ i ) {
2020-02-06 15:19:53 +01:00
size_t id = sorted_instances [ i ] -> id (). id ;
2020-02-03 11:44:26 +01:00
std :: vector < Owner >:: iterator it = std :: find_if ( owners . begin (), owners . end (), [ id ]( const Owner & owner ) {
return owner . model_instance_id == id ;
});
if ( it != owners . end ())
2020-02-21 12:50:26 +01:00
it -> print_order = std :: string (( _ ( L ( "Seq." ))). ToUTF8 ()) + "#: " + std :: to_string ( i + 1 );
2020-02-03 11:44:26 +01:00
}
}
2020-01-28 13:15:21 +01:00
// calculate eye bounding boxes center zs
2020-02-03 11:44:26 +01:00
for ( Owner & owner : owners ) {
2020-01-28 13:15:21 +01:00
owner . eye_center_z = ( world_to_eye * owner . world_box . center ())( 2 );
}
// sort owners by center eye zs and selection
std :: sort ( owners . begin (), owners . end (), []( const Owner & owner1 , const Owner & owner2 ) {
if ( ! owner1 . selected && owner2 . selected )
return true ;
else if ( owner1 . selected && ! owner2 . selected )
return false ;
else
return ( owner1 . eye_center_z < owner2 . eye_center_z );
});
ImGuiWrapper & imgui = * wxGetApp (). imgui ();
// render info windows
2020-02-03 11:44:26 +01:00
for ( const Owner & owner : owners ) {
2020-01-28 13:15:21 +01:00
Vec3d screen_box_center = world_to_screen * owner . world_box . center ();
float x = 0.0f ;
float y = 0.0f ;
2020-02-03 11:44:26 +01:00
if ( camera . get_type () == Camera :: Perspective ) {
2020-01-28 13:15:21 +01:00
x = ( 0.5f + 0.001f * 0.5f * ( float ) screen_box_center ( 0 )) * viewport [ 2 ];
y = ( 0.5f - 0.001f * 0.5f * ( float ) screen_box_center ( 1 )) * viewport [ 3 ];
2020-02-03 11:44:26 +01:00
} else {
2020-01-28 13:15:21 +01:00
x = ( 0.5f + 0.5f * ( float ) screen_box_center ( 0 )) * viewport [ 2 ];
y = ( 0.5f - 0.5f * ( float ) screen_box_center ( 1 )) * viewport [ 3 ];
}
2020-02-03 11:44:26 +01:00
if ( x < 0.0f || viewport [ 2 ] < x || y < 0.0f || viewport [ 3 ] < y )
2020-01-28 13:15:21 +01:00
continue ;
ImGui :: PushStyleVar ( ImGuiStyleVar_WindowBorderSize , owner . selected ? 3.0f : 1.5f );
ImGui :: PushStyleVar ( ImGuiStyleVar_WindowRounding , 0.0f );
ImGui :: PushStyleColor ( ImGuiCol_Border , owner . selected ? ImVec4 ( 0.757f , 0.404f , 0.216f , 1.0f ) : ImVec4 ( 0.75f , 0.75f , 0.75f , 1.0f ));
imgui . set_next_window_pos ( x , y , ImGuiCond_Always , 0.5f , 0.5f );
2020-02-03 13:24:11 +01:00
imgui . begin ( owner . title , ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove );
2020-01-28 13:15:21 +01:00
ImGui :: BringWindowToDisplayFront ( ImGui :: GetCurrentWindow ());
float win_w = ImGui :: GetWindowWidth ();
2020-02-03 11:44:26 +01:00
float label_len = imgui . calc_text_size ( owner . label ). x ;
ImGui :: SetCursorPosX ( 0.5f * ( win_w - label_len ));
2020-01-28 13:15:21 +01:00
ImGui :: AlignTextToFramePadding ();
2020-02-03 11:44:26 +01:00
imgui . text ( owner . label );
2020-11-19 12:29:19 +01:00
if ( ! owner . print_order . empty ()) {
2020-02-03 11:44:26 +01:00
ImGui :: Separator ();
float po_len = imgui . calc_text_size ( owner . print_order ). x ;
ImGui :: SetCursorPosX ( 0.5f * ( win_w - po_len ));
ImGui :: AlignTextToFramePadding ();
imgui . text ( owner . print_order );
}
2020-01-28 15:57:02 +01:00
// force re-render while the windows gets to its final size (it takes several frames)
2020-02-06 12:06:39 +01:00
if ( ImGui :: GetWindowContentRegionWidth () + 2.0f * ImGui :: GetStyle (). WindowPadding . x != ImGui :: CalcWindowExpectedSize ( ImGui :: GetCurrentWindow ()). x )
2020-01-28 15:57:02 +01:00
m_canvas . request_extra_frame ();
2020-01-28 13:15:21 +01:00
imgui . end ();
ImGui :: PopStyleColor ();
ImGui :: PopStyleVar ( 2 );
}
}
2020-03-17 14:35:56 +01:00
void GLCanvas3D :: Tooltip :: set_text ( const std :: string & text )
2020-03-17 13:01:38 +01:00
{
2020-03-18 13:23:07 +01:00
// If the mouse is inside an ImGUI dialog, then the tooltip is suppressed.
const std :: string & new_text = m_in_imgui ? std :: string () : text ;
2020-11-19 12:29:19 +01:00
if ( m_text != new_text ) {
2020-03-17 14:35:56 +01:00
if ( m_text . empty ())
2020-03-17 15:47:49 +01:00
m_start_time = std :: chrono :: steady_clock :: now ();
2020-03-17 14:35:56 +01:00
2020-03-18 13:23:07 +01:00
m_text = new_text ;
2020-03-17 14:35:56 +01:00
}
}
void GLCanvas3D :: Tooltip :: render ( const Vec2d & mouse_position , GLCanvas3D & canvas ) const
{
2020-03-18 14:13:50 +01:00
static ImVec2 size ( 0.0f , 0.0f );
auto validate_position = []( const Vec2d & position , const GLCanvas3D & canvas , const ImVec2 & wnd_size ) {
Size cnv_size = canvas . get_canvas_size ();
float x = std :: clamp (( float ) position ( 0 ), 0.0f , ( float ) cnv_size . get_width () - wnd_size . x );
float y = std :: clamp (( float ) position ( 1 ) + 16 , 0.0f , ( float ) cnv_size . get_height () - wnd_size . y );
return Vec2f ( x , y );
};
2020-03-17 15:47:49 +01:00
if ( m_text . empty ())
2020-03-17 14:35:56 +01:00
return ;
2020-03-17 15:47:49 +01:00
// draw the tooltip as hidden until the delay is expired
2020-03-23 12:41:16 +01:00
// use a value of alpha slightly different from 0.0f because newer imgui does not calculate properly the window size if alpha == 0.0f
float alpha = ( std :: chrono :: duration_cast < std :: chrono :: milliseconds > ( std :: chrono :: steady_clock :: now () - m_start_time ). count () < 500 ) ? 0.01f : 1.0f ;
2020-03-17 13:01:38 +01:00
2020-03-18 14:13:50 +01:00
Vec2f position = validate_position ( mouse_position , canvas , size );
2020-03-17 13:01:38 +01:00
ImGuiWrapper & imgui = * wxGetApp (). imgui ();
ImGui :: PushStyleVar ( ImGuiStyleVar_WindowRounding , 0.0f );
2020-03-17 15:47:49 +01:00
ImGui :: PushStyleVar ( ImGuiStyleVar_Alpha , alpha );
2020-03-18 14:13:50 +01:00
imgui . set_next_window_pos ( position ( 0 ), position ( 1 ), ImGuiCond_Always , 0.0f , 0.0f );
2020-03-17 15:47:49 +01:00
2020-11-05 22:17:49 +01:00
imgui . begin ( wxString ( "canvas_tooltip" ), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing );
2020-03-17 14:35:56 +01:00
ImGui :: BringWindowToDisplayFront ( ImGui :: GetCurrentWindow ());
2020-03-18 13:43:39 +01:00
ImGui :: TextUnformatted ( m_text . c_str ());
2020-03-17 14:35:56 +01:00
2020-03-17 15:47:49 +01:00
// force re-render while the windows gets to its final size (it may take several frames) or while hidden
2020-03-23 12:41:16 +01:00
if ( alpha < 1.0f || ImGui :: GetWindowContentRegionWidth () + 2.0f * ImGui :: GetStyle (). WindowPadding . x != ImGui :: CalcWindowExpectedSize ( ImGui :: GetCurrentWindow ()). x )
2020-03-17 14:35:56 +01:00
canvas . request_extra_frame ();
2020-03-18 14:13:50 +01:00
size = ImGui :: GetWindowSize ();
2020-03-17 13:01:38 +01:00
imgui . end ();
2020-03-17 15:47:49 +01:00
ImGui :: PopStyleVar ( 2 );
2020-03-17 13:01:38 +01:00
}
2020-10-02 10:26:11 +02:00
float GLCanvas3D :: Slope :: s_window_width ;
2018-10-25 12:10:35 +02:00
wxDEFINE_EVENT ( EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS , SimpleEvent );
2018-10-16 16:04:19 +02:00
wxDEFINE_EVENT ( EVT_GLCANVAS_OBJECT_SELECT , SimpleEvent );
2019-09-20 09:57:27 +02:00
wxDEFINE_EVENT ( EVT_GLCANVAS_RIGHT_CLICK , RBtnEvent );
2018-10-03 11:34:39 +02:00
wxDEFINE_EVENT ( EVT_GLCANVAS_REMOVE_OBJECT , SimpleEvent );
wxDEFINE_EVENT ( EVT_GLCANVAS_ARRANGE , SimpleEvent );
2019-02-03 14:06:13 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_SELECT_ALL , SimpleEvent );
2018-12-19 14:01:13 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_QUESTION_MARK , SimpleEvent );
2018-10-18 15:09:41 +02:00
wxDEFINE_EVENT ( EVT_GLCANVAS_INCREASE_INSTANCES , Event < int > );
2018-10-09 17:14:59 +02:00
wxDEFINE_EVENT ( EVT_GLCANVAS_INSTANCE_MOVED , SimpleEvent );
2019-01-03 11:24:03 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_INSTANCE_ROTATED , SimpleEvent );
wxDEFINE_EVENT ( EVT_GLCANVAS_INSTANCE_SCALED , SimpleEvent );
2020-02-04 13:03:28 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_FORCE_UPDATE , SimpleEvent );
2018-10-03 11:34:39 +02:00
wxDEFINE_EVENT ( EVT_GLCANVAS_WIPETOWER_MOVED , Vec3dEvent );
2019-04-26 15:34:26 +02:00
wxDEFINE_EVENT ( EVT_GLCANVAS_WIPETOWER_ROTATED , Vec3dEvent );
2018-10-03 11:34:39 +02:00
wxDEFINE_EVENT ( EVT_GLCANVAS_ENABLE_ACTION_BUTTONS , Event < bool > );
wxDEFINE_EVENT ( EVT_GLCANVAS_UPDATE_GEOMETRY , Vec3dsEvent < 2 > );
2018-11-23 12:47:32 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED , SimpleEvent );
2019-02-19 15:15:27 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_UPDATE_BED_SHAPE , SimpleEvent );
2019-02-21 11:54:18 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_TAB , SimpleEvent );
2019-03-27 14:16:38 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_RESETGIZMOS , SimpleEvent );
2020-12-01 08:33:06 +01:00
#if ENABLE_ARROW_KEYS_WITH_SLIDERS
wxDEFINE_EVENT ( EVT_GLCANVAS_MOVE_SLIDERS , wxKeyEvent );
#else
2020-05-15 12:25:38 +02:00
wxDEFINE_EVENT ( EVT_GLCANVAS_MOVE_LAYERS_SLIDER , wxKeyEvent );
2020-12-01 08:33:06 +01:00
#endif // ENABLE_ARROW_KEYS_WITH_SLIDERS
2019-06-18 11:40:26 +02:00
wxDEFINE_EVENT ( EVT_GLCANVAS_EDIT_COLOR_CHANGE , wxKeyEvent );
2020-11-04 16:26:25 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_JUMP_TO , wxKeyEvent );
2019-07-04 10:45:41 +02:00
wxDEFINE_EVENT ( EVT_GLCANVAS_UNDO , SimpleEvent );
wxDEFINE_EVENT ( EVT_GLCANVAS_REDO , SimpleEvent );
2020-03-28 19:39:24 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_COLLAPSE_SIDEBAR , SimpleEvent );
2019-11-07 15:55:45 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE , SimpleEvent );
2019-11-14 10:22:48 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE , Event < float > );
2019-11-20 14:06:30 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE , HeightProfileSmoothEvent );
2020-01-31 12:15:04 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_RELOAD_FROM_DISK , SimpleEvent );
2020-12-15 16:03:20 +01:00
wxDEFINE_EVENT ( EVT_GLCANVAS_RENDER_TIMER , wxTimerEvent /*RenderTimerEvent*/ );
2018-10-03 11:34:39 +02:00
2019-10-31 16:40:38 +01:00
const double GLCanvas3D :: DefaultCameraZoomToBoxMarginFactor = 1.25 ;
2020-12-02 14:04:39 +01:00
void GLCanvas3D :: load_arrange_settings ()
2020-11-18 14:38:46 +01:00
{
2020-12-02 14:04:39 +01:00
std :: string dist_fff_str =
wxGetApp (). app_config -> get ( "arrange" , "min_object_distance_fff" );
2020-11-18 14:38:46 +01:00
2020-12-02 14:04:39 +01:00
std :: string dist_fff_seq_print_str =
wxGetApp (). app_config -> get ( "arrange" , "min_object_distance_fff_seq_print" );
2020-11-18 14:38:46 +01:00
2020-12-02 10:28:13 +01:00
std :: string dist_sla_str =
wxGetApp (). app_config -> get ( "arrange" , "min_object_distance_sla" );
2020-11-18 14:38:46 +01:00
2020-12-02 14:04:39 +01:00
std :: string en_rot_fff_str =
wxGetApp (). app_config -> get ( "arrange" , "enable_rotation_fff" );
2020-11-18 14:38:46 +01:00
2020-12-02 14:04:39 +01:00
std :: string en_rot_fff_seqp_str =
wxGetApp (). app_config -> get ( "arrange" , "enable_rotation_fff_seq_print" );
2020-11-18 14:38:46 +01:00
2020-12-02 14:04:39 +01:00
std :: string en_rot_sla_str =
wxGetApp (). app_config -> get ( "arrange" , "enable_rotation_sla" );
if ( ! dist_fff_str . empty ())
m_arrange_settings_fff . distance = std :: stof ( dist_fff_str );
if ( ! dist_fff_seq_print_str . empty ())
m_arrange_settings_fff_seq_print . distance = std :: stof ( dist_fff_seq_print_str );
2020-12-01 14:07:34 +01:00
2020-12-02 10:28:13 +01:00
if ( ! dist_sla_str . empty ())
2020-12-02 14:04:39 +01:00
m_arrange_settings_sla . distance = std :: stof ( dist_sla_str );
2020-12-02 10:28:13 +01:00
2020-12-02 14:04:39 +01:00
if ( ! en_rot_fff_str . empty ())
m_arrange_settings_fff . enable_rotation = ( en_rot_fff_str == "1" || en_rot_fff_str == "yes" );
2020-11-18 14:38:46 +01:00
2020-12-02 14:04:39 +01:00
if ( ! en_rot_fff_seqp_str . empty ())
m_arrange_settings_fff_seq_print . enable_rotation = ( en_rot_fff_seqp_str == "1" || en_rot_fff_seqp_str == "yes" );
if ( ! en_rot_sla_str . empty ())
m_arrange_settings_sla . enable_rotation = ( en_rot_sla_str == "1" || en_rot_sla_str == "yes" );
}
PrinterTechnology GLCanvas3D :: current_printer_technology () const
{
return m_process -> current_printer_technology ();
2020-11-18 14:38:46 +01:00
}
2020-03-02 10:58:46 +01:00
GLCanvas3D :: GLCanvas3D ( wxGLCanvas * canvas )
2018-05-09 10:47:04 +02:00
: m_canvas ( canvas )
2018-06-27 11:31:11 +02:00
, m_context ( nullptr )
2019-01-24 11:30:29 +01:00
#if ENABLE_RETINA_GL
, m_retina_helper ( nullptr )
#endif
2018-11-27 16:55:54 +01:00
, m_in_render ( false )
2020-06-05 12:33:09 +02:00
, m_main_toolbar ( GLToolbar :: Normal , "Main" )
, m_undoredo_toolbar ( GLToolbar :: Normal , "Undo_Redo" )
2019-07-17 12:43:27 +02:00
, m_gizmos ( * this )
2018-11-27 14:50:57 +01:00
, m_use_clipping_planes ( false )
2018-12-18 10:40:53 +01:00
, m_sidebar_field ( "" )
2019-12-12 13:38:08 +01:00
, m_extra_frame_requested ( false )
2018-05-23 11:14:49 +02:00
, m_config ( nullptr )
2018-11-22 15:29:59 +01:00
, m_process ( nullptr )
2018-06-07 11:18:28 +02:00
, m_model ( nullptr )
2018-05-09 10:47:04 +02:00
, m_dirty ( true )
2018-06-05 12:24:26 +02:00
, m_initialized ( false )
2018-05-14 14:14:19 +02:00
, m_apply_zoom_to_volumes_filter ( false )
2018-05-22 09:02:42 +02:00
, m_picking_enabled ( false )
2018-05-31 13:51:50 +02:00
, m_moving_enabled ( false )
2018-07-27 09:38:39 +02:00
, m_dynamic_background_enabled ( false )
2018-05-23 15:35:11 +02:00
, m_multisample_allowed ( false )
2018-11-26 10:41:16 +01:00
, m_moving ( false )
2019-02-21 18:31:01 +01:00
, m_tab_down ( false )
2019-04-24 15:43:52 +02:00
, m_cursor_type ( Standard )
2018-06-06 10:16:58 +02:00
, m_color_by ( "volume" )
2018-06-08 09:40:00 +02:00
, m_reload_delayed ( false )
2019-06-27 11:25:04 +02:00
#if ENABLE_RENDER_PICKING_PASS
, m_show_picking_texture ( false )
#endif // ENABLE_RENDER_PICKING_PASS
2019-02-01 16:12:00 +01:00
, m_render_sla_auxiliaries ( true )
2020-01-28 15:57:02 +01:00
, m_labels ( * this )
2020-03-13 15:09:07 +01:00
, m_slope ( * this , m_volumes )
2018-05-09 10:47:04 +02:00
{
2019-01-26 13:10:58 +01:00
if ( m_canvas != nullptr ) {
2018-10-25 09:35:08 +02:00
m_timer . SetOwner ( m_canvas );
2020-12-15 16:03:20 +01:00
m_render_timer . SetOwner ( m_canvas );
2019-01-24 11:30:29 +01:00
#if ENABLE_RETINA_GL
m_retina_helper . reset ( new RetinaHelper ( canvas ));
2020-03-02 12:45:04 +01:00
#endif // ENABLE_RETINA_GL
2018-06-27 11:31:11 +02:00
}
2018-10-08 14:02:12 +02:00
2020-12-02 14:04:39 +01:00
load_arrange_settings ();
2020-11-18 14:38:46 +01:00
2018-10-08 14:02:12 +02:00
m_selection . set_volumes ( & m_volumes . volumes );
2018-05-09 10:47:04 +02:00
}
2018-05-15 09:50:01 +02:00
GLCanvas3D ::~ GLCanvas3D ()
{
2018-06-11 15:13:13 +02:00
reset_volumes ();
2018-10-03 11:34:39 +02:00
}
2018-06-27 11:31:11 +02:00
2018-10-03 14:07:10 +02:00
void GLCanvas3D :: post_event ( wxEvent && event )
2018-10-03 11:34:39 +02:00
{
2018-10-03 14:07:10 +02:00
event . SetEventObject ( m_canvas );
2018-10-03 11:34:39 +02:00
wxPostEvent ( m_canvas , event );
}
2019-07-01 12:28:16 +02:00
bool GLCanvas3D :: init ()
2018-05-23 09:57:44 +02:00
{
2018-06-05 12:24:26 +02:00
if ( m_initialized )
return true ;
2020-10-12 08:39:40 +02:00
if ( m_canvas == nullptr || m_context == nullptr )
2018-06-27 11:31:11 +02:00
return false ;
2019-03-27 14:42:09 +01:00
glsafe ( :: glClearColor ( 1.0f , 1.0f , 1.0f , 1.0f ));
glsafe ( :: glClearDepth ( 1.0f ));
2018-06-01 15:54:41 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glDepthFunc ( GL_LESS ));
2018-05-31 08:44:39 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnable ( GL_DEPTH_TEST ));
glsafe ( :: glEnable ( GL_CULL_FACE ));
glsafe ( :: glEnable ( GL_BLEND ));
glsafe ( :: glBlendFunc ( GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA ));
2018-05-23 15:35:11 +02:00
// Set antialiasing / multisampling
2019-03-27 14:42:09 +01:00
glsafe ( :: glDisable ( GL_LINE_SMOOTH ));
glsafe ( :: glDisable ( GL_POLYGON_SMOOTH ));
2018-05-23 15:35:11 +02:00
// ambient lighting
GLfloat ambient [ 4 ] = { 0.3f , 0.3f , 0.3f , 1.0f };
2019-03-27 14:42:09 +01:00
glsafe ( :: glLightModelfv ( GL_LIGHT_MODEL_AMBIENT , ambient ));
2018-05-23 15:35:11 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnable ( GL_LIGHT0 ));
glsafe ( :: glEnable ( GL_LIGHT1 ));
2018-05-23 15:35:11 +02:00
// light from camera
2018-06-11 10:46:32 +02:00
GLfloat specular_cam [ 4 ] = { 0.3f , 0.3f , 0.3f , 1.0f };
2019-03-27 14:42:09 +01:00
glsafe ( :: glLightfv ( GL_LIGHT1 , GL_SPECULAR , specular_cam ));
2018-06-11 10:46:32 +02:00
GLfloat diffuse_cam [ 4 ] = { 0.2f , 0.2f , 0.2f , 1.0f };
2019-03-27 14:42:09 +01:00
glsafe ( :: glLightfv ( GL_LIGHT1 , GL_DIFFUSE , diffuse_cam ));
2018-05-23 15:35:11 +02:00
2018-05-31 08:44:39 +02:00
// light from above
2018-06-11 10:46:32 +02:00
GLfloat specular_top [ 4 ] = { 0.2f , 0.2f , 0.2f , 1.0f };
2019-03-27 14:42:09 +01:00
glsafe ( :: glLightfv ( GL_LIGHT0 , GL_SPECULAR , specular_top ));
2018-06-11 10:46:32 +02:00
GLfloat diffuse_top [ 4 ] = { 0.5f , 0.5f , 0.5f , 1.0f };
2019-03-27 14:42:09 +01:00
glsafe ( :: glLightfv ( GL_LIGHT0 , GL_DIFFUSE , diffuse_top ));
2018-05-31 08:44:39 +02:00
2018-05-23 15:35:11 +02:00
// Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
2019-03-27 14:42:09 +01:00
glsafe ( :: glShadeModel ( GL_SMOOTH ));
2018-05-23 15:35:11 +02:00
// A handy trick -- have surface material mirror the color.
2019-03-27 14:42:09 +01:00
glsafe ( :: glColorMaterial ( GL_FRONT_AND_BACK , GL_AMBIENT_AND_DIFFUSE ));
glsafe ( :: glEnable ( GL_COLOR_MATERIAL ));
2018-05-23 15:35:11 +02:00
2018-06-01 15:54:41 +02:00
if ( m_multisample_allowed )
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnable ( GL_MULTISAMPLE ));
2018-05-23 15:35:11 +02:00
2020-05-20 14:11:22 +02:00
if ( m_main_toolbar . is_enabled ())
m_layers_editing . init ();
2018-05-25 14:05:08 +02:00
2019-08-05 14:30:32 +02:00
// on linux the gl context is not valid until the canvas is not shown on screen
// we defer the geometry finalization of volumes until the first call to render()
m_volumes . finalize_geometry ( true );
2018-06-05 12:24:26 +02:00
2019-07-17 12:43:27 +02:00
if ( m_gizmos . is_enabled () && ! m_gizmos . init ())
2019-07-01 12:28:16 +02:00
std :: cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std :: endl ;
2018-06-13 09:12:16 +02:00
2019-07-19 15:36:55 +02:00
if ( ! _init_toolbars ())
2018-07-23 13:49:48 +02:00
return false ;
2019-07-01 12:28:16 +02:00
if ( m_selection . is_enabled () && ! m_selection . init ())
2018-12-19 14:44:37 +01:00
return false ;
2018-06-05 12:24:26 +02:00
m_initialized = true ;
2018-05-23 09:57:44 +02:00
return true ;
2018-05-15 09:50:01 +02:00
}
2018-06-22 16:06:37 +02:00
void GLCanvas3D :: set_as_dirty ()
{
m_dirty = true ;
}
2018-06-11 13:48:02 +02:00
unsigned int GLCanvas3D :: get_volumes_count () const
2018-05-14 14:14:19 +02:00
{
2018-06-11 15:13:13 +02:00
return ( unsigned int ) m_volumes . volumes . size ();
2018-05-14 14:14:19 +02:00
}
2018-05-18 14:08:59 +02:00
void GLCanvas3D :: reset_volumes ()
{
2019-02-04 16:05:54 +01:00
if ( ! m_initialized )
return ;
2020-03-02 10:58:46 +01:00
if ( m_volumes . empty ())
return ;
_set_current ();
m_selection . clear ();
m_volumes . clear ();
m_dirty = true ;
_set_warning_texture ( WarningTexture :: ObjectOutside , false );
2018-05-18 14:08:59 +02:00
}
2018-12-04 13:55:25 +01:00
int GLCanvas3D :: check_volumes_outside_state () const
{
2020-05-24 00:01:34 +02:00
ModelInstanceEPrintVolumeState state ;
2018-12-04 13:55:25 +01:00
m_volumes . check_outside_state ( m_config , & state );
return ( int ) state ;
}
2018-06-11 13:48:02 +02:00
2020-01-16 12:43:42 +01:00
void GLCanvas3D :: toggle_sla_auxiliaries_visibility ( bool visible , const ModelObject * mo , int instance_idx )
2019-02-01 16:12:00 +01:00
{
2020-01-16 12:43:42 +01:00
m_render_sla_auxiliaries = visible ;
2019-11-29 13:49:38 +01:00
2019-02-01 16:12:00 +01:00
for ( GLVolume * vol : m_volumes . volumes ) {
2020-04-20 09:15:52 +02:00
if ( vol -> composite_id . object_id == 1000 )
continue ; // the wipe tower
2019-03-24 13:35:09 +01:00
if (( mo == nullptr || m_model -> objects [ vol -> composite_id . object_id ] == mo )
&& ( instance_idx == - 1 || vol -> composite_id . instance_id == instance_idx )
&& vol -> composite_id . volume_id < 0 )
vol -> is_active = visible ;
2019-02-01 16:12:00 +01:00
}
}
2019-02-06 15:16:25 +01:00
void GLCanvas3D :: toggle_model_objects_visibility ( bool visible , const ModelObject * mo , int instance_idx )
2019-02-01 16:12:00 +01:00
{
for ( GLVolume * vol : m_volumes . volumes ) {
2019-09-20 12:42:08 +02:00
if ( vol -> composite_id . object_id == 1000 ) { // wipe tower
2021-01-05 11:58:48 +01:00
vol -> is_active = ( visible && mo == nullptr );
2019-09-20 12:42:08 +02:00
}
else {
if (( mo == nullptr || m_model -> objects [ vol -> composite_id . object_id ] == mo )
&& ( instance_idx == - 1 || vol -> composite_id . instance_id == instance_idx )) {
vol -> is_active = visible ;
2020-09-25 16:04:28 +02:00
if ( instance_idx == - 1 ) {
vol -> force_native_color = false ;
vol -> force_neutral_color = false ;
} else {
const GLGizmosManager & gm = get_gizmos_manager ();
auto gizmo_type = gm . get_current_type ();
2020-10-03 15:45:21 +02:00
if ( ( gizmo_type == GLGizmosManager :: FdmSupports
|| gizmo_type == GLGizmosManager :: Seam )
&& ! vol -> is_modifier )
2020-09-25 16:04:28 +02:00
vol -> force_neutral_color = true ;
else
vol -> force_native_color = true ;
}
2019-09-20 12:42:08 +02:00
}
2019-03-20 14:04:20 +01:00
}
2019-02-01 16:12:00 +01:00
}
if ( visible && ! mo )
2019-03-24 13:35:09 +01:00
toggle_sla_auxiliaries_visibility ( true , mo , instance_idx );
2019-02-20 12:09:45 +01:00
if ( ! mo && ! visible && ! m_model -> objects . empty () && ( m_model -> objects . size () > 1 || m_model -> objects . front () -> instances . size () > 1 ))
_set_warning_texture ( WarningTexture :: SomethingNotShown , true );
if ( ! mo && visible )
_set_warning_texture ( WarningTexture :: SomethingNotShown , false );
2019-02-01 16:12:00 +01:00
}
2019-08-16 13:14:51 +02:00
void GLCanvas3D :: update_instance_printable_state_for_object ( const size_t obj_idx )
{
ModelObject * model_object = m_model -> objects [ obj_idx ];
2020-11-19 12:29:19 +01:00
for ( int inst_idx = 0 ; inst_idx < ( int ) model_object -> instances . size (); ++ inst_idx ) {
2019-08-16 13:14:51 +02:00
ModelInstance * instance = model_object -> instances [ inst_idx ];
2020-11-19 12:29:19 +01:00
for ( GLVolume * volume : m_volumes . volumes ) {
2019-09-04 09:47:00 +02:00
if (( volume -> object_idx () == ( int ) obj_idx ) && ( volume -> instance_idx () == inst_idx ))
2019-08-16 13:14:51 +02:00
volume -> printable = instance -> printable ;
}
}
}
2021-01-18 15:19:02 +01:00
void GLCanvas3D :: update_instance_printable_state_for_objects ( const std :: vector < size_t >& object_idxs )
2019-08-05 14:57:30 +02:00
{
for ( size_t obj_idx : object_idxs )
2019-08-16 13:14:51 +02:00
update_instance_printable_state_for_object ( obj_idx );
2019-08-05 14:57:30 +02:00
}
2019-02-01 16:12:00 +01:00
2019-01-21 10:06:51 +01:00
void GLCanvas3D :: set_config ( const DynamicPrintConfig * config )
2018-05-23 11:14:49 +02:00
{
m_config = config ;
2019-01-21 10:06:51 +01:00
m_layers_editing . set_config ( config );
2018-05-23 11:14:49 +02:00
}
2018-11-22 15:29:59 +01:00
void GLCanvas3D :: set_process ( BackgroundSlicingProcess * process )
2018-05-28 15:23:01 +02:00
{
2018-11-22 15:29:59 +01:00
m_process = process ;
2018-11-13 17:45:44 +01:00
}
2018-06-07 11:18:28 +02:00
void GLCanvas3D :: set_model ( Model * model )
{
m_model = model ;
2018-10-08 14:02:12 +02:00
m_selection . set_model ( m_model );
2018-06-07 11:18:28 +02:00
}
2019-02-19 15:15:27 +01:00
void GLCanvas3D :: bed_shape_changed ()
{
2019-11-07 11:48:59 +01:00
refresh_camera_scene_box ();
2020-03-02 10:58:46 +01:00
wxGetApp (). plater () -> get_camera (). requires_zoom_to_bed = true ;
2019-02-19 15:15:27 +01:00
m_dirty = true ;
}
2018-05-15 11:07:32 +02:00
2018-06-06 12:36:52 +02:00
void GLCanvas3D :: set_color_by ( const std :: string & value )
{
m_color_by = value ;
}
2020-03-02 10:58:46 +01:00
void GLCanvas3D :: refresh_camera_scene_box ()
{
wxGetApp (). plater () -> get_camera (). set_scene_box ( scene_bounding_box ());
2020-03-25 14:41:47 +01:00
}
2020-03-02 10:58:46 +01:00
2018-05-14 14:14:19 +02:00
BoundingBoxf3 GLCanvas3D :: volumes_bounding_box () const
{
BoundingBoxf3 bb ;
2020-11-19 12:29:19 +01:00
for ( const GLVolume * volume : m_volumes . volumes ) {
2018-06-11 15:13:13 +02:00
if ( ! m_apply_zoom_to_volumes_filter || (( volume != nullptr ) && volume -> zoom_to_volumes ))
bb . merge ( volume -> transformed_bounding_box ());
2018-05-14 14:14:19 +02:00
}
return bb ;
}
2018-12-07 16:23:04 +01:00
BoundingBoxf3 GLCanvas3D :: scene_bounding_box () const
{
BoundingBoxf3 bb = volumes_bounding_box ();
2020-03-02 10:58:46 +01:00
bb . merge ( wxGetApp (). plater () -> get_bed (). get_bounding_box ( true ));
2020-11-19 12:29:19 +01:00
if ( m_config != nullptr ) {
2019-01-08 09:10:57 +01:00
double h = m_config -> opt_float ( "max_print_height" );
bb . min ( 2 ) = std :: min ( bb . min ( 2 ), - h );
bb . max ( 2 ) = std :: max ( bb . max ( 2 ), h );
}
2019-05-16 15:54:11 +02:00
2018-12-07 16:23:04 +01:00
return bb ;
}
2018-05-18 14:08:59 +02:00
bool GLCanvas3D :: is_layers_editing_enabled () const
{
return m_layers_editing . is_enabled ();
}
2018-05-25 16:28:24 +02:00
bool GLCanvas3D :: is_layers_editing_allowed () const
{
return m_layers_editing . is_allowed ();
}
2019-11-07 15:55:45 +01:00
void GLCanvas3D :: reset_layer_height_profile ()
{
2020-11-19 12:29:19 +01:00
wxGetApp (). plater () -> take_snapshot ( _L ( "Variable layer height - Reset" ));
2019-11-07 15:55:45 +01:00
m_layers_editing . reset_layer_height_profile ( * this );
m_layers_editing . state = LayersEditing :: Completed ;
m_dirty = true ;
}
2019-11-12 14:18:43 +01:00
2019-12-13 13:43:16 +01:00
void GLCanvas3D :: adaptive_layer_height_profile ( float quality_factor )
2019-11-12 14:18:43 +01:00
{
2020-11-19 12:29:19 +01:00
wxGetApp (). plater () -> take_snapshot ( _L ( "Variable layer height - Adaptive" ));
2019-12-13 13:43:16 +01:00
m_layers_editing . adaptive_layer_height_profile ( * this , quality_factor );
2019-11-12 14:18:43 +01:00
m_layers_editing . state = LayersEditing :: Completed ;
m_dirty = true ;
}
2019-11-15 15:49:07 +01:00
2019-11-20 14:06:30 +01:00
void GLCanvas3D :: smooth_layer_height_profile ( const HeightProfileSmoothingParams & smoothing_params )
2019-11-15 15:49:07 +01:00
{
2020-11-19 12:29:19 +01:00
wxGetApp (). plater () -> take_snapshot ( _L ( "Variable layer height - Smooth all" ));
2019-11-20 14:06:30 +01:00
m_layers_editing . smooth_layer_height_profile ( * this , smoothing_params );
2019-11-15 15:49:07 +01:00
m_layers_editing . state = LayersEditing :: Completed ;
m_dirty = true ;
}
2019-11-07 15:55:45 +01:00
2018-06-08 09:40:00 +02:00
bool GLCanvas3D :: is_reload_delayed () const
{
return m_reload_delayed ;
}
2018-05-25 14:05:08 +02:00
void GLCanvas3D :: enable_layers_editing ( bool enable )
{
m_layers_editing . set_enabled ( enable );
2019-01-31 14:25:11 +01:00
const Selection :: IndicesList & idxs = m_selection . get_volume_idxs ();
2020-11-19 12:29:19 +01:00
for ( unsigned int idx : idxs ) {
2019-01-31 14:25:11 +01:00
GLVolume * v = m_volumes . volumes [ idx ];
if ( v -> is_modifier )
v -> force_transparent = enable ;
}
2019-04-15 14:19:18 +02:00
set_as_dirty ();
2018-05-25 14:05:08 +02:00
}
2018-05-21 14:57:43 +02:00
void GLCanvas3D :: enable_legend_texture ( bool enable )
{
2020-04-18 10:41:37 +02:00
m_gcode_viewer . enable_legend ( enable );
2018-05-21 14:57:43 +02:00
}
2018-05-22 09:02:42 +02:00
void GLCanvas3D :: enable_picking ( bool enable )
{
m_picking_enabled = enable ;
2018-10-08 14:02:12 +02:00
m_selection . set_mode ( Selection :: Instance );
2018-05-22 09:02:42 +02:00
}
2018-05-31 13:51:50 +02:00
void GLCanvas3D :: enable_moving ( bool enable )
{
m_moving_enabled = enable ;
}
2018-06-13 09:12:16 +02:00
void GLCanvas3D :: enable_gizmos ( bool enable )
{
m_gizmos . set_enabled ( enable );
}
2019-03-28 08:44:46 +01:00
void GLCanvas3D :: enable_selection ( bool enable )
{
m_selection . set_enabled ( enable );
}
2019-07-19 15:36:55 +02:00
void GLCanvas3D :: enable_main_toolbar ( bool enable )
2018-07-23 13:49:48 +02:00
{
2019-07-19 15:36:55 +02:00
m_main_toolbar . set_enabled ( enable );
}
void GLCanvas3D :: enable_undoredo_toolbar ( bool enable )
{
m_undoredo_toolbar . set_enabled ( enable );
2018-07-23 13:49:48 +02:00
}
2018-07-27 09:38:39 +02:00
void GLCanvas3D :: enable_dynamic_background ( bool enable )
{
m_dynamic_background_enabled = enable ;
}
2018-05-23 15:35:11 +02:00
void GLCanvas3D :: allow_multisample ( bool allow )
{
m_multisample_allowed = allow ;
}
2018-05-15 10:32:38 +02:00
void GLCanvas3D :: zoom_to_bed ()
{
2020-03-02 10:58:46 +01:00
_zoom_to_box ( wxGetApp (). plater () -> get_bed (). get_bounding_box ( false ));
2018-05-15 10:32:38 +02:00
}
void GLCanvas3D :: zoom_to_volumes ()
{
m_apply_zoom_to_volumes_filter = true ;
2019-06-19 14:18:51 +02:00
_zoom_to_box ( volumes_bounding_box ());
2018-05-15 10:32:38 +02:00
m_apply_zoom_to_volumes_filter = false ;
}
2018-10-26 09:50:28 +02:00
void GLCanvas3D :: zoom_to_selection ()
{
if ( ! m_selection . is_empty ())
2019-06-19 14:18:51 +02:00
_zoom_to_box ( m_selection . get_bounding_box ());
2018-10-26 09:50:28 +02:00
}
2020-06-08 12:27:32 +02:00
void GLCanvas3D :: zoom_to_gcode ()
{
2020-06-24 16:57:09 +02:00
_zoom_to_box ( m_gcode_viewer . get_paths_bounding_box (), 1.05 );
2020-06-08 12:27:32 +02:00
}
2018-05-15 11:30:11 +02:00
void GLCanvas3D :: select_view ( const std :: string & direction )
{
2020-03-02 10:58:46 +01:00
wxGetApp (). plater () -> get_camera (). select_view ( direction );
2020-01-15 12:49:34 +01:00
if ( m_canvas != nullptr )
m_canvas -> Refresh ();
2018-05-15 11:30:11 +02:00
}
2018-05-29 15:07:06 +02:00
void GLCanvas3D :: update_volumes_colors_by_extruder ()
{
2018-06-11 15:13:13 +02:00
if ( m_config != nullptr )
m_volumes . update_colors_by_extruder ( m_config );
2018-05-29 15:07:06 +02:00
}
2018-06-04 12:26:39 +02:00
void GLCanvas3D :: render ()
2018-05-23 15:35:11 +02:00
{
2020-11-19 12:29:19 +01:00
if ( m_in_render ) {
2019-06-10 10:47:23 +02:00
// if called recursively, return
m_dirty = true ;
return ;
}
2018-11-27 16:55:54 +01:00
m_in_render = true ;
Slic3r :: ScopeGuard in_render_guard ([ this ]() { m_in_render = false ; });
( void ) in_render_guard ;
2018-05-29 14:34:45 +02:00
if ( m_canvas == nullptr )
return ;
2018-06-25 15:17:13 +02:00
// ensures this canvas is current and initialized
2020-03-02 10:58:46 +01:00
if ( ! _is_shown_on_screen () || ! _set_current () || ! wxGetApp (). init_opengl ())
return ;
if ( ! is_initialized () && ! init ())
return ;
2018-06-04 12:26:39 +02:00
2019-04-25 11:10:01 +02:00
#if ENABLE_RENDER_STATISTICS
auto start_time = std :: chrono :: high_resolution_clock :: now ();
#endif // ENABLE_RENDER_STATISTICS
2020-11-19 12:29:19 +01:00
if ( wxGetApp (). plater () -> get_bed (). get_shape (). empty ()) {
2019-02-19 15:15:27 +01:00
// this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE
post_event ( SimpleEvent ( EVT_GLCANVAS_UPDATE_BED_SHAPE ));
2019-03-18 10:10:11 +01:00
return ;
}
2019-02-05 10:18:40 +01:00
2020-05-28 15:27:29 +02:00
#if ENABLE_ENVIRONMENT_MAP
2020-10-06 15:11:08 +02:00
if ( wxGetApp (). is_editor ())
wxGetApp (). plater () -> init_environment_texture ();
2020-05-28 15:27:29 +02:00
#endif // ENABLE_ENVIRONMENT_MAP
2020-12-15 16:03:20 +01:00
m_render_timer . Stop ();
2020-12-16 16:15:58 +01:00
m_extra_frame_requested_delayed = std :: numeric_limits < int >:: max ();
2020-12-15 16:03:20 +01:00
2019-10-03 11:38:31 +02:00
const Size & cnv_size = get_canvas_size ();
2020-01-31 14:50:37 +01:00
// Probably due to different order of events on Linux/GTK2, when one switched from 3D scene
// to preview, this was called before canvas had its final size. It reported zero width
// and the viewport was set incorrectly, leading to tripping glAsserts further down
// the road (in apply_projection). That's why the minimum size is forced to 10.
2020-03-02 10:58:46 +01:00
Camera & camera = wxGetApp (). plater () -> get_camera ();
camera . apply_viewport ( 0 , 0 , std :: max ( 10u , ( unsigned int ) cnv_size . get_width ()), std :: max ( 10u , ( unsigned int ) cnv_size . get_height ()));
2020-11-19 12:29:19 +01:00
if ( camera . requires_zoom_to_bed ) {
2020-03-02 10:58:46 +01:00
zoom_to_bed ();
_resize (( unsigned int ) cnv_size . get_width (), ( unsigned int ) cnv_size . get_height ());
camera . requires_zoom_to_bed = false ;
}
camera . apply_view_matrix ();
camera . apply_projection ( _max_bounding_box ( true , true ));
2018-06-04 12:26:39 +02:00
2018-06-11 10:46:32 +02:00
GLfloat position_cam [ 4 ] = { 1.0f , 0.0f , 1.0f , 0.0f };
2019-03-27 14:42:09 +01:00
glsafe ( :: glLightfv ( GL_LIGHT1 , GL_POSITION , position_cam ));
2018-06-11 10:46:32 +02:00
GLfloat position_top [ 4 ] = { - 0.5f , - 0.5f , 1.0f , 0.0f };
2019-03-27 14:42:09 +01:00
glsafe ( :: glLightfv ( GL_LIGHT0 , GL_POSITION , position_top ));
2018-06-04 12:26:39 +02:00
2018-11-26 10:56:07 +01:00
wxGetApp (). imgui () -> new_frame ();
2018-10-31 10:19:44 +01:00
2020-11-19 12:29:19 +01:00
if ( m_picking_enabled ) {
2019-04-25 15:08:14 +02:00
if ( m_rectangle_selection . is_dragging ())
// picking pass using rectangle selection
_rectangular_selection_picking_pass ();
else
// regular picking pass
_picking_pass ();
}
2018-08-21 14:27:36 +02:00
2019-06-27 11:25:04 +02:00
#if ENABLE_RENDER_PICKING_PASS
2020-11-19 12:29:19 +01:00
if ( ! m_picking_enabled || ! m_show_picking_texture ) {
2019-06-27 11:25:04 +02:00
#endif // ENABLE_RENDER_PICKING_PASS
2018-08-24 11:17:53 +02:00
// draw scene
2019-03-27 14:42:09 +01:00
glsafe ( :: glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ));
2018-08-24 11:17:53 +02:00
_render_background ();
2018-08-21 14:27:36 +02:00
2018-11-14 10:43:52 +01:00
_render_objects ();
2020-04-15 14:31:39 +02:00
if ( ! m_main_toolbar . is_enabled ())
_render_gcode ();
2018-11-28 15:13:25 +01:00
_render_sla_slices ();
2018-10-08 14:02:12 +02:00
_render_selection ();
2020-03-02 10:58:46 +01:00
_render_bed ( ! camera . is_looking_downward (), true );
2018-08-24 11:17:53 +02:00
2018-12-18 12:35:49 +01:00
#if ENABLE_RENDER_SELECTION_CENTER
_render_selection_center ();
#endif // ENABLE_RENDER_SELECTION_CENTER
2018-11-15 11:38:40 +01:00
// we need to set the mouse's scene position here because the depth buffer
// could be invalidated by the following gizmo render methods
// this position is used later into on_mouse() to drag the objects
2020-04-16 15:09:04 +02:00
if ( m_picking_enabled )
2020-11-19 12:29:19 +01:00
m_mouse . scene_position = _mouse_to_3d ( m_mouse . position . cast < coord_t > ());
2018-11-15 11:38:40 +01:00
2019-02-15 16:15:18 +01:00
_render_current_gizmo ();
2018-12-19 14:44:37 +01:00
_render_selection_sidebar_hints ();
2019-06-27 11:25:04 +02:00
#if ENABLE_RENDER_PICKING_PASS
}
#endif // ENABLE_RENDER_PICKING_PASS
2018-12-19 14:44:37 +01:00
2018-10-26 09:50:28 +02:00
#if ENABLE_SHOW_CAMERA_TARGET
_render_camera_target ();
#endif // ENABLE_SHOW_CAMERA_TARGET
2018-08-24 11:17:53 +02:00
2019-04-25 09:10:03 +02:00
if ( m_picking_enabled && m_rectangle_selection . is_dragging ())
m_rectangle_selection . render ( * this );
2018-08-24 11:17:53 +02:00
// draw overlays
2019-06-14 10:38:09 +02:00
_render_overlays ();
2018-05-29 14:34:45 +02:00
2019-04-25 11:10:01 +02:00
#if ENABLE_RENDER_STATISTICS
2020-12-04 15:36:30 +01:00
if ( wxGetApp (). plater () -> is_render_statistic_dialog_visible ()) {
ImGuiWrapper & imgui = * wxGetApp (). imgui ();
imgui . begin ( std :: string ( "Render statistics" ), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse );
2020-12-08 11:43:00 +01:00
imgui . text ( "Last frame:" );
2020-12-04 15:36:30 +01:00
ImGui :: SameLine ();
2020-12-10 08:40:08 +01:00
int64_t average = m_render_stats . get_average ();
2020-12-08 11:43:00 +01:00
imgui . text ( std :: to_string ( average ));
2020-12-04 15:36:30 +01:00
ImGui :: SameLine ();
2020-12-08 11:43:00 +01:00
imgui . text ( "ms" );
imgui . text ( "FPS:" );
2020-12-04 15:36:30 +01:00
ImGui :: SameLine ();
2020-12-08 11:43:00 +01:00
imgui . text ( std :: to_string (( average == 0 ) ? 0 : static_cast < int > ( 1000.0f / static_cast < float > ( average ))));
2020-12-04 15:36:30 +01:00
ImGui :: Separator ();
2020-12-08 11:43:00 +01:00
imgui . text ( "Compressed textures:" );
2020-12-04 15:36:30 +01:00
ImGui :: SameLine ();
imgui . text ( OpenGLManager :: are_compressed_textures_supported () ? "supported" : "not supported" );
2020-12-08 11:43:00 +01:00
imgui . text ( "Max texture size:" );
2020-12-04 15:36:30 +01:00
ImGui :: SameLine ();
imgui . text ( std :: to_string ( OpenGLManager :: get_gl_info (). get_max_tex_size ()));
imgui . end ();
}
2019-04-25 11:10:01 +02:00
#endif // ENABLE_RENDER_STATISTICS
2019-05-20 09:39:57 +02:00
#if ENABLE_CAMERA_STATISTICS
2020-05-22 09:51:57 +02:00
camera . debug_render ();
2019-05-20 09:39:57 +02:00
#endif // ENABLE_CAMERA_STATISTICS
2020-03-18 14:28:43 +01:00
std :: string tooltip ;
2020-03-16 12:45:39 +01:00
2020-03-18 14:28:43 +01:00
// Negative coordinate means out of the window, likely because the window was deactivated.
// In that case the tooltip should be hidden.
2020-11-19 12:29:19 +01:00
if ( m_mouse . position . x () >= 0. && m_mouse . position . y () >= 0. ) {
2020-03-18 14:28:43 +01:00
if ( tooltip . empty ())
tooltip = m_layers_editing . get_tooltip ( * this );
2020-03-16 12:45:39 +01:00
2020-03-18 14:28:43 +01:00
if ( tooltip . empty ())
tooltip = m_gizmos . get_tooltip ();
2020-03-16 12:45:39 +01:00
2020-03-18 14:28:43 +01:00
if ( tooltip . empty ())
tooltip = m_main_toolbar . get_tooltip ();
2020-03-16 12:45:39 +01:00
2020-03-18 14:28:43 +01:00
if ( tooltip . empty ())
tooltip = m_undoredo_toolbar . get_tooltip ();
2020-03-16 12:45:39 +01:00
2020-03-18 14:28:43 +01:00
if ( tooltip . empty ())
2020-06-05 12:33:09 +02:00
tooltip = wxGetApp (). plater () -> get_collapse_toolbar (). get_tooltip ();
2020-04-23 12:41:38 +02:00
if ( tooltip . empty ())
2020-03-25 14:41:47 +01:00
tooltip = wxGetApp (). plater () -> get_view_toolbar (). get_tooltip ();
}
2020-03-16 12:45:39 +01:00
set_tooltip ( tooltip );
2020-03-17 13:01:38 +01:00
2020-05-07 16:47:15 +02:00
if ( m_tooltip_enabled )
m_tooltip . render ( m_mouse . position , * this );
2020-03-17 13:01:38 +01:00
2019-12-16 13:35:45 +01:00
wxGetApp (). plater () -> get_mouse3d_controller (). render_settings_dialog ( * this );
2020-12-03 15:12:32 +01:00
wxGetApp (). plater () -> get_notification_manager () -> render_notifications ( get_overlay_window_width ());
2019-10-02 15:55:26 +02:00
2018-11-26 10:56:07 +01:00
wxGetApp (). imgui () -> render ();
2018-10-31 10:19:44 +01:00
2018-05-29 14:34:45 +02:00
m_canvas -> SwapBuffers ();
2019-04-25 11:10:01 +02:00
#if ENABLE_RENDER_STATISTICS
auto end_time = std :: chrono :: high_resolution_clock :: now ();
2020-12-08 11:43:00 +01:00
m_render_stats . add_frame ( std :: chrono :: duration_cast < std :: chrono :: milliseconds > ( end_time - start_time ). count ());
2019-04-25 11:10:01 +02:00
#endif // ENABLE_RENDER_STATISTICS
2018-05-21 15:24:52 +02:00
}
2019-12-10 12:57:55 +01:00
void GLCanvas3D :: render_thumbnail ( ThumbnailData & thumbnail_data , unsigned int w , unsigned int h , bool printable_only , bool parts_only , bool show_bed , bool transparent_background ) const
2019-10-24 12:09:09 +02:00
{
2020-05-06 14:38:53 +02:00
switch ( OpenGLManager :: get_framebuffers_type ())
2019-11-05 14:50:58 +01:00
{
2020-05-06 14:38:53 +02:00
case OpenGLManager :: EFramebufferType :: Arb : { _render_thumbnail_framebuffer ( thumbnail_data , w , h , printable_only , parts_only , show_bed , transparent_background ); break ; }
case OpenGLManager :: EFramebufferType :: Ext : { _render_thumbnail_framebuffer_ext ( thumbnail_data , w , h , printable_only , parts_only , show_bed , transparent_background ); break ; }
2019-11-28 14:18:24 +01:00
default : { _render_thumbnail_legacy ( thumbnail_data , w , h , printable_only , parts_only , show_bed , transparent_background ); break ; }
2019-11-05 14:50:58 +01:00
}
2019-10-24 12:09:09 +02:00
}
2018-11-21 15:28:35 +01:00
void GLCanvas3D :: select_all ()
{
m_selection . add_all ();
2018-12-04 08:38:31 +01:00
m_dirty = true ;
2018-11-21 15:28:35 +01:00
}
2019-05-14 11:57:39 +02:00
void GLCanvas3D :: deselect_all ()
{
2019-07-23 15:14:08 +02:00
m_selection . remove_all ();
2019-05-14 11:57:39 +02:00
wxGetApp (). obj_manipul () -> set_dirty ();
m_gizmos . reset_all_states ();
2019-07-17 08:38:48 +02:00
m_gizmos . update_data ();
2019-05-14 11:57:39 +02:00
post_event ( SimpleEvent ( EVT_GLCANVAS_OBJECT_SELECT ));
}
2018-11-14 08:53:56 +01:00
void GLCanvas3D :: delete_selected ()
{
m_selection . erase ();
}
2018-11-21 12:27:20 +01:00
void GLCanvas3D :: ensure_on_bed ( unsigned int object_idx )
{
typedef std :: map < std :: pair < int , int > , double > InstancesToZMap ;
InstancesToZMap instances_min_z ;
2020-11-19 12:29:19 +01:00
for ( GLVolume * volume : m_volumes . volumes ) {
if ( volume -> object_idx () == ( int ) object_idx && ! volume -> is_modifier ) {
2018-11-21 12:27:20 +01:00
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 );
}
}
2020-11-19 12:29:19 +01:00
for ( GLVolume * volume : m_volumes . volumes ) {
2018-11-21 12:27:20 +01:00
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 );
}
}
2020-04-14 16:40:08 +02:00
2020-04-29 10:18:29 +02:00
const std :: vector < double >& GLCanvas3D :: get_gcode_layers_zs () const
2020-04-14 16:40:08 +02:00
{
return m_gcode_viewer . get_layers_zs ();
}
2020-04-29 10:18:29 +02:00
std :: vector < double > GLCanvas3D :: get_volumes_print_zs ( bool active_only ) const
{
return m_volumes . get_current_print_zs ( active_only );
}
2020-04-24 16:12:38 +02:00
void GLCanvas3D :: set_gcode_options_visibility_from_flags ( unsigned int flags )
2020-04-14 16:40:08 +02:00
{
2020-04-24 16:12:38 +02:00
m_gcode_viewer . set_options_visibility_from_flags ( flags );
2020-04-17 10:43:29 +02:00
}
void GLCanvas3D :: set_toolpath_role_visibility_flags ( unsigned int flags )
{
m_gcode_viewer . set_toolpath_role_visibility_flags ( flags );
2020-04-14 16:40:08 +02:00
}
2020-04-15 14:31:39 +02:00
2020-04-16 15:59:36 +02:00
void GLCanvas3D :: set_toolpath_view_type ( GCodeViewer :: EViewType type )
{
m_gcode_viewer . set_view_type ( type );
}
2020-04-27 11:44:29 +02:00
2020-11-13 11:50:52 +01:00
void GLCanvas3D :: set_volumes_z_range ( const std :: array < double , 2 >& range )
2020-04-27 11:44:29 +02:00
{
2020-04-29 10:18:29 +02:00
m_volumes . set_range ( range [ 0 ] - 1e-6 , range [ 1 ] + 1e-6 );
2020-11-11 16:22:09 +01:00
}
2020-11-13 11:50:52 +01:00
void GLCanvas3D :: set_toolpaths_z_range ( const std :: array < unsigned int , 2 >& range )
2020-11-11 16:22:09 +01:00
{
2020-05-05 12:09:11 +02:00
if ( m_gcode_viewer . has_data ())
m_gcode_viewer . set_layers_z_range ( range );
2020-04-27 11:44:29 +02:00
}
2018-06-04 15:42:34 +02:00
2018-06-06 10:16:58 +02:00
std :: vector < int > GLCanvas3D :: load_object ( const ModelObject & model_object , int obj_idx , std :: vector < int > instance_idxs )
{
2020-11-19 12:29:19 +01:00
if ( instance_idxs . empty ()) {
for ( unsigned int i = 0 ; i < model_object . instances . size (); ++ i ) {
2020-03-13 15:57:07 +01:00
instance_idxs . emplace_back ( i );
2018-06-06 10:16:58 +02:00
}
}
2019-08-05 14:30:32 +02:00
return m_volumes . load_object ( & model_object , obj_idx , instance_idxs , m_color_by , m_initialized );
2018-06-06 10:16:58 +02:00
}
2018-06-07 09:22:19 +02:00
std :: vector < int > GLCanvas3D :: load_object ( const Model & model , int obj_idx )
2018-06-06 10:16:58 +02:00
{
2020-11-19 12:29:19 +01:00
if ( 0 <= obj_idx && obj_idx < ( int ) model . objects . size ()) {
2018-06-06 10:16:58 +02:00
const ModelObject * model_object = model . objects [ obj_idx ];
if ( model_object != nullptr )
2018-06-07 09:22:19 +02:00
return load_object ( * model_object , obj_idx , std :: vector < int > ());
2018-06-06 10:16:58 +02:00
}
return std :: vector < int > ();
}
2018-10-18 15:50:51 +02:00
void GLCanvas3D :: mirror_selection ( Axis axis )
{
m_selection . mirror ( axis );
2019-07-17 14:13:50 +02:00
do_mirror ( L ( "Mirror Object" ));
2019-05-03 12:36:26 +02:00
wxGetApp (). obj_manipul () -> set_dirty ();
2018-10-18 15:50:51 +02:00
}
2018-11-16 18:28:50 +01:00
// Reload the 3D scene of
// 1) Model / ModelObjects / ModelInstances / ModelVolumes
// 2) Print bed
// 3) SLA support meshes for their respective ModelObjects / ModelInstances
// 4) Wipe tower preview
// 5) Out of bed collision status & message overlay (texture)
2018-11-21 13:52:46 +01:00
void GLCanvas3D :: reload_scene ( bool refresh_immediately , bool force_full_scene_refresh )
2018-06-08 09:40:00 +02:00
{
2020-11-19 12:29:19 +01:00
if ( m_canvas == nullptr || m_config == nullptr || m_model == nullptr )
2018-06-08 09:40:00 +02:00
return ;
2018-06-25 15:17:13 +02:00
2020-03-02 10:58:46 +01:00
if ( ! m_initialized )
return ;
_set_current ();
2019-01-30 19:02:30 +01:00
2018-11-16 18:28:50 +01:00
struct ModelVolumeState {
2019-09-19 09:09:11 +02:00
ModelVolumeState ( const GLVolume * volume ) :
model_volume ( nullptr ), geometry_id ( volume -> geometry_id ), volume_idx ( - 1 ) {}
ModelVolumeState ( const ModelVolume * model_volume , const ObjectID & instance_id , const GLVolume :: CompositeID & composite_id ) :
model_volume ( model_volume ), geometry_id ( std :: make_pair ( model_volume -> id (). id , instance_id . id )), composite_id ( composite_id ), volume_idx ( - 1 ) {}
ModelVolumeState ( const ObjectID & volume_id , const ObjectID & instance_id ) :
model_volume ( nullptr ), geometry_id ( std :: make_pair ( volume_id . id , instance_id . id )), volume_idx ( - 1 ) {}
bool new_geometry () const { return this -> volume_idx == size_t ( - 1 ); }
const ModelVolume * model_volume ;
2019-06-27 11:02:45 +02:00
// ObjectID of ModelVolume + ObjectID of ModelInstance
// or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance
2018-11-16 18:28:50 +01:00
std :: pair < size_t , size_t > geometry_id ;
GLVolume :: CompositeID composite_id ;
// Volume index in the new GLVolume vector.
2019-09-19 09:09:11 +02:00
size_t volume_idx ;
2018-11-16 18:28:50 +01:00
};
std :: vector < ModelVolumeState > model_volume_state ;
2019-09-19 09:09:11 +02:00
std :: vector < ModelVolumeState > aux_volume_state ;
struct GLVolumeState {
GLVolumeState () :
2019-12-10 11:12:17 +01:00
volume_idx ( size_t ( - 1 )) {}
2019-09-19 09:09:11 +02:00
GLVolumeState ( const GLVolume * volume , unsigned int volume_idx ) :
composite_id ( volume -> composite_id ), volume_idx ( volume_idx ) {}
2019-12-10 11:12:17 +01:00
GLVolumeState ( const GLVolume :: CompositeID & composite_id ) :
composite_id ( composite_id ), volume_idx ( size_t ( - 1 )) {}
2019-09-19 09:09:11 +02:00
GLVolume :: CompositeID composite_id ;
// Volume index in the old GLVolume vector.
size_t volume_idx ;
};
2018-10-22 11:45:03 +02:00
2018-11-16 18:28:50 +01:00
// SLA steps to pull the preview meshes for.
2020-01-30 17:58:40 +01:00
typedef std :: array < SLAPrintObjectStep , 3 > SLASteps ;
2020-02-03 15:42:54 +01:00
SLASteps sla_steps = { slaposDrillHoles , slaposSupportTree , slaposPad };
2018-11-16 18:28:50 +01:00
struct SLASupportState {
2019-09-19 09:09:11 +02:00
std :: array < PrintStateBase :: StateWithTimeStamp , std :: tuple_size < SLASteps >:: value > step ;
2018-11-16 18:28:50 +01:00
};
// State of the sla_steps for all SLAPrintObjects.
std :: vector < SLASupportState > sla_support_state ;
2018-06-25 15:17:13 +02:00
2019-04-26 17:28:31 +02:00
std :: vector < size_t > instance_ids_selected ;
2018-11-16 18:28:50 +01:00
std :: vector < size_t > map_glvolume_old_to_new ( m_volumes . volumes . size (), size_t ( - 1 ));
2019-09-19 09:09:11 +02:00
std :: vector < GLVolumeState > deleted_volumes ;
2018-11-16 18:28:50 +01:00
std :: vector < GLVolume *> glvolumes_new ;
glvolumes_new . reserve ( m_volumes . volumes . size ());
2019-09-19 09:09:11 +02:00
auto model_volume_state_lower = []( const ModelVolumeState & m1 , const ModelVolumeState & m2 ) { return m1 . geometry_id < m2 . geometry_id ; };
2018-06-08 09:40:00 +02:00
2019-09-19 09:09:11 +02:00
m_reload_delayed = ! m_canvas -> IsShown () && ! refresh_immediately && ! force_full_scene_refresh ;
2018-06-08 09:40:00 +02:00
2019-09-19 09:09:11 +02:00
PrinterTechnology printer_technology = m_process -> current_printer_technology ();
2018-12-12 14:40:56 +01:00
int volume_idx_wipe_tower_old = - 1 ;
2018-11-16 18:28:50 +01:00
2019-07-25 15:28:34 +02:00
// Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed).
// First initialize model_volumes_new_sorted & model_instances_new_sorted.
2019-09-19 09:09:11 +02:00
for ( int object_idx = 0 ; object_idx < ( int ) m_model -> objects . size (); ++ object_idx ) {
const ModelObject * model_object = m_model -> objects [ object_idx ];
for ( int instance_idx = 0 ; instance_idx < ( int ) model_object -> instances . size (); ++ instance_idx ) {
const ModelInstance * model_instance = model_object -> instances [ instance_idx ];
for ( int volume_idx = 0 ; volume_idx < ( int ) model_object -> volumes . size (); ++ volume_idx ) {
const ModelVolume * model_volume = model_object -> volumes [ volume_idx ];
model_volume_state . emplace_back ( model_volume , model_instance -> id (), GLVolume :: CompositeID ( object_idx , volume_idx , instance_idx ));
2018-11-16 18:28:50 +01:00
}
}
2018-10-08 14:02:12 +02:00
}
2019-07-25 15:28:34 +02:00
if ( printer_technology == ptSLA ) {
2019-09-19 09:09:11 +02:00
const SLAPrint * sla_print = this -> sla_print ();
#ifndef NDEBUG
2019-07-25 15:28:34 +02:00
// Verify that the SLAPrint object is synchronized with m_model.
check_model_ids_equal ( * m_model , sla_print -> model ());
2019-09-19 09:09:11 +02:00
#endif /* NDEBUG */
2019-07-25 15:28:34 +02:00
sla_support_state . reserve ( sla_print -> objects (). size ());
2019-09-19 09:09:11 +02:00
for ( const SLAPrintObject * print_object : sla_print -> objects ()) {
2019-07-25 15:28:34 +02:00
SLASupportState state ;
2019-09-19 09:09:11 +02:00
for ( size_t istep = 0 ; istep < sla_steps . size (); ++ istep ) {
state . step [ istep ] = print_object -> step_state_with_timestamp ( sla_steps [ istep ]);
if ( state . step [ istep ]. state == PrintStateBase :: DONE ) {
if ( ! print_object -> has_mesh ( sla_steps [ istep ]))
2019-07-25 15:28:34 +02:00
// Consider the DONE step without a valid mesh as invalid for the purpose
// of mesh visualization.
state . step [ istep ]. state = PrintStateBase :: INVALID ;
2020-02-03 15:42:54 +01:00
else if ( sla_steps [ istep ] != slaposDrillHoles )
2019-09-19 09:09:11 +02:00
for ( const ModelInstance * model_instance : print_object -> model_object () -> instances )
// Only the instances, which are currently printable, will have the SLA support structures kept.
// The instances outside the print bed will have the GLVolumes of their support structures released.
if ( model_instance -> is_printable ())
2019-07-25 15:28:34 +02:00
aux_volume_state . emplace_back ( state . step [ istep ]. timestamp , model_instance -> id ());
}
2019-09-19 09:09:11 +02:00
}
sla_support_state . emplace_back ( state );
2019-07-25 15:28:34 +02:00
}
}
std :: sort ( model_volume_state . begin (), model_volume_state . end (), model_volume_state_lower );
2019-09-19 09:09:11 +02:00
std :: sort ( aux_volume_state . begin (), aux_volume_state . end (), model_volume_state_lower );
2020-01-30 17:58:40 +01:00
// Release all ModelVolume based GLVolumes not found in the current Model. Find the GLVolume of a hollowed mesh.
2019-09-19 09:09:11 +02:00
for ( size_t volume_id = 0 ; volume_id < m_volumes . volumes . size (); ++ volume_id ) {
GLVolume * volume = m_volumes . volumes [ volume_id ];
2019-07-25 15:28:34 +02:00
ModelVolumeState key ( volume );
2019-09-19 09:09:11 +02:00
ModelVolumeState * mvs = nullptr ;
2019-07-25 15:28:34 +02:00
if ( volume -> volume_idx () < 0 ) {
2019-09-19 09:09:11 +02:00
auto it = std :: lower_bound ( aux_volume_state . begin (), aux_volume_state . end (), key , model_volume_state_lower );
2019-07-25 15:28:34 +02:00
if ( it != aux_volume_state . end () && it -> geometry_id == key . geometry_id )
2019-07-25 11:23:23 +02:00
// This can be an SLA support structure that should not be rendered (in case someone used undo
// to revert to before it was generated). We only reuse the volume if that's not the case.
if ( m_model -> objects [ volume -> composite_id . object_id ] -> sla_points_status != sla :: PointsStatus :: NoPoints )
mvs = & ( * it );
2019-09-19 09:09:11 +02:00
}
else {
auto it = std :: lower_bound ( model_volume_state . begin (), model_volume_state . end (), key , model_volume_state_lower );
2019-07-25 15:28:34 +02:00
if ( it != model_volume_state . end () && it -> geometry_id == key . geometry_id )
2019-09-19 09:09:11 +02:00
mvs = & ( * it );
2019-07-25 15:28:34 +02:00
}
// Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID.
// The wipe tower has its own wipe_tower_instance_id().
if ( m_selection . contains_volume ( volume_id ))
instance_ids_selected . emplace_back ( volume -> geometry_id . second );
if ( mvs == nullptr || force_full_scene_refresh ) {
// This GLVolume will be released.
if ( volume -> is_wipe_tower ) {
// There is only one wipe tower.
assert ( volume_idx_wipe_tower_old == - 1 );
volume_idx_wipe_tower_old = ( int ) volume_id ;
}
2019-09-19 09:09:11 +02:00
if ( ! m_reload_delayed )
{
deleted_volumes . emplace_back ( volume , volume_id );
2019-07-25 15:28:34 +02:00
delete volume ;
2019-09-19 09:09:11 +02:00
}
}
else {
2019-07-25 15:28:34 +02:00
// This GLVolume will be reused.
volume -> set_sla_shift_z ( 0.0 );
map_glvolume_old_to_new [ volume_id ] = glvolumes_new . size ();
mvs -> volume_idx = glvolumes_new . size ();
glvolumes_new . emplace_back ( volume );
// Update color of the volume based on the current extruder.
2019-09-19 09:09:11 +02:00
if ( mvs -> model_volume != nullptr ) {
int extruder_id = mvs -> model_volume -> extruder_id ();
if ( extruder_id != - 1 )
volume -> extruder_id = extruder_id ;
2019-07-25 15:28:34 +02:00
volume -> is_modifier = ! mvs -> model_volume -> is_model_part ();
volume -> set_color_from_model_volume ( mvs -> model_volume );
// updates volumes transformations
volume -> set_instance_transformation ( mvs -> model_volume -> get_object () -> instances [ mvs -> composite_id . instance_id ] -> get_transformation ());
volume -> set_volume_transformation ( mvs -> model_volume -> get_transformation ());
}
}
}
sort_remove_duplicates ( instance_ids_selected );
2019-12-10 11:12:17 +01:00
auto deleted_volumes_lower = []( const GLVolumeState & v1 , const GLVolumeState & v2 ) { return v1 . composite_id < v2 . composite_id ; };
std :: sort ( deleted_volumes . begin (), deleted_volumes . end (), deleted_volumes_lower );
2018-10-08 14:02:12 +02:00
2018-11-16 18:28:50 +01:00
if ( m_reload_delayed )
return ;
2019-09-24 16:01:01 +02:00
bool update_object_list = false ;
2019-07-25 15:28:34 +02:00
if ( m_volumes . volumes != glvolumes_new )
update_object_list = true ;
m_volumes . volumes = std :: move ( glvolumes_new );
for ( unsigned int obj_idx = 0 ; obj_idx < ( unsigned int ) m_model -> objects . size (); ++ obj_idx ) {
const ModelObject & model_object = * m_model -> objects [ obj_idx ];
for ( int volume_idx = 0 ; volume_idx < ( int ) model_object . volumes . size (); ++ volume_idx ) {
const ModelVolume & model_volume = * model_object . volumes [ volume_idx ];
for ( int instance_idx = 0 ; instance_idx < ( int ) model_object . instances . size (); ++ instance_idx ) {
const ModelInstance & model_instance = * model_object . instances [ instance_idx ];
ModelVolumeState key ( model_volume . id (), model_instance . id ());
auto it = std :: lower_bound ( model_volume_state . begin (), model_volume_state . end (), key , model_volume_state_lower );
assert ( it != model_volume_state . end () && it -> geometry_id == key . geometry_id );
if ( it -> new_geometry ()) {
// New volume.
2019-12-10 11:12:17 +01:00
auto it_old_volume = std :: lower_bound ( deleted_volumes . begin (), deleted_volumes . end (), GLVolumeState ( it -> composite_id ), deleted_volumes_lower );
if ( it_old_volume != deleted_volumes . end () && it_old_volume -> composite_id == it -> composite_id )
// If a volume changed its ObjectID, but it reuses a GLVolume's CompositeID, maintain its selection.
map_glvolume_old_to_new [ it_old_volume -> volume_idx ] = m_volumes . volumes . size ();
2020-01-30 17:58:40 +01:00
// Note the index of the loaded volume, so that we can reload the main model GLVolume with the hollowed mesh
// later in this function.
it -> volume_idx = m_volumes . volumes . size ();
2019-08-05 14:30:32 +02:00
m_volumes . load_object_volume ( & model_object , obj_idx , volume_idx , instance_idx , m_color_by , m_initialized );
2019-07-25 15:28:34 +02:00
m_volumes . volumes . back () -> geometry_id = key . geometry_id ;
2019-09-19 09:09:11 +02:00
update_object_list = true ;
2019-07-25 15:28:34 +02:00
} else {
// Recycling an old GLVolume.
GLVolume & existing_volume = * m_volumes . volumes [ it -> volume_idx ];
assert ( existing_volume . geometry_id == key . geometry_id );
// Update the Object/Volume/Instance indices into the current Model.
if ( existing_volume . composite_id != it -> composite_id ) {
existing_volume . composite_id = it -> composite_id ;
2019-04-24 19:03:05 +02:00
update_object_list = true ;
2019-07-25 15:28:34 +02:00
}
}
}
}
}
if ( printer_technology == ptSLA ) {
size_t idx = 0 ;
const SLAPrint * sla_print = this -> sla_print ();
std :: vector < double > shift_zs ( m_model -> objects . size (), 0 );
double relative_correction_z = sla_print -> relative_correction (). z ();
if ( relative_correction_z <= EPSILON )
relative_correction_z = 1. ;
for ( const SLAPrintObject * print_object : sla_print -> objects ()) {
SLASupportState & state = sla_support_state [ idx ++ ];
const ModelObject * model_object = print_object -> model_object ();
// Find an index of the ModelObject
int object_idx ;
// There may be new SLA volumes added to the scene for this print_object.
// Find the object index of this print_object in the Model::objects list.
auto it = std :: find ( sla_print -> model (). objects . begin (), sla_print -> model (). objects . end (), model_object );
assert ( it != sla_print -> model (). objects . end ());
object_idx = it - sla_print -> model (). objects . begin ();
// Cache the Z offset to be applied to all volumes with this object_idx.
shift_zs [ object_idx ] = print_object -> get_current_elevation () / relative_correction_z ;
// Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene.
// pairs of <instance_idx, print_instance_idx>
std :: vector < std :: pair < size_t , size_t >> instances [ std :: tuple_size < SLASteps >:: value ];
for ( size_t print_instance_idx = 0 ; print_instance_idx < print_object -> instances (). size (); ++ print_instance_idx ) {
const SLAPrintObject :: Instance & instance = print_object -> instances ()[ print_instance_idx ];
// Find index of ModelInstance corresponding to this SLAPrintObject::Instance.
auto it = std :: find_if ( model_object -> instances . begin (), model_object -> instances . end (),
[ & instance ]( const ModelInstance * mi ) { return mi -> id () == instance . instance_id ; });
assert ( it != model_object -> instances . end ());
int instance_idx = it - model_object -> instances . begin ();
for ( size_t istep = 0 ; istep < sla_steps . size (); ++ istep )
2020-02-03 15:42:54 +01:00
if ( sla_steps [ istep ] == slaposDrillHoles ) {
2020-02-03 12:55:38 +01:00
// Hollowing is a special case, where the mesh from the backend is being loaded into the 1st volume of an instance,
// not into its own GLVolume.
// There shall always be such a GLVolume allocated.
ModelVolumeState key ( model_object -> volumes . front () -> id (), instance . instance_id );
auto it = std :: lower_bound ( model_volume_state . begin (), model_volume_state . end (), key , model_volume_state_lower );
assert ( it != model_volume_state . end () && it -> geometry_id == key . geometry_id );
assert ( ! it -> new_geometry ());
GLVolume & volume = * m_volumes . volumes [ it -> volume_idx ];
if ( ! volume . offsets . empty () && state . step [ istep ]. timestamp != volume . offsets . front ()) {
// The backend either produced a new hollowed mesh, or it invalidated the one that the front end has seen.
volume . indexed_vertex_array . release_geometry ();
if ( state . step [ istep ]. state == PrintStateBase :: DONE ) {
2020-02-03 15:42:54 +01:00
TriangleMesh mesh = print_object -> get_mesh ( slaposDrillHoles );
2020-02-03 12:55:38 +01:00
assert ( ! mesh . empty ());
mesh . transform ( sla_print -> sla_trafo ( * m_model -> objects [ volume . object_idx ()]). inverse ());
2020-06-02 10:02:50 +02:00
#if ENABLE_SMOOTH_NORMALS
volume . indexed_vertex_array . load_mesh ( mesh , true );
#else
2020-01-30 17:58:40 +01:00
volume . indexed_vertex_array . load_mesh ( mesh );
2020-06-02 10:02:50 +02:00
#endif // ENABLE_SMOOTH_NORMALS
} else {
2020-02-03 12:55:38 +01:00
// Reload the original volume.
2020-06-02 10:02:50 +02:00
#if ENABLE_SMOOTH_NORMALS
volume . indexed_vertex_array . load_mesh ( m_model -> objects [ volume . object_idx ()] -> volumes [ volume . volume_idx ()] -> mesh (), true );
#else
2020-02-03 12:55:38 +01:00
volume . indexed_vertex_array . load_mesh ( m_model -> objects [ volume . object_idx ()] -> volumes [ volume . volume_idx ()] -> mesh ());
2020-06-02 10:02:50 +02:00
#endif // ENABLE_SMOOTH_NORMALS
}
2020-02-03 12:55:38 +01:00
volume . finalize_geometry ( true );
}
//FIXME it is an ugly hack to write the timestamp into the "offsets" field to not have to add another member variable
// to the GLVolume. We should refactor GLVolume significantly, so that the GLVolume will not contain member variables
// of various concenrs (model vs. 3D print path).
volume . offsets = { state . step [ istep ]. timestamp };
} else if ( state . step [ istep ]. state == PrintStateBase :: DONE ) {
// Check whether there is an existing auxiliary volume to be updated, or a new auxiliary volume to be created.
ModelVolumeState key ( state . step [ istep ]. timestamp , instance . instance_id . id );
auto it = std :: lower_bound ( aux_volume_state . begin (), aux_volume_state . end (), key , model_volume_state_lower );
assert ( it != aux_volume_state . end () && it -> geometry_id == key . geometry_id );
if ( it -> new_geometry ()) {
2019-07-25 11:23:23 +02:00
// This can be an SLA support structure that should not be rendered (in case someone used undo
// to revert to before it was generated). If that's the case, we should not generate anything.
if ( model_object -> sla_points_status != sla :: PointsStatus :: NoPoints )
instances [ istep ]. emplace_back ( std :: pair < size_t , size_t > ( instance_idx , print_instance_idx ));
else
shift_zs [ object_idx ] = 0. ;
2020-01-30 17:58:40 +01:00
} else {
2020-02-03 12:55:38 +01:00
// Recycling an old GLVolume. Update the Object/Instance indices into the current Model.
m_volumes . volumes [ it -> volume_idx ] -> composite_id = GLVolume :: CompositeID ( object_idx , m_volumes . volumes [ it -> volume_idx ] -> volume_idx (), instance_idx );
m_volumes . volumes [ it -> volume_idx ] -> set_instance_transformation ( model_object -> instances [ instance_idx ] -> get_transformation ());
2019-07-25 11:23:23 +02:00
}
2018-11-16 18:28:50 +01:00
}
}
2018-11-26 15:19:42 +01:00
2019-07-25 15:28:34 +02:00
for ( size_t istep = 0 ; istep < sla_steps . size (); ++ istep )
if ( ! instances [ istep ]. empty ())
2019-08-05 14:30:32 +02:00
m_volumes . load_object_auxiliary ( print_object , object_idx , instances [ istep ], sla_steps [ istep ], state . step [ istep ]. timestamp , m_initialized );
2018-11-16 18:28:50 +01:00
}
2019-07-25 15:28:34 +02:00
// Shift-up all volumes of the object so that it has the right elevation with respect to the print bed
for ( GLVolume * volume : m_volumes . volumes )
2019-09-04 09:47:00 +02:00
if ( volume -> object_idx () < ( int ) m_model -> objects . size () && m_model -> objects [ volume -> object_idx ()] -> instances [ volume -> instance_idx ()] -> is_printable ())
2019-07-25 15:28:34 +02:00
volume -> set_sla_shift_z ( shift_zs [ volume -> object_idx ()]);
}
2020-11-19 12:29:19 +01:00
if ( printer_technology == ptFFF && m_config -> has ( "nozzle_diameter" )) {
2019-07-25 15:28:34 +02:00
// Should the wipe tower be visualized ?
unsigned int extruders_count = ( unsigned int ) dynamic_cast < const ConfigOptionFloats *> ( m_config -> option ( "nozzle_diameter" )) -> values . size ();
bool wt = dynamic_cast < const ConfigOptionBool *> ( m_config -> option ( "wipe_tower" )) -> value ;
bool co = dynamic_cast < const ConfigOptionBool *> ( m_config -> option ( "complete_objects" )) -> value ;
2020-11-19 12:29:19 +01:00
if (( extruders_count > 1 ) && wt && ! co ) {
2019-07-25 15:28:34 +02:00
// Height of a print (Show at least a slab)
double height = std :: max ( m_model -> bounding_box (). max ( 2 ), 10.0 );
2018-06-08 09:40:00 +02:00
2019-07-25 15:28:34 +02:00
float x = dynamic_cast < const ConfigOptionFloat *> ( m_config -> option ( "wipe_tower_x" )) -> value ;
float y = dynamic_cast < const ConfigOptionFloat *> ( m_config -> option ( "wipe_tower_y" )) -> value ;
float w = dynamic_cast < const ConfigOptionFloat *> ( m_config -> option ( "wipe_tower_width" )) -> value ;
float a = dynamic_cast < const ConfigOptionFloat *> ( m_config -> option ( "wipe_tower_rotation_angle" )) -> value ;
2018-06-08 09:40:00 +02:00
2019-07-25 15:28:34 +02:00
const Print * print = m_process -> fff_print ();
2018-07-27 15:56:27 +02:00
2019-07-25 15:28:34 +02:00
const DynamicPrintConfig & print_config = wxGetApp (). preset_bundle -> prints . get_edited_preset (). config ;
double layer_height = print_config . opt_float ( "layer_height" );
double first_layer_height = print_config . get_abs_value ( "first_layer_height" , layer_height );
2019-10-08 13:50:51 +02:00
double nozzle_diameter = print -> config (). nozzle_diameter . values [ 0 ];
float depth = print -> wipe_tower_data ( extruders_count , first_layer_height , nozzle_diameter ). depth ;
float brim_width = print -> wipe_tower_data ( extruders_count , first_layer_height , nozzle_diameter ). brim_width ;
2018-10-08 14:02:12 +02:00
2019-07-25 15:28:34 +02:00
int volume_idx_wipe_tower_new = m_volumes . load_wipe_tower_preview (
1000 , x , y , w , depth , ( float ) height , a , ! print -> is_step_done ( psWipeTower ),
2019-10-08 13:50:51 +02:00
brim_width , m_initialized );
2019-07-25 15:28:34 +02:00
if ( volume_idx_wipe_tower_old != - 1 )
map_glvolume_old_to_new [ volume_idx_wipe_tower_old ] = volume_idx_wipe_tower_new ;
2018-06-08 09:40:00 +02:00
}
2019-07-25 15:28:34 +02:00
}
2018-06-08 09:40:00 +02:00
2019-07-25 15:28:34 +02:00
update_volumes_colors_by_extruder ();
// Update selection indices based on the old/new GLVolumeCollection.
if ( m_selection . get_mode () == Selection :: Instance )
m_selection . instances_changed ( instance_ids_selected );
else
m_selection . volumes_changed ( map_glvolume_old_to_new );
2018-11-16 18:28:50 +01:00
2019-07-17 08:38:48 +02:00
m_gizmos . update_data ();
m_gizmos . refresh_on_off_state ();
2018-11-16 18:28:50 +01:00
// Update the toolbar
2019-04-24 19:03:05 +02:00
if ( update_object_list )
post_event ( SimpleEvent ( EVT_GLCANVAS_OBJECT_SELECT ));
2018-06-08 09:40:00 +02:00
// checks for geometry outside the print volume to render it accordingly
2020-11-19 12:29:19 +01:00
if ( ! m_volumes . empty ()) {
2020-05-24 00:01:34 +02:00
ModelInstanceEPrintVolumeState state ;
2018-07-18 14:26:42 +02:00
2019-03-01 11:00:34 +01:00
const bool contained_min_one = m_volumes . check_outside_state ( m_config , & state );
2020-05-24 00:01:34 +02:00
_set_warning_texture ( WarningTexture :: ObjectClashed , state == ModelInstancePVS_Partly_Outside );
_set_warning_texture ( WarningTexture :: ObjectOutside , state == ModelInstancePVS_Fully_Outside );
2020-10-01 19:39:51 +02:00
if ( printer_technology != ptSLA || state == ModelInstancePVS_Inside )
_set_warning_texture ( WarningTexture :: SlaSupportsOutside , false );
2019-03-01 11:00:34 +01:00
post_event ( Event < bool > ( EVT_GLCANVAS_ENABLE_ACTION_BUTTONS ,
2020-05-24 00:01:34 +02:00
contained_min_one && ! m_model -> objects . empty () && state != ModelInstancePVS_Partly_Outside ));
2018-06-08 09:40:00 +02:00
}
2020-11-19 12:29:19 +01:00
else {
2019-02-20 12:09:45 +01:00
_set_warning_texture ( WarningTexture :: ObjectOutside , false );
2019-03-01 11:00:34 +01:00
_set_warning_texture ( WarningTexture :: ObjectClashed , false );
2020-10-01 19:39:51 +02:00
_set_warning_texture ( WarningTexture :: SlaSupportsOutside , false );
2018-10-03 11:34:39 +02:00
post_event ( Event < bool > ( EVT_GLCANVAS_ENABLE_ACTION_BUTTONS , false ));
2018-06-08 09:40:00 +02:00
}
2018-10-08 14:02:12 +02:00
2019-11-07 11:48:59 +01:00
refresh_camera_scene_box ();
2018-12-07 16:23:04 +01:00
2020-11-19 12:29:19 +01:00
if ( m_selection . is_empty ()) {
2019-01-14 14:42:21 +01:00
// If no object is selected, deactivate the active gizmo, if any
// Otherwise it may be shown after cleaning the scene (if it was active while the objects were deleted)
2019-01-10 14:43:07 +01:00
m_gizmos . reset_all_states ();
2019-01-14 14:42:21 +01:00
// If no object is selected, reset the objects manipulator on the sidebar
// to force a reset of its cache
auto manip = wxGetApp (). obj_manipul ();
if ( manip != nullptr )
2019-05-03 12:36:26 +02:00
manip -> set_dirty ();
2019-01-14 14:42:21 +01:00
}
2018-11-16 18:28:50 +01:00
// and force this canvas to be redrawn.
m_dirty = true ;
2018-06-08 09:40:00 +02:00
}
2019-08-27 15:55:53 +02:00
static void reserve_new_volume_finalize_old_volume ( GLVolume & vol_new , GLVolume & vol_old , bool gl_initialized , size_t prealloc_size = VERTEX_BUFFER_RESERVE_SIZE )
2019-08-26 11:12:48 +02:00
{
// Assign the large pre-allocated buffers to the new GLVolume.
vol_new . indexed_vertex_array = std :: move ( vol_old . indexed_vertex_array );
// Copy the content back to the old GLVolume.
vol_old . indexed_vertex_array = vol_new . indexed_vertex_array ;
// Clear the buffers, but keep them pre-allocated.
vol_new . indexed_vertex_array . clear ();
// Just make sure that clear did not clear the reserved memory.
// Reserving number of vertices (3x position + 3x color)
2019-08-27 15:55:53 +02:00
vol_new . indexed_vertex_array . reserve ( prealloc_size / 6 );
2019-08-26 11:12:48 +02:00
// Finalize the old geometry, possibly move data to the graphics card.
vol_old . finalize_geometry ( gl_initialized );
}
2020-04-22 16:29:07 +02:00
void GLCanvas3D :: load_gcode_preview ( const GCodeProcessor :: Result & gcode_result )
2020-03-02 15:13:23 +01:00
{
2020-04-22 16:29:07 +02:00
m_gcode_viewer . load ( gcode_result , * this -> fff_print (), m_initialized );
2020-12-18 09:03:18 +01:00
if ( wxGetApp (). is_editor ()) {
m_gcode_viewer . update_shells_color_by_extruder ( m_config );
2020-06-08 12:27:32 +02:00
_show_warning_texture_if_needed ( WarningTexture :: ToolpathOutside );
2020-12-18 09:03:18 +01:00
}
2020-03-02 15:13:23 +01:00
}
2020-04-20 16:04:59 +02:00
2020-04-22 16:29:07 +02:00
void GLCanvas3D :: refresh_gcode_preview ( const GCodeProcessor :: Result & gcode_result , const std :: vector < std :: string >& str_tool_colors )
2020-04-20 16:04:59 +02:00
{
2020-04-22 16:29:07 +02:00
m_gcode_viewer . refresh ( gcode_result , str_tool_colors );
2020-04-27 12:43:51 +02:00
set_as_dirty ();
request_extra_frame ();
2020-04-20 16:04:59 +02:00
}
2018-06-05 10:56:55 +02:00
2020-12-11 14:59:58 +01:00
#if ENABLE_RENDER_PATH_REFRESH_AFTER_OPTIONS_CHANGE
void GLCanvas3D :: refresh_gcode_preview_render_paths ()
{
m_gcode_viewer . refresh_render_paths ();
set_as_dirty ();
request_extra_frame ();
}
#endif // ENABLE_RENDER_PATH_REFRESH_AFTER_OPTIONS_CHANGE
2018-11-26 15:16:35 +01:00
void GLCanvas3D :: load_sla_preview ()
{
const SLAPrint * print = this -> sla_print ();
2020-11-19 12:29:19 +01:00
if ( m_canvas != nullptr && print != nullptr ) {
2019-01-30 19:02:30 +01:00
_set_current ();
2019-08-26 11:12:48 +02:00
// Release OpenGL data before generating new data.
this -> reset_volumes ();
2019-08-08 18:26:41 +02:00
_load_sla_shells ();
2019-05-07 12:29:48 +02:00
_update_sla_shells_outside_state ();
_show_warning_texture_if_needed ( WarningTexture :: SlaSupportsOutside );
2018-11-26 15:16:35 +01:00
}
}
2020-01-23 12:49:39 +01:00
void GLCanvas3D :: load_preview ( const std :: vector < std :: string >& str_tool_colors , const std :: vector < CustomGCode :: Item >& color_print_values )
2018-07-24 13:39:17 +02:00
{
2018-11-22 15:29:59 +01:00
const Print * print = this -> fff_print ();
if ( print == nullptr )
2018-07-24 13:39:17 +02:00
return ;
2019-01-30 19:02:30 +01:00
_set_current ();
2019-08-26 11:12:48 +02:00
// Release OpenGL data before generating new data.
this -> reset_volumes ();
2018-07-24 13:39:17 +02:00
_load_print_toolpaths ();
_load_wipe_tower_toolpaths ( str_tool_colors );
2018-11-22 15:29:59 +01:00
for ( const PrintObject * object : print -> objects ())
2019-08-26 11:12:48 +02:00
_load_print_object_toolpaths ( * object , str_tool_colors , color_print_values );
2018-07-27 08:49:58 +02:00
2018-07-24 13:39:17 +02:00
_update_toolpath_volumes_outside_state ();
2019-05-07 12:29:48 +02:00
_show_warning_texture_if_needed ( WarningTexture :: ToolpathOutside );
2018-07-24 13:39:17 +02:00
}
2018-06-06 14:19:28 +02:00
void GLCanvas3D :: bind_event_handlers ()
{
2020-10-19 12:34:33 +02:00
if ( m_canvas != nullptr ) {
2018-06-06 14:19:28 +02:00
m_canvas -> Bind ( wxEVT_SIZE , & GLCanvas3D :: on_size , this );
m_canvas -> Bind ( wxEVT_IDLE , & GLCanvas3D :: on_idle , this );
m_canvas -> Bind ( wxEVT_CHAR , & GLCanvas3D :: on_char , this );
2019-02-15 15:35:32 +01:00
m_canvas -> Bind ( wxEVT_KEY_DOWN , & GLCanvas3D :: on_key , this );
m_canvas -> Bind ( wxEVT_KEY_UP , & GLCanvas3D :: on_key , this );
2018-06-06 14:19:28 +02:00
m_canvas -> Bind ( wxEVT_MOUSEWHEEL , & GLCanvas3D :: on_mouse_wheel , this );
m_canvas -> Bind ( wxEVT_TIMER , & GLCanvas3D :: on_timer , this );
2020-12-15 16:03:20 +01:00
m_canvas -> Bind ( EVT_GLCANVAS_RENDER_TIMER , & GLCanvas3D :: on_render_timer , this );
2018-06-06 14:19:28 +02:00
m_canvas -> Bind ( wxEVT_LEFT_DOWN , & GLCanvas3D :: on_mouse , this );
m_canvas -> Bind ( wxEVT_LEFT_UP , & GLCanvas3D :: on_mouse , this );
m_canvas -> Bind ( wxEVT_MIDDLE_DOWN , & GLCanvas3D :: on_mouse , this );
m_canvas -> Bind ( wxEVT_MIDDLE_UP , & GLCanvas3D :: on_mouse , this );
m_canvas -> Bind ( wxEVT_RIGHT_DOWN , & GLCanvas3D :: on_mouse , this );
m_canvas -> Bind ( wxEVT_RIGHT_UP , & GLCanvas3D :: on_mouse , this );
m_canvas -> Bind ( wxEVT_MOTION , & GLCanvas3D :: on_mouse , this );
m_canvas -> Bind ( wxEVT_ENTER_WINDOW , & GLCanvas3D :: on_mouse , this );
m_canvas -> Bind ( wxEVT_LEAVE_WINDOW , & GLCanvas3D :: on_mouse , this );
m_canvas -> Bind ( wxEVT_LEFT_DCLICK , & GLCanvas3D :: on_mouse , this );
m_canvas -> Bind ( wxEVT_MIDDLE_DCLICK , & GLCanvas3D :: on_mouse , this );
m_canvas -> Bind ( wxEVT_RIGHT_DCLICK , & GLCanvas3D :: on_mouse , this );
m_canvas -> Bind ( wxEVT_PAINT , & GLCanvas3D :: on_paint , this );
2020-05-07 16:47:15 +02:00
m_canvas -> Bind ( wxEVT_SET_FOCUS , & GLCanvas3D :: on_set_focus , this );
2020-10-19 12:34:33 +02:00
m_event_handlers_bound = true ;
2018-06-06 14:19:28 +02:00
}
}
void GLCanvas3D :: unbind_event_handlers ()
{
2020-10-19 12:34:33 +02:00
if ( m_canvas != nullptr && m_event_handlers_bound ) {
2018-06-06 14:19:28 +02:00
m_canvas -> Unbind ( wxEVT_SIZE , & GLCanvas3D :: on_size , this );
m_canvas -> Unbind ( wxEVT_IDLE , & GLCanvas3D :: on_idle , this );
m_canvas -> Unbind ( wxEVT_CHAR , & GLCanvas3D :: on_char , this );
2019-02-15 15:35:32 +01:00
m_canvas -> Unbind ( wxEVT_KEY_DOWN , & GLCanvas3D :: on_key , this );
m_canvas -> Unbind ( wxEVT_KEY_UP , & GLCanvas3D :: on_key , this );
2018-06-06 14:19:28 +02:00
m_canvas -> Unbind ( wxEVT_MOUSEWHEEL , & GLCanvas3D :: on_mouse_wheel , this );
m_canvas -> Unbind ( wxEVT_TIMER , & GLCanvas3D :: on_timer , this );
2020-12-16 16:15:58 +01:00
m_canvas -> Unbind ( EVT_GLCANVAS_RENDER_TIMER , & GLCanvas3D :: on_render_timer , this );
2018-06-06 14:19:28 +02:00
m_canvas -> Unbind ( wxEVT_LEFT_DOWN , & GLCanvas3D :: on_mouse , this );
2019-02-03 14:11:09 +01:00
m_canvas -> Unbind ( wxEVT_LEFT_UP , & GLCanvas3D :: on_mouse , this );
2018-06-06 14:19:28 +02:00
m_canvas -> Unbind ( wxEVT_MIDDLE_DOWN , & GLCanvas3D :: on_mouse , this );
m_canvas -> Unbind ( wxEVT_MIDDLE_UP , & GLCanvas3D :: on_mouse , this );
m_canvas -> Unbind ( wxEVT_RIGHT_DOWN , & GLCanvas3D :: on_mouse , this );
m_canvas -> Unbind ( wxEVT_RIGHT_UP , & GLCanvas3D :: on_mouse , this );
m_canvas -> Unbind ( wxEVT_MOTION , & GLCanvas3D :: on_mouse , this );
m_canvas -> Unbind ( wxEVT_ENTER_WINDOW , & GLCanvas3D :: on_mouse , this );
m_canvas -> Unbind ( wxEVT_LEAVE_WINDOW , & GLCanvas3D :: on_mouse , this );
m_canvas -> Unbind ( wxEVT_LEFT_DCLICK , & GLCanvas3D :: on_mouse , this );
m_canvas -> Unbind ( wxEVT_MIDDLE_DCLICK , & GLCanvas3D :: on_mouse , this );
m_canvas -> Unbind ( wxEVT_RIGHT_DCLICK , & GLCanvas3D :: on_mouse , this );
m_canvas -> Unbind ( wxEVT_PAINT , & GLCanvas3D :: on_paint , this );
2020-05-07 16:47:15 +02:00
m_canvas -> Unbind ( wxEVT_SET_FOCUS , & GLCanvas3D :: on_set_focus , this );
2020-10-19 12:34:33 +02:00
m_event_handlers_bound = false ;
2018-06-06 14:19:28 +02:00
}
}
2018-05-14 14:47:13 +02:00
void GLCanvas3D :: on_size ( wxSizeEvent & evt )
{
2018-06-01 15:54:41 +02:00
m_dirty = true ;
2018-05-14 14:47:13 +02:00
}
2020-12-08 09:09:38 +01:00
2018-05-14 14:47:13 +02:00
void GLCanvas3D :: on_idle ( wxIdleEvent & evt )
{
2019-05-18 14:52:29 +02:00
if ( ! m_initialized )
return ;
2020-12-03 15:12:32 +01:00
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
2020-12-15 16:03:20 +01:00
/*NotificationManager* notification_mgr = wxGetApp().plater()->get_notification_manager();
2020-12-03 15:12:32 +01:00
if (notification_mgr->requires_update())
notification_mgr->update_notifications();
2020-12-15 16:03:20 +01:00
m_dirty |= notification_mgr->requires_render();*/
2020-12-03 15:12:32 +01:00
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
2020-12-08 09:09:38 +01:00
// FIXME
2019-07-19 15:36:55 +02:00
m_dirty |= m_main_toolbar . update_items_state ();
m_dirty |= m_undoredo_toolbar . update_items_state ();
2020-03-02 10:58:46 +01:00
m_dirty |= wxGetApp (). plater () -> get_view_toolbar (). update_items_state ();
2020-06-05 12:33:09 +02:00
m_dirty |= wxGetApp (). plater () -> get_collapse_toolbar (). update_items_state ();
2020-03-02 10:58:46 +01:00
bool mouse3d_controller_applied = wxGetApp (). plater () -> get_mouse3d_controller (). apply ( wxGetApp (). plater () -> get_camera ());
2019-10-08 08:44:50 +02:00
m_dirty |= mouse3d_controller_applied ;
2019-03-14 13:54:05 +01:00
2020-12-03 15:12:32 +01:00
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
2020-12-15 16:03:20 +01:00
/*
2020-12-08 09:09:38 +01:00
if (notification_mgr->requires_update()) {
evt.RequestMore();
2020-12-15 16:03:20 +01:00
}*/
2020-12-08 09:09:38 +01:00
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
2018-06-01 15:54:41 +02:00
if ( ! m_dirty )
2018-05-14 14:47:13 +02:00
return ;
2018-05-28 15:23:01 +02:00
_refresh_if_shown_on_screen ();
2019-06-05 10:07:59 +02:00
2020-10-19 12:34:33 +02:00
if ( m_extra_frame_requested || mouse3d_controller_applied ) {
2019-06-05 10:07:59 +02:00
m_dirty = true ;
2019-12-12 13:38:08 +01:00
m_extra_frame_requested = false ;
2019-09-27 14:52:19 +02:00
evt . RequestMore ();
}
else
m_dirty = false ;
2018-05-14 14:47:13 +02:00
}
2018-05-15 16:09:04 +02:00
void GLCanvas3D :: on_char ( wxKeyEvent & evt )
{
2019-04-10 13:52:18 +02:00
if ( ! m_initialized )
return ;
2019-02-03 14:06:13 +01:00
// see include/wx/defs.h enum wxKeyCode
int keyCode = evt . GetKeyCode ();
2019-02-03 17:57:55 +01:00
int ctrlMask = wxMOD_CONTROL ;
2020-11-04 16:26:25 +01:00
int shiftMask = wxMOD_SHIFT ;
2019-02-15 15:35:32 +01:00
auto imgui = wxGetApp (). imgui ();
if ( imgui -> update_key_data ( evt )) {
render ();
2019-02-20 16:55:00 +01:00
return ;
2019-02-15 15:35:32 +01:00
}
2020-11-20 17:50:10 +01:00
if ( keyCode == WXK_ESCAPE && ( _deactivate_undo_redo_toolbar_items () || _deactivate_search_toolbar_item () || _deactivate_arrange_menu ()))
2019-07-11 07:54:33 +02:00
return ;
2019-07-17 08:38:48 +02:00
if ( m_gizmos . on_char ( evt ))
2019-03-26 14:08:02 +01:00
return ;
2019-02-03 17:57:55 +01:00
if (( evt . GetModifiers () & ctrlMask ) != 0 ) {
2020-12-01 10:43:04 +01:00
// CTRL is pressed
2019-02-03 14:06:13 +01:00
switch ( keyCode ) {
2019-04-10 10:16:04 +02:00
#ifdef __APPLE__
2019-02-04 15:12:24 +01:00
case 'a' :
case 'A' :
2019-04-10 10:16:04 +02:00
#else /* __APPLE__ */
2019-02-11 08:21:37 +01:00
case WXK_CONTROL_A :
2019-04-10 10:16:04 +02:00
#endif /* __APPLE__ */
2020-12-15 09:21:09 +01:00
post_event ( SimpleEvent ( EVT_GLCANVAS_SELECT_ALL ));
2019-02-11 08:21:37 +01:00
break ;
2019-04-16 13:47:37 +02:00
#ifdef __APPLE__
case 'c' :
case 'C' :
#else /* __APPLE__ */
case WXK_CONTROL_C :
#endif /* __APPLE__ */
post_event ( SimpleEvent ( EVT_GLTOOLBAR_COPY ));
break ;
2019-10-02 15:55:26 +02:00
2020-11-03 08:41:04 +01:00
#if ENABLE_CTRL_M_ON_WINDOWS
2020-12-08 12:27:15 +01:00
#ifdef __APPLE__
case 'm' :
case 'M' :
#else /* __APPLE__ */
2020-11-03 08:41:04 +01:00
case WXK_CONTROL_M :
2020-12-08 12:27:15 +01:00
#endif /* __APPLE__ */
2020-11-03 08:41:04 +01:00
{
2020-11-26 11:00:24 +01:00
#ifdef _WIN32
if ( wxGetApp (). app_config -> get ( "use_legacy_3DConnexion" ) == "1" ) {
#endif //_WIN32
Mouse3DController & controller = wxGetApp (). plater () -> get_mouse3d_controller ();
controller . show_settings_dialog ( ! controller . is_settings_dialog_shown ());
m_dirty = true ;
#ifdef _WIN32
}
#endif //_WIN32
2020-11-03 08:41:04 +01:00
break ;
}
#else
2020-10-26 11:35:53 +01:00
#if defined(__linux__) || defined(__APPLE__)
2019-10-02 15:55:26 +02:00
case WXK_CONTROL_M :
2020-03-31 14:16:53 +02:00
{
Mouse3DController & controller = wxGetApp (). plater () -> get_mouse3d_controller ();
controller . show_settings_dialog ( ! controller . is_settings_dialog_shown ());
m_dirty = true ;
break ;
}
2020-03-31 15:30:24 +02:00
#endif /* __linux__ */
2020-11-03 08:41:04 +01:00
#endif // ENABLE_CTRL_M_ON_WINDOWS
2019-10-02 15:55:26 +02:00
2019-04-16 13:47:37 +02:00
#ifdef __APPLE__
case 'v' :
case 'V' :
#else /* __APPLE__ */
case WXK_CONTROL_V :
#endif /* __APPLE__ */
post_event ( SimpleEvent ( EVT_GLTOOLBAR_PASTE ));
break ;
2019-07-04 10:45:41 +02:00
2020-04-08 09:47:33 +02:00
#ifdef __APPLE__
case 'f' :
case 'F' :
#else /* __APPLE__ */
case WXK_CONTROL_F :
#endif /* __APPLE__ */
_activate_search_toolbar_item ();
break ;
2019-07-04 10:45:41 +02:00
#ifdef __APPLE__
case 'y' :
case 'Y' :
#else /* __APPLE__ */
case WXK_CONTROL_Y :
#endif /* __APPLE__ */
post_event ( SimpleEvent ( EVT_GLCANVAS_REDO ));
break ;
#ifdef __APPLE__
case 'z' :
case 'Z' :
#else /* __APPLE__ */
case WXK_CONTROL_Z :
#endif /* __APPLE__ */
post_event ( SimpleEvent ( EVT_GLCANVAS_UNDO ));
break ;
2019-07-29 15:44:00 +02:00
case WXK_BACK :
2019-02-04 15:46:12 +01:00
case WXK_DELETE :
2019-07-29 15:44:00 +02:00
post_event ( SimpleEvent ( EVT_GLTOOLBAR_DELETE_ALL )); break ;
2020-12-03 15:12:32 +01:00
default : evt . Skip ();
2019-02-03 14:06:13 +01:00
}
} else {
switch ( keyCode )
2018-05-15 16:09:04 +02:00
{
2019-07-29 15:44:00 +02:00
case WXK_BACK :
2020-12-15 09:21:09 +01:00
case WXK_DELETE : { post_event ( SimpleEvent ( EVT_GLTOOLBAR_DELETE )); break ; }
2019-05-14 11:57:39 +02:00
case WXK_ESCAPE : { deselect_all (); break ; }
2020-12-15 09:21:09 +01:00
case WXK_F5 : {
2020-12-03 13:48:54 +01:00
if (( wxGetApp (). is_editor () && ! wxGetApp (). plater () -> model (). objects . empty ()) ||
( wxGetApp (). is_gcode_viewer () && ! wxGetApp (). plater () -> get_last_loaded_gcode (). empty ()))
post_event ( SimpleEvent ( EVT_GLCANVAS_RELOAD_FROM_DISK ));
break ;
}
2019-05-14 11:57:39 +02:00
case '0' : { select_view ( "iso" ); break ; }
2019-02-03 14:06:13 +01:00
case '1' : { select_view ( "top" ); break ; }
case '2' : { select_view ( "bottom" ); break ; }
case '3' : { select_view ( "front" ); break ; }
case '4' : { select_view ( "rear" ); break ; }
case '5' : { select_view ( "left" ); break ; }
case '6' : { select_view ( "right" ); break ; }
2020-12-15 09:21:09 +01:00
case '+' : {
if ( dynamic_cast < Preview *> ( m_canvas -> GetParent ()) != nullptr )
post_event ( wxKeyEvent ( EVT_GLCANVAS_EDIT_COLOR_CHANGE , evt ));
else
post_event ( Event < int > ( EVT_GLCANVAS_INCREASE_INSTANCES , + 1 ));
break ;
}
2020-11-18 15:14:49 +01:00
case '-' : {
2020-12-15 09:21:09 +01:00
if ( dynamic_cast < Preview *> ( m_canvas -> GetParent ()) != nullptr )
post_event ( wxKeyEvent ( EVT_GLCANVAS_EDIT_COLOR_CHANGE , evt ));
else
post_event ( Event < int > ( EVT_GLCANVAS_INCREASE_INSTANCES , - 1 ));
break ;
}
case '?' : { post_event ( SimpleEvent ( EVT_GLCANVAS_QUESTION_MARK )); break ; }
2019-02-03 14:06:13 +01:00
case 'A' :
2020-12-15 09:21:09 +01:00
case 'a' : { post_event ( SimpleEvent ( EVT_GLCANVAS_ARRANGE )); break ; }
2019-02-03 14:06:13 +01:00
case 'B' :
case 'b' : { zoom_to_bed (); break ; }
2020-01-28 13:15:21 +01:00
case 'E' :
case 'e' : { m_labels . show ( ! m_labels . is_shown ()); m_dirty = true ; break ; }
2020-12-15 09:21:09 +01:00
case 'G' :
case 'g' : {
if (( evt . GetModifiers () & shiftMask ) != 0 ) {
if ( dynamic_cast < Preview *> ( m_canvas -> GetParent ()) != nullptr )
post_event ( wxKeyEvent ( EVT_GLCANVAS_JUMP_TO , evt ));
}
break ;
}
2019-02-03 14:06:13 +01:00
case 'I' :
2019-10-03 11:38:31 +02:00
case 'i' : { _update_camera_zoom ( 1.0 ); break ; }
2019-06-19 14:33:09 +02:00
case 'K' :
2020-03-02 10:58:46 +01:00
case 'k' : { wxGetApp (). plater () -> get_camera (). select_next_type (); m_dirty = true ; break ; }
2020-04-27 08:19:48 +02:00
case 'L' :
case 'l' : {
2020-12-15 09:21:09 +01:00
if ( ! m_main_toolbar . is_enabled ()) {
m_gcode_viewer . enable_legend ( ! m_gcode_viewer . is_legend_enabled ());
m_dirty = true ;
wxGetApp (). plater () -> update_preview_bottom_toolbar ();
}
break ;
}
2019-02-03 14:06:13 +01:00
case 'O' :
2019-10-03 11:38:31 +02:00
case 'o' : { _update_camera_zoom ( - 1.0 ); break ; }
2019-06-27 11:25:04 +02:00
#if ENABLE_RENDER_PICKING_PASS
2020-07-08 13:33:50 +02:00
case 'P' :
case 'p' : {
2019-06-27 11:25:04 +02:00
m_show_picking_texture = ! m_show_picking_texture ;
2020-07-08 13:33:50 +02:00
m_dirty = true ;
2019-06-27 11:25:04 +02:00
break ;
}
#endif // ENABLE_RENDER_PICKING_PASS
2019-02-03 14:06:13 +01:00
case 'Z' :
2020-12-15 09:21:09 +01:00
case 'z' : {
2020-05-07 10:49:12 +02:00
if ( ! m_selection . is_empty ())
zoom_to_selection ();
2020-11-18 15:14:49 +01:00
else {
2020-05-07 10:49:12 +02:00
if ( ! m_volumes . empty ())
zoom_to_volumes ();
else
2020-06-24 16:57:09 +02:00
_zoom_to_box ( m_gcode_viewer . get_paths_bounding_box ());
2020-05-07 10:49:12 +02:00
}
break ;
}
2019-05-14 11:57:39 +02:00
default : { evt . Skip (); break ; }
2018-05-15 16:09:04 +02:00
}
}
}
2020-02-05 13:14:50 +01:00
class TranslationProcessor
{
using UpAction = std :: function < void ( void ) > ;
using DownAction = std :: function < void ( const Vec3d & , bool , bool ) > ;
UpAction m_up_action { nullptr };
DownAction m_down_action { nullptr };
bool m_running { false };
Vec3d m_direction { Vec3d :: UnitX () };
public :
TranslationProcessor ( UpAction up_action , DownAction down_action )
: m_up_action ( up_action ), m_down_action ( down_action )
{
}
void process ( wxKeyEvent & evt )
{
const int keyCode = evt . GetKeyCode ();
wxEventType type = evt . GetEventType ();
if ( type == wxEVT_KEY_UP ) {
switch ( keyCode )
{
case WXK_NUMPAD_LEFT : case WXK_LEFT :
case WXK_NUMPAD_RIGHT : case WXK_RIGHT :
case WXK_NUMPAD_UP : case WXK_UP :
case WXK_NUMPAD_DOWN : case WXK_DOWN :
{
m_running = false ;
m_up_action ();
break ;
}
default : { break ; }
}
}
else if ( type == wxEVT_KEY_DOWN ) {
bool apply = false ;
switch ( keyCode )
{
case WXK_SHIFT :
{
if ( m_running )
apply = true ;
break ;
}
case WXK_NUMPAD_LEFT :
case WXK_LEFT :
{
m_direction = - Vec3d :: UnitX ();
apply = true ;
break ;
}
case WXK_NUMPAD_RIGHT :
case WXK_RIGHT :
{
m_direction = Vec3d :: UnitX ();
apply = true ;
break ;
}
case WXK_NUMPAD_UP :
case WXK_UP :
{
m_direction = Vec3d :: UnitY ();
apply = true ;
break ;
}
case WXK_NUMPAD_DOWN :
case WXK_DOWN :
{
m_direction = - Vec3d :: UnitY ();
apply = true ;
break ;
}
default : { break ; }
}
if ( apply ) {
m_running = true ;
m_down_action ( m_direction , evt . ShiftDown (), evt . CmdDown ());
}
}
}
};
2019-02-15 15:35:32 +01:00
void GLCanvas3D :: on_key ( wxKeyEvent & evt )
2019-02-11 08:21:37 +01:00
{
2020-02-05 13:14:50 +01:00
static TranslationProcessor translationProcessor (
[ this ]() {
do_move ( L ( "Gizmo-Move" ));
m_gizmos . update_data ();
wxGetApp (). obj_manipul () -> set_dirty ();
// Let the plater know that the dragging finished, so a delayed refresh
// of the scene with the background processing data should be performed.
post_event ( SimpleEvent ( EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED ));
// updates camera target constraints
refresh_camera_scene_box ();
m_dirty = true ;
},
[ this ]( const Vec3d & direction , bool slow , bool camera_space ) {
m_selection . start_dragging ();
double multiplier = slow ? 1.0 : 10.0 ;
Vec3d displacement ;
2020-11-19 12:29:19 +01:00
if ( camera_space ) {
2020-03-02 10:58:46 +01:00
Eigen :: Matrix < double , 3 , 3 , Eigen :: DontAlign > inv_view_3x3 = wxGetApp (). plater () -> get_camera (). get_view_matrix (). inverse (). matrix (). block ( 0 , 0 , 3 , 3 );
2020-02-05 13:14:50 +01:00
displacement = multiplier * ( inv_view_3x3 * direction );
displacement ( 2 ) = 0.0 ;
}
else
displacement = multiplier * direction ;
m_selection . translate ( displacement );
m_dirty = true ;
}
);
2019-02-21 18:31:01 +01:00
const int keyCode = evt . GetKeyCode ();
2019-02-15 15:35:32 +01:00
auto imgui = wxGetApp (). imgui ();
if ( imgui -> update_key_data ( evt )) {
render ();
2019-03-26 14:38:30 +01:00
}
else
{
2020-11-19 12:29:19 +01:00
if ( ! m_gizmos . on_key ( evt )) {
2019-03-26 14:38:30 +01:00
if ( evt . GetEventType () == wxEVT_KEY_UP ) {
2020-12-04 15:36:30 +01:00
#if ENABLE_RENDER_STATISTICS
if ( evt . ShiftDown () && evt . ControlDown () && keyCode == WXK_SPACE ) {
wxGetApp (). plater () -> toggle_render_statistic_dialog ();
m_dirty = true ;
}
2019-03-26 14:38:30 +01:00
if ( m_tab_down && keyCode == WXK_TAB && ! evt . HasAnyModifiers ()) {
2020-12-04 15:36:30 +01:00
#else
if ( m_tab_down && keyCode == WXK_TAB && ! evt . HasAnyModifiers ()) {
#endif // ENABLE_RENDER_STATISTICS
2019-03-26 14:38:30 +01:00
// Enable switching between 3D and Preview with Tab
// m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux
post_event ( SimpleEvent ( EVT_GLCANVAS_TAB ));
}
2020-10-28 10:42:42 +01:00
else if ( keyCode == WXK_TAB && evt . ShiftDown () && ! wxGetApp (). is_gcode_viewer ()) {
2020-09-11 20:33:27 +02:00
// Collapse side-panel with Shift+Tab
post_event ( SimpleEvent ( EVT_GLCANVAS_COLLAPSE_SIDEBAR ));
}
2020-11-19 12:29:19 +01:00
else if ( keyCode == WXK_SHIFT ) {
2020-02-05 13:14:50 +01:00
translationProcessor . process ( evt );
2020-11-19 12:29:19 +01:00
if ( m_picking_enabled && m_rectangle_selection . is_dragging ()) {
2019-04-25 12:31:55 +02:00
_update_selection_from_hover ();
2019-04-25 09:10:03 +02:00
m_rectangle_selection . stop_dragging ();
2019-04-26 14:07:46 +02:00
m_mouse . ignore_left_up = true ;
2019-04-25 09:10:03 +02:00
m_dirty = true ;
}
2019-04-29 08:26:08 +02:00
// set_cursor(Standard);
2019-04-24 15:43:52 +02:00
}
2020-11-19 12:29:19 +01:00
else if ( keyCode == WXK_ALT ) {
if ( m_picking_enabled && m_rectangle_selection . is_dragging ()) {
2019-04-25 12:31:55 +02:00
_update_selection_from_hover ();
2019-04-25 09:10:03 +02:00
m_rectangle_selection . stop_dragging ();
2019-04-26 14:07:46 +02:00
m_mouse . ignore_left_up = true ;
2019-04-25 09:10:03 +02:00
m_dirty = true ;
}
2019-04-29 08:26:08 +02:00
// set_cursor(Standard);
2019-04-24 15:43:52 +02:00
}
2019-04-25 09:46:26 +02:00
else if ( keyCode == WXK_CONTROL )
m_dirty = true ;
2020-02-04 11:42:58 +01:00
else if ( m_gizmos . is_enabled () && ! m_selection . is_empty ()) {
2020-02-05 13:14:50 +01:00
translationProcessor . process ( evt );
2020-02-04 11:42:58 +01:00
switch ( keyCode )
{
case WXK_NUMPAD_PAGEUP : case WXK_PAGEUP :
case WXK_NUMPAD_PAGEDOWN : case WXK_PAGEDOWN :
{
do_rotate ( L ( "Gizmo-Rotate" ));
m_gizmos . update_data ();
wxGetApp (). obj_manipul () -> set_dirty ();
// Let the plater know that the dragging finished, so a delayed refresh
// of the scene with the background processing data should be performed.
post_event ( SimpleEvent ( EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED ));
// updates camera target constraints
refresh_camera_scene_box ();
m_dirty = true ;
break ;
}
default : { break ; }
}
}
2019-03-26 14:38:30 +01:00
}
else if ( evt . GetEventType () == wxEVT_KEY_DOWN ) {
m_tab_down = keyCode == WXK_TAB && ! evt . HasAnyModifiers ();
2020-11-19 12:29:19 +01:00
if ( keyCode == WXK_SHIFT ) {
2020-02-05 13:14:50 +01:00
translationProcessor . process ( evt );
2019-04-25 09:10:03 +02:00
if ( m_picking_enabled && ( m_gizmos . get_current_type () != GLGizmosManager :: SlaSupports ))
2019-04-26 14:07:46 +02:00
{
m_mouse . ignore_left_up = false ;
2019-04-29 08:26:08 +02:00
// set_cursor(Cross);
2019-04-26 14:07:46 +02:00
}
2019-04-24 15:43:52 +02:00
}
2020-11-19 12:29:19 +01:00
else if ( keyCode == WXK_ALT ) {
2019-04-25 09:10:03 +02:00
if ( m_picking_enabled && ( m_gizmos . get_current_type () != GLGizmosManager :: SlaSupports ))
2019-04-26 14:07:46 +02:00
{
m_mouse . ignore_left_up = false ;
2019-04-29 08:26:08 +02:00
// set_cursor(Cross);
2019-04-26 14:07:46 +02:00
}
2019-04-24 15:43:52 +02:00
}
2019-04-25 09:46:26 +02:00
else if ( keyCode == WXK_CONTROL )
m_dirty = true ;
2020-11-19 12:29:19 +01:00
else if ( m_gizmos . is_enabled () && ! m_selection . is_empty ()) {
2020-02-04 11:42:58 +01:00
auto do_rotate = [ this ]( double angle_z_rad ) {
m_selection . start_dragging ();
m_selection . rotate ( Vec3d ( 0.0 , 0.0 , angle_z_rad ), TransformationType ( TransformationType :: World_Relative_Joint ));
m_dirty = true ;
// wxGetApp().obj_manipul()->set_dirty();
};
2020-02-05 13:14:50 +01:00
translationProcessor . process ( evt );
2020-02-04 11:42:58 +01:00
switch ( keyCode )
{
case WXK_NUMPAD_PAGEUP : case WXK_PAGEUP : { do_rotate ( 0.25 * M_PI ); break ; }
case WXK_NUMPAD_PAGEDOWN : case WXK_PAGEDOWN : { do_rotate ( - 0.25 * M_PI ); break ; }
default : { break ; }
}
}
2020-11-19 12:29:19 +01:00
else if ( ! m_gizmos . is_enabled ()) {
2020-02-04 11:42:58 +01:00
// DoubleSlider navigation in Preview
if ( keyCode == WXK_LEFT ||
keyCode == WXK_RIGHT ||
keyCode == WXK_UP ||
2020-11-19 12:29:19 +01:00
keyCode == WXK_DOWN ) {
2020-02-04 11:42:58 +01:00
if ( dynamic_cast < Preview *> ( m_canvas -> GetParent ()) != nullptr )
2020-12-01 08:33:06 +01:00
#if ENABLE_ARROW_KEYS_WITH_SLIDERS
post_event ( wxKeyEvent ( EVT_GLCANVAS_MOVE_SLIDERS , evt ));
#else
2020-05-15 12:25:38 +02:00
post_event ( wxKeyEvent ( EVT_GLCANVAS_MOVE_LAYERS_SLIDER , evt ));
2020-12-01 08:33:06 +01:00
#endif // ENABLE_ARROW_KEYS_WITH_SLIDERS
2020-02-04 11:42:58 +01:00
}
2019-06-13 16:10:33 +02:00
}
2019-03-26 14:38:30 +01:00
}
2019-02-21 11:54:18 +01:00
}
2019-02-15 15:35:32 +01:00
}
2019-02-11 08:21:37 +01:00
2019-02-21 18:31:01 +01:00
if ( keyCode != WXK_TAB
&& keyCode != WXK_LEFT
&& keyCode != WXK_UP
&& keyCode != WXK_RIGHT
&& keyCode != WXK_DOWN ) {
evt . Skip (); // Needed to have EVT_CHAR generated as well
}
2019-02-11 08:21:37 +01:00
}
2018-05-28 15:23:01 +02:00
void GLCanvas3D :: on_mouse_wheel ( wxMouseEvent & evt )
{
2020-03-04 11:55:47 +01:00
#ifdef WIN32
// Try to filter out spurious mouse wheel events comming from 3D mouse.
if ( wxGetApp (). plater () -> get_mouse3d_controller (). process_mouse_wheel ())
2019-09-27 14:52:19 +02:00
return ;
2020-03-04 11:55:47 +01:00
#endif
2019-09-27 14:52:19 +02:00
2019-04-09 11:18:40 +02:00
if ( ! m_initialized )
return ;
2018-05-28 15:23:01 +02:00
// Ignore the wheel events if the middle button is pressed.
if ( evt . MiddleIsDown ())
return ;
2019-02-06 08:44:06 +01:00
#if ENABLE_RETINA_GL
const float scale = m_retina_helper -> get_scale_factor ();
evt . SetX ( evt . GetX () * scale );
evt . SetY ( evt . GetY () * scale );
#endif
2020-11-02 10:17:28 +01:00
if ( wxGetApp (). imgui () -> update_mouse_data ( evt )) {
m_dirty = true ;
return ;
}
2019-08-19 19:48:07 +02:00
#ifdef __WXMSW__
// For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad,
// if the event is not allowed to be passed further.
// https://github.com/prusa3d/PrusaSlicer/issues/2750
evt . Skip ();
#endif /* __WXMSW__ */
2018-05-28 15:23:01 +02:00
// Performs layers editing updates, if enabled
2020-11-19 12:29:19 +01:00
if ( is_layers_editing_enabled ()) {
2018-10-09 15:56:34 +02:00
int object_idx_selected = m_selection . get_object_idx ();
2020-11-19 12:29:19 +01:00
if ( object_idx_selected != - 1 ) {
2018-05-28 15:23:01 +02:00
// A volume is selected. Test, whether hovering over a layer thickness bar.
2020-11-19 12:29:19 +01:00
if ( m_layers_editing . bar_rect_contains ( * this , ( float ) evt . GetX (), ( float ) evt . GetY ())) {
2018-05-28 15:23:01 +02:00
// Adjust the width of the selection.
2018-06-01 15:54:41 +02:00
m_layers_editing . band_width = std :: max ( std :: min ( m_layers_editing . band_width * ( 1.0f + 0.1f * ( float ) evt . GetWheelRotation () / ( float ) evt . GetWheelDelta ()), 10.0f ), 1.5f );
2018-05-28 15:23:01 +02:00
if ( m_canvas != nullptr )
m_canvas -> Refresh ();
2019-06-13 10:24:19 +02:00
2018-05-28 15:23:01 +02:00
return ;
}
}
}
2020-04-27 16:00:54 +02:00
// If the Search window or Undo/Redo list is opened,
// update them according to the event
if ( m_main_toolbar . is_item_pressed ( "search" ) ||
m_undoredo_toolbar . is_item_pressed ( "undo" ) ||
m_undoredo_toolbar . is_item_pressed ( "redo" )) {
m_mouse_wheel = int (( double ) evt . GetWheelRotation () / ( double ) evt . GetWheelDelta ());
return ;
}
2019-04-17 10:16:39 +02:00
// Inform gizmos about the event so they have the opportunity to react.
2019-07-17 08:38:48 +02:00
if ( m_gizmos . on_mouse_wheel ( evt ))
2019-04-17 10:16:39 +02:00
return ;
2018-05-28 15:23:01 +02:00
// Calculate the zoom delta and apply it to the current zoom factor
2020-11-02 13:08:13 +01:00
double direction_factor = ( wxGetApp (). app_config -> get ( "reverse_mouse_wheel_zoom" ) == "1" ) ? - 1.0 : 1.0 ;
_update_camera_zoom ( direction_factor * ( double ) evt . GetWheelRotation () / ( double ) evt . GetWheelDelta ());
2018-05-28 15:23:01 +02:00
}
2018-05-30 15:18:45 +02:00
void GLCanvas3D :: on_timer ( wxTimerEvent & evt )
{
2019-01-21 10:06:51 +01:00
if ( m_layers_editing . state == LayersEditing :: Editing )
_perform_layer_editing_action ();
2018-05-30 15:18:45 +02:00
}
2020-12-15 16:03:20 +01:00
void GLCanvas3D :: on_render_timer ( wxTimerEvent & evt )
{
2020-12-16 13:59:16 +01:00
// If slicer is not top window -> restart timer with one second to try again
wxWindow * p = dynamic_cast < wxWindow *> ( wxGetApp (). plater ());
while ( p -> GetParent () != nullptr )
p = p -> GetParent ();
wxTopLevelWindow * top_level_wnd = dynamic_cast < wxTopLevelWindow *> ( p );
if ( ! top_level_wnd -> IsActive ()) {
request_extra_frame_delayed ( 1000 );
return ;
}
//render();
m_dirty = true ;
2021-01-07 12:54:53 +01:00
wxWakeUpIdle ();
2020-12-15 16:03:20 +01:00
}
2020-12-16 16:15:58 +01:00
void GLCanvas3D :: request_extra_frame_delayed ( int miliseconds )
2020-12-15 16:03:20 +01:00
{
2020-12-16 16:15:58 +01:00
int64_t now = timestamp_now ();
if ( ! m_render_timer . IsRunning ()) {
2020-12-15 16:03:20 +01:00
m_extra_frame_requested_delayed = miliseconds ;
2020-12-16 16:15:58 +01:00
m_render_timer . StartOnce ( miliseconds );
m_render_timer_start = now ;
2020-12-15 16:03:20 +01:00
} else {
2020-12-16 16:15:58 +01:00
const int64_t remaining_time = ( m_render_timer_start + m_extra_frame_requested_delayed ) - now ;
if ( miliseconds < remaining_time ) {
2020-12-16 13:59:16 +01:00
m_render_timer . Stop ();
2020-12-15 16:03:20 +01:00
m_extra_frame_requested_delayed = miliseconds ;
2020-12-16 16:15:58 +01:00
m_render_timer . StartOnce ( miliseconds );
m_render_timer_start = now ;
2020-12-15 16:03:20 +01:00
}
}
}
2019-02-25 17:09:44 +01:00
#ifndef NDEBUG
// #define SLIC3R_DEBUG_MOUSE_EVENTS
#endif
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
std :: string format_mouse_event_debug_message ( const wxMouseEvent & evt )
{
static int idx = 0 ;
char buf [ 2048 ];
std :: string out ;
sprintf ( buf , "Mouse Event %d - " , idx ++ );
out = buf ;
if ( evt . Entering ())
out += "Entering " ;
if ( evt . Leaving ())
out += "Leaving " ;
if ( evt . Dragging ())
out += "Dragging " ;
if ( evt . Moving ())
out += "Moving " ;
if ( evt . Magnify ())
out += "Magnify " ;
if ( evt . LeftDown ())
out += "LeftDown " ;
if ( evt . LeftUp ())
out += "LeftUp " ;
if ( evt . LeftDClick ())
out += "LeftDClick " ;
if ( evt . MiddleDown ())
out += "MiddleDown " ;
if ( evt . MiddleUp ())
out += "MiddleUp " ;
if ( evt . MiddleDClick ())
out += "MiddleDClick " ;
if ( evt . RightDown ())
out += "RightDown " ;
if ( evt . RightUp ())
out += "RightUp " ;
if ( evt . RightDClick ())
out += "RightDClick " ;
sprintf ( buf , "(%d, %d)" , evt . GetX (), evt . GetY ());
out += buf ;
return out ;
}
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
2018-05-31 13:51:50 +02:00
void GLCanvas3D :: on_mouse ( wxMouseEvent & evt )
{
2020-03-11 12:19:52 +01:00
if ( ! m_initialized || ! _set_current ())
return ;
2019-01-24 11:30:29 +01:00
#if ENABLE_RETINA_GL
const float scale = m_retina_helper -> get_scale_factor ();
evt . SetX ( evt . GetX () * scale );
evt . SetY ( evt . GetY () * scale );
#endif
2020-03-18 10:46:46 +01:00
Point pos ( evt . GetX (), evt . GetY ());
2019-02-22 16:16:04 +01:00
2020-03-18 10:46:46 +01:00
ImGuiWrapper * imgui = wxGetApp (). imgui ();
2020-03-24 12:26:30 +01:00
if ( m_tooltip . is_in_imgui () && evt . LeftUp ())
// ignore left up events coming from imgui windows and not processed by them
m_mouse . ignore_left_up = true ;
2020-03-18 13:23:07 +01:00
m_tooltip . set_in_imgui ( false );
2018-11-26 10:56:07 +01:00
if ( imgui -> update_mouse_data ( evt )) {
2019-02-22 16:16:04 +01:00
m_mouse . position = evt . Leaving () ? Vec2d ( - 1.0 , - 1.0 ) : pos . cast < double > ();
2020-03-18 13:23:07 +01:00
m_tooltip . set_in_imgui ( true );
2018-11-26 10:56:07 +01:00
render ();
2019-02-25 17:09:44 +01:00
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
2020-03-18 10:46:46 +01:00
printf (( format_mouse_event_debug_message ( evt ) + " - Consumed by ImGUI \n " ). c_str ());
2019-02-25 17:09:44 +01:00
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
2020-08-03 15:36:55 +02:00
m_dirty = true ;
2020-03-18 10:46:46 +01:00
// do not return if dragging or tooltip not empty to allow for tooltip update
2020-03-19 15:12:18 +01:00
if ( ! m_mouse . dragging && m_tooltip . is_empty ())
2020-03-24 12:26:30 +01:00
return ;
2018-11-26 10:56:07 +01:00
}
2018-10-31 10:19:44 +01:00
2019-02-26 11:50:45 +01:00
#ifdef __WXMSW__
2019-02-25 17:09:44 +01:00
bool on_enter_workaround = false ;
2019-02-22 16:16:04 +01:00
if ( ! evt . Entering () && ! evt . Leaving () && m_mouse . position . x () == - 1.0 ) {
// Workaround for SPE-832: There seems to be a mouse event sent to the window before evt.Entering()
m_mouse . position = pos . cast < double > ();
render ();
2019-02-25 17:09:44 +01:00
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
printf (( format_mouse_event_debug_message ( evt ) + " - OnEnter workaround \n " ). c_str ());
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
on_enter_workaround = true ;
2019-02-26 11:50:45 +01:00
} else
#endif /* __WXMSW__ */
{
2019-02-25 17:09:44 +01:00
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
printf (( format_mouse_event_debug_message ( evt ) + " - other \n " ). c_str ());
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
}
2018-05-31 13:51:50 +02:00
2020-10-19 15:55:16 +02:00
if ( m_main_toolbar . on_mouse ( evt , * this )) {
2019-07-19 15:36:55 +02:00
if ( evt . LeftUp () || evt . MiddleUp () || evt . RightUp ())
mouse_up_cleanup ();
m_mouse . set_start_position_3D_as_invalid ();
return ;
}
2020-10-19 15:55:16 +02:00
if ( m_undoredo_toolbar . on_mouse ( evt , * this )) {
2019-03-26 12:30:17 +01:00
if ( evt . LeftUp () || evt . MiddleUp () || evt . RightUp ())
mouse_up_cleanup ();
2019-03-19 09:21:27 +01:00
m_mouse . set_start_position_3D_as_invalid ();
return ;
}
2019-03-14 13:54:05 +01:00
2020-10-19 15:55:16 +02:00
if ( wxGetApp (). plater () -> get_collapse_toolbar (). on_mouse ( evt , * this )) {
2020-04-23 12:41:38 +02:00
if ( evt . LeftUp () || evt . MiddleUp () || evt . RightUp ())
mouse_up_cleanup ();
m_mouse . set_start_position_3D_as_invalid ();
return ;
}
2020-10-19 15:55:16 +02:00
if ( wxGetApp (). plater () -> get_view_toolbar (). on_mouse ( evt , * this )) {
2019-03-26 12:30:17 +01:00
if ( evt . LeftUp () || evt . MiddleUp () || evt . RightUp ())
mouse_up_cleanup ();
m_mouse . set_start_position_3D_as_invalid ();
return ;
}
2020-10-19 15:55:16 +02:00
if ( m_gizmos . on_mouse ( evt )) {
2020-12-15 10:57:16 +01:00
if ( wxWindow :: FindFocus () != this -> m_canvas )
// Grab keyboard focus for input in gizmo dialogs.
m_canvas -> SetFocus ();
2019-03-26 12:30:17 +01:00
if ( evt . LeftUp () || evt . MiddleUp () || evt . RightUp ())
mouse_up_cleanup ();
2019-03-26 12:39:40 +01:00
2019-03-19 09:21:27 +01:00
m_mouse . set_start_position_3D_as_invalid ();
2020-03-17 13:01:38 +01:00
m_mouse . position = pos . cast < double > ();
2019-03-19 09:21:27 +01:00
return ;
}
2019-03-14 13:54:05 +01:00
2020-10-19 15:55:16 +02:00
bool any_gizmo_active = m_gizmos . get_current () != nullptr ;
2018-10-09 15:56:34 +02:00
int selected_object_idx = m_selection . get_object_idx ();
2018-10-08 14:02:12 +02:00
int layer_editing_object_idx = is_layers_editing_enabled () ? selected_object_idx : - 1 ;
2019-01-21 10:06:51 +01:00
m_layers_editing . select_object ( * m_model , layer_editing_object_idx );
2018-05-31 13:51:50 +02:00
2020-10-19 15:55:16 +02:00
if ( m_mouse . drag . move_requires_threshold && m_mouse . is_move_start_threshold_position_2D_defined () && m_mouse . is_move_threshold_met ( pos )) {
2019-01-14 09:29:17 +01:00
m_mouse . drag . move_requires_threshold = false ;
m_mouse . set_move_start_threshold_position_2D_as_invalid ();
}
2019-02-26 11:50:45 +01:00
if ( evt . ButtonDown () && wxWindow :: FindFocus () != this -> m_canvas )
// Grab keyboard focus on any mouse click event.
m_canvas -> SetFocus ();
2020-10-19 15:55:16 +02:00
if ( evt . Entering ()) {
2019-01-08 10:06:10 +01:00
//#if defined(__WXMSW__) || defined(__linux__)
// // On Windows and Linux needs focus in order to catch key events
// Set focus in order to remove it from sidebar fields
2019-01-02 17:49:23 +01:00
if ( m_canvas != nullptr ) {
// Only set focus, if the top level window of this canvas is active.
2019-02-06 12:23:57 +01:00
auto p = dynamic_cast < wxWindow *> ( evt . GetEventObject ());
2019-01-02 17:49:23 +01:00
while ( p -> GetParent ())
p = p -> GetParent ();
auto * top_level_wnd = dynamic_cast < wxTopLevelWindow *> ( p );
if ( top_level_wnd && top_level_wnd -> IsActive ())
m_canvas -> SetFocus ();
2019-03-05 10:54:03 +01:00
m_mouse . position = pos . cast < double > ();
2020-05-07 16:47:15 +02:00
m_tooltip_enabled = false ;
2019-04-24 15:07:28 +02:00
// 1) forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while
2019-02-26 11:50:45 +01:00
// the context menu is shown, ensuring it to disappear if the mouse is outside any volume and to
// change the volume hover state if any is under the mouse
2019-03-05 10:54:03 +01:00
// 2) when switching between 3d view and preview the size of the canvas changes if the side panels are visible,
// so forces a resize to avoid multiple renders with different sizes (seen as flickering)
_refresh_if_shown_on_screen ();
2020-05-07 16:47:15 +02:00
m_tooltip_enabled = true ;
2019-02-06 12:23:57 +01:00
}
2018-06-01 15:54:41 +02:00
m_mouse . set_start_position_2D_as_invalid ();
2019-01-08 10:06:10 +01:00
//#endif
2018-07-26 12:10:45 +02:00
}
2020-10-19 15:55:16 +02:00
else if ( evt . Leaving ()) {
2019-07-11 07:46:40 +02:00
_deactivate_undo_redo_toolbar_items ();
2018-07-30 09:09:14 +02:00
// to remove hover on objects when the mouse goes out of this canvas
2018-08-21 21:05:24 +02:00
m_mouse . position = Vec2d ( - 1.0 , - 1.0 );
2018-07-30 09:09:14 +02:00
m_dirty = true ;
2018-07-26 12:51:31 +02:00
}
2020-10-19 15:55:16 +02:00
else if ( evt . LeftDown () || evt . RightDown () || evt . MiddleDown ()) {
2020-11-20 17:50:10 +01:00
if ( _deactivate_undo_redo_toolbar_items () || _deactivate_search_toolbar_item () || _deactivate_arrange_menu ())
2019-07-11 07:46:40 +02:00
return ;
2018-05-31 13:51:50 +02:00
// If user pressed left or right button we first check whether this happened
// on a volume or not.
2018-06-01 15:54:41 +02:00
m_layers_editing . state = LayersEditing :: Unknown ;
2020-10-19 15:55:16 +02:00
if ( layer_editing_object_idx != - 1 && m_layers_editing . bar_rect_contains ( * this , pos ( 0 ), pos ( 1 ))) {
2018-05-31 13:51:50 +02:00
// A volume is selected and the mouse is inside the layer thickness bar.
// Start editing the layer height.
2018-06-01 15:54:41 +02:00
m_layers_editing . state = LayersEditing :: Editing ;
_perform_layer_editing_action ( & evt );
2018-05-31 13:51:50 +02:00
}
2020-10-19 15:55:16 +02:00
else if ( evt . LeftDown () && ( evt . ShiftDown () || evt . AltDown ()) && m_picking_enabled ) {
2020-04-29 10:58:38 +02:00
if ( m_gizmos . get_current_type () != GLGizmosManager :: SlaSupports
2020-08-31 07:25:12 +02:00
&& m_gizmos . get_current_type () != GLGizmosManager :: FdmSupports
2020-10-19 15:55:16 +02:00
&& m_gizmos . get_current_type () != GLGizmosManager :: Seam ) {
2019-04-25 09:10:03 +02:00
m_rectangle_selection . start_dragging ( m_mouse . position , evt . ShiftDown () ? GLSelectionRectangle :: Select : GLSelectionRectangle :: Deselect );
m_dirty = true ;
}
}
2020-10-19 15:55:16 +02:00
else {
// Select volume in this 3D canvas.
// Don't deselect a volume if layer editing is enabled or any gizmo is active. We want the object to stay selected
// during the scene manipulation.
2020-10-21 09:52:05 +02:00
if ( m_picking_enabled && ( ! any_gizmo_active || ! evt . CmdDown ()) && ( ! m_hover_volume_idxs . empty () || ! is_layers_editing_enabled ())) {
2020-10-19 15:55:16 +02:00
if ( evt . LeftDown () && ! m_hover_volume_idxs . empty ()) {
2019-04-24 15:07:28 +02:00
int volume_idx = get_first_hover_volume_idx ();
bool already_selected = m_selection . contains_volume ( volume_idx );
2019-04-08 08:30:28 +02:00
bool ctrl_down = evt . CmdDown ();
2018-10-30 11:18:15 +01:00
2019-01-03 11:24:03 +01:00
Selection :: IndicesList curr_idxs = m_selection . get_volume_idxs ();
2019-04-08 08:30:28 +02:00
if ( already_selected && ctrl_down )
2019-04-24 15:07:28 +02:00
m_selection . remove ( volume_idx );
2020-10-19 15:55:16 +02:00
else {
2019-04-24 15:07:28 +02:00
m_selection . add ( volume_idx , ! ctrl_down , true );
2019-01-14 09:29:17 +01:00
m_mouse . drag . move_requires_threshold = ! already_selected ;
if ( already_selected )
m_mouse . set_move_start_threshold_position_2D_as_invalid ();
else
m_mouse . drag . move_start_threshold_position_2D = pos ;
2018-10-30 11:18:15 +01:00
}
2018-10-08 14:02:12 +02:00
2019-04-24 15:07:28 +02:00
// propagate event through callback
2020-10-19 15:55:16 +02:00
if ( curr_idxs != m_selection . get_volume_idxs ()) {
2019-05-09 10:09:33 +02:00
if ( m_selection . is_empty ())
m_gizmos . reset_all_states ();
else
2019-07-17 08:38:48 +02:00
m_gizmos . refresh_on_off_state ();
2019-05-09 10:09:33 +02:00
2019-07-17 08:38:48 +02:00
m_gizmos . update_data ();
2019-01-03 11:24:03 +01:00
post_event ( SimpleEvent ( EVT_GLCANVAS_OBJECT_SELECT ));
m_dirty = true ;
}
2018-10-08 14:02:12 +02:00
}
2018-05-31 13:51:50 +02:00
}
2020-10-19 15:55:16 +02:00
if ( ! m_hover_volume_idxs . empty ()) {
if ( evt . LeftDown () && m_moving_enabled && m_mouse . drag . move_volume_idx == - 1 ) {
2018-05-31 13:51:50 +02:00
// Only accept the initial position, if it is inside the volume bounding box.
2019-04-24 15:07:28 +02:00
int volume_idx = get_first_hover_volume_idx ();
BoundingBoxf3 volume_bbox = m_volumes . volumes [ volume_idx ] -> transformed_bounding_box ();
2018-05-31 13:51:50 +02:00
volume_bbox . offset ( 1.0 );
2020-10-21 09:52:05 +02:00
if (( ! any_gizmo_active || ! evt . CmdDown ()) && volume_bbox . contains ( m_mouse . scene_position )) {
2019-11-12 09:14:42 +01:00
m_volumes . volumes [ volume_idx ] -> hover = GLVolume :: HS_None ;
2018-05-31 13:51:50 +02:00
// The dragging operation is initiated.
2019-04-24 15:07:28 +02:00
m_mouse . drag . move_volume_idx = volume_idx ;
2018-10-08 14:02:12 +02:00
m_selection . start_dragging ();
2018-11-15 11:38:40 +01:00
m_mouse . drag . start_position_3D = m_mouse . scene_position ;
2018-11-26 10:41:16 +01:00
m_moving = true ;
2018-05-31 13:51:50 +02:00
}
}
}
}
}
2020-10-19 15:55:16 +02:00
else if ( evt . Dragging () && evt . LeftIsDown () && m_layers_editing . state == LayersEditing :: Unknown && m_mouse . drag . move_volume_idx != - 1 ) {
if ( ! m_mouse . drag . move_requires_threshold ) {
2019-01-14 09:29:17 +01:00
m_mouse . dragging = true ;
2019-02-22 11:34:37 +01:00
Vec3d cur_pos = m_mouse . drag . start_position_3D ;
2019-01-14 09:29:17 +01:00
// we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag
2020-10-19 15:55:16 +02:00
if ( m_selection . contains_volume ( get_first_hover_volume_idx ())) {
2020-03-02 10:58:46 +01:00
const Camera & camera = wxGetApp (). plater () -> get_camera ();
2020-10-19 15:55:16 +02:00
if ( std :: abs ( camera . get_dir_forward ()( 2 )) < EPSILON ) {
2019-02-22 11:34:37 +01:00
// side view -> move selected volumes orthogonally to camera view direction
Linef3 ray = mouse_ray ( pos );
Vec3d dir = ray . unit_vector ();
// finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
// in our case plane normal and ray direction are the same (orthogonal view)
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
Vec3d inters = ray . a + ( m_mouse . drag . start_position_3D - ray . a ). dot ( dir ) / dir . squaredNorm () * dir ;
// vector from the starting position to the found intersection
Vec3d inters_vec = inters - m_mouse . drag . start_position_3D ;
2020-03-02 10:58:46 +01:00
Vec3d camera_right = camera . get_dir_right ();
Vec3d camera_up = camera . get_dir_up ();
2019-02-22 11:34:37 +01:00
2019-03-06 12:09:20 +01:00
// finds projection of the vector along the camera axes
double projection_x = inters_vec . dot ( camera_right );
double projection_z = inters_vec . dot ( camera_up );
2019-02-22 11:34:37 +01:00
2019-03-06 12:09:20 +01:00
// apply offset
cur_pos = m_mouse . drag . start_position_3D + projection_x * camera_right + projection_z * camera_up ;
2019-02-22 11:34:37 +01:00
}
2020-10-19 15:55:16 +02:00
else {
2019-02-22 11:34:37 +01:00
// Generic view
// Get new position at the same Z of the initial click point.
float z0 = 0.0f ;
float z1 = 1.0f ;
cur_pos = Linef3 ( _mouse_to_3d ( pos , & z0 ), _mouse_to_3d ( pos , & z1 )). intersect_plane ( m_mouse . drag . start_position_3D ( 2 ));
}
}
2018-05-31 16:04:59 +02:00
2019-01-14 09:29:17 +01:00
m_selection . translate ( cur_pos - m_mouse . drag . start_position_3D );
2019-05-03 12:36:26 +02:00
wxGetApp (). obj_manipul () -> set_dirty ();
2019-01-14 09:29:17 +01:00
m_dirty = true ;
}
2018-05-31 16:04:59 +02:00
}
2020-10-19 15:55:16 +02:00
else if ( evt . Dragging () && evt . LeftIsDown () && m_picking_enabled && m_rectangle_selection . is_dragging ()) {
2019-04-25 09:10:03 +02:00
m_rectangle_selection . dragging ( pos . cast < double > ());
m_dirty = true ;
}
2020-10-19 15:55:16 +02:00
else if ( evt . Dragging ()) {
2018-06-01 15:54:41 +02:00
m_mouse . dragging = true ;
2020-10-19 15:55:16 +02:00
if ( m_layers_editing . state != LayersEditing :: Unknown && layer_editing_object_idx != - 1 ) {
if ( m_layers_editing . state == LayersEditing :: Editing ) {
2018-06-01 15:54:41 +02:00
_perform_layer_editing_action ( & evt );
2020-03-23 12:41:16 +01:00
m_mouse . position = pos . cast < double > ();
}
2018-05-31 16:04:59 +02:00
}
2019-03-01 12:23:33 +01:00
// do not process the dragging if the left mouse was set down in another canvas
2020-10-19 15:55:16 +02:00
else if ( evt . LeftIsDown ()) {
2018-06-01 09:00:30 +02:00
// if dragging over blank area with left button, rotate
2020-10-19 15:55:16 +02:00
if (( any_gizmo_active || m_hover_volume_idxs . empty ()) && m_mouse . is_start_position_3D_defined ()) {
2020-02-28 14:59:59 +01:00
const Vec3d rot = ( Vec3d ( pos . x (), pos . y (), 0. ) - m_mouse . drag . start_position_3D ) * ( PI * TRACKBALLSIZE / 180. );
2020-03-12 09:29:24 +01:00
if ( wxGetApp (). app_config -> get ( "use_free_camera" ) == "1" )
// Virtual track ball (similar to the 3DConnexion mouse).
2020-03-02 11:35:03 +01:00
wxGetApp (). plater () -> get_camera (). rotate_local_around_target ( Vec3d ( rot . y (), rot . x (), 0. ));
2020-10-19 15:55:16 +02:00
else {
2020-03-25 14:41:47 +01:00
// Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
// It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
// which checks an atomics (flushes CPU caches).
// See GH issue #3816.
Camera & camera = wxGetApp (). plater () -> get_camera ();
camera . recover_from_free_camera ();
camera . rotate_on_sphere ( rot . x (), rot . y (), wxGetApp (). preset_bundle -> printers . get_edited_preset (). printer_technology () != ptSLA );
2020-03-12 09:29:24 +01:00
}
2020-02-13 12:13:54 +01:00
2018-06-01 15:54:41 +02:00
m_dirty = true ;
2018-06-01 09:00:30 +02:00
}
2018-08-24 10:20:00 +02:00
m_mouse . drag . start_position_3D = Vec3d (( double ) pos ( 0 ), ( double ) pos ( 1 ), 0.0 );
2018-06-01 09:00:30 +02:00
}
2020-10-19 15:55:16 +02:00
else if ( evt . MiddleIsDown () || evt . RightIsDown ()) {
2018-06-01 09:00:30 +02:00
// If dragging over blank area with right button, pan.
2020-10-19 15:55:16 +02:00
if ( m_mouse . is_start_position_2D_defined ()) {
2018-06-01 09:00:30 +02:00
// get point in model space at Z = 0
float z = 0.0f ;
2018-08-21 17:43:05 +02:00
const Vec3d & cur_pos = _mouse_to_3d ( pos , & z );
Vec3d orig = _mouse_to_3d ( m_mouse . drag . start_position_2D , & z );
2020-03-02 10:58:46 +01:00
Camera & camera = wxGetApp (). plater () -> get_camera ();
2020-03-12 09:29:24 +01:00
if ( wxGetApp (). app_config -> get ( "use_free_camera" ) != "1" )
2020-03-25 14:41:47 +01:00
// Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
// It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
// which checks an atomics (flushes CPU caches).
// See GH issue #3816.
camera . recover_from_free_camera ();
2020-03-02 10:58:46 +01:00
camera . set_target ( camera . get_target () + orig - cur_pos );
2018-06-01 15:54:41 +02:00
m_dirty = true ;
2018-06-01 09:00:30 +02:00
}
2020-03-25 14:41:47 +01:00
2018-06-01 15:54:41 +02:00
m_mouse . drag . start_position_2D = pos ;
2018-06-01 09:00:30 +02:00
}
2018-05-31 16:04:59 +02:00
}
2020-10-19 15:55:16 +02:00
else if ( evt . LeftUp () || evt . MiddleUp () || evt . RightUp ()) {
if ( m_layers_editing . state != LayersEditing :: Unknown ) {
2018-06-01 15:54:41 +02:00
m_layers_editing . state = LayersEditing :: Unknown ;
_stop_timer ();
2019-01-21 10:06:51 +01:00
m_layers_editing . accept_changes ( * this );
2018-05-31 16:04:59 +02:00
}
2020-10-19 15:55:16 +02:00
else if ( m_mouse . drag . move_volume_idx != - 1 && m_mouse . dragging ) {
2019-07-17 14:13:50 +02:00
do_move ( L ( "Move Object" ));
2019-05-03 12:36:26 +02:00
wxGetApp (). obj_manipul () -> set_dirty ();
2019-07-05 19:06:19 +02:00
// Let the plater know that the dragging finished, so a delayed refresh
2018-11-26 08:36:31 +01:00
// of the scene with the background processing data should be performed.
post_event ( SimpleEvent ( EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED ));
2018-05-31 16:04:59 +02:00
}
2020-10-19 15:55:16 +02:00
else if ( evt . LeftUp () && m_picking_enabled && m_rectangle_selection . is_dragging ()) {
2019-04-25 12:31:55 +02:00
if ( evt . ShiftDown () || evt . AltDown ())
_update_selection_from_hover ();
2019-04-25 09:10:03 +02:00
m_rectangle_selection . stop_dragging ();
}
2020-10-19 15:55:16 +02:00
else if ( evt . LeftUp () && ! m_mouse . ignore_left_up && ! m_mouse . dragging && m_hover_volume_idxs . empty () && ! is_layers_editing_enabled ()) {
2018-06-14 12:34:19 +02:00
// deselect and propagate event through callback
2020-10-21 13:17:41 +02:00
if ( ! evt . ShiftDown () && ( ! any_gizmo_active || ! evt . CmdDown ()) && m_picking_enabled )
2019-05-14 11:57:39 +02:00
deselect_all ();
2018-06-15 14:10:28 +02:00
}
2020-10-19 15:55:16 +02:00
else if ( evt . RightUp ()) {
2019-03-18 10:47:01 +01:00
m_mouse . position = pos . cast < double > ();
2019-04-24 15:07:28 +02:00
// forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while
2019-03-18 10:47:01 +01:00
// the context menu is already shown
render ();
2020-10-19 15:55:16 +02:00
if ( ! m_hover_volume_idxs . empty ()) {
2019-03-18 10:47:01 +01:00
// if right clicking on volume, propagate event through callback (shows context menu)
2019-04-24 15:07:28 +02:00
int volume_idx = get_first_hover_volume_idx ();
if ( ! m_volumes . volumes [ volume_idx ] -> is_wipe_tower // no context menu for the wipe tower
2019-03-20 13:51:25 +01:00
&& m_gizmos . get_current_type () != GLGizmosManager :: SlaSupports ) // disable context menu when the gizmo is open
2019-03-18 10:47:01 +01:00
{
// forces the selection of the volume
2019-08-09 09:49:10 +02:00
/* m_selection.add(volume_idx); // #et_FIXME_if_needed
* To avoid extra "Add-Selection" snapshots,
* call add() with check_for_already_contained=true
* */
m_selection . add ( volume_idx , true , true );
2019-07-17 08:38:48 +02:00
m_gizmos . refresh_on_off_state ();
2019-03-18 10:47:01 +01:00
post_event ( SimpleEvent ( EVT_GLCANVAS_OBJECT_SELECT ));
2019-07-17 08:38:48 +02:00
m_gizmos . update_data ();
2019-05-03 12:36:26 +02:00
wxGetApp (). obj_manipul () -> set_dirty ();
2019-03-18 10:47:01 +01:00
// forces a frame render to update the view before the context menu is shown
render ();
}
}
2019-09-20 09:57:27 +02:00
Vec2d logical_pos = pos . cast < double > ();
#if ENABLE_RETINA_GL
const float factor = m_retina_helper -> get_scale_factor ();
logical_pos = logical_pos . cwiseQuotient ( Vec2d ( factor , factor ));
#endif // ENABLE_RETINA_GL
2020-11-23 13:45:12 +01:00
if ( ! m_mouse . dragging ) {
2019-09-20 11:19:06 +02:00
// do not post the event if the user is panning the scene
2020-11-23 13:45:12 +01:00
// or if right click was done over the wipe tower
bool post_right_click_event = m_hover_volume_idxs . empty () || ! m_volumes . volumes [ get_first_hover_volume_idx ()] -> is_wipe_tower ;
if ( post_right_click_event )
post_event ( RBtnEvent ( EVT_GLCANVAS_RIGHT_CLICK , { logical_pos , m_hover_volume_idxs . empty () }));
}
2019-03-18 10:47:01 +01:00
}
2018-06-14 12:34:19 +02:00
2019-03-26 12:30:17 +01:00
mouse_up_cleanup ();
2018-05-31 16:04:59 +02:00
}
2020-10-19 15:55:16 +02:00
else if ( evt . Moving ()) {
2018-12-06 10:38:19 +01:00
m_mouse . position = pos . cast < double > ();
2019-03-26 09:10:35 +01:00
// updates gizmos overlay
2019-03-26 09:01:04 +01:00
if ( m_selection . is_empty ())
m_gizmos . reset_all_states ();
2020-04-27 14:10:18 +02:00
m_dirty = true ;
2018-05-31 13:51:50 +02:00
}
else
evt . Skip ();
2019-02-25 17:09:44 +01:00
2019-02-26 11:50:45 +01:00
#ifdef __WXMSW__
2019-02-25 17:09:44 +01:00
if ( on_enter_workaround )
m_mouse . position = Vec2d ( - 1. , - 1. );
2019-02-26 11:50:45 +01:00
#endif /* __WXMSW__ */
2018-05-31 13:51:50 +02:00
}
2018-06-04 12:26:39 +02:00
void GLCanvas3D :: on_paint ( wxPaintEvent & evt )
{
2019-01-30 19:48:26 +01:00
if ( m_initialized )
m_dirty = true ;
else
// Call render directly, so it gets initialized immediately, not from On Idle handler.
this -> render ();
2018-06-04 12:26:39 +02:00
}
2020-05-07 16:47:15 +02:00
void GLCanvas3D :: on_set_focus ( wxFocusEvent & evt )
{
m_tooltip_enabled = false ;
_refresh_if_shown_on_screen ();
m_tooltip_enabled = true ;
}
2018-05-24 13:46:17 +02:00
Size GLCanvas3D :: get_canvas_size () const
{
int w = 0 ;
int h = 0 ;
if ( m_canvas != nullptr )
m_canvas -> GetSize ( & w , & h );
2019-01-24 11:30:29 +01:00
#if ENABLE_RETINA_GL
const float factor = m_retina_helper -> get_scale_factor ();
w *= factor ;
h *= factor ;
#else
2019-04-01 11:08:26 +02:00
const float factor = 1.0f ;
2019-01-24 11:30:29 +01:00
#endif
return Size ( w , h , factor );
2018-05-24 13:46:17 +02:00
}
2019-04-01 11:08:26 +02:00
Vec2d GLCanvas3D :: get_local_mouse_position () const
2018-05-25 15:56:14 +02:00
{
if ( m_canvas == nullptr )
2019-04-01 11:08:26 +02:00
return Vec2d :: Zero ();
2018-05-25 15:56:14 +02:00
wxPoint mouse_pos = m_canvas -> ScreenToClient ( wxGetMousePosition ());
2019-04-01 11:08:26 +02:00
const double factor =
#if ENABLE_RETINA_GL
m_retina_helper -> get_scale_factor ();
#else
1.0 ;
#endif
return Vec2d ( factor * mouse_pos . x , factor * mouse_pos . y );
2018-05-25 15:56:14 +02:00
}
2018-10-25 09:35:08 +02:00
void GLCanvas3D :: set_tooltip ( const std :: string & tooltip ) const
2018-07-25 10:01:17 +02:00
{
if ( m_canvas != nullptr )
2020-03-18 13:35:46 +01:00
m_tooltip . set_text ( tooltip );
2018-07-25 10:01:17 +02:00
}
2019-07-09 10:18:57 +02:00
void GLCanvas3D :: do_move ( const std :: string & snapshot_type )
2018-11-21 10:36:09 +01:00
{
if ( m_model == nullptr )
return ;
2019-07-09 10:18:57 +02:00
if ( ! snapshot_type . empty ())
2019-07-17 14:13:50 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( snapshot_type ));
2019-07-09 10:18:57 +02:00
2018-11-21 10:36:09 +01:00
std :: set < std :: pair < int , int >> done ; // keeps track of modified instances
bool object_moved = false ;
Vec3d wipe_tower_origin = Vec3d :: Zero ();
Selection :: EMode selection_mode = m_selection . get_mode ();
for ( const GLVolume * v : m_volumes . volumes )
{
int object_idx = v -> object_idx ();
int instance_idx = v -> instance_idx ();
int volume_idx = v -> volume_idx ();
std :: pair < int , int > done_id ( object_idx , instance_idx );
if (( 0 <= object_idx ) && ( object_idx < ( int ) m_model -> objects . size ()))
{
done . insert ( done_id );
// Move instances/volumes
ModelObject * model_object = m_model -> objects [ object_idx ];
if ( model_object != nullptr )
{
2019-01-03 11:24:03 +01:00
if ( selection_mode == Selection :: Instance )
model_object -> instances [ instance_idx ] -> set_offset ( v -> get_instance_offset ());
else if ( selection_mode == Selection :: Volume )
model_object -> volumes [ volume_idx ] -> set_offset ( v -> get_volume_offset ());
object_moved = true ;
model_object -> invalidate_bounding_box ();
2018-11-21 10:36:09 +01:00
}
}
else if ( object_idx == 1000 )
// Move a wipe tower proxy.
wipe_tower_origin = v -> get_volume_offset ();
}
// Fixes sinking/flying instances
for ( const std :: pair < int , int >& i : done )
{
ModelObject * m = m_model -> objects [ i . first ];
Vec3d shift ( 0.0 , 0.0 , - m -> get_instance_min_z ( i . second ));
m_selection . translate ( i . first , i . second , shift );
m -> translate_instance ( i . second , shift );
}
if ( object_moved )
post_event ( SimpleEvent ( EVT_GLCANVAS_INSTANCE_MOVED ));
if ( wipe_tower_origin != Vec3d :: Zero ())
post_event ( Vec3dEvent ( EVT_GLCANVAS_WIPETOWER_MOVED , std :: move ( wipe_tower_origin )));
2019-07-09 10:18:57 +02:00
m_dirty = true ;
2018-11-21 10:36:09 +01:00
}
2019-07-09 10:18:57 +02:00
void GLCanvas3D :: do_rotate ( const std :: string & snapshot_type )
2018-11-21 10:36:09 +01:00
{
if ( m_model == nullptr )
return ;
2019-07-09 10:18:57 +02:00
if ( ! snapshot_type . empty ())
2019-07-17 14:13:50 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( snapshot_type ));
2019-07-09 10:18:57 +02:00
2018-11-21 10:36:09 +01:00
std :: set < std :: pair < int , int >> done ; // keeps track of modified instances
Selection :: EMode selection_mode = m_selection . get_mode ();
for ( const GLVolume * v : m_volumes . volumes )
{
int object_idx = v -> object_idx ();
2019-04-26 15:34:26 +02:00
if ( object_idx == 1000 ) { // the wipe tower
Vec3d offset = v -> get_volume_offset ();
post_event ( Vec3dEvent ( EVT_GLCANVAS_WIPETOWER_ROTATED , Vec3d ( offset ( 0 ), offset ( 1 ), v -> get_volume_rotation ()( 2 ))));
}
2018-11-21 10:36:09 +01:00
if (( object_idx < 0 ) || (( int ) m_model -> objects . size () <= object_idx ))
continue ;
int instance_idx = v -> instance_idx ();
int volume_idx = v -> volume_idx ();
done . insert ( std :: pair < int , int > ( object_idx , instance_idx ));
// Rotate instances/volumes.
ModelObject * model_object = m_model -> objects [ object_idx ];
if ( model_object != nullptr )
{
if ( selection_mode == Selection :: Instance )
{
model_object -> instances [ instance_idx ] -> set_rotation ( v -> get_instance_rotation ());
model_object -> instances [ instance_idx ] -> set_offset ( v -> get_instance_offset ());
}
else if ( selection_mode == Selection :: Volume )
{
model_object -> volumes [ volume_idx ] -> set_rotation ( v -> get_volume_rotation ());
model_object -> volumes [ volume_idx ] -> set_offset ( v -> get_volume_offset ());
}
model_object -> invalidate_bounding_box ();
}
}
// Fixes sinking/flying instances
for ( const std :: pair < int , int >& i : done )
{
ModelObject * m = m_model -> objects [ i . first ];
Vec3d shift ( 0.0 , 0.0 , - m -> get_instance_min_z ( i . second ));
m_selection . translate ( i . first , i . second , shift );
m -> translate_instance ( i . second , shift );
}
2019-01-03 11:24:03 +01:00
if ( ! done . empty ())
post_event ( SimpleEvent ( EVT_GLCANVAS_INSTANCE_ROTATED ));
2019-07-09 10:18:57 +02:00
m_dirty = true ;
2018-11-21 10:36:09 +01:00
}
2019-07-09 10:18:57 +02:00
void GLCanvas3D :: do_scale ( const std :: string & snapshot_type )
2018-11-21 10:36:09 +01:00
{
if ( m_model == nullptr )
return ;
2019-07-09 10:18:57 +02:00
if ( ! snapshot_type . empty ())
2019-07-17 14:13:50 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( snapshot_type ));
2019-07-09 10:18:57 +02:00
2018-11-21 10:36:09 +01:00
std :: set < std :: pair < int , int >> done ; // keeps track of modified instances
Selection :: EMode selection_mode = m_selection . get_mode ();
for ( const GLVolume * v : m_volumes . volumes )
{
int object_idx = v -> object_idx ();
if (( object_idx < 0 ) || (( int ) m_model -> objects . size () <= object_idx ))
continue ;
int instance_idx = v -> instance_idx ();
int volume_idx = v -> volume_idx ();
done . insert ( std :: pair < int , int > ( object_idx , instance_idx ));
// Rotate instances/volumes
ModelObject * model_object = m_model -> objects [ object_idx ];
if ( model_object != nullptr )
{
if ( selection_mode == Selection :: Instance )
{
model_object -> instances [ instance_idx ] -> set_scaling_factor ( v -> get_instance_scaling_factor ());
model_object -> instances [ instance_idx ] -> set_offset ( v -> get_instance_offset ());
}
else if ( selection_mode == Selection :: Volume )
{
model_object -> instances [ instance_idx ] -> set_offset ( v -> get_instance_offset ());
model_object -> volumes [ volume_idx ] -> set_scaling_factor ( v -> get_volume_scaling_factor ());
model_object -> volumes [ volume_idx ] -> set_offset ( v -> get_volume_offset ());
}
model_object -> invalidate_bounding_box ();
}
}
// Fixes sinking/flying instances
for ( const std :: pair < int , int >& i : done )
{
ModelObject * m = m_model -> objects [ i . first ];
Vec3d shift ( 0.0 , 0.0 , - m -> get_instance_min_z ( i . second ));
m_selection . translate ( i . first , i . second , shift );
m -> translate_instance ( i . second , shift );
}
2019-01-03 11:24:03 +01:00
if ( ! done . empty ())
post_event ( SimpleEvent ( EVT_GLCANVAS_INSTANCE_ROTATED ));
2019-07-09 10:18:57 +02:00
m_dirty = true ;
2018-11-21 10:36:09 +01:00
}
2019-07-09 10:18:57 +02:00
void GLCanvas3D :: do_flatten ( const Vec3d & normal , const std :: string & snapshot_type )
2018-11-21 10:36:09 +01:00
{
2019-07-09 10:18:57 +02:00
if ( ! snapshot_type . empty ())
2019-07-17 14:13:50 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( snapshot_type ));
2019-07-09 10:18:57 +02:00
m_selection . flattening_rotate ( normal );
2019-08-06 10:01:10 +02:00
do_rotate ( "" ); // avoid taking another snapshot
2018-11-21 10:36:09 +01:00
}
2019-07-09 10:18:57 +02:00
void GLCanvas3D :: do_mirror ( const std :: string & snapshot_type )
2018-11-21 10:36:09 +01:00
{
if ( m_model == nullptr )
return ;
2019-07-09 10:18:57 +02:00
if ( ! snapshot_type . empty ())
2019-07-17 14:13:50 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( snapshot_type ));
2019-07-09 10:18:57 +02:00
2018-11-21 10:36:09 +01:00
std :: set < std :: pair < int , int >> done ; // keeps track of modified instances
Selection :: EMode selection_mode = m_selection . get_mode ();
for ( const GLVolume * v : m_volumes . volumes )
{
int object_idx = v -> object_idx ();
if (( object_idx < 0 ) || (( int ) m_model -> objects . size () <= object_idx ))
continue ;
int instance_idx = v -> instance_idx ();
int volume_idx = v -> volume_idx ();
done . insert ( std :: pair < int , int > ( object_idx , instance_idx ));
// Mirror instances/volumes
ModelObject * model_object = m_model -> objects [ object_idx ];
if ( model_object != nullptr )
{
if ( selection_mode == Selection :: Instance )
model_object -> instances [ instance_idx ] -> set_mirror ( v -> get_instance_mirror ());
else if ( selection_mode == Selection :: Volume )
model_object -> volumes [ volume_idx ] -> set_mirror ( v -> get_volume_mirror ());
2019-01-02 10:49:13 +01:00
2018-11-21 10:36:09 +01:00
model_object -> invalidate_bounding_box ();
}
}
2018-12-10 09:46:01 +01:00
// Fixes sinking/flying instances
for ( const std :: pair < int , int >& i : done )
{
ModelObject * m = m_model -> objects [ i . first ];
Vec3d shift ( 0.0 , 0.0 , - m -> get_instance_min_z ( i . second ));
m_selection . translate ( i . first , i . second , shift );
m -> translate_instance ( i . second , shift );
}
2018-11-21 10:36:09 +01:00
post_event ( SimpleEvent ( EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS ));
2019-07-09 10:18:57 +02:00
m_dirty = true ;
2018-11-21 10:36:09 +01:00
}
2018-12-03 13:29:07 +01:00
void GLCanvas3D :: update_gizmos_on_off_state ()
{
set_as_dirty ();
2019-07-17 08:38:48 +02:00
m_gizmos . update_data ();
m_gizmos . refresh_on_off_state ();
2018-12-03 13:29:07 +01:00
}
2018-12-18 10:40:53 +01:00
void GLCanvas3D :: handle_sidebar_focus_event ( const std :: string & opt_key , bool focus_on )
{
m_sidebar_field = focus_on ? opt_key : "" ;
if ( ! m_sidebar_field . empty ())
m_gizmos . reset_all_states ();
2019-09-25 13:05:18 +02:00
m_dirty = true ;
2018-12-18 10:40:53 +01:00
}
2019-06-26 13:30:20 +02:00
void GLCanvas3D :: handle_layers_data_focus_event ( const t_layer_height_range range , const EditorType type )
{
2019-06-27 13:42:50 +02:00
std :: string field = "layer_" + std :: to_string ( type ) + "_" + std :: to_string ( range . first ) + "_" + std :: to_string ( range . second );
handle_sidebar_focus_event ( field , true );
2019-06-26 13:30:20 +02:00
}
2019-01-24 13:16:46 +01:00
void GLCanvas3D :: update_ui_from_settings ()
{
2019-06-24 15:55:14 +02:00
m_dirty = true ;
2020-12-15 12:14:30 +01:00
#if __APPLE__
// Update OpenGL scaling on OSX after the user toggled the "use_retina_opengl" settings in Preferences dialog.
2019-01-24 13:16:46 +01:00
const float orig_scaling = m_retina_helper -> get_scale_factor ();
const bool use_retina = wxGetApp (). app_config -> get ( "use_retina_opengl" ) == "1" ;
BOOST_LOG_TRIVIAL ( debug ) << "GLCanvas3D: Use Retina OpenGL: " << use_retina ;
m_retina_helper -> set_use_retina ( use_retina );
const float new_scaling = m_retina_helper -> get_scale_factor ();
if ( new_scaling != orig_scaling ) {
BOOST_LOG_TRIVIAL ( debug ) << "GLCanvas3D: Scaling factor: " << new_scaling ;
2020-03-25 15:30:25 +01:00
Camera & camera = wxGetApp (). plater () -> get_camera ();
camera . set_zoom ( camera . get_zoom () * new_scaling / orig_scaling );
2019-01-24 13:16:46 +01:00
_refresh_if_shown_on_screen ();
}
2020-05-06 12:19:34 +02:00
#endif // ENABLE_RETINA_GL
2020-05-04 22:30:38 +02:00
2020-09-08 11:40:06 +02:00
if ( wxGetApp (). is_editor ())
2020-10-06 15:11:08 +02:00
wxGetApp (). plater () -> enable_collapse_toolbar ( wxGetApp (). app_config -> get ( "show_collapse_button" ) == "1" );
2019-01-24 13:16:46 +01:00
}
2019-06-27 21:13:44 +02:00
GLCanvas3D :: WipeTowerInfo GLCanvas3D :: get_wipe_tower_info () const
2019-04-29 14:32:02 +02:00
{
2019-06-27 21:13:44 +02:00
WipeTowerInfo wti ;
2019-04-29 14:32:02 +02:00
for ( const GLVolume * vol : m_volumes . volumes ) {
if ( vol -> is_wipe_tower ) {
2019-06-27 21:13:44 +02:00
wti . m_pos = Vec2d ( m_config -> opt_float ( "wipe_tower_x" ),
2019-04-29 14:32:02 +02:00
m_config -> opt_float ( "wipe_tower_y" ));
2019-07-17 18:10:08 +02:00
wti . m_rotation = ( M_PI / 180. ) * m_config -> opt_float ( "wipe_tower_rotation_angle" );
2019-07-01 13:26:06 +02:00
const BoundingBoxf3 & bb = vol -> bounding_box ();
2020-12-07 18:00:10 +01:00
wti . m_bb = BoundingBoxf { to_2d ( bb . min ), to_2d ( bb . max )};
2019-04-29 14:32:02 +02:00
break ;
}
}
2019-06-27 21:13:44 +02:00
2019-04-29 14:32:02 +02:00
return wti ;
}
2019-03-26 12:30:17 +01:00
Linef3 GLCanvas3D :: mouse_ray ( const Point & mouse_pos )
{
float z0 = 0.0f ;
float z1 = 1.0f ;
return Linef3 ( _mouse_to_3d ( mouse_pos , & z0 ), _mouse_to_3d ( mouse_pos , & z1 ));
}
2019-04-12 15:31:33 +02:00
double GLCanvas3D :: get_size_proportional_to_max_bed_size ( double factor ) const
{
2020-03-02 10:58:46 +01:00
return factor * wxGetApp (). plater () -> get_bed (). get_bounding_box ( false ). max_size ();
2019-04-12 15:31:33 +02:00
}
2019-04-24 15:43:52 +02:00
void GLCanvas3D :: set_cursor ( ECursorType type )
{
if (( m_canvas != nullptr ) && ( m_cursor_type != type ))
{
switch ( type )
{
case Standard : { m_canvas -> SetCursor ( * wxSTANDARD_CURSOR ); break ; }
case Cross : { m_canvas -> SetCursor ( * wxCROSS_CURSOR ); break ; }
}
m_cursor_type = type ;
}
}
2019-04-25 01:45:00 +02:00
void GLCanvas3D :: msw_rescale ()
2019-04-16 12:11:48 +02:00
{
2019-04-26 13:07:31 +02:00
m_warning_texture . msw_rescale ( * this );
2019-04-16 12:11:48 +02:00
}
2020-05-05 17:23:58 +02:00
void GLCanvas3D :: update_tooltip_for_settings_item_in_main_toolbar ()
{
std :: string new_tooltip = _u8L ( "Switch to Settings" ) +
" \n " + "[" + GUI :: shortkey_ctrl_prefix () + "2] - " + _u8L ( "Print Settings Tab" ) +
" \n " + "[" + GUI :: shortkey_ctrl_prefix () + "3] - " + ( m_process -> current_printer_technology () == ptFFF ? _u8L ( "Filament Settings Tab" ) : _u8L ( "Material Settings Tab" )) +
" \n " + "[" + GUI :: shortkey_ctrl_prefix () + "4] - " + _u8L ( "Printer Settings Tab" ) ;
m_main_toolbar . set_tooltip ( get_main_toolbar_item_id ( "settings" ), new_tooltip );
}
2019-08-20 11:33:58 +02:00
bool GLCanvas3D :: has_toolpaths_to_export () const
{
2021-01-13 10:45:52 +01:00
#if ENABLE_SPLITTED_VERTEX_BUFFER
return m_gcode_viewer . can_export_toolpaths ();
#else
2020-07-03 12:17:12 +02:00
return m_gcode_viewer . has_data ();
2021-01-13 10:45:52 +01:00
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
2019-08-20 11:33:58 +02:00
}
2019-08-20 09:01:09 +02:00
void GLCanvas3D :: export_toolpaths_to_obj ( const char * filename ) const
{
2020-07-03 12:17:12 +02:00
m_gcode_viewer . export_toolpaths_to_obj ( filename );
2019-08-20 09:01:09 +02:00
}
2019-10-09 13:39:24 +02:00
void GLCanvas3D :: mouse_up_cleanup ()
{
m_moving = false ;
m_mouse . drag . move_volume_idx = - 1 ;
m_mouse . set_start_position_3D_as_invalid ();
m_mouse . set_start_position_2D_as_invalid ();
m_mouse . dragging = false ;
m_mouse . ignore_left_up = false ;
m_dirty = true ;
if ( m_canvas -> HasCapture ())
m_canvas -> ReleaseMouse ();
}
2018-06-12 09:18:25 +02:00
bool GLCanvas3D :: _is_shown_on_screen () const
{
2018-06-27 12:05:23 +02:00
return ( m_canvas != nullptr ) ? m_canvas -> IsShownOnScreen () : false ;
2018-06-12 09:18:25 +02:00
}
2019-07-09 15:27:28 +02:00
// Getter for the const char*[]
static bool string_getter ( const bool is_undo , int idx , const char ** out_text )
{
return wxGetApp (). plater () -> undo_redo_string_getter ( is_undo , idx , out_text );
}
2020-03-23 16:00:56 +01:00
bool GLCanvas3D :: _render_undo_redo_stack ( const bool is_undo , float pos_x ) const
2019-07-09 15:27:28 +02:00
{
2020-03-23 16:00:56 +01:00
bool action_taken = false ;
2019-07-10 10:15:07 +02:00
ImGuiWrapper * imgui = wxGetApp (). imgui ();
2019-07-09 15:27:28 +02:00
2020-03-02 10:58:46 +01:00
const float x = pos_x * ( float ) wxGetApp (). plater () -> get_camera (). get_zoom () + 0.5f * ( float ) get_canvas_size (). get_width ();
2019-07-19 15:36:55 +02:00
imgui -> set_next_window_pos ( x , m_undoredo_toolbar . get_height (), ImGuiCond_Always , 0.5f , 0.0f );
2019-12-11 15:30:25 +01:00
std :: string title = is_undo ? L ( "Undo History" ) : L ( "Redo History" );
2019-08-27 11:39:51 +02:00
imgui -> begin ( _ ( title ), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse );
2019-07-09 15:27:28 +02:00
2019-07-10 14:08:14 +02:00
int hovered = m_imgui_undo_redo_hovered_pos ;
2019-07-10 10:15:07 +02:00
int selected = - 1 ;
2019-07-19 17:16:20 +02:00
float em = static_cast < float > ( wxGetApp (). em_unit ());
2019-07-19 15:59:23 +02:00
#if ENABLE_RETINA_GL
em *= m_retina_helper -> get_scale_factor ();
#endif
2019-07-09 15:27:28 +02:00
2020-04-27 16:00:54 +02:00
if ( imgui -> undo_redo_list ( ImVec2 ( 18 * em , 26 * em ), is_undo , & string_getter , hovered , selected , m_mouse_wheel ))
2019-07-10 14:08:14 +02:00
m_imgui_undo_redo_hovered_pos = hovered ;
2019-07-10 10:15:07 +02:00
else
2019-07-10 14:08:14 +02:00
m_imgui_undo_redo_hovered_pos = - 1 ;
2019-07-09 15:27:28 +02:00
2019-07-10 10:15:07 +02:00
if ( selected >= 0 )
2020-03-23 16:00:56 +01:00
{
2019-07-10 10:15:07 +02:00
is_undo ? wxGetApp (). plater () -> undo_to ( selected ) : wxGetApp (). plater () -> redo_to ( selected );
2020-03-23 16:00:56 +01:00
action_taken = true ;
}
2019-07-09 15:27:28 +02:00
2019-08-28 16:19:46 +02:00
imgui -> text ( wxString :: Format ( is_undo ? _L_PLURAL ( "Undo %1$d Action" , "Undo %1$d Actions" , hovered + 1 ) : _L_PLURAL ( "Redo %1$d Action" , "Redo %1$d Actions" , hovered + 1 ), hovered + 1 ));
2019-07-10 10:15:07 +02:00
imgui -> end ();
2020-03-23 16:00:56 +01:00
return action_taken ;
2019-07-09 15:27:28 +02:00
}
2020-03-28 19:39:24 +01:00
// Getter for the const char*[] for the search list
2020-04-20 21:46:16 +02:00
static bool search_string_getter ( int idx , const char ** label , const char ** tooltip )
2020-03-28 19:39:24 +01:00
{
2020-04-20 21:46:16 +02:00
return wxGetApp (). plater () -> search_string_getter ( idx , label , tooltip );
2020-03-28 19:39:24 +01:00
}
bool GLCanvas3D :: _render_search_list ( float pos_x ) const
{
bool action_taken = false ;
ImGuiWrapper * imgui = wxGetApp (). imgui ();
2020-05-11 22:00:26 +02:00
const float x = /*pos_x * (float)wxGetApp().plater()->get_camera().get_zoom() + */ 0.5f * ( float ) get_canvas_size (). get_width ();
2020-04-23 12:41:38 +02:00
imgui -> set_next_window_pos ( x , m_main_toolbar . get_height (), ImGuiCond_Always , 0.5f , 0.0f );
2020-03-28 19:39:24 +01:00
std :: string title = L ( "Search" );
imgui -> begin ( _ ( title ), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse );
2020-04-20 21:46:16 +02:00
int selected = - 1 ;
2020-03-28 19:39:24 +01:00
bool edited = false ;
float em = static_cast < float > ( wxGetApp (). em_unit ());
#if ENABLE_RETINA_GL
em *= m_retina_helper -> get_scale_factor ();
2020-05-06 12:19:34 +02:00
#endif // ENABLE_RETINA_GL
2020-03-28 19:39:24 +01:00
2020-04-14 17:54:15 +02:00
Sidebar & sidebar = wxGetApp (). sidebar ();
std :: string & search_line = sidebar . get_search_line ();
2020-03-28 19:39:24 +01:00
char * s = new char [ 255 ];
2020-11-05 22:17:49 +01:00
strcpy ( s , search_line . empty () ? _u8L ( "Enter a search term" ). c_str () : search_line . c_str ());
2020-03-28 19:39:24 +01:00
2020-04-14 17:54:15 +02:00
imgui -> search_list ( ImVec2 ( 45 * em , 30 * em ), & search_string_getter , s ,
2020-04-20 21:46:16 +02:00
sidebar . get_searcher (). view_params ,
2020-05-07 18:39:38 +02:00
selected , edited , m_mouse_wheel , wxGetApp (). is_localized ());
2020-03-28 19:39:24 +01:00
search_line = s ;
delete [] s ;
2020-11-05 22:17:49 +01:00
if ( search_line == _u8L ( "Enter a search term" ))
2020-04-27 11:51:17 +02:00
search_line . clear ();
2020-03-28 19:39:24 +01:00
2020-04-27 11:51:17 +02:00
if ( edited )
2020-04-24 11:01:14 +02:00
sidebar . search ();
2020-04-01 11:51:44 +02:00
2020-05-07 18:39:38 +02:00
if ( selected >= 0 ) {
2020-04-08 20:22:38 +02:00
// selected == 9999 means that Esc kye was pressed
2020-10-15 16:48:48 +02:00
/*// revert commit https://github.com/prusa3d/PrusaSlicer/commit/91897589928789b261ca0dc735ffd46f2b0b99f2
2020-05-07 18:39:38 +02:00
if (selected == 9999)
action_taken = true;
else
2020-10-15 16:48:48 +02:00
sidebar.jump_to_option(selected);*/
if ( selected != 9999 )
2020-04-14 17:54:15 +02:00
sidebar . jump_to_option ( selected );
2020-10-15 16:48:48 +02:00
action_taken = true ;
2020-03-28 19:39:24 +01:00
}
imgui -> end ();
return action_taken ;
}
2020-11-20 17:50:10 +01:00
bool GLCanvas3D :: _render_arrange_menu ( float pos_x )
2020-11-18 00:06:34 +01:00
{
ImGuiWrapper * imgui = wxGetApp (). imgui ();
2020-11-20 17:50:10 +01:00
auto canvas_w = float ( get_canvas_size (). get_width ());
const float x = pos_x * float ( wxGetApp (). plater () -> get_camera (). get_zoom ()) + 0.5f * canvas_w ;
2020-11-18 00:06:34 +01:00
imgui -> set_next_window_pos ( x , m_main_toolbar . get_height (), ImGuiCond_Always , 0.5f , 0.0f );
2020-11-26 11:00:24 +01:00
imgui -> begin ( _L ( "Arrange options" ), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse );
2020-12-02 14:04:39 +01:00
ArrangeSettings settings = get_arrange_settings ();
ArrangeSettings & settings_out = get_arrange_settings ();
2020-11-18 00:06:34 +01:00
2020-11-18 14:38:46 +01:00
auto & appcfg = wxGetApp (). app_config ;
2020-12-02 10:28:13 +01:00
PrinterTechnology ptech = m_process -> current_printer_technology ();
2020-11-18 14:38:46 +01:00
2020-11-20 17:50:10 +01:00
bool settings_changed = false ;
2020-12-02 10:28:13 +01:00
float dist_min = 0.f ;
2020-12-02 14:04:39 +01:00
std :: string dist_key = "min_object_distance" , rot_key = "enable_rotation" ;
std :: string postfix ;
2020-11-20 17:50:10 +01:00
2020-12-02 10:28:13 +01:00
if ( ptech == ptSLA ) {
dist_min = 0.f ;
2020-12-02 14:04:39 +01:00
postfix = "_sla" ;
2020-12-02 10:28:13 +01:00
} else if ( ptech == ptFFF ) {
auto co_opt = m_config -> option < ConfigOptionBool > ( "complete_objects" );
if ( co_opt && co_opt -> value ) {
dist_min = float ( min_object_distance ( * m_config ));
2020-12-02 14:04:39 +01:00
postfix = "_fff_seq_print" ;
2020-12-02 10:28:13 +01:00
} else {
dist_min = 0.f ;
2020-12-02 14:04:39 +01:00
postfix = "_fff" ;
2020-12-02 10:28:13 +01:00
}
}
2020-12-01 14:07:34 +01:00
2020-12-02 14:04:39 +01:00
dist_key += postfix ;
rot_key += postfix ;
2020-12-16 17:28:42 +01:00
imgui -> text ( GUI :: format_wxstr ( _L ( "Press %1%left mouse button to enter the exact value" ), shortkey_ctrl_prefix ()));
2020-12-01 14:07:34 +01:00
2020-12-04 13:08:20 +01:00
if ( imgui -> slider_float ( _L ( "Spacing" ), & settings . distance , dist_min , 100.0f , "%5.2f" ) || dist_min > settings . distance ) {
2020-12-02 14:04:39 +01:00
settings . distance = std :: max ( dist_min , settings . distance );
settings_out . distance = settings . distance ;
appcfg -> set ( "arrange" , dist_key . c_str (), std :: to_string ( settings_out . distance ));
2020-11-20 17:50:10 +01:00
settings_changed = true ;
2020-11-18 14:38:46 +01:00
}
2020-11-18 00:06:34 +01:00
2020-11-26 11:00:24 +01:00
if ( imgui -> checkbox ( _L ( "Enable rotations (slow)" ), settings . enable_rotation )) {
2020-12-02 14:04:39 +01:00
settings_out . enable_rotation = settings . enable_rotation ;
appcfg -> set ( "arrange" , rot_key . c_str (), settings_out . enable_rotation ? "1" : "0" );
2020-11-20 17:50:10 +01:00
settings_changed = true ;
}
ImGui :: Separator ();
2020-11-26 11:00:24 +01:00
if ( imgui -> button ( _L ( "Reset" ))) {
2020-12-02 14:04:39 +01:00
settings_out = ArrangeSettings {};
settings_out . distance = std :: max ( dist_min , settings_out . distance );
appcfg -> set ( "arrange" , dist_key . c_str (), std :: to_string ( settings_out . distance ));
appcfg -> set ( "arrange" , rot_key . c_str (), settings_out . enable_rotation ? "1" : "0" );
2020-11-20 17:50:10 +01:00
settings_changed = true ;
}
ImGui :: SameLine ();
2020-11-26 11:00:24 +01:00
if ( imgui -> button ( _L ( "Arrange" ))) {
2020-11-20 17:50:10 +01:00
wxGetApp (). plater () -> arrange ();
2020-11-18 14:38:46 +01:00
}
2020-11-18 00:06:34 +01:00
imgui -> end ();
2020-11-20 17:50:10 +01:00
return settings_changed ;
2020-11-18 00:06:34 +01:00
}
2019-10-29 14:45:15 +01:00
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
static void debug_output_thumbnail ( const ThumbnailData & thumbnail_data )
{
// debug export of generated image
wxImage image ( thumbnail_data . width , thumbnail_data . height );
image . InitAlpha ();
for ( unsigned int r = 0 ; r < thumbnail_data . height ; ++ r )
{
unsigned int rr = ( thumbnail_data . height - 1 - r ) * thumbnail_data . width ;
for ( unsigned int c = 0 ; c < thumbnail_data . width ; ++ c )
{
unsigned char * px = ( unsigned char * ) thumbnail_data . pixels . data () + 4 * ( rr + c );
image . SetRGB (( int ) c , ( int ) r , px [ 0 ], px [ 1 ], px [ 2 ]);
image . SetAlpha (( int ) c , ( int ) r , px [ 3 ]);
}
}
image . SaveFile ( "C:/prusa/test/test.png" , wxBITMAP_TYPE_PNG );
}
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
2019-12-10 12:57:55 +01:00
void GLCanvas3D :: _render_thumbnail_internal ( ThumbnailData & thumbnail_data , bool printable_only , bool parts_only , bool show_bed , bool transparent_background ) const
2019-10-29 10:27:51 +01:00
{
2020-10-15 12:20:38 +02:00
auto is_visible = []( const GLVolume & v ) {
2019-10-29 10:27:51 +01:00
bool ret = v . printable ;
ret &= ( ! v . shader_outside_printer_detection_enabled || ! v . is_outside );
return ret ;
};
2020-05-20 14:11:22 +02:00
static const std :: array < float , 4 > orange = { 0.923f , 0.504f , 0.264f , 1.0f };
static const std :: array < float , 4 > gray = { 0.64f , 0.64f , 0.64f , 1.0f };
2019-10-29 10:27:51 +01:00
GLVolumePtrs visible_volumes ;
2020-10-15 12:20:38 +02:00
for ( GLVolume * vol : m_volumes . volumes ) {
if ( ! vol -> is_modifier && ! vol -> is_wipe_tower && ( ! parts_only || ( vol -> composite_id . volume_id >= 0 ))) {
2019-10-29 10:27:51 +01:00
if ( ! printable_only || is_visible ( * vol ))
2020-03-13 15:57:07 +01:00
visible_volumes . emplace_back ( vol );
2019-10-29 10:27:51 +01:00
}
}
if ( visible_volumes . empty ())
return ;
BoundingBoxf3 box ;
2020-10-15 12:20:38 +02:00
for ( const GLVolume * vol : visible_volumes ) {
2019-10-29 10:27:51 +01:00
box . merge ( vol -> transformed_bounding_box ());
}
Camera camera ;
2019-10-31 17:03:33 +01:00
camera . set_type ( Camera :: Ortho );
2020-01-15 12:49:34 +01:00
camera . set_scene_box ( scene_bounding_box ());
2020-01-29 12:08:38 +01:00
camera . apply_viewport ( 0 , 0 , thumbnail_data . width , thumbnail_data . height );
camera . zoom_to_volumes ( visible_volumes );
2019-10-29 10:27:51 +01:00
camera . apply_view_matrix ();
2019-11-28 14:18:24 +01:00
double near_z = - 1.0 ;
double far_z = - 1.0 ;
2020-10-15 12:20:38 +02:00
if ( show_bed ) {
2019-11-28 14:18:24 +01:00
// extends the near and far z of the frustrum to avoid the bed being clipped
2019-11-28 15:19:42 +01:00
// box in eye space
2020-03-02 10:58:46 +01:00
BoundingBoxf3 t_bed_box = wxGetApp (). plater () -> get_bed (). get_bounding_box ( true ). transformed ( camera . get_view_matrix ());
2019-11-28 15:19:42 +01:00
near_z = - t_bed_box . max ( 2 );
far_z = - t_bed_box . min ( 2 );
2019-11-28 14:18:24 +01:00
}
camera . apply_projection ( box , near_z , far_z );
2019-10-29 10:27:51 +01:00
2020-10-15 12:20:38 +02:00
GLShaderProgram * shader = wxGetApp (). get_shader ( "gouraud_light" );
2020-05-20 14:11:22 +02:00
if ( shader == nullptr )
return ;
if ( transparent_background )
glsafe ( :: glClearColor ( 0.0f , 0.0f , 0.0f , 0.0f ));
glsafe ( :: glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ));
glsafe ( :: glEnable ( GL_DEPTH_TEST ));
shader -> start_using ();
shader -> set_uniform ( "print_box.volume_detection" , 0 );
2019-11-01 12:19:27 +01:00
2021-01-05 11:58:48 +01:00
for ( GLVolume * vol : visible_volumes ) {
2020-05-20 14:11:22 +02:00
shader -> set_uniform ( "uniform_color" , ( vol -> printable && ! vol -> is_outside ) ? orange : gray );
2021-01-05 11:58:48 +01:00
// the volume may have been deactivated by an active gizmo
bool is_active = vol -> is_active ;
vol -> is_active = true ;
2019-10-29 10:27:51 +01:00
vol -> render ();
2021-01-05 11:58:48 +01:00
vol -> is_active = is_active ;
2019-10-29 10:27:51 +01:00
}
2020-05-20 14:11:22 +02:00
shader -> stop_using ();
2019-11-01 12:19:27 +01:00
2019-10-29 10:27:51 +01:00
glsafe ( :: glDisable ( GL_DEPTH_TEST ));
2019-11-04 11:59:23 +01:00
2019-11-28 14:18:24 +01:00
if ( show_bed )
2020-01-15 12:49:34 +01:00
_render_bed ( ! camera . is_looking_downward (), false );
2019-11-28 14:18:24 +01:00
2020-05-20 14:11:22 +02:00
// restore background color
2019-11-04 11:59:23 +01:00
if ( transparent_background )
glsafe ( :: glClearColor ( 1.0f , 1.0f , 1.0f , 1.0f ));
2019-10-29 10:27:51 +01:00
}
2019-12-10 12:57:55 +01:00
void GLCanvas3D :: _render_thumbnail_framebuffer ( ThumbnailData & thumbnail_data , unsigned int w , unsigned int h , bool printable_only , bool parts_only , bool show_bed , bool transparent_background ) const
2019-10-29 10:27:51 +01:00
{
thumbnail_data . set ( w , h );
if ( ! thumbnail_data . is_valid ())
return ;
2019-10-29 14:45:15 +01:00
bool multisample = m_multisample_allowed ;
if ( multisample )
glsafe ( :: glEnable ( GL_MULTISAMPLE ));
2019-10-29 10:27:51 +01:00
2019-10-29 14:45:15 +01:00
GLint max_samples ;
glsafe ( :: glGetIntegerv ( GL_MAX_SAMPLES , & max_samples ));
GLsizei num_samples = max_samples / 2 ;
2019-10-29 10:27:51 +01:00
2019-10-29 14:45:15 +01:00
GLuint render_fbo ;
glsafe ( :: glGenFramebuffers ( 1 , & render_fbo ));
glsafe ( :: glBindFramebuffer ( GL_FRAMEBUFFER , render_fbo ));
GLuint render_tex = 0 ;
GLuint render_tex_buffer = 0 ;
if ( multisample )
{
// use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2
glsafe ( :: glGenRenderbuffers ( 1 , & render_tex_buffer ));
glsafe ( :: glBindRenderbuffer ( GL_RENDERBUFFER , render_tex_buffer ));
glsafe ( :: glRenderbufferStorageMultisample ( GL_RENDERBUFFER , num_samples , GL_RGBA8 , w , h ));
glsafe ( :: glFramebufferRenderbuffer ( GL_FRAMEBUFFER , GL_COLOR_ATTACHMENT0 , GL_RENDERBUFFER , render_tex_buffer ));
}
else
{
glsafe ( :: glGenTextures ( 1 , & render_tex ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , render_tex ));
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 0 , GL_RGBA8 , w , h , 0 , GL_RGBA , GL_UNSIGNED_BYTE , nullptr ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_LINEAR ));
glsafe ( :: glFramebufferTexture2D ( GL_FRAMEBUFFER , GL_COLOR_ATTACHMENT0 , GL_TEXTURE_2D , render_tex , 0 ));
}
GLuint render_depth ;
glsafe ( :: glGenRenderbuffers ( 1 , & render_depth ));
glsafe ( :: glBindRenderbuffer ( GL_RENDERBUFFER , render_depth ));
if ( multisample )
glsafe ( :: glRenderbufferStorageMultisample ( GL_RENDERBUFFER , num_samples , GL_DEPTH_COMPONENT24 , w , h ));
else
glsafe ( :: glRenderbufferStorage ( GL_RENDERBUFFER , GL_DEPTH_COMPONENT , w , h ));
glsafe ( :: glFramebufferRenderbuffer ( GL_FRAMEBUFFER , GL_DEPTH_ATTACHMENT , GL_RENDERBUFFER , render_depth ));
2019-10-29 10:27:51 +01:00
GLenum drawBufs [] = { GL_COLOR_ATTACHMENT0 };
glsafe ( :: glDrawBuffers ( 1 , drawBufs ));
if ( :: glCheckFramebufferStatus ( GL_FRAMEBUFFER ) == GL_FRAMEBUFFER_COMPLETE )
2019-10-29 14:45:15 +01:00
{
2019-11-28 14:18:24 +01:00
_render_thumbnail_internal ( thumbnail_data , printable_only , parts_only , show_bed , transparent_background );
2019-10-29 10:27:51 +01:00
2019-10-29 14:45:15 +01:00
if ( multisample )
{
GLuint resolve_fbo ;
glsafe ( :: glGenFramebuffers ( 1 , & resolve_fbo ));
glsafe ( :: glBindFramebuffer ( GL_FRAMEBUFFER , resolve_fbo ));
GLuint resolve_tex ;
glsafe ( :: glGenTextures ( 1 , & resolve_tex ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , resolve_tex ));
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 0 , GL_RGBA8 , w , h , 0 , GL_RGBA , GL_UNSIGNED_BYTE , nullptr ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_LINEAR ));
glsafe ( :: glFramebufferTexture2D ( GL_FRAMEBUFFER , GL_COLOR_ATTACHMENT0 , GL_TEXTURE_2D , resolve_tex , 0 ));
glsafe ( :: glDrawBuffers ( 1 , drawBufs ));
if ( :: glCheckFramebufferStatus ( GL_FRAMEBUFFER ) == GL_FRAMEBUFFER_COMPLETE )
{
glsafe ( :: glBindFramebuffer ( GL_READ_FRAMEBUFFER , render_fbo ));
glsafe ( :: glBindFramebuffer ( GL_DRAW_FRAMEBUFFER , resolve_fbo ));
glsafe ( :: glBlitFramebuffer ( 0 , 0 , w , h , 0 , 0 , w , h , GL_COLOR_BUFFER_BIT , GL_LINEAR ));
glsafe ( :: glBindFramebuffer ( GL_READ_FRAMEBUFFER , resolve_fbo ));
glsafe ( :: glReadPixels ( 0 , 0 , w , h , GL_RGBA , GL_UNSIGNED_BYTE , ( void * ) thumbnail_data . pixels . data ()));
}
glsafe ( :: glDeleteTextures ( 1 , & resolve_tex ));
glsafe ( :: glDeleteFramebuffers ( 1 , & resolve_fbo ));
}
else
glsafe ( :: glReadPixels ( 0 , 0 , w , h , GL_RGBA , GL_UNSIGNED_BYTE , ( void * ) thumbnail_data . pixels . data ()));
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
debug_output_thumbnail ( thumbnail_data );
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
}
2019-10-29 10:27:51 +01:00
glsafe ( :: glBindFramebuffer ( GL_FRAMEBUFFER , 0 ));
2019-10-29 14:45:15 +01:00
glsafe ( :: glDeleteRenderbuffers ( 1 , & render_depth ));
if ( render_tex_buffer != 0 )
glsafe ( :: glDeleteRenderbuffers ( 1 , & render_tex_buffer ));
if ( render_tex != 0 )
glsafe ( :: glDeleteTextures ( 1 , & render_tex ));
glsafe ( :: glDeleteFramebuffers ( 1 , & render_fbo ));
if ( multisample )
glsafe ( :: glDisable ( GL_MULTISAMPLE ));
2019-10-29 10:27:51 +01:00
}
2019-12-10 12:57:55 +01:00
void GLCanvas3D :: _render_thumbnail_framebuffer_ext ( ThumbnailData & thumbnail_data , unsigned int w , unsigned int h , bool printable_only , bool parts_only , bool show_bed , bool transparent_background ) const
2019-11-05 14:50:58 +01:00
{
thumbnail_data . set ( w , h );
if ( ! thumbnail_data . is_valid ())
return ;
bool multisample = m_multisample_allowed ;
if ( multisample )
glsafe ( :: glEnable ( GL_MULTISAMPLE ));
GLint max_samples ;
glsafe ( :: glGetIntegerv ( GL_MAX_SAMPLES_EXT , & max_samples ));
GLsizei num_samples = max_samples / 2 ;
GLuint render_fbo ;
glsafe ( :: glGenFramebuffersEXT ( 1 , & render_fbo ));
glsafe ( :: glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT , render_fbo ));
GLuint render_tex = 0 ;
GLuint render_tex_buffer = 0 ;
if ( multisample )
{
// use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2
glsafe ( :: glGenRenderbuffersEXT ( 1 , & render_tex_buffer ));
glsafe ( :: glBindRenderbufferEXT ( GL_RENDERBUFFER_EXT , render_tex_buffer ));
glsafe ( :: glRenderbufferStorageMultisampleEXT ( GL_RENDERBUFFER_EXT , num_samples , GL_RGBA8 , w , h ));
glsafe ( :: glFramebufferRenderbufferEXT ( GL_FRAMEBUFFER_EXT , GL_COLOR_ATTACHMENT0_EXT , GL_RENDERBUFFER_EXT , render_tex_buffer ));
}
else
{
glsafe ( :: glGenTextures ( 1 , & render_tex ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , render_tex ));
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 0 , GL_RGBA8 , w , h , 0 , GL_RGBA , GL_UNSIGNED_BYTE , nullptr ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_LINEAR ));
glsafe ( :: glFramebufferTexture2D ( GL_FRAMEBUFFER_EXT , GL_COLOR_ATTACHMENT0_EXT , GL_TEXTURE_2D , render_tex , 0 ));
}
GLuint render_depth ;
glsafe ( :: glGenRenderbuffersEXT ( 1 , & render_depth ));
glsafe ( :: glBindRenderbufferEXT ( GL_RENDERBUFFER_EXT , render_depth ));
if ( multisample )
glsafe ( :: glRenderbufferStorageMultisampleEXT ( GL_RENDERBUFFER_EXT , num_samples , GL_DEPTH_COMPONENT24 , w , h ));
else
glsafe ( :: glRenderbufferStorageEXT ( GL_RENDERBUFFER_EXT , GL_DEPTH_COMPONENT , w , h ));
glsafe ( :: glFramebufferRenderbufferEXT ( GL_FRAMEBUFFER_EXT , GL_DEPTH_ATTACHMENT_EXT , GL_RENDERBUFFER_EXT , render_depth ));
GLenum drawBufs [] = { GL_COLOR_ATTACHMENT0 };
glsafe ( :: glDrawBuffers ( 1 , drawBufs ));
if ( :: glCheckFramebufferStatusEXT ( GL_FRAMEBUFFER_EXT ) == GL_FRAMEBUFFER_COMPLETE_EXT )
{
2019-11-28 14:18:24 +01:00
_render_thumbnail_internal ( thumbnail_data , printable_only , parts_only , show_bed , transparent_background );
2019-11-05 14:50:58 +01:00
if ( multisample )
{
GLuint resolve_fbo ;
glsafe ( :: glGenFramebuffersEXT ( 1 , & resolve_fbo ));
glsafe ( :: glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT , resolve_fbo ));
GLuint resolve_tex ;
glsafe ( :: glGenTextures ( 1 , & resolve_tex ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , resolve_tex ));
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 0 , GL_RGBA8 , w , h , 0 , GL_RGBA , GL_UNSIGNED_BYTE , nullptr ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_LINEAR ));
glsafe ( :: glFramebufferTexture2DEXT ( GL_FRAMEBUFFER_EXT , GL_COLOR_ATTACHMENT0_EXT , GL_TEXTURE_2D , resolve_tex , 0 ));
glsafe ( :: glDrawBuffers ( 1 , drawBufs ));
if ( :: glCheckFramebufferStatusEXT ( GL_FRAMEBUFFER_EXT ) == GL_FRAMEBUFFER_COMPLETE_EXT )
{
glsafe ( :: glBindFramebufferEXT ( GL_READ_FRAMEBUFFER_EXT , render_fbo ));
glsafe ( :: glBindFramebufferEXT ( GL_DRAW_FRAMEBUFFER_EXT , resolve_fbo ));
glsafe ( :: glBlitFramebufferEXT ( 0 , 0 , w , h , 0 , 0 , w , h , GL_COLOR_BUFFER_BIT , GL_LINEAR ));
glsafe ( :: glBindFramebufferEXT ( GL_READ_FRAMEBUFFER_EXT , resolve_fbo ));
glsafe ( :: glReadPixels ( 0 , 0 , w , h , GL_RGBA , GL_UNSIGNED_BYTE , ( void * ) thumbnail_data . pixels . data ()));
}
glsafe ( :: glDeleteTextures ( 1 , & resolve_tex ));
glsafe ( :: glDeleteFramebuffersEXT ( 1 , & resolve_fbo ));
}
else
glsafe ( :: glReadPixels ( 0 , 0 , w , h , GL_RGBA , GL_UNSIGNED_BYTE , ( void * ) thumbnail_data . pixels . data ()));
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
debug_output_thumbnail ( thumbnail_data );
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
}
glsafe ( :: glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT , 0 ));
glsafe ( :: glDeleteRenderbuffersEXT ( 1 , & render_depth ));
if ( render_tex_buffer != 0 )
glsafe ( :: glDeleteRenderbuffersEXT ( 1 , & render_tex_buffer ));
if ( render_tex != 0 )
glsafe ( :: glDeleteTextures ( 1 , & render_tex ));
glsafe ( :: glDeleteFramebuffersEXT ( 1 , & render_fbo ));
if ( multisample )
glsafe ( :: glDisable ( GL_MULTISAMPLE ));
}
2019-12-10 12:57:55 +01:00
void GLCanvas3D :: _render_thumbnail_legacy ( ThumbnailData & thumbnail_data , unsigned int w , unsigned int h , bool printable_only , bool parts_only , bool show_bed , bool transparent_background ) const
2019-10-29 10:27:51 +01:00
{
// check that thumbnail size does not exceed the default framebuffer size
const Size & cnv_size = get_canvas_size ();
unsigned int cnv_w = ( unsigned int ) cnv_size . get_width ();
unsigned int cnv_h = ( unsigned int ) cnv_size . get_height ();
if (( w > cnv_w ) || ( h > cnv_h ))
{
float ratio = std :: min (( float ) cnv_w / ( float ) w , ( float ) cnv_h / ( float ) h );
w = ( unsigned int )( ratio * ( float ) w );
h = ( unsigned int )( ratio * ( float ) h );
}
thumbnail_data . set ( w , h );
if ( ! thumbnail_data . is_valid ())
return ;
2019-11-28 14:18:24 +01:00
_render_thumbnail_internal ( thumbnail_data , printable_only , parts_only , show_bed , transparent_background );
2019-10-29 10:27:51 +01:00
2019-10-29 14:45:15 +01:00
glsafe ( :: glReadPixels ( 0 , 0 , w , h , GL_RGBA , GL_UNSIGNED_BYTE , ( void * ) thumbnail_data . pixels . data ()));
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
debug_output_thumbnail ( thumbnail_data );
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
2019-10-29 10:27:51 +01:00
// restore the default framebuffer size to avoid flickering on the 3D scene
2020-03-02 10:58:46 +01:00
wxGetApp (). plater () -> get_camera (). apply_viewport ( 0 , 0 , cnv_size . get_width (), cnv_size . get_height ());
2019-10-29 10:27:51 +01:00
}
2019-07-19 15:36:55 +02:00
bool GLCanvas3D :: _init_toolbars ()
2018-07-23 13:49:48 +02:00
{
2019-07-19 15:36:55 +02:00
if ( ! _init_main_toolbar ())
return false ;
if ( ! _init_undoredo_toolbar ())
return false ;
2019-12-10 10:56:21 +01:00
if ( ! _init_view_toolbar ())
return false ;
2020-04-23 12:41:38 +02:00
if ( ! _init_collapse_toolbar ())
return false ;
2019-07-19 15:36:55 +02:00
return true ;
}
bool GLCanvas3D :: _init_main_toolbar ()
{
if ( ! m_main_toolbar . is_enabled ())
2018-07-23 13:49:48 +02:00
return true ;
2018-12-17 10:55:14 +01:00
BackgroundTexture :: Metadata background_data ;
background_data . filename = "toolbar_background.png" ;
background_data . left = 16 ;
background_data . top = 16 ;
background_data . right = 16 ;
background_data . bottom = 16 ;
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . init ( background_data ))
2018-07-31 12:25:00 +02:00
{
// unable to init the toolbar texture, disable it
2019-07-19 15:36:55 +02:00
m_main_toolbar . set_enabled ( false );
2018-07-31 12:25:00 +02:00
return true ;
}
2019-07-19 15:36:55 +02:00
// m_main_toolbar.set_layout_type(GLToolbar::Layout::Vertical);
m_main_toolbar . set_layout_type ( GLToolbar :: Layout :: Horizontal );
2019-07-20 12:02:29 +02:00
m_main_toolbar . set_horizontal_orientation ( GLToolbar :: Layout :: HO_Right );
m_main_toolbar . set_vertical_orientation ( GLToolbar :: Layout :: VO_Top );
2019-07-19 15:36:55 +02:00
m_main_toolbar . set_border ( 5.0f );
m_main_toolbar . set_separator_size ( 5 );
2020-09-25 11:02:29 +02:00
m_main_toolbar . set_gap_size ( 4 );
2018-07-31 12:25:00 +02:00
GLToolbarItem :: Data item ;
2018-07-23 13:49:48 +02:00
item . name = "add" ;
2019-02-26 10:40:00 +01:00
item . icon_filename = "add.svg" ;
2019-05-09 17:18:03 +02:00
item . tooltip = _utf8 ( L ( "Add..." )) + " [" + GUI :: shortkey_ctrl_prefix () + "I]" ;
2018-07-31 12:25:00 +02:00
item . sprite_id = 0 ;
2019-07-12 09:26:19 +02:00
item . left . action_callback = [ this ]() { if ( m_canvas != nullptr ) wxPostEvent ( m_canvas , SimpleEvent ( EVT_GLTOOLBAR_ADD )); };
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . add_item ( item ))
2018-07-23 13:49:48 +02:00
return false ;
item . name = "delete" ;
2019-02-26 10:40:00 +01:00
item . icon_filename = "remove.svg" ;
2019-05-09 17:18:03 +02:00
item . tooltip = _utf8 ( L ( "Delete" )) + " [Del]" ;
2018-07-31 12:25:00 +02:00
item . sprite_id = 1 ;
2019-07-12 09:26:19 +02:00
item . left . action_callback = [ this ]() { if ( m_canvas != nullptr ) wxPostEvent ( m_canvas , SimpleEvent ( EVT_GLTOOLBAR_DELETE )); };
2019-07-11 15:29:46 +02:00
item . enabling_callback = []() -> bool { return wxGetApp (). plater () -> can_delete (); };
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . add_item ( item ))
2018-07-23 13:49:48 +02:00
return false ;
item . name = "deleteall" ;
2019-02-26 10:40:00 +01:00
item . icon_filename = "delete_all.svg" ;
2019-05-09 17:18:03 +02:00
item . tooltip = _utf8 ( L ( "Delete all" )) + " [" + GUI :: shortkey_ctrl_prefix () + "Del]" ;
2018-07-31 12:25:00 +02:00
item . sprite_id = 2 ;
2019-07-12 09:26:19 +02:00
item . left . action_callback = [ this ]() { if ( m_canvas != nullptr ) wxPostEvent ( m_canvas , SimpleEvent ( EVT_GLTOOLBAR_DELETE_ALL )); };
2019-07-11 15:29:46 +02:00
item . enabling_callback = []() -> bool { return wxGetApp (). plater () -> can_delete_all (); };
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . add_item ( item ))
2018-07-23 13:49:48 +02:00
return false ;
item . name = "arrange" ;
2019-02-26 10:40:00 +01:00
item . icon_filename = "arrange.svg" ;
2020-11-20 17:50:10 +01:00
item . tooltip = _utf8 ( L ( "Arrange" )) + " [A] \n " + _utf8 ( L ( "Arrange selection" )) + " [Shift+A] \n " + _utf8 ( L ( "Click right mouse button to show arrangement options" ));
2018-07-31 12:25:00 +02:00
item . sprite_id = 3 ;
2019-07-12 09:26:19 +02:00
item . left . action_callback = [ this ]() { if ( m_canvas != nullptr ) wxPostEvent ( m_canvas , SimpleEvent ( EVT_GLTOOLBAR_ARRANGE )); };
2019-07-11 15:29:46 +02:00
item . enabling_callback = []() -> bool { return wxGetApp (). plater () -> can_arrange (); };
2020-11-18 00:06:34 +01:00
item . right . toggable = true ;
item . right . render_callback = [ this ]( float left , float right , float , float ) {
if ( m_canvas != nullptr )
{
2020-11-20 17:50:10 +01:00
_render_arrange_menu ( 0.5f * ( left + right ));
2020-11-18 00:06:34 +01:00
}
};
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . add_item ( item ))
2018-07-23 13:49:48 +02:00
return false ;
2020-11-18 14:38:46 +01:00
item . right . toggable = false ;
item . right . render_callback = GLToolbarItem :: Default_Render_Callback ;
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . add_separator ())
2018-07-23 13:49:48 +02:00
return false ;
2019-04-11 13:20:34 +02:00
item . name = "copy" ;
item . icon_filename = "copy.svg" ;
2019-05-09 17:18:03 +02:00
item . tooltip = _utf8 ( L ( "Copy" )) + " [" + GUI :: shortkey_ctrl_prefix () + "C]" ;
2019-04-11 13:20:34 +02:00
item . sprite_id = 4 ;
2019-07-12 09:26:19 +02:00
item . left . action_callback = [ this ]() { if ( m_canvas != nullptr ) wxPostEvent ( m_canvas , SimpleEvent ( EVT_GLTOOLBAR_COPY )); };
2019-07-11 15:29:46 +02:00
item . enabling_callback = []() -> bool { return wxGetApp (). plater () -> can_copy_to_clipboard (); };
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . add_item ( item ))
2019-04-11 13:20:34 +02:00
return false ;
item . name = "paste" ;
item . icon_filename = "paste.svg" ;
2019-05-09 17:18:03 +02:00
item . tooltip = _utf8 ( L ( "Paste" )) + " [" + GUI :: shortkey_ctrl_prefix () + "V]" ;
2019-04-11 13:20:34 +02:00
item . sprite_id = 5 ;
2019-07-12 09:26:19 +02:00
item . left . action_callback = [ this ]() { if ( m_canvas != nullptr ) wxPostEvent ( m_canvas , SimpleEvent ( EVT_GLTOOLBAR_PASTE )); };
2019-07-11 15:29:46 +02:00
item . enabling_callback = []() -> bool { return wxGetApp (). plater () -> can_paste_from_clipboard (); };
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . add_item ( item ))
2019-04-11 13:20:34 +02:00
return false ;
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . add_separator ())
2019-04-11 13:20:34 +02:00
return false ;
2018-07-23 13:49:48 +02:00
item . name = "more" ;
2019-02-26 10:40:00 +01:00
item . icon_filename = "instance_add.svg" ;
2019-05-09 17:18:03 +02:00
item . tooltip = _utf8 ( L ( "Add instance" )) + " [+]" ;
2019-04-11 13:20:34 +02:00
item . sprite_id = 6 ;
2019-07-12 09:26:19 +02:00
item . left . action_callback = [ this ]() { if ( m_canvas != nullptr ) wxPostEvent ( m_canvas , SimpleEvent ( EVT_GLTOOLBAR_MORE )); };
2019-03-14 13:54:05 +01:00
item . visibility_callback = []() -> bool { return wxGetApp (). get_mode () != comSimple ; };
2019-07-11 15:29:46 +02:00
item . enabling_callback = []() -> bool { return wxGetApp (). plater () -> can_increase_instances (); };
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . add_item ( item ))
2018-07-23 13:49:48 +02:00
return false ;
item . name = "fewer" ;
2019-02-26 10:40:00 +01:00
item . icon_filename = "instance_remove.svg" ;
2019-05-09 17:18:03 +02:00
item . tooltip = _utf8 ( L ( "Remove instance" )) + " [-]" ;
2019-04-11 13:20:34 +02:00
item . sprite_id = 7 ;
2019-07-12 09:26:19 +02:00
item . left . action_callback = [ this ]() { if ( m_canvas != nullptr ) wxPostEvent ( m_canvas , SimpleEvent ( EVT_GLTOOLBAR_FEWER )); };
2019-03-14 13:54:05 +01:00
item . visibility_callback = []() -> bool { return wxGetApp (). get_mode () != comSimple ; };
2019-07-11 15:29:46 +02:00
item . enabling_callback = []() -> bool { return wxGetApp (). plater () -> can_decrease_instances (); };
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . add_item ( item ))
2018-07-23 13:49:48 +02:00
return false ;
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . add_separator ())
2018-07-23 13:49:48 +02:00
return false ;
2018-10-24 12:55:38 +02:00
item . name = "splitobjects" ;
2019-02-26 10:40:00 +01:00
item . icon_filename = "split_objects.svg" ;
2019-05-09 17:18:03 +02:00
item . tooltip = _utf8 ( L ( "Split to objects" ));
2019-04-11 13:20:34 +02:00
item . sprite_id = 8 ;
2019-07-12 09:26:19 +02:00
item . left . action_callback = [ this ]() { if ( m_canvas != nullptr ) wxPostEvent ( m_canvas , SimpleEvent ( EVT_GLTOOLBAR_SPLIT_OBJECTS )); };
2019-03-14 13:54:05 +01:00
item . visibility_callback = GLToolbarItem :: Default_Visibility_Callback ;
2019-07-11 15:29:46 +02:00
item . enabling_callback = []() -> bool { return wxGetApp (). plater () -> can_split_to_objects (); };
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . add_item ( item ))
2018-10-24 12:55:38 +02:00
return false ;
item . name = "splitvolumes" ;
2019-02-26 10:40:00 +01:00
item . icon_filename = "split_parts.svg" ;
2019-05-09 17:18:03 +02:00
item . tooltip = _utf8 ( L ( "Split to parts" ));
2019-04-11 13:20:34 +02:00
item . sprite_id = 9 ;
2019-07-12 09:26:19 +02:00
item . left . action_callback = [ this ]() { if ( m_canvas != nullptr ) wxPostEvent ( m_canvas , SimpleEvent ( EVT_GLTOOLBAR_SPLIT_VOLUMES )); };
2019-03-14 13:54:05 +01:00
item . visibility_callback = []() -> bool { return wxGetApp (). get_mode () != comSimple ; };
2019-07-11 15:29:46 +02:00
item . enabling_callback = []() -> bool { return wxGetApp (). plater () -> can_split_to_volumes (); };
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . add_item ( item ))
2018-07-23 13:49:48 +02:00
return false ;
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . add_separator ())
2018-07-23 13:49:48 +02:00
return false ;
2020-05-04 22:30:38 +02:00
item . name = "settings" ;
2020-09-14 17:25:47 +02:00
item . icon_filename = "settings.svg" ;
2020-05-05 17:23:58 +02:00
item . tooltip = _u8L ( "Switch to Settings" ) + " \n " + "[" + GUI :: shortkey_ctrl_prefix () + "2] - " + _u8L ( "Print Settings Tab" ) +
" \n " + "[" + GUI :: shortkey_ctrl_prefix () + "3] - " + ( m_process -> current_printer_technology () == ptFFF ? _u8L ( "Filament Settings Tab" ) : _u8L ( "Material Settings Tab" )) +
" \n " + "[" + GUI :: shortkey_ctrl_prefix () + "4] - " + _u8L ( "Printer Settings Tab" ) ;
2020-05-04 22:30:38 +02:00
item . sprite_id = 10 ;
item . enabling_callback = GLToolbarItem :: Default_Enabling_Callback ;
2020-05-06 20:45:57 +02:00
item . visibility_callback = [ this ]() { return ( wxGetApp (). app_config -> get ( "new_settings_layout_mode" ) == "1" ||
wxGetApp (). app_config -> get ( "dlg_settings_layout_mode" ) == "1" ); };
2020-05-04 22:30:38 +02:00
item . left . action_callback = [ this ]() { wxGetApp (). mainframe -> select_tab (); };
if ( ! m_main_toolbar . add_item ( item ))
return false ;
2020-09-14 17:25:47 +02:00
/*
2020-05-04 22:30:38 +02:00
if (!m_main_toolbar.add_separator())
return false;
2020-09-14 17:25:47 +02:00
*/
2020-03-28 19:39:24 +01:00
item . name = "search" ;
item . icon_filename = "search_.svg" ;
2020-04-08 09:47:33 +02:00
item . tooltip = _utf8 ( L ( "Search" )) + " [" + GUI :: shortkey_ctrl_prefix () + "F]" ;
2020-09-14 17:25:47 +02:00
item . sprite_id = 11 ;
item . left . toggable = true ;
2020-03-28 19:39:24 +01:00
item . left . render_callback = [ this ]( float left , float right , float , float ) {
if ( m_canvas != nullptr )
{
2020-04-01 11:51:44 +02:00
if ( _render_search_list ( 0.5f * ( left + right )))
_deactivate_search_toolbar_item ();
2020-03-28 19:39:24 +01:00
}
};
2020-04-20 21:46:16 +02:00
item . left . action_callback = GLToolbarItem :: Default_Action_Callback ;
2020-04-01 11:51:44 +02:00
item . visibility_callback = GLToolbarItem :: Default_Visibility_Callback ;
item . enabling_callback = GLToolbarItem :: Default_Enabling_Callback ;
2020-03-28 19:39:24 +01:00
if ( ! m_main_toolbar . add_item ( item ))
return false ;
2020-09-14 17:25:47 +02:00
if ( ! m_main_toolbar . add_separator ())
return false ;
item . name = "layersediting" ;
item . icon_filename = "layers_white.svg" ;
item . tooltip = _utf8 ( L ( "Variable layer height" ));
item . sprite_id = 12 ;
item . left . action_callback = [ this ]() { if ( m_canvas != nullptr ) wxPostEvent ( m_canvas , SimpleEvent ( EVT_GLTOOLBAR_LAYERSEDITING )); };
item . visibility_callback = [ this ]() -> bool {
bool res = m_process -> current_printer_technology () == ptFFF ;
// turns off if changing printer technology
if ( ! res && m_main_toolbar . is_item_visible ( "layersediting" ) && m_main_toolbar . is_item_pressed ( "layersediting" ))
force_main_toolbar_left_action ( get_main_toolbar_item_id ( "layersediting" ));
return res ;
};
item . enabling_callback = []() -> bool { return wxGetApp (). plater () -> can_layers_editing (); };
item . left . render_callback = GLToolbarItem :: Default_Render_Callback ;
if ( ! m_main_toolbar . add_item ( item ))
return false ;
2019-07-19 15:36:55 +02:00
return true ;
}
bool GLCanvas3D :: _init_undoredo_toolbar ()
{
if ( ! m_undoredo_toolbar . is_enabled ())
return true ;
BackgroundTexture :: Metadata background_data ;
background_data . filename = "toolbar_background.png" ;
background_data . left = 16 ;
background_data . top = 16 ;
background_data . right = 16 ;
background_data . bottom = 16 ;
if ( ! m_undoredo_toolbar . init ( background_data ))
{
// unable to init the toolbar texture, disable it
m_undoredo_toolbar . set_enabled ( false );
return true ;
}
// m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Vertical);
m_undoredo_toolbar . set_layout_type ( GLToolbar :: Layout :: Horizontal );
2019-07-20 12:02:29 +02:00
m_undoredo_toolbar . set_horizontal_orientation ( GLToolbar :: Layout :: HO_Left );
m_undoredo_toolbar . set_vertical_orientation ( GLToolbar :: Layout :: VO_Top );
2019-07-19 15:36:55 +02:00
m_undoredo_toolbar . set_border ( 5.0f );
m_undoredo_toolbar . set_separator_size ( 5 );
2020-09-25 11:02:29 +02:00
m_undoredo_toolbar . set_gap_size ( 4 );
2019-07-19 15:36:55 +02:00
GLToolbarItem :: Data item ;
2019-07-08 18:01:14 +02:00
item . name = "undo" ;
item . icon_filename = "undo_toolbar.svg" ;
2020-03-23 16:00:56 +01:00
item . tooltip = _utf8 ( L ( "Undo" )) + " [" + GUI :: shortkey_ctrl_prefix () + "Z] \n " + _utf8 ( L ( "Click right mouse button to open/close History" ));
2019-07-19 15:36:55 +02:00
item . sprite_id = 0 ;
2019-07-12 09:26:19 +02:00
item . left . action_callback = [ this ]() { post_event ( SimpleEvent ( EVT_GLCANVAS_UNDO )); };
item . right . toggable = true ;
item . right . action_callback = [ this ]() { m_imgui_undo_redo_hovered_pos = - 1 ; };
2020-03-23 16:00:56 +01:00
item . right . render_callback = [ this ]( float left , float right , float , float ) {
if ( m_canvas != nullptr )
{
if ( _render_undo_redo_stack ( true , 0.5f * ( left + right )))
_deactivate_undo_redo_toolbar_items ();
}
};
2019-07-20 14:03:34 +02:00
item . enabling_callback = [ this ]() -> bool {
bool can_undo = wxGetApp (). plater () -> can_undo ();
2019-09-04 09:47:00 +02:00
int id = m_undoredo_toolbar . get_item_id ( "undo" );
2019-07-20 14:03:34 +02:00
std :: string curr_additional_tooltip ;
m_undoredo_toolbar . get_additional_tooltip ( id , curr_additional_tooltip );
2019-08-06 10:01:10 +02:00
std :: string new_additional_tooltip = "" ;
2019-08-27 11:39:51 +02:00
if ( can_undo ) {
std :: string action ;
wxGetApp (). plater () -> undo_redo_topmost_string_getter ( true , action );
2019-08-27 11:49:46 +02:00
new_additional_tooltip = ( boost :: format ( _utf8 ( L ( "Next Undo action: %1%" ))) % action ). str ();
2019-08-27 11:39:51 +02:00
}
2019-07-20 14:03:34 +02:00
if ( new_additional_tooltip != curr_additional_tooltip )
{
m_undoredo_toolbar . set_additional_tooltip ( id , new_additional_tooltip );
2019-08-06 10:01:10 +02:00
set_tooltip ( "" );
2019-07-20 14:03:34 +02:00
}
return can_undo ;
};
2019-07-19 15:36:55 +02:00
if ( ! m_undoredo_toolbar . add_item ( item ))
2019-07-08 18:01:14 +02:00
return false ;
item . name = "redo" ;
item . icon_filename = "redo_toolbar.svg" ;
2020-03-23 16:00:56 +01:00
item . tooltip = _utf8 ( L ( "Redo" )) + " [" + GUI :: shortkey_ctrl_prefix () + "Y] \n " + _utf8 ( L ( "Click right mouse button to open/close History" ));
2019-07-19 15:36:55 +02:00
item . sprite_id = 1 ;
2019-07-12 09:26:19 +02:00
item . left . action_callback = [ this ]() { post_event ( SimpleEvent ( EVT_GLCANVAS_REDO )); };
item . right . action_callback = [ this ]() { m_imgui_undo_redo_hovered_pos = - 1 ; };
2020-03-23 16:00:56 +01:00
item . right . render_callback = [ this ]( float left , float right , float , float ) {
if ( m_canvas != nullptr )
{
if ( _render_undo_redo_stack ( false , 0.5f * ( left + right )))
_deactivate_undo_redo_toolbar_items ();
}
};
2019-07-20 14:03:34 +02:00
item . enabling_callback = [ this ]() -> bool {
bool can_redo = wxGetApp (). plater () -> can_redo ();
2019-09-04 09:47:00 +02:00
int id = m_undoredo_toolbar . get_item_id ( "redo" );
2019-07-20 14:03:34 +02:00
std :: string curr_additional_tooltip ;
m_undoredo_toolbar . get_additional_tooltip ( id , curr_additional_tooltip );
2019-08-06 10:01:10 +02:00
std :: string new_additional_tooltip = "" ;
2019-08-27 11:39:51 +02:00
if ( can_redo ) {
std :: string action ;
wxGetApp (). plater () -> undo_redo_topmost_string_getter ( false , action );
2019-08-27 11:49:46 +02:00
new_additional_tooltip = ( boost :: format ( _utf8 ( L ( "Next Redo action: %1%" ))) % action ). str ();
2019-08-27 11:39:51 +02:00
}
2019-07-20 14:03:34 +02:00
if ( new_additional_tooltip != curr_additional_tooltip )
{
m_undoredo_toolbar . set_additional_tooltip ( id , new_additional_tooltip );
2019-08-06 10:01:10 +02:00
set_tooltip ( "" );
2019-07-20 14:03:34 +02:00
}
return can_redo ;
};
2019-07-19 15:36:55 +02:00
if ( ! m_undoredo_toolbar . add_item ( item ))
2018-07-23 13:49:48 +02:00
return false ;
2020-09-14 17:25:47 +02:00
/*
2020-03-28 19:39:24 +01:00
if (!m_undoredo_toolbar.add_separator())
return false;
2020-09-14 17:25:47 +02:00
*/
2018-07-23 13:49:48 +02:00
return true ;
}
2019-12-10 10:56:21 +01:00
bool GLCanvas3D :: _init_view_toolbar ()
{
return wxGetApp (). plater () -> init_view_toolbar ();
}
2020-04-23 12:41:38 +02:00
bool GLCanvas3D :: _init_collapse_toolbar ()
{
2020-06-05 12:33:09 +02:00
return wxGetApp (). plater () -> init_collapse_toolbar ();
2020-04-23 12:41:38 +02:00
}
2018-10-04 10:41:11 +02:00
bool GLCanvas3D :: _set_current ()
{
2019-08-26 11:12:48 +02:00
return m_context != nullptr && m_canvas -> SetCurrent ( * m_context );
2018-10-04 10:41:11 +02:00
}
2018-06-01 15:54:41 +02:00
void GLCanvas3D :: _resize ( unsigned int w , unsigned int h )
2018-05-30 15:18:45 +02:00
{
2020-10-12 08:39:40 +02:00
if ( m_canvas == nullptr && m_context == nullptr )
2018-06-01 15:54:41 +02:00
return ;
2019-04-01 14:12:05 +02:00
auto * imgui = wxGetApp (). imgui ();
imgui -> set_display_size (( float ) w , ( float ) h );
2019-04-02 13:26:22 +02:00
const float font_size = 1.5f * wxGetApp (). em_unit ();
2019-01-24 11:30:29 +01:00
#if ENABLE_RETINA_GL
2019-04-02 13:26:22 +02:00
imgui -> set_scaling ( font_size , 1.0f , m_retina_helper -> get_scale_factor ());
2019-02-04 21:41:10 +01:00
#else
2019-04-02 13:26:22 +02:00
imgui -> set_scaling ( font_size , m_canvas -> GetContentScaleFactor (), 1.0f );
2019-02-04 21:41:10 +01:00
#endif
2018-10-31 10:19:44 +01:00
2018-06-25 15:17:13 +02:00
// ensures that this canvas is current
2018-10-04 10:41:11 +02:00
_set_current ();
2018-05-30 15:18:45 +02:00
}
2019-08-02 15:30:37 +02:00
BoundingBoxf3 GLCanvas3D :: _max_bounding_box ( bool include_gizmos , bool include_bed_model ) const
2018-05-30 15:18:45 +02:00
{
2019-02-19 15:15:27 +01:00
BoundingBoxf3 bb = volumes_bounding_box ();
2019-08-02 15:30:37 +02:00
// The following is a workaround for gizmos not being taken in account when calculating the tight camera frustrum
// A better solution would ask the gizmo manager for the bounding box of the current active gizmo, if any
if ( include_gizmos && m_gizmos . is_running ())
{
BoundingBoxf3 sel_bb = m_selection . get_bounding_box ();
Vec3d sel_bb_center = sel_bb . center ();
Vec3d extend_by = sel_bb . max_size () * Vec3d :: Ones ();
bb . merge ( BoundingBoxf3 ( sel_bb_center - extend_by , sel_bb_center + extend_by ));
}
2020-03-02 10:58:46 +01:00
bb . merge ( wxGetApp (). plater () -> get_bed (). get_bounding_box ( include_bed_model ));
2020-04-17 13:28:25 +02:00
if ( ! m_main_toolbar . is_enabled ())
2020-06-24 16:57:09 +02:00
bb . merge ( m_gcode_viewer . get_max_bounding_box ());
2020-04-17 13:28:25 +02:00
2019-02-19 15:15:27 +01:00
return bb ;
2018-05-30 15:18:45 +02:00
}
2019-10-31 16:40:38 +01:00
void GLCanvas3D :: _zoom_to_box ( const BoundingBoxf3 & box , double margin_factor )
{
2020-03-02 10:58:46 +01:00
wxGetApp (). plater () -> get_camera (). zoom_to_box ( box , margin_factor );
2019-10-31 16:40:38 +01:00
m_dirty = true ;
}
2018-05-15 09:50:01 +02:00
2019-10-03 11:38:31 +02:00
void GLCanvas3D :: _update_camera_zoom ( double zoom )
{
2020-03-02 10:58:46 +01:00
wxGetApp (). plater () -> get_camera (). update_zoom ( zoom );
2019-10-03 11:38:31 +02:00
m_dirty = true ;
}
2018-05-28 15:23:01 +02:00
void GLCanvas3D :: _refresh_if_shown_on_screen ()
{
2018-06-12 09:18:25 +02:00
if ( _is_shown_on_screen ())
2018-05-28 15:23:01 +02:00
{
const Size & cnv_size = get_canvas_size ();
2018-06-01 15:54:41 +02:00
_resize (( unsigned int ) cnv_size . get_width (), ( unsigned int ) cnv_size . get_height ());
2018-11-02 10:31:10 +01:00
// Because of performance problems on macOS, where PaintEvents are not delivered
// frequently enough, we call render() here directly when we can.
2019-01-17 13:21:33 +01:00
render ();
2018-05-28 15:23:01 +02:00
}
}
2018-05-29 13:54:34 +02:00
void GLCanvas3D :: _picking_pass () const
{
2020-10-21 11:04:48 +02:00
if ( m_picking_enabled && ! m_mouse . dragging && m_mouse . position != Vec2d ( DBL_MAX , DBL_MAX )) {
2019-04-24 15:07:28 +02:00
m_hover_volume_idxs . clear ();
2018-05-29 13:54:34 +02:00
// Render the object for picking.
// FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing.
// Better to use software ray - casting on a bounding - box hierarchy.
2018-06-01 15:54:41 +02:00
if ( m_multisample_allowed )
2019-08-07 11:14:04 +02:00
// This flag is often ignored by NVIDIA drivers if rendering into a screen buffer.
2019-03-27 14:42:09 +01:00
glsafe ( :: glDisable ( GL_MULTISAMPLE ));
2018-05-29 13:54:34 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glDisable ( GL_BLEND ));
glsafe ( :: glEnable ( GL_DEPTH_TEST ));
2018-05-29 13:54:34 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ));
2018-05-29 13:54:34 +02:00
2019-09-20 12:42:08 +02:00
m_camera_clipping_plane = m_gizmos . get_clipping_plane ();
2019-04-12 16:08:40 +02:00
if ( m_camera_clipping_plane . is_active ()) {
2019-04-11 15:44:32 +02:00
:: glClipPlane ( GL_CLIP_PLANE0 , ( GLdouble * ) m_camera_clipping_plane . get_data ());
:: glEnable ( GL_CLIP_PLANE0 );
}
2019-04-10 11:20:09 +02:00
_render_volumes_for_picking ();
2019-04-12 16:08:40 +02:00
if ( m_camera_clipping_plane . is_active ())
2019-04-11 15:44:32 +02:00
:: glDisable ( GL_CLIP_PLANE0 );
2019-03-25 12:01:02 +01:00
2019-07-17 08:38:48 +02:00
m_gizmos . render_current_gizmo_for_picking_pass ();
2018-05-29 13:54:34 +02:00
2018-06-01 15:54:41 +02:00
if ( m_multisample_allowed )
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnable ( GL_MULTISAMPLE ));
2018-05-29 13:54:34 +02:00
2018-07-30 09:09:14 +02:00
int volume_id = - 1 ;
2018-05-29 13:54:34 +02:00
2018-07-30 09:09:14 +02:00
GLubyte color [ 4 ] = { 0 , 0 , 0 , 0 };
const Size & cnv_size = get_canvas_size ();
2020-10-21 11:04:48 +02:00
bool inside = 0 <= m_mouse . position ( 0 ) && m_mouse . position ( 0 ) < cnv_size . get_width () && 0 <= m_mouse . position ( 1 ) && m_mouse . position ( 1 ) < cnv_size . get_height ();
if ( inside ) {
2019-04-25 13:41:00 +02:00
glsafe ( :: glReadPixels ( m_mouse . position ( 0 ), cnv_size . get_height () - m_mouse . position ( 1 ) - 1 , 1 , 1 , GL_RGBA , GL_UNSIGNED_BYTE , ( void * ) color ));
2019-08-07 14:15:38 +02:00
if ( picking_checksum_alpha_channel ( color [ 0 ], color [ 1 ], color [ 2 ]) == color [ 3 ])
2019-08-07 11:14:04 +02:00
// Only non-interpolated colors are valid, those have their lowest three bits zeroed.
2019-08-07 11:37:38 +02:00
volume_id = color [ 0 ] + ( color [ 1 ] << 8 ) + ( color [ 2 ] << 16 );
2018-07-30 09:09:14 +02:00
}
2020-10-21 11:04:48 +02:00
if ( 0 <= volume_id && volume_id < ( int ) m_volumes . volumes . size ()) {
// do not add the volume id if any gizmo is active and CTRL is pressed
if ( m_gizmos . get_current_type () == GLGizmosManager :: EType :: Undefined || ! wxGetKeyState ( WXK_CONTROL ))
m_hover_volume_idxs . emplace_back ( volume_id );
2018-06-14 15:32:26 +02:00
m_gizmos . set_hover_id ( - 1 );
2018-05-29 13:54:34 +02:00
}
2018-06-14 15:32:26 +02:00
else
2019-09-09 12:11:49 +02:00
m_gizmos . set_hover_id ( inside && ( unsigned int ) volume_id <= GLGizmoBase :: BASE_ID ? (( int ) GLGizmoBase :: BASE_ID - volume_id ) : - 1 );
2018-06-13 10:49:59 +02:00
2018-10-08 14:02:12 +02:00
_update_volumes_hover_state ();
2018-05-29 13:54:34 +02:00
}
}
2019-04-25 09:46:26 +02:00
void GLCanvas3D :: _rectangular_selection_picking_pass () const
{
m_gizmos . set_hover_id ( - 1 );
std :: set < int > idxs ;
if ( m_picking_enabled )
{
if ( m_multisample_allowed )
2019-08-07 11:14:04 +02:00
// This flag is often ignored by NVIDIA drivers if rendering into a screen buffer.
2019-04-25 09:46:26 +02:00
glsafe ( :: glDisable ( GL_MULTISAMPLE ));
glsafe ( :: glDisable ( GL_BLEND ));
glsafe ( :: glEnable ( GL_DEPTH_TEST ));
glsafe ( :: glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ));
_render_volumes_for_picking ();
if ( m_multisample_allowed )
glsafe ( :: glEnable ( GL_MULTISAMPLE ));
2019-04-29 08:31:32 +02:00
int width = std :: max (( int ) m_rectangle_selection . get_width (), 1 );
int height = std :: max (( int ) m_rectangle_selection . get_height (), 1 );
2019-04-25 09:46:26 +02:00
int px_count = width * height ;
2019-04-29 08:31:32 +02:00
int left = ( int ) m_rectangle_selection . get_left ();
int top = get_canvas_size (). get_height () - ( int ) m_rectangle_selection . get_top ();
if (( left >= 0 ) && ( top >= 0 ))
2019-04-25 09:46:26 +02:00
{
#define USE_PARALLEL 1
#if USE_PARALLEL
2019-04-29 08:31:32 +02:00
struct Pixel
{
std :: array < GLubyte , 4 > data ;
2019-08-07 11:14:04 +02:00
// Only non-interpolated colors are valid, those have their lowest three bits zeroed.
2019-08-07 14:15:38 +02:00
bool valid () const { return picking_checksum_alpha_channel ( data [ 0 ], data [ 1 ], data [ 2 ]) == data [ 3 ]; }
2019-08-07 11:37:38 +02:00
int id () const { return data [ 0 ] + ( data [ 1 ] << 8 ) + ( data [ 2 ] << 16 ); }
2019-04-29 08:31:32 +02:00
};
std :: vector < Pixel > frame ( px_count );
glsafe ( :: glReadPixels ( left , top , width , height , GL_RGBA , GL_UNSIGNED_BYTE , ( void * ) frame . data ()));
tbb :: spin_mutex mutex ;
tbb :: parallel_for ( tbb :: blocked_range < size_t > ( 0 , frame . size (), ( size_t ) width ),
[ this , & frame , & idxs , & mutex ]( const tbb :: blocked_range < size_t >& range ) {
for ( size_t i = range . begin (); i < range . end (); ++ i )
2019-08-07 11:14:04 +02:00
if ( frame [ i ]. valid ()) {
int volume_id = frame [ i ]. id ();
if (( 0 <= volume_id ) && ( volume_id < ( int ) m_volumes . volumes . size ())) {
mutex . lock ();
idxs . insert ( volume_id );
mutex . unlock ();
}
}
});
2019-04-29 08:31:32 +02:00
#else
std :: vector < GLubyte > frame ( 4 * px_count );
glsafe ( :: glReadPixels ( left , top , width , height , GL_RGBA , GL_UNSIGNED_BYTE , ( void * ) frame . data ()));
for ( int i = 0 ; i < px_count ; ++ i )
{
int px_id = 4 * i ;
int volume_id = frame [ px_id ] + ( frame [ px_id + 1 ] << 8 ) + ( frame [ px_id + 2 ] << 16 );
if (( 0 <= volume_id ) && ( volume_id < ( int ) m_volumes . volumes . size ()))
idxs . insert ( volume_id );
}
#endif // USE_PARALLEL
2019-04-25 09:46:26 +02:00
}
}
m_hover_volume_idxs . assign ( idxs . begin (), idxs . end ());
_update_volumes_hover_state ();
}
2020-05-07 10:49:12 +02:00
static BoundingBoxf3 print_volume ( const DynamicPrintConfig & config )
{
// tolerance to avoid false detection at bed edges
const double tolerance_x = 0.05 ;
const double tolerance_y = 0.05 ;
BoundingBoxf3 ret ;
const ConfigOptionPoints * opt = dynamic_cast < const ConfigOptionPoints *> ( config . option ( "bed_shape" ));
2020-06-08 12:27:32 +02:00
if ( opt != nullptr ) {
2020-05-07 10:49:12 +02:00
BoundingBox bed_box_2D = get_extents ( Polygon :: new_scale ( opt -> values ));
ret = BoundingBoxf3 ( Vec3d ( unscale < double > ( bed_box_2D . min ( 0 )) - tolerance_x , unscale < double > ( bed_box_2D . min ( 1 )) - tolerance_y , 0.0 ), Vec3d ( unscale < double > ( bed_box_2D . max ( 0 )) + tolerance_x , unscale < double > ( bed_box_2D . max ( 1 )) + tolerance_y , config . opt_float ( "max_print_height" )));
// Allow the objects to protrude below the print bed
ret . min ( 2 ) = - 1e10 ;
}
return ret ;
}
2018-05-29 13:54:34 +02:00
void GLCanvas3D :: _render_background () const
{
2020-06-08 12:27:32 +02:00
bool use_error_color = false ;
2020-09-08 11:40:06 +02:00
if ( wxGetApp (). is_editor ()) {
2020-09-30 13:24:37 +02:00
use_error_color = m_dynamic_background_enabled &&
( wxGetApp (). preset_bundle -> printers . get_edited_preset (). printer_technology () != ptSLA || ! m_volumes . empty ());
2020-06-08 12:27:32 +02:00
if ( ! m_volumes . empty ())
use_error_color &= _is_any_volume_outside ();
else {
BoundingBoxf3 test_volume = ( m_config != nullptr ) ? print_volume ( * m_config ) : BoundingBoxf3 ();
2020-06-24 16:57:09 +02:00
use_error_color &= ( test_volume . radius () > 0.0 ) ? ! test_volume . contains ( m_gcode_viewer . get_paths_bounding_box ()) : false ;
2020-06-08 12:27:32 +02:00
}
2020-05-07 10:49:12 +02:00
}
2019-03-27 14:42:09 +01:00
glsafe ( :: glPushMatrix ());
glsafe ( :: glLoadIdentity ());
glsafe ( :: glMatrixMode ( GL_PROJECTION ));
glsafe ( :: glPushMatrix ());
glsafe ( :: glLoadIdentity ());
2018-05-29 13:54:34 +02:00
2018-12-13 11:13:58 +01:00
// Draws a bottom to top gradient over the complete screen.
2019-03-27 14:42:09 +01:00
glsafe ( :: glDisable ( GL_DEPTH_TEST ));
2018-05-29 13:54:34 +02:00
:: glBegin ( GL_QUADS );
2020-05-07 10:49:12 +02:00
if ( use_error_color )
2018-12-12 10:38:07 +01:00
:: glColor3fv ( ERROR_BG_DARK_COLOR );
else
:: glColor3fv ( DEFAULT_BG_DARK_COLOR );
2018-07-27 09:38:39 +02:00
:: glVertex2f ( - 1.0f , - 1.0f );
:: glVertex2f ( 1.0f , - 1.0f );
2020-05-07 10:49:12 +02:00
if ( use_error_color )
2020-11-19 12:29:19 +01:00
:: glColor3fv ( ERROR_BG_LIGHT_COLOR );
2018-07-27 09:38:39 +02:00
else
2018-12-12 10:38:07 +01:00
:: glColor3fv ( DEFAULT_BG_LIGHT_COLOR );
2018-07-27 09:38:39 +02:00
:: glVertex2f ( 1.0f , 1.0f );
:: glVertex2f ( - 1.0f , 1.0f );
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnd ());
2018-05-29 13:54:34 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnable ( GL_DEPTH_TEST ));
2018-05-29 13:54:34 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glPopMatrix ());
glsafe ( :: glMatrixMode ( GL_MODELVIEW ));
glsafe ( :: glPopMatrix ());
2018-05-29 13:54:34 +02:00
}
2020-04-14 13:18:08 +02:00
void GLCanvas3D :: _render_bed ( bool bottom , bool show_axes ) const
2018-05-29 13:54:34 +02:00
{
2019-01-24 15:44:00 +01:00
float scale_factor = 1.0 ;
#if ENABLE_RETINA_GL
scale_factor = m_retina_helper -> get_scale_factor ();
2019-03-07 11:49:00 +01:00
#endif // ENABLE_RETINA_GL
2020-04-14 13:18:08 +02:00
bool show_texture = ! bottom ||
( m_gizmos . get_current_type () != GLGizmosManager :: FdmSupports
2020-08-31 07:25:12 +02:00
&& m_gizmos . get_current_type () != GLGizmosManager :: SlaSupports
2020-10-16 15:56:45 +02:00
&& m_gizmos . get_current_type () != GLGizmosManager :: Hollow
2020-08-31 07:25:12 +02:00
&& m_gizmos . get_current_type () != GLGizmosManager :: Seam );
2020-04-14 13:18:08 +02:00
wxGetApp (). plater () -> get_bed (). render ( const_cast < GLCanvas3D &> ( * this ), bottom , scale_factor , show_axes , show_texture );
2018-05-29 13:54:34 +02:00
}
2018-06-04 12:26:39 +02:00
void GLCanvas3D :: _render_objects () const
2018-05-29 13:54:34 +02:00
{
2018-06-11 15:13:13 +02:00
if ( m_volumes . empty ())
2018-05-29 13:54:34 +02:00
return ;
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnable ( GL_DEPTH_TEST ));
2018-05-29 13:54:34 +02:00
2019-09-20 12:42:08 +02:00
m_camera_clipping_plane = m_gizmos . get_clipping_plane ();
2019-03-20 08:48:42 +01:00
2020-05-22 16:08:02 +02:00
if ( m_picking_enabled ) {
2019-07-01 12:28:16 +02:00
// Update the layer editing selection to the first object selected, update the current object maximum Z.
const_cast < LayersEditing &> ( m_layers_editing ). select_object ( * m_model , this -> is_layers_editing_enabled () ? m_selection . get_object_idx () : - 1 );
2020-05-22 16:08:02 +02:00
if ( m_config != nullptr ) {
2020-03-02 10:58:46 +01:00
const BoundingBoxf3 & bed_bb = wxGetApp (). plater () -> get_bed (). get_bounding_box ( false );
2020-12-03 12:00:12 +01:00
m_volumes . set_print_box (( float ) bed_bb . min ( 0 ) - BedEpsilon , ( float ) bed_bb . min ( 1 ) - BedEpsilon , 0.0f , ( float ) bed_bb . max ( 0 ) + BedEpsilon , ( float ) bed_bb . max ( 1 ) + BedEpsilon , ( float ) m_config -> opt_float ( "max_print_height" ));
2019-07-01 12:28:16 +02:00
m_volumes . check_outside_state ( m_config , nullptr );
2018-05-29 13:54:34 +02:00
}
}
2019-07-01 12:28:16 +02:00
if ( m_use_clipping_planes )
m_volumes . set_z_range ( - m_clipping_planes [ 0 ]. get_data ()[ 3 ], m_clipping_planes [ 1 ]. get_data ()[ 3 ]);
2018-05-29 13:54:34 +02:00
else
2019-07-01 12:28:16 +02:00
m_volumes . set_z_range ( - FLT_MAX , FLT_MAX );
2019-03-25 12:01:02 +01:00
2019-07-01 12:28:16 +02:00
m_volumes . set_clipping_plane ( m_camera_clipping_plane . get_data ());
2018-11-27 14:50:57 +01:00
2020-05-20 14:11:22 +02:00
GLShaderProgram * shader = wxGetApp (). get_shader ( "gouraud" );
2020-05-22 16:08:02 +02:00
if ( shader != nullptr ) {
2020-05-20 14:11:22 +02:00
shader -> start_using ();
2020-05-22 16:08:02 +02:00
if ( m_picking_enabled && ! m_gizmos . is_dragging () && m_layers_editing . is_enabled () && ( m_layers_editing . last_object_id != - 1 ) && ( m_layers_editing . object_max_z () > 0.0f )) {
int object_id = m_layers_editing . last_object_id ;
m_volumes . render ( GLVolumeCollection :: Opaque , false , wxGetApp (). plater () -> get_camera (). get_view_matrix (), [ object_id ]( const GLVolume & volume ) {
// Which volume to paint without the layer height profile shader?
return volume . is_active && ( volume . is_modifier || volume . composite_id . object_id != object_id );
});
// Let LayersEditing handle rendering of the active object using the layer height profile shader.
m_layers_editing . render_volumes ( * this , this -> m_volumes );
} else {
2018-12-21 09:56:11 +01:00
// do not cull backfaces to show broken geometry, if any
2020-03-02 10:58:46 +01:00
m_volumes . render ( GLVolumeCollection :: Opaque , m_picking_enabled , wxGetApp (). plater () -> get_camera (). get_view_matrix (), [ this ]( const GLVolume & volume ) {
return ( m_render_sla_auxiliaries || volume . composite_id . volume_id >= 0 );
});
2020-05-22 16:08:02 +02:00
}
2020-03-02 10:58:46 +01:00
2020-10-05 16:27:16 +02:00
// In case a painting gizmo is open, it should render the painted triangles
// before transparent objects are rendered. Otherwise they would not be
// visible when inside modifier meshes etc.
{
const GLGizmosManager & gm = get_gizmos_manager ();
GLGizmosManager :: EType type = gm . get_current_type ();
if ( type == GLGizmosManager :: FdmSupports
|| type == GLGizmosManager :: Seam ) {
shader -> stop_using ();
gm . render_painter_gizmo ();
shader -> start_using ();
}
}
2020-05-22 16:08:02 +02:00
m_volumes . render ( GLVolumeCollection :: Transparent , false , wxGetApp (). plater () -> get_camera (). get_view_matrix ());
shader -> stop_using ();
2020-05-20 14:11:22 +02:00
}
2019-07-01 12:28:16 +02:00
2019-03-25 12:01:02 +01:00
m_camera_clipping_plane = ClippingPlane :: ClipsNothing ();
2018-05-29 13:54:34 +02:00
}
2020-04-14 10:02:08 +02:00
void GLCanvas3D :: _render_gcode () const
{
m_gcode_viewer . render ();
}
2018-10-08 14:02:12 +02:00
void GLCanvas3D :: _render_selection () const
{
2019-01-24 15:44:00 +01:00
float scale_factor = 1.0 ;
#if ENABLE_RETINA_GL
scale_factor = m_retina_helper -> get_scale_factor ();
2020-05-06 12:19:34 +02:00
#endif // ENABLE_RETINA_GL
2019-01-24 15:44:00 +01:00
2018-11-06 10:31:19 +01:00
if ( ! m_gizmos . is_running ())
2019-01-24 15:44:00 +01:00
m_selection . render ( scale_factor );
2018-10-08 14:02:12 +02:00
}
2018-12-18 12:35:49 +01:00
#if ENABLE_RENDER_SELECTION_CENTER
void GLCanvas3D :: _render_selection_center () const
{
2019-05-20 15:59:54 +02:00
m_selection . render_center ( m_gizmos . is_dragging ());
2018-12-18 12:35:49 +01:00
}
#endif // ENABLE_RENDER_SELECTION_CENTER
2020-05-12 23:07:06 +02:00
void GLCanvas3D :: _check_and_update_toolbar_icon_scale () const
{
2020-09-14 17:25:47 +02:00
// Don't update a toolbar scale, when we are on a Preview
if ( wxGetApp (). plater () -> is_preview_shown ())
return ;
2020-05-12 23:07:06 +02:00
float scale = wxGetApp (). toolbar_icon_scale ();
Size cnv_size = get_canvas_size ();
float size = GLToolbar :: Default_Icons_Size * scale ;
// Set current size for all top toolbars. It will be used for next calculations
2020-06-05 12:33:09 +02:00
GLToolbar & collapse_toolbar = wxGetApp (). plater () -> get_collapse_toolbar ();
2020-05-12 23:07:06 +02:00
#if ENABLE_RETINA_GL
const float sc = m_retina_helper -> get_scale_factor () * scale ;
m_main_toolbar . set_scale ( sc );
m_undoredo_toolbar . set_scale ( sc );
2020-06-05 12:33:09 +02:00
collapse_toolbar . set_scale ( sc );
2020-05-22 15:23:05 +02:00
size *= m_retina_helper -> get_scale_factor ();
2020-05-12 23:07:06 +02:00
#else
m_main_toolbar . set_icons_size ( size );
m_undoredo_toolbar . set_icons_size ( size );
2020-06-05 12:33:09 +02:00
collapse_toolbar . set_icons_size ( size );
2020-05-12 23:07:06 +02:00
#endif // ENABLE_RETINA_GL
2020-06-05 12:33:09 +02:00
float top_tb_width = m_main_toolbar . get_width () + m_undoredo_toolbar . get_width () + collapse_toolbar . get_width ();
int items_cnt = m_main_toolbar . get_visible_items_cnt () + m_undoredo_toolbar . get_visible_items_cnt () + collapse_toolbar . get_visible_items_cnt ();
2020-05-12 23:07:06 +02:00
float noitems_width = top_tb_width - size * items_cnt ; // width of separators and borders in top toolbars
// calculate scale needed for items in all top toolbars
float new_h_scale = ( cnv_size . get_width () - noitems_width ) / ( items_cnt * GLToolbar :: Default_Icons_Size );
items_cnt = m_gizmos . get_selectable_icons_cnt () + 3 ; // +3 means a place for top and view toolbars and separators in gizmos toolbar
// calculate scale needed for items in the gizmos toolbar
float new_v_scale = cnv_size . get_height () / ( items_cnt * GLGizmosManager :: Default_Icons_Size );
// set minimum scale as a auto scale for the toolbars
float new_scale = std :: min ( new_h_scale , new_v_scale );
2020-05-22 15:23:05 +02:00
#if ENABLE_RETINA_GL
new_scale /= m_retina_helper -> get_scale_factor ();
#endif
2020-05-19 16:50:39 +02:00
if ( fabs ( new_scale - scale ) > 0.01 ) // scale is changed by 1% and more
2020-05-12 23:07:06 +02:00
wxGetApp (). set_auto_toolbar_icon_scale ( new_scale );
}
2019-06-14 10:38:09 +02:00
void GLCanvas3D :: _render_overlays () const
{
glsafe ( :: glDisable ( GL_DEPTH_TEST ));
glsafe ( :: glPushMatrix ());
glsafe ( :: glLoadIdentity ());
2019-06-24 09:38:46 +02:00
// ensure that the textures are renderered inside the frustrum
2020-03-02 10:58:46 +01:00
const Camera & camera = wxGetApp (). plater () -> get_camera ();
glsafe ( :: glTranslated ( 0.0 , 0.0 , - ( camera . get_near_z () + 0.005 )));
// ensure that the overlay fits the frustrum near z plane
double gui_scale = camera . get_gui_scale ();
2019-06-24 09:38:46 +02:00
glsafe ( :: glScaled ( gui_scale , gui_scale , 1.0 ));
2019-06-14 10:38:09 +02:00
2020-05-12 23:07:06 +02:00
_check_and_update_toolbar_icon_scale ();
2019-06-14 10:38:09 +02:00
_render_gizmos_overlay ();
_render_warning_texture ();
2020-03-11 14:15:36 +01:00
// main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed
// to correctly place them
#if ENABLE_RETINA_GL
2020-05-12 23:07:06 +02:00
const float scale = m_retina_helper -> get_scale_factor () * wxGetApp (). toolbar_icon_scale ( /*true*/ );
2020-03-11 14:15:36 +01:00
m_main_toolbar . set_scale ( scale );
m_undoredo_toolbar . set_scale ( scale );
2020-06-05 12:33:09 +02:00
wxGetApp (). plater () -> get_collapse_toolbar (). set_scale ( scale );
2020-03-11 14:15:36 +01:00
#else
2020-05-12 23:07:06 +02:00
const float size = int ( GLToolbar :: Default_Icons_Size * wxGetApp (). toolbar_icon_scale ( /*true*/ ));
2020-03-11 14:15:36 +01:00
m_main_toolbar . set_icons_size ( size );
m_undoredo_toolbar . set_icons_size ( size );
2020-06-05 12:33:09 +02:00
wxGetApp (). plater () -> get_collapse_toolbar (). set_icons_size ( size );
2020-03-11 14:15:36 +01:00
#endif // ENABLE_RETINA_GL
2019-07-19 15:36:55 +02:00
_render_main_toolbar ();
_render_undoredo_toolbar ();
2020-04-23 12:41:38 +02:00
_render_collapse_toolbar ();
2019-06-14 10:38:09 +02:00
_render_view_toolbar ();
if (( m_layers_editing . last_object_id >= 0 ) && ( m_layers_editing . object_max_z () > 0.0f ))
m_layers_editing . render_overlay ( * this );
2020-02-03 11:44:26 +01:00
const ConfigOptionBool * opt = dynamic_cast < const ConfigOptionBool *> ( m_config -> option ( "complete_objects" ));
2020-02-06 14:03:18 +01:00
bool sequential_print = opt != nullptr && opt -> value ;
2020-02-06 15:19:53 +01:00
std :: vector < const ModelInstance *> sorted_instances ;
2020-02-03 11:44:26 +01:00
if ( sequential_print ) {
2020-02-06 15:19:53 +01:00
for ( ModelObject * model_object : m_model -> objects )
for ( ModelInstance * model_instance : model_object -> instances ) {
2020-03-13 15:57:07 +01:00
sorted_instances . emplace_back ( model_instance );
2020-02-06 15:19:53 +01:00
}
2020-02-03 11:44:26 +01:00
}
m_labels . render ( sorted_instances );
2020-01-28 15:57:02 +01:00
2019-06-14 10:38:09 +02:00
glsafe ( :: glPopMatrix ());
}
2018-05-29 13:54:34 +02:00
void GLCanvas3D :: _render_warning_texture () const
{
2018-07-31 14:20:16 +02:00
m_warning_texture . render ( * this );
2018-05-29 13:54:34 +02:00
}
2019-04-10 11:20:09 +02:00
void GLCanvas3D :: _render_volumes_for_picking () const
2018-06-01 15:54:41 +02:00
{
2018-06-14 15:32:26 +02:00
static const GLfloat INV_255 = 1.0f / 255.0f ;
2018-06-01 15:54:41 +02:00
// do not cull backfaces to show broken geometry, if any
2019-03-27 14:42:09 +01:00
glsafe ( :: glDisable ( GL_CULL_FACE ));
2018-06-01 15:54:41 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnableClientState ( GL_VERTEX_ARRAY ));
glsafe ( :: glEnableClientState ( GL_NORMAL_ARRAY ));
2018-06-01 15:54:41 +02:00
2020-03-02 10:58:46 +01:00
const Transform3d & view_matrix = wxGetApp (). plater () -> get_camera (). get_view_matrix ();
2019-08-07 11:14:04 +02:00
for ( size_t type = 0 ; type < 2 ; ++ type ) {
GLVolumeWithIdAndZList to_render = volumes_to_render ( m_volumes . volumes , ( type == 0 ) ? GLVolumeCollection :: Opaque : GLVolumeCollection :: Transparent , view_matrix );
for ( const GLVolumeWithIdAndZ & volume : to_render )
if ( ! volume . first -> disabled && (( volume . first -> composite_id . volume_id >= 0 ) || m_render_sla_auxiliaries )) {
// Object picking mode. Render the object with a color encoding the object index.
unsigned int id = volume . second . first ;
2019-08-07 11:37:38 +02:00
unsigned int r = ( id & ( 0x000000FF << 0 )) << 0 ;
unsigned int g = ( id & ( 0x000000FF << 8 )) >> 8 ;
unsigned int b = ( id & ( 0x000000FF << 16 )) >> 16 ;
2019-08-07 14:15:38 +02:00
unsigned int a = picking_checksum_alpha_channel ( r , g , b );
2019-08-07 11:14:04 +02:00
glsafe ( :: glColor4f (( GLfloat ) r * INV_255 , ( GLfloat ) g * INV_255 , ( GLfloat ) b * INV_255 , ( GLfloat ) a * INV_255 ));
volume . first -> render ();
}
}
2018-06-01 15:54:41 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glDisableClientState ( GL_NORMAL_ARRAY ));
glsafe ( :: glDisableClientState ( GL_VERTEX_ARRAY ));
2018-06-01 15:54:41 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnable ( GL_CULL_FACE ));
2018-06-01 15:54:41 +02:00
}
2018-08-21 14:27:36 +02:00
void GLCanvas3D :: _render_current_gizmo () const
2018-06-13 09:12:16 +02:00
{
2019-07-17 08:38:48 +02:00
m_gizmos . render_current_gizmo ();
2018-08-21 14:27:36 +02:00
}
void GLCanvas3D :: _render_gizmos_overlay () const
{
2019-01-24 11:30:29 +01:00
#if ENABLE_RETINA_GL
2019-05-22 13:51:02 +02:00
// m_gizmos.set_overlay_scale(m_retina_helper->get_scale_factor());
const float scale = m_retina_helper -> get_scale_factor () * wxGetApp (). toolbar_icon_scale ();
m_gizmos . set_overlay_scale ( scale ); //! #ys_FIXME_experiment
2019-02-04 21:41:10 +01:00
#else
2019-04-16 12:11:48 +02:00
// m_gizmos.set_overlay_scale(m_canvas->GetContentScaleFactor());
2019-05-22 13:51:02 +02:00
// m_gizmos.set_overlay_scale(wxGetApp().em_unit()*0.1f);
const float size = int ( GLGizmosManager :: Default_Icons_Size * wxGetApp (). toolbar_icon_scale ());
m_gizmos . set_overlay_icon_size ( size ); //! #ys_FIXME_experiment
2019-02-04 21:41:10 +01:00
#endif /* __WXMSW__ */
2019-07-17 08:38:48 +02:00
m_gizmos . render_overlay ();
2018-06-13 09:12:16 +02:00
}
2019-07-19 15:36:55 +02:00
void GLCanvas3D :: _render_main_toolbar () const
2018-07-23 13:49:48 +02:00
{
2019-07-19 15:36:55 +02:00
if ( ! m_main_toolbar . is_enabled ())
return ;
2019-02-26 09:56:23 +01:00
Size cnv_size = get_canvas_size ();
2020-03-02 10:58:46 +01:00
float inv_zoom = ( float ) wxGetApp (). plater () -> get_camera (). get_inv_zoom ();
2019-02-26 09:56:23 +01:00
2019-07-19 15:36:55 +02:00
float top = 0.5f * ( float ) cnv_size . get_height () * inv_zoom ;
2020-06-05 12:33:09 +02:00
const GLToolbar & collapse_toolbar = wxGetApp (). plater () -> get_collapse_toolbar ();
float collapse_toolbar_width = collapse_toolbar . is_enabled () ? collapse_toolbar . get_width () : 0.0f ;
2020-05-12 23:34:08 +02:00
float left = - 0.5f * ( m_main_toolbar . get_width () + m_undoredo_toolbar . get_width () + collapse_toolbar_width ) * inv_zoom ;
2019-02-26 09:56:23 +01:00
2019-07-19 15:36:55 +02:00
m_main_toolbar . set_position ( top , left );
m_main_toolbar . render ( * this );
}
2019-02-04 21:41:10 +01:00
2019-07-19 15:36:55 +02:00
void GLCanvas3D :: _render_undoredo_toolbar () const
{
if ( ! m_undoredo_toolbar . is_enabled ())
return ;
Size cnv_size = get_canvas_size ();
2020-03-02 10:58:46 +01:00
float inv_zoom = ( float ) wxGetApp (). plater () -> get_camera (). get_inv_zoom ();
2019-07-19 15:36:55 +02:00
float top = 0.5f * ( float ) cnv_size . get_height () * inv_zoom ;
2020-06-05 12:33:09 +02:00
const GLToolbar & collapse_toolbar = wxGetApp (). plater () -> get_collapse_toolbar ();
float collapse_toolbar_width = collapse_toolbar . is_enabled () ? collapse_toolbar . get_width () : 0.0f ;
2020-05-12 23:34:08 +02:00
float left = ( m_main_toolbar . get_width () - 0.5f * ( m_main_toolbar . get_width () + m_undoredo_toolbar . get_width () + collapse_toolbar_width )) * inv_zoom ;
2019-07-19 15:36:55 +02:00
m_undoredo_toolbar . set_position ( top , left );
m_undoredo_toolbar . render ( * this );
2018-07-23 13:49:48 +02:00
}
2020-04-23 12:41:38 +02:00
void GLCanvas3D :: _render_collapse_toolbar () const
{
2020-06-05 12:33:09 +02:00
GLToolbar & collapse_toolbar = wxGetApp (). plater () -> get_collapse_toolbar ();
2020-04-23 12:41:38 +02:00
Size cnv_size = get_canvas_size ();
float inv_zoom = ( float ) wxGetApp (). plater () -> get_camera (). get_inv_zoom ();
float band = m_layers_editing . is_enabled () ? ( wxGetApp (). imgui () -> get_style_scaling () * LayersEditing :: THICKNESS_BAR_WIDTH ) : 0.0 ;
float top = 0.5f * ( float ) cnv_size . get_height () * inv_zoom ;
2020-06-05 12:33:09 +02:00
float left = ( 0.5f * ( float ) cnv_size . get_width () - ( float ) collapse_toolbar . get_width () - band ) * inv_zoom ;
2020-04-23 12:41:38 +02:00
2020-06-05 12:33:09 +02:00
collapse_toolbar . set_position ( top , left );
collapse_toolbar . render ( * this );
2020-04-23 12:41:38 +02:00
}
2018-12-06 10:38:19 +01:00
void GLCanvas3D :: _render_view_toolbar () const
{
2020-03-02 10:58:46 +01:00
GLToolbar & view_toolbar = wxGetApp (). plater () -> get_view_toolbar ();
2020-12-18 11:27:28 +01:00
#if ENABLE_RETINA_GL
2019-05-22 13:51:02 +02:00
const float scale = m_retina_helper -> get_scale_factor () * wxGetApp (). toolbar_icon_scale ();
2020-12-18 11:27:28 +01:00
#if __APPLE__
view_toolbar . set_scale ( scale );
#else // if GTK3
const float size = int ( GLGizmosManager :: Default_Icons_Size * scale );
view_toolbar . set_icons_size ( size );
2020-12-18 08:40:17 +01:00
#endif // __APPLE__
2020-12-18 11:27:28 +01:00
#else
const float size = int ( GLGizmosManager :: Default_Icons_Size * wxGetApp (). toolbar_icon_scale ());
view_toolbar . set_icons_size ( size );
#endif // ENABLE_RETINA_GL
2019-02-26 09:56:23 +01:00
2019-03-07 11:49:00 +01:00
Size cnv_size = get_canvas_size ();
2020-03-02 10:58:46 +01:00
float inv_zoom = ( float ) wxGetApp (). plater () -> get_camera (). get_inv_zoom ();
2019-02-26 09:56:23 +01:00
2019-03-07 11:49:00 +01:00
// places the toolbar on the bottom-left corner of the 3d scene
2020-03-02 10:58:46 +01:00
float top = ( - 0.5f * ( float ) cnv_size . get_height () + view_toolbar . get_height ()) * inv_zoom ;
2019-03-07 11:49:00 +01:00
float left = - 0.5f * ( float ) cnv_size . get_width () * inv_zoom ;
2020-03-02 10:58:46 +01:00
view_toolbar . set_position ( top , left );
view_toolbar . render ( * this );
2018-12-06 10:38:19 +01:00
}
2018-10-26 09:50:28 +02:00
#if ENABLE_SHOW_CAMERA_TARGET
void GLCanvas3D :: _render_camera_target () const
{
double half_length = 5.0 ;
2019-03-27 14:42:09 +01:00
glsafe ( :: glDisable ( GL_DEPTH_TEST ));
2018-10-26 09:50:28 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glLineWidth ( 2.0f ));
2018-10-26 09:50:28 +02:00
:: glBegin ( GL_LINES );
2019-03-07 11:49:00 +01:00
const Vec3d & target = m_camera . get_target ();
2018-12-07 16:23:04 +01:00
// draw line for x axis
:: glColor3f ( 1.0f , 0.0f , 0.0f );
:: glVertex3d ( target ( 0 ) - half_length , target ( 1 ), target ( 2 ));
:: glVertex3d ( target ( 0 ) + half_length , target ( 1 ), target ( 2 ));
// draw line for y axis
:: glColor3f ( 0.0f , 1.0f , 0.0f );
:: glVertex3d ( target ( 0 ), target ( 1 ) - half_length , target ( 2 ));
:: glVertex3d ( target ( 0 ), target ( 1 ) + half_length , target ( 2 ));
2018-12-18 13:07:50 +01:00
// draw line for z axis
2018-12-07 16:23:04 +01:00
:: glColor3f ( 0.0f , 0.0f , 1.0f );
:: glVertex3d ( target ( 0 ), target ( 1 ), target ( 2 ) - half_length );
:: glVertex3d ( target ( 0 ), target ( 1 ), target ( 2 ) + half_length );
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnd ());
2018-10-26 09:50:28 +02:00
}
#endif // ENABLE_SHOW_CAMERA_TARGET
2018-11-28 15:13:25 +01:00
void GLCanvas3D :: _render_sla_slices () const
{
if ( ! m_use_clipping_planes || wxGetApp (). preset_bundle -> printers . get_edited_preset (). printer_technology () != ptSLA )
return ;
const SLAPrint * print = this -> sla_print ();
2018-12-10 12:59:49 +01:00
const PrintObjects & print_objects = print -> objects ();
if ( print_objects . empty ())
2018-11-28 15:13:25 +01:00
// nothing to render, return
return ;
double clip_min_z = - m_clipping_planes [ 0 ]. get_data ()[ 3 ];
double clip_max_z = m_clipping_planes [ 1 ]. get_data ()[ 3 ];
2018-12-10 12:59:49 +01:00
for ( unsigned int i = 0 ; i < ( unsigned int ) print_objects . size (); ++ i )
2018-11-28 15:13:25 +01:00
{
2018-12-10 12:59:49 +01:00
const SLAPrintObject * obj = print_objects [ i ];
2019-04-02 12:13:45 +02:00
if ( ! obj -> is_step_done ( slaposSliceSupports ))
continue ;
2019-01-24 19:08:58 +01:00
SlaCap :: ObjectIdToTrianglesMap :: iterator it_caps_bottom = m_sla_caps [ 0 ]. triangles . find ( i );
SlaCap :: ObjectIdToTrianglesMap :: iterator it_caps_top = m_sla_caps [ 1 ]. triangles . find ( i );
2018-12-10 12:59:49 +01:00
{
2019-01-24 19:08:58 +01:00
if ( it_caps_bottom == m_sla_caps [ 0 ]. triangles . end ())
it_caps_bottom = m_sla_caps [ 0 ]. triangles . emplace ( i , SlaCap :: Triangles ()). first ;
2019-03-22 21:26:58 +01:00
if ( ! m_sla_caps [ 0 ]. matches ( clip_min_z )) {
m_sla_caps [ 0 ]. z = clip_min_z ;
2019-01-24 19:08:58 +01:00
it_caps_bottom -> second . object . clear ();
it_caps_bottom -> second . supports . clear ();
2018-12-10 12:59:49 +01:00
}
2019-01-24 19:08:58 +01:00
if ( it_caps_top == m_sla_caps [ 1 ]. triangles . end ())
it_caps_top = m_sla_caps [ 1 ]. triangles . emplace ( i , SlaCap :: Triangles ()). first ;
2019-03-22 21:26:58 +01:00
if ( ! m_sla_caps [ 1 ]. matches ( clip_max_z )) {
m_sla_caps [ 1 ]. z = clip_max_z ;
2019-01-24 19:08:58 +01:00
it_caps_top -> second . object . clear ();
it_caps_top -> second . supports . clear ();
2018-12-10 12:59:49 +01:00
}
}
2019-01-24 19:08:58 +01:00
Pointf3s & bottom_obj_triangles = it_caps_bottom -> second . object ;
Pointf3s & bottom_sup_triangles = it_caps_bottom -> second . supports ;
Pointf3s & top_obj_triangles = it_caps_top -> second . object ;
Pointf3s & top_sup_triangles = it_caps_top -> second . supports ;
2018-12-10 12:59:49 +01:00
2019-03-25 19:02:05 +01:00
if (( bottom_obj_triangles . empty () || bottom_sup_triangles . empty () || top_obj_triangles . empty () || top_sup_triangles . empty ()) &&
2019-04-02 12:13:45 +02:00
! obj -> get_slice_index (). empty ())
2018-11-28 15:13:25 +01:00
{
2019-03-26 10:38:50 +01:00
double layer_height = print -> default_object_config (). layer_height . value ;
2019-03-22 21:26:58 +01:00
double initial_layer_height = print -> material_config (). initial_layer_height . value ;
2019-04-02 13:47:49 +02:00
bool left_handed = obj -> is_left_handed ();
2019-03-26 10:38:50 +01:00
2019-03-25 19:02:05 +01:00
coord_t key_zero = obj -> get_slice_index (). front (). print_level ();
2019-03-26 11:13:28 +01:00
// Slice at the center of the slab starting at clip_min_z will be rendered for the lower plane.
coord_t key_low = coord_t (( clip_min_z - initial_layer_height + layer_height ) / SCALING_FACTOR ) + key_zero ;
// Slice at the center of the slab ending at clip_max_z will be rendered for the upper plane.
2019-03-25 19:02:05 +01:00
coord_t key_high = coord_t (( clip_max_z - initial_layer_height ) / SCALING_FACTOR ) + key_zero ;
2019-03-26 11:13:28 +01:00
const SliceRecord & slice_low = obj -> closest_slice_to_print_level ( key_low , coord_t ( SCALED_EPSILON ));
const SliceRecord & slice_high = obj -> closest_slice_to_print_level ( key_high , coord_t ( SCALED_EPSILON ));
// Offset to avoid OpenGL Z fighting between the object's horizontal surfaces and the triangluated surfaces of the cuts.
double plane_shift_z = 0.002 ;
2019-03-26 16:45:04 +01:00
if ( slice_low . is_valid ()) {
2019-03-26 10:57:45 +01:00
const ExPolygons & obj_bottom = slice_low . get_slice ( soModel );
const ExPolygons & sup_bottom = slice_low . get_slice ( soSupport );
2019-03-22 21:26:58 +01:00
// calculate model bottom cap
if ( bottom_obj_triangles . empty () && ! obj_bottom . empty ())
2019-04-02 13:47:49 +02:00
bottom_obj_triangles = triangulate_expolygons_3d ( obj_bottom , clip_min_z - plane_shift_z , ! left_handed );
2019-03-22 21:26:58 +01:00
// calculate support bottom cap
if ( bottom_sup_triangles . empty () && ! sup_bottom . empty ())
2019-04-02 13:47:49 +02:00
bottom_sup_triangles = triangulate_expolygons_3d ( sup_bottom , clip_min_z - plane_shift_z , ! left_handed );
2019-03-22 21:26:58 +01:00
}
2018-11-28 15:13:25 +01:00
2019-03-26 16:45:04 +01:00
if ( slice_high . is_valid ()) {
2019-03-26 10:57:45 +01:00
const ExPolygons & obj_top = slice_high . get_slice ( soModel );
const ExPolygons & sup_top = slice_high . get_slice ( soSupport );
2019-03-22 21:26:58 +01:00
// calculate model top cap
if ( top_obj_triangles . empty () && ! obj_top . empty ())
2019-04-02 13:47:49 +02:00
top_obj_triangles = triangulate_expolygons_3d ( obj_top , clip_max_z + plane_shift_z , left_handed );
2019-03-22 21:26:58 +01:00
// calculate support top cap
if ( top_sup_triangles . empty () && ! sup_top . empty ())
2019-04-02 13:47:49 +02:00
top_sup_triangles = triangulate_expolygons_3d ( sup_top , clip_max_z + plane_shift_z , left_handed );
2019-03-22 21:26:58 +01:00
}
2018-12-10 12:59:49 +01:00
}
if ( ! bottom_obj_triangles . empty () || ! top_obj_triangles . empty () || ! bottom_sup_triangles . empty () || ! top_sup_triangles . empty ())
{
2019-04-02 13:47:49 +02:00
for ( const SLAPrintObject :: Instance & inst : obj -> instances ())
2018-12-10 12:59:49 +01:00
{
2019-03-27 14:42:09 +01:00
glsafe ( :: glPushMatrix ());
2019-04-02 15:13:26 +02:00
glsafe ( :: glTranslated ( unscale < double > ( inst . shift . x ()), unscale < double > ( inst . shift . y ()), 0 ));
glsafe ( :: glRotatef ( Geometry :: rad2deg ( inst . rotation ), 0.0 , 0.0 , 1.0 ));
2019-04-02 13:47:49 +02:00
if ( obj -> is_left_handed ())
// The polygons are mirrored by X.
2019-04-02 15:13:26 +02:00
glsafe ( :: glScalef ( - 1.0 , 1.0 , 1.0 ));
glsafe ( :: glEnableClientState ( GL_VERTEX_ARRAY ));
2019-04-03 08:38:32 +02:00
glsafe ( :: glColor3f ( 1.0f , 0.37f , 0.0f ));
2019-04-02 18:04:23 +02:00
if ( ! bottom_obj_triangles . empty ()) {
2019-04-03 08:38:32 +02:00
glsafe ( :: glVertexPointer ( 3 , GL_DOUBLE , 0 , ( GLdouble * ) bottom_obj_triangles . front (). data ()));
glsafe ( :: glDrawArrays ( GL_TRIANGLES , 0 , bottom_obj_triangles . size ()));
2019-04-02 18:04:23 +02:00
}
if ( ! top_obj_triangles . empty ()) {
2019-04-03 08:38:32 +02:00
glsafe ( :: glVertexPointer ( 3 , GL_DOUBLE , 0 , ( GLdouble * ) top_obj_triangles . front (). data ()));
glsafe ( :: glDrawArrays ( GL_TRIANGLES , 0 , top_obj_triangles . size ()));
2019-04-02 18:04:23 +02:00
}
2019-04-02 15:13:26 +02:00
glsafe ( :: glColor3f ( 1.0f , 0.0f , 0.37f ));
2019-04-02 18:04:23 +02:00
if ( ! bottom_sup_triangles . empty ()) {
2019-04-03 08:38:32 +02:00
glsafe ( :: glVertexPointer ( 3 , GL_DOUBLE , 0 , ( GLdouble * ) bottom_sup_triangles . front (). data ()));
glsafe ( :: glDrawArrays ( GL_TRIANGLES , 0 , bottom_sup_triangles . size ()));
2019-04-02 18:04:23 +02:00
}
if ( ! top_sup_triangles . empty ()) {
2019-04-03 08:38:32 +02:00
glsafe ( :: glVertexPointer ( 3 , GL_DOUBLE , 0 , ( GLdouble * ) top_sup_triangles . front (). data ()));
glsafe ( :: glDrawArrays ( GL_TRIANGLES , 0 , top_sup_triangles . size ()));
2019-04-02 18:04:23 +02:00
}
2019-04-02 15:13:26 +02:00
glsafe ( :: glDisableClientState ( GL_VERTEX_ARRAY ));
2019-03-27 14:42:09 +01:00
glsafe ( :: glPopMatrix ());
2018-11-28 15:13:25 +01:00
}
}
}
}
2018-12-19 14:44:37 +01:00
void GLCanvas3D :: _render_selection_sidebar_hints () const
{
2020-05-13 09:07:06 +02:00
m_selection . render_sidebar_hints ( m_sidebar_field );
2018-12-19 14:44:37 +01:00
}
2018-10-08 14:02:12 +02:00
void GLCanvas3D :: _update_volumes_hover_state () const
{
for ( GLVolume * v : m_volumes . volumes )
{
2019-04-25 13:35:24 +02:00
v -> hover = GLVolume :: HS_None ;
2018-10-08 14:02:12 +02:00
}
2019-04-24 15:07:28 +02:00
if ( m_hover_volume_idxs . empty ())
2018-10-08 14:02:12 +02:00
return ;
2019-04-26 13:37:34 +02:00
bool ctrl_pressed = wxGetKeyState ( WXK_CONTROL ); // additive select/deselect
bool shift_pressed = wxGetKeyState ( WXK_SHIFT ); // select by rectangle
bool alt_pressed = wxGetKeyState ( WXK_ALT ); // deselect by rectangle
2018-10-08 14:02:12 +02:00
2019-04-26 13:37:34 +02:00
if ( alt_pressed && ( shift_pressed || ctrl_pressed ))
{
// illegal combinations of keys
m_hover_volume_idxs . clear ();
return ;
}
bool selection_modifiers_only = m_selection . is_empty () || m_selection . is_any_modifier ();
bool hover_modifiers_only = true ;
for ( int i : m_hover_volume_idxs )
{
if ( ! m_volumes . volumes [ i ] -> is_modifier )
2018-10-08 14:02:12 +02:00
{
2019-04-26 13:37:34 +02:00
hover_modifiers_only = false ;
break ;
}
}
std :: set < std :: pair < int , int >> hover_instances ;
for ( int i : m_hover_volume_idxs )
{
const GLVolume & v = * m_volumes . volumes [ i ];
hover_instances . insert ( std :: make_pair ( v . object_idx (), v . instance_idx ()));
}
bool hover_from_single_instance = hover_instances . size () == 1 ;
if ( hover_modifiers_only && ! hover_from_single_instance )
{
// do not allow to select volumes from different instances
m_hover_volume_idxs . clear ();
return ;
}
2018-10-08 14:02:12 +02:00
2019-04-25 09:46:26 +02:00
for ( int i : m_hover_volume_idxs )
{
2019-04-26 13:37:34 +02:00
GLVolume & volume = * m_volumes . volumes [ i ];
if ( volume . hover != GLVolume :: HS_None )
continue ;
bool deselect = volume . selected && (( ctrl_pressed && ! shift_pressed ) || alt_pressed );
// (volume->is_modifier && !selection_modifiers_only && !is_ctrl_pressed) -> allows hovering on selected modifiers belonging to selection of type Instance
bool select = ( ! volume . selected || ( volume . is_modifier && ! selection_modifiers_only && ! ctrl_pressed )) && ! alt_pressed ;
2019-04-25 09:46:26 +02:00
if ( select || deselect )
2018-10-08 14:02:12 +02:00
{
2019-04-26 13:37:34 +02:00
bool as_volume =
volume . is_modifier && hover_from_single_instance && ! ctrl_pressed &&
(
( ! deselect ) ||
( deselect && ! m_selection . is_single_full_instance () && ( volume . object_idx () == m_selection . get_object_idx ()) && ( volume . instance_idx () == m_selection . get_instance_idx ()))
);
if ( as_volume )
2019-04-25 09:46:26 +02:00
{
if ( deselect )
2019-04-26 13:37:34 +02:00
volume . hover = GLVolume :: HS_Deselect ;
2019-04-25 09:46:26 +02:00
else
2019-04-26 13:37:34 +02:00
volume . hover = GLVolume :: HS_Select ;
2019-04-25 09:46:26 +02:00
}
else
{
2019-04-26 13:37:34 +02:00
int object_idx = volume . object_idx ();
int instance_idx = volume . instance_idx ();
2019-04-25 09:46:26 +02:00
for ( GLVolume * v : m_volumes . volumes )
{
if (( v -> object_idx () == object_idx ) && ( v -> instance_idx () == instance_idx ))
{
if ( deselect )
2019-04-25 13:35:24 +02:00
v -> hover = GLVolume :: HS_Deselect ;
2019-04-25 09:46:26 +02:00
else
2019-04-25 13:35:24 +02:00
v -> hover = GLVolume :: HS_Select ;
2019-04-25 09:46:26 +02:00
}
}
}
2018-10-08 14:02:12 +02:00
}
}
}
2018-05-30 15:18:45 +02:00
void GLCanvas3D :: _perform_layer_editing_action ( wxMouseEvent * evt )
{
2018-06-01 15:54:41 +02:00
int object_idx_selected = m_layers_editing . last_object_id ;
2018-05-30 15:18:45 +02:00
if ( object_idx_selected == - 1 )
return ;
// A volume is selected. Test, whether hovering over a layer thickness bar.
if ( evt != nullptr )
{
const Rect & rect = LayersEditing :: get_bar_rect_screen ( * this );
float b = rect . get_bottom ();
2019-01-21 10:06:51 +01:00
m_layers_editing . last_z = m_layers_editing . object_max_z () * ( b - evt -> GetY () - 1.0f ) / ( b - rect . get_top ());
m_layers_editing . last_action =
evt -> ShiftDown () ? ( evt -> RightIsDown () ? LAYER_HEIGHT_EDIT_ACTION_SMOOTH : LAYER_HEIGHT_EDIT_ACTION_REDUCE ) :
( evt -> RightIsDown () ? LAYER_HEIGHT_EDIT_ACTION_INCREASE : LAYER_HEIGHT_EDIT_ACTION_DECREASE );
2018-05-30 15:18:45 +02:00
}
2019-01-21 10:06:51 +01:00
m_layers_editing . adjust_layer_height_profile ();
2018-05-30 15:18:45 +02:00
_refresh_if_shown_on_screen ();
// Automatic action on mouse down with the same coordinate.
2018-06-01 15:54:41 +02:00
_start_timer ();
}
2018-08-21 17:43:05 +02:00
Vec3d GLCanvas3D :: _mouse_to_3d ( const Point & mouse_pos , float * z )
2018-05-31 13:51:50 +02:00
{
2018-06-25 15:17:13 +02:00
if ( m_canvas == nullptr )
2018-08-21 17:43:05 +02:00
return Vec3d ( DBL_MAX , DBL_MAX , DBL_MAX );
2018-05-31 13:51:50 +02:00
2020-03-02 10:58:46 +01:00
const Camera & camera = wxGetApp (). plater () -> get_camera ();
const std :: array < int , 4 >& viewport = camera . get_viewport ();
const Transform3d & modelview_matrix = camera . get_view_matrix ();
const Transform3d & projection_matrix = camera . get_projection_matrix ();
2018-05-31 13:51:50 +02:00
2018-08-17 15:53:43 +02:00
GLint y = viewport [ 3 ] - ( GLint ) mouse_pos ( 1 );
2018-05-31 13:51:50 +02:00
GLfloat mouse_z ;
if ( z == nullptr )
2019-03-27 14:42:09 +01:00
glsafe ( :: glReadPixels (( GLint ) mouse_pos ( 0 ), y , 1 , 1 , GL_DEPTH_COMPONENT , GL_FLOAT , ( void * ) & mouse_z ));
2018-05-31 13:51:50 +02:00
else
mouse_z = * z ;
GLdouble out_x , out_y , out_z ;
2019-04-01 10:00:10 +02:00
:: gluUnProject (( GLdouble ) mouse_pos ( 0 ), ( GLdouble ) y , ( GLdouble ) mouse_z , ( GLdouble * ) modelview_matrix . data (), ( GLdouble * ) projection_matrix . data (), ( GLint * ) viewport . data (), & out_x , & out_y , & out_z );
2018-08-24 10:20:00 +02:00
return Vec3d (( double ) out_x , ( double ) out_y , ( double ) out_z );
2018-05-31 13:51:50 +02:00
}
2018-08-21 17:43:05 +02:00
Vec3d GLCanvas3D :: _mouse_to_bed_3d ( const Point & mouse_pos )
2018-08-20 10:23:17 +02:00
{
return mouse_ray ( mouse_pos ). intersect_plane ( 0.0 );
}
2018-06-01 15:54:41 +02:00
void GLCanvas3D :: _start_timer ()
{
2018-10-25 09:35:08 +02:00
m_timer . Start ( 100 , wxTIMER_CONTINUOUS );
2018-06-01 15:54:41 +02:00
}
void GLCanvas3D :: _stop_timer ()
{
2018-10-25 09:35:08 +02:00
m_timer . Stop ();
2018-06-01 15:54:41 +02:00
}
2018-07-24 13:39:17 +02:00
void GLCanvas3D :: _load_print_toolpaths ()
{
2018-11-22 15:29:59 +01:00
const Print * print = this -> fff_print ();
if ( print == nullptr )
2018-07-24 13:39:17 +02:00
return ;
2018-11-22 15:29:59 +01:00
if ( ! print -> is_step_done ( psSkirt ) || ! print -> is_step_done ( psBrim ))
2018-07-24 13:39:17 +02:00
return ;
2018-11-22 15:29:59 +01:00
if ( ! print -> has_skirt () && ( print -> config (). brim_width . value == 0 ))
2018-07-24 13:39:17 +02:00
return ;
const float color [] = { 0.5f , 1.0f , 0.5f , 1.0f }; // greenish
// number of skirt layers
size_t total_layer_count = 0 ;
2018-11-22 15:29:59 +01:00
for ( const PrintObject * print_object : print -> objects ())
2018-07-24 13:39:17 +02:00
{
total_layer_count = std :: max ( total_layer_count , print_object -> total_layer_count ());
}
2018-11-22 15:29:59 +01:00
size_t skirt_height = print -> has_infinite_skirt () ? total_layer_count : std :: min < size_t > ( print -> config (). skirt_height . value , total_layer_count );
if (( skirt_height == 0 ) && ( print -> config (). brim_width . value > 0 ))
2018-07-24 13:39:17 +02:00
skirt_height = 1 ;
2020-03-13 15:57:07 +01:00
// Get first skirt_height layers.
//FIXME This code is fishy. It may not work for multiple objects with different layering due to variable layer height feature.
// This is not critical as this is just an initial preview.
const PrintObject * highest_object = * std :: max_element ( print -> objects (). begin (), print -> objects (). end (), []( auto l , auto r ){ return l -> layers (). size () < r -> layers (). size (); });
2018-07-24 13:39:17 +02:00
std :: vector < float > print_zs ;
print_zs . reserve ( skirt_height * 2 );
2020-03-13 15:57:07 +01:00
for ( size_t i = 0 ; i < std :: min ( skirt_height , highest_object -> layers (). size ()); ++ i )
print_zs . emplace_back ( float ( highest_object -> layers ()[ i ] -> print_z ));
// Only add skirt for the raft layers.
for ( size_t i = 0 ; i < std :: min ( skirt_height , std :: min ( highest_object -> slicing_parameters (). raft_layers (), highest_object -> support_layers (). size ())); ++ i )
print_zs . emplace_back ( float ( highest_object -> support_layers ()[ i ] -> print_z ));
2018-07-24 13:39:17 +02:00
sort_remove_duplicates ( print_zs );
2020-03-13 15:57:07 +01:00
skirt_height = std :: min ( skirt_height , print_zs . size ());
print_zs . erase ( print_zs . begin () + skirt_height , print_zs . end ());
2018-07-24 13:39:17 +02:00
2019-08-26 11:12:48 +02:00
GLVolume * volume = m_volumes . new_toolpath_volume ( color , VERTEX_BUFFER_RESERVE_SIZE );
2020-03-13 15:57:07 +01:00
for ( size_t i = 0 ; i < skirt_height ; ++ i ) {
volume -> print_zs . emplace_back ( print_zs [ i ]);
volume -> offsets . emplace_back ( volume -> indexed_vertex_array . quad_indices . size ());
volume -> offsets . emplace_back ( volume -> indexed_vertex_array . triangle_indices . size ());
2019-08-26 11:12:48 +02:00
if ( i == 0 )
_3DScene :: extrusionentity_to_verts ( print -> brim (), print_zs [ i ], Point ( 0 , 0 ), * volume );
_3DScene :: extrusionentity_to_verts ( print -> skirt (), print_zs [ i ], Point ( 0 , 0 ), * volume );
// Ensure that no volume grows over the limits. If the volume is too large, allocate a new one.
if ( volume -> indexed_vertex_array . vertices_and_normals_interleaved . size () > MAX_VERTEX_BUFFER_SIZE ) {
GLVolume & vol = * volume ;
volume = m_volumes . new_toolpath_volume ( vol . color );
reserve_new_volume_finalize_old_volume ( * volume , vol , m_initialized );
}
2018-07-24 13:39:17 +02:00
}
2019-08-26 11:12:48 +02:00
volume -> indexed_vertex_array . finalize_geometry ( m_initialized );
2018-07-24 13:39:17 +02:00
}
2020-01-23 12:49:39 +01:00
void GLCanvas3D :: _load_print_object_toolpaths ( const PrintObject & print_object , const std :: vector < std :: string >& str_tool_colors , const std :: vector < CustomGCode :: Item >& color_print_values )
2018-07-24 13:39:17 +02:00
{
std :: vector < float > tool_colors = _parse_colors ( str_tool_colors );
struct Ctxt
{
2020-01-23 09:53:06 +01:00
const PrintInstances * shifted_copies ;
2018-07-24 13:39:17 +02:00
std :: vector < const Layer *> layers ;
bool has_perimeters ;
bool has_infill ;
bool has_support ;
const std :: vector < float >* tool_colors ;
2019-11-03 19:33:02 +01:00
bool is_single_material_print ;
2019-11-04 13:42:47 +01:00
int extruders_cnt ;
2020-01-23 12:49:39 +01:00
const std :: vector < CustomGCode :: Item >* color_print_values ;
2018-07-24 13:39:17 +02:00
static const float * color_perimeters () { static float color [ 4 ] = { 1.0f , 1.0f , 0.0f , 1.f }; return color ; } // yellow
static const float * color_infill () { static float color [ 4 ] = { 1.0f , 0.5f , 0.5f , 1.f }; return color ; } // redish
static const float * color_support () { static float color [ 4 ] = { 0.5f , 1.0f , 0.5f , 1.f }; return color ; } // greenish
2019-11-03 19:33:02 +01:00
static const float * color_pause_or_custom_code () { static float color [ 4 ] = { 0.5f , 0.5f , 0.5f , 1.f }; return color ; } // gray
2018-07-24 13:39:17 +02:00
// For cloring by a tool, return a parsed color.
bool color_by_tool () const { return tool_colors != nullptr ; }
size_t number_tools () const { return this -> color_by_tool () ? tool_colors -> size () / 4 : 0 ; }
const float * color_tool ( size_t tool ) const { return tool_colors -> data () + tool * 4 ; }
2019-01-29 15:11:29 +01:00
// For coloring by a color_print(M600), return a parsed color.
bool color_by_color_print () const { return color_print_values != nullptr ; }
2019-06-24 15:27:32 +02:00
const size_t color_print_color_idx_by_layer_idx ( const size_t layer_idx ) const {
2020-06-03 10:42:47 +02:00
const CustomGCode :: Item value { layers [ layer_idx ] -> print_z + EPSILON , CustomGCode :: Custom , 0 , "" };
2019-11-03 19:33:02 +01:00
auto it = std :: lower_bound ( color_print_values -> begin (), color_print_values -> end (), value );
2019-06-24 15:27:32 +02:00
return ( it - color_print_values -> begin ()) % number_tools ();
2019-01-29 15:11:29 +01:00
}
2019-11-03 19:33:02 +01:00
2019-11-04 13:42:47 +01:00
const size_t color_print_color_idx_by_layer_idx_and_extruder ( const size_t layer_idx , const int extruder ) const
2019-11-03 19:33:02 +01:00
{
const coordf_t print_z = layers [ layer_idx ] -> print_z ;
2019-11-04 13:42:47 +01:00
auto it = std :: find_if ( color_print_values -> begin (), color_print_values -> end (),
2020-01-23 12:49:39 +01:00
[ print_z ]( const CustomGCode :: Item & code )
2019-12-17 08:37:50 +01:00
{ return fabs ( code . print_z - print_z ) < EPSILON ; });
2019-11-04 13:42:47 +01:00
if ( it != color_print_values -> end ())
{
2020-06-03 10:42:47 +02:00
CustomGCode :: Type type = it -> type ;
2019-11-04 13:42:47 +01:00
// pause print or custom Gcode
2020-06-03 10:42:47 +02:00
if ( type == CustomGCode :: PausePrint ||
( type != CustomGCode :: ColorChange && type != CustomGCode :: ToolChange ))
2019-11-04 16:28:57 +01:00
return number_tools () - 1 ; // last color item is a gray color for pause print or custom G-code
2019-11-04 13:42:47 +01:00
// change tool (extruder)
2020-06-03 10:42:47 +02:00
if ( type == CustomGCode :: ToolChange )
2019-11-05 12:55:16 +01:00
return get_color_idx_for_tool_change ( it , extruder );
2019-11-04 13:42:47 +01:00
// change color for current extruder
2020-06-03 10:42:47 +02:00
if ( type == CustomGCode :: ColorChange ) {
2019-11-05 12:55:16 +01:00
int color_idx = get_color_idx_for_color_change ( it , extruder );
if ( color_idx >= 0 )
return color_idx ;
2019-11-04 13:42:47 +01:00
}
}
2020-06-03 10:42:47 +02:00
const CustomGCode :: Item value { print_z + EPSILON , CustomGCode :: Custom , 0 , "" };
2019-11-04 13:42:47 +01:00
it = std :: lower_bound ( color_print_values -> begin (), color_print_values -> end (), value );
while ( it != color_print_values -> begin ())
{
-- it ;
// change color for current extruder
2020-06-03 10:42:47 +02:00
if ( it -> type == CustomGCode :: ColorChange ) {
2019-11-05 12:55:16 +01:00
int color_idx = get_color_idx_for_color_change ( it , extruder );
if ( color_idx >= 0 )
return color_idx ;
2019-11-04 13:42:47 +01:00
}
// change tool (extruder)
2020-06-03 10:42:47 +02:00
if ( it -> type == CustomGCode :: ToolChange )
2019-11-05 12:55:16 +01:00
return get_color_idx_for_tool_change ( it , extruder );
2019-11-04 13:42:47 +01:00
}
return std :: min < int > ( extruders_cnt - 1 , std :: max < int > ( extruder - 1 , 0 ));;
2019-11-03 19:33:02 +01:00
}
2019-11-05 12:55:16 +01:00
private :
2020-01-23 12:49:39 +01:00
int get_m600_color_idx ( std :: vector < CustomGCode :: Item >:: const_iterator it ) const
2019-11-05 12:55:16 +01:00
{
int shift = 0 ;
while ( it != color_print_values -> begin ()) {
-- it ;
2020-06-03 10:42:47 +02:00
if ( it -> type == CustomGCode :: ColorChange )
2019-11-05 12:55:16 +01:00
shift ++ ;
}
return extruders_cnt + shift ;
}
2020-01-23 12:49:39 +01:00
int get_color_idx_for_tool_change ( std :: vector < CustomGCode :: Item >:: const_iterator it , const int extruder ) const
2019-11-05 12:55:16 +01:00
{
const int current_extruder = it -> extruder == 0 ? extruder : it -> extruder ;
2021-01-29 07:39:27 +01:00
if ( number_tools () == size_t ( extruders_cnt + 1 )) // there is no one "M600"
2019-11-05 12:55:16 +01:00
return std :: min < int > ( extruders_cnt - 1 , std :: max < int > ( current_extruder - 1 , 0 ));
auto it_n = it ;
while ( it_n != color_print_values -> begin ()) {
-- it_n ;
2020-06-03 10:42:47 +02:00
if ( it_n -> type == CustomGCode :: ColorChange && it_n -> extruder == current_extruder )
2019-11-05 12:55:16 +01:00
return get_m600_color_idx ( it_n );
}
return std :: min < int > ( extruders_cnt - 1 , std :: max < int > ( current_extruder - 1 , 0 ));
}
2020-01-23 12:49:39 +01:00
int get_color_idx_for_color_change ( std :: vector < CustomGCode :: Item >:: const_iterator it , const int extruder ) const
2019-11-05 12:55:16 +01:00
{
if ( extruders_cnt == 1 )
return get_m600_color_idx ( it );
auto it_n = it ;
bool is_tool_change = false ;
while ( it_n != color_print_values -> begin ()) {
-- it_n ;
2020-06-03 10:42:47 +02:00
if ( it_n -> type == CustomGCode :: ToolChange ) {
2019-11-05 12:55:16 +01:00
is_tool_change = true ;
2019-11-06 16:06:21 +01:00
if ( it_n -> extruder == it -> extruder || ( it_n -> extruder == 0 && it -> extruder == extruder ))
2019-11-05 12:55:16 +01:00
return get_m600_color_idx ( it );
break ;
}
}
if ( ! is_tool_change && it -> extruder == extruder )
return get_m600_color_idx ( it );
return - 1 ;
}
2018-07-24 13:39:17 +02:00
} ctxt ;
2018-09-12 11:59:02 +02:00
ctxt . has_perimeters = print_object . is_step_done ( posPerimeters );
ctxt . has_infill = print_object . is_step_done ( posInfill );
ctxt . has_support = print_object . is_step_done ( posSupportMaterial );
2018-07-24 13:39:17 +02:00
ctxt . tool_colors = tool_colors . empty () ? nullptr : & tool_colors ;
2019-01-29 15:11:29 +01:00
ctxt . color_print_values = color_print_values . empty () ? nullptr : & color_print_values ;
2019-11-03 19:33:02 +01:00
ctxt . is_single_material_print = this -> fff_print () -> extruders (). size () == 1 ;
2019-11-04 13:42:47 +01:00
ctxt . extruders_cnt = wxGetApp (). extruders_edited_cnt ();
2018-07-24 13:39:17 +02:00
2020-01-23 09:53:06 +01:00
ctxt . shifted_copies = & print_object . instances ();
2019-01-09 15:07:10 +01:00
// order layers by print_z
{
size_t nlayers = 0 ;
if ( ctxt . has_perimeters || ctxt . has_infill )
nlayers = print_object . layers (). size ();
if ( ctxt . has_support )
nlayers += print_object . support_layers (). size ();
ctxt . layers . reserve ( nlayers );
}
if ( ctxt . has_perimeters || ctxt . has_infill )
for ( const Layer * layer : print_object . layers ())
2020-03-13 15:57:07 +01:00
ctxt . layers . emplace_back ( layer );
2019-01-09 15:07:10 +01:00
if ( ctxt . has_support )
for ( const Layer * layer : print_object . support_layers ())
2020-03-13 15:57:07 +01:00
ctxt . layers . emplace_back ( layer );
2019-01-09 15:07:10 +01:00
std :: sort ( ctxt . layers . begin (), ctxt . layers . end (), []( const Layer * l1 , const Layer * l2 ) { return l1 -> print_z < l2 -> print_z ; });
// Maximum size of an allocation block: 32MB / sizeof(float)
2019-08-05 14:30:32 +02:00
BOOST_LOG_TRIVIAL ( debug ) << "Loading print object toolpaths in parallel - start" << m_volumes . log_memory_info () << log_memory_info ();
2018-07-24 13:39:17 +02:00
2019-10-24 08:43:27 +02:00
const bool is_selected_separate_extruder = m_selected_extruder > 0 && ctxt . color_by_color_print ();
2018-07-24 13:39:17 +02:00
//FIXME Improve the heuristics for a grain size.
2019-06-24 15:27:32 +02:00
size_t grain_size = std :: max ( ctxt . layers . size () / 16 , size_t ( 1 ));
2018-07-24 13:39:17 +02:00
tbb :: spin_mutex new_volume_mutex ;
auto new_volume = [ this , & new_volume_mutex ]( const float * color ) -> GLVolume * {
2019-08-26 11:12:48 +02:00
// Allocate the volume before locking.
GLVolume * volume = new GLVolume ( color );
volume -> is_extrusion_path = true ;
tbb :: spin_mutex :: scoped_lock lock ;
// Lock by ROII, so if the emplace_back() fails, the lock will be released.
lock . acquire ( new_volume_mutex );
2018-07-24 13:39:17 +02:00
m_volumes . volumes . emplace_back ( volume );
2019-08-26 11:12:48 +02:00
lock . release ();
2018-07-24 13:39:17 +02:00
return volume ;
};
2019-06-24 15:27:32 +02:00
const size_t volumes_cnt_initial = m_volumes . volumes . size ();
2018-07-24 13:39:17 +02:00
tbb :: parallel_for (
tbb :: blocked_range < size_t > ( 0 , ctxt . layers . size (), grain_size ),
2019-10-24 08:43:27 +02:00
[ & ctxt , & new_volume , is_selected_separate_extruder , this ]( const tbb :: blocked_range < size_t >& range ) {
2019-06-24 15:27:32 +02:00
GLVolumePtrs vols ;
std :: vector < size_t > color_print_layer_to_glvolume ;
2019-11-04 13:42:47 +01:00
auto volume = [ & ctxt , & vols , & color_print_layer_to_glvolume , & range ]( size_t layer_idx , int extruder , int feature ) -> GLVolume & {
return * vols [ ctxt . color_by_color_print () ?
2019-11-04 16:28:57 +01:00
ctxt . color_print_color_idx_by_layer_idx_and_extruder ( layer_idx , extruder ) :
2019-06-24 15:27:32 +02:00
ctxt . color_by_tool () ?
std :: min < int > ( ctxt . number_tools () - 1 , std :: max < int > ( extruder - 1 , 0 )) :
feature
];
};
2019-11-27 15:27:44 +01:00
if ( ctxt . color_by_color_print () || ctxt . color_by_tool ()) {
2018-07-24 13:39:17 +02:00
for ( size_t i = 0 ; i < ctxt . number_tools (); ++ i )
vols . emplace_back ( new_volume ( ctxt . color_tool ( i )));
}
else
vols = { new_volume ( ctxt . color_perimeters ()), new_volume ( ctxt . color_infill ()), new_volume ( ctxt . color_support ()) };
for ( GLVolume * vol : vols )
2019-08-26 11:12:48 +02:00
// Reserving number of vertices (3x position + 3x color)
vol -> indexed_vertex_array . reserve ( VERTEX_BUFFER_RESERVE_SIZE / 6 );
2019-06-24 15:27:32 +02:00
for ( size_t idx_layer = range . begin (); idx_layer < range . end (); ++ idx_layer ) {
2018-07-24 13:39:17 +02:00
const Layer * layer = ctxt . layers [ idx_layer ];
2019-10-24 08:43:27 +02:00
if ( is_selected_separate_extruder )
{
bool at_least_one_has_correct_extruder = false ;
for ( const LayerRegion * layerm : layer -> regions ())
{
if ( layerm -> slices . surfaces . empty ())
continue ;
const PrintRegionConfig & cfg = layerm -> region () -> config ();
if ( cfg . perimeter_extruder . value == m_selected_extruder ||
cfg . infill_extruder . value == m_selected_extruder ||
cfg . solid_infill_extruder . value == m_selected_extruder ) {
at_least_one_has_correct_extruder = true ;
break ;
}
}
if ( ! at_least_one_has_correct_extruder )
continue ;
}
2019-06-24 15:27:32 +02:00
for ( GLVolume * vol : vols )
if ( vol -> print_zs . empty () || vol -> print_zs . back () != layer -> print_z ) {
2020-03-13 15:57:07 +01:00
vol -> print_zs . emplace_back ( layer -> print_z );
vol -> offsets . emplace_back ( vol -> indexed_vertex_array . quad_indices . size ());
vol -> offsets . emplace_back ( vol -> indexed_vertex_array . triangle_indices . size ());
2018-07-24 13:39:17 +02:00
}
2020-01-23 09:53:06 +01:00
for ( const PrintInstance & instance : * ctxt . shifted_copies ) {
const Point & copy = instance . shift ;
2018-09-12 11:59:02 +02:00
for ( const LayerRegion * layerm : layer -> regions ()) {
2019-10-24 08:43:27 +02:00
if ( is_selected_separate_extruder )
{
const PrintRegionConfig & cfg = layerm -> region () -> config ();
if ( cfg . perimeter_extruder . value != m_selected_extruder ||
cfg . infill_extruder . value != m_selected_extruder ||
cfg . solid_infill_extruder . value != m_selected_extruder )
continue ;
}
2018-07-24 13:39:17 +02:00
if ( ctxt . has_perimeters )
_3DScene :: extrusionentity_to_verts ( layerm -> perimeters , float ( layer -> print_z ), copy ,
2019-06-24 15:27:32 +02:00
volume ( idx_layer , layerm -> region () -> config (). perimeter_extruder . value , 0 ));
2018-07-24 13:39:17 +02:00
if ( ctxt . has_infill ) {
for ( const ExtrusionEntity * ee : layerm -> fills . entities ) {
// fill represents infill extrusions of a single island.
const auto * fill = dynamic_cast < const ExtrusionEntityCollection *> ( ee );
2019-06-24 15:27:32 +02:00
if ( ! fill -> entities . empty ())
2018-07-24 13:39:17 +02:00
_3DScene :: extrusionentity_to_verts ( * fill , float ( layer -> print_z ), copy ,
2019-06-24 15:27:32 +02:00
volume ( idx_layer ,
is_solid_infill ( fill -> entities . front () -> role ()) ?
layerm -> region () -> config (). solid_infill_extruder :
layerm -> region () -> config (). infill_extruder ,
1 ));
2018-07-24 13:39:17 +02:00
}
}
}
if ( ctxt . has_support ) {
const SupportLayer * support_layer = dynamic_cast < const SupportLayer *> ( layer );
if ( support_layer ) {
for ( const ExtrusionEntity * extrusion_entity : support_layer -> support_fills . entities )
_3DScene :: extrusionentity_to_verts ( extrusion_entity , float ( layer -> print_z ), copy ,
2019-06-24 15:27:32 +02:00
volume ( idx_layer ,
( extrusion_entity -> role () == erSupportMaterial ) ?
support_layer -> object () -> config (). support_material_extruder :
support_layer -> object () -> config (). support_material_interface_extruder ,
2 ));
2018-07-24 13:39:17 +02:00
}
}
}
2019-06-24 15:27:32 +02:00
// Ensure that no volume grows over the limits. If the volume is too large, allocate a new one.
2019-06-25 08:56:53 +02:00
for ( size_t i = 0 ; i < vols . size (); ++ i ) {
GLVolume & vol = * vols [ i ];
2019-08-26 11:12:48 +02:00
if ( vol . indexed_vertex_array . vertices_and_normals_interleaved . size () > MAX_VERTEX_BUFFER_SIZE ) {
2019-06-25 08:56:53 +02:00
vols [ i ] = new_volume ( vol . color );
2019-08-26 11:12:48 +02:00
reserve_new_volume_finalize_old_volume ( * vols [ i ], vol , false );
2019-06-25 08:56:53 +02:00
}
}
2018-07-24 13:39:17 +02:00
}
2019-08-05 14:30:32 +02:00
for ( GLVolume * vol : vols )
// Ideally one would call vol->indexed_vertex_array.finalize() here to move the buffers to the OpenGL driver,
// but this code runs in parallel and the OpenGL driver is not thread safe.
vol -> indexed_vertex_array . shrink_to_fit ();
2018-07-24 13:39:17 +02:00
});
2019-08-05 14:30:32 +02:00
BOOST_LOG_TRIVIAL ( debug ) << "Loading print object toolpaths in parallel - finalizing results" << m_volumes . log_memory_info () << log_memory_info ();
2018-07-24 13:39:17 +02:00
// Remove empty volumes from the newly added volumes.
m_volumes . volumes . erase (
std :: remove_if ( m_volumes . volumes . begin () + volumes_cnt_initial , m_volumes . volumes . end (),
[]( const GLVolume * volume ) { return volume -> empty (); }),
m_volumes . volumes . end ());
2019-08-05 14:30:32 +02:00
for ( size_t i = volumes_cnt_initial ; i < m_volumes . volumes . size (); ++ i )
m_volumes . volumes [ i ] -> indexed_vertex_array . finalize_geometry ( m_initialized );
2018-07-24 13:39:17 +02:00
2019-08-05 14:30:32 +02:00
BOOST_LOG_TRIVIAL ( debug ) << "Loading print object toolpaths in parallel - end" << m_volumes . log_memory_info () << log_memory_info ();
2018-07-24 13:39:17 +02:00
}
void GLCanvas3D :: _load_wipe_tower_toolpaths ( const std :: vector < std :: string >& str_tool_colors )
{
2018-11-22 15:29:59 +01:00
const Print * print = this -> fff_print ();
if (( print == nullptr ) || print -> wipe_tower_data (). tool_changes . empty ())
2018-07-24 13:39:17 +02:00
return ;
2018-11-22 15:29:59 +01:00
if ( ! print -> is_step_done ( psWipeTower ))
2018-07-24 13:39:17 +02:00
return ;
std :: vector < float > tool_colors = _parse_colors ( str_tool_colors );
struct Ctxt
{
const Print * print ;
const std :: vector < float > * tool_colors ;
2019-06-17 10:16:07 +02:00
Vec2f wipe_tower_pos ;
2018-08-02 13:29:39 +02:00
float wipe_tower_angle ;
2018-07-24 13:39:17 +02:00
static const float * color_support () { static float color [ 4 ] = { 0.5f , 1.0f , 0.5f , 1.f }; return color ; } // greenish
// For cloring by a tool, return a parsed color.
bool color_by_tool () const { return tool_colors != nullptr ; }
size_t number_tools () const { return this -> color_by_tool () ? tool_colors -> size () / 4 : 0 ; }
const float * color_tool ( size_t tool ) const { return tool_colors -> data () + tool * 4 ; }
int volume_idx ( int tool , int feature ) const
{
return this -> color_by_tool () ? std :: min < int > ( this -> number_tools () - 1 , std :: max < int > ( tool , 0 )) : feature ;
}
const std :: vector < WipeTower :: ToolChangeResult >& tool_change ( size_t idx ) {
2018-09-12 11:59:02 +02:00
const auto & tool_changes = print -> wipe_tower_data (). tool_changes ;
2018-07-24 13:39:17 +02:00
return priming . empty () ?
2018-09-12 11:59:02 +02:00
(( idx == tool_changes . size ()) ? final : tool_changes [ idx ]) :
(( idx == 0 ) ? priming : ( idx == tool_changes . size () + 1 ) ? final : tool_changes [ idx - 1 ]);
2018-07-24 13:39:17 +02:00
}
std :: vector < WipeTower :: ToolChangeResult > priming ;
std :: vector < WipeTower :: ToolChangeResult > final ;
} ctxt ;
2018-11-22 15:29:59 +01:00
ctxt . print = print ;
2018-07-24 13:39:17 +02:00
ctxt . tool_colors = tool_colors . empty () ? nullptr : & tool_colors ;
2018-11-22 15:29:59 +01:00
if ( print -> wipe_tower_data (). priming && print -> config (). single_extruder_multi_material_priming )
2019-09-04 09:47:00 +02:00
for ( int i = 0 ; i < ( int ) print -> wipe_tower_data (). priming . get () -> size (); ++ i )
2019-06-14 12:28:24 +02:00
ctxt . priming . emplace_back ( print -> wipe_tower_data (). priming . get () -> at ( i ));
2018-11-22 15:29:59 +01:00
if ( print -> wipe_tower_data (). final_purge )
ctxt . final . emplace_back ( * print -> wipe_tower_data (). final_purge . get ());
2018-07-24 13:39:17 +02:00
2018-09-12 11:59:02 +02:00
ctxt . wipe_tower_angle = ctxt . print -> config (). wipe_tower_rotation_angle . value / 180.f * PI ;
2019-06-17 10:16:07 +02:00
ctxt . wipe_tower_pos = Vec2f ( ctxt . print -> config (). wipe_tower_x . value , ctxt . print -> config (). wipe_tower_y . value );
2018-08-02 13:29:39 +02:00
2019-08-05 14:30:32 +02:00
BOOST_LOG_TRIVIAL ( debug ) << "Loading wipe tower toolpaths in parallel - start" << m_volumes . log_memory_info () << log_memory_info ();
2018-07-24 13:39:17 +02:00
//FIXME Improve the heuristics for a grain size.
2018-11-22 15:29:59 +01:00
size_t n_items = print -> wipe_tower_data (). tool_changes . size () + ( ctxt . priming . empty () ? 0 : 1 );
2018-07-24 13:39:17 +02:00
size_t grain_size = std :: max ( n_items / 128 , size_t ( 1 ));
tbb :: spin_mutex new_volume_mutex ;
auto new_volume = [ this , & new_volume_mutex ]( const float * color ) -> GLVolume * {
auto * volume = new GLVolume ( color );
2019-08-26 11:12:48 +02:00
volume -> is_extrusion_path = true ;
tbb :: spin_mutex :: scoped_lock lock ;
lock . acquire ( new_volume_mutex );
2018-07-24 13:39:17 +02:00
m_volumes . volumes . emplace_back ( volume );
2019-08-26 11:12:48 +02:00
lock . release ();
2018-07-24 13:39:17 +02:00
return volume ;
};
const size_t volumes_cnt_initial = m_volumes . volumes . size ();
std :: vector < GLVolumeCollection > volumes_per_thread ( n_items );
tbb :: parallel_for (
tbb :: blocked_range < size_t > ( 0 , n_items , grain_size ),
[ & ctxt , & new_volume ]( const tbb :: blocked_range < size_t >& range ) {
// Bounding box of this slab of a wipe tower.
2018-10-08 14:02:12 +02:00
GLVolumePtrs vols ;
2018-07-24 13:39:17 +02:00
if ( ctxt . color_by_tool ()) {
for ( size_t i = 0 ; i < ctxt . number_tools (); ++ i )
vols . emplace_back ( new_volume ( ctxt . color_tool ( i )));
}
else
vols = { new_volume ( ctxt . color_support ()) };
for ( GLVolume * volume : vols )
2019-08-26 11:12:48 +02:00
// Reserving number of vertices (3x position + 3x color)
volume -> indexed_vertex_array . reserve ( VERTEX_BUFFER_RESERVE_SIZE / 6 );
2018-07-24 13:39:17 +02:00
for ( size_t idx_layer = range . begin (); idx_layer < range . end (); ++ idx_layer ) {
const std :: vector < WipeTower :: ToolChangeResult > & layer = ctxt . tool_change ( idx_layer );
for ( size_t i = 0 ; i < vols . size (); ++ i ) {
GLVolume & vol = * vols [ i ];
if ( vol . print_zs . empty () || vol . print_zs . back () != layer . front (). print_z ) {
2020-03-13 15:57:07 +01:00
vol . print_zs . emplace_back ( layer . front (). print_z );
vol . offsets . emplace_back ( vol . indexed_vertex_array . quad_indices . size ());
vol . offsets . emplace_back ( vol . indexed_vertex_array . triangle_indices . size ());
2018-07-24 13:39:17 +02:00
}
}
for ( const WipeTower :: ToolChangeResult & extrusions : layer ) {
for ( size_t i = 1 ; i < extrusions . extrusions . size ();) {
const WipeTower :: Extrusion & e = extrusions . extrusions [ i ];
if ( e . width == 0. ) {
++ i ;
continue ;
}
size_t j = i + 1 ;
if ( ctxt . color_by_tool ())
for (; j < extrusions . extrusions . size () && extrusions . extrusions [ j ]. tool == e . tool && extrusions . extrusions [ j ]. width > 0.f ; ++ j );
else
for (; j < extrusions . extrusions . size () && extrusions . extrusions [ j ]. width > 0.f ; ++ j );
size_t n_lines = j - i ;
Lines lines ;
std :: vector < double > widths ;
std :: vector < double > heights ;
lines . reserve ( n_lines );
widths . reserve ( n_lines );
heights . assign ( n_lines , extrusions . layer_height );
2018-08-02 13:29:39 +02:00
WipeTower :: Extrusion e_prev = extrusions . extrusions [ i - 1 ];
if ( ! extrusions . priming ) { // wipe tower extrusions describe the wipe tower at the origin with no rotation
2019-06-17 10:16:07 +02:00
e_prev . pos = Eigen :: Rotation2Df ( ctxt . wipe_tower_angle ) * e_prev . pos ;
e_prev . pos += ctxt . wipe_tower_pos ;
2018-08-02 13:29:39 +02:00
}
2018-07-24 13:39:17 +02:00
for (; i < j ; ++ i ) {
2018-08-02 13:29:39 +02:00
WipeTower :: Extrusion e = extrusions . extrusions [ i ];
2018-07-24 13:39:17 +02:00
assert ( e . width > 0.f );
2018-08-02 13:29:39 +02:00
if ( ! extrusions . priming ) {
2019-06-17 10:16:07 +02:00
e . pos = Eigen :: Rotation2Df ( ctxt . wipe_tower_angle ) * e . pos ;
e . pos += ctxt . wipe_tower_pos ;
2018-08-02 13:29:39 +02:00
}
2019-06-17 10:16:07 +02:00
lines . emplace_back ( Point :: new_scale ( e_prev . pos . x (), e_prev . pos . y ()), Point :: new_scale ( e . pos . x (), e . pos . y ()));
2018-07-24 13:39:17 +02:00
widths . emplace_back ( e . width );
2018-08-02 13:29:39 +02:00
e_prev = e ;
2018-07-24 13:39:17 +02:00
}
_3DScene :: thick_lines_to_verts ( lines , widths , heights , lines . front (). a == lines . back (). b , extrusions . print_z ,
* vols [ ctxt . volume_idx ( e . tool , 0 )]);
}
}
}
for ( size_t i = 0 ; i < vols . size (); ++ i ) {
GLVolume & vol = * vols [ i ];
2019-08-26 11:12:48 +02:00
if ( vol . indexed_vertex_array . vertices_and_normals_interleaved . size () > MAX_VERTEX_BUFFER_SIZE ) {
2018-07-24 13:39:17 +02:00
vols [ i ] = new_volume ( vol . color );
2019-08-26 11:12:48 +02:00
reserve_new_volume_finalize_old_volume ( * vols [ i ], vol , false );
2018-07-24 13:39:17 +02:00
}
}
2019-08-05 14:30:32 +02:00
for ( GLVolume * vol : vols )
vol -> indexed_vertex_array . shrink_to_fit ();
2018-07-24 13:39:17 +02:00
});
2019-08-05 14:30:32 +02:00
BOOST_LOG_TRIVIAL ( debug ) << "Loading wipe tower toolpaths in parallel - finalizing results" << m_volumes . log_memory_info () << log_memory_info ();
2018-07-24 13:39:17 +02:00
// Remove empty volumes from the newly added volumes.
m_volumes . volumes . erase (
std :: remove_if ( m_volumes . volumes . begin () + volumes_cnt_initial , m_volumes . volumes . end (),
[]( const GLVolume * volume ) { return volume -> empty (); }),
m_volumes . volumes . end ());
2019-08-05 14:30:32 +02:00
for ( size_t i = volumes_cnt_initial ; i < m_volumes . volumes . size (); ++ i )
m_volumes . volumes [ i ] -> indexed_vertex_array . finalize_geometry ( m_initialized );
2018-07-24 13:39:17 +02:00
2019-08-05 14:30:32 +02:00
BOOST_LOG_TRIVIAL ( debug ) << "Loading wipe tower toolpaths in parallel - end" << m_volumes . log_memory_info () << log_memory_info ();
2018-07-24 13:39:17 +02:00
}
2019-08-08 18:26:41 +02:00
// While it looks like we can call
// this->reload_scene(true, true)
// the two functions are quite different:
// 1) This function only loads objects, for which the step slaposSliceSupports already finished. Therefore objects outside of the print bed never load.
// 2) This function loads object mesh with the relative scaling correction (the "relative_correction" parameter) was applied,
// therefore the mesh may be slightly larger or smaller than the mesh shown in the 3D scene.
void GLCanvas3D :: _load_sla_shells ()
{
const SLAPrint * print = this -> sla_print ();
if ( print -> objects (). empty ())
// nothing to render, return
return ;
auto add_volume = [ this ]( const SLAPrintObject & object , int volume_id , const SLAPrintObject :: Instance & instance ,
const TriangleMesh & mesh , const float color [ 4 ], bool outside_printer_detection_enabled ) {
m_volumes . volumes . emplace_back ( new GLVolume ( color ));
GLVolume & v = * m_volumes . volumes . back ();
2020-06-02 10:02:50 +02:00
#if ENABLE_SMOOTH_NORMALS
v . indexed_vertex_array . load_mesh ( mesh , true );
#else
2019-08-08 18:26:41 +02:00
v . indexed_vertex_array . load_mesh ( mesh );
2020-06-02 10:02:50 +02:00
#endif // ENABLE_SMOOTH_NORMALS
2019-08-08 18:26:41 +02:00
v . indexed_vertex_array . finalize_geometry ( this -> m_initialized );
v . shader_outside_printer_detection_enabled = outside_printer_detection_enabled ;
v . composite_id . volume_id = volume_id ;
2020-02-07 14:10:18 +01:00
v . set_instance_offset ( unscale ( instance . shift . x (), instance . shift . y (), 0 ));
2019-08-08 18:26:41 +02:00
v . set_instance_rotation ( Vec3d ( 0.0 , 0.0 , ( double ) instance . rotation ));
v . set_instance_mirror ( X , object . is_left_handed () ? - 1. : 1. );
v . set_convex_hull ( mesh . convex_hull_3d ());
};
// adds objects' volumes
for ( const SLAPrintObject * obj : print -> objects ())
if ( obj -> is_step_done ( slaposSliceSupports )) {
unsigned int initial_volumes_count = ( unsigned int ) m_volumes . volumes . size ();
for ( const SLAPrintObject :: Instance & instance : obj -> instances ()) {
2020-01-09 11:19:52 +01:00
add_volume ( * obj , 0 , instance , obj -> get_mesh_to_print (), GLVolume :: MODEL_COLOR [ 0 ], true );
2019-08-08 18:26:41 +02:00
// Set the extruder_id and volume_id to achieve the same color as in the 3D scene when
// through the update_volumes_colors_by_extruder() call.
m_volumes . volumes . back () -> extruder_id = obj -> model_object () -> volumes . front () -> extruder_id ();
if ( obj -> is_step_done ( slaposSupportTree ) && obj -> has_mesh ( slaposSupportTree ))
add_volume ( * obj , - int ( slaposSupportTree ), instance , obj -> support_mesh (), GLVolume :: SLA_SUPPORT_COLOR , true );
2019-09-24 15:15:49 +02:00
if ( obj -> is_step_done ( slaposPad ) && obj -> has_mesh ( slaposPad ))
add_volume ( * obj , - int ( slaposPad ), instance , obj -> pad_mesh (), GLVolume :: SLA_PAD_COLOR , false );
2019-08-08 18:26:41 +02:00
}
double shift_z = obj -> get_current_elevation ();
for ( unsigned int i = initial_volumes_count ; i < m_volumes . volumes . size (); ++ i ) {
GLVolume & v = * m_volumes . volumes [ i ];
// apply shift z
v . set_sla_shift_z ( shift_z );
}
}
update_volumes_colors_by_extruder ();
}
2018-07-24 13:39:17 +02:00
void GLCanvas3D :: _update_toolpath_volumes_outside_state ()
{
2020-05-07 10:49:12 +02:00
BoundingBoxf3 test_volume = ( m_config != nullptr ) ? print_volume ( * m_config ) : BoundingBoxf3 ();
2020-11-19 12:29:19 +01:00
for ( GLVolume * volume : m_volumes . volumes ) {
2020-05-07 10:49:12 +02:00
volume -> is_outside = (( test_volume . radius () > 0.0 ) && volume -> is_extrusion_path ) ? ! test_volume . contains ( volume -> bounding_box ()) : false ;
2018-07-24 13:39:17 +02:00
}
}
2019-05-07 12:29:48 +02:00
void GLCanvas3D :: _update_sla_shells_outside_state ()
{
2020-05-07 10:49:12 +02:00
BoundingBoxf3 test_volume = ( m_config != nullptr ) ? print_volume ( * m_config ) : BoundingBoxf3 ();
2020-11-19 12:29:19 +01:00
for ( GLVolume * volume : m_volumes . volumes ) {
2020-05-07 10:49:12 +02:00
volume -> is_outside = (( test_volume . radius () > 0.0 ) && volume -> shader_outside_printer_detection_enabled ) ? ! test_volume . contains ( volume -> transformed_convex_hull_bounding_box ()) : false ;
2019-05-07 12:29:48 +02:00
}
}
void GLCanvas3D :: _show_warning_texture_if_needed ( WarningTexture :: Warning warning )
2018-07-24 13:39:17 +02:00
{
2019-01-31 09:15:43 +01:00
_set_current ();
2020-05-07 10:49:12 +02:00
bool show = false ;
if ( ! m_volumes . empty ())
show = _is_any_volume_outside ();
2020-08-03 15:00:19 +02:00
else {
2020-09-08 11:40:06 +02:00
if ( wxGetApp (). is_editor ()) {
2020-06-22 12:43:52 +02:00
BoundingBoxf3 test_volume = ( m_config != nullptr ) ? print_volume ( * m_config ) : BoundingBoxf3 ();
2020-06-24 16:57:09 +02:00
const BoundingBoxf3 & paths_volume = m_gcode_viewer . get_paths_bounding_box ();
2020-06-22 12:43:52 +02:00
if ( test_volume . radius () > 0.0 && paths_volume . radius () > 0.0 )
show = ! test_volume . contains ( paths_volume );
}
2020-05-07 10:49:12 +02:00
}
_set_warning_texture ( warning , show );
2018-07-24 13:39:17 +02:00
}
2018-06-05 14:09:36 +02:00
std :: vector < float > GLCanvas3D :: _parse_colors ( const std :: vector < std :: string >& colors )
{
2018-06-08 11:37:07 +02:00
static const float INV_255 = 1.0f / 255.0f ;
2018-06-05 14:09:36 +02:00
std :: vector < float > output ( colors . size () * 4 , 1.0f );
2018-06-08 11:37:07 +02:00
for ( size_t i = 0 ; i < colors . size (); ++ i )
{
2018-06-05 14:09:36 +02:00
const std :: string & color = colors [ i ];
const char * c = color . data () + 1 ;
if (( color . size () == 7 ) && ( color . front () == '#' ))
{
for ( size_t j = 0 ; j < 3 ; ++ j )
{
int digit1 = hex_digit_to_int ( * c ++ );
int digit2 = hex_digit_to_int ( * c ++ );
if (( digit1 == - 1 ) || ( digit2 == - 1 ))
break ;
2018-06-08 11:37:07 +02:00
output [ i * 4 + j ] = float ( digit1 * 16 + digit2 ) * INV_255 ;
2018-06-05 14:09:36 +02:00
}
}
}
return output ;
}
2019-02-20 12:09:45 +01:00
void GLCanvas3D :: _set_warning_texture ( WarningTexture :: Warning warning , bool state )
2018-07-19 13:18:19 +02:00
{
2019-02-20 12:09:45 +01:00
m_warning_texture . activate ( warning , state , * this );
2018-07-19 13:18:19 +02:00
}
2018-07-27 09:38:39 +02:00
bool GLCanvas3D :: _is_any_volume_outside () const
{
2020-11-19 12:29:19 +01:00
for ( const GLVolume * volume : m_volumes . volumes ) {
2018-07-27 09:38:39 +02:00
if (( volume != nullptr ) && volume -> is_outside )
return true ;
}
return false ;
}
2019-04-25 12:31:55 +02:00
void GLCanvas3D :: _update_selection_from_hover ()
{
2019-04-26 13:37:34 +02:00
bool ctrl_pressed = wxGetKeyState ( WXK_CONTROL );
2019-04-25 12:31:55 +02:00
if ( m_hover_volume_idxs . empty ())
2019-04-26 13:37:34 +02:00
{
if ( ! ctrl_pressed && ( m_rectangle_selection . get_state () == GLSelectionRectangle :: Select ))
2019-07-23 15:14:08 +02:00
m_selection . remove_all ();
2019-04-26 13:37:34 +02:00
2019-04-25 12:31:55 +02:00
return ;
2019-04-26 13:37:34 +02:00
}
2019-04-25 12:31:55 +02:00
GLSelectionRectangle :: EState state = m_rectangle_selection . get_state ();
2019-04-26 13:37:34 +02:00
bool hover_modifiers_only = true ;
for ( int i : m_hover_volume_idxs )
{
if ( ! m_volumes . volumes [ i ] -> is_modifier )
{
hover_modifiers_only = false ;
break ;
}
}
2019-04-25 12:31:55 +02:00
2019-07-23 15:14:08 +02:00
bool selection_changed = false ;
if ( state == GLSelectionRectangle :: Select )
{
bool contains_all = true ;
for ( int i : m_hover_volume_idxs )
{
if ( ! m_selection . contains_volume (( unsigned int ) i ))
{
contains_all = false ;
break ;
}
}
// the selection is going to be modified (Add)
if ( ! contains_all )
{
2019-07-26 11:32:44 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( L ( "Selection-Add from rectangle" )));
2019-07-23 15:14:08 +02:00
selection_changed = true ;
}
}
else
{
bool contains_any = false ;
for ( int i : m_hover_volume_idxs )
{
if ( m_selection . contains_volume (( unsigned int ) i ))
{
contains_any = true ;
break ;
}
}
// the selection is going to be modified (Remove)
if ( contains_any )
{
2019-07-26 11:32:44 +02:00
wxGetApp (). plater () -> take_snapshot ( _ ( L ( "Selection-Remove from rectangle" )));
2019-07-23 15:14:08 +02:00
selection_changed = true ;
}
}
if ( ! selection_changed )
return ;
Plater :: SuppressSnapshots suppress ( wxGetApp (). plater ());
2019-04-26 13:37:34 +02:00
if (( state == GLSelectionRectangle :: Select ) && ! ctrl_pressed )
2019-04-25 12:31:55 +02:00
m_selection . clear ();
2019-04-26 13:37:34 +02:00
for ( int i : m_hover_volume_idxs )
2019-04-25 12:31:55 +02:00
{
if ( state == GLSelectionRectangle :: Select )
2019-04-26 13:37:34 +02:00
{
if ( hover_modifiers_only )
{
const GLVolume & v = * m_volumes . volumes [ i ];
m_selection . add_volume ( v . object_idx (), v . volume_idx (), v . instance_idx (), false );
}
else
m_selection . add ( i , false );
}
2019-04-25 12:31:55 +02:00
else
2019-04-26 13:37:34 +02:00
m_selection . remove ( i );
2019-04-25 12:31:55 +02:00
}
2019-05-09 10:09:33 +02:00
if ( m_selection . is_empty ())
m_gizmos . reset_all_states ();
else
2019-07-17 08:38:48 +02:00
m_gizmos . refresh_on_off_state ();
2019-05-09 10:09:33 +02:00
2019-07-17 08:38:48 +02:00
m_gizmos . update_data ();
2019-04-25 12:31:55 +02:00
post_event ( SimpleEvent ( EVT_GLCANVAS_OBJECT_SELECT ));
m_dirty = true ;
}
2019-07-11 07:46:40 +02:00
bool GLCanvas3D :: _deactivate_undo_redo_toolbar_items ()
{
2019-07-19 15:36:55 +02:00
if ( m_undoredo_toolbar . is_item_pressed ( "undo" ))
2019-07-11 07:46:40 +02:00
{
2019-07-19 15:36:55 +02:00
m_undoredo_toolbar . force_right_action ( m_undoredo_toolbar . get_item_id ( "undo" ), * this );
2019-07-11 07:46:40 +02:00
return true ;
}
2019-07-19 15:36:55 +02:00
else if ( m_undoredo_toolbar . is_item_pressed ( "redo" ))
2019-07-11 07:46:40 +02:00
{
2019-07-19 15:36:55 +02:00
m_undoredo_toolbar . force_right_action ( m_undoredo_toolbar . get_item_id ( "redo" ), * this );
2019-07-11 07:46:40 +02:00
return true ;
}
return false ;
}
2020-05-11 22:00:26 +02:00
bool GLCanvas3D :: is_search_pressed () const
{
return m_main_toolbar . is_item_pressed ( "search" );
}
2020-11-20 17:50:10 +01:00
bool GLCanvas3D :: _deactivate_arrange_menu ()
{
if ( m_main_toolbar . is_item_pressed ( "arrange" )) {
m_main_toolbar . force_right_action ( m_main_toolbar . get_item_id ( "arrange" ), * this );
return true ;
}
return false ;
}
2020-04-01 11:51:44 +02:00
bool GLCanvas3D :: _deactivate_search_toolbar_item ()
{
2020-05-11 22:00:26 +02:00
if ( is_search_pressed ())
2020-04-01 11:51:44 +02:00
{
m_main_toolbar . force_left_action ( m_main_toolbar . get_item_id ( "search" ), * this );
return true ;
}
return false ;
}
2020-04-08 09:47:33 +02:00
bool GLCanvas3D :: _activate_search_toolbar_item ()
{
if ( ! m_main_toolbar . is_item_pressed ( "search" ))
{
m_main_toolbar . force_left_action ( m_main_toolbar . get_item_id ( "search" ), * this );
return true ;
}
return false ;
}
2020-04-23 12:41:38 +02:00
bool GLCanvas3D :: _deactivate_collapse_toolbar_items ()
{
2020-06-05 12:33:09 +02:00
GLToolbar & collapse_toolbar = wxGetApp (). plater () -> get_collapse_toolbar ();
if ( collapse_toolbar . is_item_pressed ( "print" ))
2020-04-23 12:41:38 +02:00
{
2020-06-05 12:33:09 +02:00
collapse_toolbar . force_left_action ( collapse_toolbar . get_item_id ( "print" ), * this );
2020-04-23 12:41:38 +02:00
return true ;
}
return false ;
}
2018-11-22 15:29:59 +01:00
const Print * GLCanvas3D :: fff_print () const
{
return ( m_process == nullptr ) ? nullptr : m_process -> fff_print ();
}
const SLAPrint * GLCanvas3D :: sla_print () const
{
return ( m_process == nullptr ) ? nullptr : m_process -> sla_print ();
}
2019-07-16 18:33:42 +02:00
void GLCanvas3D :: WipeTowerInfo :: apply_wipe_tower () const
2019-06-27 21:13:44 +02:00
{
DynamicPrintConfig cfg ;
cfg . opt < ConfigOptionFloat > ( "wipe_tower_x" , true ) -> value = m_pos ( X );
cfg . opt < ConfigOptionFloat > ( "wipe_tower_y" , true ) -> value = m_pos ( Y );
cfg . opt < ConfigOptionFloat > ( "wipe_tower_rotation_angle" , true ) -> value = ( 180. / M_PI ) * m_rotation ;
wxGetApp (). get_tab ( Preset :: TYPE_PRINT ) -> load_config ( cfg );
}
2020-12-15 16:03:20 +01:00
void GLCanvas3D :: RenderTimer :: Notify ()
{
wxPostEvent (( wxEvtHandler * ) GetOwner (), RenderTimerEvent ( EVT_GLCANVAS_RENDER_TIMER , * this ));
}
2018-05-09 10:47:04 +02:00
} // namespace GUI
} // namespace Slic3r