Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/Slic3r/GUI.pm4
-rw-r--r--lib/Slic3r/GUI/3DScene.pm4284
-rw-r--r--lib/Slic3r/GUI/MainFrame.pm12
-rw-r--r--lib/Slic3r/GUI/Plater.pm174
-rw-r--r--lib/Slic3r/GUI/Plater/2D.pm2
-rw-r--r--lib/Slic3r/GUI/Plater/3D.pm509
-rw-r--r--lib/Slic3r/GUI/Plater/3DPreview.pm35
-rw-r--r--lib/Slic3r/GUI/Plater/ObjectCutDialog.pm25
-rw-r--r--lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm66
-rw-r--r--lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm1
-rw-r--r--resources/icons/bed/mk2_bottom.pngbin0 -> 27212 bytes
-rw-r--r--resources/icons/bed/mk2_top.pngbin0 -> 102340 bytes
-rw-r--r--resources/icons/bed/mk3_bottom.pngbin0 -> 174128 bytes
-rw-r--r--resources/icons/bed/mk3_top.pngbin0 -> 85263 bytes
-rw-r--r--resources/icons/overlay/rotate_hover.pngbin0 -> 3808 bytes
-rw-r--r--resources/icons/overlay/rotate_off.pngbin0 -> 4514 bytes
-rw-r--r--resources/icons/overlay/rotate_on.pngbin0 -> 3441 bytes
-rw-r--r--resources/icons/overlay/scale_hover.pngbin0 -> 6474 bytes
-rw-r--r--resources/icons/overlay/scale_off.pngbin0 -> 7232 bytes
-rw-r--r--resources/icons/overlay/scale_on.pngbin0 -> 5293 bytes
-rw-r--r--resources/shaders/gouraud.fs18
-rw-r--r--resources/shaders/gouraud.vs70
-rw-r--r--resources/shaders/variable_layer_height.fs40
-rw-r--r--resources/shaders/variable_layer_height.vs46
-rw-r--r--xs/CMakeLists.txt14
-rw-r--r--xs/lib/Slic3r/XS.pm3
-rw-r--r--xs/src/libslic3r/BoundingBox.cpp8
-rw-r--r--xs/src/libslic3r/BoundingBox.hpp1
-rw-r--r--xs/src/libslic3r/Model.cpp5
-rw-r--r--xs/src/libslic3r/Model.hpp11
-rw-r--r--xs/src/libslic3r/Point.hpp5
-rw-r--r--xs/src/libslic3r/Print.hpp2
-rw-r--r--xs/src/libslic3r/PrintObject.cpp9
-rw-r--r--xs/src/libslic3r/Utils.hpp11
-rw-r--r--xs/src/libslic3r/utils.cpp52
-rw-r--r--xs/src/slic3r/GUI/3DScene.cpp1238
-rw-r--r--xs/src/slic3r/GUI/3DScene.hpp184
-rw-r--r--xs/src/slic3r/GUI/GLCanvas3D.cpp4308
-rw-r--r--xs/src/slic3r/GUI/GLCanvas3D.hpp639
-rw-r--r--xs/src/slic3r/GUI/GLCanvas3DManager.cpp707
-rw-r--r--xs/src/slic3r/GUI/GLCanvas3DManager.hpp167
-rw-r--r--xs/src/slic3r/GUI/GLGizmo.cpp454
-rw-r--r--xs/src/slic3r/GUI/GLGizmo.hpp145
-rw-r--r--xs/src/slic3r/GUI/GLShader.cpp40
-rw-r--r--xs/src/slic3r/GUI/GLShader.hpp4
-rw-r--r--xs/src/slic3r/GUI/GLTexture.cpp185
-rw-r--r--xs/src/slic3r/GUI/GLTexture.hpp41
-rw-r--r--xs/xsp/GUI_3DScene.xsp518
-rw-r--r--xs/xsp/Print.xsp12
49 files changed, 10498 insertions, 3551 deletions
diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm
index f2bf3baf8..52c482813 100644
--- a/lib/Slic3r/GUI.pm
+++ b/lib/Slic3r/GUI.pm
@@ -223,8 +223,8 @@ sub system_info {
my $opengl_info_txt = '';
if (defined($self->{mainframe}) && defined($self->{mainframe}->{plater}) &&
defined($self->{mainframe}->{plater}->{canvas3D})) {
- $opengl_info = $self->{mainframe}->{plater}->{canvas3D}->opengl_info(format => 'html');
- $opengl_info_txt = $self->{mainframe}->{plater}->{canvas3D}->opengl_info;
+ $opengl_info = Slic3r::GUI::_3DScene::get_gl_info(1, 1);
+ $opengl_info_txt = Slic3r::GUI::_3DScene::get_gl_info(0, 1);
}
my $about = Slic3r::GUI::SystemInfo->new(
parent => undef,
diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm
index 33c0e8d37..157e7229c 100644
--- a/lib/Slic3r/GUI/3DScene.pm
+++ b/lib/Slic3r/GUI/3DScene.pm
@@ -16,97 +16,102 @@ use strict;
use warnings;
use Wx qw(wxTheApp :timer :bitmap :icon :dialog);
-use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS EVT_CHAR EVT_TIMER);
+#==============================================================================================================================
+#use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS EVT_CHAR EVT_TIMER);
# must load OpenGL *before* Wx::GLCanvas
use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants);
use base qw(Wx::GLCanvas Class::Accessor);
-use Math::Trig qw(asin tan);
-use List::Util qw(reduce min max first);
-use Slic3r::Geometry qw(X Y normalize scale unscale scaled_epsilon);
-use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl JT_ROUND);
+#==============================================================================================================================
+#use Math::Trig qw(asin tan);
+#use List::Util qw(reduce min max first);
+#use Slic3r::Geometry qw(X Y normalize scale unscale scaled_epsilon);
+#use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl JT_ROUND);
+#==============================================================================================================================
use Wx::GLCanvas qw(:all);
-use Slic3r::Geometry qw(PI);
+#==============================================================================================================================
+#use Slic3r::Geometry qw(PI);
+#==============================================================================================================================
-# _dirty: boolean flag indicating, that the screen has to be redrawn on EVT_IDLE.
# volumes: reference to vector of Slic3r::GUI::3DScene::Volume.
-# _camera_type: 'perspective' or 'ortho'
-__PACKAGE__->mk_accessors( qw(_quat _dirty init
- enable_picking
- enable_moving
- use_plain_shader
- on_viewport_changed
- on_hover
- on_select
- on_double_click
- on_right_click
- on_move
- on_model_update
- volumes
- _sphi _stheta
- cutting_plane_z
- cut_lines_vertices
- bed_shape
- bed_triangles
- bed_grid_lines
- bed_polygon
- background
- origin
- _mouse_pos
- _hover_volume_idx
-
- _drag_volume_idx
- _drag_start_pos
- _drag_volume_center_offset
- _drag_start_xy
- _dragged
-
- _layer_height_edited
-
- _camera_type
- _camera_target
- _camera_distance
- _zoom
-
- _legend_enabled
- _warning_enabled
- _apply_zoom_to_volumes_filter
- _mouse_dragging
-
- ) );
-
-use constant TRACKBALLSIZE => 0.8;
-use constant TURNTABLE_MODE => 1;
-use constant GROUND_Z => -0.02;
-# For mesh selection: Not selected - bright yellow.
-use constant DEFAULT_COLOR => [1,1,0];
-# For mesh selection: Selected - bright green.
-use constant SELECTED_COLOR => [0,1,0,1];
-# For mesh selection: Mouse hovers over the object, but object not selected yet - dark green.
-use constant HOVER_COLOR => [0.4,0.9,0,1];
-
-# phi / theta angles to orient the camera.
-use constant VIEW_DEFAULT => [45.0,45.0];
-use constant VIEW_LEFT => [90.0,90.0];
-use constant VIEW_RIGHT => [-90.0,90.0];
-use constant VIEW_TOP => [0.0,0.0];
-use constant VIEW_BOTTOM => [0.0,180.0];
-use constant VIEW_FRONT => [0.0,90.0];
-use constant VIEW_REAR => [180.0,90.0];
-
-use constant MANIPULATION_IDLE => 0;
-use constant MANIPULATION_DRAGGING => 1;
-use constant MANIPULATION_LAYER_HEIGHT => 2;
-
-use constant GIMBALL_LOCK_THETA_MAX => 180;
-
-use constant VARIABLE_LAYER_THICKNESS_BAR_WIDTH => 70;
-use constant VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT => 22;
-
-# make OpenGL::Array thread-safe
-{
- no warnings 'redefine';
- *OpenGL::Array::CLONE_SKIP = sub { 1 };
-}
+#==============================================================================================================================
+#__PACKAGE__->mk_accessors( qw(_quat _dirty init
+# enable_picking
+# enable_moving
+# use_plain_shader
+# on_viewport_changed
+# on_hover
+# on_select
+# on_double_click
+# on_right_click
+# on_move
+# on_model_update
+# volumes
+# _sphi _stheta
+# cutting_plane_z
+# cut_lines_vertices
+# bed_shape
+# bed_triangles
+# bed_grid_lines
+# bed_polygon
+# background
+# origin
+# _mouse_pos
+# _hover_volume_idx
+#
+# _drag_volume_idx
+# _drag_start_pos
+# _drag_volume_center_offset
+# _drag_start_xy
+# _dragged
+#
+# _layer_height_edited
+#
+# _camera_type
+# _camera_target
+# _camera_distance
+# _zoom
+#
+# _legend_enabled
+# _warning_enabled
+# _apply_zoom_to_volumes_filter
+# _mouse_dragging
+#
+# ) );
+#
+#use constant TRACKBALLSIZE => 0.8;
+#use constant TURNTABLE_MODE => 1;
+#use constant GROUND_Z => -0.02;
+## For mesh selection: Not selected - bright yellow.
+#use constant DEFAULT_COLOR => [1,1,0];
+## For mesh selection: Selected - bright green.
+#use constant SELECTED_COLOR => [0,1,0,1];
+## For mesh selection: Mouse hovers over the object, but object not selected yet - dark green.
+#use constant HOVER_COLOR => [0.4,0.9,0,1];
+#
+## phi / theta angles to orient the camera.
+#use constant VIEW_DEFAULT => [45.0,45.0];
+#use constant VIEW_LEFT => [90.0,90.0];
+#use constant VIEW_RIGHT => [-90.0,90.0];
+#use constant VIEW_TOP => [0.0,0.0];
+#use constant VIEW_BOTTOM => [0.0,180.0];
+#use constant VIEW_FRONT => [0.0,90.0];
+#use constant VIEW_REAR => [180.0,90.0];
+#
+#use constant MANIPULATION_IDLE => 0;
+#use constant MANIPULATION_DRAGGING => 1;
+#use constant MANIPULATION_LAYER_HEIGHT => 2;
+#
+#use constant GIMBALL_LOCK_THETA_MAX => 180;
+#
+#use constant VARIABLE_LAYER_THICKNESS_BAR_WIDTH => 70;
+#use constant VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT => 22;
+#
+## make OpenGL::Array thread-safe
+#{
+# no warnings 'redefine';
+# *OpenGL::Array::CLONE_SKIP = sub { 1 };
+#}
+#==============================================================================================================================
sub new {
my ($class, $parent) = @_;
@@ -130,2070 +135,2097 @@ sub new {
# we request a depth buffer explicitely because it looks like it's not created by
# default on Linux, causing transparency issues
my $self = $class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "", $attrib);
- if (Wx::wxVERSION >= 3.000003) {
- # Wx 3.0.3 contains an ugly hack to support some advanced OpenGL attributes through the attribute list.
- # The attribute list is transferred between the wxGLCanvas and wxGLContext constructors using a single static array s_wglContextAttribs.
- # Immediatelly force creation of the OpenGL context to consume the static variable s_wglContextAttribs.
- $self->GetContext();
- }
-
- $self->{can_multisample} = $can_multisample;
- $self->background(1);
- $self->_quat((0, 0, 0, 1));
- $self->_stheta(45);
- $self->_sphi(45);
- $self->_zoom(1);
- $self->_legend_enabled(0);
- $self->_warning_enabled(0);
- $self->use_plain_shader(0);
- $self->_apply_zoom_to_volumes_filter(0);
- $self->_mouse_dragging(0);
-
- # Collection of GLVolume objects
- $self->volumes(Slic3r::GUI::_3DScene::GLVolume::Collection->new);
-
- # 3D point in model space
- $self->_camera_type('ortho');
-# $self->_camera_type('perspective');
- $self->_camera_target(Slic3r::Pointf3->new(0,0,0));
- $self->_camera_distance(0.);
-
- $self->layer_editing_enabled(0);
- $self->{layer_height_edit_band_width} = 2.;
- $self->{layer_height_edit_strength} = 0.005;
- $self->{layer_height_edit_last_object_id} = -1;
- $self->{layer_height_edit_last_z} = 0.;
- $self->{layer_height_edit_last_action} = 0;
-
- $self->reset_objects;
-
- EVT_PAINT($self, sub {
- my $dc = Wx::PaintDC->new($self);
- $self->Render($dc);
- });
- EVT_SIZE($self, sub { $self->_dirty(1) });
- EVT_IDLE($self, sub {
- return unless $self->_dirty;
- return if !$self->IsShownOnScreen;
- $self->Resize( $self->GetSizeWH );
- $self->Refresh;
- });
- EVT_MOUSEWHEEL($self, \&mouse_wheel_event);
- EVT_MOUSE_EVENTS($self, \&mouse_event);
-# EVT_KEY_DOWN($self, sub {
- EVT_CHAR($self, sub {
- my ($s, $event) = @_;
- if ($event->HasModifiers) {
- $event->Skip;
- } else {
- my $key = $event->GetKeyCode;
- if ($key == ord('0')) {
- $self->select_view('iso');
- } elsif ($key == ord('1')) {
- $self->select_view('top');
- } elsif ($key == ord('2')) {
- $self->select_view('bottom');
- } elsif ($key == ord('3')) {
- $self->select_view('front');
- } elsif ($key == ord('4')) {
- $self->select_view('rear');
- } elsif ($key == ord('5')) {
- $self->select_view('left');
- } elsif ($key == ord('6')) {
- $self->select_view('right');
- } elsif ($key == ord('z')) {
- $self->zoom_to_volumes;
- } elsif ($key == ord('b')) {
- $self->zoom_to_bed;
- } else {
- $event->Skip;
- }
- }
- });
-
- $self->{layer_height_edit_timer_id} = &Wx::NewId();
- $self->{layer_height_edit_timer} = Wx::Timer->new($self, $self->{layer_height_edit_timer_id});
- EVT_TIMER($self, $self->{layer_height_edit_timer_id}, sub {
- my ($self, $event) = @_;
- return if $self->_layer_height_edited != 1;
- return if $self->{layer_height_edit_last_object_id} == -1;
- $self->_variable_layer_thickness_action(undef);
- });
+#==============================================================================================================================
+# if (Wx::wxVERSION >= 3.000003) {
+# # Wx 3.0.3 contains an ugly hack to support some advanced OpenGL attributes through the attribute list.
+# # The attribute list is transferred between the wxGLCanvas and wxGLContext constructors using a single static array s_wglContextAttribs.
+# # Immediatelly force creation of the OpenGL context to consume the static variable s_wglContextAttribs.
+# $self->GetContext();
+# }
+#==============================================================================================================================
+
+#==============================================================================================================================
+ Slic3r::GUI::_3DScene::add_canvas($self);
+ Slic3r::GUI::_3DScene::allow_multisample($self, $can_multisample);
+# my $context = $self->GetContext;
+# $self->SetCurrent($context);
+# Slic3r::GUI::_3DScene::add_canvas($self, $context);
+#
+# $self->{can_multisample} = $can_multisample;
+# $self->background(1);
+# $self->_quat((0, 0, 0, 1));
+# $self->_stheta(45);
+# $self->_sphi(45);
+# $self->_zoom(1);
+# $self->_legend_enabled(0);
+# $self->_warning_enabled(0);
+# $self->use_plain_shader(0);
+# $self->_apply_zoom_to_volumes_filter(0);
+# $self->_mouse_dragging(0);
+#
+# # Collection of GLVolume objects
+# $self->volumes(Slic3r::GUI::_3DScene::GLVolume::Collection->new);
+#
+# # 3D point in model space
+# $self->_camera_type('ortho');
+## $self->_camera_type('perspective');
+# $self->_camera_target(Slic3r::Pointf3->new(0,0,0));
+# $self->_camera_distance(0.);
+# $self->layer_editing_enabled(0);
+# $self->{layer_height_edit_band_width} = 2.;
+# $self->{layer_height_edit_strength} = 0.005;
+# $self->{layer_height_edit_last_object_id} = -1;
+# $self->{layer_height_edit_last_z} = 0.;
+# $self->{layer_height_edit_last_action} = 0;
+#
+# $self->reset_objects;
+#
+# EVT_PAINT($self, sub {
+# my $dc = Wx::PaintDC->new($self);
+# $self->Render($dc);
+# });
+# EVT_SIZE($self, sub { $self->_dirty(1) });
+# EVT_IDLE($self, sub {
+# return unless $self->_dirty;
+# return if !$self->IsShownOnScreen;
+# $self->Resize( $self->GetSizeWH );
+# $self->Refresh;
+# });
+# EVT_MOUSEWHEEL($self, \&mouse_wheel_event);
+# EVT_MOUSE_EVENTS($self, \&mouse_event);
+## EVT_KEY_DOWN($self, sub {
+# EVT_CHAR($self, sub {
+# my ($s, $event) = @_;
+# if ($event->HasModifiers) {
+# $event->Skip;
+# } else {
+# my $key = $event->GetKeyCode;
+# if ($key == ord('0')) {
+# $self->select_view('iso');
+# } elsif ($key == ord('1')) {
+# $self->select_view('top');
+# } elsif ($key == ord('2')) {
+# $self->select_view('bottom');
+# } elsif ($key == ord('3')) {
+# $self->select_view('front');
+# } elsif ($key == ord('4')) {
+# $self->select_view('rear');
+# } elsif ($key == ord('5')) {
+# $self->select_view('left');
+# } elsif ($key == ord('6')) {
+# $self->select_view('right');
+# } elsif ($key == ord('z')) {
+# $self->zoom_to_volumes;
+# } elsif ($key == ord('b')) {
+# $self->zoom_to_bed;
+# } else {
+# $event->Skip;
+# }
+# }
+# });
+#
+# $self->{layer_height_edit_timer_id} = &Wx::NewId();
+# $self->{layer_height_edit_timer} = Wx::Timer->new($self, $self->{layer_height_edit_timer_id});
+# EVT_TIMER($self, $self->{layer_height_edit_timer_id}, sub {
+# my ($self, $event) = @_;
+# return if $self->_layer_height_edited != 1;
+# $self->_variable_layer_thickness_action(undef);
+# });
+#==============================================================================================================================
return $self;
}
-sub set_legend_enabled {
- my ($self, $value) = @_;
- $self->_legend_enabled($value);
-}
-
-sub set_warning_enabled {
- my ($self, $value) = @_;
- $self->_warning_enabled($value);
-}
+#==============================================================================================================================
+#sub set_legend_enabled {
+# my ($self, $value) = @_;
+# $self->_legend_enabled($value);
+#}
+#
+#sub set_warning_enabled {
+# my ($self, $value) = @_;
+# $self->_warning_enabled($value);
+#}
+#==============================================================================================================================
sub Destroy {
my ($self) = @_;
- $self->{layer_height_edit_timer}->Stop;
- $self->DestroyGL;
+#==============================================================================================================================
+ Slic3r::GUI::_3DScene::remove_canvas($self);
+# $self->{layer_height_edit_timer}->Stop;
+# $self->DestroyGL;
+#==============================================================================================================================
return $self->SUPER::Destroy;
}
-sub layer_editing_enabled {
- my ($self, $value) = @_;
- if (@_ == 2) {
- $self->{layer_editing_enabled} = $value;
- if ($value) {
- if (! $self->{layer_editing_initialized}) {
- # Enabling the layer editing for the first time. This triggers compilation of the necessary OpenGL shaders.
- # If compilation fails, a message box is shown with the error codes.
- $self->SetCurrent($self->GetContext);
- my $shader = new Slic3r::GUI::_3DScene::GLShader;
- my $error_message;
- if (! $shader->load($self->_fragment_shader_variable_layer_height, $self->_vertex_shader_variable_layer_height)) {
- # Compilation or linking of the shaders failed.
- $error_message = "Cannot compile an OpenGL Shader, therefore the Variable Layer Editing will be disabled.\n\n"
- . $shader->last_error;
- $shader = undef;
- } else {
- $self->{layer_height_edit_shader} = $shader;
- ($self->{layer_preview_z_texture_id}) = glGenTextures_p(1);
- glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id});
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
- glBindTexture(GL_TEXTURE_2D, 0);
- }
- if (defined($error_message)) {
- # Don't enable the layer editing tool.
- $self->{layer_editing_enabled} = 0;
- # 2 means failed
- $self->{layer_editing_initialized} = 2;
- # Show the error message.
- Wx::MessageBox($error_message, "Slic3r Error", wxOK | wxICON_EXCLAMATION, $self);
- } else {
- $self->{layer_editing_initialized} = 1;
- }
- } elsif ($self->{layer_editing_initialized} == 2) {
- # Initilization failed before. Don't try to initialize and disable layer editing.
- $self->{layer_editing_enabled} = 0;
- }
- }
- }
- return $self->{layer_editing_enabled};
-}
-
-sub layer_editing_allowed {
- my ($self) = @_;
- # Allow layer editing if either the shaders were not initialized yet and we don't know
- # whether it will be possible to initialize them,
- # or if the initialization was done already and it failed.
- return ! (defined($self->{layer_editing_initialized}) && $self->{layer_editing_initialized} == 2);
-}
-
-sub _first_selected_object_id_for_variable_layer_height_editing {
- my ($self) = @_;
- for my $i (0..$#{$self->volumes}) {
- if ($self->volumes->[$i]->selected) {
- my $object_id = int($self->volumes->[$i]->select_group_id / 1000000);
- # Objects with object_id >= 1000 have a specific meaning, for example the wipe tower proxy.
- return ($object_id >= $self->{print}->object_count) ? -1 : $object_id
- if $object_id < 10000;
- }
- }
- return -1;
-}
-
-# Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen.
-sub _variable_layer_thickness_bar_rect_screen {
- my ($self) = @_;
- my ($cw, $ch) = $self->GetSizeWH;
- return ($cw - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, 0, $cw, $ch - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT);
-}
-
-sub _variable_layer_thickness_bar_rect_viewport {
- my ($self) = @_;
- my ($cw, $ch) = $self->GetSizeWH;
- return ((0.5*$cw-VARIABLE_LAYER_THICKNESS_BAR_WIDTH)/$self->_zoom, (-0.5*$ch+VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT)/$self->_zoom, $cw/(2*$self->_zoom), $ch/(2*$self->_zoom));
-}
-
-# Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen.
-sub _variable_layer_thickness_reset_rect_screen {
- my ($self) = @_;
- my ($cw, $ch) = $self->GetSizeWH;
- return ($cw - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, $ch - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT, $cw, $ch);
-}
-
-sub _variable_layer_thickness_reset_rect_viewport {
- my ($self) = @_;
- my ($cw, $ch) = $self->GetSizeWH;
- return ((0.5*$cw-VARIABLE_LAYER_THICKNESS_BAR_WIDTH)/$self->_zoom, -$ch/(2*$self->_zoom), $cw/(2*$self->_zoom), (-0.5*$ch+VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT)/$self->_zoom);
-}
-
-sub _variable_layer_thickness_bar_rect_mouse_inside {
- my ($self, $mouse_evt) = @_;
- my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen;
- return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom;
-}
-
-sub _variable_layer_thickness_reset_rect_mouse_inside {
- my ($self, $mouse_evt) = @_;
- my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_reset_rect_screen;
- return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom;
-}
-
-sub _variable_layer_thickness_bar_mouse_cursor_z_relative {
- my ($self) = @_;
- my $mouse_pos = $self->ScreenToClientPoint(Wx::GetMousePosition());
- my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen;
- return ($mouse_pos->x >= $bar_left && $mouse_pos->x <= $bar_right && $mouse_pos->y >= $bar_top && $mouse_pos->y <= $bar_bottom) ?
- # Inside the bar.
- ($bar_bottom - $mouse_pos->y - 1.) / ($bar_bottom - $bar_top - 1) :
- # Outside the bar.
- -1000.;
-}
-
-sub _variable_layer_thickness_action {
- my ($self, $mouse_event, $do_modification) = @_;
- # A volume is selected. Test, whether hovering over a layer thickness bar.
- return if $self->{layer_height_edit_last_object_id} == -1;
- if (defined($mouse_event)) {
- my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen;
- $self->{layer_height_edit_last_z} = unscale($self->{print}->get_object($self->{layer_height_edit_last_object_id})->size->z)
- * ($bar_bottom - $mouse_event->GetY - 1.) / ($bar_bottom - $bar_top);
- $self->{layer_height_edit_last_action} = $mouse_event->ShiftDown ? ($mouse_event->RightIsDown ? 3 : 2) : ($mouse_event->RightIsDown ? 0 : 1);
- }
- # Mark the volume as modified, so Print will pick its layer height profile? Where to mark it?
- # Start a timer to refresh the print? schedule_background_process() ?
- # The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself.
- $self->{print}->get_object($self->{layer_height_edit_last_object_id})->adjust_layer_height_profile(
- $self->{layer_height_edit_last_z},
- $self->{layer_height_edit_strength},
- $self->{layer_height_edit_band_width},
- $self->{layer_height_edit_last_action});
- $self->volumes->[$self->{layer_height_edit_last_object_id}]->generate_layer_height_texture(
- $self->{print}->get_object($self->{layer_height_edit_last_object_id}), 1);
- $self->Refresh;
- # Automatic action on mouse down with the same coordinate.
- $self->{layer_height_edit_timer}->Start(100, wxTIMER_CONTINUOUS);
-}
-
-sub mouse_event {
- my ($self, $e) = @_;
-
- my $pos = Slic3r::Pointf->new($e->GetPositionXY);
- my $object_idx_selected = $self->{layer_height_edit_last_object_id} = ($self->layer_editing_enabled && $self->{print}) ? $self->_first_selected_object_id_for_variable_layer_height_editing : -1;
-
- $self->_mouse_dragging($e->Dragging);
-
- if ($e->Entering && (&Wx::wxMSW || $^O eq 'linux')) {
- # wxMSW needs focus in order to catch mouse wheel events
- $self->SetFocus;
- $self->_drag_start_xy(undef);
- } elsif ($e->LeftDClick) {
- if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) {
- } elsif ($self->on_double_click) {
- $self->on_double_click->();
- }
- } elsif ($e->LeftDown || $e->RightDown) {
- # If user pressed left or right button we first check whether this happened
- # on a volume or not.
- my $volume_idx = $self->_hover_volume_idx // -1;
- $self->_layer_height_edited(0);
- if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) {
- # A volume is selected and the mouse is hovering over a layer thickness bar.
- # Start editing the layer height.
- $self->_layer_height_edited(1);
- $self->_variable_layer_thickness_action($e);
- } elsif ($object_idx_selected != -1 && $self->_variable_layer_thickness_reset_rect_mouse_inside($e)) {
- $self->{print}->get_object($object_idx_selected)->reset_layer_height_profile;
- # Index 2 means no editing, just wait for mouse up event.
- $self->_layer_height_edited(2);
- $self->Refresh;
- $self->Update;
- } else {
- # The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate $pos->x,y,
- # an converts the screen space coordinate to unscaled object space.
- my $pos3d = ($volume_idx == -1) ? undef : $self->mouse_to_3d(@$pos);
-
- # Select volume in this 3D canvas.
- # Don't deselect a volume if layer editing is enabled. We want the object to stay selected
- # during the scene manipulation.
- if ($self->enable_picking && ($volume_idx != -1 || ! $self->layer_editing_enabled)) {
- $self->deselect_volumes;
- $self->select_volume($volume_idx);
-
- if ($volume_idx != -1) {
- my $group_id = $self->volumes->[$volume_idx]->select_group_id;
- my @volumes;
- if ($group_id != -1) {
- $self->select_volume($_)
- for grep $self->volumes->[$_]->select_group_id == $group_id,
- 0..$#{$self->volumes};
- }
- }
-
- $self->Refresh;
- $self->Update;
- }
-
- # propagate event through callback
- $self->on_select->($volume_idx)
- if $self->on_select;
-
- if ($volume_idx != -1) {
- if ($e->LeftDown && $self->enable_moving) {
- # Only accept the initial position, if it is inside the volume bounding box.
- my $volume_bbox = $self->volumes->[$volume_idx]->transformed_bounding_box;
- $volume_bbox->offset(1.);
- if ($volume_bbox->contains_point($pos3d)) {
- # The dragging operation is initiated.
- $self->_drag_volume_idx($volume_idx);
- $self->_drag_start_pos($pos3d);
- # Remember the shift to to the object center. The object center will later be used
- # to limit the object placement close to the bed.
- $self->_drag_volume_center_offset($pos3d->vector_to($volume_bbox->center));
- }
- } elsif ($e->RightDown) {
- # if right clicking on volume, propagate event through callback
- $self->on_right_click->($e->GetPosition)
- if $self->on_right_click;
- }
- }
- }
- } elsif ($e->Dragging && $e->LeftIsDown && ! $self->_layer_height_edited && defined($self->_drag_volume_idx)) {
- # Get new position at the same Z of the initial click point.
- my $cur_pos = Slic3r::Linef3->new(
- $self->mouse_to_3d($e->GetX, $e->GetY, 0),
- $self->mouse_to_3d($e->GetX, $e->GetY, 1))
- ->intersect_plane($self->_drag_start_pos->z);
- # Clip the new position, so the object center remains close to the bed.
- {
- $cur_pos->translate(@{$self->_drag_volume_center_offset});
- my $cur_pos2 = Slic3r::Point->new(scale($cur_pos->x), scale($cur_pos->y));
- if (! $self->bed_polygon->contains_point($cur_pos2)) {
- my $ip = $self->bed_polygon->point_projection($cur_pos2);
- $cur_pos->set_x(unscale($ip->x));
- $cur_pos->set_y(unscale($ip->y));
- }
- $cur_pos->translate(@{$self->_drag_volume_center_offset->negative});
- }
- # Calculate the translation vector.
- my $vector = $self->_drag_start_pos->vector_to($cur_pos);
- # Get the volume being dragged.
- my $volume = $self->volumes->[$self->_drag_volume_idx];
- # Get all volumes belonging to the same group, if any.
- my @volumes = ($volume->drag_group_id == -1) ?
- ($volume) :
- grep $_->drag_group_id == $volume->drag_group_id, @{$self->volumes};
- # Apply new temporary volume origin and ignore Z.
- $_->translate($vector->x, $vector->y, 0) for @volumes;
- $self->_drag_start_pos($cur_pos);
- $self->_dragged(1);
- $self->Refresh;
- $self->Update;
- } elsif ($e->Dragging) {
- if ($self->_layer_height_edited && $object_idx_selected != -1) {
- $self->_variable_layer_thickness_action($e) if ($self->_layer_height_edited == 1);
- } elsif ($e->LeftIsDown) {
- # if dragging over blank area with left button, rotate
- if (defined $self->_drag_start_pos) {
- my $orig = $self->_drag_start_pos;
- if (TURNTABLE_MODE) {
- # Turntable mode is enabled by default.
- $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE);
- $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #-
- $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_LOCK_THETA_MAX;
- $self->_stheta(0) if $self->_stheta < 0;
- } else {
- my $size = $self->GetClientSize;
- my @quat = trackball(
- $orig->x / ($size->width / 2) - 1,
- 1 - $orig->y / ($size->height / 2), #/
- $pos->x / ($size->width / 2) - 1,
- 1 - $pos->y / ($size->height / 2), #/
- );
- $self->_quat(mulquats($self->_quat, \@quat));
- }
- $self->on_viewport_changed->() if $self->on_viewport_changed;
- $self->Refresh;
- $self->Update;
- }
- $self->_drag_start_pos($pos);
- } elsif ($e->MiddleIsDown || $e->RightIsDown) {
- # If dragging over blank area with right button, pan.
- if (defined $self->_drag_start_xy) {
- # get point in model space at Z = 0
- my $cur_pos = $self->mouse_to_3d($e->GetX, $e->GetY, 0);
- my $orig = $self->mouse_to_3d($self->_drag_start_xy->x, $self->_drag_start_xy->y, 0);
- $self->_camera_target->translate(@{$orig->vector_to($cur_pos)->negative});
- $self->on_viewport_changed->() if $self->on_viewport_changed;
- $self->Refresh;
- $self->Update;
- }
- $self->_drag_start_xy($pos);
- }
- } elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
- if ($self->_layer_height_edited) {
- $self->_layer_height_edited(undef);
- $self->{layer_height_edit_timer}->Stop;
- $self->on_model_update->()
- if ($object_idx_selected != -1 && $self->on_model_update);
- } elsif ($self->on_move && defined($self->_drag_volume_idx) && $self->_dragged) {
- # get all volumes belonging to the same group, if any
- my @volume_idxs;
- my $group_id = $self->volumes->[$self->_drag_volume_idx]->drag_group_id;
- if ($group_id == -1) {
- @volume_idxs = ($self->_drag_volume_idx);
- } else {
- @volume_idxs = grep $self->volumes->[$_]->drag_group_id == $group_id,
- 0..$#{$self->volumes};
- }
- $self->on_move->(@volume_idxs);
- }
- $self->_drag_volume_idx(undef);
- $self->_drag_start_pos(undef);
- $self->_drag_start_xy(undef);
- $self->_dragged(undef);
- } elsif ($e->Moving) {
- $self->_mouse_pos($pos);
- # Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor
- # hovers over.
- if ($self->enable_picking) {
- $self->Update;
- $self->Refresh;
- }
- } else {
- $e->Skip();
- }
-}
-
-sub mouse_wheel_event {
- my ($self, $e) = @_;
-
- if ($e->MiddleIsDown) {
- # Ignore the wheel events if the middle button is pressed.
- return;
- }
-
- if ($self->layer_editing_enabled && $self->{print}) {
- my $object_idx_selected = $self->_first_selected_object_id_for_variable_layer_height_editing;
- if ($object_idx_selected != -1) {
- # A volume is selected. Test, whether hovering over a layer thickness bar.
- if ($self->_variable_layer_thickness_bar_rect_mouse_inside($e)) {
- # Adjust the width of the selection.
- $self->{layer_height_edit_band_width} = max(min($self->{layer_height_edit_band_width} * (1 + 0.1 * $e->GetWheelRotation() / $e->GetWheelDelta()), 10.), 1.5);
- $self->Refresh;
- return;
- }
- }
- }
-
- # Calculate the zoom delta and apply it to the current zoom factor
- my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta();
- $zoom = max(min($zoom, 4), -4);
- $zoom /= 10;
- $zoom = $self->_zoom / (1-$zoom);
- # Don't allow to zoom too far outside the scene.
- my $zoom_min = $self->get_zoom_to_bounding_box_factor($self->max_bounding_box);
- $zoom_min *= 0.4 if defined $zoom_min;
- $zoom = $zoom_min if defined $zoom_min && $zoom < $zoom_min;
- $self->_zoom($zoom);
-
-# # In order to zoom around the mouse point we need to translate
-# # the camera target
-# my $size = Slic3r::Pointf->new($self->GetSizeWH);
-# my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #-
-# $self->_camera_target->translate(
-# # ($pos - $size/2) represents the vector from the viewport center
-# # to the mouse point. By multiplying it by $zoom we get the new,
-# # transformed, length of such vector.
-# # Since we want that point to stay fixed, we move our camera target
-# # in the opposite direction by the delta of the length of such vector
-# # ($zoom - 1). We then scale everything by 1/$self->_zoom since
-# # $self->_camera_target is expressed in terms of model units.
-# -($pos->x - $size->x/2) * ($zoom) / $self->_zoom,
-# -($pos->y - $size->y/2) * ($zoom) / $self->_zoom,
-# 0,
-# ) if 0;
-
- $self->on_viewport_changed->() if $self->on_viewport_changed;
- $self->Resize($self->GetSizeWH) if $self->IsShownOnScreen;
- $self->Refresh;
-}
-
-# Reset selection.
-sub reset_objects {
- my ($self) = @_;
- if ($self->GetContext) {
- $self->SetCurrent($self->GetContext);
- $self->volumes->release_geometry;
- }
- $self->volumes->erase;
- $self->_dirty(1);
-}
-
-# Setup camera to view all objects.
-sub set_viewport_from_scene {
- my ($self, $scene) = @_;
-
- $self->_sphi($scene->_sphi);
- $self->_stheta($scene->_stheta);
- $self->_camera_target($scene->_camera_target);
- $self->_zoom($scene->_zoom);
- $self->_quat($scene->_quat);
- $self->_dirty(1);
-}
-
-# Set the camera to a default orientation,
-# zoom to volumes.
-sub select_view {
- my ($self, $direction) = @_;
- my $dirvec;
- if (ref($direction)) {
- $dirvec = $direction;
- } else {
- if ($direction eq 'iso') {
- $dirvec = VIEW_DEFAULT;
- } elsif ($direction eq 'left') {
- $dirvec = VIEW_LEFT;
- } elsif ($direction eq 'right') {
- $dirvec = VIEW_RIGHT;
- } elsif ($direction eq 'top') {
- $dirvec = VIEW_TOP;
- } elsif ($direction eq 'bottom') {
- $dirvec = VIEW_BOTTOM;
- } elsif ($direction eq 'front') {
- $dirvec = VIEW_FRONT;
- } elsif ($direction eq 'rear') {
- $dirvec = VIEW_REAR;
- }
- }
- my $bb = $self->volumes_bounding_box;
- if (! $bb->empty) {
- $self->_sphi($dirvec->[0]);
- $self->_stheta($dirvec->[1]);
- # Avoid gimball lock.
- $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_LOCK_THETA_MAX;
- $self->_stheta(0) if $self->_stheta < 0;
- $self->on_viewport_changed->() if $self->on_viewport_changed;
- $self->Refresh;
- }
-}
-
-sub get_zoom_to_bounding_box_factor {
- my ($self, $bb) = @_;
- my $max_bb_size = max(@{ $bb->size });
- return undef if ($max_bb_size == 0);
-
- # project the bbox vertices on a plane perpendicular to the camera forward axis
- # then calculates the vertices coordinate on this plane along the camera xy axes
-
- # we need the view matrix, we let opengl calculate it (same as done in render sub)
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
-
- if (!TURNTABLE_MODE) {
- # Shift the perspective camera.
- my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance);
- glTranslatef(@$camera_pos);
- }
-
- if (TURNTABLE_MODE) {
- # Turntable mode is enabled by default.
- glRotatef(-$self->_stheta, 1, 0, 0); # pitch
- glRotatef($self->_sphi, 0, 0, 1); # yaw
- } else {
- # Shift the perspective camera.
- my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance);
- glTranslatef(@$camera_pos);
- my @rotmat = quat_to_rotmatrix($self->quat);
- glMultMatrixd_p(@rotmat[0..15]);
- }
- glTranslatef(@{ $self->_camera_target->negative });
-
- # get the view matrix back from opengl
- my @matrix = glGetFloatv_p(GL_MODELVIEW_MATRIX);
-
- # camera axes
- my $right = Slic3r::Pointf3->new($matrix[0], $matrix[4], $matrix[8]);
- my $up = Slic3r::Pointf3->new($matrix[1], $matrix[5], $matrix[9]);
- my $forward = Slic3r::Pointf3->new($matrix[2], $matrix[6], $matrix[10]);
-
- my $bb_min = $bb->min_point();
- my $bb_max = $bb->max_point();
- my $bb_center = $bb->center();
-
- # bbox vertices in world space
- my @vertices = ();
- push(@vertices, $bb_min);
- push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_min->y(), $bb_min->z()));
- push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_max->y(), $bb_min->z()));
- push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_max->y(), $bb_min->z()));
- push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_min->y(), $bb_max->z()));
- push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_min->y(), $bb_max->z()));
- push(@vertices, $bb_max);
- push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_max->y(), $bb_max->z()));
-
- my $max_x = 0.0;
- my $max_y = 0.0;
-
- # margin factor to give some empty space around the bbox
- my $margin_factor = 1.25;
-
- foreach my $v (@vertices) {
- # project vertex on the plane perpendicular to camera forward axis
- my $pos = Slic3r::Pointf3->new($v->x() - $bb_center->x(), $v->y() - $bb_center->y(), $v->z() - $bb_center->z());
- my $proj_on_normal = $pos->x() * $forward->x() + $pos->y() * $forward->y() + $pos->z() * $forward->z();
- my $proj_on_plane = Slic3r::Pointf3->new($pos->x() - $proj_on_normal * $forward->x(), $pos->y() - $proj_on_normal * $forward->y(), $pos->z() - $proj_on_normal * $forward->z());
-
- # calculates vertex coordinate along camera xy axes
- my $x_on_plane = $proj_on_plane->x() * $right->x() + $proj_on_plane->y() * $right->y() + $proj_on_plane->z() * $right->z();
- my $y_on_plane = $proj_on_plane->x() * $up->x() + $proj_on_plane->y() * $up->y() + $proj_on_plane->z() * $up->z();
-
- $max_x = max($max_x, $margin_factor * 2 * abs($x_on_plane));
- $max_y = max($max_y, $margin_factor * 2 * abs($y_on_plane));
- }
-
- return undef if (($max_x == 0) || ($max_y == 0));
-
- my ($cw, $ch) = $self->GetSizeWH;
- my $min_ratio = min($cw / $max_x, $ch / $max_y);
-
- return $min_ratio;
-}
-
-sub zoom_to_bounding_box {
- my ($self, $bb) = @_;
- # Calculate the zoom factor needed to adjust viewport to bounding box.
- my $zoom = $self->get_zoom_to_bounding_box_factor($bb);
- if (defined $zoom) {
- $self->_zoom($zoom);
- # center view around bounding box center
- $self->_camera_target($bb->center);
- $self->on_viewport_changed->() if $self->on_viewport_changed;
- $self->Resize($self->GetSizeWH) if $self->IsShownOnScreen;
- $self->Refresh;
- }
-}
-
-sub zoom_to_bed {
- my ($self) = @_;
-
- if ($self->bed_shape) {
- $self->zoom_to_bounding_box($self->bed_bounding_box);
- }
-}
-
-sub zoom_to_volume {
- my ($self, $volume_idx) = @_;
-
- my $volume = $self->volumes->[$volume_idx];
- my $bb = $volume->transformed_bounding_box;
- $self->zoom_to_bounding_box($bb);
-}
-
-sub zoom_to_volumes {
- my ($self) = @_;
- $self->_apply_zoom_to_volumes_filter(1);
- $self->zoom_to_bounding_box($self->volumes_bounding_box);
- $self->_apply_zoom_to_volumes_filter(0);
-}
-
-sub volumes_bounding_box {
- my ($self) = @_;
-
- my $bb = Slic3r::Geometry::BoundingBoxf3->new;
- foreach my $v (@{$self->volumes}) {
- $bb->merge($v->transformed_bounding_box) if (! $self->_apply_zoom_to_volumes_filter || $v->zoom_to_volumes);
- }
- return $bb;
-}
-
-sub bed_bounding_box {
- my ($self) = @_;
-
- my $bb = Slic3r::Geometry::BoundingBoxf3->new;
- if ($self->bed_shape) {
- $bb->merge_point(Slic3r::Pointf3->new(@$_, 0)) for @{$self->bed_shape};
- }
- return $bb;
-}
-
-sub max_bounding_box {
- my ($self) = @_;
-
- my $bb = $self->bed_bounding_box;
- $bb->merge($self->volumes_bounding_box);
- return $bb;
-}
-
-# Used by ObjectCutDialog and ObjectPartsPanel to generate a rectangular ground plane
-# to support the scene objects.
-sub set_auto_bed_shape {
- my ($self, $bed_shape) = @_;
-
- # draw a default square bed around object center
- my $max_size = max(@{ $self->volumes_bounding_box->size });
- my $center = $self->volumes_bounding_box->center;
- $self->set_bed_shape([
- [ $center->x - $max_size, $center->y - $max_size ], #--
- [ $center->x + $max_size, $center->y - $max_size ], #--
- [ $center->x + $max_size, $center->y + $max_size ], #++
- [ $center->x - $max_size, $center->y + $max_size ], #++
- ]);
- # Set the origin for painting of the coordinate system axes.
- $self->origin(Slic3r::Pointf->new(@$center[X,Y]));
-}
-
-# Set the bed shape to a single closed 2D polygon (array of two element arrays),
-# triangulate the bed and store the triangles into $self->bed_triangles,
-# fills the $self->bed_grid_lines and sets $self->origin.
-# Sets $self->bed_polygon to limit the object placement.
-sub set_bed_shape {
- my ($self, $bed_shape) = @_;
-
- $self->bed_shape($bed_shape);
-
- # triangulate bed
- my $expolygon = Slic3r::ExPolygon->new([ map [map scale($_), @$_], @$bed_shape ]);
- my $bed_bb = $expolygon->bounding_box;
-
- {
- my @points = ();
- foreach my $triangle (@{ $expolygon->triangulate }) {
- push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$triangle;
- }
- $self->bed_triangles(OpenGL::Array->new_list(GL_FLOAT, @points));
- }
-
- {
- my @polylines = ();
- for (my $x = $bed_bb->x_min; $x <= $bed_bb->x_max; $x += scale 10) {
- push @polylines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]);
- }
- for (my $y = $bed_bb->y_min; $y <= $bed_bb->y_max; $y += scale 10) {
- push @polylines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]);
- }
- # clip with a slightly grown expolygon because our lines lay on the contours and
- # may get erroneously clipped
- my @lines = map Slic3r::Line->new(@$_[0,-1]),
- @{intersection_pl(\@polylines, [ @{$expolygon->offset(+scaled_epsilon)} ])};
-
- # append bed contours
- push @lines, map @{$_->lines}, @$expolygon;
-
- my @points = ();
- foreach my $line (@lines) {
- push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$line; #))
- }
- $self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points));
- }
-
- # Set the origin for painting of the coordinate system axes.
- $self->origin(Slic3r::Pointf->new(0,0));
-
- $self->bed_polygon(offset_ex([$expolygon->contour], $bed_bb->radius * 1.7, JT_ROUND, scale(0.5))->[0]->contour->clone);
-}
-
-sub deselect_volumes {
- my ($self) = @_;
- $_->set_selected(0) for @{$self->volumes};
-}
-
-sub select_volume {
- my ($self, $volume_idx) = @_;
-
- return if ($volume_idx >= scalar(@{$self->volumes}));
-
- $self->volumes->[$volume_idx]->set_selected(1)
- if $volume_idx != -1;
-}
-
-sub SetCuttingPlane {
- my ($self, $z, $expolygons) = @_;
-
- $self->cutting_plane_z($z);
-
- # grow slices in order to display them better
- $expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1);
-
- my @verts = ();
- foreach my $line (map @{$_->lines}, map @$_, @$expolygons) {
- push @verts, (
- unscale($line->a->x), unscale($line->a->y), $z, #))
- unscale($line->b->x), unscale($line->b->y), $z, #))
- );
- }
- $self->cut_lines_vertices(OpenGL::Array->new_list(GL_FLOAT, @verts));
-}
-
-# Given an axis and angle, compute quaternion.
-sub axis_to_quat {
- my ($ax, $phi) = @_;
-
- my $lena = sqrt(reduce { $a + $b } (map { $_ * $_ } @$ax));
- my @q = map { $_ * (1 / $lena) } @$ax;
- @q = map { $_ * sin($phi / 2.0) } @q;
- $q[$#q + 1] = cos($phi / 2.0);
- return @q;
-}
-
-# Project a point on the virtual trackball.
-# If it is inside the sphere, map it to the sphere, if it outside map it
-# to a hyperbola.
-sub project_to_sphere {
- my ($r, $x, $y) = @_;
-
- my $d = sqrt($x * $x + $y * $y);
- if ($d < $r * 0.70710678118654752440) { # Inside sphere
- return sqrt($r * $r - $d * $d);
- } else { # On hyperbola
- my $t = $r / 1.41421356237309504880;
- return $t * $t / $d;
- }
-}
-
-sub cross {
- my ($v1, $v2) = @_;
-
- return (@$v1[1] * @$v2[2] - @$v1[2] * @$v2[1],
- @$v1[2] * @$v2[0] - @$v1[0] * @$v2[2],
- @$v1[0] * @$v2[1] - @$v1[1] * @$v2[0]);
-}
-
-# Simulate a track-ball. Project the points onto the virtual trackball,
-# then figure out the axis of rotation, which is the cross product of
-# P1 P2 and O P1 (O is the center of the ball, 0,0,0) Note: This is a
-# deformed trackball-- is a trackball in the center, but is deformed
-# into a hyperbolic sheet of rotation away from the center.
-# It is assumed that the arguments to this routine are in the range
-# (-1.0 ... 1.0).
-sub trackball {
- my ($p1x, $p1y, $p2x, $p2y) = @_;
-
- if ($p1x == $p2x && $p1y == $p2y) {
- # zero rotation
- return (0.0, 0.0, 0.0, 1.0);
- }
-
- # First, figure out z-coordinates for projection of P1 and P2 to
- # deformed sphere
- my @p1 = ($p1x, $p1y, project_to_sphere(TRACKBALLSIZE, $p1x, $p1y));
- my @p2 = ($p2x, $p2y, project_to_sphere(TRACKBALLSIZE, $p2x, $p2y));
-
- # axis of rotation (cross product of P1 and P2)
- my @a = cross(\@p2, \@p1);
-
- # Figure out how much to rotate around that axis.
- my @d = map { $_ * $_ } (map { $p1[$_] - $p2[$_] } 0 .. $#p1);
- my $t = sqrt(reduce { $a + $b } @d) / (2.0 * TRACKBALLSIZE);
-
- # Avoid problems with out-of-control values...
- $t = 1.0 if ($t > 1.0);
- $t = -1.0 if ($t < -1.0);
- my $phi = 2.0 * asin($t);
-
- return axis_to_quat(\@a, $phi);
-}
-
-# Build a rotation matrix, given a quaternion rotation.
-sub quat_to_rotmatrix {
- my ($q) = @_;
-
- my @m = ();
-
- $m[0] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[2] * @$q[2]);
- $m[1] = 2.0 * (@$q[0] * @$q[1] - @$q[2] * @$q[3]);
- $m[2] = 2.0 * (@$q[2] * @$q[0] + @$q[1] * @$q[3]);
- $m[3] = 0.0;
-
- $m[4] = 2.0 * (@$q[0] * @$q[1] + @$q[2] * @$q[3]);
- $m[5] = 1.0 - 2.0 * (@$q[2] * @$q[2] + @$q[0] * @$q[0]);
- $m[6] = 2.0 * (@$q[1] * @$q[2] - @$q[0] * @$q[3]);
- $m[7] = 0.0;
-
- $m[8] = 2.0 * (@$q[2] * @$q[0] - @$q[1] * @$q[3]);
- $m[9] = 2.0 * (@$q[1] * @$q[2] + @$q[0] * @$q[3]);
- $m[10] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[0] * @$q[0]);
- $m[11] = 0.0;
-
- $m[12] = 0.0;
- $m[13] = 0.0;
- $m[14] = 0.0;
- $m[15] = 1.0;
-
- return @m;
-}
-
-sub mulquats {
- my ($q1, $rq) = @_;
-
- return (@$q1[3] * @$rq[0] + @$q1[0] * @$rq[3] + @$q1[1] * @$rq[2] - @$q1[2] * @$rq[1],
- @$q1[3] * @$rq[1] + @$q1[1] * @$rq[3] + @$q1[2] * @$rq[0] - @$q1[0] * @$rq[2],
- @$q1[3] * @$rq[2] + @$q1[2] * @$rq[3] + @$q1[0] * @$rq[1] - @$q1[1] * @$rq[0],
- @$q1[3] * @$rq[3] - @$q1[0] * @$rq[0] - @$q1[1] * @$rq[1] - @$q1[2] * @$rq[2])
-}
-
-# Convert the screen space coordinate to an object space coordinate.
-# If the Z screen space coordinate is not provided, a depth buffer value is substituted.
-sub mouse_to_3d {
- my ($self, $x, $y, $z) = @_;
-
- return unless $self->GetContext;
- $self->SetCurrent($self->GetContext);
-
- my @viewport = glGetIntegerv_p(GL_VIEWPORT); # 4 items
- my @mview = glGetDoublev_p(GL_MODELVIEW_MATRIX); # 16 items
- my @proj = glGetDoublev_p(GL_PROJECTION_MATRIX); # 16 items
-
- $y = $viewport[3] - $y;
- $z //= glReadPixels_p($x, $y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT);
- my @projected = gluUnProject_p($x, $y, $z, @mview, @proj, @viewport);
- return Slic3r::Pointf3->new(@projected);
-}
-
-sub GetContext {
- my ($self) = @_;
- return $self->{context} ||= Wx::GLContext->new($self);
-}
-
-sub SetCurrent {
- my ($self, $context) = @_;
- return $self->SUPER::SetCurrent($context);
-}
-
-sub UseVBOs {
- my ($self) = @_;
-
- if (! defined ($self->{use_VBOs})) {
- my $use_legacy = wxTheApp->{app_config}->get('use_legacy_opengl');
- if ($use_legacy eq '1') {
- # Disable OpenGL 2.0 rendering.
- $self->{use_VBOs} = 0;
- # Don't enable the layer editing tool.
- $self->{layer_editing_enabled} = 0;
- # 2 means failed
- $self->{layer_editing_initialized} = 2;
- return 0;
- }
- # This is a special path for wxWidgets on GTK, where an OpenGL context is initialized
- # first when an OpenGL widget is shown for the first time. How ugly.
- return 0 if (! $self->init && $^O eq 'linux');
- # Don't use VBOs if anything fails.
- $self->{use_VBOs} = 0;
- if ($self->GetContext) {
- $self->SetCurrent($self->GetContext);
- Slic3r::GUI::_3DScene::_glew_init;
- my @gl_version = split(/\./, glGetString(GL_VERSION));
- $self->{use_VBOs} = int($gl_version[0]) >= 2;
- # print "UseVBOs $self OpenGL major: $gl_version[0], minor: $gl_version[1]. Use VBOs: ", $self->{use_VBOs}, "\n";
- }
- }
- return $self->{use_VBOs};
-}
-
-sub Resize {
- my ($self, $x, $y) = @_;
-
- return unless $self->GetContext;
- $self->_dirty(0);
-
- $self->SetCurrent($self->GetContext);
- glViewport(0, 0, $x, $y);
-
- $x /= $self->_zoom;
- $y /= $self->_zoom;
-
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- if ($self->_camera_type eq 'ortho') {
- #FIXME setting the size of the box 10x larger than necessary
- # is only a workaround for an incorrectly set camera.
- # This workaround harms Z-buffer accuracy!
-# my $depth = 1.05 * $self->max_bounding_box->radius();
- my $depth = 5.0 * max(@{ $self->max_bounding_box->size });
- glOrtho(
- -$x/2, $x/2, -$y/2, $y/2,
- -$depth, $depth,
- );
- } else {
- die "Invalid camera type: ", $self->_camera_type, "\n" if ($self->_camera_type ne 'perspective');
- my $bbox_r = $self->max_bounding_box->radius();
- my $fov = PI * 45. / 180.;
- my $fov_tan = tan(0.5 * $fov);
- my $cam_distance = 0.5 * $bbox_r / $fov_tan;
- $self->_camera_distance($cam_distance);
- my $nr = $cam_distance - $bbox_r * 1.1;
- my $fr = $cam_distance + $bbox_r * 1.1;
- $nr = 1 if ($nr < 1);
- $fr = $nr + 1 if ($fr < $nr + 1);
- my $h2 = $fov_tan * $nr;
- my $w2 = $h2 * $x / $y;
- glFrustum(-$w2, $w2, -$h2, $h2, $nr, $fr);
- }
-
- glMatrixMode(GL_MODELVIEW);
-}
-
-sub InitGL {
- my $self = shift;
-
- return if $self->init;
- return unless $self->GetContext;
- $self->init(1);
-
- # This is a special path for wxWidgets on GTK, where an OpenGL context is initialized
- # first when an OpenGL widget is shown for the first time. How ugly.
- # In that case the volumes are wainting to be moved to Vertex Buffer Objects
- # after the OpenGL context is being initialized.
- $self->volumes->finalize_geometry(1)
- if ($^O eq 'linux' && $self->UseVBOs);
-
- $self->zoom_to_bed;
-
- glClearColor(0, 0, 0, 1);
- glColor3f(1, 0, 0);
- glEnable(GL_DEPTH_TEST);
- glClearDepth(1.0);
- glDepthFunc(GL_LEQUAL);
- glEnable(GL_CULL_FACE);
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
- # Set antialiasing/multisampling
- glDisable(GL_LINE_SMOOTH);
- glDisable(GL_POLYGON_SMOOTH);
-
- # See "GL_MULTISAMPLE and GL_ARRAY_BUFFER_ARB messages on failed launch"
- # https://github.com/alexrj/Slic3r/issues/4085
- eval {
- # Disable the multi sampling by default, so the picking by color will work correctly.
- glDisable(GL_MULTISAMPLE);
- };
- # Disable multi sampling if the eval failed.
- $self->{can_multisample} = 0 if $@;
-
- # ambient lighting
- glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.3, 0.3, 0.3, 1);
-
- glEnable(GL_LIGHTING);
- glEnable(GL_LIGHT0);
- glEnable(GL_LIGHT1);
-
- # light from camera
- glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
- glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.3, 0.3, 0.3, 1);
- glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 0.2, 0.2, 0.2, 1);
-
- # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
- glShadeModel(GL_SMOOTH);
-
-# glMaterialfv_p(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 0.5, 0.3, 0.3, 1);
-# glMaterialfv_p(GL_FRONT_AND_BACK, GL_SPECULAR, 1, 1, 1, 1);
-# glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50);
-# glMaterialfv_p(GL_FRONT_AND_BACK, GL_EMISSION, 0.1, 0, 0, 0.9);
-
- # A handy trick -- have surface material mirror the color.
- glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
- glEnable(GL_COLOR_MATERIAL);
- glEnable(GL_MULTISAMPLE) if ($self->{can_multisample});
-
- if ($self->UseVBOs) {
- my $shader = new Slic3r::GUI::_3DScene::GLShader;
- if (! $shader->load($self->_fragment_shader_Gouraud, $self->_vertex_shader_Gouraud)) {
-# if (! $shader->load($self->_fragment_shader_Phong, $self->_vertex_shader_Phong)) {
- print "Compilaton of path shader failed: \n" . $shader->last_error . "\n";
- $shader = undef;
- } else {
- $self->{plain_shader} = $shader;
- }
- }
-}
-
-sub DestroyGL {
- my $self = shift;
- if ($self->GetContext) {
- $self->SetCurrent($self->GetContext);
- if ($self->{plain_shader}) {
- $self->{plain_shader}->release;
- delete $self->{plain_shader};
- }
- if ($self->{layer_height_edit_shader}) {
- $self->{layer_height_edit_shader}->release;
- delete $self->{layer_height_edit_shader};
- }
- $self->volumes->release_geometry;
- }
-}
-
-sub Render {
- my ($self, $dc) = @_;
-
- # prevent calling SetCurrent() when window is not shown yet
- return unless $self->IsShownOnScreen;
- return unless my $context = $self->GetContext;
- $self->SetCurrent($context);
- $self->InitGL;
-
- glClearColor(1, 1, 1, 1);
- glClearDepth(1);
- glDepthFunc(GL_LESS);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
-
- if (!TURNTABLE_MODE) {
- # Shift the perspective camera.
- my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance);
- glTranslatef(@$camera_pos);
- }
-
- if (TURNTABLE_MODE) {
- # Turntable mode is enabled by default.
- glRotatef(-$self->_stheta, 1, 0, 0); # pitch
- glRotatef($self->_sphi, 0, 0, 1); # yaw
- } else {
- my @rotmat = quat_to_rotmatrix($self->quat);
- glMultMatrixd_p(@rotmat[0..15]);
- }
- glTranslatef(@{ $self->_camera_target->negative });
-
- # light from above
- glLightfv_p(GL_LIGHT0, GL_POSITION, -0.5, -0.5, 1, 0);
- glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.2, 0.2, 0.2, 1);
- glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.5, 0.5, 0.5, 1);
-
- # Head light
- glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
-
- if ($self->enable_picking && !$self->_mouse_dragging) {
- if (my $pos = $self->_mouse_pos) {
- # 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.
- glPushAttrib(GL_ENABLE_BIT);
- glDisable(GL_MULTISAMPLE) if ($self->{can_multisample});
- glDisable(GL_LIGHTING);
- glDisable(GL_BLEND);
- $self->draw_volumes(1);
- glPopAttrib();
- glFlush();
- my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ];
- my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256;
- $self->_hover_volume_idx(undef);
- $_->set_hover(0) for @{$self->volumes};
- if ($volume_idx <= $#{$self->volumes}) {
- $self->_hover_volume_idx($volume_idx);
-
- $self->volumes->[$volume_idx]->set_hover(1);
- my $group_id = $self->volumes->[$volume_idx]->select_group_id;
- if ($group_id != -1) {
- $_->set_hover(1) for grep { $_->select_group_id == $group_id } @{$self->volumes};
- }
-
- $self->on_hover->($volume_idx) if $self->on_hover;
- }
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- }
- }
-
- # draw fixed background
- if ($self->background) {
- glDisable(GL_LIGHTING);
- glPushMatrix();
- glLoadIdentity();
-
- glMatrixMode(GL_PROJECTION);
- glPushMatrix();
- glLoadIdentity();
-
- # Draws a bluish bottom to top gradient over the complete screen.
- glDisable(GL_DEPTH_TEST);
- glBegin(GL_QUADS);
- glColor3f(0.0,0.0,0.0);
- glVertex3f(-1.0,-1.0, 1.0);
- glVertex3f( 1.0,-1.0, 1.0);
- glColor3f(10/255,98/255,144/255);
- glVertex3f( 1.0, 1.0, 1.0);
- glVertex3f(-1.0, 1.0, 1.0);
- glEnd();
- glPopMatrix();
- glEnable(GL_DEPTH_TEST);
-
- glMatrixMode(GL_MODELVIEW);
- glPopMatrix();
- glEnable(GL_LIGHTING);
- }
-
- # draw ground and axes
- glDisable(GL_LIGHTING);
-
- # draw ground
- my $ground_z = GROUND_Z;
- if ($self->bed_triangles) {
- glDisable(GL_DEPTH_TEST);
-
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
- glEnableClientState(GL_VERTEX_ARRAY);
- glColor4f(0.8, 0.6, 0.5, 0.4);
- glNormal3d(0,0,1);
- glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_triangles->ptr());
- glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
- glDisableClientState(GL_VERTEX_ARRAY);
-
- # we need depth test for grid, otherwise it would disappear when looking
- # the object from below
- glEnable(GL_DEPTH_TEST);
-
- # draw grid
- glLineWidth(3);
- glColor4f(0.2, 0.2, 0.2, 0.4);
- glEnableClientState(GL_VERTEX_ARRAY);
- glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_grid_lines->ptr());
- glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
- glDisableClientState(GL_VERTEX_ARRAY);
-
- glDisable(GL_BLEND);
- }
-
- my $volumes_bb = $self->volumes_bounding_box;
-
- {
- # draw axes
- # disable depth testing so that axes are not covered by ground
- glDisable(GL_DEPTH_TEST);
- my $origin = $self->origin;
- my $axis_len = $self->use_plain_shader ? 0.3 * max(@{ $self->bed_bounding_box->size }) : 2 * max(@{ $volumes_bb->size });
- glLineWidth(2);
- glBegin(GL_LINES);
- # draw line for x axis
- glColor3f(1, 0, 0);
- glVertex3f(@$origin, $ground_z);
- glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,,
- # draw line for y axis
- glColor3f(0, 1, 0);
- glVertex3f(@$origin, $ground_z);
- glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++
- glEnd();
- # draw line for Z axis
- # (re-enable depth test so that axis is correctly shown when objects are behind it)
- glEnable(GL_DEPTH_TEST);
- glBegin(GL_LINES);
- glColor3f(0, 0, 1);
- glVertex3f(@$origin, $ground_z);
- glVertex3f(@$origin, $ground_z+$axis_len);
- glEnd();
- }
-
- glEnable(GL_LIGHTING);
-
- # draw objects
- if (! $self->use_plain_shader) {
- $self->draw_volumes;
- } elsif ($self->UseVBOs) {
- if ($self->enable_picking) {
- $self->mark_volumes_for_layer_height;
- $self->volumes->set_print_box($self->bed_bounding_box->x_min, $self->bed_bounding_box->y_min, 0.0, $self->bed_bounding_box->x_max, $self->bed_bounding_box->y_max, $self->{config}->get('max_print_height'));
- $self->volumes->check_outside_state($self->{config});
- # do not cull backfaces to show broken geometry, if any
- glDisable(GL_CULL_FACE);
- }
- $self->{plain_shader}->enable if $self->{plain_shader};
- $self->volumes->render_VBOs;
- $self->{plain_shader}->disable;
- glEnable(GL_CULL_FACE) if ($self->enable_picking);
- } else {
- # do not cull backfaces to show broken geometry, if any
- glDisable(GL_CULL_FACE) if ($self->enable_picking);
- $self->volumes->render_legacy;
- glEnable(GL_CULL_FACE) if ($self->enable_picking);
- }
-
- if (defined $self->cutting_plane_z) {
- # draw cutting plane
- my $plane_z = $self->cutting_plane_z;
- my $bb = $volumes_bb;
- glDisable(GL_CULL_FACE);
- glDisable(GL_LIGHTING);
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glBegin(GL_QUADS);
- glColor4f(0.8, 0.8, 0.8, 0.5);
- glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z);
- glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z);
- glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z);
- glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z);
- glEnd();
- glEnable(GL_CULL_FACE);
- glDisable(GL_BLEND);
-
- # draw cutting contours
- glEnableClientState(GL_VERTEX_ARRAY);
- glLineWidth(2);
- glColor3f(0, 0, 0);
- glVertexPointer_c(3, GL_FLOAT, 0, $self->cut_lines_vertices->ptr());
- glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3);
- glVertexPointer_c(3, GL_FLOAT, 0, 0);
- glDisableClientState(GL_VERTEX_ARRAY);
- }
-
- # draw warning message
- $self->draw_warning;
-
- # draw gcode preview legend
- $self->draw_legend;
-
- $self->draw_active_object_annotations;
-
- $self->SwapBuffers();
-}
-
-sub draw_volumes {
- # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking.
- my ($self, $fakecolor) = @_;
-
- # do not cull backfaces to show broken geometry, if any
- glDisable(GL_CULL_FACE);
-
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
- glEnableClientState(GL_VERTEX_ARRAY);
- glEnableClientState(GL_NORMAL_ARRAY);
-
- foreach my $volume_idx (0..$#{$self->volumes}) {
- my $volume = $self->volumes->[$volume_idx];
-
- if ($fakecolor) {
- # Object picking mode. Render the object with a color encoding the object index.
- my $r = ($volume_idx & 0x000000FF) >> 0;
- my $g = ($volume_idx & 0x0000FF00) >> 8;
- my $b = ($volume_idx & 0x00FF0000) >> 16;
- glColor4f($r/255.0, $g/255.0, $b/255.0, 1);
- } elsif ($volume->selected) {
- glColor4f(@{ &SELECTED_COLOR });
- } elsif ($volume->hover) {
- glColor4f(@{ &HOVER_COLOR });
- } else {
- glColor4f(@{ $volume->color });
- }
-
- $volume->render;
- }
- glDisableClientState(GL_NORMAL_ARRAY);
- glDisableClientState(GL_VERTEX_ARRAY);
-
- glDisable(GL_BLEND);
- glEnable(GL_CULL_FACE);
-}
-
-sub mark_volumes_for_layer_height {
- my ($self) = @_;
-
- foreach my $volume_idx (0..$#{$self->volumes}) {
- my $volume = $self->volumes->[$volume_idx];
- my $object_id = int($volume->select_group_id / 1000000);
- if ($self->layer_editing_enabled && $volume->selected && $self->{layer_height_edit_shader} &&
- $volume->has_layer_height_texture && $object_id < $self->{print}->object_count) {
- $volume->set_layer_height_texture_data($self->{layer_preview_z_texture_id}, $self->{layer_height_edit_shader}->shader_program_id,
- $self->{print}->get_object($object_id), $self->_variable_layer_thickness_bar_mouse_cursor_z_relative, $self->{layer_height_edit_band_width});
- } else {
- $volume->reset_layer_height_texture_data();
- }
- }
-}
-
-sub _load_image_set_texture {
- my ($self, $file_name) = @_;
- # Load a PNG with an alpha channel.
- my $img = Wx::Image->new;
- $img->LoadFile(Slic3r::var($file_name), wxBITMAP_TYPE_PNG);
- # Get RGB & alpha raw data from wxImage, interleave them into a Perl array.
- my @rgb = unpack 'C*', $img->GetData();
- my @alpha = $img->HasAlpha ? unpack 'C*', $img->GetAlpha() : (255) x (int(@rgb) / 3);
- my $n_pixels = int(@alpha);
- my @data = (0)x($n_pixels * 4);
- for (my $i = 0; $i < $n_pixels; $i += 1) {
- $data[$i*4 ] = $rgb[$i*3];
- $data[$i*4+1] = $rgb[$i*3+1];
- $data[$i*4+2] = $rgb[$i*3+2];
- $data[$i*4+3] = $alpha[$i];
- }
- # Initialize a raw bitmap data.
- my $params = {
- loaded => 1,
- valid => $n_pixels > 0,
- width => $img->GetWidth,
- height => $img->GetHeight,
- data => OpenGL::Array->new_list(GL_UNSIGNED_BYTE, @data),
- texture_id => glGenTextures_p(1)
- };
- # Create and initialize a texture with the raw data.
- glBindTexture(GL_TEXTURE_2D, $params->{texture_id});
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
- glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $params->{width}, $params->{height}, 0, GL_RGBA, GL_UNSIGNED_BYTE, $params->{data}->ptr);
- glBindTexture(GL_TEXTURE_2D, 0);
- return $params;
-}
-
-sub _variable_layer_thickness_load_overlay_image {
- my ($self) = @_;
- $self->{layer_preview_annotation} = $self->_load_image_set_texture('variable_layer_height_tooltip.png')
- if (! $self->{layer_preview_annotation}->{loaded});
- return $self->{layer_preview_annotation}->{valid};
-}
-
-sub _variable_layer_thickness_load_reset_image {
- my ($self) = @_;
- $self->{layer_preview_reset_image} = $self->_load_image_set_texture('variable_layer_height_reset.png')
- if (! $self->{layer_preview_reset_image}->{loaded});
- return $self->{layer_preview_reset_image}->{valid};
-}
-
-# Paint the tooltip.
-sub _render_image {
- my ($self, $image, $l, $r, $b, $t) = @_;
- $self->_render_texture($image->{texture_id}, $l, $r, $b, $t);
-}
-
-sub _render_texture {
- my ($self, $tex_id, $l, $r, $b, $t) = @_;
-
- glColor4f(1.,1.,1.,1.);
- glDisable(GL_LIGHTING);
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glEnable(GL_TEXTURE_2D);
- glBindTexture(GL_TEXTURE_2D, $tex_id);
- glBegin(GL_QUADS);
- glTexCoord2d(0.,1.); glVertex3f($l, $b, 0);
- glTexCoord2d(1.,1.); glVertex3f($r, $b, 0);
- glTexCoord2d(1.,0.); glVertex3f($r, $t, 0);
- glTexCoord2d(0.,0.); glVertex3f($l, $t, 0);
- glEnd();
- glBindTexture(GL_TEXTURE_2D, 0);
- glDisable(GL_TEXTURE_2D);
- glDisable(GL_BLEND);
- glEnable(GL_LIGHTING);
-}
-
-sub draw_active_object_annotations {
- # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking.
- my ($self) = @_;
-
- return if (! $self->{layer_height_edit_shader} || ! $self->layer_editing_enabled);
-
- # Find the selected volume, over which the layer editing is active.
- my $volume;
- foreach my $volume_idx (0..$#{$self->volumes}) {
- my $v = $self->volumes->[$volume_idx];
- if ($v->selected && $v->has_layer_height_texture) {
- $volume = $v;
- last;
- }
- }
- return if (! $volume);
-
- # If the active object was not allocated at the Print, go away. This should only be a momentary case between an object addition / deletion
- # and an update by Platter::async_apply_config.
- my $object_idx = int($volume->select_group_id / 1000000);
- return if $object_idx >= $self->{print}->object_count;
-
- # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth),
- # where x, y is the window size divided by $self->_zoom.
- my ($bar_left, $bar_bottom, $bar_right, $bar_top) = $self->_variable_layer_thickness_bar_rect_viewport;
- my ($reset_left, $reset_bottom, $reset_right, $reset_top) = $self->_variable_layer_thickness_reset_rect_viewport;
- my $z_cursor_relative = $self->_variable_layer_thickness_bar_mouse_cursor_z_relative;
-
- my $print_object = $self->{print}->get_object($object_idx);
- my $z_max = $print_object->model_object->bounding_box->z_max;
-
- $self->{layer_height_edit_shader}->enable;
- $self->{layer_height_edit_shader}->set_uniform('z_to_texture_row', $volume->layer_height_texture_z_to_row_id);
- $self->{layer_height_edit_shader}->set_uniform('z_texture_row_to_normalized', 1. / $volume->layer_height_texture_height);
- $self->{layer_height_edit_shader}->set_uniform('z_cursor', $z_max * $z_cursor_relative);
- $self->{layer_height_edit_shader}->set_uniform('z_cursor_band_width', $self->{layer_height_edit_band_width});
- glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id});
- glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $volume->layer_height_texture_width, $volume->layer_height_texture_height,
- 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
- glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2,
- 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
- glTexSubImage2D_c(GL_TEXTURE_2D, 0, 0, 0, $volume->layer_height_texture_width, $volume->layer_height_texture_height,
- GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level0);
- glTexSubImage2D_c(GL_TEXTURE_2D, 1, 0, 0, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2,
- GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level1);
-
- # Render the color bar.
- glDisable(GL_DEPTH_TEST);
- # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth),
- # where x, y is the window size divided by $self->_zoom.
- glPushMatrix();
- glLoadIdentity();
- # Paint the overlay.
- glBegin(GL_QUADS);
- glVertex3f($bar_left, $bar_bottom, 0);
- glVertex3f($bar_right, $bar_bottom, 0);
- glVertex3f($bar_right, $bar_top, $z_max);
- glVertex3f($bar_left, $bar_top, $z_max);
- glEnd();
- glBindTexture(GL_TEXTURE_2D, 0);
- $self->{layer_height_edit_shader}->disable;
-
- # Paint the tooltip.
- if ($self->_variable_layer_thickness_load_overlay_image) {
- my $gap = 10/$self->_zoom;
- my ($l, $r, $b, $t) = ($bar_left - $self->{layer_preview_annotation}->{width}/$self->_zoom - $gap, $bar_left - $gap, $reset_bottom + $self->{layer_preview_annotation}->{height}/$self->_zoom + $gap, $reset_bottom + $gap);
- $self->_render_image($self->{layer_preview_annotation}, $l, $r, $t, $b);
- }
-
- # Paint the reset button.
- if ($self->_variable_layer_thickness_load_reset_image) {
- $self->_render_image($self->{layer_preview_reset_image}, $reset_left, $reset_right, $reset_bottom, $reset_top);
- }
-
- # Paint the graph.
- #FIXME show some kind of legend.
- my $max_z = unscale($print_object->size->z);
- my $profile = $print_object->model_object->layer_height_profile;
- my $layer_height = $print_object->config->get('layer_height');
- my $layer_height_max = 10000000000.;
- {
- # Get a maximum layer height value.
- #FIXME This is a duplicate code of Slicing.cpp.
- my $nozzle_diameters = $print_object->print->config->get('nozzle_diameter');
- my $layer_heights_min = $print_object->print->config->get('min_layer_height');
- my $layer_heights_max = $print_object->print->config->get('max_layer_height');
- for (my $i = 0; $i < scalar(@{$nozzle_diameters}); $i += 1) {
- my $lh_min = ($layer_heights_min->[$i] == 0.) ? 0.07 : max(0.01, $layer_heights_min->[$i]);
- my $lh_max = ($layer_heights_max->[$i] == 0.) ? (0.75 * $nozzle_diameters->[$i]) : $layer_heights_max->[$i];
- $layer_height_max = min($layer_height_max, max($lh_min, $lh_max));
- }
- }
- # Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region.
- $layer_height_max *= 1.12;
- # Baseline
- glColor3f(0., 0., 0.);
- glBegin(GL_LINE_STRIP);
- glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / $layer_height_max, $bar_bottom);
- glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / $layer_height_max, $bar_top);
- glEnd();
- # Curve
- glColor3f(0., 0., 1.);
- glBegin(GL_LINE_STRIP);
- for (my $i = 0; $i < int(@{$profile}); $i += 2) {
- my $z = $profile->[$i];
- my $h = $profile->[$i+1];
- glVertex3f($bar_left + $h * ($bar_right - $bar_left) / $layer_height_max, $bar_bottom + $z * ($bar_top - $bar_bottom) / $max_z, $z);
- }
- glEnd();
- # Revert the matrices.
- glPopMatrix();
- glEnable(GL_DEPTH_TEST);
-}
-
-sub draw_legend {
- my ($self) = @_;
-
- if (!$self->_legend_enabled) {
- return;
- }
-
- # If the legend texture has not been loaded into the GPU, do it now.
- my $tex_id = Slic3r::GUI::_3DScene::finalize_legend_texture;
- if ($tex_id > 0)
- {
- my $tex_w = Slic3r::GUI::_3DScene::get_legend_texture_width;
- my $tex_h = Slic3r::GUI::_3DScene::get_legend_texture_height;
- if (($tex_w > 0) && ($tex_h > 0))
- {
- glDisable(GL_DEPTH_TEST);
- glPushMatrix();
- glLoadIdentity();
-
- my ($cw, $ch) = $self->GetSizeWH;
-
- my $l = (-0.5 * $cw) / $self->_zoom;
- my $t = (0.5 * $ch) / $self->_zoom;
- my $r = $l + $tex_w / $self->_zoom;
- my $b = $t - $tex_h / $self->_zoom;
- $self->_render_texture($tex_id, $l, $r, $b, $t);
-
- glPopMatrix();
- glEnable(GL_DEPTH_TEST);
- }
- }
-}
-
-sub draw_warning {
- my ($self) = @_;
-
- if (!$self->_warning_enabled) {
- return;
- }
-
- # If the warning texture has not been loaded into the GPU, do it now.
- my $tex_id = Slic3r::GUI::_3DScene::finalize_warning_texture;
- if ($tex_id > 0)
- {
- my $tex_w = Slic3r::GUI::_3DScene::get_warning_texture_width;
- my $tex_h = Slic3r::GUI::_3DScene::get_warning_texture_height;
- if (($tex_w > 0) && ($tex_h > 0))
- {
- glDisable(GL_DEPTH_TEST);
- glPushMatrix();
- glLoadIdentity();
-
- my ($cw, $ch) = $self->GetSizeWH;
-
- my $l = (-0.5 * $tex_w) / $self->_zoom;
- my $t = (-0.5 * $ch + $tex_h) / $self->_zoom;
- my $r = $l + $tex_w / $self->_zoom;
- my $b = $t - $tex_h / $self->_zoom;
- $self->_render_texture($tex_id, $l, $r, $b, $t);
-
- glPopMatrix();
- glEnable(GL_DEPTH_TEST);
- }
- }
-}
-
-sub update_volumes_colors_by_extruder {
- my ($self, $config) = @_;
- $self->volumes->update_colors_by_extruder($config);
-}
-
-sub opengl_info
-{
- my ($self, %params) = @_;
- my %tag = Slic3r::tags($params{format});
-
- my $gl_version = glGetString(GL_VERSION);
- my $gl_vendor = glGetString(GL_VENDOR);
- my $gl_renderer = glGetString(GL_RENDERER);
- my $glsl_version = glGetString(GL_SHADING_LANGUAGE_VERSION);
-
- my $out = '';
- $out .= "$tag{h2start}OpenGL installation$tag{h2end}$tag{eol}";
- $out .= " $tag{bstart}Using POGL$tag{bend} v$OpenGL::BUILD_VERSION$tag{eol}";
- $out .= " $tag{bstart}GL version: $tag{bend}${gl_version}$tag{eol}";
- $out .= " $tag{bstart}vendor: $tag{bend}${gl_vendor}$tag{eol}";
- $out .= " $tag{bstart}renderer: $tag{bend}${gl_renderer}$tag{eol}";
- $out .= " $tag{bstart}GLSL version: $tag{bend}${glsl_version}$tag{eol}";
-
- # Check for other OpenGL extensions
- $out .= "$tag{h2start}Installed extensions (* implemented in the module):$tag{h2end}$tag{eol}";
- my $extensions = glGetString(GL_EXTENSIONS);
- my @extensions = split(' ',$extensions);
- foreach my $ext (sort @extensions) {
- my $stat = glpCheckExtension($ext);
- $out .= sprintf("%s ${ext}$tag{eol}", $stat?' ':'*');
- $out .= sprintf(" ${stat}$tag{eol}") if ($stat && $stat !~ m|^$ext |);
- }
-
- return $out;
-}
-
-sub _report_opengl_state
-{
- my ($self, $comment) = @_;
- my $err = glGetError();
- return 0 if ($err == 0);
-
- # gluErrorString() hangs. Don't use it.
-# my $errorstr = gluErrorString();
- my $errorstr = '';
- if ($err == 0x0500) {
- $errorstr = 'GL_INVALID_ENUM';
- } elsif ($err == GL_INVALID_VALUE) {
- $errorstr = 'GL_INVALID_VALUE';
- } elsif ($err == GL_INVALID_OPERATION) {
- $errorstr = 'GL_INVALID_OPERATION';
- } elsif ($err == GL_STACK_OVERFLOW) {
- $errorstr = 'GL_STACK_OVERFLOW';
- } elsif ($err == GL_OUT_OF_MEMORY) {
- $errorstr = 'GL_OUT_OF_MEMORY';
- } else {
- $errorstr = 'unknown';
- }
- if (defined($comment)) {
- printf("OpenGL error at %s, nr %d (0x%x): %s\n", $comment, $err, $err, $errorstr);
- } else {
- printf("OpenGL error nr %d (0x%x): %s\n", $err, $err, $errorstr);
- }
-}
-
-sub _vertex_shader_Gouraud {
- return <<'VERTEX';
-#version 110
-
-#define INTENSITY_CORRECTION 0.6
-
-// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
-const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
-#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
-#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
-#define LIGHT_TOP_SHININESS 20.0
-
-// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
-const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
-#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
-//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
-//#define LIGHT_FRONT_SHININESS 5.0
-
-#define INTENSITY_AMBIENT 0.3
-
-const vec3 ZERO = vec3(0.0, 0.0, 0.0);
-
-struct PrintBoxDetection
-{
- vec3 min;
- vec3 max;
- // xyz contains the offset, if w == 1.0 detection needs to be performed
- vec4 volume_origin;
-};
-
-uniform PrintBoxDetection print_box;
-
-// x = tainted, y = specular;
-varying vec2 intensity;
-
-varying vec3 delta_box_min;
-varying vec3 delta_box_max;
-
-void main()
-{
- // First transform the normal into camera space and normalize the result.
- vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
-
- // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
- // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
- float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
-
- intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
- intensity.y = 0.0;
-
- if (NdotL > 0.0)
- intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
-
- // Perform the same lighting calculation for the 2nd light source (no specular applied).
- NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
- intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
-
- // compute deltas for out of print volume detection (world coordinates)
- if (print_box.volume_origin.w == 1.0)
- {
- vec3 v = gl_Vertex.xyz + print_box.volume_origin.xyz;
- delta_box_min = v - print_box.min;
- delta_box_max = v - print_box.max;
- }
- else
- {
- delta_box_min = ZERO;
- delta_box_max = ZERO;
- }
-
- gl_Position = ftransform();
-}
-
-VERTEX
-}
-
-sub _fragment_shader_Gouraud {
- return <<'FRAGMENT';
-#version 110
-
-const vec3 ZERO = vec3(0.0, 0.0, 0.0);
-
-// x = tainted, y = specular;
-varying vec2 intensity;
-
-varying vec3 delta_box_min;
-varying vec3 delta_box_max;
-
-uniform vec4 uniform_color;
-
-void main()
-{
- // if the fragment is outside the print volume -> use darker color
- vec3 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(uniform_color.rgb, ZERO, 0.3333) : uniform_color.rgb;
- gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + color * intensity.x, uniform_color.a);
-}
-
-FRAGMENT
-}
-
-sub _vertex_shader_Phong {
- return <<'VERTEX';
-#version 110
-
-varying vec3 normal;
-varying vec3 eye;
-void main(void)
-{
- eye = normalize(vec3(gl_ModelViewMatrix * gl_Vertex));
- normal = normalize(gl_NormalMatrix * gl_Normal);
- gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
-}
-VERTEX
-}
-
-sub _fragment_shader_Phong {
- return <<'FRAGMENT';
-#version 110
-
-#define INTENSITY_CORRECTION 0.7
-
-#define LIGHT_TOP_DIR -0.6/1.31, 0.6/1.31, 1./1.31
-#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
-#define LIGHT_TOP_SPECULAR (0.5 * INTENSITY_CORRECTION)
-//#define LIGHT_TOP_SHININESS 50.
-#define LIGHT_TOP_SHININESS 10.
-
-#define LIGHT_FRONT_DIR 1./1.43, 0.2/1.43, 1./1.43
-#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
-#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
-#define LIGHT_FRONT_SHININESS 50.
-
-#define INTENSITY_AMBIENT 0.0
-
-varying vec3 normal;
-varying vec3 eye;
-uniform vec4 uniform_color;
-void main() {
-
- float intensity_specular = 0.;
- float intensity_tainted = 0.;
- float intensity = max(dot(normal,vec3(LIGHT_TOP_DIR)), 0.0);
- // if the vertex is lit compute the specular color
- if (intensity > 0.0) {
- intensity_tainted = LIGHT_TOP_DIFFUSE * intensity;
- // compute the half vector
- vec3 h = normalize(vec3(LIGHT_TOP_DIR) + eye);
- // compute the specular term into spec
- intensity_specular = LIGHT_TOP_SPECULAR * pow(max(dot(h, normal), 0.0), LIGHT_TOP_SHININESS);
- }
- intensity = max(dot(normal,vec3(LIGHT_FRONT_DIR)), 0.0);
- // if the vertex is lit compute the specular color
- if (intensity > 0.0) {
- intensity_tainted += LIGHT_FRONT_DIFFUSE * intensity;
- // compute the half vector
-// vec3 h = normalize(vec3(LIGHT_FRONT_DIR) + eye);
- // compute the specular term into spec
-// intensity_specular += LIGHT_FRONT_SPECULAR * pow(max(dot(h,normal), 0.0), LIGHT_FRONT_SHININESS);
- }
-
- gl_FragColor = max(
- vec4(intensity_specular, intensity_specular, intensity_specular, 0.) + uniform_color * intensity_tainted,
- INTENSITY_AMBIENT * uniform_color);
- gl_FragColor.a = uniform_color.a;
-}
-FRAGMENT
-}
-
-sub _vertex_shader_variable_layer_height {
- return <<'VERTEX';
-#version 110
-
-#define INTENSITY_CORRECTION 0.6
-
-const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
-#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
-#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
-#define LIGHT_TOP_SHININESS 20.0
-
-const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
-#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
-//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
-//#define LIGHT_FRONT_SHININESS 5.0
-
-#define INTENSITY_AMBIENT 0.3
-
-// x = tainted, y = specular;
-varying vec2 intensity;
-
-varying float object_z;
-
-void main()
-{
- // First transform the normal into camera space and normalize the result.
- vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
-
- // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
- // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
- float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
-
- intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
- intensity.y = 0.0;
-
- if (NdotL > 0.0)
- intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
-
- // Perform the same lighting calculation for the 2nd light source (no specular)
- NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
-
- intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
-
- // Scaled to widths of the Z texture.
- object_z = gl_Vertex.z;
-
- gl_Position = ftransform();
-}
-
-VERTEX
-}
-
-sub _fragment_shader_variable_layer_height {
- return <<'FRAGMENT';
-#version 110
-
-#define M_PI 3.1415926535897932384626433832795
-
-// 2D texture (1D texture split by the rows) of color along the object Z axis.
-uniform sampler2D z_texture;
-// Scaling from the Z texture rows coordinate to the normalized texture row coordinate.
-uniform float z_to_texture_row;
-uniform float z_texture_row_to_normalized;
-uniform float z_cursor;
-uniform float z_cursor_band_width;
-
-// x = tainted, y = specular;
-varying vec2 intensity;
-
-varying float object_z;
-
-void main()
-{
- float object_z_row = z_to_texture_row * object_z;
- // Index of the row in the texture.
- float z_texture_row = floor(object_z_row);
- // Normalized coordinate from 0. to 1.
- float z_texture_col = object_z_row - z_texture_row;
- float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25;
- // Calculate level of detail from the object Z coordinate.
- // This makes the slowly sloping surfaces to be show with high detail (with stripes),
- // and the vertical surfaces to be shown with low detail (no stripes)
- float z_in_cells = object_z_row * 190.;
- // Gradient of Z projected on the screen.
- float dx_vtc = dFdx(z_in_cells);
- float dy_vtc = dFdy(z_in_cells);
- float lod = clamp(0.5 * log2(max(dx_vtc*dx_vtc, dy_vtc*dy_vtc)), 0., 1.);
- // Sample the Z texture. Texture coordinates are normalized to <0, 1>.
- vec4 color =
- mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.),
- texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod);
-
- // Mix the final color.
- gl_FragColor =
- vec4(intensity.y, intensity.y, intensity.y, 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend);
-}
-
-FRAGMENT
-}
+#==============================================================================================================================
+#sub layer_editing_enabled {
+# my ($self, $value) = @_;
+# if (@_ == 2) {
+# $self->{layer_editing_enabled} = $value;
+# if ($value) {
+# if (! $self->{layer_editing_initialized}) {
+# # Enabling the layer editing for the first time. This triggers compilation of the necessary OpenGL shaders.
+# # If compilation fails, a message box is shown with the error codes.
+# $self->SetCurrent($self->GetContext);
+# my $shader = new Slic3r::GUI::_3DScene::GLShader;
+# my $error_message;
+# if (! $shader->load_from_text($self->_fragment_shader_variable_layer_height, $self->_vertex_shader_variable_layer_height)) {
+# # Compilation or linking of the shaders failed.
+# $error_message = "Cannot compile an OpenGL Shader, therefore the Variable Layer Editing will be disabled.\n\n"
+# . $shader->last_error;
+# $shader = undef;
+# } else {
+# $self->{layer_height_edit_shader} = $shader;
+# ($self->{layer_preview_z_texture_id}) = glGenTextures_p(1);
+# glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id});
+# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
+# glBindTexture(GL_TEXTURE_2D, 0);
+# }
+# if (defined($error_message)) {
+# # Don't enable the layer editing tool.
+# $self->{layer_editing_enabled} = 0;
+# # 2 means failed
+# $self->{layer_editing_initialized} = 2;
+# # Show the error message.
+# Wx::MessageBox($error_message, "Slic3r Error", wxOK | wxICON_EXCLAMATION, $self);
+# } else {
+# $self->{layer_editing_initialized} = 1;
+# }
+# } elsif ($self->{layer_editing_initialized} == 2) {
+# # Initilization failed before. Don't try to initialize and disable layer editing.
+# $self->{layer_editing_enabled} = 0;
+# }
+# }
+# }
+# return $self->{layer_editing_enabled};
+#}
+#
+#sub layer_editing_allowed {
+# my ($self) = @_;
+# # Allow layer editing if either the shaders were not initialized yet and we don't know
+# # whether it will be possible to initialize them,
+# # or if the initialization was done already and it failed.
+# return ! (defined($self->{layer_editing_initialized}) && $self->{layer_editing_initialized} == 2);
+#}
+#
+#sub _first_selected_object_id_for_variable_layer_height_editing {
+# my ($self) = @_;
+# for my $i (0..$#{$self->volumes}) {
+# if ($self->volumes->[$i]->selected) {
+# my $object_id = int($self->volumes->[$i]->select_group_id / 1000000);
+# # Objects with object_id >= 1000 have a specific meaning, for example the wipe tower proxy.
+# return ($object_id >= $self->{print}->object_count) ? -1 : $object_id
+# if $object_id < 10000;
+# }
+# }
+# return -1;
+#}
+#
+## Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen.
+#sub _variable_layer_thickness_bar_rect_screen {
+# my ($self) = @_;
+# my ($cw, $ch) = $self->GetSizeWH;
+# return ($cw - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, 0, $cw, $ch - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT);
+#}
+#
+#sub _variable_layer_thickness_bar_rect_viewport {
+# my ($self) = @_;
+# my ($cw, $ch) = $self->GetSizeWH;
+# return ((0.5*$cw-VARIABLE_LAYER_THICKNESS_BAR_WIDTH)/$self->_zoom, (-0.5*$ch+VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT)/$self->_zoom, $cw/(2*$self->_zoom), $ch/(2*$self->_zoom));
+#}
+#
+## Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen.
+#sub _variable_layer_thickness_reset_rect_screen {
+# my ($self) = @_;
+# my ($cw, $ch) = $self->GetSizeWH;
+# return ($cw - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, $ch - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT, $cw, $ch);
+#}
+#
+#sub _variable_layer_thickness_reset_rect_viewport {
+# my ($self) = @_;
+# my ($cw, $ch) = $self->GetSizeWH;
+# return ((0.5*$cw-VARIABLE_LAYER_THICKNESS_BAR_WIDTH)/$self->_zoom, -$ch/(2*$self->_zoom), $cw/(2*$self->_zoom), (-0.5*$ch+VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT)/$self->_zoom);
+#}
+#
+#sub _variable_layer_thickness_bar_rect_mouse_inside {
+# my ($self, $mouse_evt) = @_;
+# my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen;
+# return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom;
+#}
+#
+#sub _variable_layer_thickness_reset_rect_mouse_inside {
+# my ($self, $mouse_evt) = @_;
+# my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_reset_rect_screen;
+# return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom;
+#}
+#
+#sub _variable_layer_thickness_bar_mouse_cursor_z_relative {
+# my ($self) = @_;
+# my $mouse_pos = $self->ScreenToClientPoint(Wx::GetMousePosition());
+# my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen;
+# return ($mouse_pos->x >= $bar_left && $mouse_pos->x <= $bar_right && $mouse_pos->y >= $bar_top && $mouse_pos->y <= $bar_bottom) ?
+# # Inside the bar.
+# ($bar_bottom - $mouse_pos->y - 1.) / ($bar_bottom - $bar_top - 1) :
+# # Outside the bar.
+# -1000.;
+#}
+#
+#sub _variable_layer_thickness_action {
+# my ($self, $mouse_event, $do_modification) = @_;
+# # A volume is selected. Test, whether hovering over a layer thickness bar.
+# return if $self->{layer_height_edit_last_object_id} == -1;
+# if (defined($mouse_event)) {
+# my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen;
+# $self->{layer_height_edit_last_z} = unscale($self->{print}->get_object($self->{layer_height_edit_last_object_id})->size->z)
+# * ($bar_bottom - $mouse_event->GetY - 1.) / ($bar_bottom - $bar_top);
+# $self->{layer_height_edit_last_action} = $mouse_event->ShiftDown ? ($mouse_event->RightIsDown ? 3 : 2) : ($mouse_event->RightIsDown ? 0 : 1);
+# }
+# # Mark the volume as modified, so Print will pick its layer height profile? Where to mark it?
+# # Start a timer to refresh the print? schedule_background_process() ?
+# # The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself.
+# $self->{print}->get_object($self->{layer_height_edit_last_object_id})->adjust_layer_height_profile(
+# $self->{layer_height_edit_last_z},
+# $self->{layer_height_edit_strength},
+# $self->{layer_height_edit_band_width},
+# $self->{layer_height_edit_last_action});
+#
+# $self->volumes->[$self->{layer_height_edit_last_object_id}]->generate_layer_height_texture(
+# $self->{print}->get_object($self->{layer_height_edit_last_object_id}), 1);
+# $self->Refresh;
+# # Automatic action on mouse down with the same coordinate.
+# $self->{layer_height_edit_timer}->Start(100, wxTIMER_CONTINUOUS);
+#}
+#
+#sub mouse_event {
+# my ($self, $e) = @_;
+#
+# my $pos = Slic3r::Pointf->new($e->GetPositionXY);
+# my $object_idx_selected = $self->{layer_height_edit_last_object_id} = ($self->layer_editing_enabled && $self->{print}) ? $self->_first_selected_object_id_for_variable_layer_height_editing : -1;
+#
+# $self->_mouse_dragging($e->Dragging);
+#
+# if ($e->Entering && (&Wx::wxMSW || $^O eq 'linux')) {
+# # wxMSW needs focus in order to catch mouse wheel events
+# $self->SetFocus;
+# $self->_drag_start_xy(undef);
+# } elsif ($e->LeftDClick) {
+# if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) {
+# } elsif ($self->on_double_click) {
+# $self->on_double_click->();
+# }
+# } elsif ($e->LeftDown || $e->RightDown) {
+# # If user pressed left or right button we first check whether this happened
+# # on a volume or not.
+# my $volume_idx = $self->_hover_volume_idx // -1;
+# $self->_layer_height_edited(0);
+# if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) {
+# # A volume is selected and the mouse is hovering over a layer thickness bar.
+# # Start editing the layer height.
+# $self->_layer_height_edited(1);
+# $self->_variable_layer_thickness_action($e);
+# } elsif ($object_idx_selected != -1 && $self->_variable_layer_thickness_reset_rect_mouse_inside($e)) {
+# $self->{print}->get_object($object_idx_selected)->reset_layer_height_profile;
+# # Index 2 means no editing, just wait for mouse up event.
+# $self->_layer_height_edited(2);
+# $self->Refresh;
+# $self->Update;
+# } else {
+# # The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate $pos->x,y,
+# # an converts the screen space coordinate to unscaled object space.
+# my $pos3d = ($volume_idx == -1) ? undef : $self->mouse_to_3d(@$pos);
+#
+# # Select volume in this 3D canvas.
+# # Don't deselect a volume if layer editing is enabled. We want the object to stay selected
+# # during the scene manipulation.
+#
+# if ($self->enable_picking && ($volume_idx != -1 || ! $self->layer_editing_enabled)) {
+# $self->deselect_volumes;
+# $self->select_volume($volume_idx);
+#
+# if ($volume_idx != -1) {
+# my $group_id = $self->volumes->[$volume_idx]->select_group_id;
+# my @volumes;
+# if ($group_id != -1) {
+# $self->select_volume($_)
+# for grep $self->volumes->[$_]->select_group_id == $group_id,
+# 0..$#{$self->volumes};
+# }
+# }
+#
+# $self->Refresh;
+# $self->Update;
+# }
+#
+# # propagate event through callback
+# $self->on_select->($volume_idx)
+# if $self->on_select;
+#
+# if ($volume_idx != -1) {
+# if ($e->LeftDown && $self->enable_moving) {
+# # Only accept the initial position, if it is inside the volume bounding box.
+# my $volume_bbox = $self->volumes->[$volume_idx]->transformed_bounding_box;
+# $volume_bbox->offset(1.);
+# if ($volume_bbox->contains_point($pos3d)) {
+# # The dragging operation is initiated.
+# $self->_drag_volume_idx($volume_idx);
+# $self->_drag_start_pos($pos3d);
+# # Remember the shift to to the object center. The object center will later be used
+# # to limit the object placement close to the bed.
+# $self->_drag_volume_center_offset($pos3d->vector_to($volume_bbox->center));
+# }
+# } elsif ($e->RightDown) {
+# # if right clicking on volume, propagate event through callback
+# $self->on_right_click->($e->GetPosition)
+# if $self->on_right_click;
+# }
+# }
+# }
+# } elsif ($e->Dragging && $e->LeftIsDown && ! $self->_layer_height_edited && defined($self->_drag_volume_idx)) {
+# # Get new position at the same Z of the initial click point.
+# my $cur_pos = Slic3r::Linef3->new(
+# $self->mouse_to_3d($e->GetX, $e->GetY, 0),
+# $self->mouse_to_3d($e->GetX, $e->GetY, 1))
+# ->intersect_plane($self->_drag_start_pos->z);
+#
+# # Clip the new position, so the object center remains close to the bed.
+# {
+# $cur_pos->translate(@{$self->_drag_volume_center_offset});
+# my $cur_pos2 = Slic3r::Point->new(scale($cur_pos->x), scale($cur_pos->y));
+# if (! $self->bed_polygon->contains_point($cur_pos2)) {
+# my $ip = $self->bed_polygon->point_projection($cur_pos2);
+# $cur_pos->set_x(unscale($ip->x));
+# $cur_pos->set_y(unscale($ip->y));
+# }
+# $cur_pos->translate(@{$self->_drag_volume_center_offset->negative});
+# }
+# # Calculate the translation vector.
+# my $vector = $self->_drag_start_pos->vector_to($cur_pos);
+# # Get the volume being dragged.
+# my $volume = $self->volumes->[$self->_drag_volume_idx];
+# # Get all volumes belonging to the same group, if any.
+# my @volumes = ($volume->drag_group_id == -1) ?
+# ($volume) :
+# grep $_->drag_group_id == $volume->drag_group_id, @{$self->volumes};
+# # Apply new temporary volume origin and ignore Z.
+# $_->translate($vector->x, $vector->y, 0) for @volumes;
+# $self->_drag_start_pos($cur_pos);
+# $self->_dragged(1);
+# $self->Refresh;
+# $self->Update;
+# } elsif ($e->Dragging) {
+# if ($self->_layer_height_edited && $object_idx_selected != -1) {
+# $self->_variable_layer_thickness_action($e) if ($self->_layer_height_edited == 1);
+# } elsif ($e->LeftIsDown) {
+# # if dragging over blank area with left button, rotate
+# if (defined $self->_drag_start_pos) {
+# my $orig = $self->_drag_start_pos;
+# if (TURNTABLE_MODE) {
+# # Turntable mode is enabled by default.
+# $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE);
+# $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #-
+# $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_LOCK_THETA_MAX;
+# $self->_stheta(0) if $self->_stheta < 0;
+# } else {
+# my $size = $self->GetClientSize;
+# my @quat = trackball(
+# $orig->x / ($size->width / 2) - 1,
+# 1 - $orig->y / ($size->height / 2), #/
+# $pos->x / ($size->width / 2) - 1,
+# 1 - $pos->y / ($size->height / 2), #/
+# );
+# $self->_quat(mulquats($self->_quat, \@quat));
+# }
+# $self->on_viewport_changed->() if $self->on_viewport_changed;
+# $self->Refresh;
+# $self->Update;
+# }
+# $self->_drag_start_pos($pos);
+# } elsif ($e->MiddleIsDown || $e->RightIsDown) {
+# # If dragging over blank area with right button, pan.
+# if (defined $self->_drag_start_xy) {
+# # get point in model space at Z = 0
+# my $cur_pos = $self->mouse_to_3d($e->GetX, $e->GetY, 0);
+# my $orig = $self->mouse_to_3d($self->_drag_start_xy->x, $self->_drag_start_xy->y, 0);
+# $self->_camera_target->translate(@{$orig->vector_to($cur_pos)->negative});
+# $self->on_viewport_changed->() if $self->on_viewport_changed;
+# $self->Refresh;
+# $self->Update;
+# }
+# $self->_drag_start_xy($pos);
+# }
+# } elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
+# if ($self->_layer_height_edited) {
+# $self->_layer_height_edited(undef);
+# $self->{layer_height_edit_timer}->Stop;
+# $self->on_model_update->()
+# if ($object_idx_selected != -1 && $self->on_model_update);
+# } elsif ($self->on_move && defined($self->_drag_volume_idx) && $self->_dragged) {
+# # get all volumes belonging to the same group, if any
+# my @volume_idxs;
+# my $group_id = $self->volumes->[$self->_drag_volume_idx]->drag_group_id;
+# if ($group_id == -1) {
+# @volume_idxs = ($self->_drag_volume_idx);
+# } else {
+# @volume_idxs = grep $self->volumes->[$_]->drag_group_id == $group_id,
+# 0..$#{$self->volumes};
+# }
+# $self->on_move->(@volume_idxs);
+# }
+# $self->_drag_volume_idx(undef);
+# $self->_drag_start_pos(undef);
+# $self->_drag_start_xy(undef);
+# $self->_dragged(undef);
+# } elsif ($e->Moving) {
+# $self->_mouse_pos($pos);
+# # Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor
+# # hovers over.
+# if ($self->enable_picking) {
+# $self->Update;
+# $self->Refresh;
+# }
+# } else {
+# $e->Skip();
+# }
+#}
+#
+#sub mouse_wheel_event {
+# my ($self, $e) = @_;
+#
+# if ($e->MiddleIsDown) {
+# # Ignore the wheel events if the middle button is pressed.
+# return;
+# }
+# if ($self->layer_editing_enabled && $self->{print}) {
+# my $object_idx_selected = $self->_first_selected_object_id_for_variable_layer_height_editing;
+# if ($object_idx_selected != -1) {
+# # A volume is selected. Test, whether hovering over a layer thickness bar.
+# if ($self->_variable_layer_thickness_bar_rect_mouse_inside($e)) {
+# # Adjust the width of the selection.
+# $self->{layer_height_edit_band_width} = max(min($self->{layer_height_edit_band_width} * (1 + 0.1 * $e->GetWheelRotation() / $e->GetWheelDelta()), 10.), 1.5);
+# $self->Refresh;
+# return;
+# }
+# }
+# }
+#
+# # Calculate the zoom delta and apply it to the current zoom factor
+# my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta();
+# $zoom = max(min($zoom, 4), -4);
+# $zoom /= 10;
+# $zoom = $self->_zoom / (1-$zoom);
+# # Don't allow to zoom too far outside the scene.
+# my $zoom_min = $self->get_zoom_to_bounding_box_factor($self->max_bounding_box);
+# $zoom_min *= 0.4 if defined $zoom_min;
+# $zoom = $zoom_min if defined $zoom_min && $zoom < $zoom_min;
+# $self->_zoom($zoom);
+#
+## # In order to zoom around the mouse point we need to translate
+## # the camera target
+## my $size = Slic3r::Pointf->new($self->GetSizeWH);
+## my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #-
+## $self->_camera_target->translate(
+## # ($pos - $size/2) represents the vector from the viewport center
+## # to the mouse point. By multiplying it by $zoom we get the new,
+## # transformed, length of such vector.
+## # Since we want that point to stay fixed, we move our camera target
+## # in the opposite direction by the delta of the length of such vector
+## # ($zoom - 1). We then scale everything by 1/$self->_zoom since
+## # $self->_camera_target is expressed in terms of model units.
+## -($pos->x - $size->x/2) * ($zoom) / $self->_zoom,
+## -($pos->y - $size->y/2) * ($zoom) / $self->_zoom,
+## 0,
+## ) if 0;
+#
+# $self->on_viewport_changed->() if $self->on_viewport_changed;
+# $self->Resize($self->GetSizeWH) if $self->IsShownOnScreen;
+# $self->Refresh;
+#}
+#
+## Reset selection.
+#sub reset_objects {
+# my ($self) = @_;
+# if ($self->GetContext) {
+# $self->SetCurrent($self->GetContext);
+# $self->volumes->release_geometry;
+# }
+# $self->volumes->erase;
+# $self->_dirty(1);
+#}
+#
+## Setup camera to view all objects.
+#sub set_viewport_from_scene {
+# my ($self, $scene) = @_;
+#
+# $self->_sphi($scene->_sphi);
+# $self->_stheta($scene->_stheta);
+# $self->_camera_target($scene->_camera_target);
+# $self->_zoom($scene->_zoom);
+# $self->_quat($scene->_quat);
+# $self->_dirty(1);
+#}
+#
+## Set the camera to a default orientation,
+## zoom to volumes.
+#sub select_view {
+# my ($self, $direction) = @_;
+#
+# my $dirvec;
+# if (ref($direction)) {
+# $dirvec = $direction;
+# } else {
+# if ($direction eq 'iso') {
+# $dirvec = VIEW_DEFAULT;
+# } elsif ($direction eq 'left') {
+# $dirvec = VIEW_LEFT;
+# } elsif ($direction eq 'right') {
+# $dirvec = VIEW_RIGHT;
+# } elsif ($direction eq 'top') {
+# $dirvec = VIEW_TOP;
+# } elsif ($direction eq 'bottom') {
+# $dirvec = VIEW_BOTTOM;
+# } elsif ($direction eq 'front') {
+# $dirvec = VIEW_FRONT;
+# } elsif ($direction eq 'rear') {
+# $dirvec = VIEW_REAR;
+# }
+# }
+# my $bb = $self->volumes_bounding_box;
+# if (! $bb->empty) {
+# $self->_sphi($dirvec->[0]);
+# $self->_stheta($dirvec->[1]);
+# # Avoid gimball lock.
+# $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_LOCK_THETA_MAX;
+# $self->_stheta(0) if $self->_stheta < 0;
+# $self->on_viewport_changed->() if $self->on_viewport_changed;
+# $self->Refresh;
+# }
+#}
+#
+#sub get_zoom_to_bounding_box_factor {
+# my ($self, $bb) = @_;
+# my $max_bb_size = max(@{ $bb->size });
+# return undef if ($max_bb_size == 0);
+#
+# # project the bbox vertices on a plane perpendicular to the camera forward axis
+# # then calculates the vertices coordinate on this plane along the camera xy axes
+#
+# # we need the view matrix, we let opengl calculate it (same as done in render sub)
+# glMatrixMode(GL_MODELVIEW);
+# glLoadIdentity();
+#
+# if (!TURNTABLE_MODE) {
+# # Shift the perspective camera.
+# my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance);
+# glTranslatef(@$camera_pos);
+# }
+#
+# if (TURNTABLE_MODE) {
+# # Turntable mode is enabled by default.
+# glRotatef(-$self->_stheta, 1, 0, 0); # pitch
+# glRotatef($self->_sphi, 0, 0, 1); # yaw
+# } else {
+# # Shift the perspective camera.
+# my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance);
+# glTranslatef(@$camera_pos);
+# my @rotmat = quat_to_rotmatrix($self->quat);
+# glMultMatrixd_p(@rotmat[0..15]);
+# }
+# glTranslatef(@{ $self->_camera_target->negative });
+#
+# # get the view matrix back from opengl
+# my @matrix = glGetFloatv_p(GL_MODELVIEW_MATRIX);
+#
+# # camera axes
+# my $right = Slic3r::Pointf3->new($matrix[0], $matrix[4], $matrix[8]);
+# my $up = Slic3r::Pointf3->new($matrix[1], $matrix[5], $matrix[9]);
+# my $forward = Slic3r::Pointf3->new($matrix[2], $matrix[6], $matrix[10]);
+#
+# my $bb_min = $bb->min_point();
+# my $bb_max = $bb->max_point();
+# my $bb_center = $bb->center();
+#
+# # bbox vertices in world space
+# my @vertices = ();
+# push(@vertices, $bb_min);
+# push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_min->y(), $bb_min->z()));
+# push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_max->y(), $bb_min->z()));
+# push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_max->y(), $bb_min->z()));
+# push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_min->y(), $bb_max->z()));
+# push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_min->y(), $bb_max->z()));
+# push(@vertices, $bb_max);
+# push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_max->y(), $bb_max->z()));
+#
+# my $max_x = 0.0;
+# my $max_y = 0.0;
+#
+# # margin factor to give some empty space around the bbox
+# my $margin_factor = 1.25;
+#
+# foreach my $v (@vertices) {
+# # project vertex on the plane perpendicular to camera forward axis
+# my $pos = Slic3r::Pointf3->new($v->x() - $bb_center->x(), $v->y() - $bb_center->y(), $v->z() - $bb_center->z());
+# my $proj_on_normal = $pos->x() * $forward->x() + $pos->y() * $forward->y() + $pos->z() * $forward->z();
+# my $proj_on_plane = Slic3r::Pointf3->new($pos->x() - $proj_on_normal * $forward->x(), $pos->y() - $proj_on_normal * $forward->y(), $pos->z() - $proj_on_normal * $forward->z());
+#
+# # calculates vertex coordinate along camera xy axes
+# my $x_on_plane = $proj_on_plane->x() * $right->x() + $proj_on_plane->y() * $right->y() + $proj_on_plane->z() * $right->z();
+# my $y_on_plane = $proj_on_plane->x() * $up->x() + $proj_on_plane->y() * $up->y() + $proj_on_plane->z() * $up->z();
+#
+# $max_x = max($max_x, $margin_factor * 2 * abs($x_on_plane));
+# $max_y = max($max_y, $margin_factor * 2 * abs($y_on_plane));
+# }
+#
+# return undef if (($max_x == 0) || ($max_y == 0));
+#
+# my ($cw, $ch) = $self->GetSizeWH;
+# my $min_ratio = min($cw / $max_x, $ch / $max_y);
+#
+# return $min_ratio;
+#}
+#
+#sub zoom_to_bounding_box {
+# my ($self, $bb) = @_;
+# # Calculate the zoom factor needed to adjust viewport to bounding box.
+# my $zoom = $self->get_zoom_to_bounding_box_factor($bb);
+# if (defined $zoom) {
+# $self->_zoom($zoom);
+# # center view around bounding box center
+# $self->_camera_target($bb->center);
+# $self->on_viewport_changed->() if $self->on_viewport_changed;
+# $self->Resize($self->GetSizeWH) if $self->IsShownOnScreen;
+# $self->Refresh;
+# }
+#}
+#
+#sub zoom_to_bed {
+# my ($self) = @_;
+#
+# if ($self->bed_shape) {
+# $self->zoom_to_bounding_box($self->bed_bounding_box);
+# }
+#}
+#
+#sub zoom_to_volume {
+# my ($self, $volume_idx) = @_;
+#
+# my $volume = $self->volumes->[$volume_idx];
+# my $bb = $volume->transformed_bounding_box;
+# $self->zoom_to_bounding_box($bb);
+#}
+#
+#sub zoom_to_volumes {
+# my ($self) = @_;
+#
+# $self->_apply_zoom_to_volumes_filter(1);
+# $self->zoom_to_bounding_box($self->volumes_bounding_box);
+# $self->_apply_zoom_to_volumes_filter(0);
+#}
+#
+#sub volumes_bounding_box {
+# my ($self) = @_;
+#
+# my $bb = Slic3r::Geometry::BoundingBoxf3->new;
+# foreach my $v (@{$self->volumes}) {
+# $bb->merge($v->transformed_bounding_box) if (! $self->_apply_zoom_to_volumes_filter || $v->zoom_to_volumes);
+# }
+# return $bb;
+#}
+#
+#sub bed_bounding_box {
+# my ($self) = @_;
+#
+# my $bb = Slic3r::Geometry::BoundingBoxf3->new;
+# if ($self->bed_shape) {
+# $bb->merge_point(Slic3r::Pointf3->new(@$_, 0)) for @{$self->bed_shape};
+# }
+# return $bb;
+#}
+#
+#sub max_bounding_box {
+# my ($self) = @_;
+#
+# my $bb = $self->bed_bounding_box;
+# $bb->merge($self->volumes_bounding_box);
+# return $bb;
+#}
+#
+## Used by ObjectCutDialog and ObjectPartsPanel to generate a rectangular ground plane
+## to support the scene objects.
+#sub set_auto_bed_shape {
+# my ($self, $bed_shape) = @_;
+#
+# # draw a default square bed around object center
+# my $max_size = max(@{ $self->volumes_bounding_box->size });
+# my $center = $self->volumes_bounding_box->center;
+# $self->set_bed_shape([
+# [ $center->x - $max_size, $center->y - $max_size ], #--
+# [ $center->x + $max_size, $center->y - $max_size ], #--
+# [ $center->x + $max_size, $center->y + $max_size ], #++
+# [ $center->x - $max_size, $center->y + $max_size ], #++
+# ]);
+# # Set the origin for painting of the coordinate system axes.
+# $self->origin(Slic3r::Pointf->new(@$center[X,Y]));
+#}
+#
+## Set the bed shape to a single closed 2D polygon (array of two element arrays),
+## triangulate the bed and store the triangles into $self->bed_triangles,
+## fills the $self->bed_grid_lines and sets $self->origin.
+## Sets $self->bed_polygon to limit the object placement.
+#sub set_bed_shape {
+# my ($self, $bed_shape) = @_;
+#
+# $self->bed_shape($bed_shape);
+#
+# # triangulate bed
+# my $expolygon = Slic3r::ExPolygon->new([ map [map scale($_), @$_], @$bed_shape ]);
+# my $bed_bb = $expolygon->bounding_box;
+#
+# {
+# my @points = ();
+# foreach my $triangle (@{ $expolygon->triangulate }) {
+# push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$triangle;
+# }
+# $self->bed_triangles(OpenGL::Array->new_list(GL_FLOAT, @points));
+# }
+#
+# {
+# my @polylines = ();
+# for (my $x = $bed_bb->x_min; $x <= $bed_bb->x_max; $x += scale 10) {
+# push @polylines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]);
+# }
+# for (my $y = $bed_bb->y_min; $y <= $bed_bb->y_max; $y += scale 10) {
+# push @polylines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]);
+# }
+# # clip with a slightly grown expolygon because our lines lay on the contours and
+# # may get erroneously clipped
+# my @lines = map Slic3r::Line->new(@$_[0,-1]),
+# @{intersection_pl(\@polylines, [ @{$expolygon->offset(+scaled_epsilon)} ])};
+#
+# # append bed contours
+# push @lines, map @{$_->lines}, @$expolygon;
+#
+# my @points = ();
+# foreach my $line (@lines) {
+# push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$line; #))
+# }
+# $self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points));
+# }
+#
+# # Set the origin for painting of the coordinate system axes.
+# $self->origin(Slic3r::Pointf->new(0,0));
+#
+# $self->bed_polygon(offset_ex([$expolygon->contour], $bed_bb->radius * 1.7, JT_ROUND, scale(0.5))->[0]->contour->clone);
+#}
+#
+#sub deselect_volumes {
+# my ($self) = @_;
+# $_->set_selected(0) for @{$self->volumes};
+#}
+#
+#sub select_volume {
+# my ($self, $volume_idx) = @_;
+#
+# return if ($volume_idx >= scalar(@{$self->volumes}));
+#
+# $self->volumes->[$volume_idx]->set_selected(1)
+# if $volume_idx != -1;
+#}
+#
+#sub SetCuttingPlane {
+# my ($self, $z, $expolygons) = @_;
+#
+# $self->cutting_plane_z($z);
+#
+# # grow slices in order to display them better
+# $expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1);
+#
+# my @verts = ();
+# foreach my $line (map @{$_->lines}, map @$_, @$expolygons) {
+# push @verts, (
+# unscale($line->a->x), unscale($line->a->y), $z, #))
+# unscale($line->b->x), unscale($line->b->y), $z, #))
+# );
+# }
+# $self->cut_lines_vertices(OpenGL::Array->new_list(GL_FLOAT, @verts));
+#}
+#
+## Given an axis and angle, compute quaternion.
+#sub axis_to_quat {
+# my ($ax, $phi) = @_;
+#
+# my $lena = sqrt(reduce { $a + $b } (map { $_ * $_ } @$ax));
+# my @q = map { $_ * (1 / $lena) } @$ax;
+# @q = map { $_ * sin($phi / 2.0) } @q;
+# $q[$#q + 1] = cos($phi / 2.0);
+# return @q;
+#}
+#
+## Project a point on the virtual trackball.
+## If it is inside the sphere, map it to the sphere, if it outside map it
+## to a hyperbola.
+#sub project_to_sphere {
+# my ($r, $x, $y) = @_;
+#
+# my $d = sqrt($x * $x + $y * $y);
+# if ($d < $r * 0.70710678118654752440) { # Inside sphere
+# return sqrt($r * $r - $d * $d);
+# } else { # On hyperbola
+# my $t = $r / 1.41421356237309504880;
+# return $t * $t / $d;
+# }
+#}
+#
+#sub cross {
+# my ($v1, $v2) = @_;
+#
+# return (@$v1[1] * @$v2[2] - @$v1[2] * @$v2[1],
+# @$v1[2] * @$v2[0] - @$v1[0] * @$v2[2],
+# @$v1[0] * @$v2[1] - @$v1[1] * @$v2[0]);
+#}
+#
+## Simulate a track-ball. Project the points onto the virtual trackball,
+## then figure out the axis of rotation, which is the cross product of
+## P1 P2 and O P1 (O is the center of the ball, 0,0,0) Note: This is a
+## deformed trackball-- is a trackball in the center, but is deformed
+## into a hyperbolic sheet of rotation away from the center.
+## It is assumed that the arguments to this routine are in the range
+## (-1.0 ... 1.0).
+#sub trackball {
+# my ($p1x, $p1y, $p2x, $p2y) = @_;
+#
+# if ($p1x == $p2x && $p1y == $p2y) {
+# # zero rotation
+# return (0.0, 0.0, 0.0, 1.0);
+# }
+#
+# # First, figure out z-coordinates for projection of P1 and P2 to
+# # deformed sphere
+# my @p1 = ($p1x, $p1y, project_to_sphere(TRACKBALLSIZE, $p1x, $p1y));
+# my @p2 = ($p2x, $p2y, project_to_sphere(TRACKBALLSIZE, $p2x, $p2y));
+#
+# # axis of rotation (cross product of P1 and P2)
+# my @a = cross(\@p2, \@p1);
+#
+# # Figure out how much to rotate around that axis.
+# my @d = map { $_ * $_ } (map { $p1[$_] - $p2[$_] } 0 .. $#p1);
+# my $t = sqrt(reduce { $a + $b } @d) / (2.0 * TRACKBALLSIZE);
+#
+# # Avoid problems with out-of-control values...
+# $t = 1.0 if ($t > 1.0);
+# $t = -1.0 if ($t < -1.0);
+# my $phi = 2.0 * asin($t);
+#
+# return axis_to_quat(\@a, $phi);
+#}
+#
+## Build a rotation matrix, given a quaternion rotation.
+#sub quat_to_rotmatrix {
+# my ($q) = @_;
+#
+# my @m = ();
+#
+# $m[0] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[2] * @$q[2]);
+# $m[1] = 2.0 * (@$q[0] * @$q[1] - @$q[2] * @$q[3]);
+# $m[2] = 2.0 * (@$q[2] * @$q[0] + @$q[1] * @$q[3]);
+# $m[3] = 0.0;
+#
+# $m[4] = 2.0 * (@$q[0] * @$q[1] + @$q[2] * @$q[3]);
+# $m[5] = 1.0 - 2.0 * (@$q[2] * @$q[2] + @$q[0] * @$q[0]);
+# $m[6] = 2.0 * (@$q[1] * @$q[2] - @$q[0] * @$q[3]);
+# $m[7] = 0.0;
+#
+# $m[8] = 2.0 * (@$q[2] * @$q[0] - @$q[1] * @$q[3]);
+# $m[9] = 2.0 * (@$q[1] * @$q[2] + @$q[0] * @$q[3]);
+# $m[10] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[0] * @$q[0]);
+# $m[11] = 0.0;
+#
+# $m[12] = 0.0;
+# $m[13] = 0.0;
+# $m[14] = 0.0;
+# $m[15] = 1.0;
+#
+# return @m;
+#}
+#
+#sub mulquats {
+# my ($q1, $rq) = @_;
+#
+# return (@$q1[3] * @$rq[0] + @$q1[0] * @$rq[3] + @$q1[1] * @$rq[2] - @$q1[2] * @$rq[1],
+# @$q1[3] * @$rq[1] + @$q1[1] * @$rq[3] + @$q1[2] * @$rq[0] - @$q1[0] * @$rq[2],
+# @$q1[3] * @$rq[2] + @$q1[2] * @$rq[3] + @$q1[0] * @$rq[1] - @$q1[1] * @$rq[0],
+# @$q1[3] * @$rq[3] - @$q1[0] * @$rq[0] - @$q1[1] * @$rq[1] - @$q1[2] * @$rq[2])
+#}
+#
+## Convert the screen space coordinate to an object space coordinate.
+## If the Z screen space coordinate is not provided, a depth buffer value is substituted.
+#sub mouse_to_3d {
+# my ($self, $x, $y, $z) = @_;
+#
+# return unless $self->GetContext;
+# $self->SetCurrent($self->GetContext);
+#
+# my @viewport = glGetIntegerv_p(GL_VIEWPORT); # 4 items
+# my @mview = glGetDoublev_p(GL_MODELVIEW_MATRIX); # 16 items
+# my @proj = glGetDoublev_p(GL_PROJECTION_MATRIX); # 16 items
+#
+# $y = $viewport[3] - $y;
+# $z //= glReadPixels_p($x, $y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT);
+# my @projected = gluUnProject_p($x, $y, $z, @mview, @proj, @viewport);
+# return Slic3r::Pointf3->new(@projected);
+#}
+#
+#sub GetContext {
+# my ($self) = @_;
+# return $self->{context} ||= Wx::GLContext->new($self);
+#}
+#
+#sub SetCurrent {
+# my ($self, $context) = @_;
+# return $self->SUPER::SetCurrent($context);
+#}
+#
+#sub UseVBOs {
+# my ($self) = @_;
+#
+# if (! defined ($self->{use_VBOs})) {
+# my $use_legacy = wxTheApp->{app_config}->get('use_legacy_opengl');
+# if ($use_legacy eq '1') {
+# # Disable OpenGL 2.0 rendering.
+# $self->{use_VBOs} = 0;
+# # Don't enable the layer editing tool.
+# $self->{layer_editing_enabled} = 0;
+# # 2 means failed
+# $self->{layer_editing_initialized} = 2;
+# return 0;
+# }
+# # This is a special path for wxWidgets on GTK, where an OpenGL context is initialized
+# # first when an OpenGL widget is shown for the first time. How ugly.
+# return 0 if (! $self->init && $^O eq 'linux');
+# # Don't use VBOs if anything fails.
+# $self->{use_VBOs} = 0;
+# if ($self->GetContext) {
+# $self->SetCurrent($self->GetContext);
+# Slic3r::GUI::_3DScene::_glew_init;
+# my @gl_version = split(/\./, glGetString(GL_VERSION));
+# $self->{use_VBOs} = int($gl_version[0]) >= 2;
+# # print "UseVBOs $self OpenGL major: $gl_version[0], minor: $gl_version[1]. Use VBOs: ", $self->{use_VBOs}, "\n";
+# }
+# }
+# return $self->{use_VBOs};
+#}
+#
+#sub Resize {
+# my ($self, $x, $y) = @_;
+#
+# return unless $self->GetContext;
+# $self->_dirty(0);
+#
+# $self->SetCurrent($self->GetContext);
+# glViewport(0, 0, $x, $y);
+#
+# $x /= $self->_zoom;
+# $y /= $self->_zoom;
+#
+# glMatrixMode(GL_PROJECTION);
+# glLoadIdentity();
+# if ($self->_camera_type eq 'ortho') {
+# #FIXME setting the size of the box 10x larger than necessary
+# # is only a workaround for an incorrectly set camera.
+# # This workaround harms Z-buffer accuracy!
+## my $depth = 1.05 * $self->max_bounding_box->radius();
+# my $depth = 5.0 * max(@{ $self->max_bounding_box->size });
+# glOrtho(
+# -$x/2, $x/2, -$y/2, $y/2,
+# -$depth, $depth,
+# );
+# } else {
+# die "Invalid camera type: ", $self->_camera_type, "\n" if ($self->_camera_type ne 'perspective');
+# my $bbox_r = $self->max_bounding_box->radius();
+# my $fov = PI * 45. / 180.;
+# my $fov_tan = tan(0.5 * $fov);
+# my $cam_distance = 0.5 * $bbox_r / $fov_tan;
+# $self->_camera_distance($cam_distance);
+# my $nr = $cam_distance - $bbox_r * 1.1;
+# my $fr = $cam_distance + $bbox_r * 1.1;
+# $nr = 1 if ($nr < 1);
+# $fr = $nr + 1 if ($fr < $nr + 1);
+# my $h2 = $fov_tan * $nr;
+# my $w2 = $h2 * $x / $y;
+# glFrustum(-$w2, $w2, -$h2, $h2, $nr, $fr);
+# }
+# glMatrixMode(GL_MODELVIEW);
+#}
+#
+#sub InitGL {
+# my $self = shift;
+#
+# return if $self->init;
+# return unless $self->GetContext;
+# $self->init(1);
+#
+## # This is a special path for wxWidgets on GTK, where an OpenGL context is initialized
+## # first when an OpenGL widget is shown for the first time. How ugly.
+## # In that case the volumes are wainting to be moved to Vertex Buffer Objects
+## # after the OpenGL context is being initialized.
+## $self->volumes->finalize_geometry(1)
+## if ($^O eq 'linux' && $self->UseVBOs);
+#
+# $self->zoom_to_bed;
+#
+# glClearColor(0, 0, 0, 1);
+# glColor3f(1, 0, 0);
+# glEnable(GL_DEPTH_TEST);
+# glClearDepth(1.0);
+# glDepthFunc(GL_LEQUAL);
+# glEnable(GL_CULL_FACE);
+# glEnable(GL_BLEND);
+# glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+#
+# # Set antialiasing/multisampling
+# glDisable(GL_LINE_SMOOTH);
+# glDisable(GL_POLYGON_SMOOTH);
+#
+# # See "GL_MULTISAMPLE and GL_ARRAY_BUFFER_ARB messages on failed launch"
+# # https://github.com/alexrj/Slic3r/issues/4085
+# eval {
+# # Disable the multi sampling by default, so the picking by color will work correctly.
+# glDisable(GL_MULTISAMPLE);
+# };
+# # Disable multi sampling if the eval failed.
+# $self->{can_multisample} = 0 if $@;
+#
+# # ambient lighting
+# glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.3, 0.3, 0.3, 1);
+#
+# glEnable(GL_LIGHTING);
+# glEnable(GL_LIGHT0);
+# glEnable(GL_LIGHT1);
+#
+# # light from camera
+# glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
+# glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.3, 0.3, 0.3, 1);
+# glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 0.2, 0.2, 0.2, 1);
+#
+# # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
+# glShadeModel(GL_SMOOTH);
+#
+## glMaterialfv_p(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 0.5, 0.3, 0.3, 1);
+## glMaterialfv_p(GL_FRONT_AND_BACK, GL_SPECULAR, 1, 1, 1, 1);
+## glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50);
+## glMaterialfv_p(GL_FRONT_AND_BACK, GL_EMISSION, 0.1, 0, 0, 0.9);
+#
+# # A handy trick -- have surface material mirror the color.
+# glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
+# glEnable(GL_COLOR_MATERIAL);
+# glEnable(GL_MULTISAMPLE) if ($self->{can_multisample});
+#
+# if ($self->UseVBOs) {
+# my $shader = new Slic3r::GUI::_3DScene::GLShader;
+## if (! $shader->load($self->_fragment_shader_Phong, $self->_vertex_shader_Phong)) {
+# print "Compilaton of path shader failed: \n" . $shader->last_error . "\n";
+# $shader = undef;
+# } else {
+# $self->{plain_shader} = $shader;
+# }
+# }
+#}
+#
+#sub DestroyGL {
+# my $self = shift;
+# if ($self->GetContext) {
+# $self->SetCurrent($self->GetContext);
+# if ($self->{plain_shader}) {
+# $self->{plain_shader}->release;
+# delete $self->{plain_shader};
+# }
+# if ($self->{layer_height_edit_shader}) {
+# $self->{layer_height_edit_shader}->release;
+# delete $self->{layer_height_edit_shader};
+# }
+# $self->volumes->release_geometry;
+# }
+#}
+#
+#sub Render {
+# my ($self, $dc) = @_;
+#
+# # prevent calling SetCurrent() when window is not shown yet
+# return unless $self->IsShownOnScreen;
+# return unless my $context = $self->GetContext;
+# $self->SetCurrent($context);
+# $self->InitGL;
+#
+# glClearColor(1, 1, 1, 1);
+# glClearDepth(1);
+# glDepthFunc(GL_LESS);
+# glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+#
+# glMatrixMode(GL_MODELVIEW);
+# glLoadIdentity();
+#
+# if (!TURNTABLE_MODE) {
+# # Shift the perspective camera.
+# my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance);
+# glTranslatef(@$camera_pos);
+# }
+#
+# if (TURNTABLE_MODE) {
+# # Turntable mode is enabled by default.
+# glRotatef(-$self->_stheta, 1, 0, 0); # pitch
+# glRotatef($self->_sphi, 0, 0, 1); # yaw
+# } else {
+# my @rotmat = quat_to_rotmatrix($self->quat);
+# glMultMatrixd_p(@rotmat[0..15]);
+# }
+#
+# glTranslatef(@{ $self->_camera_target->negative });
+#
+# # light from above
+# glLightfv_p(GL_LIGHT0, GL_POSITION, -0.5, -0.5, 1, 0);
+# glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.2, 0.2, 0.2, 1);
+# glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.5, 0.5, 0.5, 1);
+#
+# # Head light
+# glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
+#
+# if ($self->enable_picking && !$self->_mouse_dragging) {
+# if (my $pos = $self->_mouse_pos) {
+# # 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.
+# glPushAttrib(GL_ENABLE_BIT);
+# glDisable(GL_MULTISAMPLE) if ($self->{can_multisample});
+# glDisable(GL_LIGHTING);
+# glDisable(GL_BLEND);
+# $self->draw_volumes(1);
+# glPopAttrib();
+# glFlush();
+# my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ];
+# my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256;
+# $self->_hover_volume_idx(undef);
+# $_->set_hover(0) for @{$self->volumes};
+# if ($volume_idx <= $#{$self->volumes}) {
+# $self->_hover_volume_idx($volume_idx);
+#
+# $self->volumes->[$volume_idx]->set_hover(1);
+# my $group_id = $self->volumes->[$volume_idx]->select_group_id;
+# if ($group_id != -1) {
+# $_->set_hover(1) for grep { $_->select_group_id == $group_id } @{$self->volumes};
+# }
+#
+# $self->on_hover->($volume_idx) if $self->on_hover;
+# }
+# glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+# }
+# }
+#
+# # draw fixed background
+# if ($self->background) {
+# glDisable(GL_LIGHTING);
+# glPushMatrix();
+# glLoadIdentity();
+#
+# glMatrixMode(GL_PROJECTION);
+# glPushMatrix();
+# glLoadIdentity();
+#
+# # Draws a bluish bottom to top gradient over the complete screen.
+# glDisable(GL_DEPTH_TEST);
+# glBegin(GL_QUADS);
+# glColor3f(0.0,0.0,0.0);
+# glVertex3f(-1.0,-1.0, 1.0);
+# glVertex3f( 1.0,-1.0, 1.0);
+# glColor3f(10/255,98/255,144/255);
+# glVertex3f( 1.0, 1.0, 1.0);
+# glVertex3f(-1.0, 1.0, 1.0);
+# glEnd();
+# glPopMatrix();
+# glEnable(GL_DEPTH_TEST);
+#
+# glMatrixMode(GL_MODELVIEW);
+# glPopMatrix();
+# glEnable(GL_LIGHTING);
+# }
+#
+# # draw ground and axes
+# glDisable(GL_LIGHTING);
+#
+# # draw ground
+# my $ground_z = GROUND_Z;
+#
+# if ($self->bed_triangles) {
+# glDisable(GL_DEPTH_TEST);
+#
+# glEnable(GL_BLEND);
+# glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+#
+# glEnableClientState(GL_VERTEX_ARRAY);
+# glColor4f(0.8, 0.6, 0.5, 0.4);
+# glNormal3d(0,0,1);
+# glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_triangles->ptr());
+# glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
+# glDisableClientState(GL_VERTEX_ARRAY);
+#
+# # we need depth test for grid, otherwise it would disappear when looking
+# # the object from below
+# glEnable(GL_DEPTH_TEST);
+#
+# # draw grid
+# glLineWidth(3);
+# glColor4f(0.2, 0.2, 0.2, 0.4);
+# glEnableClientState(GL_VERTEX_ARRAY);
+# glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_grid_lines->ptr());
+# glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
+# glDisableClientState(GL_VERTEX_ARRAY);
+#
+# glDisable(GL_BLEND);
+# }
+#
+# my $volumes_bb = $self->volumes_bounding_box;
+#
+# {
+# # draw axes
+# # disable depth testing so that axes are not covered by ground
+# glDisable(GL_DEPTH_TEST);
+# my $origin = $self->origin;
+# my $axis_len = $self->use_plain_shader ? 0.3 * max(@{ $self->bed_bounding_box->size }) : 2 * max(@{ $volumes_bb->size });
+# glLineWidth(2);
+# glBegin(GL_LINES);
+# # draw line for x axis
+# glColor3f(1, 0, 0);
+# glVertex3f(@$origin, $ground_z);
+# glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,,
+# # draw line for y axis
+# glColor3f(0, 1, 0);
+# glVertex3f(@$origin, $ground_z);
+# glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++
+# glEnd();
+# # draw line for Z axis
+# # (re-enable depth test so that axis is correctly shown when objects are behind it)
+# glEnable(GL_DEPTH_TEST);
+# glBegin(GL_LINES);
+# glColor3f(0, 0, 1);
+# glVertex3f(@$origin, $ground_z);
+# glVertex3f(@$origin, $ground_z+$axis_len);
+# glEnd();
+# }
+#
+# glEnable(GL_LIGHTING);
+#
+# # draw objects
+# if (! $self->use_plain_shader) {
+# $self->draw_volumes;
+# } elsif ($self->UseVBOs) {
+# if ($self->enable_picking) {
+# $self->mark_volumes_for_layer_height;
+# $self->volumes->set_print_box($self->bed_bounding_box->x_min, $self->bed_bounding_box->y_min, 0.0, $self->bed_bounding_box->x_max, $self->bed_bounding_box->y_max, $self->{config}->get('max_print_height'));
+# $self->volumes->check_outside_state($self->{config});
+# # do not cull backfaces to show broken geometry, if any
+# glDisable(GL_CULL_FACE);
+# }
+# $self->{plain_shader}->enable if $self->{plain_shader};
+# $self->volumes->render_VBOs;
+# $self->{plain_shader}->disable;
+# glEnable(GL_CULL_FACE) if ($self->enable_picking);
+# } else {
+# # do not cull backfaces to show broken geometry, if any
+# glDisable(GL_CULL_FACE) if ($self->enable_picking);
+# $self->volumes->render_legacy;
+# glEnable(GL_CULL_FACE) if ($self->enable_picking);
+# }
+#
+# if (defined $self->cutting_plane_z) {
+# # draw cutting plane
+# my $plane_z = $self->cutting_plane_z;
+# my $bb = $volumes_bb;
+# glDisable(GL_CULL_FACE);
+# glDisable(GL_LIGHTING);
+# glEnable(GL_BLEND);
+# glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+# glBegin(GL_QUADS);
+# glColor4f(0.8, 0.8, 0.8, 0.5);
+# glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z);
+# glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z);
+# glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z);
+# glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z);
+# glEnd();
+# glEnable(GL_CULL_FACE);
+# glDisable(GL_BLEND);
+#
+# # draw cutting contours
+# glEnableClientState(GL_VERTEX_ARRAY);
+# glLineWidth(2);
+# glColor3f(0, 0, 0);
+# glVertexPointer_c(3, GL_FLOAT, 0, $self->cut_lines_vertices->ptr());
+# glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3);
+# glVertexPointer_c(3, GL_FLOAT, 0, 0);
+# glDisableClientState(GL_VERTEX_ARRAY);
+# }
+#
+# # draw warning message
+# $self->draw_warning;
+#
+# # draw gcode preview legend
+# $self->draw_legend;
+#
+# $self->draw_active_object_annotations;
+#
+# $self->SwapBuffers();
+#}
+#
+#sub draw_volumes {
+# # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking.
+# my ($self, $fakecolor) = @_;
+#
+# # do not cull backfaces to show broken geometry, if any
+# glDisable(GL_CULL_FACE);
+#
+# glEnable(GL_BLEND);
+# glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+#
+# glEnableClientState(GL_VERTEX_ARRAY);
+# glEnableClientState(GL_NORMAL_ARRAY);
+#
+# foreach my $volume_idx (0..$#{$self->volumes}) {
+# my $volume = $self->volumes->[$volume_idx];
+#
+# if ($fakecolor) {
+# # Object picking mode. Render the object with a color encoding the object index.
+# my $r = ($volume_idx & 0x000000FF) >> 0;
+# my $g = ($volume_idx & 0x0000FF00) >> 8;
+# my $b = ($volume_idx & 0x00FF0000) >> 16;
+# glColor4f($r/255.0, $g/255.0, $b/255.0, 1);
+# } elsif ($volume->selected) {
+# glColor4f(@{ &SELECTED_COLOR });
+# } elsif ($volume->hover) {
+# glColor4f(@{ &HOVER_COLOR });
+# } else {
+# glColor4f(@{ $volume->color });
+# }
+#
+# $volume->render;
+# }
+# glDisableClientState(GL_NORMAL_ARRAY);
+# glDisableClientState(GL_VERTEX_ARRAY);
+#
+# glDisable(GL_BLEND);
+# glEnable(GL_CULL_FACE);
+#}
+#
+#sub mark_volumes_for_layer_height {
+# my ($self) = @_;
+#
+# foreach my $volume_idx (0..$#{$self->volumes}) {
+# my $volume = $self->volumes->[$volume_idx];
+# my $object_id = int($volume->select_group_id / 1000000);
+# if ($self->layer_editing_enabled && $volume->selected && $self->{layer_height_edit_shader} &&
+# $volume->has_layer_height_texture && $object_id < $self->{print}->object_count) {
+# $volume->set_layer_height_texture_data($self->{layer_preview_z_texture_id}, $self->{layer_height_edit_shader}->shader_program_id,
+# $self->{print}->get_object($object_id), $self->_variable_layer_thickness_bar_mouse_cursor_z_relative, $self->{layer_height_edit_band_width});
+# } else {
+# $volume->reset_layer_height_texture_data();
+# }
+# }
+#}
+#
+#sub _load_image_set_texture {
+# my ($self, $file_name) = @_;
+# # Load a PNG with an alpha channel.
+# my $img = Wx::Image->new;
+# $img->LoadFile(Slic3r::var($file_name), wxBITMAP_TYPE_PNG);
+# # Get RGB & alpha raw data from wxImage, interleave them into a Perl array.
+# my @rgb = unpack 'C*', $img->GetData();
+# my @alpha = $img->HasAlpha ? unpack 'C*', $img->GetAlpha() : (255) x (int(@rgb) / 3);
+# my $n_pixels = int(@alpha);
+# my @data = (0)x($n_pixels * 4);
+# for (my $i = 0; $i < $n_pixels; $i += 1) {
+# $data[$i*4 ] = $rgb[$i*3];
+# $data[$i*4+1] = $rgb[$i*3+1];
+# $data[$i*4+2] = $rgb[$i*3+2];
+# $data[$i*4+3] = $alpha[$i];
+# }
+# # Initialize a raw bitmap data.
+# my $params = {
+# loaded => 1,
+# valid => $n_pixels > 0,
+# width => $img->GetWidth,
+# height => $img->GetHeight,
+# data => OpenGL::Array->new_list(GL_UNSIGNED_BYTE, @data),
+# texture_id => glGenTextures_p(1)
+# };
+# # Create and initialize a texture with the raw data.
+# glBindTexture(GL_TEXTURE_2D, $params->{texture_id});
+# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
+# glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $params->{width}, $params->{height}, 0, GL_RGBA, GL_UNSIGNED_BYTE, $params->{data}->ptr);
+# glBindTexture(GL_TEXTURE_2D, 0);
+# return $params;
+#}
+#
+#sub _variable_layer_thickness_load_overlay_image {
+# my ($self) = @_;
+# $self->{layer_preview_annotation} = $self->_load_image_set_texture('variable_layer_height_tooltip.png')
+# if (! $self->{layer_preview_annotation}->{loaded});
+# return $self->{layer_preview_annotation}->{valid};
+#}
+#
+#sub _variable_layer_thickness_load_reset_image {
+# my ($self) = @_;
+# $self->{layer_preview_reset_image} = $self->_load_image_set_texture('variable_layer_height_reset.png')
+# if (! $self->{layer_preview_reset_image}->{loaded});
+# return $self->{layer_preview_reset_image}->{valid};
+#}
+#
+## Paint the tooltip.
+#sub _render_image {
+# my ($self, $image, $l, $r, $b, $t) = @_;
+# $self->_render_texture($image->{texture_id}, $l, $r, $b, $t);
+#}
+#
+#sub _render_texture {
+# my ($self, $tex_id, $l, $r, $b, $t) = @_;
+#
+# glColor4f(1.,1.,1.,1.);
+# glDisable(GL_LIGHTING);
+# glEnable(GL_BLEND);
+# glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+# glEnable(GL_TEXTURE_2D);
+# glBindTexture(GL_TEXTURE_2D, $tex_id);
+# glBegin(GL_QUADS);
+# glTexCoord2d(0.,1.); glVertex3f($l, $b, 0);
+# glTexCoord2d(1.,1.); glVertex3f($r, $b, 0);
+# glTexCoord2d(1.,0.); glVertex3f($r, $t, 0);
+# glTexCoord2d(0.,0.); glVertex3f($l, $t, 0);
+# glEnd();
+# glBindTexture(GL_TEXTURE_2D, 0);
+# glDisable(GL_TEXTURE_2D);
+# glDisable(GL_BLEND);
+# glEnable(GL_LIGHTING);
+#}
+#
+#sub draw_active_object_annotations {
+# # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking.
+# my ($self) = @_;
+#
+# return if (! $self->{layer_height_edit_shader} || ! $self->layer_editing_enabled);
+#
+# # Find the selected volume, over which the layer editing is active.
+# my $volume;
+# foreach my $volume_idx (0..$#{$self->volumes}) {
+# my $v = $self->volumes->[$volume_idx];
+# if ($v->selected && $v->has_layer_height_texture) {
+# $volume = $v;
+# last;
+# }
+# }
+# return if (! $volume);
+#
+# # If the active object was not allocated at the Print, go away. This should only be a momentary case between an object addition / deletion
+# # and an update by Platter::async_apply_config.
+# my $object_idx = int($volume->select_group_id / 1000000);
+# return if $object_idx >= $self->{print}->object_count;
+#
+# # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth),
+# # where x, y is the window size divided by $self->_zoom.
+# my ($bar_left, $bar_bottom, $bar_right, $bar_top) = $self->_variable_layer_thickness_bar_rect_viewport;
+# my ($reset_left, $reset_bottom, $reset_right, $reset_top) = $self->_variable_layer_thickness_reset_rect_viewport;
+# my $z_cursor_relative = $self->_variable_layer_thickness_bar_mouse_cursor_z_relative;
+#
+# my $print_object = $self->{print}->get_object($object_idx);
+# my $z_max = $print_object->model_object->bounding_box->z_max;
+#
+# $self->{layer_height_edit_shader}->enable;
+# $self->{layer_height_edit_shader}->set_uniform('z_to_texture_row', $volume->layer_height_texture_z_to_row_id);
+# $self->{layer_height_edit_shader}->set_uniform('z_texture_row_to_normalized', 1. / $volume->layer_height_texture_height);
+# $self->{layer_height_edit_shader}->set_uniform('z_cursor', $z_max * $z_cursor_relative);
+# $self->{layer_height_edit_shader}->set_uniform('z_cursor_band_width', $self->{layer_height_edit_band_width});
+# glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id});
+# glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $volume->layer_height_texture_width, $volume->layer_height_texture_height,
+# 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+# glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2,
+# 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+# glTexSubImage2D_c(GL_TEXTURE_2D, 0, 0, 0, $volume->layer_height_texture_width, $volume->layer_height_texture_height,
+# GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level0);
+# glTexSubImage2D_c(GL_TEXTURE_2D, 1, 0, 0, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2,
+# GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level1);
+#
+# # Render the color bar.
+# glDisable(GL_DEPTH_TEST);
+# # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth),
+# # where x, y is the window size divided by $self->_zoom.
+# glPushMatrix();
+# glLoadIdentity();
+# # Paint the overlay.
+# glBegin(GL_QUADS);
+# glVertex3f($bar_left, $bar_bottom, 0);
+# glVertex3f($bar_right, $bar_bottom, 0);
+# glVertex3f($bar_right, $bar_top, $z_max);
+# glVertex3f($bar_left, $bar_top, $z_max);
+# glEnd();
+# glBindTexture(GL_TEXTURE_2D, 0);
+# $self->{layer_height_edit_shader}->disable;
+#
+# # Paint the tooltip.
+# if ($self->_variable_layer_thickness_load_overlay_image)
+# my $gap = 10/$self->_zoom;
+# my ($l, $r, $b, $t) = ($bar_left - $self->{layer_preview_annotation}->{width}/$self->_zoom - $gap, $bar_left - $gap, $reset_bottom + $self->{layer_preview_annotation}->{height}/$self->_zoom + $gap, $reset_bottom + $gap);
+# $self->_render_image($self->{layer_preview_annotation}, $l, $r, $t, $b);
+# }
+#
+# # Paint the reset button.
+# if ($self->_variable_layer_thickness_load_reset_image) {
+# $self->_render_image($self->{layer_preview_reset_image}, $reset_left, $reset_right, $reset_bottom, $reset_top);
+# }
+#
+# # Paint the graph.
+# #FIXME show some kind of legend.
+# my $max_z = unscale($print_object->size->z);
+# my $profile = $print_object->model_object->layer_height_profile;
+# my $layer_height = $print_object->config->get('layer_height');
+# my $layer_height_max = 10000000000.;
+# {
+# # Get a maximum layer height value.
+# #FIXME This is a duplicate code of Slicing.cpp.
+# my $nozzle_diameters = $print_object->print->config->get('nozzle_diameter');
+# my $layer_heights_min = $print_object->print->config->get('min_layer_height');
+# my $layer_heights_max = $print_object->print->config->get('max_layer_height');
+# for (my $i = 0; $i < scalar(@{$nozzle_diameters}); $i += 1) {
+# my $lh_min = ($layer_heights_min->[$i] == 0.) ? 0.07 : max(0.01, $layer_heights_min->[$i]);
+# my $lh_max = ($layer_heights_max->[$i] == 0.) ? (0.75 * $nozzle_diameters->[$i]) : $layer_heights_max->[$i];
+# $layer_height_max = min($layer_height_max, max($lh_min, $lh_max));
+# }
+# }
+# # Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region.
+# $layer_height_max *= 1.12;
+# # Baseline
+# glColor3f(0., 0., 0.);
+# glBegin(GL_LINE_STRIP);
+# glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / $layer_height_max, $bar_bottom);
+# glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / $layer_height_max, $bar_top);
+# glEnd();
+# # Curve
+# glColor3f(0., 0., 1.);
+# glBegin(GL_LINE_STRIP);
+# for (my $i = 0; $i < int(@{$profile}); $i += 2) {
+# my $z = $profile->[$i];
+# my $h = $profile->[$i+1];
+# glVertex3f($bar_left + $h * ($bar_right - $bar_left) / $layer_height_max, $bar_bottom + $z * ($bar_top - $bar_bottom) / $max_z, $z);
+# }
+# glEnd();
+# # Revert the matrices.
+# glPopMatrix();
+# glEnable(GL_DEPTH_TEST);
+#}
+#
+#sub draw_legend {
+# my ($self) = @_;
+#
+# if (!$self->_legend_enabled) {
+# return;
+# }
+#
+# # If the legend texture has not been loaded into the GPU, do it now.
+# my $tex_id = Slic3r::GUI::_3DScene::finalize_legend_texture;
+# if ($tex_id > 0)
+# {
+# my $tex_w = Slic3r::GUI::_3DScene::get_legend_texture_width;
+# my $tex_h = Slic3r::GUI::_3DScene::get_legend_texture_height;
+# if (($tex_w > 0) && ($tex_h > 0))
+# {
+# glDisable(GL_DEPTH_TEST);
+# glPushMatrix();
+# glLoadIdentity();
+#
+# my ($cw, $ch) = $self->GetSizeWH;
+#
+# my $l = (-0.5 * $cw) / $self->_zoom;
+# my $t = (0.5 * $ch) / $self->_zoom;
+# my $r = $l + $tex_w / $self->_zoom;
+# my $b = $t - $tex_h / $self->_zoom;
+# $self->_render_texture($tex_id, $l, $r, $b, $t);
+#
+# glPopMatrix();
+# glEnable(GL_DEPTH_TEST);
+# }
+# }
+#}
+#
+#sub draw_warning {
+# my ($self) = @_;
+#
+# if (!$self->_warning_enabled) {
+# return;
+# }
+#
+# # If the warning texture has not been loaded into the GPU, do it now.
+# my $tex_id = Slic3r::GUI::_3DScene::finalize_warning_texture;
+# if ($tex_id > 0)
+# {
+# my $tex_w = Slic3r::GUI::_3DScene::get_warning_texture_width;
+# my $tex_h = Slic3r::GUI::_3DScene::get_warning_texture_height;
+# if (($tex_w > 0) && ($tex_h > 0))
+# {
+# glDisable(GL_DEPTH_TEST);
+# glPushMatrix();
+# glLoadIdentity();
+#
+# my ($cw, $ch) = $self->GetSizeWH;
+#
+# my $l = (-0.5 * $tex_w) / $self->_zoom;
+# my $t = (-0.5 * $ch + $tex_h) / $self->_zoom;
+# my $r = $l + $tex_w / $self->_zoom;
+# my $b = $t - $tex_h / $self->_zoom;
+# $self->_render_texture($tex_id, $l, $r, $b, $t);
+#
+# glPopMatrix();
+# glEnable(GL_DEPTH_TEST);
+# }
+# }
+#}
+#
+#sub update_volumes_colors_by_extruder {
+# my ($self, $config) = @_;
+# $self->volumes->update_colors_by_extruder($config);
+#}
+#
+#sub opengl_info
+#{
+# my ($self, %params) = @_;
+# my %tag = Slic3r::tags($params{format});
+#
+# my $gl_version = glGetString(GL_VERSION);
+# my $gl_vendor = glGetString(GL_VENDOR);
+# my $gl_renderer = glGetString(GL_RENDERER);
+# my $glsl_version = glGetString(GL_SHADING_LANGUAGE_VERSION);
+#
+# my $out = '';
+# $out .= "$tag{h2start}OpenGL installation$tag{h2end}$tag{eol}";
+# $out .= " $tag{bstart}Using POGL$tag{bend} v$OpenGL::BUILD_VERSION$tag{eol}";
+# $out .= " $tag{bstart}GL version: $tag{bend}${gl_version}$tag{eol}";
+# $out .= " $tag{bstart}vendor: $tag{bend}${gl_vendor}$tag{eol}";
+# $out .= " $tag{bstart}renderer: $tag{bend}${gl_renderer}$tag{eol}";
+# $out .= " $tag{bstart}GLSL version: $tag{bend}${glsl_version}$tag{eol}";
+#
+# # Check for other OpenGL extensions
+# $out .= "$tag{h2start}Installed extensions (* implemented in the module):$tag{h2end}$tag{eol}";
+# my $extensions = glGetString(GL_EXTENSIONS);
+# my @extensions = split(' ',$extensions);
+# foreach my $ext (sort @extensions) {
+# my $stat = glpCheckExtension($ext);
+# $out .= sprintf("%s ${ext}$tag{eol}", $stat?' ':'*');
+# $out .= sprintf(" ${stat}$tag{eol}") if ($stat && $stat !~ m|^$ext |);
+# }
+#
+# return $out;
+#}
+#
+#sub _report_opengl_state
+#{
+# my ($self, $comment) = @_;
+# my $err = glGetError();
+# return 0 if ($err == 0);
+#
+# # gluErrorString() hangs. Don't use it.
+## my $errorstr = gluErrorString();
+# my $errorstr = '';
+# if ($err == 0x0500) {
+# $errorstr = 'GL_INVALID_ENUM';
+# } elsif ($err == GL_INVALID_VALUE) {
+# $errorstr = 'GL_INVALID_VALUE';
+# } elsif ($err == GL_INVALID_OPERATION) {
+# $errorstr = 'GL_INVALID_OPERATION';
+# } elsif ($err == GL_STACK_OVERFLOW) {
+# $errorstr = 'GL_STACK_OVERFLOW';
+# } elsif ($err == GL_OUT_OF_MEMORY) {
+# $errorstr = 'GL_OUT_OF_MEMORY';
+# } else {
+# $errorstr = 'unknown';
+# }
+# if (defined($comment)) {
+# printf("OpenGL error at %s, nr %d (0x%x): %s\n", $comment, $err, $err, $errorstr);
+# } else {
+# printf("OpenGL error nr %d (0x%x): %s\n", $err, $err, $errorstr);
+# }
+#}
+#
+#sub _vertex_shader_Gouraud {
+# return <<'VERTEX';
+##version 110
+#
+##define INTENSITY_CORRECTION 0.6
+#
+#// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
+#const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+##define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
+##define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
+##define LIGHT_TOP_SHININESS 20.0
+#
+#// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
+#const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
+##define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
+#//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
+#//#define LIGHT_FRONT_SHININESS 5.0
+#
+##define INTENSITY_AMBIENT 0.3
+#
+#const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+#
+#struct PrintBoxDetection
+#{
+# vec3 min;
+# vec3 max;
+# // xyz contains the offset, if w == 1.0 detection needs to be performed
+# vec4 volume_origin;
+#};
+#
+#uniform PrintBoxDetection print_box;
+#
+#// x = tainted, y = specular;
+#varying vec2 intensity;
+#
+#varying vec3 delta_box_min;
+#varying vec3 delta_box_max;
+#
+#void main()
+#{
+# // First transform the normal into camera space and normalize the result.
+# vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
+#
+# // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+# // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+# float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+#
+# intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+# intensity.y = 0.0;
+#
+# if (NdotL > 0.0)
+# intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+#
+# // Perform the same lighting calculation for the 2nd light source (no specular applied).
+# NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+# intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+#
+# // compute deltas for out of print volume detection (world coordinates)
+# if (print_box.volume_origin.w == 1.0)
+# {
+# vec3 v = gl_Vertex.xyz + print_box.volume_origin.xyz;
+# delta_box_min = v - print_box.min;
+# delta_box_max = v - print_box.max;
+# }
+# else
+# {
+# delta_box_min = ZERO;
+# delta_box_max = ZERO;
+# }
+#
+# gl_Position = ftransform();
+#}
+#
+#VERTEX
+#}
+#
+#sub _fragment_shader_Gouraud {
+# return <<'FRAGMENT';
+##version 110
+#
+#const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+#
+#// x = tainted, y = specular;
+#varying vec2 intensity;
+#
+#varying vec3 delta_box_min;
+#varying vec3 delta_box_max;
+#
+#uniform vec4 uniform_color;
+#
+#void main()
+#{
+# // if the fragment is outside the print volume -> use darker color
+# vec3 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(uniform_color.rgb, ZERO, 0.3333) : uniform_color.rgb;
+# gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + color * intensity.x, uniform_color.a);
+#}
+#
+#FRAGMENT
+#}
+#
+#sub _vertex_shader_Phong {
+# return <<'VERTEX';
+##version 110
+#
+#varying vec3 normal;
+#varying vec3 eye;
+#void main(void)
+#{
+# eye = normalize(vec3(gl_ModelViewMatrix * gl_Vertex));
+# normal = normalize(gl_NormalMatrix * gl_Normal);
+# gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+#}
+#VERTEX
+#}
+#
+#sub _fragment_shader_Phong {
+# return <<'FRAGMENT';
+##version 110
+#
+##define INTENSITY_CORRECTION 0.7
+#
+##define LIGHT_TOP_DIR -0.6/1.31, 0.6/1.31, 1./1.31
+##define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
+##define LIGHT_TOP_SPECULAR (0.5 * INTENSITY_CORRECTION)
+#//#define LIGHT_TOP_SHININESS 50.
+##define LIGHT_TOP_SHININESS 10.
+#
+##define LIGHT_FRONT_DIR 1./1.43, 0.2/1.43, 1./1.43
+##define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
+##define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
+##define LIGHT_FRONT_SHININESS 50.
+#
+##define INTENSITY_AMBIENT 0.0
+#
+#varying vec3 normal;
+#varying vec3 eye;
+#uniform vec4 uniform_color;
+#void main() {
+#
+# float intensity_specular = 0.;
+# float intensity_tainted = 0.;
+# float intensity = max(dot(normal,vec3(LIGHT_TOP_DIR)), 0.0);
+# // if the vertex is lit compute the specular color
+# if (intensity > 0.0) {
+# intensity_tainted = LIGHT_TOP_DIFFUSE * intensity;
+# // compute the half vector
+# vec3 h = normalize(vec3(LIGHT_TOP_DIR) + eye);
+# // compute the specular term into spec
+# intensity_specular = LIGHT_TOP_SPECULAR * pow(max(dot(h, normal), 0.0), LIGHT_TOP_SHININESS);
+# }
+# intensity = max(dot(normal,vec3(LIGHT_FRONT_DIR)), 0.0);
+# // if the vertex is lit compute the specular color
+# if (intensity > 0.0) {
+# intensity_tainted += LIGHT_FRONT_DIFFUSE * intensity;
+# // compute the half vector
+#// vec3 h = normalize(vec3(LIGHT_FRONT_DIR) + eye);
+# // compute the specular term into spec
+#// intensity_specular += LIGHT_FRONT_SPECULAR * pow(max(dot(h,normal), 0.0), LIGHT_FRONT_SHININESS);
+# }
+#
+# gl_FragColor = max(
+# vec4(intensity_specular, intensity_specular, intensity_specular, 0.) + uniform_color * intensity_tainted,
+# INTENSITY_AMBIENT * uniform_color);
+# gl_FragColor.a = uniform_color.a;
+#}
+#FRAGMENT
+#}
+#
+#sub _vertex_shader_variable_layer_height {
+# return <<'VERTEX';
+##version 110
+#
+##define INTENSITY_CORRECTION 0.6
+#
+#const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+##define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
+##define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
+##define LIGHT_TOP_SHININESS 20.0
+#
+#const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
+##define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
+#//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
+#//#define LIGHT_FRONT_SHININESS 5.0
+#
+##define INTENSITY_AMBIENT 0.3
+#
+#// x = tainted, y = specular;
+#varying vec2 intensity;
+#
+#varying float object_z;
+#
+#void main()
+#{
+# // First transform the normal into camera space and normalize the result.
+# vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
+#
+# // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+# // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+# float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+#
+# intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+# intensity.y = 0.0;
+#
+# if (NdotL > 0.0)
+# intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+#
+# // Perform the same lighting calculation for the 2nd light source (no specular)
+# NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+#
+# intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+#
+# // Scaled to widths of the Z texture.
+# object_z = gl_Vertex.z;
+#
+# gl_Position = ftransform();
+#}
+#
+#VERTEX
+#}
+#
+#sub _fragment_shader_variable_layer_height {
+# return <<'FRAGMENT';
+##version 110
+#
+##define M_PI 3.1415926535897932384626433832795
+#
+#// 2D texture (1D texture split by the rows) of color along the object Z axis.
+#uniform sampler2D z_texture;
+#// Scaling from the Z texture rows coordinate to the normalized texture row coordinate.
+#uniform float z_to_texture_row;
+#uniform float z_texture_row_to_normalized;
+#uniform float z_cursor;
+#uniform float z_cursor_band_width;
+#
+#// x = tainted, y = specular;
+#varying vec2 intensity;
+#
+#varying float object_z;
+#
+#void main()
+#{
+# float object_z_row = z_to_texture_row * object_z;
+# // Index of the row in the texture.
+# float z_texture_row = floor(object_z_row);
+# // Normalized coordinate from 0. to 1.
+# float z_texture_col = object_z_row - z_texture_row;
+# float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25;
+# // Calculate level of detail from the object Z coordinate.
+# // This makes the slowly sloping surfaces to be show with high detail (with stripes),
+# // and the vertical surfaces to be shown with low detail (no stripes)
+# float z_in_cells = object_z_row * 190.;
+# // Gradient of Z projected on the screen.
+# float dx_vtc = dFdx(z_in_cells);
+# float dy_vtc = dFdy(z_in_cells);
+# float lod = clamp(0.5 * log2(max(dx_vtc*dx_vtc, dy_vtc*dy_vtc)), 0., 1.);
+# // Sample the Z texture. Texture coordinates are normalized to <0, 1>.
+# vec4 color =
+# mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.),
+# texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod);
+#
+# // Mix the final color.
+# gl_FragColor =
+# vec4(intensity.y, intensity.y, intensity.y, 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend);
+#}
+#
+#FRAGMENT
+#}
+#===================================================================================================================================
# The 3D canvas to display objects and tool paths.
package Slic3r::GUI::3DScene;
use base qw(Slic3r::GUI::3DScene::Base);
-use OpenGL qw(:glconstants :gluconstants :glufunctions);
-use List::Util qw(first min max);
-use Slic3r::Geometry qw(scale unscale epsilon);
-use Slic3r::Print::State ':steps';
-
-__PACKAGE__->mk_accessors(qw(
- color_by
- select_by
- drag_by
-));
+#===================================================================================================================================
+#use OpenGL qw(:glconstants :gluconstants :glufunctions);
+#use List::Util qw(first min max);
+#use Slic3r::Geometry qw(scale unscale epsilon);
+#use Slic3r::Print::State ':steps';
+#===================================================================================================================================
+
+#===================================================================================================================================
+#__PACKAGE__->mk_accessors(qw(
+# color_by
+# select_by
+# drag_by
+#));
+#===================================================================================================================================
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
- $self->color_by('volume'); # object | volume
- $self->select_by('object'); # object | volume | instance
- $self->drag_by('instance'); # object | instance
+#===================================================================================================================================
+# $self->color_by('volume'); # object | volume
+# $self->select_by('object'); # object | volume | instance
+# $self->drag_by('instance'); # object | instance
+#===================================================================================================================================
return $self;
}
-sub load_object {
- my ($self, $model, $print, $obj_idx, $instance_idxs) = @_;
-
- $self->SetCurrent($self->GetContext) if $self->UseVBOs;
-
- my $model_object;
- if ($model->isa('Slic3r::Model::Object')) {
- $model_object = $model;
- $model = $model_object->model;
- $obj_idx = 0;
- } else {
- $model_object = $model->get_object($obj_idx);
- }
-
- $instance_idxs ||= [0..$#{$model_object->instances}];
- my $volume_indices = $self->volumes->load_object(
- $model_object, $obj_idx, $instance_idxs, $self->color_by, $self->select_by, $self->drag_by,
- $self->UseVBOs);
- return @{$volume_indices};
-}
-
-# Create 3D thick extrusion lines for a skirt and brim.
-# Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes.
-sub load_print_toolpaths {
- my ($self, $print, $colors) = @_;
-
- $self->SetCurrent($self->GetContext) if $self->UseVBOs;
- Slic3r::GUI::_3DScene::_load_print_toolpaths($print, $self->volumes, $colors, $self->UseVBOs)
- if ($print->step_done(STEP_SKIRT) && $print->step_done(STEP_BRIM));
-}
-
-# Create 3D thick extrusion lines for object forming extrusions.
-# Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
-# one for perimeters, one for infill and one for supports.
-sub load_print_object_toolpaths {
- my ($self, $object, $colors) = @_;
-
- $self->SetCurrent($self->GetContext) if $self->UseVBOs;
- Slic3r::GUI::_3DScene::_load_print_object_toolpaths($object, $self->volumes, $colors, $self->UseVBOs);
-}
-
-# Create 3D thick extrusion lines for wipe tower extrusions.
-sub load_wipe_tower_toolpaths {
- my ($self, $print, $colors) = @_;
-
- $self->SetCurrent($self->GetContext) if $self->UseVBOs;
- Slic3r::GUI::_3DScene::_load_wipe_tower_toolpaths($print, $self->volumes, $colors, $self->UseVBOs)
- if ($print->step_done(STEP_WIPE_TOWER));
-}
-
-sub load_gcode_preview {
- my ($self, $print, $gcode_preview_data, $colors) = @_;
-
- $self->SetCurrent($self->GetContext) if $self->UseVBOs;
- Slic3r::GUI::_3DScene::load_gcode_preview($print, $gcode_preview_data, $self->volumes, $colors, $self->UseVBOs);
-}
-
-sub set_toolpaths_range {
- my ($self, $min_z, $max_z) = @_;
- $self->volumes->set_range($min_z, $max_z);
-}
-
-sub reset_legend_texture {
- Slic3r::GUI::_3DScene::reset_legend_texture();
-}
-
-sub get_current_print_zs {
- my ($self, $active_only) = @_;
- return $self->volumes->get_current_print_zs($active_only);
-}
+#==============================================================================================================================
+#sub load_object {
+# my ($self, $model, $print, $obj_idx, $instance_idxs) = @_;
+#
+# $self->SetCurrent($self->GetContext) if $useVBOs;
+#
+# my $model_object;
+# if ($model->isa('Slic3r::Model::Object')) {
+# $model_object = $model;
+# $model = $model_object->model;
+# $obj_idx = 0;
+# } else {
+# $model_object = $model->get_object($obj_idx);
+# }
+#
+# $instance_idxs ||= [0..$#{$model_object->instances}];
+# my $volume_indices = $self->volumes->load_object(
+# $model_object, $obj_idx, $instance_idxs, $self->color_by, $self->select_by, $self->drag_by,
+# $self->UseVBOs);
+# return @{$volume_indices};
+#}
+#
+## Create 3D thick extrusion lines for a skirt and brim.
+## Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes.
+#sub load_print_toolpaths {
+# my ($self, $print, $colors) = @_;
+#
+# $self->SetCurrent($self->GetContext) if $self->UseVBOs;
+# Slic3r::GUI::_3DScene::_load_print_toolpaths($print, $self->volumes, $colors, $self->UseVBOs)
+# if ($print->step_done(STEP_SKIRT) && $print->step_done(STEP_BRIM));
+#}
+#
+## Create 3D thick extrusion lines for object forming extrusions.
+## Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
+## one for perimeters, one for infill and one for supports.
+#sub load_print_object_toolpaths {
+# my ($self, $object, $colors) = @_;
+#
+# $self->SetCurrent($self->GetContext) if $self->UseVBOs;
+# Slic3r::GUI::_3DScene::_load_print_object_toolpaths($object, $self->volumes, $colors, $self->UseVBOs);
+#}
+#
+## Create 3D thick extrusion lines for wipe tower extrusions.
+#sub load_wipe_tower_toolpaths {
+# my ($self, $print, $colors) = @_;
+#
+# $self->SetCurrent($self->GetContext) if $self->UseVBOs;
+# Slic3r::GUI::_3DScene::_load_wipe_tower_toolpaths($print, $self->volumes, $colors, $self->UseVBOs)
+# if ($print->step_done(STEP_WIPE_TOWER));
+#}
+#
+#sub load_gcode_preview {
+# my ($self, $print, $gcode_preview_data, $colors) = @_;
+#
+# $self->SetCurrent($self->GetContext) if $self->UseVBOs;
+# Slic3r::GUI::_3DScene::load_gcode_preview($print, $gcode_preview_data, $self->volumes, $colors, $self->UseVBOs);
+#}
+#
+#sub set_toolpaths_range {
+# my ($self, $min_z, $max_z) = @_;
+# $self->volumes->set_range($min_z, $max_z);
+#}
+#
+#sub reset_legend_texture {
+# Slic3r::GUI::_3DScene::reset_legend_texture();
+#}
+#
+#sub get_current_print_zs {
+# my ($self, $active_only) = @_;
+# return $self->volumes->get_current_print_zs($active_only);
+#}
+#==============================================================================================================================
1;
diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm
index 2b93351f8..910b86dd8 100644
--- a/lib/Slic3r/GUI/MainFrame.pm
+++ b/lib/Slic3r/GUI/MainFrame.pm
@@ -28,9 +28,9 @@ our $PRESETS_CHANGED_EVENT = Wx::NewEventType;
sub new {
my ($class, %params) = @_;
-
+
my $self = $class->SUPER::new(undef, -1, $Slic3r::FORK_NAME . ' - ' . $Slic3r::VERSION, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE);
- Slic3r::GUI::set_main_frame($self);
+ Slic3r::GUI::set_main_frame($self);
if ($^O eq 'MSWin32') {
# Load the icon either from the exe, or from the ico file.
my $iconfile = Slic3r::decode_path($FindBin::Bin) . '\slic3r.exe';
@@ -39,7 +39,7 @@ sub new {
} else {
$self->SetIcon(Wx::Icon->new(Slic3r::var("Slic3r_128px.png"), wxBITMAP_TYPE_PNG));
}
-
+
# store input params
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
$self->{no_controller} = $params{no_controller};
@@ -47,7 +47,7 @@ sub new {
$self->{loaded} = 0;
$self->{lang_ch_event} = $params{lang_ch_event};
$self->{preferences_event} = $params{preferences_event};
-
+
# initialize tabpanel and menubar
$self->_init_tabpanel;
$self->_init_menubar;
@@ -63,7 +63,7 @@ sub new {
$self->SetStatusBar($self->{statusbar});
$self->{loaded} = 1;
-
+
# initialize layout
{
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
@@ -90,6 +90,8 @@ sub new {
# Save the slic3r.ini. Usually the ini file is saved from "on idle" callback,
# but in rare cases it may not have been called yet.
wxTheApp->{app_config}->save;
+ $self->{plater}->{print} = undef if($self->{plater});
+ Slic3r::GUI::_3DScene::remove_all_canvases();
# propagate event
$event->Skip;
});
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 068944d6a..0ac24664c 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -78,19 +78,19 @@ sub new {
my $on_select_object = sub {
my ($obj_idx) = @_;
# Ignore the special objects (the wipe tower proxy and such).
- $self->select_object((defined($obj_idx) && $obj_idx < 1000) ? $obj_idx : undef);
+ $self->select_object((defined($obj_idx) && $obj_idx >= 0 && $obj_idx < 1000) ? $obj_idx : undef);
};
my $on_double_click = sub {
$self->object_settings_dialog if $self->selected_object;
};
my $on_right_click = sub {
- my ($canvas, $click_pos) = @_;
-
+ my ($canvas, $click_pos_x, $click_pos_y) = @_;
+
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
my $menu = $self->object_menu;
- $canvas->PopupMenu($menu, $click_pos);
+ $canvas->PopupMenu($menu, $click_pos_x, $click_pos_y);
$menu->Destroy;
};
my $on_instances_moved = sub {
@@ -105,32 +105,65 @@ sub new {
$self->{btn_print}->Enable($enable);
$self->{btn_send_gcode}->Enable($enable);
};
+
+ # callback to react to gizmo scale
+ my $on_gizmo_scale_uniformly = sub {
+ my ($scale) = @_;
+
+ my ($obj_idx, $object) = $self->selected_object;
+ return if !defined $obj_idx;
+
+ my $model_object = $self->{model}->objects->[$obj_idx];
+ my $model_instance = $model_object->instances->[0];
+
+ my $variation = $scale / $model_instance->scaling_factor;
+ #FIXME Scale the layer height profile?
+ foreach my $range (@{ $model_object->layer_height_ranges }) {
+ $range->[0] *= $variation;
+ $range->[1] *= $variation;
+ }
+ $_->set_scaling_factor($scale) for @{ $model_object->instances };
+ $object->transform_thumbnail($self->{model}, $obj_idx);
+
+ #update print and start background processing
+ $self->stop_background_process;
+ $self->{print}->add_model_object($model_object, $obj_idx);
+
+ $self->selection_changed(1); # refresh info (size, volume etc.)
+ $self->update;
+ $self->schedule_background_process;
+ };
# Initialize 3D plater
if ($Slic3r::GUI::have_OpenGL) {
$self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{print}, $self->{config});
$self->{preview_notebook}->AddPage($self->{canvas3D}, L('3D'));
- $self->{canvas3D}->set_on_select_object($on_select_object);
- $self->{canvas3D}->set_on_double_click($on_double_click);
- $self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); });
- $self->{canvas3D}->set_on_arrange(sub { $self->arrange });
- $self->{canvas3D}->set_on_rotate_object_left(sub { $self->rotate(-45, Z, 'relative') });
- $self->{canvas3D}->set_on_rotate_object_right(sub { $self->rotate( 45, Z, 'relative') });
- $self->{canvas3D}->set_on_scale_object_uniformly(sub { $self->changescale(undef) });
- $self->{canvas3D}->set_on_increase_objects(sub { $self->increase() });
- $self->{canvas3D}->set_on_decrease_objects(sub { $self->decrease() });
- $self->{canvas3D}->set_on_remove_object(sub { $self->remove() });
- $self->{canvas3D}->set_on_instances_moved($on_instances_moved);
- $self->{canvas3D}->set_on_enable_action_buttons($enable_action_buttons);
- $self->{canvas3D}->use_plain_shader(1);
- $self->{canvas3D}->set_on_wipe_tower_moved(sub {
- my ($new_pos_3f) = @_;
+ Slic3r::GUI::_3DScene::register_on_select_object_callback($self->{canvas3D}, $on_select_object);
+ Slic3r::GUI::_3DScene::register_on_double_click_callback($self->{canvas3D}, $on_double_click);
+ Slic3r::GUI::_3DScene::register_on_right_click_callback($self->{canvas3D}, sub { $on_right_click->($self->{canvas3D}, @_); });
+ Slic3r::GUI::_3DScene::register_on_arrange_callback($self->{canvas3D}, sub { $self->arrange });
+ Slic3r::GUI::_3DScene::register_on_rotate_object_left_callback($self->{canvas3D}, sub { $self->rotate(-45, Z, 'relative') });
+ Slic3r::GUI::_3DScene::register_on_rotate_object_right_callback($self->{canvas3D}, sub { $self->rotate( 45, Z, 'relative') });
+ Slic3r::GUI::_3DScene::register_on_scale_object_uniformly_callback($self->{canvas3D}, sub { $self->changescale(undef) });
+ Slic3r::GUI::_3DScene::register_on_increase_objects_callback($self->{canvas3D}, sub { $self->increase() });
+ Slic3r::GUI::_3DScene::register_on_decrease_objects_callback($self->{canvas3D}, sub { $self->decrease() });
+ Slic3r::GUI::_3DScene::register_on_remove_object_callback($self->{canvas3D}, sub { $self->remove() });
+ Slic3r::GUI::_3DScene::register_on_instance_moved_callback($self->{canvas3D}, $on_instances_moved);
+ Slic3r::GUI::_3DScene::register_on_enable_action_buttons_callback($self->{canvas3D}, $enable_action_buttons);
+ Slic3r::GUI::_3DScene::register_on_gizmo_scale_uniformly_callback($self->{canvas3D}, $on_gizmo_scale_uniformly);
+ Slic3r::GUI::_3DScene::enable_gizmos($self->{canvas3D}, 1);
+ Slic3r::GUI::_3DScene::enable_shader($self->{canvas3D}, 1);
+ Slic3r::GUI::_3DScene::enable_force_zoom_to_bed($self->{canvas3D}, 1);
+
+ Slic3r::GUI::_3DScene::register_on_wipe_tower_moved_callback($self->{canvas3D}, sub {
+ my ($x, $y) = @_;
my $cfg = Slic3r::Config->new;
- $cfg->set('wipe_tower_x', $new_pos_3f->x);
- $cfg->set('wipe_tower_y', $new_pos_3f->y);
+ $cfg->set('wipe_tower_x', $x);
+ $cfg->set('wipe_tower_y', $y);
$self->GetFrame->{options_tabs}{print}->load_config($cfg);
});
- $self->{canvas3D}->set_on_model_update(sub {
+
+ Slic3r::GUI::_3DScene::register_on_model_update_callback($self->{canvas3D}, sub {
if (wxTheApp->{app_config}->get("background_processing")) {
$self->schedule_background_process;
} else {
@@ -138,9 +171,8 @@ sub new {
$self->{"print_info_box_show"}->(0);
}
});
- $self->{canvas3D}->on_viewport_changed(sub {
- $self->{preview3D}->canvas->set_viewport_from_scene($self->{canvas3D});
- });
+
+ Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{canvas3D}, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{preview3D}->canvas, $self->{canvas3D}); });
}
# Initialize 2D preview canvas
@@ -154,9 +186,8 @@ sub new {
# Initialize 3D toolpaths preview
if ($Slic3r::GUI::have_OpenGL) {
$self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print}, $self->{gcode_preview_data}, $self->{config});
- $self->{preview3D}->canvas->on_viewport_changed(sub {
- $self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas);
- });
+ Slic3r::GUI::_3DScene::set_active($self->{preview3D}->canvas, 0);
+ Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{preview3D}->canvas, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{canvas3D}, $self->{preview3D}->canvas); });
$self->{preview_notebook}->AddPage($self->{preview3D}, L('Preview'));
$self->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1;
}
@@ -171,13 +202,25 @@ sub new {
my $preview = $self->{preview_notebook}->GetCurrentPage;
if ($preview == $self->{preview3D})
{
- $self->{preview3D}->canvas->set_legend_enabled(1);
+ Slic3r::GUI::_3DScene::set_active($self->{preview3D}->canvas, 1);
+ Slic3r::GUI::_3DScene::set_active($self->{canvas3D}, 0);
+ Slic3r::GUI::_3DScene::enable_legend_texture($self->{preview3D}->canvas, 1);
$self->{preview3D}->load_print(1);
} else {
- $self->{preview3D}->canvas->set_legend_enabled(0);
+ Slic3r::GUI::_3DScene::enable_legend_texture($self->{preview3D}->canvas, 0);
}
- $preview->OnActivate if $preview->can('OnActivate');
+ if ($preview == $self->{canvas3D}) {
+ Slic3r::GUI::_3DScene::set_active($self->{canvas3D}, 1);
+ Slic3r::GUI::_3DScene::set_active($self->{preview3D}->canvas, 0);
+ if (Slic3r::GUI::_3DScene::is_reload_delayed($self->{canvas3D})) {
+ my $selections = $self->collect_selections;
+ Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections);
+ Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1);
+ }
+ } else {
+ $preview->OnActivate if $preview->can('OnActivate');
+ }
});
# toolbar for object manipulation
@@ -314,7 +357,7 @@ sub new {
EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog });
EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog });
EVT_TOOL($self, TB_LAYER_EDITING, sub {
- my $state = $self->{canvas3D}->layer_editing_enabled;
+ my $state = Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D});
$self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, ! $state);
$self->on_layer_editing_toggled(! $state);
});
@@ -369,11 +412,11 @@ sub new {
$self->{canvas}->update_bed_size;
if ($self->{canvas3D}) {
- $self->{canvas3D}->update_bed_size;
- $self->{canvas3D}->zoom_to_bed;
+ Slic3r::GUI::_3DScene::set_bed_shape($self->{canvas3D}, $self->{config}->bed_shape);
+ Slic3r::GUI::_3DScene::zoom_to_bed($self->{canvas3D});
}
if ($self->{preview3D}) {
- $self->{preview3D}->set_bed_shape($self->{config}->bed_shape);
+ Slic3r::GUI::_3DScene::set_bed_shape($self->{preview3D}->canvas, $self->{config}->bed_shape);
}
$self->update;
@@ -590,8 +633,8 @@ sub _on_select_preset {
sub on_layer_editing_toggled {
my ($self, $new_state) = @_;
- $self->{canvas3D}->layer_editing_enabled($new_state);
- if ($new_state && ! $self->{canvas3D}->layer_editing_enabled) {
+ Slic3r::GUI::_3DScene::enable_layers_editing($self->{canvas3D}, $new_state);
+ if ($new_state && ! Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D})) {
# Initialization of the OpenGL shaders failed. Disable the tool.
if ($self->{htoolbar}) {
$self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0);
@@ -825,8 +868,7 @@ sub load_model_objects {
$self->update;
# zoom to objects
- $self->{canvas3D}->zoom_to_volumes
- if $self->{canvas3D};
+ Slic3r::GUI::_3DScene::zoom_to_volumes($self->{canvas3D}) if $self->{canvas3D};
$self->{list}->Update;
$self->{list}->Select($obj_idx[-1], 1);
@@ -1217,8 +1259,7 @@ sub async_apply_config {
my $invalidated = $self->{print}->apply_config(wxTheApp->{preset_bundle}->full_config);
# Just redraw the 3D canvas without reloading the scene.
-# $self->{canvas3D}->Refresh if ($invalidated && $self->{canvas3D}->layer_editing_enabled);
- $self->{canvas3D}->Refresh if ($self->{canvas3D}->layer_editing_enabled);
+ $self->{canvas3D}->Refresh if Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D});
# Hide the slicing results if the current slicing status is no more valid.
$self->{"print_info_box_show"}->(0) if $invalidated;
@@ -1751,7 +1792,9 @@ sub update {
}
$self->{canvas}->reload_scene if $self->{canvas};
- $self->{canvas3D}->reload_scene if $self->{canvas3D};
+ my $selections = $self->collect_selections;
+ Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections);
+ Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 0);
$self->{preview3D}->reset_gcode_preview_data if $self->{preview3D};
$self->{preview3D}->reload_print if $self->{preview3D};
}
@@ -1806,9 +1849,8 @@ sub on_config_change {
$self->{config}->set($opt_key, $config->get($opt_key));
if ($opt_key eq 'bed_shape') {
$self->{canvas}->update_bed_size;
- $self->{canvas3D}->update_bed_size if $self->{canvas3D};
- $self->{preview3D}->set_bed_shape($self->{config}->bed_shape)
- if $self->{preview3D};
+ Slic3r::GUI::_3DScene::set_bed_shape($self->{canvas3D}, $self->{config}->bed_shape) if $self->{canvas3D};
+ Slic3r::GUI::_3DScene::set_bed_shape($self->{preview3D}->canvas, $self->{config}->bed_shape) if $self->{preview3D};
$update_scheduled = 1;
} elsif ($opt_key =~ '^wipe_tower' || $opt_key eq 'single_extruder_multi_material') {
$update_scheduled = 1;
@@ -1827,10 +1869,10 @@ sub on_config_change {
$self->{"btn_layer_editing"}->Disable;
$self->{"btn_layer_editing"}->SetValue(0);
}
- $self->{canvas3D}->layer_editing_enabled(0);
+ Slic3r::GUI::_3DScene::enable_layers_editing($self->{canvas3D}, 0);
$self->{canvas3D}->Refresh;
$self->{canvas3D}->Update;
- } elsif ($self->{canvas3D}->layer_editing_allowed) {
+ } elsif (Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D})) {
# Want to allow the layer editing, but do it only if the OpenGL supports it.
if ($self->{htoolbar}) {
$self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 1);
@@ -1862,8 +1904,8 @@ sub list_item_deselected {
if ($self->{list}->GetFirstSelected == -1) {
$self->select_object(undef);
$self->{canvas}->Refresh;
- $self->{canvas3D}->deselect_volumes if $self->{canvas3D};
- $self->{canvas3D}->Render if $self->{canvas3D};
+ Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas3D}) if $self->{canvas3D};
+ Slic3r::GUI::_3DScene::render($self->{canvas3D}) if $self->{canvas3D};
}
undef $self->{_lecursor};
}
@@ -1875,11 +1917,23 @@ sub list_item_selected {
my $obj_idx = $event->GetIndex;
$self->select_object($obj_idx);
$self->{canvas}->Refresh;
- $self->{canvas3D}->update_volumes_selection if $self->{canvas3D};
- $self->{canvas3D}->Render if $self->{canvas3D};
+ if ($self->{canvas3D}) {
+ my $selections = $self->collect_selections;
+ Slic3r::GUI::_3DScene::update_volumes_selection($self->{canvas3D}, \@$selections);
+ Slic3r::GUI::_3DScene::render($self->{canvas3D});
+ }
undef $self->{_lecursor};
}
+sub collect_selections {
+ my ($self) = @_;
+ my $selections = [];
+ foreach my $o (@{$self->{objects}}) {
+ push(@$selections, $o->selected);
+ }
+ return $selections;
+}
+
sub list_item_activated {
my ($self, $event, $obj_idx) = @_;
@@ -1936,7 +1990,7 @@ sub object_cut_dialog {
$self->remove($obj_idx);
$self->load_model_objects(grep defined($_), @new_objects);
$self->arrange;
- $self->{canvas3D}->zoom_to_volumes if $self->{canvas3D};
+ Slic3r::GUI::_3DScene::zoom_to_volumes($self->{canvas3D}) if $self->{canvas3D};
}
}
@@ -1972,7 +2026,9 @@ sub object_settings_dialog {
$self->{print}->reload_object($obj_idx);
$self->schedule_background_process;
$self->{canvas}->reload_scene if $self->{canvas};
- $self->{canvas3D}->reload_scene if $self->{canvas3D};
+ my $selections = $self->collect_selections;
+ Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections);
+ Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 0);
} else {
$self->resume_background_process;
}
@@ -1985,7 +2041,7 @@ sub object_list_changed {
# Enable/disable buttons depending on whether there are any objects on the platter.
my $have_objects = @{$self->{objects}} ? 1 : 0;
- my $variable_layer_height_allowed = $self->{config}->variable_layer_height && $self->{canvas3D}->layer_editing_allowed;
+ my $variable_layer_height_allowed = $self->{config}->variable_layer_height && Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D});
if ($self->{htoolbar}) {
# On OSX or Linux
$self->{htoolbar}->EnableTool($_, $have_objects)
@@ -2000,7 +2056,7 @@ sub object_list_changed {
}
my $export_in_progress = $self->{export_gcode_output_file} || $self->{send_gcode_file};
- my $model_fits = $self->{canvas3D} ? $self->{canvas3D}->volumes->check_outside_state($self->{config}) : 1;
+ my $model_fits = $self->{canvas3D} ? Slic3r::GUI::_3DScene::check_volumes_outside_state($self->{canvas3D}, $self->{config}) : 1;
my $method = ($have_objects && ! $export_in_progress && $model_fits) ? 'Enable' : 'Disable';
$self->{"btn_$_"}->$method
for grep $self->{"btn_$_"}, qw(reslice export_gcode print send_gcode);
@@ -2220,11 +2276,11 @@ sub select_view {
my $idx_page = $self->{preview_notebook}->GetSelection;
my $page = ($idx_page == &Wx::wxNOT_FOUND) ? L('3D') : $self->{preview_notebook}->GetPageText($idx_page);
if ($page eq L('Preview')) {
- $self->{preview3D}->canvas->select_view($direction);
- $self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas);
+ Slic3r::GUI::_3DScene::select_view($self->{preview3D}->canvas, $direction);
+ Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{canvas3D}, $self->{preview3D}->canvas);
} else {
- $self->{canvas3D}->select_view($direction);
- $self->{preview3D}->canvas->set_viewport_from_scene($self->{canvas3D});
+ Slic3r::GUI::_3DScene::select_view($self->{canvas3D}, $direction);
+ Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{preview3D}->canvas, $self->{canvas3D});
}
}
diff --git a/lib/Slic3r/GUI/Plater/2D.pm b/lib/Slic3r/GUI/Plater/2D.pm
index 7beba9299..ad8f54ddb 100644
--- a/lib/Slic3r/GUI/Plater/2D.pm
+++ b/lib/Slic3r/GUI/Plater/2D.pm
@@ -222,7 +222,7 @@ sub mouse_event {
];
$self->{drag_object} = [ $obj_idx, $instance_idx ];
} elsif ($event->RightDown) {
- $self->{on_right_click}->($pos);
+ $self->{on_right_click}->($pos->x, $pos->y);
}
last OBJECTS;
diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm
index 6f77f805a..7f83e0f57 100644
--- a/lib/Slic3r/GUI/Plater/3D.pm
+++ b/lib/Slic3r/GUI/Plater/3D.pm
@@ -5,264 +5,279 @@ use utf8;
use List::Util qw();
use Wx qw(:misc :pen :brush :sizer :font :cursor :keycode wxTAB_TRAVERSAL);
-use Wx::Event qw(EVT_KEY_DOWN EVT_CHAR);
+#==============================================================================================================================
+#use Wx::Event qw(EVT_KEY_DOWN EVT_CHAR);
+#==============================================================================================================================
use base qw(Slic3r::GUI::3DScene Class::Accessor);
-use Wx::Locale gettext => 'L';
-
-__PACKAGE__->mk_accessors(qw(
- on_arrange on_rotate_object_left on_rotate_object_right on_scale_object_uniformly
- on_remove_object on_increase_objects on_decrease_objects on_enable_action_buttons));
+#==============================================================================================================================
+#use Wx::Locale gettext => 'L';
+#
+#__PACKAGE__->mk_accessors(qw(
+# on_arrange on_rotate_object_left on_rotate_object_right on_scale_object_uniformly
+# on_remove_object on_increase_objects on_decrease_objects on_enable_action_buttons));
+#==============================================================================================================================
sub new {
my $class = shift;
my ($parent, $objects, $model, $print, $config) = @_;
my $self = $class->SUPER::new($parent);
- $self->enable_picking(1);
- $self->enable_moving(1);
- $self->select_by('object');
- $self->drag_by('instance');
-
- $self->{objects} = $objects;
- $self->{model} = $model;
- $self->{print} = $print;
- $self->{config} = $config;
- $self->{on_select_object} = sub {};
- $self->{on_instances_moved} = sub {};
- $self->{on_wipe_tower_moved} = sub {};
-
- $self->{objects_volumes_idxs} = [];
-
- $self->on_select(sub {
- my ($volume_idx) = @_;
- $self->{on_select_object}->(($volume_idx == -1) ? undef : $self->volumes->[$volume_idx]->object_idx)
- if ($self->{on_select_object});
- });
- $self->on_move(sub {
- my @volume_idxs = @_;
-
- my %done = (); # prevent moving instances twice
- my $object_moved;
- my $wipe_tower_moved;
- foreach my $volume_idx (@volume_idxs) {
- my $volume = $self->volumes->[$volume_idx];
- my $obj_idx = $volume->object_idx;
- my $instance_idx = $volume->instance_idx;
- next if $done{"${obj_idx}_${instance_idx}"};
- $done{"${obj_idx}_${instance_idx}"} = 1;
- if ($obj_idx < 1000) {
- # Move a regular object.
- my $model_object = $self->{model}->get_object($obj_idx);
- $model_object
- ->instances->[$instance_idx]
- ->offset
- ->translate($volume->origin->x, $volume->origin->y); #))
- $model_object->invalidate_bounding_box;
- $object_moved = 1;
- } elsif ($obj_idx == 1000) {
- # Move a wipe tower proxy.
- $wipe_tower_moved = $volume->origin;
- }
- }
-
- $self->{on_instances_moved}->()
- if $object_moved && $self->{on_instances_moved};
- $self->{on_wipe_tower_moved}->($wipe_tower_moved)
- if $wipe_tower_moved && $self->{on_wipe_tower_moved};
- });
-
- EVT_KEY_DOWN($self, sub {
- my ($s, $event) = @_;
- if ($event->HasModifiers) {
- $event->Skip;
- } else {
- my $key = $event->GetKeyCode;
- if ($key == WXK_DELETE) {
- $self->on_remove_object->() if $self->on_remove_object;
- } else {
- $event->Skip;
- }
- }
- });
-
- EVT_CHAR($self, sub {
- my ($s, $event) = @_;
- if ($event->HasModifiers) {
- $event->Skip;
- } else {
- my $key = $event->GetKeyCode;
- if ($key == ord('a')) {
- $self->on_arrange->() if $self->on_arrange;
- } elsif ($key == ord('l')) {
- $self->on_rotate_object_left->() if $self->on_rotate_object_left;
- } elsif ($key == ord('r')) {
- $self->on_rotate_object_right->() if $self->on_rotate_object_right;
- } elsif ($key == ord('s')) {
- $self->on_scale_object_uniformly->() if $self->on_scale_object_uniformly;
- } elsif ($key == ord('+')) {
- $self->on_increase_objects->() if $self->on_increase_objects;
- } elsif ($key == ord('-')) {
- $self->on_decrease_objects->() if $self->on_decrease_objects;
- } else {
- $event->Skip;
- }
- }
- });
+#==============================================================================================================================
+ Slic3r::GUI::_3DScene::enable_picking($self, 1);
+ Slic3r::GUI::_3DScene::enable_moving($self, 1);
+ Slic3r::GUI::_3DScene::set_select_by($self, 'object');
+ Slic3r::GUI::_3DScene::set_drag_by($self, 'instance');
+ Slic3r::GUI::_3DScene::set_model($self, $model);
+ Slic3r::GUI::_3DScene::set_print($self, $print);
+ Slic3r::GUI::_3DScene::set_config($self, $config);
+# $self->enable_picking(1);
+# $self->enable_moving(1);
+# $self->select_by('object');
+# $self->drag_by('instance');
+#
+# $self->{objects} = $objects;
+# $self->{model} = $model;
+# $self->{print} = $print;
+# $self->{config} = $config;
+# $self->{on_select_object} = sub {};
+# $self->{on_instances_moved} = sub {};
+# $self->{on_wipe_tower_moved} = sub {};
+#
+# $self->{objects_volumes_idxs} = [];
+#
+# $self->on_select(sub {
+# my ($volume_idx) = @_;
+# $self->{on_select_object}->(($volume_idx == -1) ? undef : $self->volumes->[$volume_idx]->object_idx)
+# if ($self->{on_select_object});
+# });
+#
+# $self->on_move(sub {
+# my @volume_idxs = @_;
+# my %done = (); # prevent moving instances twice
+# my $object_moved;
+# my $wipe_tower_moved;
+# foreach my $volume_idx (@volume_idxs) {
+# my $volume = $self->volumes->[$volume_idx];
+# my $obj_idx = $volume->object_idx;
+# my $instance_idx = $volume->instance_idx;
+# next if $done{"${obj_idx}_${instance_idx}"};
+# $done{"${obj_idx}_${instance_idx}"} = 1;
+# if ($obj_idx < 1000) {
+# # Move a regular object.
+# my $model_object = $self->{model}->get_object($obj_idx);
+# $model_object
+# ->instances->[$instance_idx]
+# ->offset
+# ->translate($volume->origin->x, $volume->origin->y); #))
+# $model_object->invalidate_bounding_box;
+# $object_moved = 1;
+# } elsif ($obj_idx == 1000) {
+# # Move a wipe tower proxy.
+# $wipe_tower_moved = $volume->origin;
+# }
+# }
+#
+# $self->{on_instances_moved}->()
+# if $object_moved && $self->{on_instances_moved};
+# $self->{on_wipe_tower_moved}->($wipe_tower_moved)
+# if $wipe_tower_moved && $self->{on_wipe_tower_moved};
+# });
+#
+# EVT_KEY_DOWN($self, sub {
+# my ($s, $event) = @_;
+# if ($event->HasModifiers) {
+# $event->Skip;
+# } else {
+# my $key = $event->GetKeyCode;
+# if ($key == WXK_DELETE) {
+# $self->on_remove_object->() if $self->on_remove_object;
+# } else {
+# $event->Skip;
+# }
+# }
+# });
+#
+# EVT_CHAR($self, sub {
+# my ($s, $event) = @_;
+# if ($event->HasModifiers) {
+# $event->Skip;
+# } else {
+# my $key = $event->GetKeyCode;
+# if ($key == ord('a')) {
+# $self->on_arrange->() if $self->on_arrange;
+# } elsif ($key == ord('l')) {
+# $self->on_rotate_object_left->() if $self->on_rotate_object_left;
+# } elsif ($key == ord('r')) {
+# $self->on_rotate_object_right->() if $self->on_rotate_object_right;
+# } elsif ($key == ord('s')) {
+# $self->on_scale_object_uniformly->() if $self->on_scale_object_uniformly;
+# } elsif ($key == ord('+')) {
+# $self->on_increase_objects->() if $self->on_increase_objects;
+# } elsif ($key == ord('-')) {
+# $self->on_decrease_objects->() if $self->on_decrease_objects;
+# } else {
+# $event->Skip;
+# }
+# }
+# });
+#==============================================================================================================================
return $self;
}
-sub set_on_select_object {
- my ($self, $cb) = @_;
- $self->{on_select_object} = $cb;
-}
-
-sub set_on_double_click {
- my ($self, $cb) = @_;
- $self->on_double_click($cb);
-}
-
-sub set_on_right_click {
- my ($self, $cb) = @_;
- $self->on_right_click($cb);
-}
-
-sub set_on_arrange {
- my ($self, $cb) = @_;
- $self->on_arrange($cb);
-}
-
-sub set_on_rotate_object_left {
- my ($self, $cb) = @_;
- $self->on_rotate_object_left($cb);
-}
-
-sub set_on_rotate_object_right {
- my ($self, $cb) = @_;
- $self->on_rotate_object_right($cb);
-}
-
-sub set_on_scale_object_uniformly {
- my ($self, $cb) = @_;
- $self->on_scale_object_uniformly($cb);
-}
-
-sub set_on_increase_objects {
- my ($self, $cb) = @_;
- $self->on_increase_objects($cb);
-}
-
-sub set_on_decrease_objects {
- my ($self, $cb) = @_;
- $self->on_decrease_objects($cb);
-}
-
-sub set_on_remove_object {
- my ($self, $cb) = @_;
- $self->on_remove_object($cb);
-}
-
-sub set_on_instances_moved {
- my ($self, $cb) = @_;
- $self->{on_instances_moved} = $cb;
-}
-
-sub set_on_wipe_tower_moved {
- my ($self, $cb) = @_;
- $self->{on_wipe_tower_moved} = $cb;
-}
-
-sub set_on_model_update {
- my ($self, $cb) = @_;
- $self->on_model_update($cb);
-}
-
-sub set_on_enable_action_buttons {
- my ($self, $cb) = @_;
- $self->on_enable_action_buttons($cb);
-}
-
-sub update_volumes_selection {
- my ($self) = @_;
-
- foreach my $obj_idx (0..$#{$self->{model}->objects}) {
- if ($self->{objects}[$obj_idx]->selected) {
- my $volume_idxs = $self->{objects_volumes_idxs}->[$obj_idx];
- $self->select_volume($_) for @{$volume_idxs};
- }
- }
-}
-
-sub reload_scene {
- my ($self, $force) = @_;
-
- $self->reset_objects;
- $self->update_bed_size;
-
- if (! $self->IsShown && ! $force) {
- $self->{reload_delayed} = 1;
- return;
- }
-
- $self->{reload_delayed} = 0;
-
- $self->{objects_volumes_idxs} = [];
- foreach my $obj_idx (0..$#{$self->{model}->objects}) {
- my @volume_idxs = $self->load_object($self->{model}, $self->{print}, $obj_idx);
- push(@{$self->{objects_volumes_idxs}}, \@volume_idxs);
- }
-
- $self->update_volumes_selection;
-
- if (defined $self->{config}->nozzle_diameter) {
- # Should the wipe tower be visualized?
- my $extruders_count = scalar @{ $self->{config}->nozzle_diameter };
- # Height of a print.
- my $height = $self->{model}->bounding_box->z_max;
- # Show at least a slab.
- $height = 10 if $height < 10;
- if ($extruders_count > 1 && $self->{config}->single_extruder_multi_material && $self->{config}->wipe_tower &&
- ! $self->{config}->complete_objects) {
- $self->volumes->load_wipe_tower_preview(1000,
- $self->{config}->wipe_tower_x, $self->{config}->wipe_tower_y, $self->{config}->wipe_tower_width,
- #$self->{config}->wipe_tower_per_color_wipe# 15 * ($extruders_count - 1), # this is just a hack when the config parameter became obsolete
- 15 * ($extruders_count - 1),
- $self->{model}->bounding_box->z_max, $self->{config}->wipe_tower_rotation_angle, $self->UseVBOs);
- }
- }
-
- $self->update_volumes_colors_by_extruder($self->{config});
-
- # checks for geometry outside the print volume to render it accordingly
- if (scalar @{$self->volumes} > 0)
- {
- my $contained = $self->volumes->check_outside_state($self->{config});
- if (!$contained) {
- $self->set_warning_enabled(1);
- Slic3r::GUI::_3DScene::generate_warning_texture(L("Detected object outside print volume"));
- $self->on_enable_action_buttons->(0) if ($self->on_enable_action_buttons);
- } else {
- $self->set_warning_enabled(0);
- $self->volumes->reset_outside_state();
- Slic3r::GUI::_3DScene::reset_warning_texture();
- $self->on_enable_action_buttons->(scalar @{$self->{model}->objects} > 0) if ($self->on_enable_action_buttons);
- }
- } else {
- $self->set_warning_enabled(0);
- Slic3r::GUI::_3DScene::reset_warning_texture();
- }
-}
-
-sub update_bed_size {
- my ($self) = @_;
- $self->set_bed_shape($self->{config}->bed_shape);
-}
-
-# Called by the Platter wxNotebook when this page is activated.
-sub OnActivate {
- my ($self) = @_;
- $self->reload_scene(1) if ($self->{reload_delayed});
-}
+#==============================================================================================================================
+#sub set_on_select_object {
+# my ($self, $cb) = @_;
+# $self->{on_select_object} = $cb;
+#}
+#
+#sub set_on_double_click {
+# my ($self, $cb) = @_;
+# $self->on_double_click($cb);
+#}
+#
+#sub set_on_right_click {
+# my ($self, $cb) = @_;
+# $self->on_right_click($cb);
+#}
+#
+#sub set_on_arrange {
+# my ($self, $cb) = @_;
+# $self->on_arrange($cb);
+#}
+#
+#sub set_on_rotate_object_left {
+# my ($self, $cb) = @_;
+# $self->on_rotate_object_left($cb);
+#}
+#
+#sub set_on_rotate_object_right {
+# my ($self, $cb) = @_;
+# $self->on_rotate_object_right($cb);
+#}
+#
+#sub set_on_scale_object_uniformly {
+# my ($self, $cb) = @_;
+# $self->on_scale_object_uniformly($cb);
+#}
+#
+#sub set_on_increase_objects {
+# my ($self, $cb) = @_;
+# $self->on_increase_objects($cb);
+#}
+#
+#sub set_on_decrease_objects {
+# my ($self, $cb) = @_;
+# $self->on_decrease_objects($cb);
+#}
+#
+#sub set_on_remove_object {
+# my ($self, $cb) = @_;
+# $self->on_remove_object($cb);
+#}
+#
+#sub set_on_instances_moved {
+# my ($self, $cb) = @_;
+# $self->{on_instances_moved} = $cb;
+#}
+#
+#sub set_on_wipe_tower_moved {
+# my ($self, $cb) = @_;
+# $self->{on_wipe_tower_moved} = $cb;
+#}
+#
+#sub set_on_model_update {
+# my ($self, $cb) = @_;
+# $self->on_model_update($cb);
+#}
+#
+#sub set_on_enable_action_buttons {
+# my ($self, $cb) = @_;
+# $self->on_enable_action_buttons($cb);
+#}
+#
+#sub update_volumes_selection {
+# my ($self) = @_;
+#
+# foreach my $obj_idx (0..$#{$self->{model}->objects}) {
+# if ($self->{objects}[$obj_idx]->selected) {
+# my $volume_idxs = $self->{objects_volumes_idxs}->[$obj_idx];
+# $self->select_volume($_) for @{$volume_idxs};
+# }
+# }
+#}
+#
+#sub reload_scene {
+# my ($self, $force) = @_;
+#
+# $self->reset_objects;
+# $self->update_bed_size;
+#
+# if (! $self->IsShown && ! $force) {
+# $self->{reload_delayed} = 1;
+# return;
+# }
+#
+# $self->{reload_delayed} = 0;
+#
+# $self->{objects_volumes_idxs} = [];
+# foreach my $obj_idx (0..$#{$self->{model}->objects}) {
+# my @volume_idxs = $self->load_object($self->{model}, $self->{print}, $obj_idx);
+# push(@{$self->{objects_volumes_idxs}}, \@volume_idxs);
+# }
+#
+# $self->update_volumes_selection;
+#
+# if (defined $self->{config}->nozzle_diameter) {
+# # Should the wipe tower be visualized?
+# my $extruders_count = scalar @{ $self->{config}->nozzle_diameter };
+# # Height of a print.
+# my $height = $self->{model}->bounding_box->z_max;
+# # Show at least a slab.
+# $height = 10 if $height < 10;
+# if ($extruders_count > 1 && $self->{config}->single_extruder_multi_material && $self->{config}->wipe_tower &&
+# ! $self->{config}->complete_objects) {
+# $self->volumes->load_wipe_tower_preview(1000,
+# $self->{config}->wipe_tower_x, $self->{config}->wipe_tower_y, $self->{config}->wipe_tower_width,
+# #$self->{config}->wipe_tower_per_color_wipe# 15 * ($extruders_count - 1), # this is just a hack when the config parameter became obsolete
+# 15 * ($extruders_count - 1),
+# $self->{model}->bounding_box->z_max, $self->{config}->wipe_tower_rotation_angle, $self->UseVBOs);
+# }
+# }
+#
+# $self->update_volumes_colors_by_extruder($self->{config});
+#
+# # checks for geometry outside the print volume to render it accordingly
+# if (scalar @{$self->volumes} > 0)
+# {
+# my $contained = $self->volumes->check_outside_state($self->{config});
+# if (!$contained) {
+# $self->set_warning_enabled(1);
+# Slic3r::GUI::_3DScene::generate_warning_texture(L("Detected object outside print volume"));
+# $self->on_enable_action_buttons->(0) if ($self->on_enable_action_buttons);
+# } else {
+# $self->set_warning_enabled(0);
+# $self->volumes->reset_outside_state();
+# Slic3r::GUI::_3DScene::reset_warning_texture();
+# $self->on_enable_action_buttons->(scalar @{$self->{model}->objects} > 0) if ($self->on_enable_action_buttons);
+# }
+# } else {
+# $self->set_warning_enabled(0);
+# Slic3r::GUI::_3DScene::reset_warning_texture();
+# }
+#}
+#
+#sub update_bed_size {
+# my ($self) = @_;
+# $self->set_bed_shape($self->{config}->bed_shape);
+#}
+#
+## Called by the Platter wxNotebook when this page is activated.
+#sub OnActivate {
+# my ($self) = @_;
+# $self->reload_scene(1) if ($self->{reload_delayed});
+#}
+#==============================================================================================================================
1;
diff --git a/lib/Slic3r/GUI/Plater/3DPreview.pm b/lib/Slic3r/GUI/Plater/3DPreview.pm
index de6c6e818..9ed2374ec 100644
--- a/lib/Slic3r/GUI/Plater/3DPreview.pm
+++ b/lib/Slic3r/GUI/Plater/3DPreview.pm
@@ -24,7 +24,7 @@ sub new {
# init GUI elements
my $canvas = Slic3r::GUI::3DScene->new($self);
- $canvas->use_plain_shader(1);
+ Slic3r::GUI::_3DScene::enable_shader($canvas, 1);
$self->canvas($canvas);
my $slider_low = Wx::Slider->new(
$self, -1,
@@ -277,8 +277,8 @@ sub new {
sub reload_print {
my ($self, $force) = @_;
-
- $self->canvas->reset_objects;
+
+ Slic3r::GUI::_3DScene::reset_volumes($self->canvas);
$self->_loaded(0);
if (! $self->IsShown && ! $force) {
@@ -304,7 +304,7 @@ sub refresh_print {
sub reset_gcode_preview_data {
my ($self) = @_;
$self->gcode_preview_data->reset;
- $self->canvas->reset_legend_texture();
+ Slic3r::GUI::_3DScene::reset_legend_texture();
}
sub load_print {
@@ -329,7 +329,7 @@ sub load_print {
if ($n_layers == 0) {
$self->reset_sliders;
- $self->canvas->reset_legend_texture();
+ Slic3r::GUI::_3DScene::reset_legend_texture();
$self->canvas->Refresh; # clears canvas
return;
}
@@ -364,23 +364,25 @@ sub load_print {
if ($self->gcode_preview_data->empty) {
# load skirt and brim
- $self->canvas->load_print_toolpaths($self->print, \@colors);
- $self->canvas->load_wipe_tower_toolpaths($self->print, \@colors);
+ Slic3r::GUI::_3DScene::set_print($self->canvas, $self->print);
+ Slic3r::GUI::_3DScene::load_print_toolpaths($self->canvas);
+ Slic3r::GUI::_3DScene::load_wipe_tower_toolpaths($self->canvas, \@colors);
foreach my $object (@{$self->print->objects}) {
- $self->canvas->load_print_object_toolpaths($object, \@colors);
+ Slic3r::GUI::_3DScene::load_print_object_toolpaths($self->canvas, $object, \@colors);
# Show the objects in very transparent color.
#my @volume_ids = $self->canvas->load_object($object->model_object);
#$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids;
}
$self->show_hide_ui_elements('simple');
- $self->canvas->reset_legend_texture();
+ Slic3r::GUI::_3DScene::reset_legend_texture();
} else {
- $self->{force_sliders_full_range} = (scalar(@{$self->canvas->volumes}) == 0);
- $self->canvas->load_gcode_preview($self->print, $self->gcode_preview_data, \@colors);
+ $self->{force_sliders_full_range} = (Slic3r::GUI::_3DScene::get_volumes_count($self->canvas) == 0);
+ Slic3r::GUI::_3DScene::set_print($self->canvas, $self->print);
+ Slic3r::GUI::_3DScene::load_gcode_preview($self->canvas, $self->gcode_preview_data, \@colors);
$self->show_hide_ui_elements('full');
# recalculates zs and update sliders accordingly
- $self->{layers_z} = $self->canvas->get_current_print_zs(1);
+ $self->{layers_z} = Slic3r::GUI::_3DScene::get_current_print_zs($self->canvas, 1);
$n_layers = scalar(@{$self->{layers_z}});
if ($n_layers == 0) {
# all layers filtered out
@@ -466,7 +468,7 @@ sub set_z_range
$self->{z_label_low}->SetLabel(sprintf '%.2f', $z_low);
$self->{z_label_high}->SetLabel(sprintf '%.2f', $z_high);
- my $layers_z = $self->canvas->get_current_print_zs(0);
+ my $layers_z = Slic3r::GUI::_3DScene::get_current_print_zs($self->canvas, 0);
for (my $i = 0; $i < scalar(@{$layers_z}); $i += 1) {
if (($z_low - 1e-6 < @{$layers_z}[$i]) && (@{$layers_z}[$i] < $z_low + 1e-6)) {
$self->{z_label_low_idx}->SetLabel(sprintf '%d', $i + 1);
@@ -480,7 +482,7 @@ sub set_z_range
}
}
- $self->canvas->set_toolpaths_range($z_low - 1e-6, $z_high + 1e-6);
+ Slic3r::GUI::_3DScene::set_toolpaths_range($self->canvas, $z_low - 1e-6, $z_high + 1e-6);
$self->canvas->Refresh if $self->IsShown;
}
@@ -510,11 +512,6 @@ sub set_z_idx_high
}
}
-sub set_bed_shape {
- my ($self, $bed_shape) = @_;
- $self->canvas->set_bed_shape($bed_shape);
-}
-
sub set_number_extruders {
my ($self, $number_extruders) = @_;
if ($self->{number_extruders} != $number_extruders) {
diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm
index 4d55e313a..35aa28818 100644
--- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm
+++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm
@@ -9,6 +9,7 @@ use utf8;
use Slic3r::Geometry qw(PI X);
use Wx qw(wxTheApp :dialog :id :misc :sizer wxTAB_TRAVERSAL);
use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
+use List::Util qw(max);
use base 'Wx::Dialog';
sub new {
@@ -112,10 +113,13 @@ sub new {
my $canvas;
if ($Slic3r::GUI::have_OpenGL) {
$canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
- $canvas->load_object($self->{model_object}, undef, undef, [0]);
- $canvas->set_auto_bed_shape;
+ Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $self->{model_object}, 0, [0]);
+ Slic3r::GUI::_3DScene::set_auto_bed_shape($canvas);
+ Slic3r::GUI::_3DScene::set_axes_length($canvas, 2.0 * max(@{ Slic3r::GUI::_3DScene::get_volumes_bounding_box($canvas)->size }));
$canvas->SetSize([500,500]);
$canvas->SetMinSize($canvas->GetSize);
+ Slic3r::GUI::_3DScene::set_config($canvas, $self->GetParent->{config});
+ Slic3r::GUI::_3DScene::enable_force_zoom_to_bed($canvas, 1);
}
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
@@ -144,6 +148,7 @@ sub new {
# Note that the window was already closed, so a pending update will not be executed.
$self->{already_closed} = 1;
$self->EndModal(wxID_OK);
+ $self->{canvas}->Destroy;
$self->Destroy();
});
@@ -151,6 +156,7 @@ sub new {
# Note that the window was already closed, so a pending update will not be executed.
$self->{already_closed} = 1;
$self->EndModal(wxID_CANCEL);
+ $self->{canvas}->Destroy;
$self->Destroy();
});
@@ -241,15 +247,12 @@ sub _update {
for @$expolygon;
$expolygon->translate(map Slic3r::Geometry::scale($_), @{ $self->{model_object}->instances->[0]->offset });
}
-
- $self->{canvas}->reset_objects;
- $self->{canvas}->load_object($_, undef, undef, [0]) for @objects;
- $self->{canvas}->SetCuttingPlane(
- $self->{cut_options}{z},
- [@expolygons],
- );
- $self->{canvas}->update_volumes_colors_by_extruder($self->GetParent->{config});
- $self->{canvas}->Render;
+
+ Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
+ Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $_, 0, [0]) for @objects;
+ Slic3r::GUI::_3DScene::set_cutting_plane($self->{canvas}, $self->{cut_options}{z}, [@expolygons]);
+ Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
+ Slic3r::GUI::_3DScene::render($self->{canvas});
}
}
diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
index a632edeea..1ec0ce1cb 100644
--- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
+++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
@@ -10,6 +10,7 @@ use File::Basename qw(basename);
use Wx qw(:misc :sizer :treectrl :button :keycode wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxID_CANCEL wxMOD_CONTROL
wxTheApp);
use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED EVT_TREE_KEY_DOWN EVT_KEY_DOWN);
+use List::Util qw(max);
use base 'Wx::Panel';
use constant ICON_OBJECT => 0;
@@ -150,19 +151,19 @@ sub new {
my $canvas;
if ($Slic3r::GUI::have_OpenGL) {
$canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
- $canvas->enable_picking(1);
- $canvas->select_by('volume');
-
- $canvas->on_select(sub {
+ Slic3r::GUI::_3DScene::enable_picking($canvas, 1);
+ Slic3r::GUI::_3DScene::set_select_by($canvas, 'volume');
+ Slic3r::GUI::_3DScene::register_on_select_object_callback($canvas, sub {
my ($volume_idx) = @_;
- # convert scene volume to model object volume
- $self->reload_tree(($volume_idx == -1) ? undef : $canvas->volumes->[$volume_idx]->volume_idx);
+ $self->reload_tree($volume_idx);
});
-
- $canvas->load_object($self->{model_object}, undef, undef, [0]);
- $canvas->set_auto_bed_shape;
+ Slic3r::GUI::_3DScene::load_model_object($canvas, $self->{model_object}, 0, [0]);
+ Slic3r::GUI::_3DScene::set_auto_bed_shape($canvas);
+ Slic3r::GUI::_3DScene::set_axes_length($canvas, 2.0 * max(@{ Slic3r::GUI::_3DScene::get_volumes_bounding_box($canvas)->size }));
$canvas->SetSize([500,700]);
- $canvas->update_volumes_colors_by_extruder($self->GetParent->GetParent->GetParent->{config});
+ Slic3r::GUI::_3DScene::set_config($canvas, $self->GetParent->GetParent->GetParent->{config});
+ Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($canvas);
+ Slic3r::GUI::_3DScene::enable_force_zoom_to_bed($canvas, 1);
}
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
@@ -262,7 +263,7 @@ sub selection_changed {
# deselect all meshes
if ($self->{canvas}) {
- $_->set_selected(0) for @{$self->{canvas}->volumes};
+ Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas});
}
# disable things as if nothing is selected
@@ -290,7 +291,7 @@ sub selection_changed {
if ($itemData->{type} eq 'volume') {
# select volume in 3D preview
if ($self->{canvas}) {
- $self->{canvas}->volumes->[ $itemData->{volume_id} ]->set_selected(1);
+ Slic3r::GUI::_3DScene::select_volume($self->{canvas}, $itemData->{volume_id});
}
$self->{btn_delete}->Enable;
$self->{btn_split}->Enable;
@@ -333,7 +334,7 @@ sub selection_changed {
$self->{settings_panel}->enable;
}
- $self->{canvas}->Render if $self->{canvas};
+ Slic3r::GUI::_3DScene::render($self->{canvas}) if $self->{canvas};
}
sub on_btn_load {
@@ -429,7 +430,7 @@ sub on_btn_move_up {
if ($itemData && $itemData->{type} eq 'volume') {
my $volume_id = $itemData->{volume_id};
if ($self->{model_object}->move_volume_up($volume_id)) {
- $self->{canvas}->volumes->move_volume_up($volume_id);
+ Slic3r::GUI::_3DScene::move_volume_up($self->{canvas}, $volume_id);
$self->{parts_changed} = 1;
$self->reload_tree($volume_id - 1);
}
@@ -442,7 +443,7 @@ sub on_btn_move_down {
if ($itemData && $itemData->{type} eq 'volume') {
my $volume_id = $itemData->{volume_id};
if ($self->{model_object}->move_volume_down($volume_id)) {
- $self->{canvas}->volumes->move_volume_down($volume_id);
+ Slic3r::GUI::_3DScene::move_volume_down($self->{canvas}, $volume_id);
$self->{parts_changed} = 1;
$self->reload_tree($volume_id + 1);
}
@@ -487,11 +488,11 @@ sub _parts_changed {
$self->reload_tree;
if ($self->{canvas}) {
- $self->{canvas}->reset_objects;
- $self->{canvas}->load_object($self->{model_object});
- $self->{canvas}->zoom_to_volumes;
- $self->{canvas}->update_volumes_colors_by_extruder($self->GetParent->GetParent->GetParent->{config});
- $self->{canvas}->Render;
+ Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
+ Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $self->{model_object}, 0, [0]);
+ Slic3r::GUI::_3DScene::zoom_to_volumes($self->{canvas});
+ Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
+ Slic3r::GUI::_3DScene::render($self->{canvas});
}
}
@@ -511,6 +512,11 @@ sub CanClose {
return ! Slic3r::GUI::catch_error($self);
}
+sub Destroy {
+ my ($self) = @_;
+ $self->{canvas}->Destroy if ($self->{canvas});
+}
+
sub PartsChanged {
my ($self) = @_;
return $self->{parts_changed};
@@ -525,18 +531,18 @@ sub _update_canvas {
my ($self) = @_;
if ($self->{canvas}) {
- $self->{canvas}->reset_objects;
- $self->{canvas}->load_object($self->{model_object});
+ Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
+ Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $self->{model_object}, 0, [0]);
# restore selection, if any
if (my $itemData = $self->get_selection) {
if ($itemData->{type} eq 'volume') {
- $self->{canvas}->volumes->[ $itemData->{volume_id} ]->set_selected(1);
+ Slic3r::GUI::_3DScene::select_volume($self->{canvas}, $itemData->{volume_id});
}
}
-
- $self->{canvas}->update_volumes_colors_by_extruder($self->GetParent->GetParent->GetParent->{config});
- $self->{canvas}->Render;
+
+ Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
+ Slic3r::GUI::_3DScene::render($self->{canvas});
}
}
@@ -558,10 +564,10 @@ sub _update {
$self->{parts_changed} = 1;
my @objects = ();
push @objects, $self->{model_object};
- $self->{canvas}->reset_objects;
- $self->{canvas}->load_object($_, undef, [0]) for @objects;
- $self->{canvas}->update_volumes_colors_by_extruder($self->GetParent->GetParent->GetParent->{config});
- $self->{canvas}->Render;
+ Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
+ Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $_, 0, [0]) for @objects;
+ Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
+ Slic3r::GUI::_3DScene::render($self->{canvas});
}
1;
diff --git a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm
index 61a8f2a01..3befba708 100644
--- a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm
+++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm
@@ -36,6 +36,7 @@ sub new {
wxTheApp->save_window_pos($self, "object_settings");
$self->EndModal(wxID_OK);
+ $self->{parts}->Destroy;
$self->Destroy;
});
diff --git a/resources/icons/bed/mk2_bottom.png b/resources/icons/bed/mk2_bottom.png
new file mode 100644
index 000000000..d06de22eb
--- /dev/null
+++ b/resources/icons/bed/mk2_bottom.png
Binary files differ
diff --git a/resources/icons/bed/mk2_top.png b/resources/icons/bed/mk2_top.png
new file mode 100644
index 000000000..142050c3a
--- /dev/null
+++ b/resources/icons/bed/mk2_top.png
Binary files differ
diff --git a/resources/icons/bed/mk3_bottom.png b/resources/icons/bed/mk3_bottom.png
new file mode 100644
index 000000000..072c14dae
--- /dev/null
+++ b/resources/icons/bed/mk3_bottom.png
Binary files differ
diff --git a/resources/icons/bed/mk3_top.png b/resources/icons/bed/mk3_top.png
new file mode 100644
index 000000000..0403c2f05
--- /dev/null
+++ b/resources/icons/bed/mk3_top.png
Binary files differ
diff --git a/resources/icons/overlay/rotate_hover.png b/resources/icons/overlay/rotate_hover.png
new file mode 100644
index 000000000..56d4fd277
--- /dev/null
+++ b/resources/icons/overlay/rotate_hover.png
Binary files differ
diff --git a/resources/icons/overlay/rotate_off.png b/resources/icons/overlay/rotate_off.png
new file mode 100644
index 000000000..8491f7e43
--- /dev/null
+++ b/resources/icons/overlay/rotate_off.png
Binary files differ
diff --git a/resources/icons/overlay/rotate_on.png b/resources/icons/overlay/rotate_on.png
new file mode 100644
index 000000000..e2db5120c
--- /dev/null
+++ b/resources/icons/overlay/rotate_on.png
Binary files differ
diff --git a/resources/icons/overlay/scale_hover.png b/resources/icons/overlay/scale_hover.png
new file mode 100644
index 000000000..d620aee7a
--- /dev/null
+++ b/resources/icons/overlay/scale_hover.png
Binary files differ
diff --git a/resources/icons/overlay/scale_off.png b/resources/icons/overlay/scale_off.png
new file mode 100644
index 000000000..1ae999bbe
--- /dev/null
+++ b/resources/icons/overlay/scale_off.png
Binary files differ
diff --git a/resources/icons/overlay/scale_on.png b/resources/icons/overlay/scale_on.png
new file mode 100644
index 000000000..62e805f12
--- /dev/null
+++ b/resources/icons/overlay/scale_on.png
Binary files differ
diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs
new file mode 100644
index 000000000..9edc8fa76
--- /dev/null
+++ b/resources/shaders/gouraud.fs
@@ -0,0 +1,18 @@
+#version 110
+
+const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+varying vec3 delta_box_min;
+varying vec3 delta_box_max;
+
+uniform vec4 uniform_color;
+
+void main()
+{
+ // if the fragment is outside the print volume -> use darker color
+ vec3 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(uniform_color.rgb, ZERO, 0.3333) : uniform_color.rgb;
+ gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + color * intensity.x, uniform_color.a);
+}
diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs
new file mode 100644
index 000000000..22ba91a93
--- /dev/null
+++ b/resources/shaders/gouraud.vs
@@ -0,0 +1,70 @@
+#version 110
+
+#define INTENSITY_CORRECTION 0.6
+
+// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
+const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SHININESS 20.0
+
+// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
+const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
+#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
+//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
+//#define LIGHT_FRONT_SHININESS 5.0
+
+#define INTENSITY_AMBIENT 0.3
+
+const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+
+struct PrintBoxDetection
+{
+ vec3 min;
+ vec3 max;
+ // xyz contains the offset, if w == 1.0 detection needs to be performed
+ vec4 volume_origin;
+};
+
+uniform PrintBoxDetection print_box;
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+varying vec3 delta_box_min;
+varying vec3 delta_box_max;
+
+void main()
+{
+ // First transform the normal into camera space and normalize the result.
+ vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
+
+ // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+ // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+ float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+
+ intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+ intensity.y = 0.0;
+
+ if (NdotL > 0.0)
+ intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+ // Perform the same lighting calculation for the 2nd light source (no specular applied).
+ NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+ intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+
+ // compute deltas for out of print volume detection (world coordinates)
+ if (print_box.volume_origin.w == 1.0)
+ {
+ vec3 v = gl_Vertex.xyz + print_box.volume_origin.xyz;
+ delta_box_min = v - print_box.min;
+ delta_box_max = v - print_box.max;
+ }
+ else
+ {
+ delta_box_min = ZERO;
+ delta_box_max = ZERO;
+ }
+
+ gl_Position = ftransform();
+}
diff --git a/resources/shaders/variable_layer_height.fs b/resources/shaders/variable_layer_height.fs
new file mode 100644
index 000000000..f87e6627e
--- /dev/null
+++ b/resources/shaders/variable_layer_height.fs
@@ -0,0 +1,40 @@
+#version 110
+
+#define M_PI 3.1415926535897932384626433832795
+
+// 2D texture (1D texture split by the rows) of color along the object Z axis.
+uniform sampler2D z_texture;
+// Scaling from the Z texture rows coordinate to the normalized texture row coordinate.
+uniform float z_to_texture_row;
+uniform float z_texture_row_to_normalized;
+uniform float z_cursor;
+uniform float z_cursor_band_width;
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+varying float object_z;
+
+void main()
+{
+ float object_z_row = z_to_texture_row * object_z;
+ // Index of the row in the texture.
+ float z_texture_row = floor(object_z_row);
+ // Normalized coordinate from 0. to 1.
+ float z_texture_col = object_z_row - z_texture_row;
+ float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25;
+ // Calculate level of detail from the object Z coordinate.
+ // This makes the slowly sloping surfaces to be show with high detail (with stripes),
+ // and the vertical surfaces to be shown with low detail (no stripes)
+ float z_in_cells = object_z_row * 190.;
+ // Gradient of Z projected on the screen.
+ float dx_vtc = dFdx(z_in_cells);
+ float dy_vtc = dFdy(z_in_cells);
+ float lod = clamp(0.5 * log2(max(dx_vtc * dx_vtc, dy_vtc * dy_vtc)), 0., 1.);
+ // Sample the Z texture. Texture coordinates are normalized to <0, 1>.
+ vec4 color = mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.),
+ texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod);
+
+ // Mix the final color.
+ gl_FragColor = vec4(intensity.y, intensity.y, intensity.y, 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend);
+}
diff --git a/resources/shaders/variable_layer_height.vs b/resources/shaders/variable_layer_height.vs
new file mode 100644
index 000000000..2c918c0d4
--- /dev/null
+++ b/resources/shaders/variable_layer_height.vs
@@ -0,0 +1,46 @@
+#version 110
+
+#define INTENSITY_CORRECTION 0.6
+
+const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SHININESS 20.0
+
+const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
+#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
+//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
+//#define LIGHT_FRONT_SHININESS 5.0
+
+#define INTENSITY_AMBIENT 0.3
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+varying float object_z;
+
+void main()
+{
+ // First transform the normal into camera space and normalize the result.
+ vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
+
+ // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+ // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+ float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+
+ intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+ intensity.y = 0.0;
+
+ if (NdotL > 0.0)
+ intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+ // Perform the same lighting calculation for the 2nd light source (no specular)
+ NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+
+ intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+
+ // Scaled to widths of the Z texture.
+ object_z = gl_Vertex.z;
+
+ gl_Position = ftransform();
+}
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index 05293a523..117af1959 100644
--- a/xs/CMakeLists.txt
+++ b/xs/CMakeLists.txt
@@ -188,7 +188,15 @@ add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/GUI/3DScene.cpp
${LIBDIR}/slic3r/GUI/3DScene.hpp
${LIBDIR}/slic3r/GUI/GLShader.cpp
- ${LIBDIR}/slic3r/GUI/GLShader.hpp
+ ${LIBDIR}/slic3r/GUI/GLShader.hpp
+ ${LIBDIR}/slic3r/GUI/GLCanvas3D.hpp
+ ${LIBDIR}/slic3r/GUI/GLCanvas3D.cpp
+ ${LIBDIR}/slic3r/GUI/GLCanvas3DManager.hpp
+ ${LIBDIR}/slic3r/GUI/GLCanvas3DManager.cpp
+ ${LIBDIR}/slic3r/GUI/GLGizmo.hpp
+ ${LIBDIR}/slic3r/GUI/GLGizmo.cpp
+ ${LIBDIR}/slic3r/GUI/GLTexture.hpp
+ ${LIBDIR}/slic3r/GUI/GLTexture.cpp
${LIBDIR}/slic3r/GUI/Preferences.cpp
${LIBDIR}/slic3r/GUI/Preferences.hpp
${LIBDIR}/slic3r/GUI/Preset.cpp
@@ -566,13 +574,13 @@ if (SLIC3R_PRUSACONTROL)
set(wxWidgets_UseAlienWx 1)
if (wxWidgets_UseAlienWx)
set(AlienWx_DEBUG 1)
- find_package(AlienWx REQUIRED COMPONENTS base core adv html)
+ find_package(AlienWx REQUIRED COMPONENTS base core adv html gl)
include_directories(${AlienWx_INCLUDE_DIRS})
#add_compile_options(${AlienWx_CXX_FLAGS})
add_definitions(${AlienWx_DEFINITIONS})
set(wxWidgets_LIBRARIES ${AlienWx_LIBRARIES})
else ()
- find_package(wxWidgets REQUIRED COMPONENTS base core adv html)
+ find_package(wxWidgets REQUIRED COMPONENTS base core adv html gl)
include(${wxWidgets_USE_FILE})
endif ()
add_definitions(-DSLIC3R_GUI -DSLIC3R_PRUS)
diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm
index 06eb041df..a4847fb45 100644
--- a/xs/lib/Slic3r/XS.pm
+++ b/xs/lib/Slic3r/XS.pm
@@ -12,6 +12,8 @@ our $VERSION = '0.01';
BEGIN {
if ($^O eq 'MSWin32') {
eval "use Wx";
+ eval "use Wx::GLCanvas";
+ eval "use Wx::GLContext";
eval "use Wx::Html";
eval "use Wx::Print"; # because of some Wx bug, thread creation fails if we don't have this (looks like Wx::Printout is hard-coded in some thread cleanup code)
}
@@ -280,6 +282,7 @@ for my $class (qw(
Slic3r::Geometry::BoundingBox
Slic3r::Geometry::BoundingBoxf
Slic3r::Geometry::BoundingBoxf3
+ Slic3r::GUI::_3DScene::GLShader
Slic3r::GUI::_3DScene::GLVolume
Slic3r::GUI::Preset
Slic3r::GUI::PresetCollection
diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp
index 91ba88d84..ceb968a50 100644
--- a/xs/src/libslic3r/BoundingBox.cpp
+++ b/xs/src/libslic3r/BoundingBox.cpp
@@ -222,6 +222,14 @@ BoundingBox3Base<PointClass>::center() const
}
template Pointf3 BoundingBox3Base<Pointf3>::center() const;
+template <class PointClass> coordf_t
+BoundingBox3Base<PointClass>::max_size() const
+{
+ PointClass s = size();
+ return std::max(s.x, std::max(s.y, s.z));
+}
+template coordf_t BoundingBox3Base<Pointf3>::max_size() const;
+
// Align a coordinate to a grid. The coordinate may be negative,
// the aligned value will never be bigger than the original one.
static inline coord_t _align_to_grid(const coord_t coord, const coord_t spacing) {
diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp
index 92a2bd451..5de94aa9c 100644
--- a/xs/src/libslic3r/BoundingBox.hpp
+++ b/xs/src/libslic3r/BoundingBox.hpp
@@ -94,6 +94,7 @@ public:
void translate(const Pointf3 &pos) { this->translate(pos.x, pos.y, pos.z); }
void offset(coordf_t delta);
PointClass center() const;
+ coordf_t max_size() const;
bool contains(const PointClass &point) const {
return BoundingBoxBase<PointClass>::contains(point) && point.z >= this->min.z && point.z <= this->max.z;
diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp
index 8ce23b1e5..147353abd 100644
--- a/xs/src/libslic3r/Model.cpp
+++ b/xs/src/libslic3r/Model.cpp
@@ -603,7 +603,10 @@ void ModelObject::clear_instances()
// Returns the bounding box of the transformed instances.
// This bounding box is approximate and not snug.
-const BoundingBoxf3& ModelObject::bounding_box()
+//========================================================================================================
+const BoundingBoxf3& ModelObject::bounding_box() const
+//const BoundingBoxf3& ModelObject::bounding_box()
+//========================================================================================================
{
if (! m_bounding_box_valid) {
BoundingBoxf3 raw_bbox;
diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp
index 8b63c3641..b148ec29d 100644
--- a/xs/src/libslic3r/Model.hpp
+++ b/xs/src/libslic3r/Model.hpp
@@ -103,7 +103,10 @@ public:
// Returns the bounding box of the transformed instances.
// This bounding box is approximate and not snug.
// This bounding box is being cached.
- const BoundingBoxf3& bounding_box();
+//========================================================================================================
+ const BoundingBoxf3& bounding_box() const;
+// const BoundingBoxf3& bounding_box();
+//========================================================================================================
void invalidate_bounding_box() { m_bounding_box_valid = false; }
// Returns a snug bounding box of the transformed instances.
// This bounding box is not being cached.
@@ -145,8 +148,10 @@ private:
// Parent object, owning this ModelObject.
Model *m_model;
// Bounding box, cached.
- BoundingBoxf3 m_bounding_box;
- bool m_bounding_box_valid;
+//========================================================================================================
+ mutable BoundingBoxf3 m_bounding_box;
+ mutable bool m_bounding_box_valid;
+//========================================================================================================
};
// An object STL, or a modifier volume, over which a different set of parameters shall be applied.
diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp
index 6c9096a3d..a52cdceb6 100644
--- a/xs/src/libslic3r/Point.hpp
+++ b/xs/src/libslic3r/Point.hpp
@@ -238,6 +238,11 @@ inline coordf_t dot(const Pointf &v1, const Pointf &v2) { return v1.x * v2.x + v
inline coordf_t dot(const Pointf &v) { return v.x * v.x + v.y * v.y; }
inline double length(const Vectorf &v) { return sqrt(dot(v)); }
inline double l2(const Vectorf &v) { return dot(v); }
+inline Vectorf normalize(const Vectorf& v)
+{
+ coordf_t len = ::sqrt(sqr(v.x) + sqr(v.y));
+ return (len != 0.0) ? 1.0 / len * v : Vectorf(0.0, 0.0);
+}
class Pointf3 : public Pointf
{
diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp
index c56e64c6c..86c15b679 100644
--- a/xs/src/libslic3r/Print.hpp
+++ b/xs/src/libslic3r/Print.hpp
@@ -184,6 +184,8 @@ public:
void reset_layer_height_profile();
+ void adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action);
+
// Collect the slicing parameters, to be used by variable layer thickness algorithm,
// by the interactive layer height editor and by the printing process itself.
// The slicing parameters are dependent on various configuration values
diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp
index b0341db16..ba0876a85 100644
--- a/xs/src/libslic3r/PrintObject.cpp
+++ b/xs/src/libslic3r/PrintObject.cpp
@@ -4,6 +4,7 @@
#include "Geometry.hpp"
#include "SupportMaterial.hpp"
#include "Surface.hpp"
+#include "Slicing.hpp"
#include <utility>
#include <boost/log/trivial.hpp>
@@ -1961,4 +1962,12 @@ void PrintObject::reset_layer_height_profile()
this->model_object()->layer_height_profile_valid = false;
}
+void PrintObject::adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action)
+{
+ update_layer_height_profile(_model_object->layer_height_profile);
+ Slic3r::adjust_layer_height_profile(slicing_parameters(), _model_object->layer_height_profile, z, layer_thickness_delta, band_width, LayerHeightEditActionType(action));
+ _model_object->layer_height_profile_valid = true;
+ layer_height_profile_valid = false;
+}
+
} // namespace Slic3r
diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp
index e5157741e..921841a27 100644
--- a/xs/src/libslic3r/Utils.hpp
+++ b/xs/src/libslic3r/Utils.hpp
@@ -91,10 +91,13 @@ public:
~PerlCallback() { this->deregister_callback(); }
void register_callback(void *sv);
void deregister_callback();
- void call();
- void call(int i);
- void call(int i, int j);
-// void call(const std::vector<int> &ints);
+ void call() const;
+ void call(int i) const;
+ void call(int i, int j) const;
+ void call(const std::vector<int>& ints) const;
+ void call(double d) const;
+ void call(double x, double y) const;
+ void call(bool b) const;
private:
void *m_callback;
};
diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp
index 582488c5a..745d07fcd 100644
--- a/xs/src/libslic3r/utils.cpp
+++ b/xs/src/libslic3r/utils.cpp
@@ -184,7 +184,7 @@ void PerlCallback::deregister_callback()
}
}
-void PerlCallback::call()
+void PerlCallback::call() const
{
if (! m_callback)
return;
@@ -198,7 +198,7 @@ void PerlCallback::call()
LEAVE;
}
-void PerlCallback::call(int i)
+void PerlCallback::call(int i) const
{
if (! m_callback)
return;
@@ -213,7 +213,7 @@ void PerlCallback::call(int i)
LEAVE;
}
-void PerlCallback::call(int i, int j)
+void PerlCallback::call(int i, int j) const
{
if (! m_callback)
return;
@@ -229,8 +229,7 @@ void PerlCallback::call(int i, int j)
LEAVE;
}
-/*
-void PerlCallback::call(const std::vector<int> &ints)
+void PerlCallback::call(const std::vector<int>& ints) const
{
if (! m_callback)
return;
@@ -238,16 +237,51 @@ void PerlCallback::call(const std::vector<int> &ints)
ENTER;
SAVETMPS;
PUSHMARK(SP);
- AV* av = newAV();
for (int i : ints)
- av_push(av, newSViv(i));
- XPUSHs(av);
+ {
+ XPUSHs(sv_2mortal(newSViv(i)));
+ }
+ PUTBACK;
+ perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
+ FREETMPS;
+ LEAVE;
+}
+
+void PerlCallback::call(double d) const
+{
+ if (!m_callback)
+ return;
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ XPUSHs(sv_2mortal(newSVnv(d)));
+ PUTBACK;
+ perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
+ FREETMPS;
+ LEAVE;
+}
+
+void PerlCallback::call(double x, double y) const
+{
+ if (!m_callback)
+ return;
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ XPUSHs(sv_2mortal(newSVnv(x)));
+ XPUSHs(sv_2mortal(newSVnv(y)));
PUTBACK;
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
FREETMPS;
LEAVE;
}
-*/
+
+void PerlCallback::call(bool b) const
+{
+ call(b ? 1 : 0);
+}
#ifdef WIN32
#ifndef NOMINMAX
diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
index cb2e08e1f..1879b3082 100644
--- a/xs/src/slic3r/GUI/3DScene.cpp
+++ b/xs/src/slic3r/GUI/3DScene.cpp
@@ -1348,8 +1348,8 @@ static void point_to_indexed_vertex_array(const Point3& point,
volume.push_triangle(idxs[0], idxs[3], idxs[4]);
}
-static void thick_lines_to_verts(
- const Lines &lines,
+void _3DScene::thick_lines_to_verts(
+ const Lines &lines,
const std::vector<double> &widths,
const std::vector<double> &heights,
bool closed,
@@ -1359,7 +1359,7 @@ static void thick_lines_to_verts(
thick_lines_to_indexed_vertex_array(lines, widths, heights, closed, top_z, volume.indexed_vertex_array);
}
-static void thick_lines_to_verts(const Lines3& lines,
+void _3DScene::thick_lines_to_verts(const Lines3& lines,
const std::vector<double>& widths,
const std::vector<double>& heights,
bool closed,
@@ -1377,7 +1377,7 @@ static void thick_point_to_verts(const Point3& point,
}
// Fill in the qverts and tverts with quads and triangles for the extrusion_path.
-static inline void extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, GLVolume &volume)
{
Lines lines = extrusion_path.polyline.lines();
std::vector<double> widths(lines.size(), extrusion_path.width);
@@ -1386,7 +1386,7 @@ static inline void extrusionentity_to_verts(const ExtrusionPath &extrusion_path,
}
// Fill in the qverts and tverts with quads and triangles for the extrusion_path.
-static inline void extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, const Point &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, const Point &copy, GLVolume &volume)
{
Polyline polyline = extrusion_path.polyline;
polyline.remove_duplicate_points();
@@ -1398,7 +1398,7 @@ static inline void extrusionentity_to_verts(const ExtrusionPath &extrusion_path,
}
// Fill in the qverts and tverts with quads and triangles for the extrusion_loop.
-static inline void extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, float print_z, const Point &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, float print_z, const Point &copy, GLVolume &volume)
{
Lines lines;
std::vector<double> widths;
@@ -1416,7 +1416,7 @@ static inline void extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop,
}
// Fill in the qverts and tverts with quads and triangles for the extrusion_multi_path.
-static inline void extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_multi_path, float print_z, const Point &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_multi_path, float print_z, const Point &copy, GLVolume &volume)
{
Lines lines;
std::vector<double> widths;
@@ -1433,15 +1433,13 @@ static inline void extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_
thick_lines_to_verts(lines, widths, heights, false, print_z, volume);
}
-static void extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point &copy, GLVolume &volume);
-
-static inline void extrusionentity_to_verts(const ExtrusionEntityCollection &extrusion_entity_collection, float print_z, const Point &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionEntityCollection &extrusion_entity_collection, float print_z, const Point &copy, GLVolume &volume)
{
for (const ExtrusionEntity *extrusion_entity : extrusion_entity_collection.entities)
extrusionentity_to_verts(extrusion_entity, print_z, copy, volume);
}
-static void extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point &copy, GLVolume &volume)
{
if (extrusion_entity != nullptr) {
auto *extrusion_path = dynamic_cast<const ExtrusionPath*>(extrusion_entity);
@@ -1468,7 +1466,7 @@ static void extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, fl
}
}
-static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume)
+void _3DScene::polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume)
{
Lines3 lines = polyline.lines();
std::vector<double> widths(lines.size(), width);
@@ -1476,14 +1474,14 @@ static void polyline3_to_verts(const Polyline3& polyline, double width, double h
thick_lines_to_verts(lines, widths, heights, false, volume);
}
-static void point3_to_verts(const Point3& point, double width, double height, GLVolume& volume)
+void _3DScene::point3_to_verts(const Point3& point, double width, double height, GLVolume& volume)
{
thick_point_to_verts(point, width, height, volume);
}
-_3DScene::GCodePreviewVolumeIndex _3DScene::s_gcode_preview_volume_index;
_3DScene::LegendTexture _3DScene::s_legend_texture;
_3DScene::WarningTexture _3DScene::s_warning_texture;
+GUI::GLCanvas3DManager _3DScene::s_canvas_mgr;
unsigned int _3DScene::TextureBase::finalize()
{
@@ -1720,1049 +1718,441 @@ bool _3DScene::LegendTexture::generate(const GCodePreviewData& preview_data, con
return true;
}
-void _3DScene::_glew_init()
-{
- glewInit();
+void _3DScene::init_gl()
+{
+ s_canvas_mgr.init_gl();
}
-static inline int hex_digit_to_int(const char c)
+std::string _3DScene::get_gl_info(bool format_as_html, bool extensions)
{
- return
- (c >= '0' && c <= '9') ? int(c - '0') :
- (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
- (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
+ return s_canvas_mgr.get_gl_info(format_as_html, extensions);
}
-static inline std::vector<float> parse_colors(const std::vector<std::string> &scolors)
+bool _3DScene::use_VBOs()
{
- std::vector<float> output(scolors.size() * 4, 1.f);
- for (size_t i = 0; i < scolors.size(); ++ i) {
- const std::string &scolor = scolors[i];
- const char *c = scolor.data() + 1;
- if (scolor.size() == 7 && scolor.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;
- output[i * 4 + j] = float(digit1 * 16 + digit2) / 255.f;
- }
- }
- }
- return output;
+ return s_canvas_mgr.use_VBOs();
}
-void _3DScene::load_gcode_preview(const Print* print, const GCodePreviewData* preview_data, GLVolumeCollection* volumes, const std::vector<std::string>& str_tool_colors, bool use_VBOs)
+bool _3DScene::add_canvas(wxGLCanvas* canvas)
{
- if ((preview_data == nullptr) || (volumes == nullptr))
- return;
-
- if (volumes->empty())
- {
- std::vector<float> tool_colors = parse_colors(str_tool_colors);
-
- s_gcode_preview_volume_index.reset();
-
- _load_gcode_extrusion_paths(*preview_data, *volumes, tool_colors, use_VBOs);
- _load_gcode_travel_paths(*preview_data, *volumes, tool_colors, use_VBOs);
- _load_gcode_retractions(*preview_data, *volumes, use_VBOs);
- _load_gcode_unretractions(*preview_data, *volumes, use_VBOs);
-
- if (volumes->empty())
- reset_legend_texture();
- else
- {
- _generate_legend_texture(*preview_data, tool_colors);
-
- // removes empty volumes
- volumes->volumes.erase(std::remove_if(volumes->volumes.begin(), volumes->volumes.end(),
- [](const GLVolume *volume) { return volume->print_zs.empty(); }),
- volumes->volumes.end());
-
- _load_shells(*print, *volumes, use_VBOs);
- }
- }
-
- _update_gcode_volumes_visibility(*preview_data, *volumes);
+ return s_canvas_mgr.add(canvas);
}
-unsigned int _3DScene::get_legend_texture_width()
+bool _3DScene::remove_canvas(wxGLCanvas* canvas)
{
- return s_legend_texture.get_texture_width();
+ return s_canvas_mgr.remove(canvas);
}
-unsigned int _3DScene::get_legend_texture_height()
+void _3DScene::remove_all_canvases()
{
- return s_legend_texture.get_texture_height();
+ s_canvas_mgr.remove_all();
}
-void _3DScene::reset_legend_texture()
+bool _3DScene::init(wxGLCanvas* canvas)
{
- s_legend_texture.reset_texture();
+ return s_canvas_mgr.init(canvas);
}
-unsigned int _3DScene::finalize_legend_texture()
+void _3DScene::set_active(wxGLCanvas* canvas, bool active)
{
- return s_legend_texture.finalize();
+ s_canvas_mgr.set_active(canvas, active);
}
-unsigned int _3DScene::get_warning_texture_width()
+unsigned int _3DScene::get_volumes_count(wxGLCanvas* canvas)
{
- return s_warning_texture.get_texture_width();
+ return s_canvas_mgr.get_volumes_count(canvas);
}
-unsigned int _3DScene::get_warning_texture_height()
+void _3DScene::reset_volumes(wxGLCanvas* canvas)
{
- return s_warning_texture.get_texture_height();
+ s_canvas_mgr.reset_volumes(canvas);
}
-void _3DScene::generate_warning_texture(const std::string& msg)
+void _3DScene::deselect_volumes(wxGLCanvas* canvas)
{
- s_warning_texture.generate(msg);
+ s_canvas_mgr.deselect_volumes(canvas);
}
-void _3DScene::reset_warning_texture()
+void _3DScene::select_volume(wxGLCanvas* canvas, unsigned int id)
{
- s_warning_texture.reset_texture();
+ s_canvas_mgr.select_volume(canvas, id);
}
-unsigned int _3DScene::finalize_warning_texture()
+void _3DScene::update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections)
{
- return s_warning_texture.finalize();
+ s_canvas_mgr.update_volumes_selection(canvas, selections);
}
-// Create 3D thick extrusion lines for a skirt and brim.
-// Adds a new Slic3r::GUI::3DScene::Volume to volumes.
-void _3DScene::_load_print_toolpaths(
- const Print *print,
- GLVolumeCollection *volumes,
- const std::vector<std::string> &tool_colors,
- bool use_VBOs)
+bool _3DScene::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config)
{
- if (!print->has_skirt() && print->config.brim_width.value == 0)
- return;
-
- const float color[] = { 0.5f, 1.0f, 0.5f, 1.f }; // greenish
-
- // number of skirt layers
- size_t total_layer_count = 0;
- for (const PrintObject *print_object : print->objects)
- total_layer_count = std::max(total_layer_count, print_object->total_layer_count());
- 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)
- skirt_height = 1;
-
- // get first skirt_height layers (maybe this should be moved to a PrintObject method?)
- const PrintObject *object0 = print->objects.front();
- std::vector<float> print_zs;
- print_zs.reserve(skirt_height * 2);
- for (size_t i = 0; i < std::min(skirt_height, object0->layers.size()); ++ i)
- print_zs.push_back(float(object0->layers[i]->print_z));
- //FIXME why there are support layers?
- for (size_t i = 0; i < std::min(skirt_height, object0->support_layers.size()); ++ i)
- print_zs.push_back(float(object0->support_layers[i]->print_z));
- sort_remove_duplicates(print_zs);
- if (print_zs.size() > skirt_height)
- print_zs.erase(print_zs.begin() + skirt_height, print_zs.end());
-
- volumes->volumes.emplace_back(new GLVolume(color));
- GLVolume &volume = *volumes->volumes.back();
- for (size_t i = 0; i < skirt_height; ++ i) {
- volume.print_zs.push_back(print_zs[i]);
- volume.offsets.push_back(volume.indexed_vertex_array.quad_indices.size());
- volume.offsets.push_back(volume.indexed_vertex_array.triangle_indices.size());
- if (i == 0)
- extrusionentity_to_verts(print->brim, print_zs[i], Point(0, 0), volume);
- extrusionentity_to_verts(print->skirt, print_zs[i], Point(0, 0), volume);
- }
- volume.bounding_box = volume.indexed_vertex_array.bounding_box();
- volume.indexed_vertex_array.finalize_geometry(use_VBOs);
+ return s_canvas_mgr.check_volumes_outside_state(canvas, config);
}
-// Create 3D thick extrusion lines for object forming extrusions.
-// Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
-// one for perimeters, one for infill and one for supports.
-void _3DScene::_load_print_object_toolpaths(
- const PrintObject *print_object,
- GLVolumeCollection *volumes,
- const std::vector<std::string> &tool_colors_str,
- bool use_VBOs)
+bool _3DScene::move_volume_up(wxGLCanvas* canvas, unsigned int id)
{
- std::vector<float> tool_colors = parse_colors(tool_colors_str);
-
- struct Ctxt
- {
- const Points *shifted_copies;
- std::vector<const Layer*> layers;
- bool has_perimeters;
- bool has_infill;
- bool has_support;
- const std::vector<float>* tool_colors;
-
- // Number of vertices (each vertex is 6x4=24 bytes long)
- static const size_t alloc_size_max () { return 131072; } // 3.15MB
-// static const size_t alloc_size_max () { return 65536; } // 1.57MB
-// static const size_t alloc_size_max () { return 32768; } // 786kB
- static const size_t alloc_size_reserve() { return alloc_size_max() * 2; }
-
- 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
-
- // 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 extruder, int feature) const
- { return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(extruder - 1, 0)) : feature; }
- } ctxt;
-
- ctxt.shifted_copies = &print_object->_shifted_copies;
-
- // order layers by print_z
- ctxt.layers.reserve(print_object->layers.size() + print_object->support_layers.size());
- for (const Layer *layer : print_object->layers)
- ctxt.layers.push_back(layer);
- for (const Layer *layer : print_object->support_layers)
- ctxt.layers.push_back(layer);
- 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)
- ctxt.has_perimeters = print_object->state.is_done(posPerimeters);
- ctxt.has_infill = print_object->state.is_done(posInfill);
- ctxt.has_support = print_object->state.is_done(posSupportMaterial);
- ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors;
-
- BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start";
-
- //FIXME Improve the heuristics for a grain size.
- size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1));
- tbb::spin_mutex new_volume_mutex;
- auto new_volume = [volumes, &new_volume_mutex](const float *color) -> GLVolume* {
- auto *volume = new GLVolume(color);
- new_volume_mutex.lock();
- volume->outside_printer_detection_enabled = false;
- volumes->volumes.emplace_back(volume);
- new_volume_mutex.unlock();
- return volume;
- };
- const size_t volumes_cnt_initial = volumes->volumes.size();
- std::vector<GLVolumeCollection> volumes_per_thread(ctxt.layers.size());
- tbb::parallel_for(
- tbb::blocked_range<size_t>(0, ctxt.layers.size(), grain_size),
- [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) {
- std::vector<GLVolume*> vols;
- 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_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) };
- for (GLVolume *vol : vols)
- vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
- for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
- const Layer *layer = ctxt.layers[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->print_z) {
- vol.print_zs.push_back(layer->print_z);
- vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
- vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
- }
- }
- for (const Point &copy: *ctxt.shifted_copies) {
- for (const LayerRegion *layerm : layer->regions) {
- if (ctxt.has_perimeters)
- extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy,
- *vols[ctxt.volume_idx(layerm->region()->config.perimeter_extruder.value, 0)]);
- 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);
- if (! fill->entities.empty())
- extrusionentity_to_verts(*fill, float(layer->print_z), copy,
- *vols[ctxt.volume_idx(
- is_solid_infill(fill->entities.front()->role()) ?
- layerm->region()->config.solid_infill_extruder :
- layerm->region()->config.infill_extruder,
- 1)]);
- }
- }
- }
- 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)
- extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy,
- *vols[ctxt.volume_idx(
- (extrusion_entity->role() == erSupportMaterial) ?
- support_layer->object()->config.support_material_extruder :
- support_layer->object()->config.support_material_interface_extruder,
- 2)]);
- }
- }
- }
- for (size_t i = 0; i < vols.size(); ++ i) {
- GLVolume &vol = *vols[i];
- if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) {
- // Store the vertex arrays and restart their containers,
- vols[i] = new_volume(vol.color);
- GLVolume &vol_new = *vols[i];
- // Assign the large pre-allocated buffers to the new GLVolume.
- vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array);
- // Copy the content back to the old GLVolume.
- vol.indexed_vertex_array = vol_new.indexed_vertex_array;
- // Finalize a bounding box of the old GLVolume.
- vol.bounding_box = vol.indexed_vertex_array.bounding_box();
- // 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.
- vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
- }
- }
- }
- for (GLVolume *vol : vols) {
- vol->bounding_box = vol->indexed_vertex_array.bounding_box();
- vol->indexed_vertex_array.shrink_to_fit();
- }
- });
-
- BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results";
- // Remove empty volumes from the newly added volumes.
- volumes->volumes.erase(
- std::remove_if(volumes->volumes.begin() + volumes_cnt_initial, volumes->volumes.end(),
- [](const GLVolume *volume) { return volume->empty(); }),
- volumes->volumes.end());
- for (size_t i = volumes_cnt_initial; i < volumes->volumes.size(); ++ i)
- volumes->volumes[i]->indexed_vertex_array.finalize_geometry(use_VBOs);
-
- BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end";
-}
-
-void _3DScene::_load_wipe_tower_toolpaths(
- const Print *print,
- GLVolumeCollection *volumes,
- const std::vector<std::string> &tool_colors_str,
- bool use_VBOs)
-{
- if (print->m_wipe_tower_tool_changes.empty())
- return;
-
- std::vector<float> tool_colors = parse_colors(tool_colors_str);
-
- struct Ctxt
- {
- const Print *print;
- const std::vector<float> *tool_colors;
-
- // Number of vertices (each vertex is 6x4=24 bytes long)
- static const size_t alloc_size_max () { return 131072; } // 3.15MB
- static const size_t alloc_size_reserve() { return alloc_size_max() * 2; }
-
- 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) {
- return priming.empty() ?
- ((idx == print->m_wipe_tower_tool_changes.size()) ? final : print->m_wipe_tower_tool_changes[idx]) :
- ((idx == 0) ? priming : (idx == print->m_wipe_tower_tool_changes.size() + 1) ? final : print->m_wipe_tower_tool_changes[idx - 1]);
- }
- std::vector<WipeTower::ToolChangeResult> priming;
- std::vector<WipeTower::ToolChangeResult> final;
- } ctxt;
-
- ctxt.print = print;
- ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors;
- if (print->m_wipe_tower_priming)
- ctxt.priming.emplace_back(*print->m_wipe_tower_priming.get());
- if (print->m_wipe_tower_final_purge)
- ctxt.final.emplace_back(*print->m_wipe_tower_final_purge.get());
-
- BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start";
-
- //FIXME Improve the heuristics for a grain size.
- size_t n_items = print->m_wipe_tower_tool_changes.size() + (ctxt.priming.empty() ? 0 : 1);
- size_t grain_size = std::max(n_items / 128, size_t(1));
- tbb::spin_mutex new_volume_mutex;
- auto new_volume = [volumes, &new_volume_mutex](const float *color) -> GLVolume* {
- auto *volume = new GLVolume(color);
- new_volume_mutex.lock();
- volume->outside_printer_detection_enabled = false;
- volumes->volumes.emplace_back(volume);
- new_volume_mutex.unlock();
- return volume;
- };
- const size_t volumes_cnt_initial = 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.
- std::vector<GLVolume*> vols;
- 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)
- volume->indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
- 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) {
- vol.print_zs.push_back(layer.front().print_z);
- vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
- vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
- }
- }
- 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);
- for (; i < j; ++ i) {
- const WipeTower::Extrusion &e = extrusions.extrusions[i];
- assert(e.width > 0.f);
- const WipeTower::Extrusion &e_prev = *(&e - 1);
- lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y));
- widths.emplace_back(e.width);
- }
- 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];
- if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) {
- // Store the vertex arrays and restart their containers,
- vols[i] = new_volume(vol.color);
- GLVolume &vol_new = *vols[i];
- // Assign the large pre-allocated buffers to the new GLVolume.
- vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array);
- // Copy the content back to the old GLVolume.
- vol.indexed_vertex_array = vol_new.indexed_vertex_array;
- // Finalize a bounding box of the old GLVolume.
- vol.bounding_box = vol.indexed_vertex_array.bounding_box();
- // 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.
- vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
- }
- }
- for (GLVolume *vol : vols) {
- vol->bounding_box = vol->indexed_vertex_array.bounding_box();
- vol->indexed_vertex_array.shrink_to_fit();
- }
- });
-
- BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results";
- // Remove empty volumes from the newly added volumes.
- volumes->volumes.erase(
- std::remove_if(volumes->volumes.begin() + volumes_cnt_initial, volumes->volumes.end(),
- [](const GLVolume *volume) { return volume->empty(); }),
- volumes->volumes.end());
- for (size_t i = volumes_cnt_initial; i < volumes->volumes.size(); ++ i)
- volumes->volumes[i]->indexed_vertex_array.finalize_geometry(use_VBOs);
-
- BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end";
+ return s_canvas_mgr.move_volume_up(canvas, id);
}
-void _3DScene::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs)
+bool _3DScene::move_volume_down(wxGLCanvas* canvas, unsigned int id)
{
- // helper functions to select data in dependence of the extrusion view type
- struct Helper
- {
- static float path_filter(GCodePreviewData::Extrusion::EViewType type, const ExtrusionPath& path)
- {
- switch (type)
- {
- case GCodePreviewData::Extrusion::FeatureType:
- return (float)path.role();
- case GCodePreviewData::Extrusion::Height:
- return path.height;
- case GCodePreviewData::Extrusion::Width:
- return path.width;
- case GCodePreviewData::Extrusion::Feedrate:
- return path.feedrate;
- case GCodePreviewData::Extrusion::VolumetricRate:
- return path.feedrate * (float)path.mm3_per_mm;
- case GCodePreviewData::Extrusion::Tool:
- return (float)path.extruder_id;
- }
-
- return 0.0f;
- }
-
- static GCodePreviewData::Color path_color(const GCodePreviewData& data, const std::vector<float>& tool_colors, float value)
- {
- switch (data.extrusion.view_type)
- {
- case GCodePreviewData::Extrusion::FeatureType:
- return data.get_extrusion_role_color((ExtrusionRole)(int)value);
- case GCodePreviewData::Extrusion::Height:
- return data.get_height_color(value);
- case GCodePreviewData::Extrusion::Width:
- return data.get_width_color(value);
- case GCodePreviewData::Extrusion::Feedrate:
- return data.get_feedrate_color(value);
- case GCodePreviewData::Extrusion::VolumetricRate:
- return data.get_volumetric_rate_color(value);
- case GCodePreviewData::Extrusion::Tool:
- {
- GCodePreviewData::Color color;
- ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + (unsigned int)value * 4), 4 * sizeof(float));
- return color;
- }
- }
-
- return GCodePreviewData::Color::Dummy;
- }
- };
-
- // Helper structure for filters
- struct Filter
- {
- float value;
- ExtrusionRole role;
- GLVolume* volume;
-
- Filter(float value, ExtrusionRole role)
- : value(value)
- , role(role)
- , volume(nullptr)
- {
- }
-
- bool operator == (const Filter& other) const
- {
- if (value != other.value)
- return false;
-
- if (role != other.role)
- return false;
-
- return true;
- }
- };
+ return s_canvas_mgr.move_volume_down(canvas, id);
+}
- typedef std::vector<Filter> FiltersList;
- size_t initial_volumes_count = volumes.volumes.size();
+void _3DScene::set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections)
+{
+ s_canvas_mgr.set_objects_selections(canvas, selections);
+}
- // detects filters
- FiltersList filters;
- for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
- {
- for (const ExtrusionPath& path : layer.paths)
- {
- ExtrusionRole role = path.role();
- float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path);
- if (std::find(filters.begin(), filters.end(), Filter(path_filter, role)) == filters.end())
- filters.emplace_back(path_filter, role);
- }
- }
+void _3DScene::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config)
+{
+ s_canvas_mgr.set_config(canvas, config);
+}
- // nothing to render, return
- if (filters.empty())
- return;
+void _3DScene::set_print(wxGLCanvas* canvas, Print* print)
+{
+ s_canvas_mgr.set_print(canvas, print);
+}
- // creates a new volume for each filter
- for (Filter& filter : filters)
- {
- s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Extrusion, (unsigned int)filter.role, (unsigned int)volumes.volumes.size());
- GLVolume* volume = new GLVolume(Helper::path_color(preview_data, tool_colors, filter.value).rgba);
- if (volume != nullptr)
- {
- filter.volume = volume;
- volumes.volumes.emplace_back(volume);
- }
- else
- {
- // an error occourred - restore to previous state and return
- s_gcode_preview_volume_index.first_volumes.pop_back();
- if (initial_volumes_count != volumes.volumes.size())
- {
- std::vector<GLVolume*>::iterator begin = volumes.volumes.begin() + initial_volumes_count;
- std::vector<GLVolume*>::iterator end = volumes.volumes.end();
- for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it)
- {
- GLVolume* volume = *it;
- delete volume;
- }
- volumes.volumes.erase(begin, end);
- return;
- }
- }
- }
+void _3DScene::set_model(wxGLCanvas* canvas, Model* model)
+{
+ s_canvas_mgr.set_model(canvas, model);
+}
- // populates volumes
- for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
- {
- for (const ExtrusionPath& path : layer.paths)
- {
- float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path);
- FiltersList::iterator filter = std::find(filters.begin(), filters.end(), Filter(path_filter, path.role()));
- if (filter != filters.end())
- {
- filter->volume->print_zs.push_back(layer.z);
- filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.quad_indices.size());
- filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.triangle_indices.size());
+void _3DScene::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape)
+{
+ return s_canvas_mgr.set_bed_shape(canvas, shape);
+}
- extrusionentity_to_verts(path, layer.z, *filter->volume);
- }
- }
- }
+void _3DScene::set_auto_bed_shape(wxGLCanvas* canvas)
+{
+ return s_canvas_mgr.set_auto_bed_shape(canvas);
+}
- // finalize volumes and sends geometry to gpu
- if (volumes.volumes.size() > initial_volumes_count)
- {
- for (size_t i = initial_volumes_count; i < volumes.volumes.size(); ++i)
- {
- GLVolume* volume = volumes.volumes[i];
- volume->bounding_box = volume->indexed_vertex_array.bounding_box();
- volume->indexed_vertex_array.finalize_geometry(use_VBOs);
- }
- }
+BoundingBoxf3 _3DScene::get_volumes_bounding_box(wxGLCanvas* canvas)
+{
+ return s_canvas_mgr.get_volumes_bounding_box(canvas);
}
-void _3DScene::_load_gcode_travel_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs)
+void _3DScene::set_axes_length(wxGLCanvas* canvas, float length)
{
- size_t initial_volumes_count = volumes.volumes.size();
- s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Travel, 0, (unsigned int)initial_volumes_count);
+ s_canvas_mgr.set_axes_length(canvas, length);
+}
- bool res = true;
- switch (preview_data.extrusion.view_type)
- {
- case GCodePreviewData::Extrusion::Feedrate:
- {
- res = _travel_paths_by_feedrate(preview_data, volumes);
- break;
- }
- case GCodePreviewData::Extrusion::Tool:
- {
- res = _travel_paths_by_tool(preview_data, volumes, tool_colors);
- break;
- }
- default:
- {
- res = _travel_paths_by_type(preview_data, volumes);
- break;
- }
- }
+void _3DScene::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons)
+{
+ return s_canvas_mgr.set_cutting_plane(canvas, z, polygons);
+}
- if (!res)
- {
- // an error occourred - restore to previous state and return
- if (initial_volumes_count != volumes.volumes.size())
- {
- std::vector<GLVolume*>::iterator begin = volumes.volumes.begin() + initial_volumes_count;
- std::vector<GLVolume*>::iterator end = volumes.volumes.end();
- for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it)
- {
- GLVolume* volume = *it;
- delete volume;
- }
- volumes.volumes.erase(begin, end);
- }
+void _3DScene::set_color_by(wxGLCanvas* canvas, const std::string& value)
+{
+ return s_canvas_mgr.set_color_by(canvas, value);
+}
- return;
- }
+void _3DScene::set_select_by(wxGLCanvas* canvas, const std::string& value)
+{
+ return s_canvas_mgr.set_select_by(canvas, value);
+}
- // finalize volumes and sends geometry to gpu
- if (volumes.volumes.size() > initial_volumes_count)
- {
- for (size_t i = initial_volumes_count; i < volumes.volumes.size(); ++i)
- {
- GLVolume* volume = volumes.volumes[i];
- volume->bounding_box = volume->indexed_vertex_array.bounding_box();
- volume->indexed_vertex_array.finalize_geometry(use_VBOs);
- }
- }
+void _3DScene::set_drag_by(wxGLCanvas* canvas, const std::string& value)
+{
+ return s_canvas_mgr.set_drag_by(canvas, value);
}
-bool _3DScene::_travel_paths_by_type(const GCodePreviewData& preview_data, GLVolumeCollection& volumes)
+bool _3DScene::is_layers_editing_enabled(wxGLCanvas* canvas)
{
- // Helper structure for types
- struct Type
- {
- GCodePreviewData::Travel::EType value;
- GLVolume* volume;
+ return s_canvas_mgr.is_layers_editing_enabled(canvas);
+}
- explicit Type(GCodePreviewData::Travel::EType value)
- : value(value)
- , volume(nullptr)
- {
- }
+bool _3DScene::is_layers_editing_allowed(wxGLCanvas* canvas)
+{
+ return s_canvas_mgr.is_layers_editing_allowed(canvas);
+}
- bool operator == (const Type& other) const
- {
- return value == other.value;
- }
- };
+bool _3DScene::is_shader_enabled(wxGLCanvas* canvas)
+{
+ return s_canvas_mgr.is_shader_enabled(canvas);
+}
- typedef std::vector<Type> TypesList;
+bool _3DScene::is_reload_delayed(wxGLCanvas* canvas)
+{
+ return s_canvas_mgr.is_reload_delayed(canvas);
+}
- // colors travels by travel type
+void _3DScene::enable_layers_editing(wxGLCanvas* canvas, bool enable)
+{
+ s_canvas_mgr.enable_layers_editing(canvas, enable);
+}
- // detects types
- TypesList types;
- for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
- {
- if (std::find(types.begin(), types.end(), Type(polyline.type)) == types.end())
- types.emplace_back(polyline.type);
- }
+void _3DScene::enable_warning_texture(wxGLCanvas* canvas, bool enable)
+{
+ s_canvas_mgr.enable_warning_texture(canvas, enable);
+}
- // nothing to render, return
- if (types.empty())
- return true;
+void _3DScene::enable_legend_texture(wxGLCanvas* canvas, bool enable)
+{
+ s_canvas_mgr.enable_legend_texture(canvas, enable);
+}
- // creates a new volume for each type
- for (Type& type : types)
- {
- GLVolume* volume = new GLVolume(preview_data.travel.type_colors[type.value].rgba);
- if (volume == nullptr)
- return false;
- else
- {
- type.volume = volume;
- volumes.volumes.emplace_back(volume);
- }
- }
+void _3DScene::enable_picking(wxGLCanvas* canvas, bool enable)
+{
+ s_canvas_mgr.enable_picking(canvas, enable);
+}
- // populates volumes
- for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
- {
- TypesList::iterator type = std::find(types.begin(), types.end(), Type(polyline.type));
- if (type != types.end())
- {
- type->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
- type->volume->offsets.push_back(type->volume->indexed_vertex_array.quad_indices.size());
- type->volume->offsets.push_back(type->volume->indexed_vertex_array.triangle_indices.size());
+void _3DScene::enable_moving(wxGLCanvas* canvas, bool enable)
+{
+ s_canvas_mgr.enable_moving(canvas, enable);
+}
- polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *type->volume);
- }
- }
+void _3DScene::enable_gizmos(wxGLCanvas* canvas, bool enable)
+{
+ s_canvas_mgr.enable_gizmos(canvas, enable);
+}
- return true;
+void _3DScene::enable_shader(wxGLCanvas* canvas, bool enable)
+{
+ s_canvas_mgr.enable_shader(canvas, enable);
}
-bool _3DScene::_travel_paths_by_feedrate(const GCodePreviewData& preview_data, GLVolumeCollection& volumes)
+void _3DScene::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable)
{
- // Helper structure for feedrate
- struct Feedrate
- {
- float value;
- GLVolume* volume;
+ s_canvas_mgr.enable_force_zoom_to_bed(canvas, enable);
+}
- explicit Feedrate(float value)
- : value(value)
- , volume(nullptr)
- {
- }
+void _3DScene::allow_multisample(wxGLCanvas* canvas, bool allow)
+{
+ s_canvas_mgr.allow_multisample(canvas, allow);
+}
- bool operator == (const Feedrate& other) const
- {
- return value == other.value;
- }
- };
+void _3DScene::zoom_to_bed(wxGLCanvas* canvas)
+{
+ s_canvas_mgr.zoom_to_bed(canvas);
+}
- typedef std::vector<Feedrate> FeedratesList;
+void _3DScene::zoom_to_volumes(wxGLCanvas* canvas)
+{
+ s_canvas_mgr.zoom_to_volumes(canvas);
+}
- // colors travels by feedrate
+void _3DScene::select_view(wxGLCanvas* canvas, const std::string& direction)
+{
+ s_canvas_mgr.select_view(canvas, direction);
+}
- // detects feedrates
- FeedratesList feedrates;
- for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
- {
- if (std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate)) == feedrates.end())
- feedrates.emplace_back(polyline.feedrate);
- }
+void _3DScene::set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other)
+{
+ s_canvas_mgr.set_viewport_from_scene(canvas, other);
+}
- // nothing to render, return
- if (feedrates.empty())
- return true;
+void _3DScene::update_volumes_colors_by_extruder(wxGLCanvas* canvas)
+{
+ s_canvas_mgr.update_volumes_colors_by_extruder(canvas);
+}
- // creates a new volume for each feedrate
- for (Feedrate& feedrate : feedrates)
- {
- GLVolume* volume = new GLVolume(preview_data.get_feedrate_color(feedrate.value).rgba);
- if (volume == nullptr)
- return false;
- else
- {
- feedrate.volume = volume;
- volumes.volumes.emplace_back(volume);
- }
- }
+void _3DScene::render(wxGLCanvas* canvas)
+{
+ s_canvas_mgr.render(canvas);
+}
- // populates volumes
- for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
- {
- FeedratesList::iterator feedrate = std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate));
- if (feedrate != feedrates.end())
- {
- feedrate->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
- feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.quad_indices.size());
- feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.triangle_indices.size());
+std::vector<double> _3DScene::get_current_print_zs(wxGLCanvas* canvas, bool active_only)
+{
+ return s_canvas_mgr.get_current_print_zs(canvas, active_only);
+}
- polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *feedrate->volume);
- }
- }
+void _3DScene::set_toolpaths_range(wxGLCanvas* canvas, double low, double high)
+{
+ s_canvas_mgr.set_toolpaths_range(canvas, low, high);
+}
- return true;
+void _3DScene::register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback)
+{
+ s_canvas_mgr.register_on_viewport_changed_callback(canvas, callback);
}
-bool _3DScene::_travel_paths_by_tool(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors)
+void _3DScene::register_on_double_click_callback(wxGLCanvas* canvas, void* callback)
{
- // Helper structure for tool
- struct Tool
- {
- unsigned int value;
- GLVolume* volume;
+ s_canvas_mgr.register_on_double_click_callback(canvas, callback);
+}
- explicit Tool(unsigned int value)
- : value(value)
- , volume(nullptr)
- {
- }
+void _3DScene::register_on_right_click_callback(wxGLCanvas* canvas, void* callback)
+{
+ s_canvas_mgr.register_on_right_click_callback(canvas, callback);
+}
- bool operator == (const Tool& other) const
- {
- return value == other.value;
- }
- };
+void _3DScene::register_on_select_object_callback(wxGLCanvas* canvas, void* callback)
+{
+ s_canvas_mgr.register_on_select_object_callback(canvas, callback);
+}
- typedef std::vector<Tool> ToolsList;
+void _3DScene::register_on_model_update_callback(wxGLCanvas* canvas, void* callback)
+{
+ s_canvas_mgr.register_on_model_update_callback(canvas, callback);
+}
- // colors travels by tool
+void _3DScene::register_on_remove_object_callback(wxGLCanvas* canvas, void* callback)
+{
+ s_canvas_mgr.register_on_remove_object_callback(canvas, callback);
+}
- // detects tools
- ToolsList tools;
- for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
- {
- if (std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id)) == tools.end())
- tools.emplace_back(polyline.extruder_id);
- }
+void _3DScene::register_on_arrange_callback(wxGLCanvas* canvas, void* callback)
+{
+ s_canvas_mgr.register_on_arrange_callback(canvas, callback);
+}
- // nothing to render, return
- if (tools.empty())
- return true;
+void _3DScene::register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback)
+{
+ s_canvas_mgr.register_on_rotate_object_left_callback(canvas, callback);
+}
- // creates a new volume for each tool
- for (Tool& tool : tools)
- {
- GLVolume* volume = new GLVolume(tool_colors.data() + tool.value * 4);
- if (volume == nullptr)
- return false;
- else
- {
- tool.volume = volume;
- volumes.volumes.emplace_back(volume);
- }
- }
+void _3DScene::register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback)
+{
+ s_canvas_mgr.register_on_rotate_object_right_callback(canvas, callback);
+}
- // populates volumes
- for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
- {
- ToolsList::iterator tool = std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id));
- if (tool != tools.end())
- {
- tool->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
- tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.quad_indices.size());
- tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.triangle_indices.size());
+void _3DScene::register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback)
+{
+ s_canvas_mgr.register_on_scale_object_uniformly_callback(canvas, callback);
+}
- polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *tool->volume);
- }
- }
+void _3DScene::register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback)
+{
+ s_canvas_mgr.register_on_increase_objects_callback(canvas, callback);
+}
- return true;
+void _3DScene::register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback)
+{
+ s_canvas_mgr.register_on_decrease_objects_callback(canvas, callback);
}
-void _3DScene::_load_gcode_retractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs)
+void _3DScene::register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback)
{
- s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Retraction, 0, (unsigned int)volumes.volumes.size());
+ s_canvas_mgr.register_on_instance_moved_callback(canvas, callback);
+}
- // nothing to render, return
- if (preview_data.retraction.positions.empty())
- return;
+void _3DScene::register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback)
+{
+ s_canvas_mgr.register_on_wipe_tower_moved_callback(canvas, callback);
+}
- GLVolume* volume = new GLVolume(preview_data.retraction.color.rgba);
- if (volume != nullptr)
- {
- volumes.volumes.emplace_back(volume);
+void _3DScene::register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback)
+{
+ s_canvas_mgr.register_on_enable_action_buttons_callback(canvas, callback);
+}
- GCodePreviewData::Retraction::PositionsList copy(preview_data.retraction.positions);
- std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; });
+void _3DScene::register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback)
+{
+ s_canvas_mgr.register_on_gizmo_scale_uniformly_callback(canvas, callback);
+}
- for (const GCodePreviewData::Retraction::Position& position : copy)
- {
- volume->print_zs.push_back(unscale(position.position.z));
- volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
- volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
+static inline int hex_digit_to_int(const char c)
+{
+ return
+ (c >= '0' && c <= '9') ? int(c - '0') :
+ (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
+ (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
+}
- point3_to_verts(position.position, position.width, position.height, *volume);
+static inline std::vector<float> parse_colors(const std::vector<std::string> &scolors)
+{
+ std::vector<float> output(scolors.size() * 4, 1.f);
+ for (size_t i = 0; i < scolors.size(); ++ i) {
+ const std::string &scolor = scolors[i];
+ const char *c = scolor.data() + 1;
+ if (scolor.size() == 7 && scolor.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;
+ output[i * 4 + j] = float(digit1 * 16 + digit2) / 255.f;
+ }
}
-
- // finalize volumes and sends geometry to gpu
- volume->bounding_box = volume->indexed_vertex_array.bounding_box();
- volume->indexed_vertex_array.finalize_geometry(use_VBOs);
}
+ return output;
}
-void _3DScene::_load_gcode_unretractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs)
+std::vector<int> _3DScene::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs)
{
- s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Unretraction, 0, (unsigned int)volumes.volumes.size());
-
- // nothing to render, return
- if (preview_data.unretraction.positions.empty())
- return;
+ return s_canvas_mgr.load_object(canvas, model_object, obj_idx, instance_idxs);
+}
- GLVolume* volume = new GLVolume(preview_data.unretraction.color.rgba);
- if (volume != nullptr)
- {
- volumes.volumes.emplace_back(volume);
+std::vector<int> _3DScene::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx)
+{
+ return s_canvas_mgr.load_object(canvas, model, obj_idx);
+}
- GCodePreviewData::Retraction::PositionsList copy(preview_data.unretraction.positions);
- std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; });
+void _3DScene::reload_scene(wxGLCanvas* canvas, bool force)
+{
+ s_canvas_mgr.reload_scene(canvas, force);
+}
- for (const GCodePreviewData::Retraction::Position& position : copy)
- {
- volume->print_zs.push_back(unscale(position.position.z));
- volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
- volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
+void _3DScene::load_print_toolpaths(wxGLCanvas* canvas)
+{
+ s_canvas_mgr.load_print_toolpaths(canvas);
+}
- point3_to_verts(position.position, position.width, position.height, *volume);
- }
+void _3DScene::load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& str_tool_colors)
+{
+ s_canvas_mgr.load_print_object_toolpaths(canvas, print_object, str_tool_colors);
+}
- // finalize volumes and sends geometry to gpu
- volume->bounding_box = volume->indexed_vertex_array.bounding_box();
- volume->indexed_vertex_array.finalize_geometry(use_VBOs);
- }
+void _3DScene::load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors)
+{
+ s_canvas_mgr.load_wipe_tower_toolpaths(canvas, str_tool_colors);
}
-void _3DScene::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data, GLVolumeCollection& volumes)
+void _3DScene::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors)
{
- unsigned int size = (unsigned int)s_gcode_preview_volume_index.first_volumes.size();
- for (unsigned int i = 0; i < size; ++i)
- {
- std::vector<GLVolume*>::iterator begin = volumes.volumes.begin() + s_gcode_preview_volume_index.first_volumes[i].id;
- std::vector<GLVolume*>::iterator end = (i + 1 < size) ? volumes.volumes.begin() + s_gcode_preview_volume_index.first_volumes[i + 1].id : volumes.volumes.end();
+ s_canvas_mgr.load_gcode_preview(canvas, preview_data, str_tool_colors);
+}
- for (std::vector<GLVolume*>::iterator it = begin; it != end; ++it)
- {
- GLVolume* volume = *it;
- volume->outside_printer_detection_enabled = false;
+void _3DScene::generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors)
+{
+ s_legend_texture.generate(preview_data, tool_colors);
+}
- switch (s_gcode_preview_volume_index.first_volumes[i].type)
- {
- case GCodePreviewVolumeIndex::Extrusion:
- {
- if ((ExtrusionRole)s_gcode_preview_volume_index.first_volumes[i].flag == erCustom)
- volume->zoom_to_volumes = false;
-
- volume->is_active = preview_data.extrusion.is_role_flag_set((ExtrusionRole)s_gcode_preview_volume_index.first_volumes[i].flag);
- break;
- }
- case GCodePreviewVolumeIndex::Travel:
- {
- volume->is_active = preview_data.travel.is_visible;
- volume->zoom_to_volumes = false;
- break;
- }
- case GCodePreviewVolumeIndex::Retraction:
- {
- volume->is_active = preview_data.retraction.is_visible;
- volume->zoom_to_volumes = false;
- break;
- }
- case GCodePreviewVolumeIndex::Unretraction:
- {
- volume->is_active = preview_data.unretraction.is_visible;
- volume->zoom_to_volumes = false;
- break;
- }
- case GCodePreviewVolumeIndex::Shell:
- {
- volume->is_active = preview_data.shell.is_visible;
- volume->color[3] = 0.25f;
- volume->zoom_to_volumes = false;
- break;
- }
- default:
- {
- volume->is_active = false;
- volume->zoom_to_volumes = false;
- break;
- }
- }
- }
- }
+unsigned int _3DScene::get_legend_texture_width()
+{
+ return s_legend_texture.get_texture_width();
}
-void _3DScene::_generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors)
+unsigned int _3DScene::get_legend_texture_height()
{
- s_legend_texture.generate(preview_data, tool_colors);
+ return s_legend_texture.get_texture_height();
}
-void _3DScene::_load_shells(const Print& print, GLVolumeCollection& volumes, bool use_VBOs)
+void _3DScene::reset_legend_texture()
{
- size_t initial_volumes_count = volumes.volumes.size();
- s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Shell, 0, (unsigned int)initial_volumes_count);
+ s_legend_texture.reset_texture();
+}
- if (print.objects.empty())
- // nothing to render, return
- return;
+unsigned int _3DScene::finalize_legend_texture()
+{
+ return s_legend_texture.finalize();
+}
- // adds objects' volumes
- unsigned int object_id = 0;
- for (PrintObject* obj : print.objects)
- {
- ModelObject* model_obj = obj->model_object();
+unsigned int _3DScene::get_warning_texture_width()
+{
+ return s_warning_texture.get_texture_width();
+}
- std::vector<int> instance_ids(model_obj->instances.size());
- for (int i = 0; i < model_obj->instances.size(); ++i)
- {
- instance_ids[i] = i;
- }
+unsigned int _3DScene::get_warning_texture_height()
+{
+ return s_warning_texture.get_texture_height();
+}
- for (ModelInstance* instance : model_obj->instances)
- {
- volumes.load_object(model_obj, object_id, instance_ids, "object", "object", "object", use_VBOs);
- }
+void _3DScene::generate_warning_texture(const std::string& msg)
+{
+ s_warning_texture.generate(msg);
+}
- ++object_id;
- }
+void _3DScene::reset_warning_texture()
+{
+ s_warning_texture.reset_texture();
+}
- // adds wipe tower's volume
- coordf_t max_z = print.objects[0]->model_object()->get_model()->bounding_box().max.z;
- const PrintConfig& config = print.config;
- unsigned int extruders_count = config.nozzle_diameter.size();
- if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) {
- const float width_per_extruder = 15.f; // a simple workaround after wipe_tower_per_color_wipe got obsolete
- volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, width_per_extruder * (extruders_count - 1), max_z, config.wipe_tower_rotation_angle, use_VBOs);
- }
+unsigned int _3DScene::finalize_warning_texture()
+{
+ return s_warning_texture.finalize();
}
} // namespace Slic3r
diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp
index a417f5f9d..c6a166397 100644
--- a/xs/src/slic3r/GUI/3DScene.hpp
+++ b/xs/src/slic3r/GUI/3DScene.hpp
@@ -6,8 +6,10 @@
#include "../../libslic3r/Line.hpp"
#include "../../libslic3r/TriangleMesh.hpp"
#include "../../libslic3r/Utils.hpp"
+#include "../../slic3r/GUI/GLCanvas3DManager.hpp"
class wxBitmap;
+class wxWindow;
namespace Slic3r {
@@ -17,6 +19,11 @@ class Model;
class ModelObject;
class GCodePreviewData;
class DynamicPrintConfig;
+class ExtrusionPath;
+class ExtrusionMultiPath;
+class ExtrusionLoop;
+class ExtrusionEntity;
+class ExtrusionEntityCollection;
// A container for interleaved arrays of 3D vertices and normals,
// possibly indexed by triangles and / or quads.
@@ -305,9 +312,9 @@ public:
// Boolean: Is mouse over this object?
bool hover;
// Wheter or not this volume has been generated from a modifier
- bool is_modifier;
+ bool is_modifier;
// Wheter or not this volume has been generated from the wipe tower
- bool is_wipe_tower;
+ bool is_wipe_tower;
// Interleaved triangles & normals with indexed triangles & quads.
GLIndexedVertexArray indexed_vertex_array;
@@ -437,35 +444,6 @@ private:
class _3DScene
{
- struct GCodePreviewVolumeIndex
- {
- enum EType
- {
- Extrusion,
- Travel,
- Retraction,
- Unretraction,
- Shell,
- Num_Geometry_Types
- };
-
- struct FirstVolume
- {
- EType type;
- unsigned int flag;
- // Index of the first volume in a GLVolumeCollection.
- unsigned int id;
-
- FirstVolume(EType type, unsigned int flag, unsigned int id) : type(type), flag(flag), id(id) {}
- };
-
- std::vector<FirstVolume> first_volumes;
-
- void reset() { first_volumes.clear(); }
- };
-
- static GCodePreviewVolumeIndex s_gcode_preview_volume_index;
-
class TextureBase
{
protected:
@@ -525,12 +503,106 @@ class _3DScene
static LegendTexture s_legend_texture;
static WarningTexture s_warning_texture;
+ static GUI::GLCanvas3DManager s_canvas_mgr;
public:
- static void _glew_init();
+ static void init_gl();
+ static std::string get_gl_info(bool format_as_html, bool extensions);
+ static bool use_VBOs();
+
+ static bool add_canvas(wxGLCanvas* canvas);
+ static bool remove_canvas(wxGLCanvas* canvas);
+ static void remove_all_canvases();
+
+ static bool init(wxGLCanvas* canvas);
+
+ static void set_active(wxGLCanvas* canvas, bool active);
+
+ static unsigned int get_volumes_count(wxGLCanvas* canvas);
+ static void reset_volumes(wxGLCanvas* canvas);
+ static void deselect_volumes(wxGLCanvas* canvas);
+ static void select_volume(wxGLCanvas* canvas, unsigned int id);
+ static void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections);
+ static bool check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config);
+ static bool move_volume_up(wxGLCanvas* canvas, unsigned int id);
+ static bool move_volume_down(wxGLCanvas* canvas, unsigned int id);
+
+ static void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections);
+
+ static void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config);
+ static void set_print(wxGLCanvas* canvas, Print* print);
+ static void set_model(wxGLCanvas* canvas, Model* model);
+
+ static void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape);
+ static void set_auto_bed_shape(wxGLCanvas* canvas);
+
+ static BoundingBoxf3 get_volumes_bounding_box(wxGLCanvas* canvas);
+
+ static void set_axes_length(wxGLCanvas* canvas, float length);
- static void load_gcode_preview(const Print* print, const GCodePreviewData* preview_data, GLVolumeCollection* volumes, const std::vector<std::string>& str_tool_colors, bool use_VBOs);
+ static void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons);
+ static void set_color_by(wxGLCanvas* canvas, const std::string& value);
+ static void set_select_by(wxGLCanvas* canvas, const std::string& value);
+ static void set_drag_by(wxGLCanvas* canvas, const std::string& value);
+
+ static bool is_layers_editing_enabled(wxGLCanvas* canvas);
+ static bool is_layers_editing_allowed(wxGLCanvas* canvas);
+ static bool is_shader_enabled(wxGLCanvas* canvas);
+
+ static bool is_reload_delayed(wxGLCanvas* canvas);
+
+ static void enable_layers_editing(wxGLCanvas* canvas, bool enable);
+ static void enable_warning_texture(wxGLCanvas* canvas, bool enable);
+ static void enable_legend_texture(wxGLCanvas* canvas, bool enable);
+ static void enable_picking(wxGLCanvas* canvas, bool enable);
+ static void enable_moving(wxGLCanvas* canvas, bool enable);
+ static void enable_gizmos(wxGLCanvas* canvas, bool enable);
+ static void enable_shader(wxGLCanvas* canvas, bool enable);
+ static void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable);
+ static void allow_multisample(wxGLCanvas* canvas, bool allow);
+
+ static void zoom_to_bed(wxGLCanvas* canvas);
+ static void zoom_to_volumes(wxGLCanvas* canvas);
+ static void select_view(wxGLCanvas* canvas, const std::string& direction);
+ static void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other);
+
+ static void update_volumes_colors_by_extruder(wxGLCanvas* canvas);
+
+ static void render(wxGLCanvas* canvas);
+
+ static std::vector<double> get_current_print_zs(wxGLCanvas* canvas, bool active_only);
+ static void set_toolpaths_range(wxGLCanvas* canvas, double low, double high);
+
+ static void register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_double_click_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_right_click_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_select_object_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_model_update_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_remove_object_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_arrange_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback);
+ static void register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback);
+
+ static std::vector<int> load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs);
+ static std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx);
+
+ static void reload_scene(wxGLCanvas* canvas, bool force);
+
+ static void load_print_toolpaths(wxGLCanvas* canvas);
+ static void load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& str_tool_colors);
+ static void load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors);
+ static void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors);
+
+ // generates the legend texture in dependence of the current shown view type
+ static void generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
static unsigned int get_legend_texture_width();
static unsigned int get_legend_texture_height();
@@ -545,42 +617,16 @@ public:
static void reset_warning_texture();
static unsigned int finalize_warning_texture();
- static void _load_print_toolpaths(
- const Print *print,
- GLVolumeCollection *volumes,
- const std::vector<std::string> &tool_colors,
- bool use_VBOs);
-
- static void _load_print_object_toolpaths(
- const PrintObject *print_object,
- GLVolumeCollection *volumes,
- const std::vector<std::string> &tool_colors,
- bool use_VBOs);
-
- static void _load_wipe_tower_toolpaths(
- const Print *print,
- GLVolumeCollection *volumes,
- const std::vector<std::string> &tool_colors_str,
- bool use_VBOs);
-
-private:
- // generates gcode extrusion paths geometry
- static void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs);
- // generates gcode travel paths geometry
- static void _load_gcode_travel_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs);
- static bool _travel_paths_by_type(const GCodePreviewData& preview_data, GLVolumeCollection& volumes);
- static bool _travel_paths_by_feedrate(const GCodePreviewData& preview_data, GLVolumeCollection& volumes);
- static bool _travel_paths_by_tool(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors);
- // generates gcode retractions geometry
- static void _load_gcode_retractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs);
- // generates gcode unretractions geometry
- static void _load_gcode_unretractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs);
- // sets gcode geometry visibility according to user selection
- static void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data, GLVolumeCollection& volumes);
- // generates the legend texture in dependence of the current shown view type
- static void _generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
- // generates objects and wipe tower geometry
- static void _load_shells(const Print& print, GLVolumeCollection& volumes, bool use_VBOs);
+ static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GLVolume& volume);
+ static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GLVolume& volume);
+ static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume);
+ static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume);
+ static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume);
+ static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume);
+ static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume);
+ static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume);
+ static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume);
+ static void point3_to_verts(const Point3& point, double width, double height, GLVolume& volume);
};
}
diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
new file mode 100644
index 000000000..f9c10017e
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
@@ -0,0 +1,4308 @@
+#include "GLCanvas3D.hpp"
+
+#include "../../slic3r/GUI/3DScene.hpp"
+#include "../../slic3r/GUI/GLShader.hpp"
+#include "../../slic3r/GUI/GUI.hpp"
+#include "../../slic3r/GUI/PresetBundle.hpp"
+#include "../../slic3r/GUI/GLGizmo.hpp"
+#include "../../libslic3r/ClipperUtils.hpp"
+#include "../../libslic3r/PrintConfig.hpp"
+#include "../../libslic3r/Print.hpp"
+#include "../../libslic3r/GCode/PreviewData.hpp"
+
+#include <GL/glew.h>
+
+#include <wx/glcanvas.h>
+#include <wx/timer.h>
+
+#include <tbb/parallel_for.h>
+#include <tbb/spin_mutex.h>
+
+#include <boost/log/trivial.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <iostream>
+#include <float.h>
+#include <algorithm>
+
+static const float TRACKBALLSIZE = 0.8f;
+static const float GIMBALL_LOCK_THETA_MAX = 180.0f;
+static const float GROUND_Z = -0.02f;
+
+// phi / theta angles to orient the camera.
+static const float VIEW_DEFAULT[2] = { 45.0f, 45.0f };
+static const float VIEW_LEFT[2] = { 90.0f, 90.0f };
+static const float VIEW_RIGHT[2] = { -90.0f, 90.0f };
+static const float VIEW_TOP[2] = { 0.0f, 0.0f };
+static const float VIEW_BOTTOM[2] = { 0.0f, 180.0f };
+static const float VIEW_FRONT[2] = { 0.0f, 90.0f };
+static const float VIEW_REAR[2] = { 180.0f, 90.0f };
+
+static const float VARIABLE_LAYER_THICKNESS_BAR_WIDTH = 70.0f;
+static const float VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT = 22.0f;
+
+namespace Slic3r {
+namespace GUI {
+
+bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords)
+{
+ m_vertices.clear();
+ m_tex_coords.clear();
+
+ unsigned int v_size = 9 * (unsigned int)triangles.size();
+ unsigned int t_size = 6 * (unsigned int)triangles.size();
+ if (v_size == 0)
+ return false;
+
+ m_vertices = std::vector<float>(v_size, 0.0f);
+ if (generate_tex_coords)
+ m_tex_coords = std::vector<float>(t_size, 0.0f);
+
+ float min_x = (float)unscale(triangles[0].points[0].x);
+ float min_y = (float)unscale(triangles[0].points[0].y);
+ float max_x = min_x;
+ float max_y = min_y;
+
+ unsigned int v_coord = 0;
+ unsigned int t_coord = 0;
+ for (const Polygon& t : triangles)
+ {
+ for (unsigned int v = 0; v < 3; ++v)
+ {
+ const Point& p = t.points[v];
+ float x = (float)unscale(p.x);
+ float y = (float)unscale(p.y);
+
+ m_vertices[v_coord++] = x;
+ m_vertices[v_coord++] = y;
+ m_vertices[v_coord++] = z;
+
+ if (generate_tex_coords)
+ {
+ m_tex_coords[t_coord++] = x;
+ m_tex_coords[t_coord++] = y;
+
+ min_x = std::min(min_x, x);
+ max_x = std::max(max_x, x);
+ min_y = std::min(min_y, y);
+ max_y = std::max(max_y, y);
+ }
+ }
+ }
+
+ if (generate_tex_coords)
+ {
+ float size_x = max_x - min_x;
+ float size_y = max_y - min_y;
+
+ if ((size_x != 0.0f) && (size_y != 0.0f))
+ {
+ float inv_size_x = 1.0f / size_x;
+ float inv_size_y = -1.0f / size_y;
+ for (unsigned int i = 0; i < m_tex_coords.size(); i += 2)
+ {
+ m_tex_coords[i] *= inv_size_x;
+ m_tex_coords[i + 1] *= inv_size_y;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool GeometryBuffer::set_from_lines(const Lines& lines, float z)
+{
+ m_vertices.clear();
+ m_tex_coords.clear();
+
+ unsigned int size = 6 * (unsigned int)lines.size();
+ if (size == 0)
+ return false;
+
+ m_vertices = std::vector<float>(size, 0.0f);
+
+ unsigned int coord = 0;
+ for (const Line& l : lines)
+ {
+ m_vertices[coord++] = (float)unscale(l.a.x);
+ m_vertices[coord++] = (float)unscale(l.a.y);
+ m_vertices[coord++] = z;
+ m_vertices[coord++] = (float)unscale(l.b.x);
+ m_vertices[coord++] = (float)unscale(l.b.y);
+ m_vertices[coord++] = z;
+ }
+
+ return true;
+}
+
+const float* GeometryBuffer::get_vertices() const
+{
+ return m_vertices.data();
+}
+
+const float* GeometryBuffer::get_tex_coords() const
+{
+ return m_tex_coords.data();
+}
+
+unsigned int GeometryBuffer::get_vertices_count() const
+{
+ return (unsigned int)m_vertices.size() / 3;
+}
+
+Size::Size()
+ : m_width(0)
+ , m_height(0)
+{
+}
+
+Size::Size(int width, int height)
+ : m_width(width)
+ , m_height(height)
+{
+}
+
+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;
+}
+
+Rect::Rect()
+ : m_left(0.0f)
+ , m_top(0.0f)
+ , m_right(0.0f)
+ , m_bottom(0.0f)
+{
+}
+
+Rect::Rect(float left, float top, float right, float bottom)
+ : m_left(left)
+ , m_top(top)
+ , m_right(right)
+ , m_bottom(bottom)
+{
+}
+
+float Rect::get_left() const
+{
+ return m_left;
+}
+
+void Rect::set_left(float left)
+{
+ m_left = left;
+}
+
+float Rect::get_top() const
+{
+ return m_top;
+}
+
+void Rect::set_top(float top)
+{
+ m_top = top;
+}
+
+float Rect::get_right() const
+{
+ return m_right;
+}
+
+void Rect::set_right(float right)
+{
+ m_right = right;
+}
+
+float Rect::get_bottom() const
+{
+ return m_bottom;
+}
+
+void Rect::set_bottom(float bottom)
+{
+ m_bottom = bottom;
+}
+
+GLCanvas3D::Camera::Camera()
+ : type(Ortho)
+ , zoom(1.0f)
+ , phi(45.0f)
+// , distance(0.0f)
+ , target(0.0, 0.0, 0.0)
+ , m_theta(45.0f)
+{
+}
+
+std::string GLCanvas3D::Camera::get_type_as_string() const
+{
+ switch (type)
+ {
+ default:
+ case Unknown:
+ return "unknown";
+// case Perspective:
+// return "perspective";
+ case Ortho:
+ return "ortho";
+ };
+}
+
+float GLCanvas3D::Camera::get_theta() const
+{
+ return m_theta;
+}
+
+void GLCanvas3D::Camera::set_theta(float theta)
+{
+ m_theta = clamp(0.0f, GIMBALL_LOCK_THETA_MAX, theta);
+}
+
+GLCanvas3D::Bed::Bed()
+ : m_type(Custom)
+{
+}
+
+bool GLCanvas3D::Bed::is_prusa() const
+{
+ return (m_type == MK2) || (m_type == MK3);
+}
+
+bool GLCanvas3D::Bed::is_custom() const
+{
+ return m_type == Custom;
+}
+
+const Pointfs& GLCanvas3D::Bed::get_shape() const
+{
+ return m_shape;
+}
+
+void GLCanvas3D::Bed::set_shape(const Pointfs& shape)
+{
+ m_shape = shape;
+ m_type = _detect_type();
+
+ _calc_bounding_box();
+
+ ExPolygon poly;
+ for (const Pointf& p : m_shape)
+ {
+ poly.contour.append(Point(scale_(p.x), scale_(p.y)));
+ }
+
+ _calc_triangles(poly);
+
+ const BoundingBox& bed_bbox = poly.contour.bounding_box();
+ _calc_gridlines(poly, bed_bbox);
+
+ m_polygon = offset_ex(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0].contour;
+}
+
+const BoundingBoxf3& GLCanvas3D::Bed::get_bounding_box() const
+{
+ return m_bounding_box;
+}
+
+bool GLCanvas3D::Bed::contains(const Point& point) const
+{
+ return m_polygon.contains(point);
+}
+
+Point GLCanvas3D::Bed::point_projection(const Point& point) const
+{
+ return m_polygon.point_projection(point);
+}
+
+void GLCanvas3D::Bed::render(float theta) const
+{
+ switch (m_type)
+ {
+ case MK2:
+ {
+ _render_mk2(theta);
+ break;
+ }
+ case MK3:
+ {
+ _render_mk3(theta);
+ break;
+ }
+ default:
+ case Custom:
+ {
+ _render_custom();
+ break;
+ }
+ }
+}
+
+void GLCanvas3D::Bed::_calc_bounding_box()
+{
+ m_bounding_box = BoundingBoxf3();
+ for (const Pointf& p : m_shape)
+ {
+ m_bounding_box.merge(Pointf3(p.x, p.y, 0.0));
+ }
+}
+
+void GLCanvas3D::Bed::_calc_triangles(const ExPolygon& poly)
+{
+ Polygons triangles;
+ poly.triangulate(&triangles);
+
+ if (!m_triangles.set_from_triangles(triangles, GROUND_Z, m_type != Custom))
+ printf("Unable to create bed triangles\n");
+}
+
+void GLCanvas3D::Bed::_calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox)
+{
+ Polylines axes_lines;
+ for (coord_t x = bed_bbox.min.x; x <= bed_bbox.max.x; x += scale_(10.0))
+ {
+ Polyline line;
+ line.append(Point(x, bed_bbox.min.y));
+ line.append(Point(x, bed_bbox.max.y));
+ axes_lines.push_back(line);
+ }
+ for (coord_t y = bed_bbox.min.y; y <= bed_bbox.max.y; y += scale_(10.0))
+ {
+ Polyline line;
+ line.append(Point(bed_bbox.min.x, y));
+ line.append(Point(bed_bbox.max.x, y));
+ axes_lines.push_back(line);
+ }
+
+ // clip with a slightly grown expolygon because our lines lay on the contours and may get erroneously clipped
+ Lines gridlines = to_lines(intersection_pl(axes_lines, offset(poly, SCALED_EPSILON)));
+
+ // append bed contours
+ Lines contour_lines = to_lines(poly);
+ std::copy(contour_lines.begin(), contour_lines.end(), std::back_inserter(gridlines));
+
+ if (!m_gridlines.set_from_lines(gridlines, GROUND_Z))
+ printf("Unable to create bed grid lines\n");
+}
+
+GLCanvas3D::Bed::EType GLCanvas3D::Bed::_detect_type() const
+{
+ EType type = Custom;
+
+ const PresetBundle* bundle = get_preset_bundle();
+ if (bundle != nullptr)
+ {
+ const Preset& curr = bundle->printers.get_selected_preset();
+ if (curr.config.has("bed_shape") && _are_equal(m_shape, dynamic_cast<const ConfigOptionPoints*>(curr.config.option("bed_shape"))->values))
+ {
+ if ((curr.vendor != nullptr) && (curr.vendor->name == "Prusa Research"))
+ {
+ if (boost::contains(curr.name, "MK2"))
+ type = MK2;
+ else if (boost::contains(curr.name, "MK3"))
+ type = MK3;
+ }
+ }
+ }
+
+ return type;
+}
+
+void GLCanvas3D::Bed::_render_mk2(float theta) const
+{
+ std::string filename = resources_dir() + "/icons/bed/mk2_top.png";
+ if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename))
+ {
+ if (!m_top_texture.load_from_file(filename, true))
+ {
+ _render_custom();
+ return;
+ }
+ }
+
+ filename = resources_dir() + "/icons/bed/mk2_bottom.png";
+ if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename))
+ {
+ if (!m_bottom_texture.load_from_file(filename, true))
+ {
+ _render_custom();
+ return;
+ }
+ }
+
+ _render_prusa(theta);
+}
+
+void GLCanvas3D::Bed::_render_mk3(float theta) const
+{
+ std::string filename = resources_dir() + "/icons/bed/mk3_top.png";
+ if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename))
+ {
+ if (!m_top_texture.load_from_file(filename, true))
+ {
+ _render_custom();
+ return;
+ }
+ }
+
+ filename = resources_dir() + "/icons/bed/mk3_bottom.png";
+ if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename))
+ {
+ if (!m_bottom_texture.load_from_file(filename, true))
+ {
+ _render_custom();
+ return;
+ }
+ }
+
+ _render_prusa(theta);
+}
+
+void GLCanvas3D::Bed::_render_prusa(float theta) const
+{
+ unsigned int triangles_vcount = m_triangles.get_vertices_count();
+ if (triangles_vcount > 0)
+ {
+ ::glEnable(GL_DEPTH_TEST);
+
+ ::glEnable(GL_BLEND);
+ ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ ::glEnable(GL_TEXTURE_2D);
+
+ ::glEnableClientState(GL_VERTEX_ARRAY);
+ ::glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+
+ if (theta > 90.0f)
+ ::glFrontFace(GL_CW);
+
+ ::glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+ ::glBindTexture(GL_TEXTURE_2D, (theta <= 90.0f) ? (GLuint)m_top_texture.get_id() : (GLuint)m_bottom_texture.get_id());
+ ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices());
+ ::glTexCoordPointer(2, GL_FLOAT, 0, (GLvoid*)m_triangles.get_tex_coords());
+ ::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount);
+
+ if (theta > 90.0f)
+ ::glFrontFace(GL_CCW);
+
+ ::glBindTexture(GL_TEXTURE_2D, 0);
+ ::glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+ ::glDisableClientState(GL_VERTEX_ARRAY);
+
+ ::glDisable(GL_TEXTURE_2D);
+
+ ::glDisable(GL_BLEND);
+ }
+}
+
+void GLCanvas3D::Bed::_render_custom() const
+{
+ m_top_texture.reset();
+ m_bottom_texture.reset();
+
+ unsigned int triangles_vcount = m_triangles.get_vertices_count();
+ if (triangles_vcount > 0)
+ {
+ ::glEnable(GL_LIGHTING);
+ ::glDisable(GL_DEPTH_TEST);
+
+ ::glEnable(GL_BLEND);
+ ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ ::glEnableClientState(GL_VERTEX_ARRAY);
+
+ ::glColor4f(0.8f, 0.6f, 0.5f, 0.4f);
+ ::glNormal3d(0.0f, 0.0f, 1.0f);
+ ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices());
+ ::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount);
+
+ // draw grid
+ unsigned int gridlines_vcount = m_gridlines.get_vertices_count();
+
+ // we need depth test for grid, otherwise it would disappear when looking the object from below
+ ::glEnable(GL_DEPTH_TEST);
+ ::glLineWidth(3.0f);
+ ::glColor4f(0.2f, 0.2f, 0.2f, 0.4f);
+ ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_gridlines.get_vertices());
+ ::glDrawArrays(GL_LINES, 0, (GLsizei)gridlines_vcount);
+
+ ::glDisableClientState(GL_VERTEX_ARRAY);
+
+ ::glDisable(GL_BLEND);
+ }
+}
+
+bool GLCanvas3D::Bed::_are_equal(const Pointfs& bed_1, const Pointfs& bed_2)
+{
+ if (bed_1.size() != bed_2.size())
+ return false;
+
+ for (unsigned int i = 0; i < (unsigned int)bed_1.size(); ++i)
+ {
+ if (bed_1[i] != bed_2[i])
+ return false;
+ }
+
+ return true;
+}
+
+GLCanvas3D::Axes::Axes()
+ : length(0.0f)
+{
+}
+
+void GLCanvas3D::Axes::render(bool depth_test) const
+{
+ ::glDisable(GL_LIGHTING);
+ if (depth_test)
+ ::glEnable(GL_DEPTH_TEST);
+ else
+ ::glDisable(GL_DEPTH_TEST);
+
+ ::glLineWidth(2.0f);
+ ::glBegin(GL_LINES);
+ // draw line for x axis
+ ::glColor3f(1.0f, 0.0f, 0.0f);
+ ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z);
+ ::glVertex3f((GLfloat)origin.x + length, (GLfloat)origin.y, (GLfloat)origin.z);
+ // draw line for y axis
+ ::glColor3f(0.0f, 1.0f, 0.0f);
+ ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z);
+ ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y + length, (GLfloat)origin.z);
+ ::glEnd();
+ // draw line for Z axis
+ // (re-enable depth test so that axis is correctly shown when objects are behind it)
+ if (!depth_test)
+ ::glEnable(GL_DEPTH_TEST);
+
+ ::glBegin(GL_LINES);
+ ::glColor3f(0.0f, 0.0f, 1.0f);
+ ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z);
+ ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z + length);
+ ::glEnd();
+}
+
+GLCanvas3D::CuttingPlane::CuttingPlane()
+ : m_z(-1.0f)
+{
+}
+
+bool GLCanvas3D::CuttingPlane::set(float z, const ExPolygons& polygons)
+{
+ m_z = z;
+
+ // grow slices in order to display them better
+ ExPolygons expolygons = offset_ex(polygons, scale_(0.1));
+ Lines lines = to_lines(expolygons);
+ return m_lines.set_from_lines(lines, m_z);
+}
+
+void GLCanvas3D::CuttingPlane::render(const BoundingBoxf3& bb) const
+{
+ ::glDisable(GL_LIGHTING);
+ _render_plane(bb);
+ _render_contour();
+}
+
+void GLCanvas3D::CuttingPlane::_render_plane(const BoundingBoxf3& bb) const
+{
+ if (m_z >= 0.0f)
+ {
+ ::glDisable(GL_CULL_FACE);
+ ::glEnable(GL_BLEND);
+ ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ float margin = 20.0f;
+ float min_x = bb.min.x - margin;
+ float max_x = bb.max.x + margin;
+ float min_y = bb.min.y - margin;
+ float max_y = bb.max.y + margin;
+
+ ::glBegin(GL_QUADS);
+ ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f);
+ ::glVertex3f(min_x, min_y, m_z);
+ ::glVertex3f(max_x, min_y, m_z);
+ ::glVertex3f(max_x, max_y, m_z);
+ ::glVertex3f(min_x, max_y, m_z);
+ ::glEnd();
+
+ ::glEnable(GL_CULL_FACE);
+ ::glDisable(GL_BLEND);
+ }
+}
+
+void GLCanvas3D::CuttingPlane::_render_contour() const
+{
+ ::glEnableClientState(GL_VERTEX_ARRAY);
+
+ if (m_z >= 0.0f)
+ {
+ unsigned int lines_vcount = m_lines.get_vertices_count();
+
+ ::glLineWidth(2.0f);
+ ::glColor3f(0.0f, 0.0f, 0.0f);
+ ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_lines.get_vertices());
+ ::glDrawArrays(GL_LINES, 0, (GLsizei)lines_vcount);
+ }
+
+ ::glDisableClientState(GL_VERTEX_ARRAY);
+}
+
+GLCanvas3D::Shader::Shader()
+ : m_shader(nullptr)
+{
+}
+
+GLCanvas3D::Shader::~Shader()
+{
+ _reset();
+}
+
+bool GLCanvas3D::Shader::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename)
+{
+ if (is_initialized())
+ return true;
+
+ m_shader = new GLShader();
+ if (m_shader != nullptr)
+ {
+ if (!m_shader->load_from_file(fragment_shader_filename.c_str(), vertex_shader_filename.c_str()))
+ {
+ std::cout << "Compilaton of shader failed:" << std::endl;
+ std::cout << m_shader->last_error << std::endl;
+ _reset();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool GLCanvas3D::Shader::is_initialized() const
+{
+ return (m_shader != nullptr);
+}
+
+bool GLCanvas3D::Shader::start_using() const
+{
+ if (is_initialized())
+ {
+ m_shader->enable();
+ return true;
+ }
+ else
+ return false;
+}
+
+void GLCanvas3D::Shader::stop_using() const
+{
+ if (m_shader != nullptr)
+ m_shader->disable();
+}
+
+void GLCanvas3D::Shader::set_uniform(const std::string& name, float value) const
+{
+ if (m_shader != nullptr)
+ m_shader->set_uniform(name.c_str(), value);
+}
+
+const GLShader* GLCanvas3D::Shader::get_shader() const
+{
+ return m_shader;
+}
+
+void GLCanvas3D::Shader::_reset()
+{
+ if (m_shader != nullptr)
+ {
+ m_shader->release();
+ delete m_shader;
+ m_shader = nullptr;
+ }
+}
+
+GLCanvas3D::LayersEditing::LayersEditing()
+ : m_use_legacy_opengl(false)
+ , m_enabled(false)
+ , m_z_texture_id(0)
+ , state(Unknown)
+ , band_width(2.0f)
+ , strength(0.005f)
+ , last_object_id(-1)
+ , last_z(0.0f)
+ , last_action(0)
+{
+}
+
+GLCanvas3D::LayersEditing::~LayersEditing()
+{
+ if (m_z_texture_id != 0)
+ {
+ ::glDeleteTextures(1, &m_z_texture_id);
+ m_z_texture_id = 0;
+ }
+}
+
+bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename)
+{
+ if (!m_shader.init(vertex_shader_filename, fragment_shader_filename))
+ return false;
+
+ ::glGenTextures(1, (GLuint*)&m_z_texture_id);
+ ::glBindTexture(GL_TEXTURE_2D, m_z_texture_id);
+ ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+ ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+ ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+ ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
+ ::glBindTexture(GL_TEXTURE_2D, 0);
+
+ return true;
+}
+
+bool GLCanvas3D::LayersEditing::is_allowed() const
+{
+ return !m_use_legacy_opengl && m_shader.is_initialized();
+}
+
+void GLCanvas3D::LayersEditing::set_use_legacy_opengl(bool use_legacy_opengl)
+{
+ m_use_legacy_opengl = use_legacy_opengl;
+}
+
+bool GLCanvas3D::LayersEditing::is_enabled() const
+{
+ return m_enabled;
+}
+
+void GLCanvas3D::LayersEditing::set_enabled(bool enabled)
+{
+ m_enabled = is_allowed() && enabled;
+}
+
+unsigned int GLCanvas3D::LayersEditing::get_z_texture_id() const
+{
+ return m_z_texture_id;
+}
+
+void GLCanvas3D::LayersEditing::render(const GLCanvas3D& canvas, const PrintObject& print_object, const GLVolume& volume) const
+{
+ if (!m_enabled)
+ return;
+
+ const Rect& bar_rect = get_bar_rect_viewport(canvas);
+ const Rect& reset_rect = get_reset_rect_viewport(canvas);
+
+ ::glDisable(GL_DEPTH_TEST);
+
+ // The viewport and camera are set to complete view and glOrtho(-$x / 2, $x / 2, -$y / 2, $y / 2, -$depth, $depth),
+ // where x, y is the window size divided by $self->_zoom.
+ ::glPushMatrix();
+ ::glLoadIdentity();
+
+ _render_tooltip_texture(canvas, bar_rect, reset_rect);
+ _render_reset_texture(reset_rect);
+ _render_active_object_annotations(canvas, volume, print_object, bar_rect);
+ _render_profile(print_object, bar_rect);
+
+ // Revert the matrices.
+ ::glPopMatrix();
+
+ ::glEnable(GL_DEPTH_TEST);
+}
+
+int GLCanvas3D::LayersEditing::get_shader_program_id() const
+{
+ const GLShader* shader = m_shader.get_shader();
+ return (shader != nullptr) ? shader->shader_program_id : -1;
+}
+
+float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas)
+{
+ const Point& mouse_pos = canvas.get_local_mouse_position();
+ const Rect& rect = get_bar_rect_screen(canvas);
+ float x = (float)mouse_pos.x;
+ float y = (float)mouse_pos.y;
+ float t = rect.get_top();
+ float b = rect.get_bottom();
+
+ return ((rect.get_left() <= x) && (x <= rect.get_right()) && (t <= y) && (y <= b)) ?
+ // Inside the bar.
+ (b - y - 1.0f) / (b - t - 1.0f) :
+ // Outside the bar.
+ -1000.0f;
+}
+
+bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, float x, float y)
+{
+ const Rect& rect = get_bar_rect_screen(canvas);
+ return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom());
+}
+
+bool GLCanvas3D::LayersEditing::reset_rect_contains(const GLCanvas3D& canvas, float x, float y)
+{
+ const Rect& rect = get_reset_rect_screen(canvas);
+ return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom());
+}
+
+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();
+
+ return Rect(w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, 0.0f, w, h - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT);
+}
+
+Rect GLCanvas3D::LayersEditing::get_reset_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();
+
+ return Rect(w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, h - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT, w, h);
+}
+
+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();
+
+ float zoom = canvas.get_camera_zoom();
+ float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+
+ return Rect((half_w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, (-half_h + VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT) * inv_zoom);
+}
+
+Rect GLCanvas3D::LayersEditing::get_reset_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();
+
+ float zoom = canvas.get_camera_zoom();
+ float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+
+ return Rect((half_w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH) * inv_zoom, (-half_h + VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT) * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom);
+}
+
+
+bool GLCanvas3D::LayersEditing::_is_initialized() const
+{
+ return m_shader.is_initialized();
+}
+
+void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const
+{
+ if (m_tooltip_texture.get_id() == 0)
+ {
+ std::string filename = resources_dir() + "/icons/variable_layer_height_tooltip.png";
+ if (!m_tooltip_texture.load_from_file(filename, false))
+ return;
+ }
+
+ float zoom = canvas.get_camera_zoom();
+ float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+ float gap = 10.0f * inv_zoom;
+
+ float bar_left = bar_rect.get_left();
+ float reset_bottom = reset_rect.get_bottom();
+
+ float l = bar_left - (float)m_tooltip_texture.get_width() * inv_zoom - gap;
+ float r = bar_left - gap;
+ float t = reset_bottom + (float)m_tooltip_texture.get_height() * inv_zoom + gap;
+ float b = reset_bottom + gap;
+
+ GLTexture::render_texture(m_tooltip_texture.get_id(), l, r, b, t);
+}
+
+void GLCanvas3D::LayersEditing::_render_reset_texture(const Rect& reset_rect) const
+{
+ if (m_reset_texture.get_id() == 0)
+ {
+ std::string filename = resources_dir() + "/icons/variable_layer_height_reset.png";
+ if (!m_reset_texture.load_from_file(filename, false))
+ return;
+ }
+
+ GLTexture::render_texture(m_reset_texture.get_id(), reset_rect.get_left(), reset_rect.get_right(), reset_rect.get_bottom(), reset_rect.get_top());
+}
+
+void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas3D& canvas, const GLVolume& volume, const PrintObject& print_object, const Rect& bar_rect) const
+{
+ float max_z = print_object.model_object()->bounding_box().max.z;
+
+ m_shader.start_using();
+
+ m_shader.set_uniform("z_to_texture_row", (float)volume.layer_height_texture_z_to_row_id());
+ m_shader.set_uniform("z_texture_row_to_normalized", 1.0f / (float)volume.layer_height_texture_height());
+ m_shader.set_uniform("z_cursor", max_z * get_cursor_z_relative(canvas));
+ m_shader.set_uniform("z_cursor_band_width", band_width);
+
+ GLsizei w = (GLsizei)volume.layer_height_texture_width();
+ GLsizei h = (GLsizei)volume.layer_height_texture_height();
+ GLsizei half_w = w / 2;
+ GLsizei half_h = h / 2;
+
+ ::glBindTexture(GL_TEXTURE_2D, m_z_texture_id);
+ ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+ ::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+ ::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, volume.layer_height_texture_data_ptr_level0());
+ ::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, volume.layer_height_texture_data_ptr_level1());
+
+ // 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);
+ ::glVertex3f(l, b, 0.0f);
+ ::glVertex3f(r, b, 0.0f);
+ ::glVertex3f(r, t, max_z);
+ ::glVertex3f(l, t, max_z);
+ ::glEnd();
+ ::glBindTexture(GL_TEXTURE_2D, 0);
+
+ m_shader.stop_using();
+}
+
+void GLCanvas3D::LayersEditing::_render_profile(const PrintObject& print_object, const Rect& bar_rect) const
+{
+ // FIXME show some kind of legend.
+
+ // Get a maximum layer height value.
+ // FIXME This is a duplicate code of Slicing.cpp.
+ double layer_height_max = DBL_MAX;
+ const PrintConfig& print_config = print_object.print()->config;
+ const std::vector<double>& nozzle_diameters = dynamic_cast<const ConfigOptionFloats*>(print_config.option("nozzle_diameter"))->values;
+ const std::vector<double>& layer_heights_min = dynamic_cast<const ConfigOptionFloats*>(print_config.option("min_layer_height"))->values;
+ const std::vector<double>& layer_heights_max = dynamic_cast<const ConfigOptionFloats*>(print_config.option("max_layer_height"))->values;
+ for (unsigned int i = 0; i < (unsigned int)nozzle_diameters.size(); ++i)
+ {
+ double lh_min = (layer_heights_min[i] == 0.0) ? 0.07 : std::max(0.01, layer_heights_min[i]);
+ double lh_max = (layer_heights_max[i] == 0.0) ? (0.75 * nozzle_diameters[i]) : layer_heights_max[i];
+ layer_height_max = std::min(layer_height_max, std::max(lh_min, lh_max));
+ }
+
+ // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region.
+ layer_height_max *= 1.12;
+
+ coordf_t max_z = unscale(print_object.size.z);
+ double layer_height = dynamic_cast<const ConfigOptionFloat*>(print_object.config.option("layer_height"))->value;
+ float l = bar_rect.get_left();
+ float w = bar_rect.get_right() - l;
+ float b = bar_rect.get_bottom();
+ float t = bar_rect.get_top();
+ float h = t - b;
+ float scale_x = w / (float)layer_height_max;
+ float scale_y = h / (float)max_z;
+ float x = l + (float)layer_height * scale_x;
+
+ // Baseline
+ ::glColor3f(0.0f, 0.0f, 0.0f);
+ ::glBegin(GL_LINE_STRIP);
+ ::glVertex2f(x, b);
+ ::glVertex2f(x, t);
+ ::glEnd();
+
+ // Curve
+ const ModelObject* model_object = print_object.model_object();
+ if (model_object->layer_height_profile_valid)
+ {
+ const std::vector<coordf_t>& profile = model_object->layer_height_profile;
+
+ ::glColor3f(0.0f, 0.0f, 1.0f);
+ ::glBegin(GL_LINE_STRIP);
+ for (unsigned int i = 0; i < profile.size(); i += 2)
+ {
+ ::glVertex2f(l + (float)profile[i + 1] * scale_x, b + (float)profile[i] * scale_y);
+ }
+ ::glEnd();
+ }
+}
+
+const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX);
+const Pointf3 GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX);
+
+GLCanvas3D::Mouse::Drag::Drag()
+ : start_position_2D(Invalid_2D_Point)
+ , start_position_3D(Invalid_3D_Point)
+ , volume_idx(-1)
+{
+}
+
+GLCanvas3D::Mouse::Mouse()
+ : dragging(false)
+ , position(DBL_MAX, DBL_MAX)
+{
+}
+
+void GLCanvas3D::Mouse::set_start_position_2D_as_invalid()
+{
+ drag.start_position_2D = Drag::Invalid_2D_Point;
+}
+
+void GLCanvas3D::Mouse::set_start_position_3D_as_invalid()
+{
+ drag.start_position_3D = Drag::Invalid_3D_Point;
+}
+
+bool GLCanvas3D::Mouse::is_start_position_2D_defined() const
+{
+ return (drag.start_position_2D != Drag::Invalid_2D_Point);
+}
+
+bool GLCanvas3D::Mouse::is_start_position_3D_defined() const
+{
+ return (drag.start_position_3D != Drag::Invalid_3D_Point);
+}
+
+const float GLCanvas3D::Gizmos::OverlayOffsetX = 10.0f;
+const float GLCanvas3D::Gizmos::OverlayGapY = 10.0f;
+
+GLCanvas3D::Gizmos::Gizmos()
+ : m_enabled(false)
+ , m_current(Undefined)
+ , m_dragging(false)
+{
+}
+
+GLCanvas3D::Gizmos::~Gizmos()
+{
+ _reset();
+}
+
+bool GLCanvas3D::Gizmos::init()
+{
+ GLGizmoBase* gizmo = new GLGizmoScale;
+ if (gizmo == nullptr)
+ return false;
+
+ if (!gizmo->init())
+ return false;
+
+ m_gizmos.insert(GizmosMap::value_type(Scale, gizmo));
+
+ gizmo = new GLGizmoRotate;
+ if (gizmo == nullptr)
+ {
+ _reset();
+ return false;
+ }
+
+ if (!gizmo->init())
+ {
+ _reset();
+ return false;
+ }
+
+ m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo));
+
+ return true;
+}
+
+bool GLCanvas3D::Gizmos::is_enabled() const
+{
+ return m_enabled;
+}
+
+void GLCanvas3D::Gizmos::set_enabled(bool enable)
+{
+ m_enabled = enable;
+}
+
+void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos)
+{
+ if (!m_enabled)
+ return;
+
+ float cnv_h = (float)canvas.get_canvas_size().get_height();
+ float height = _get_total_overlay_height();
+ float top_y = 0.5f * (cnv_h - height);
+ for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
+ {
+ if (it->second == nullptr)
+ continue;
+
+ float tex_size = (float)it->second->get_textures_size();
+ float half_tex_size = 0.5f * tex_size;
+
+ // we currently use circular icons for gizmo, so we check the radius
+ if (it->second->get_state() != GLGizmoBase::On)
+ {
+ bool inside = length(Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size).vector_to(mouse_pos)) < half_tex_size;
+ it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off);
+ }
+ top_y += (tex_size + OverlayGapY);
+ }
+}
+
+void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Pointf& mouse_pos)
+{
+ if (!m_enabled)
+ return;
+
+ float cnv_h = (float)canvas.get_canvas_size().get_height();
+ float height = _get_total_overlay_height();
+ float top_y = 0.5f * (cnv_h - height);
+ for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
+ {
+ if (it->second == nullptr)
+ continue;
+
+ float tex_size = (float)it->second->get_textures_size();
+ float half_tex_size = 0.5f * tex_size;
+
+ // we currently use circular icons for gizmo, so we check the radius
+ if (length(Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size).vector_to(mouse_pos)) < half_tex_size)
+ {
+ if ((it->second->get_state() == GLGizmoBase::On))
+ {
+ it->second->set_state(GLGizmoBase::Off);
+ m_current = Undefined;
+ }
+ else
+ {
+ it->second->set_state(GLGizmoBase::On);
+ m_current = it->first;
+ }
+ }
+ else
+ it->second->set_state(GLGizmoBase::Off);
+
+ top_y += (tex_size + OverlayGapY);
+ }
+}
+
+void GLCanvas3D::Gizmos::reset_all_states()
+{
+ if (!m_enabled)
+ return;
+
+ for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
+ {
+ if (it->second != nullptr)
+ {
+ it->second->set_state(GLGizmoBase::Off);
+ it->second->set_hover_id(-1);
+ }
+ }
+
+ m_current = Undefined;
+}
+
+void GLCanvas3D::Gizmos::set_hover_id(int id)
+{
+ if (!m_enabled)
+ return;
+
+ for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
+ {
+ if ((it->second != nullptr) && (it->second->get_state() == GLGizmoBase::On))
+ it->second->set_hover_id(id);
+ }
+}
+
+bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const
+{
+ if (!m_enabled)
+ return false;
+
+ float cnv_h = (float)canvas.get_canvas_size().get_height();
+ float height = _get_total_overlay_height();
+ float top_y = 0.5f * (cnv_h - height);
+ for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
+ {
+ if (it->second == nullptr)
+ continue;
+
+ float tex_size = (float)it->second->get_textures_size();
+ float half_tex_size = 0.5f * tex_size;
+
+ // we currently use circular icons for gizmo, so we check the radius
+ if (length(Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size).vector_to(mouse_pos)) < half_tex_size)
+ return true;
+
+ top_y += (tex_size + OverlayGapY);
+ }
+
+ return false;
+}
+
+bool GLCanvas3D::Gizmos::grabber_contains_mouse() const
+{
+ if (!m_enabled)
+ return false;
+
+ GLGizmoBase* curr = _get_current();
+ return (curr != nullptr) ? (curr->get_hover_id() != -1) : false;
+}
+
+void GLCanvas3D::Gizmos::update(const Pointf& mouse_pos)
+{
+ if (!m_enabled)
+ return;
+
+ GLGizmoBase* curr = _get_current();
+ if (curr != nullptr)
+ curr->update(mouse_pos);
+}
+
+void GLCanvas3D::Gizmos::update_data(float scale)
+{
+ if (!m_enabled)
+ return;
+
+ GizmosMap::const_iterator it = m_gizmos.find(Scale);
+ if (it != m_gizmos.end())
+ reinterpret_cast<GLGizmoScale*>(it->second)->set_scale(scale);
+}
+
+bool GLCanvas3D::Gizmos::is_running() const
+{
+ if (!m_enabled)
+ return false;
+
+ GLGizmoBase* curr = _get_current();
+ return (curr != nullptr) ? (curr->get_state() == GLGizmoBase::On) : false;
+}
+
+bool GLCanvas3D::Gizmos::is_dragging() const
+{
+ return m_dragging;
+}
+
+void GLCanvas3D::Gizmos::start_dragging()
+{
+ m_dragging = true;
+ GLGizmoBase* curr = _get_current();
+ if (curr != nullptr)
+ curr->start_dragging();
+}
+
+void GLCanvas3D::Gizmos::stop_dragging()
+{
+ m_dragging = false;
+}
+
+float GLCanvas3D::Gizmos::get_scale() const
+{
+ if (!m_enabled)
+ return 1.0f;
+
+ GizmosMap::const_iterator it = m_gizmos.find(Scale);
+ return (it != m_gizmos.end()) ? reinterpret_cast<GLGizmoScale*>(it->second)->get_scale() : 1.0f;
+}
+
+void GLCanvas3D::Gizmos::render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const
+{
+ if (!m_enabled)
+ return;
+
+ ::glDisable(GL_DEPTH_TEST);
+
+ _render_current_gizmo(box);
+
+ ::glPushMatrix();
+ ::glLoadIdentity();
+
+ _render_overlay(canvas);
+
+ ::glPopMatrix();
+}
+
+void GLCanvas3D::Gizmos::render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const
+{
+ if (!m_enabled)
+ return;
+
+ ::glDisable(GL_DEPTH_TEST);
+
+ GLGizmoBase* curr = _get_current();
+ if (curr != nullptr)
+ curr->render_for_picking(box);
+}
+
+void GLCanvas3D::Gizmos::_reset()
+{
+ for (GizmosMap::value_type& gizmo : m_gizmos)
+ {
+ delete gizmo.second;
+ gizmo.second = nullptr;
+ }
+
+ m_gizmos.clear();
+}
+
+void GLCanvas3D::Gizmos::_render_overlay(const GLCanvas3D& canvas) const
+{
+ if (m_gizmos.empty())
+ return;
+
+ float cnv_w = (float)canvas.get_canvas_size().get_width();
+ float zoom = canvas.get_camera_zoom();
+ float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+
+ float height = _get_total_overlay_height();
+ float top_x = (OverlayOffsetX - 0.5f * cnv_w) * inv_zoom;
+ float top_y = 0.5f * height * inv_zoom;
+ float scaled_gap_y = OverlayGapY * inv_zoom;
+ for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
+ {
+ float tex_size = (float)it->second->get_textures_size() * inv_zoom;
+ GLTexture::render_texture(it->second->get_textures_id(), top_x, top_x + tex_size, top_y - tex_size, top_y);
+ top_y -= (tex_size + scaled_gap_y);
+ }
+}
+
+void GLCanvas3D::Gizmos::_render_current_gizmo(const BoundingBoxf3& box) const
+{
+ GLGizmoBase* curr = _get_current();
+ if (curr != nullptr)
+ curr->render(box);
+}
+
+float GLCanvas3D::Gizmos::_get_total_overlay_height() const
+{
+ float height = 0.0f;
+
+ for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
+ {
+ height += (float)it->second->get_textures_size();
+ if (std::distance(it, m_gizmos.end()) > 1)
+ height += OverlayGapY;
+ }
+
+ return height;
+}
+
+GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const
+{
+ GizmosMap::const_iterator it = m_gizmos.find(m_current);
+ return (it != m_gizmos.end()) ? it->second : nullptr;
+}
+
+GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, wxGLContext* context)
+ : m_canvas(canvas)
+ , m_context(context)
+ , m_timer(nullptr)
+ , m_config(nullptr)
+ , m_print(nullptr)
+ , m_model(nullptr)
+ , m_dirty(true)
+ , m_active(true)
+ , m_initialized(false)
+ , m_use_VBOs(false)
+ , m_force_zoom_to_bed_enabled(false)
+ , m_apply_zoom_to_volumes_filter(false)
+ , m_hover_volume_id(-1)
+ , m_warning_texture_enabled(false)
+ , m_legend_texture_enabled(false)
+ , m_picking_enabled(false)
+ , m_moving_enabled(false)
+ , m_shader_enabled(false)
+ , m_multisample_allowed(false)
+ , m_color_by("volume")
+ , m_select_by("object")
+ , m_drag_by("instance")
+ , m_reload_delayed(false)
+{
+ if (m_canvas != nullptr)
+ m_timer = new wxTimer(m_canvas);
+}
+
+GLCanvas3D::~GLCanvas3D()
+{
+ reset_volumes();
+
+ if (m_timer != nullptr)
+ {
+ delete m_timer;
+ m_timer = nullptr;
+ }
+
+ _deregister_callbacks();
+}
+
+bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl)
+{
+ if (m_initialized)
+ return true;
+
+ ::glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+ ::glClearDepth(1.0f);
+
+ ::glDepthFunc(GL_LESS);
+
+ ::glEnable(GL_DEPTH_TEST);
+ ::glEnable(GL_CULL_FACE);
+ ::glEnable(GL_BLEND);
+ ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ // Set antialiasing / multisampling
+ ::glDisable(GL_LINE_SMOOTH);
+ ::glDisable(GL_POLYGON_SMOOTH);
+
+ // ambient lighting
+ GLfloat ambient[4] = { 0.3f, 0.3f, 0.3f, 1.0f };
+ ::glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
+
+ ::glEnable(GL_LIGHT0);
+ ::glEnable(GL_LIGHT1);
+
+ // light from camera
+ GLfloat specular_cam[4] = { 0.3f, 0.3f, 0.3f, 1.0f };
+ ::glLightfv(GL_LIGHT1, GL_SPECULAR, specular_cam);
+ GLfloat diffuse_cam[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
+ ::glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse_cam);
+
+ // light from above
+ GLfloat specular_top[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
+ ::glLightfv(GL_LIGHT0, GL_SPECULAR, specular_top);
+ GLfloat diffuse_top[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
+ ::glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_top);
+
+ // Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
+ ::glShadeModel(GL_SMOOTH);
+
+ // A handy trick -- have surface material mirror the color.
+ ::glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
+ ::glEnable(GL_COLOR_MATERIAL);
+
+ if (m_multisample_allowed)
+ ::glEnable(GL_MULTISAMPLE);
+
+ if (useVBOs && !m_shader.init("gouraud.vs", "gouraud.fs"))
+ return false;
+
+ if (useVBOs && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs"))
+ return false;
+
+ m_use_VBOs = useVBOs;
+ m_layers_editing.set_use_legacy_opengl(use_legacy_opengl);
+
+ // 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()
+ if (!m_volumes.empty())
+ m_volumes.finalize_geometry(m_use_VBOs);
+
+ if (m_gizmos.is_enabled() && !m_gizmos.init())
+ return false;
+
+ m_initialized = true;
+
+ return true;
+}
+
+bool GLCanvas3D::set_current()
+{
+ if ((m_canvas != nullptr) && (m_context != nullptr))
+ {
+ m_canvas->SetCurrent(*m_context);
+ return true;
+ }
+
+ return false;
+}
+
+void GLCanvas3D::set_active(bool active)
+{
+ m_active = active;
+}
+
+unsigned int GLCanvas3D::get_volumes_count() const
+{
+ return (unsigned int)m_volumes.volumes.size();
+}
+
+void GLCanvas3D::reset_volumes()
+{
+ if (set_current())
+ {
+ m_volumes.release_geometry();
+ m_volumes.clear();
+ m_dirty = true;
+ }
+}
+
+void GLCanvas3D::deselect_volumes()
+{
+ for (GLVolume* vol : m_volumes.volumes)
+ {
+ if (vol != nullptr)
+ vol->selected = false;
+ }
+}
+
+void GLCanvas3D::select_volume(unsigned int id)
+{
+ if (id < (unsigned int)m_volumes.volumes.size())
+ {
+ GLVolume* vol = m_volumes.volumes[id];
+ if (vol != nullptr)
+ vol->selected = true;
+ }
+}
+
+void GLCanvas3D::update_volumes_selection(const std::vector<int>& selections)
+{
+ if (m_model == nullptr)
+ return;
+
+ for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx)
+ {
+ if ((selections[obj_idx] == 1) && (obj_idx < (unsigned int)m_objects_volumes_idxs.size()))
+ {
+ const std::vector<int>& volume_idxs = m_objects_volumes_idxs[obj_idx];
+ for (int v : volume_idxs)
+ {
+ select_volume(v);
+ }
+ }
+ }
+}
+
+bool GLCanvas3D::check_volumes_outside_state(const DynamicPrintConfig* config) const
+{
+ return m_volumes.check_outside_state(config);
+}
+
+bool GLCanvas3D::move_volume_up(unsigned int id)
+{
+ if ((id > 0) && (id < (unsigned int)m_volumes.volumes.size()))
+ {
+ std::swap(m_volumes.volumes[id - 1], m_volumes.volumes[id]);
+ std::swap(m_volumes.volumes[id - 1]->composite_id, m_volumes.volumes[id]->composite_id);
+ std::swap(m_volumes.volumes[id - 1]->select_group_id, m_volumes.volumes[id]->select_group_id);
+ std::swap(m_volumes.volumes[id - 1]->drag_group_id, m_volumes.volumes[id]->drag_group_id);
+ return true;
+ }
+
+ return false;
+}
+
+bool GLCanvas3D::move_volume_down(unsigned int id)
+{
+ if ((id >= 0) && (id + 1 < (unsigned int)m_volumes.volumes.size()))
+ {
+ std::swap(m_volumes.volumes[id + 1], m_volumes.volumes[id]);
+ std::swap(m_volumes.volumes[id + 1]->composite_id, m_volumes.volumes[id]->composite_id);
+ std::swap(m_volumes.volumes[id + 1]->select_group_id, m_volumes.volumes[id]->select_group_id);
+ std::swap(m_volumes.volumes[id + 1]->drag_group_id, m_volumes.volumes[id]->drag_group_id);
+ return true;
+ }
+
+ return false;
+}
+
+void GLCanvas3D::set_objects_selections(const std::vector<int>& selections)
+{
+ m_objects_selections = selections;
+}
+
+void GLCanvas3D::set_config(DynamicPrintConfig* config)
+{
+ m_config = config;
+}
+
+void GLCanvas3D::set_print(Print* print)
+{
+ m_print = print;
+}
+
+void GLCanvas3D::set_model(Model* model)
+{
+ m_model = model;
+}
+
+void GLCanvas3D::set_bed_shape(const Pointfs& shape)
+{
+ m_bed.set_shape(shape);
+
+ // Set the origin and size for painting of the coordinate system axes.
+ m_axes.origin = Pointf3(0.0, 0.0, (coordf_t)GROUND_Z);
+ set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size());
+}
+
+void GLCanvas3D::set_auto_bed_shape()
+{
+ // draw a default square bed around object center
+ const BoundingBoxf3& bbox = volumes_bounding_box();
+ coordf_t max_size = bbox.max_size();
+ const Pointf3& center = bbox.center();
+
+ Pointfs bed_shape;
+ bed_shape.reserve(4);
+ bed_shape.emplace_back(center.x - max_size, center.y - max_size);
+ bed_shape.emplace_back(center.x + max_size, center.y - max_size);
+ bed_shape.emplace_back(center.x + max_size, center.y + max_size);
+ bed_shape.emplace_back(center.x - max_size, center.y + max_size);
+
+ set_bed_shape(bed_shape);
+
+ // Set the origin for painting of the coordinate system axes.
+ m_axes.origin = Pointf3(center.x, center.y, (coordf_t)GROUND_Z);
+}
+
+void GLCanvas3D::set_axes_length(float length)
+{
+ m_axes.length = length;
+}
+
+void GLCanvas3D::set_cutting_plane(float z, const ExPolygons& polygons)
+{
+ m_cutting_plane.set(z, polygons);
+}
+
+void GLCanvas3D::set_color_by(const std::string& value)
+{
+ m_color_by = value;
+}
+
+void GLCanvas3D::set_select_by(const std::string& value)
+{
+ m_select_by = value;
+}
+
+void GLCanvas3D::set_drag_by(const std::string& value)
+{
+ m_drag_by = value;
+}
+
+float GLCanvas3D::get_camera_zoom() const
+{
+ return m_camera.zoom;
+}
+
+BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const
+{
+ BoundingBoxf3 bb;
+ for (const GLVolume* volume : m_volumes.volumes)
+ {
+ if (!m_apply_zoom_to_volumes_filter || ((volume != nullptr) && volume->zoom_to_volumes))
+ bb.merge(volume->transformed_bounding_box());
+ }
+ return bb;
+}
+
+bool GLCanvas3D::is_layers_editing_enabled() const
+{
+ return m_layers_editing.is_enabled();
+}
+
+bool GLCanvas3D::is_layers_editing_allowed() const
+{
+ return m_layers_editing.is_allowed();
+}
+
+bool GLCanvas3D::is_shader_enabled() const
+{
+ return m_shader_enabled;
+}
+
+bool GLCanvas3D::is_reload_delayed() const
+{
+ return m_reload_delayed;
+}
+
+void GLCanvas3D::enable_layers_editing(bool enable)
+{
+ m_layers_editing.set_enabled(enable);
+}
+
+void GLCanvas3D::enable_warning_texture(bool enable)
+{
+ m_warning_texture_enabled = enable;
+}
+
+void GLCanvas3D::enable_legend_texture(bool enable)
+{
+ m_legend_texture_enabled = enable;
+}
+
+void GLCanvas3D::enable_picking(bool enable)
+{
+ m_picking_enabled = enable;
+}
+
+void GLCanvas3D::enable_moving(bool enable)
+{
+ m_moving_enabled = enable;
+}
+
+void GLCanvas3D::enable_gizmos(bool enable)
+{
+ m_gizmos.set_enabled(enable);
+}
+
+void GLCanvas3D::enable_shader(bool enable)
+{
+ m_shader_enabled = enable;
+}
+
+void GLCanvas3D::enable_force_zoom_to_bed(bool enable)
+{
+ m_force_zoom_to_bed_enabled = enable;
+}
+
+void GLCanvas3D::allow_multisample(bool allow)
+{
+ m_multisample_allowed = allow;
+}
+
+void GLCanvas3D::zoom_to_bed()
+{
+ _zoom_to_bounding_box(m_bed.get_bounding_box());
+}
+
+void GLCanvas3D::zoom_to_volumes()
+{
+ m_apply_zoom_to_volumes_filter = true;
+ _zoom_to_bounding_box(volumes_bounding_box());
+ m_apply_zoom_to_volumes_filter = false;
+}
+
+void GLCanvas3D::select_view(const std::string& direction)
+{
+ const float* dir_vec = nullptr;
+
+ if (direction == "iso")
+ dir_vec = VIEW_DEFAULT;
+ else if (direction == "left")
+ dir_vec = VIEW_LEFT;
+ else if (direction == "right")
+ dir_vec = VIEW_RIGHT;
+ else if (direction == "top")
+ dir_vec = VIEW_TOP;
+ else if (direction == "bottom")
+ dir_vec = VIEW_BOTTOM;
+ else if (direction == "front")
+ dir_vec = VIEW_FRONT;
+ else if (direction == "rear")
+ dir_vec = VIEW_REAR;
+
+ if ((dir_vec != nullptr) && !empty(volumes_bounding_box()))
+ {
+ m_camera.phi = dir_vec[0];
+ m_camera.set_theta(dir_vec[1]);
+
+ m_on_viewport_changed_callback.call();
+
+ if (m_canvas != nullptr)
+ m_canvas->Refresh();
+ }
+}
+
+void GLCanvas3D::set_viewport_from_scene(const GLCanvas3D& other)
+{
+ m_camera.phi = other.m_camera.phi;
+ m_camera.set_theta(other.m_camera.get_theta());
+ m_camera.target = other.m_camera.target;
+ m_camera.zoom = other.m_camera.zoom;
+ m_dirty = true;
+}
+
+void GLCanvas3D::update_volumes_colors_by_extruder()
+{
+ if (m_config != nullptr)
+ m_volumes.update_colors_by_extruder(m_config);
+}
+
+void GLCanvas3D::render()
+{
+ if (m_canvas == nullptr)
+ return;
+
+ if (!_is_shown_on_screen())
+ return;
+
+ // ensures that the proper context is selected and that this canvas is initialized
+ if (!set_current() || !_3DScene::init(m_canvas))
+ return;
+
+ if (m_force_zoom_to_bed_enabled)
+ _force_zoom_to_bed();
+
+ _camera_tranform();
+
+ GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f };
+ ::glLightfv(GL_LIGHT1, GL_POSITION, position_cam);
+ GLfloat position_top[4] = { -0.5f, -0.5f, 1.0f, 0.0f };
+ ::glLightfv(GL_LIGHT0, GL_POSITION, position_top);
+
+ float theta = m_camera.get_theta();
+ bool is_custom_bed = m_bed.is_custom();
+
+ _picking_pass();
+ _render_background();
+ // untextured bed needs to be rendered before objects
+ if (is_custom_bed)
+ {
+ _render_bed(theta);
+ // disable depth testing so that axes are not covered by ground
+ _render_axes(false);
+ }
+ _render_objects();
+ // textured bed needs to be rendered after objects
+ if (!is_custom_bed)
+ {
+ _render_axes(true);
+ _render_bed(theta);
+ }
+ _render_cutting_plane();
+ _render_warning_texture();
+ _render_legend_texture();
+ _render_gizmo();
+ _render_layer_editing_overlay();
+
+ m_canvas->SwapBuffers();
+}
+
+std::vector<double> GLCanvas3D::get_current_print_zs(bool active_only) const
+{
+ return m_volumes.get_current_print_zs(active_only);
+}
+
+void GLCanvas3D::set_toolpaths_range(double low, double high)
+{
+ m_volumes.set_range(low, high);
+}
+
+std::vector<int> GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs)
+{
+ if (instance_idxs.empty())
+ {
+ for (unsigned int i = 0; i < model_object.instances.size(); ++i)
+ {
+ instance_idxs.push_back(i);
+ }
+ }
+ return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_select_by, m_drag_by, m_use_VBOs && m_initialized);
+}
+
+std::vector<int> GLCanvas3D::load_object(const Model& model, int obj_idx)
+{
+ if ((0 <= obj_idx) && (obj_idx < (int)model.objects.size()))
+ {
+ const ModelObject* model_object = model.objects[obj_idx];
+ if (model_object != nullptr)
+ return load_object(*model_object, obj_idx, std::vector<int>());
+ }
+
+ return std::vector<int>();
+}
+
+void GLCanvas3D::reload_scene(bool force)
+{
+ if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr))
+ return;
+
+ reset_volumes();
+ set_bed_shape(dynamic_cast<const ConfigOptionPoints*>(m_config->option("bed_shape"))->values);
+
+ if (!m_canvas->IsShown() && !force)
+ {
+ m_reload_delayed = true;
+ return;
+ }
+
+ m_reload_delayed = false;
+
+ m_objects_volumes_idxs.clear();
+
+ for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx)
+ {
+ m_objects_volumes_idxs.push_back(load_object(*m_model, obj_idx));
+ }
+
+ update_volumes_selection(m_objects_selections);
+
+ if (m_config->has("nozzle_diameter"))
+ {
+ // Should the wipe tower be visualized ?
+ unsigned int extruders_count = (unsigned int)dynamic_cast<const ConfigOptionFloats*>(m_config->option("nozzle_diameter"))->values.size();
+
+ bool semm = dynamic_cast<const ConfigOptionBool*>(m_config->option("single_extruder_multi_material"))->value;
+ bool wt = dynamic_cast<const ConfigOptionBool*>(m_config->option("wipe_tower"))->value;
+ bool co = dynamic_cast<const ConfigOptionBool*>(m_config->option("complete_objects"))->value;
+
+ if ((extruders_count > 1) && semm && wt && !co)
+ {
+ // Height of a print (Show at least a slab)
+ coordf_t height = std::max(m_model->bounding_box().max.z, 10.0);
+
+ 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;
+
+ m_volumes.load_wipe_tower_preview(1000, x, y, w, 15.0f * (float)(extruders_count - 1), (float)height, a, m_use_VBOs && m_initialized);
+ }
+ }
+
+ update_volumes_colors_by_extruder();
+
+ // checks for geometry outside the print volume to render it accordingly
+ if (!m_volumes.empty())
+ {
+ bool contained = m_volumes.check_outside_state(m_config);
+ if (!contained)
+ {
+ enable_warning_texture(true);
+ _3DScene::generate_warning_texture(L("Detected object outside print volume"));
+ m_on_enable_action_buttons_callback.call(false);
+ }
+ else
+ {
+ enable_warning_texture(false);
+ m_volumes.reset_outside_state();
+ _3DScene::reset_warning_texture();
+ m_on_enable_action_buttons_callback.call(!m_model->objects.empty());
+ }
+ }
+ else
+ {
+ enable_warning_texture(false);
+ _3DScene::reset_warning_texture();
+ }
+}
+
+void GLCanvas3D::load_print_toolpaths()
+{
+ if (m_print == nullptr)
+ return;
+
+ if (!m_print->state.is_done(psSkirt) || !m_print->state.is_done(psBrim))
+ return;
+
+ if (!m_print->has_skirt() && (m_print->config.brim_width.value == 0))
+ return;
+
+ const float color[] = { 0.5f, 1.0f, 0.5f, 1.0f }; // greenish
+
+ // number of skirt layers
+ size_t total_layer_count = 0;
+ for (const PrintObject* print_object : m_print->objects)
+ {
+ total_layer_count = std::max(total_layer_count, print_object->total_layer_count());
+ }
+ size_t skirt_height = m_print->has_infinite_skirt() ? total_layer_count : std::min<size_t>(m_print->config.skirt_height.value, total_layer_count);
+ if ((skirt_height == 0) && (m_print->config.brim_width.value > 0))
+ skirt_height = 1;
+
+ // get first skirt_height layers (maybe this should be moved to a PrintObject method?)
+ const PrintObject* object0 = m_print->objects.front();
+ std::vector<float> print_zs;
+ print_zs.reserve(skirt_height * 2);
+ for (size_t i = 0; i < std::min(skirt_height, object0->layers.size()); ++i)
+ {
+ print_zs.push_back(float(object0->layers[i]->print_z));
+ }
+ //FIXME why there are support layers?
+ for (size_t i = 0; i < std::min(skirt_height, object0->support_layers.size()); ++i)
+ {
+ print_zs.push_back(float(object0->support_layers[i]->print_z));
+ }
+ sort_remove_duplicates(print_zs);
+ if (print_zs.size() > skirt_height)
+ print_zs.erase(print_zs.begin() + skirt_height, print_zs.end());
+
+ m_volumes.volumes.emplace_back(new GLVolume(color));
+ GLVolume& volume = *m_volumes.volumes.back();
+ for (size_t i = 0; i < skirt_height; ++i) {
+ volume.print_zs.push_back(print_zs[i]);
+ volume.offsets.push_back(volume.indexed_vertex_array.quad_indices.size());
+ volume.offsets.push_back(volume.indexed_vertex_array.triangle_indices.size());
+ if (i == 0)
+ _3DScene::extrusionentity_to_verts(m_print->brim, print_zs[i], Point(0, 0), volume);
+
+ _3DScene::extrusionentity_to_verts(m_print->skirt, print_zs[i], Point(0, 0), volume);
+ }
+ volume.bounding_box = volume.indexed_vertex_array.bounding_box();
+ volume.indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
+}
+
+void GLCanvas3D::load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors)
+{
+ std::vector<float> tool_colors = _parse_colors(str_tool_colors);
+
+ struct Ctxt
+ {
+ const Points *shifted_copies;
+ std::vector<const Layer*> layers;
+ bool has_perimeters;
+ bool has_infill;
+ bool has_support;
+ const std::vector<float>* tool_colors;
+
+ // Number of vertices (each vertex is 6x4=24 bytes long)
+ static const size_t alloc_size_max() { return 131072; } // 3.15MB
+ // static const size_t alloc_size_max () { return 65536; } // 1.57MB
+ // static const size_t alloc_size_max () { return 32768; } // 786kB
+ static const size_t alloc_size_reserve() { return alloc_size_max() * 2; }
+
+ 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
+
+ // 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 extruder, int feature) const
+ {
+ return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(extruder - 1, 0)) : feature;
+ }
+ } ctxt;
+
+ ctxt.shifted_copies = &print_object._shifted_copies;
+
+ // order layers by print_z
+ ctxt.layers.reserve(print_object.layers.size() + print_object.support_layers.size());
+ for (const Layer *layer : print_object.layers)
+ ctxt.layers.push_back(layer);
+ for (const Layer *layer : print_object.support_layers)
+ ctxt.layers.push_back(layer);
+ 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)
+ ctxt.has_perimeters = print_object.state.is_done(posPerimeters);
+ ctxt.has_infill = print_object.state.is_done(posInfill);
+ ctxt.has_support = print_object.state.is_done(posSupportMaterial);
+ ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors;
+
+ BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start";
+
+ //FIXME Improve the heuristics for a grain size.
+ size_t grain_size = std::max(ctxt.layers.size() / 16, 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);
+ new_volume_mutex.lock();
+ volume->outside_printer_detection_enabled = false;
+ m_volumes.volumes.emplace_back(volume);
+ new_volume_mutex.unlock();
+ return volume;
+ };
+ const size_t volumes_cnt_initial = m_volumes.volumes.size();
+ std::vector<GLVolumeCollection> volumes_per_thread(ctxt.layers.size());
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, ctxt.layers.size(), grain_size),
+ [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) {
+ std::vector<GLVolume*> vols;
+ 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_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) };
+ for (GLVolume *vol : vols)
+ vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
+ for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) {
+ const Layer *layer = ctxt.layers[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->print_z) {
+ vol.print_zs.push_back(layer->print_z);
+ vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
+ vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
+ }
+ }
+ for (const Point &copy : *ctxt.shifted_copies) {
+ for (const LayerRegion *layerm : layer->regions) {
+ if (ctxt.has_perimeters)
+ _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy,
+ *vols[ctxt.volume_idx(layerm->region()->config.perimeter_extruder.value, 0)]);
+ 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);
+ if (!fill->entities.empty())
+ _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy,
+ *vols[ctxt.volume_idx(
+ is_solid_infill(fill->entities.front()->role()) ?
+ layerm->region()->config.solid_infill_extruder :
+ layerm->region()->config.infill_extruder,
+ 1)]);
+ }
+ }
+ }
+ 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,
+ *vols[ctxt.volume_idx(
+ (extrusion_entity->role() == erSupportMaterial) ?
+ support_layer->object()->config.support_material_extruder :
+ support_layer->object()->config.support_material_interface_extruder,
+ 2)]);
+ }
+ }
+ }
+ for (size_t i = 0; i < vols.size(); ++i) {
+ GLVolume &vol = *vols[i];
+ if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) {
+ // Store the vertex arrays and restart their containers,
+ vols[i] = new_volume(vol.color);
+ GLVolume &vol_new = *vols[i];
+ // Assign the large pre-allocated buffers to the new GLVolume.
+ vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array);
+ // Copy the content back to the old GLVolume.
+ vol.indexed_vertex_array = vol_new.indexed_vertex_array;
+ // Finalize a bounding box of the old GLVolume.
+ vol.bounding_box = vol.indexed_vertex_array.bounding_box();
+ // 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.
+ vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
+ }
+ }
+ }
+ for (GLVolume *vol : vols) {
+ vol->bounding_box = vol->indexed_vertex_array.bounding_box();
+ vol->indexed_vertex_array.shrink_to_fit();
+ }
+ });
+
+ BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results";
+ // 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());
+ for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i)
+ m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
+
+ BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end";
+}
+
+void GLCanvas3D::load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors)
+{
+ if ((m_print == nullptr) || m_print->m_wipe_tower_tool_changes.empty())
+ return;
+
+ if (!m_print->state.is_done(psWipeTower))
+ return;
+
+ std::vector<float> tool_colors = _parse_colors(str_tool_colors);
+
+ struct Ctxt
+ {
+ const Print *print;
+ const std::vector<float> *tool_colors;
+
+ // Number of vertices (each vertex is 6x4=24 bytes long)
+ static const size_t alloc_size_max() { return 131072; } // 3.15MB
+ static const size_t alloc_size_reserve() { return alloc_size_max() * 2; }
+
+ 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) {
+ return priming.empty() ?
+ ((idx == print->m_wipe_tower_tool_changes.size()) ? final : print->m_wipe_tower_tool_changes[idx]) :
+ ((idx == 0) ? priming : (idx == print->m_wipe_tower_tool_changes.size() + 1) ? final : print->m_wipe_tower_tool_changes[idx - 1]);
+ }
+ std::vector<WipeTower::ToolChangeResult> priming;
+ std::vector<WipeTower::ToolChangeResult> final;
+ } ctxt;
+
+ ctxt.print = m_print;
+ ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors;
+ if (m_print->m_wipe_tower_priming)
+ ctxt.priming.emplace_back(*m_print->m_wipe_tower_priming.get());
+ if (m_print->m_wipe_tower_final_purge)
+ ctxt.final.emplace_back(*m_print->m_wipe_tower_final_purge.get());
+
+ BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start";
+
+ //FIXME Improve the heuristics for a grain size.
+ size_t n_items = m_print->m_wipe_tower_tool_changes.size() + (ctxt.priming.empty() ? 0 : 1);
+ 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);
+ new_volume_mutex.lock();
+ volume->outside_printer_detection_enabled = false;
+ m_volumes.volumes.emplace_back(volume);
+ new_volume_mutex.unlock();
+ 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.
+ std::vector<GLVolume*> vols;
+ 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)
+ volume->indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
+ 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) {
+ vol.print_zs.push_back(layer.front().print_z);
+ vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
+ vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
+ }
+ }
+ 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);
+ for (; i < j; ++i) {
+ const WipeTower::Extrusion &e = extrusions.extrusions[i];
+ assert(e.width > 0.f);
+ const WipeTower::Extrusion &e_prev = *(&e - 1);
+ lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y));
+ widths.emplace_back(e.width);
+ }
+ _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];
+ if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) {
+ // Store the vertex arrays and restart their containers,
+ vols[i] = new_volume(vol.color);
+ GLVolume &vol_new = *vols[i];
+ // Assign the large pre-allocated buffers to the new GLVolume.
+ vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array);
+ // Copy the content back to the old GLVolume.
+ vol.indexed_vertex_array = vol_new.indexed_vertex_array;
+ // Finalize a bounding box of the old GLVolume.
+ vol.bounding_box = vol.indexed_vertex_array.bounding_box();
+ // 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.
+ vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
+ }
+ }
+ for (GLVolume *vol : vols) {
+ vol->bounding_box = vol->indexed_vertex_array.bounding_box();
+ vol->indexed_vertex_array.shrink_to_fit();
+ }
+ });
+
+ BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results";
+ // 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());
+ for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i)
+ m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
+
+ BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end";
+}
+
+void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors)
+{
+ if ((m_canvas != nullptr) && (m_print != nullptr))
+ {
+ // ensures that the proper context is selected
+ if (!set_current())
+ return;
+
+ if (m_volumes.empty())
+ {
+ std::vector<float> tool_colors = _parse_colors(str_tool_colors);
+
+ m_gcode_preview_volume_index.reset();
+
+ _load_gcode_extrusion_paths(preview_data, tool_colors);
+ _load_gcode_travel_paths(preview_data, tool_colors);
+ _load_gcode_retractions(preview_data);
+ _load_gcode_unretractions(preview_data);
+
+ if (m_volumes.empty())
+ _3DScene::reset_legend_texture();
+ else
+ {
+ _3DScene::generate_legend_texture(preview_data, tool_colors);
+
+ // removes empty volumes
+ m_volumes.volumes.erase(std::remove_if(m_volumes.volumes.begin(), m_volumes.volumes.end(),
+ [](const GLVolume* volume) { return volume->print_zs.empty(); }), m_volumes.volumes.end());
+
+ _load_shells();
+ }
+ }
+
+ _update_gcode_volumes_visibility(preview_data);
+ }
+}
+
+void GLCanvas3D::register_on_viewport_changed_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_viewport_changed_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_double_click_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_double_click_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_right_click_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_right_click_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_select_object_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_select_object_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_model_update_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_model_update_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_remove_object_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_remove_object_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_arrange_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_arrange_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_rotate_object_left_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_rotate_object_left_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_rotate_object_right_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_rotate_object_right_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_scale_object_uniformly_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_scale_object_uniformly_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_increase_objects_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_increase_objects_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_decrease_objects_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_decrease_objects_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_instance_moved_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_instance_moved_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_wipe_tower_moved_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_wipe_tower_moved_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_enable_action_buttons_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_enable_action_buttons_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_gizmo_scale_uniformly_callback(void* callback)
+{
+ if (callback != nullptr)
+ m_on_gizmo_scale_uniformly_callback.register_callback(callback);
+}
+
+void GLCanvas3D::bind_event_handlers()
+{
+ if (m_canvas != nullptr)
+ {
+ 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);
+ m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
+ m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
+ 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);
+ m_canvas->Bind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key_down, this);
+ }
+}
+
+void GLCanvas3D::unbind_event_handlers()
+{
+ if (m_canvas != nullptr)
+ {
+ 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);
+ m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
+ m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
+ m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
+ m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this);
+ 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);
+ m_canvas->Unbind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key_down, this);
+ }
+}
+
+void GLCanvas3D::on_size(wxSizeEvent& evt)
+{
+ m_dirty = true;
+}
+
+void GLCanvas3D::on_idle(wxIdleEvent& evt)
+{
+ if (!m_dirty)
+ return;
+
+ _refresh_if_shown_on_screen();
+}
+
+void GLCanvas3D::on_char(wxKeyEvent& evt)
+{
+ if (evt.HasModifiers())
+ evt.Skip();
+ else
+ {
+ int keyCode = evt.GetKeyCode();
+ switch (keyCode - 48)
+ {
+ // numerical input
+ case 0: { select_view("iso"); break; }
+ 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; }
+ default:
+ {
+ // text input
+ switch (keyCode)
+ {
+ // key +
+ case 43: { m_on_increase_objects_callback.call(); break; }
+ // key -
+ case 45: { m_on_decrease_objects_callback.call(); break; }
+ // key A/a
+ case 65:
+ case 97: { m_on_arrange_callback.call(); break; }
+ // key B/b
+ case 66:
+ case 98: { zoom_to_bed(); break; }
+ // key L/l
+ case 76:
+ case 108: { m_on_rotate_object_left_callback.call(); break; }
+ // key R/r
+ case 82:
+ case 114: { m_on_rotate_object_right_callback.call(); break; }
+ // key S/s
+ case 83:
+ case 115: { m_on_scale_object_uniformly_callback.call(); break; }
+ // key Z/z
+ case 90:
+ case 122: { zoom_to_volumes(); break; }
+ default:
+ {
+ evt.Skip();
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
+{
+ // Ignore the wheel events if the middle button is pressed.
+ if (evt.MiddleIsDown())
+ return;
+
+ // Performs layers editing updates, if enabled
+ if (is_layers_editing_enabled())
+ {
+ int object_idx_selected = _get_first_selected_object_id();
+ if (object_idx_selected != -1)
+ {
+ // A volume is selected. Test, whether hovering over a layer thickness bar.
+ if (m_layers_editing.bar_rect_contains(*this, (float)evt.GetX(), (float)evt.GetY()))
+ {
+ // Adjust the width of the selection.
+ 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);
+ if (m_canvas != nullptr)
+ m_canvas->Refresh();
+
+ return;
+ }
+ }
+ }
+
+ // Calculate the zoom delta and apply it to the current zoom factor
+ float zoom = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
+ zoom = std::max(std::min(zoom, 4.0f), -4.0f) / 10.0f;
+ zoom = get_camera_zoom() / (1.0f - zoom);
+
+ // Don't allow to zoom too far outside the scene.
+ float zoom_min = _get_zoom_to_bounding_box_factor(_max_bounding_box());
+ if (zoom_min > 0.0f)
+ zoom = std::max(zoom, zoom_min * 0.8f);
+
+ m_camera.zoom = zoom;
+ m_on_viewport_changed_callback.call();
+
+ _refresh_if_shown_on_screen();
+}
+
+void GLCanvas3D::on_timer(wxTimerEvent& evt)
+{
+ if (m_layers_editing.state != LayersEditing::Editing)
+ return;
+
+ _perform_layer_editing_action();
+}
+
+void GLCanvas3D::on_mouse(wxMouseEvent& evt)
+{
+ Point pos(evt.GetX(), evt.GetY());
+
+ int selected_object_idx = _get_first_selected_object_id();
+ int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1;
+ m_layers_editing.last_object_id = layer_editing_object_idx;
+ bool gizmos_overlay_contains_mouse = m_gizmos.overlay_contains_mouse(*this, m_mouse.position);
+
+ if (evt.Entering())
+ {
+#if defined(__WXMSW__) || defined(__linux__)
+ // On Windows and Linux needs focus in order to catch key events
+ if (m_canvas != nullptr)
+ m_canvas->SetFocus();
+
+ m_mouse.set_start_position_2D_as_invalid();
+#endif
+ }
+ else if (evt.LeftDClick() && (m_hover_volume_id != -1))
+ m_on_double_click_callback.call();
+ else if (evt.LeftDown() || evt.RightDown())
+ {
+ // If user pressed left or right button we first check whether this happened
+ // on a volume or not.
+ int volume_idx = m_hover_volume_id;
+ m_layers_editing.state = LayersEditing::Unknown;
+ if ((layer_editing_object_idx != -1) && m_layers_editing.bar_rect_contains(*this, pos.x, pos.y))
+ {
+ // A volume is selected and the mouse is inside the layer thickness bar.
+ // Start editing the layer height.
+ m_layers_editing.state = LayersEditing::Editing;
+ _perform_layer_editing_action(&evt);
+ }
+ else if ((layer_editing_object_idx != -1) && m_layers_editing.reset_rect_contains(*this, pos.x, pos.y))
+ {
+ if (evt.LeftDown())
+ {
+ // A volume is selected and the mouse is inside the reset button.
+ m_print->get_object(layer_editing_object_idx)->reset_layer_height_profile();
+ // Index 2 means no editing, just wait for mouse up event.
+ m_layers_editing.state = LayersEditing::Completed;
+
+ m_dirty = true;
+ }
+ }
+ else if ((selected_object_idx != -1) && gizmos_overlay_contains_mouse)
+ {
+ m_gizmos.update_on_off_state(*this, m_mouse.position);
+ _update_gizmos_data();
+ m_dirty = true;
+ }
+ else if ((selected_object_idx != -1) && m_gizmos.grabber_contains_mouse())
+ {
+ _update_gizmos_data();
+ m_gizmos.start_dragging();
+ m_dirty = true;
+ }
+ else
+ {
+ // Select volume in this 3D canvas.
+ // Don't deselect a volume if layer editing is enabled. We want the object to stay selected
+ // during the scene manipulation.
+
+ if (m_picking_enabled && ((volume_idx != -1) || !is_layers_editing_enabled()))
+ {
+ if (volume_idx != -1)
+ {
+ deselect_volumes();
+ select_volume(volume_idx);
+ int group_id = m_volumes.volumes[volume_idx]->select_group_id;
+ if (group_id != -1)
+ {
+ for (GLVolume* vol : m_volumes.volumes)
+ {
+ if ((vol != nullptr) && (vol->select_group_id == group_id))
+ vol->selected = true;
+ }
+ }
+
+ if (m_gizmos.is_running())
+ _update_gizmos_data();
+
+ m_dirty = true;
+ }
+ }
+
+ // propagate event through callback
+ if (m_picking_enabled && (volume_idx != -1))
+ _on_select(volume_idx);
+
+ if (volume_idx != -1)
+ {
+ if (evt.LeftDown() && m_moving_enabled)
+ {
+ // The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate pos x, y,
+ // an converts the screen space coordinate to unscaled object space.
+ Pointf3 pos3d = (volume_idx == -1) ? Pointf3(DBL_MAX, DBL_MAX) : _mouse_to_3d(pos);
+
+ // Only accept the initial position, if it is inside the volume bounding box.
+ BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box();
+ volume_bbox.offset(1.0);
+ if (volume_bbox.contains(pos3d))
+ {
+ // The dragging operation is initiated.
+ m_mouse.drag.volume_idx = volume_idx;
+ m_mouse.drag.start_position_3D = pos3d;
+ // Remember the shift to to the object center.The object center will later be used
+ // to limit the object placement close to the bed.
+ m_mouse.drag.volume_center_offset = pos3d.vector_to(volume_bbox.center());
+ }
+ }
+ else if (evt.RightDown())
+ {
+ // if right clicking on volume, propagate event through callback
+ if (m_volumes.volumes[volume_idx]->hover)
+ m_on_right_click_callback.call(pos.x, pos.y);
+ }
+ }
+ }
+ }
+ else if (evt.Dragging() && evt.LeftIsDown() && !gizmos_overlay_contains_mouse && (m_layers_editing.state == LayersEditing::Unknown) && (m_mouse.drag.volume_idx != -1))
+ {
+ m_mouse.dragging = true;
+
+ // Get new position at the same Z of the initial click point.
+ float z0 = 0.0f;
+ float z1 = 1.0f;
+ Pointf3 cur_pos = Linef3(_mouse_to_3d(pos, &z0), _mouse_to_3d(pos, &z1)).intersect_plane(m_mouse.drag.start_position_3D.z);
+
+ // Clip the new position, so the object center remains close to the bed.
+ cur_pos.translate(m_mouse.drag.volume_center_offset);
+ Point cur_pos2(scale_(cur_pos.x), scale_(cur_pos.y));
+ if (!m_bed.contains(cur_pos2))
+ {
+ Point ip = m_bed.point_projection(cur_pos2);
+ cur_pos.x = unscale(ip.x);
+ cur_pos.y = unscale(ip.y);
+ }
+ cur_pos.translate(m_mouse.drag.volume_center_offset.negative());
+
+ // Calculate the translation vector.
+ Vectorf3 vector = m_mouse.drag.start_position_3D.vector_to(cur_pos);
+ // Get the volume being dragged.
+ GLVolume* volume = m_volumes.volumes[m_mouse.drag.volume_idx];
+ // Get all volumes belonging to the same group, if any.
+ std::vector<GLVolume*> volumes;
+ if (volume->drag_group_id == -1)
+ volumes.push_back(volume);
+ else
+ {
+ for (GLVolume* v : m_volumes.volumes)
+ {
+ if ((v != nullptr) && (v->drag_group_id == volume->drag_group_id))
+ volumes.push_back(v);
+ }
+ }
+
+ // Apply new temporary volume origin and ignore Z.
+ for (GLVolume* v : volumes)
+ {
+ v->origin.translate(vector.x, vector.y, 0.0);
+ }
+
+ m_mouse.drag.start_position_3D = cur_pos;
+
+ m_dirty = true;
+ }
+ else if (evt.Dragging() && m_gizmos.is_dragging())
+ {
+ m_mouse.dragging = true;
+
+ const Pointf3& cur_pos = _mouse_to_bed_3d(pos);
+ m_gizmos.update(Pointf(cur_pos.x, cur_pos.y));
+
+ m_on_gizmo_scale_uniformly_callback.call((double)m_gizmos.get_scale());
+ m_dirty = true;
+ }
+ else if (evt.Dragging() && !gizmos_overlay_contains_mouse)
+ {
+ m_mouse.dragging = true;
+
+ if ((m_layers_editing.state != LayersEditing::Unknown) && (layer_editing_object_idx != -1))
+ {
+ if (m_layers_editing.state == LayersEditing::Editing)
+ _perform_layer_editing_action(&evt);
+ }
+ else if (evt.LeftIsDown())
+ {
+ // if dragging over blank area with left button, rotate
+ if (m_mouse.is_start_position_3D_defined())
+ {
+ const Pointf3& orig = m_mouse.drag.start_position_3D;
+ m_camera.phi += (((float)pos.x - (float)orig.x) * TRACKBALLSIZE);
+ m_camera.set_theta(m_camera.get_theta() - ((float)pos.y - (float)orig.y) * TRACKBALLSIZE);
+
+ m_on_viewport_changed_callback.call();
+
+ m_dirty = true;
+ }
+ m_mouse.drag.start_position_3D = Pointf3((coordf_t)pos.x, (coordf_t)pos.y, 0.0);
+ }
+ else if (evt.MiddleIsDown() || evt.RightIsDown())
+ {
+ // If dragging over blank area with right button, pan.
+ if (m_mouse.is_start_position_2D_defined())
+ {
+ // get point in model space at Z = 0
+ float z = 0.0f;
+ const Pointf3& cur_pos = _mouse_to_3d(pos, &z);
+ Pointf3 orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
+ Pointf3 camera_target = m_camera.target;
+ camera_target.translate(orig.vector_to(cur_pos).negative());
+ m_camera.target = camera_target;
+
+ m_on_viewport_changed_callback.call();
+
+ m_dirty = true;
+ }
+
+ m_mouse.drag.start_position_2D = pos;
+ }
+ }
+ else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
+ {
+ if (m_layers_editing.state != LayersEditing::Unknown)
+ {
+ m_layers_editing.state = LayersEditing::Unknown;
+ _stop_timer();
+
+ if (layer_editing_object_idx != -1)
+ m_on_model_update_callback.call();
+ }
+ else if ((m_mouse.drag.volume_idx != -1) && m_mouse.dragging)
+ {
+ // get all volumes belonging to the same group, if any
+ std::vector<int> volume_idxs;
+ int vol_id = m_mouse.drag.volume_idx;
+ int group_id = m_volumes.volumes[vol_id]->drag_group_id;
+ if (group_id == -1)
+ volume_idxs.push_back(vol_id);
+ else
+ {
+ for (int i = 0; i < (int)m_volumes.volumes.size(); ++i)
+ {
+ if (m_volumes.volumes[i]->drag_group_id == group_id)
+ volume_idxs.push_back(i);
+ }
+ }
+
+ _on_move(volume_idxs);
+ }
+ else if (!m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled())
+ {
+ // deselect and propagate event through callback
+ if (m_picking_enabled)
+ {
+ deselect_volumes();
+ _on_select(-1);
+ }
+ }
+ else if (evt.LeftUp() && m_gizmos.is_dragging())
+ {
+ m_gizmos.stop_dragging();
+ }
+
+ m_mouse.drag.volume_idx = -1;
+ m_mouse.set_start_position_3D_as_invalid();
+ m_mouse.set_start_position_2D_as_invalid();
+ m_mouse.dragging = false;
+ }
+ else if (evt.Moving())
+ {
+ m_mouse.position = Pointf((coordf_t)pos.x, (coordf_t)pos.y);
+ // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over.
+ if (m_picking_enabled)
+ m_dirty = true;
+ }
+ else
+ evt.Skip();
+}
+
+void GLCanvas3D::on_paint(wxPaintEvent& evt)
+{
+ render();
+}
+
+void GLCanvas3D::on_key_down(wxKeyEvent& evt)
+{
+ if (evt.HasModifiers())
+ evt.Skip();
+ else
+ {
+ int key = evt.GetKeyCode();
+ if (key == WXK_DELETE)
+ m_on_remove_object_callback.call();
+ else
+ evt.Skip();
+ }
+}
+
+Size GLCanvas3D::get_canvas_size() const
+{
+ int w = 0;
+ int h = 0;
+
+ if (m_canvas != nullptr)
+ m_canvas->GetSize(&w, &h);
+
+ return Size(w, h);
+}
+
+Point GLCanvas3D::get_local_mouse_position() const
+{
+ if (m_canvas == nullptr)
+ return Point();
+
+ wxPoint mouse_pos = m_canvas->ScreenToClient(wxGetMousePosition());
+ return Point(mouse_pos.x, mouse_pos.y);
+}
+
+bool GLCanvas3D::_is_shown_on_screen() const
+{
+ return (m_canvas != nullptr) ? m_active && m_canvas->IsShownOnScreen() : false;
+}
+
+void GLCanvas3D::_force_zoom_to_bed()
+{
+ zoom_to_bed();
+ m_force_zoom_to_bed_enabled = false;
+}
+
+void GLCanvas3D::_resize(unsigned int w, unsigned int h)
+{
+ if (m_context == nullptr)
+ return;
+
+ set_current();
+ ::glViewport(0, 0, w, h);
+
+ ::glMatrixMode(GL_PROJECTION);
+ ::glLoadIdentity();
+
+ const BoundingBoxf3& bbox = _max_bounding_box();
+
+ switch (m_camera.type)
+ {
+ case Camera::Ortho:
+ {
+ float w2 = w;
+ float h2 = h;
+ float two_zoom = 2.0f * get_camera_zoom();
+ if (two_zoom != 0.0f)
+ {
+ float inv_two_zoom = 1.0f / two_zoom;
+ w2 *= inv_two_zoom;
+ h2 *= inv_two_zoom;
+ }
+
+ // FIXME: calculate a tighter value for depth will improve z-fighting
+ float depth = 5.0f * (float)bbox.max_size();
+ ::glOrtho(-w2, w2, -h2, h2, -depth, depth);
+
+ break;
+ }
+// case Camera::Perspective:
+// {
+// float bbox_r = (float)bbox.radius();
+// float fov = PI * 45.0f / 180.0f;
+// float fov_tan = tan(0.5f * fov);
+// float cam_distance = 0.5f * bbox_r / fov_tan;
+// m_camera.distance = cam_distance;
+//
+// float nr = cam_distance - bbox_r * 1.1f;
+// float fr = cam_distance + bbox_r * 1.1f;
+// if (nr < 1.0f)
+// nr = 1.0f;
+//
+// if (fr < nr + 1.0f)
+// fr = nr + 1.0f;
+//
+// float h2 = fov_tan * nr;
+// float w2 = h2 * w / h;
+// ::glFrustum(-w2, w2, -h2, h2, nr, fr);
+//
+// break;
+// }
+ default:
+ {
+ throw std::runtime_error("Invalid camera type.");
+ break;
+ }
+ }
+
+ ::glMatrixMode(GL_MODELVIEW);
+
+ m_dirty = false;
+}
+
+BoundingBoxf3 GLCanvas3D::_max_bounding_box() const
+{
+ BoundingBoxf3 bb = m_bed.get_bounding_box();
+ bb.merge(volumes_bounding_box());
+ return bb;
+}
+
+BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const
+{
+ BoundingBoxf3 bb;
+ for (const GLVolume* volume : m_volumes.volumes)
+ {
+ if ((volume != nullptr) && volume->selected)
+ bb.merge(volume->transformed_bounding_box());
+ }
+ return bb;
+}
+
+void GLCanvas3D::_zoom_to_bounding_box(const BoundingBoxf3& bbox)
+{
+ // Calculate the zoom factor needed to adjust viewport to bounding box.
+ float zoom = _get_zoom_to_bounding_box_factor(bbox);
+ if (zoom > 0.0f)
+ {
+ m_camera.zoom = zoom;
+ // center view around bounding box center
+ m_camera.target = bbox.center();
+
+ m_on_viewport_changed_callback.call();
+
+ _refresh_if_shown_on_screen();
+ }
+}
+
+float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const
+{
+ float max_bb_size = bbox.max_size();
+ if (max_bb_size == 0.0f)
+ return -1.0f;
+
+ // project the bbox vertices on a plane perpendicular to the camera forward axis
+ // then calculates the vertices coordinate on this plane along the camera xy axes
+
+ // we need the view matrix, we let opengl calculate it (same as done in render())
+ _camera_tranform();
+
+ // get the view matrix back from opengl
+ GLfloat matrix[16];
+ ::glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
+
+ // camera axes
+ Pointf3 right((coordf_t)matrix[0], (coordf_t)matrix[4], (coordf_t)matrix[8]);
+ Pointf3 up((coordf_t)matrix[1], (coordf_t)matrix[5], (coordf_t)matrix[9]);
+ Pointf3 forward((coordf_t)matrix[2], (coordf_t)matrix[6], (coordf_t)matrix[10]);
+
+ Pointf3 bb_min = bbox.min;
+ Pointf3 bb_max = bbox.max;
+ Pointf3 bb_center = bbox.center();
+
+ // bbox vertices in world space
+ std::vector<Pointf3> vertices;
+ vertices.reserve(8);
+ vertices.push_back(bb_min);
+ vertices.emplace_back(bb_max.x, bb_min.y, bb_min.z);
+ vertices.emplace_back(bb_max.x, bb_max.y, bb_min.z);
+ vertices.emplace_back(bb_min.x, bb_max.y, bb_min.z);
+ vertices.emplace_back(bb_min.x, bb_min.y, bb_max.z);
+ vertices.emplace_back(bb_max.x, bb_min.y, bb_max.z);
+ vertices.push_back(bb_max);
+ vertices.emplace_back(bb_min.x, bb_max.y, bb_max.z);
+
+ coordf_t max_x = 0.0;
+ coordf_t max_y = 0.0;
+
+ // margin factor to give some empty space around the bbox
+ coordf_t margin_factor = 1.25;
+
+ for (const Pointf3 v : vertices)
+ {
+ // project vertex on the plane perpendicular to camera forward axis
+ Pointf3 pos(v.x - bb_center.x, v.y - bb_center.y, v.z - bb_center.z);
+ Pointf3 proj_on_plane = pos - dot(pos, forward) * forward;
+
+ // calculates vertex coordinate along camera xy axes
+ coordf_t x_on_plane = dot(proj_on_plane, right);
+ coordf_t y_on_plane = dot(proj_on_plane, up);
+
+ max_x = std::max(max_x, margin_factor * std::abs(x_on_plane));
+ max_y = std::max(max_y, margin_factor * std::abs(y_on_plane));
+ }
+
+ if ((max_x == 0.0) || (max_y == 0.0))
+ return -1.0f;
+
+ max_x *= 2.0;
+ max_y *= 2.0;
+
+ const Size& cnv_size = get_canvas_size();
+ return (float)std::min((coordf_t)cnv_size.get_width() / max_x, (coordf_t)cnv_size.get_height() / max_y);
+}
+
+void GLCanvas3D::_deregister_callbacks()
+{
+ m_on_viewport_changed_callback.deregister_callback();
+ m_on_double_click_callback.deregister_callback();
+ m_on_right_click_callback.deregister_callback();
+ m_on_select_object_callback.deregister_callback();
+ m_on_model_update_callback.deregister_callback();
+ m_on_remove_object_callback.deregister_callback();
+ m_on_arrange_callback.deregister_callback();
+ m_on_rotate_object_left_callback.deregister_callback();
+ m_on_rotate_object_right_callback.deregister_callback();
+ m_on_scale_object_uniformly_callback.deregister_callback();
+ m_on_increase_objects_callback.deregister_callback();
+ m_on_decrease_objects_callback.deregister_callback();
+ m_on_instance_moved_callback.deregister_callback();
+ m_on_wipe_tower_moved_callback.deregister_callback();
+ m_on_enable_action_buttons_callback.deregister_callback();
+ m_on_gizmo_scale_uniformly_callback.deregister_callback();
+}
+
+void GLCanvas3D::_mark_volumes_for_layer_height() const
+{
+ if (m_print == nullptr)
+ return;
+
+ for (GLVolume* vol : m_volumes.volumes)
+ {
+ int object_id = int(vol->select_group_id / 1000000);
+ int shader_id = m_layers_editing.get_shader_program_id();
+
+ if (is_layers_editing_enabled() && (shader_id != -1) && vol->selected &&
+ vol->has_layer_height_texture() && (object_id < (int)m_print->objects.size()))
+ {
+ vol->set_layer_height_texture_data(m_layers_editing.get_z_texture_id(), shader_id,
+ m_print->get_object(object_id), _get_layers_editing_cursor_z_relative(), m_layers_editing.band_width);
+ }
+ else
+ vol->reset_layer_height_texture_data();
+ }
+}
+
+void GLCanvas3D::_refresh_if_shown_on_screen()
+{
+ if (_is_shown_on_screen())
+ {
+ const Size& cnv_size = get_canvas_size();
+ _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
+ if (m_canvas != nullptr)
+ m_canvas->Refresh();
+ }
+}
+
+void GLCanvas3D::_camera_tranform() const
+{
+ ::glMatrixMode(GL_MODELVIEW);
+ ::glLoadIdentity();
+
+ ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch
+ ::glRotatef(m_camera.phi, 0.0f, 0.0f, 1.0f); // yaw
+
+ Pointf3 neg_target = m_camera.target.negative();
+ ::glTranslatef((GLfloat)neg_target.x, (GLfloat)neg_target.y, (GLfloat)neg_target.z);
+}
+
+void GLCanvas3D::_picking_pass() const
+{
+ const Pointf& pos = m_mouse.position;
+
+ if (m_picking_enabled && !m_mouse.dragging && (pos != Pointf(DBL_MAX, DBL_MAX)))
+ {
+ // 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.
+
+ if (m_multisample_allowed)
+ ::glDisable(GL_MULTISAMPLE);
+
+ ::glDisable(GL_LIGHTING);
+ ::glDisable(GL_BLEND);
+
+ ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ _render_volumes(true);
+ m_gizmos.render_current_gizmo_for_picking_pass(_selected_volumes_bounding_box());
+
+ if (m_multisample_allowed)
+ ::glEnable(GL_MULTISAMPLE);
+
+ const Size& cnv_size = get_canvas_size();
+
+ GLubyte color[4];
+ ::glReadPixels(pos.x, cnv_size.get_height() - pos.y - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color);
+ int volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256;
+
+ m_hover_volume_id = -1;
+
+ for (GLVolume* vol : m_volumes.volumes)
+ {
+ vol->hover = false;
+ }
+
+ if (volume_id < (int)m_volumes.volumes.size())
+ {
+ m_hover_volume_id = volume_id;
+ m_volumes.volumes[volume_id]->hover = true;
+ int group_id = m_volumes.volumes[volume_id]->select_group_id;
+ if (group_id != -1)
+ {
+ for (GLVolume* vol : m_volumes.volumes)
+ {
+ if (vol->select_group_id == group_id)
+ vol->hover = true;
+ }
+ }
+ m_gizmos.set_hover_id(-1);
+ }
+ else
+ m_gizmos.set_hover_id(254 - (int)color[2]);
+
+ // updates gizmos overlay
+ if (_get_first_selected_object_id() != -1)
+ m_gizmos.update_hover_state(*this, pos);
+ else
+ m_gizmos.reset_all_states();
+ }
+}
+
+void GLCanvas3D::_render_background() const
+{
+ ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ static const float COLOR[3] = { 10.0f / 255.0f, 98.0f / 255.0f, 144.0f / 255.0f };
+
+ ::glDisable(GL_LIGHTING);
+
+ ::glPushMatrix();
+ ::glLoadIdentity();
+ ::glMatrixMode(GL_PROJECTION);
+ ::glPushMatrix();
+ ::glLoadIdentity();
+
+ // Draws a bluish bottom to top gradient over the complete screen.
+ ::glDisable(GL_DEPTH_TEST);
+
+ ::glBegin(GL_QUADS);
+ ::glColor3f(0.0f, 0.0f, 0.0f);
+ ::glVertex3f(-1.0f, -1.0f, 1.0f);
+ ::glVertex3f(1.0f, -1.0f, 1.0f);
+ ::glColor3f(COLOR[0], COLOR[1], COLOR[2]);
+ ::glVertex3f(1.0f, 1.0f, 1.0f);
+ ::glVertex3f(-1.0f, 1.0f, 1.0f);
+ ::glEnd();
+
+ ::glEnable(GL_DEPTH_TEST);
+
+ ::glPopMatrix();
+ ::glMatrixMode(GL_MODELVIEW);
+ ::glPopMatrix();
+}
+
+void GLCanvas3D::_render_bed(float theta) const
+{
+ m_bed.render(theta);
+}
+
+void GLCanvas3D::_render_axes(bool depth_test) const
+{
+ m_axes.render(depth_test);
+}
+
+void GLCanvas3D::_render_objects() const
+{
+ if (m_volumes.empty())
+ return;
+
+ ::glEnable(GL_LIGHTING);
+
+ if (!m_shader_enabled)
+ _render_volumes(false);
+ else if (m_use_VBOs)
+ {
+ if (m_picking_enabled)
+ {
+ _mark_volumes_for_layer_height();
+
+ if (m_config != nullptr)
+ {
+ const BoundingBoxf3& bed_bb = m_bed.get_bounding_box();
+ m_volumes.set_print_box((float)bed_bb.min.x, (float)bed_bb.min.y, 0.0f, (float)bed_bb.max.x, (float)bed_bb.max.y, (float)m_config->opt_float("max_print_height"));
+ m_volumes.check_outside_state(m_config);
+ }
+ // do not cull backfaces to show broken geometry, if any
+ ::glDisable(GL_CULL_FACE);
+ }
+
+ m_shader.start_using();
+ m_volumes.render_VBOs();
+ m_shader.stop_using();
+
+ if (m_picking_enabled)
+ ::glEnable(GL_CULL_FACE);
+ }
+ else
+ {
+ // do not cull backfaces to show broken geometry, if any
+ if (m_picking_enabled)
+ ::glDisable(GL_CULL_FACE);
+
+ m_volumes.render_legacy();
+
+ if (m_picking_enabled)
+ ::glEnable(GL_CULL_FACE);
+ }
+}
+
+void GLCanvas3D::_render_cutting_plane() const
+{
+ m_cutting_plane.render(volumes_bounding_box());
+}
+
+void GLCanvas3D::_render_warning_texture() const
+{
+ if (!m_warning_texture_enabled)
+ return;
+
+ // If the warning texture has not been loaded into the GPU, do it now.
+ unsigned int tex_id = _3DScene::finalize_warning_texture();
+ if (tex_id > 0)
+ {
+ unsigned int w = _3DScene::get_warning_texture_width();
+ unsigned int h = _3DScene::get_warning_texture_height();
+ if ((w > 0) && (h > 0))
+ {
+ ::glDisable(GL_DEPTH_TEST);
+ ::glPushMatrix();
+ ::glLoadIdentity();
+
+ const Size& cnv_size = get_canvas_size();
+ float zoom = get_camera_zoom();
+ float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+ float l = (-0.5f * (float)w) * inv_zoom;
+ float t = (-0.5f * (float)cnv_size.get_height() + (float)h) * inv_zoom;
+ float r = l + (float)w * inv_zoom;
+ float b = t - (float)h * inv_zoom;
+
+ GLTexture::render_texture(tex_id, l, r, b, t);
+
+ ::glPopMatrix();
+ ::glEnable(GL_DEPTH_TEST);
+ }
+ }
+}
+
+void GLCanvas3D::_render_legend_texture() const
+{
+ if (!m_legend_texture_enabled)
+ return;
+
+ // If the legend texture has not been loaded into the GPU, do it now.
+ unsigned int tex_id = _3DScene::finalize_legend_texture();
+ if (tex_id > 0)
+ {
+ unsigned int w = _3DScene::get_legend_texture_width();
+ unsigned int h = _3DScene::get_legend_texture_height();
+ if ((w > 0) && (h > 0))
+ {
+ ::glDisable(GL_DEPTH_TEST);
+ ::glPushMatrix();
+ ::glLoadIdentity();
+
+ const Size& cnv_size = get_canvas_size();
+ float zoom = get_camera_zoom();
+ float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+ float l = (-0.5f * (float)cnv_size.get_width()) * inv_zoom;
+ float t = (0.5f * (float)cnv_size.get_height()) * inv_zoom;
+ float r = l + (float)w * inv_zoom;
+ float b = t - (float)h * inv_zoom;
+ GLTexture::render_texture(tex_id, l, r, b, t);
+
+ ::glPopMatrix();
+ ::glEnable(GL_DEPTH_TEST);
+ }
+ }
+}
+
+void GLCanvas3D::_render_layer_editing_overlay() const
+{
+ if (m_print == nullptr)
+ return;
+
+ GLVolume* volume = nullptr;
+
+ for (GLVolume* vol : m_volumes.volumes)
+ {
+ if ((vol != nullptr) && vol->selected && vol->has_layer_height_texture())
+ {
+ volume = vol;
+ break;
+ }
+ }
+
+ if (volume == nullptr)
+ return;
+
+ // If the active object was not allocated at the Print, go away.This should only be a momentary case between an object addition / deletion
+ // and an update by Platter::async_apply_config.
+ int object_idx = int(volume->select_group_id / 1000000);
+ if ((int)m_print->objects.size() < object_idx)
+ return;
+
+ const PrintObject* print_object = m_print->get_object(object_idx);
+ if (print_object == nullptr)
+ return;
+
+ m_layers_editing.render(*this, *print_object, *volume);
+}
+
+void GLCanvas3D::_render_volumes(bool fake_colors) const
+{
+ static const GLfloat INV_255 = 1.0f / 255.0f;
+
+ if (fake_colors)
+ ::glDisable(GL_LIGHTING);
+ else
+ ::glEnable(GL_LIGHTING);
+
+ // do not cull backfaces to show broken geometry, if any
+ ::glDisable(GL_CULL_FACE);
+
+ ::glEnable(GL_BLEND);
+ ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ ::glEnableClientState(GL_VERTEX_ARRAY);
+ ::glEnableClientState(GL_NORMAL_ARRAY);
+
+ unsigned int volume_id = 0;
+ for (GLVolume* vol : m_volumes.volumes)
+ {
+ if (fake_colors)
+ {
+ // Object picking mode. Render the object with a color encoding the object index.
+ unsigned int r = (volume_id & 0x000000FF) >> 0;
+ unsigned int g = (volume_id & 0x0000FF00) >> 8;
+ unsigned int b = (volume_id & 0x00FF0000) >> 16;
+ ::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255);
+ }
+ else
+ {
+ vol->set_render_color();
+ ::glColor4f(vol->render_color[0], vol->render_color[1], vol->render_color[2], vol->render_color[3]);
+ }
+
+ vol->render();
+ ++volume_id;
+ }
+
+ ::glDisableClientState(GL_NORMAL_ARRAY);
+ ::glDisableClientState(GL_VERTEX_ARRAY);
+ ::glDisable(GL_BLEND);
+
+ ::glEnable(GL_CULL_FACE);
+}
+
+void GLCanvas3D::_render_gizmo() const
+{
+ m_gizmos.render(*this, _selected_volumes_bounding_box());
+}
+
+float GLCanvas3D::_get_layers_editing_cursor_z_relative() const
+{
+ return m_layers_editing.get_cursor_z_relative(*this);
+}
+
+void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt)
+{
+ int object_idx_selected = m_layers_editing.last_object_id;
+ if (object_idx_selected == -1)
+ return;
+
+ if (m_print == nullptr)
+ return;
+
+ PrintObject* selected_obj = m_print->get_object(object_idx_selected);
+ if (selected_obj == nullptr)
+ 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();
+ m_layers_editing.last_z = unscale(selected_obj->size.z) * (b - evt->GetY() - 1.0f) / (b - rect.get_top());
+ m_layers_editing.last_action = evt->ShiftDown() ? (evt->RightIsDown() ? 3 : 2) : (evt->RightIsDown() ? 0 : 1);
+ }
+
+ // Mark the volume as modified, so Print will pick its layer height profile ? Where to mark it ?
+ // Start a timer to refresh the print ? schedule_background_process() ?
+ // The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself.
+ selected_obj->adjust_layer_height_profile(m_layers_editing.last_z, m_layers_editing.strength, m_layers_editing.band_width, m_layers_editing.last_action);
+
+ // searches the id of the first volume of the selected object
+ int volume_idx = 0;
+ for (int i = 0; i < object_idx_selected; ++i)
+ {
+ PrintObject* obj = m_print->get_object(i);
+ if (obj != nullptr)
+ {
+ for (int j = 0; j < (int)obj->region_volumes.size(); ++j)
+ {
+ volume_idx += (int)obj->region_volumes[j].size();
+ }
+ }
+ }
+
+ m_volumes.volumes[volume_idx]->generate_layer_height_texture(selected_obj, 1);
+ _refresh_if_shown_on_screen();
+
+ // Automatic action on mouse down with the same coordinate.
+ _start_timer();
+}
+
+Pointf3 GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z)
+{
+ if (!set_current())
+ return Pointf3(DBL_MAX, DBL_MAX, DBL_MAX);
+
+ GLint viewport[4];
+ ::glGetIntegerv(GL_VIEWPORT, viewport);
+ GLdouble modelview_matrix[16];
+ ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
+ GLdouble projection_matrix[16];
+ ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix);
+
+ GLint y = viewport[3] - (GLint)mouse_pos.y;
+ GLfloat mouse_z;
+ if (z == nullptr)
+ ::glReadPixels((GLint)mouse_pos.x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (void*)&mouse_z);
+ else
+ mouse_z = *z;
+
+ GLdouble out_x, out_y, out_z;
+ ::gluUnProject((GLdouble)mouse_pos.x, (GLdouble)y, (GLdouble)mouse_z, modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z);
+ return Pointf3((coordf_t)out_x, (coordf_t)out_y, (coordf_t)out_z);
+}
+
+Pointf3 GLCanvas3D::_mouse_to_bed_3d(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)).intersect_plane(0.0);
+}
+
+void GLCanvas3D::_start_timer()
+{
+ if (m_timer != nullptr)
+ m_timer->Start(100, wxTIMER_CONTINUOUS);
+}
+
+void GLCanvas3D::_stop_timer()
+{
+ if (m_timer != nullptr)
+ m_timer->Stop();
+}
+
+int GLCanvas3D::_get_first_selected_object_id() const
+{
+ if (m_print != nullptr)
+ {
+ int objects_count = (int)m_print->objects.size();
+
+ for (const GLVolume* vol : m_volumes.volumes)
+ {
+ if ((vol != nullptr) && vol->selected)
+ {
+ int object_id = vol->select_group_id / 1000000;
+ // Objects with object_id >= 1000 have a specific meaning, for example the wipe tower proxy.
+ if (object_id < 10000)
+ return (object_id >= objects_count) ? -1 : object_id;
+ }
+ }
+ }
+ return -1;
+}
+
+static inline int hex_digit_to_int(const char c)
+{
+ return
+ (c >= '0' && c <= '9') ? int(c - '0') :
+ (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
+ (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
+}
+
+void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors)
+{
+ // helper functions to select data in dependence of the extrusion view type
+ struct Helper
+ {
+ static float path_filter(GCodePreviewData::Extrusion::EViewType type, const ExtrusionPath& path)
+ {
+ switch (type)
+ {
+ case GCodePreviewData::Extrusion::FeatureType:
+ return (float)path.role();
+ case GCodePreviewData::Extrusion::Height:
+ return path.height;
+ case GCodePreviewData::Extrusion::Width:
+ return path.width;
+ case GCodePreviewData::Extrusion::Feedrate:
+ return path.feedrate;
+ case GCodePreviewData::Extrusion::VolumetricRate:
+ return path.feedrate * (float)path.mm3_per_mm;
+ case GCodePreviewData::Extrusion::Tool:
+ return (float)path.extruder_id;
+ default:
+ return 0.0f;
+ }
+
+ return 0.0f;
+ }
+
+ static GCodePreviewData::Color path_color(const GCodePreviewData& data, const std::vector<float>& tool_colors, float value)
+ {
+ switch (data.extrusion.view_type)
+ {
+ case GCodePreviewData::Extrusion::FeatureType:
+ return data.get_extrusion_role_color((ExtrusionRole)(int)value);
+ case GCodePreviewData::Extrusion::Height:
+ return data.get_height_color(value);
+ case GCodePreviewData::Extrusion::Width:
+ return data.get_width_color(value);
+ case GCodePreviewData::Extrusion::Feedrate:
+ return data.get_feedrate_color(value);
+ case GCodePreviewData::Extrusion::VolumetricRate:
+ return data.get_volumetric_rate_color(value);
+ case GCodePreviewData::Extrusion::Tool:
+ {
+ GCodePreviewData::Color color;
+ ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + (unsigned int)value * 4), 4 * sizeof(float));
+ return color;
+ }
+ default:
+ return GCodePreviewData::Color::Dummy;
+ }
+
+ return GCodePreviewData::Color::Dummy;
+ }
+ };
+
+ // Helper structure for filters
+ struct Filter
+ {
+ float value;
+ ExtrusionRole role;
+ GLVolume* volume;
+
+ Filter(float value, ExtrusionRole role)
+ : value(value)
+ , role(role)
+ , volume(nullptr)
+ {
+ }
+
+ bool operator == (const Filter& other) const
+ {
+ if (value != other.value)
+ return false;
+
+ if (role != other.role)
+ return false;
+
+ return true;
+ }
+ };
+
+ typedef std::vector<Filter> FiltersList;
+ size_t initial_volumes_count = m_volumes.volumes.size();
+
+ // detects filters
+ FiltersList filters;
+ for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
+ {
+ for (const ExtrusionPath& path : layer.paths)
+ {
+ ExtrusionRole role = path.role();
+ float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path);
+ if (std::find(filters.begin(), filters.end(), Filter(path_filter, role)) == filters.end())
+ filters.emplace_back(path_filter, role);
+ }
+ }
+
+ // nothing to render, return
+ if (filters.empty())
+ return;
+
+ // creates a new volume for each filter
+ for (Filter& filter : filters)
+ {
+ m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Extrusion, (unsigned int)filter.role, (unsigned int)m_volumes.volumes.size());
+ GLVolume* volume = new GLVolume(Helper::path_color(preview_data, tool_colors, filter.value).rgba);
+ if (volume != nullptr)
+ {
+ filter.volume = volume;
+ m_volumes.volumes.emplace_back(volume);
+ }
+ else
+ {
+ // an error occourred - restore to previous state and return
+ m_gcode_preview_volume_index.first_volumes.pop_back();
+ if (initial_volumes_count != m_volumes.volumes.size())
+ {
+ std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + initial_volumes_count;
+ std::vector<GLVolume*>::iterator end = m_volumes.volumes.end();
+ for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it)
+ {
+ GLVolume* volume = *it;
+ delete volume;
+ }
+ m_volumes.volumes.erase(begin, end);
+ return;
+ }
+ }
+ }
+
+ // populates volumes
+ for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
+ {
+ for (const ExtrusionPath& path : layer.paths)
+ {
+ float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path);
+ FiltersList::iterator filter = std::find(filters.begin(), filters.end(), Filter(path_filter, path.role()));
+ if (filter != filters.end())
+ {
+ filter->volume->print_zs.push_back(layer.z);
+ filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.quad_indices.size());
+ filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.triangle_indices.size());
+
+ _3DScene::extrusionentity_to_verts(path, layer.z, *filter->volume);
+ }
+ }
+ }
+
+ // finalize volumes and sends geometry to gpu
+ if (m_volumes.volumes.size() > initial_volumes_count)
+ {
+ for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i)
+ {
+ GLVolume* volume = m_volumes.volumes[i];
+ volume->bounding_box = volume->indexed_vertex_array.bounding_box();
+ volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
+ }
+ }
+}
+
+void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors)
+{
+ size_t initial_volumes_count = m_volumes.volumes.size();
+ m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Travel, 0, (unsigned int)initial_volumes_count);
+
+ bool res = true;
+ switch (preview_data.extrusion.view_type)
+ {
+ case GCodePreviewData::Extrusion::Feedrate:
+ {
+ res = _travel_paths_by_feedrate(preview_data);
+ break;
+ }
+ case GCodePreviewData::Extrusion::Tool:
+ {
+ res = _travel_paths_by_tool(preview_data, tool_colors);
+ break;
+ }
+ default:
+ {
+ res = _travel_paths_by_type(preview_data);
+ break;
+ }
+ }
+
+ if (!res)
+ {
+ // an error occourred - restore to previous state and return
+ if (initial_volumes_count != m_volumes.volumes.size())
+ {
+ std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + initial_volumes_count;
+ std::vector<GLVolume*>::iterator end = m_volumes.volumes.end();
+ for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it)
+ {
+ GLVolume* volume = *it;
+ delete volume;
+ }
+ m_volumes.volumes.erase(begin, end);
+ }
+
+ return;
+ }
+
+ // finalize volumes and sends geometry to gpu
+ if (m_volumes.volumes.size() > initial_volumes_count)
+ {
+ for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i)
+ {
+ GLVolume* volume = m_volumes.volumes[i];
+ volume->bounding_box = volume->indexed_vertex_array.bounding_box();
+ volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
+ }
+ }
+}
+
+bool GLCanvas3D::_travel_paths_by_type(const GCodePreviewData& preview_data)
+{
+ // Helper structure for types
+ struct Type
+ {
+ GCodePreviewData::Travel::EType value;
+ GLVolume* volume;
+
+ explicit Type(GCodePreviewData::Travel::EType value)
+ : value(value)
+ , volume(nullptr)
+ {
+ }
+
+ bool operator == (const Type& other) const
+ {
+ return value == other.value;
+ }
+ };
+
+ typedef std::vector<Type> TypesList;
+
+ // colors travels by travel type
+
+ // detects types
+ TypesList types;
+ for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
+ {
+ if (std::find(types.begin(), types.end(), Type(polyline.type)) == types.end())
+ types.emplace_back(polyline.type);
+ }
+
+ // nothing to render, return
+ if (types.empty())
+ return true;
+
+ // creates a new volume for each type
+ for (Type& type : types)
+ {
+ GLVolume* volume = new GLVolume(preview_data.travel.type_colors[type.value].rgba);
+ if (volume == nullptr)
+ return false;
+ else
+ {
+ type.volume = volume;
+ m_volumes.volumes.emplace_back(volume);
+ }
+ }
+
+ // populates volumes
+ for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
+ {
+ TypesList::iterator type = std::find(types.begin(), types.end(), Type(polyline.type));
+ if (type != types.end())
+ {
+ type->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
+ type->volume->offsets.push_back(type->volume->indexed_vertex_array.quad_indices.size());
+ type->volume->offsets.push_back(type->volume->indexed_vertex_array.triangle_indices.size());
+
+ _3DScene::polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *type->volume);
+ }
+ }
+
+ return true;
+}
+
+bool GLCanvas3D::_travel_paths_by_feedrate(const GCodePreviewData& preview_data)
+{
+ // Helper structure for feedrate
+ struct Feedrate
+ {
+ float value;
+ GLVolume* volume;
+
+ explicit Feedrate(float value)
+ : value(value)
+ , volume(nullptr)
+ {
+ }
+
+ bool operator == (const Feedrate& other) const
+ {
+ return value == other.value;
+ }
+ };
+
+ typedef std::vector<Feedrate> FeedratesList;
+
+ // colors travels by feedrate
+
+ // detects feedrates
+ FeedratesList feedrates;
+ for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
+ {
+ if (std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate)) == feedrates.end())
+ feedrates.emplace_back(polyline.feedrate);
+ }
+
+ // nothing to render, return
+ if (feedrates.empty())
+ return true;
+
+ // creates a new volume for each feedrate
+ for (Feedrate& feedrate : feedrates)
+ {
+ GLVolume* volume = new GLVolume(preview_data.get_feedrate_color(feedrate.value).rgba);
+ if (volume == nullptr)
+ return false;
+ else
+ {
+ feedrate.volume = volume;
+ m_volumes.volumes.emplace_back(volume);
+ }
+ }
+
+ // populates volumes
+ for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
+ {
+ FeedratesList::iterator feedrate = std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate));
+ if (feedrate != feedrates.end())
+ {
+ feedrate->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
+ feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.quad_indices.size());
+ feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.triangle_indices.size());
+
+ _3DScene::polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *feedrate->volume);
+ }
+ }
+
+ return true;
+}
+
+bool GLCanvas3D::_travel_paths_by_tool(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors)
+{
+ // Helper structure for tool
+ struct Tool
+ {
+ unsigned int value;
+ GLVolume* volume;
+
+ explicit Tool(unsigned int value)
+ : value(value)
+ , volume(nullptr)
+ {
+ }
+
+ bool operator == (const Tool& other) const
+ {
+ return value == other.value;
+ }
+ };
+
+ typedef std::vector<Tool> ToolsList;
+
+ // colors travels by tool
+
+ // detects tools
+ ToolsList tools;
+ for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
+ {
+ if (std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id)) == tools.end())
+ tools.emplace_back(polyline.extruder_id);
+ }
+
+ // nothing to render, return
+ if (tools.empty())
+ return true;
+
+ // creates a new volume for each tool
+ for (Tool& tool : tools)
+ {
+ GLVolume* volume = new GLVolume(tool_colors.data() + tool.value * 4);
+ if (volume == nullptr)
+ return false;
+ else
+ {
+ tool.volume = volume;
+ m_volumes.volumes.emplace_back(volume);
+ }
+ }
+
+ // populates volumes
+ for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
+ {
+ ToolsList::iterator tool = std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id));
+ if (tool != tools.end())
+ {
+ tool->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
+ tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.quad_indices.size());
+ tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.triangle_indices.size());
+
+ _3DScene::polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *tool->volume);
+ }
+ }
+
+ return true;
+}
+
+void GLCanvas3D::_load_gcode_retractions(const GCodePreviewData& preview_data)
+{
+ m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Retraction, 0, (unsigned int)m_volumes.volumes.size());
+
+ // nothing to render, return
+ if (preview_data.retraction.positions.empty())
+ return;
+
+ GLVolume* volume = new GLVolume(preview_data.retraction.color.rgba);
+ if (volume != nullptr)
+ {
+ m_volumes.volumes.emplace_back(volume);
+
+ GCodePreviewData::Retraction::PositionsList copy(preview_data.retraction.positions);
+ std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; });
+
+ for (const GCodePreviewData::Retraction::Position& position : copy)
+ {
+ volume->print_zs.push_back(unscale(position.position.z));
+ volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
+ volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
+
+ _3DScene::point3_to_verts(position.position, position.width, position.height, *volume);
+ }
+
+ // finalize volumes and sends geometry to gpu
+ volume->bounding_box = volume->indexed_vertex_array.bounding_box();
+ volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
+ }
+}
+
+void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data)
+{
+ m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Unretraction, 0, (unsigned int)m_volumes.volumes.size());
+
+ // nothing to render, return
+ if (preview_data.unretraction.positions.empty())
+ return;
+
+ GLVolume* volume = new GLVolume(preview_data.unretraction.color.rgba);
+ if (volume != nullptr)
+ {
+ m_volumes.volumes.emplace_back(volume);
+
+ GCodePreviewData::Retraction::PositionsList copy(preview_data.unretraction.positions);
+ std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; });
+
+ for (const GCodePreviewData::Retraction::Position& position : copy)
+ {
+ volume->print_zs.push_back(unscale(position.position.z));
+ volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
+ volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
+
+ _3DScene::point3_to_verts(position.position, position.width, position.height, *volume);
+ }
+
+ // finalize volumes and sends geometry to gpu
+ volume->bounding_box = volume->indexed_vertex_array.bounding_box();
+ volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
+ }
+}
+
+void GLCanvas3D::_load_shells()
+{
+ size_t initial_volumes_count = m_volumes.volumes.size();
+ m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Shell, 0, (unsigned int)initial_volumes_count);
+
+ if (m_print->objects.empty())
+ // nothing to render, return
+ return;
+
+ // adds objects' volumes
+ unsigned int object_id = 0;
+ for (PrintObject* obj : m_print->objects)
+ {
+ ModelObject* model_obj = obj->model_object();
+
+ std::vector<int> instance_ids(model_obj->instances.size());
+ for (int i = 0; i < (int)model_obj->instances.size(); ++i)
+ {
+ instance_ids[i] = i;
+ }
+
+ m_volumes.load_object(model_obj, object_id, instance_ids, "object", "object", "object", m_use_VBOs && m_initialized);
+
+ ++object_id;
+ }
+
+ // adds wipe tower's volume
+ coordf_t max_z = m_print->objects[0]->model_object()->get_model()->bounding_box().max.z;
+ const PrintConfig& config = m_print->config;
+ unsigned int extruders_count = config.nozzle_diameter.size();
+ if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) {
+ const float width_per_extruder = 15.0f; // a simple workaround after wipe_tower_per_color_wipe got obsolete
+ m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, width_per_extruder * (extruders_count - 1), max_z, config.wipe_tower_rotation_angle, m_use_VBOs && m_initialized);
+ }
+}
+
+void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data)
+{
+ unsigned int size = (unsigned int)m_gcode_preview_volume_index.first_volumes.size();
+ for (unsigned int i = 0; i < size; ++i)
+ {
+ std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i].id;
+ std::vector<GLVolume*>::iterator end = (i + 1 < size) ? m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i + 1].id : m_volumes.volumes.end();
+
+ for (std::vector<GLVolume*>::iterator it = begin; it != end; ++it)
+ {
+ GLVolume* volume = *it;
+ volume->outside_printer_detection_enabled = false;
+
+ switch (m_gcode_preview_volume_index.first_volumes[i].type)
+ {
+ case GCodePreviewVolumeIndex::Extrusion:
+ {
+ if ((ExtrusionRole)m_gcode_preview_volume_index.first_volumes[i].flag == erCustom)
+ volume->zoom_to_volumes = false;
+
+ volume->is_active = preview_data.extrusion.is_role_flag_set((ExtrusionRole)m_gcode_preview_volume_index.first_volumes[i].flag);
+ break;
+ }
+ case GCodePreviewVolumeIndex::Travel:
+ {
+ volume->is_active = preview_data.travel.is_visible;
+ volume->zoom_to_volumes = false;
+ break;
+ }
+ case GCodePreviewVolumeIndex::Retraction:
+ {
+ volume->is_active = preview_data.retraction.is_visible;
+ volume->zoom_to_volumes = false;
+ break;
+ }
+ case GCodePreviewVolumeIndex::Unretraction:
+ {
+ volume->is_active = preview_data.unretraction.is_visible;
+ volume->zoom_to_volumes = false;
+ break;
+ }
+ case GCodePreviewVolumeIndex::Shell:
+ {
+ volume->is_active = preview_data.shell.is_visible;
+ volume->color[3] = 0.25f;
+ volume->zoom_to_volumes = false;
+ break;
+ }
+ default:
+ {
+ volume->is_active = false;
+ volume->zoom_to_volumes = false;
+ break;
+ }
+ }
+ }
+ }
+}
+
+void GLCanvas3D::_on_move(const std::vector<int>& volume_idxs)
+{
+ if (m_model == nullptr)
+ return;
+
+ std::set<std::string> done; // prevent moving instances twice
+ bool object_moved = false;
+ Pointf3 wipe_tower_origin(0.0, 0.0, 0.0);
+ for (int volume_idx : volume_idxs)
+ {
+ GLVolume* volume = m_volumes.volumes[volume_idx];
+ int obj_idx = volume->object_idx();
+ int instance_idx = volume->instance_idx();
+
+ // prevent moving instances twice
+ char done_id[64];
+ ::sprintf(done_id, "%d_%d", obj_idx, instance_idx);
+ if (done.find(done_id) != done.end())
+ continue;
+
+ done.insert(done_id);
+
+ if (obj_idx < 1000)
+ {
+ // Move a regular object.
+ ModelObject* model_object = m_model->objects[obj_idx];
+ model_object->instances[instance_idx]->offset.translate(volume->origin.x, volume->origin.y);
+ model_object->invalidate_bounding_box();
+ object_moved = true;
+ }
+ else if (obj_idx == 1000)
+ // Move a wipe tower proxy.
+ wipe_tower_origin = volume->origin;
+ }
+
+ if (object_moved)
+ m_on_instance_moved_callback.call();
+
+ if (wipe_tower_origin != Pointf3(0.0, 0.0, 0.0))
+ m_on_wipe_tower_moved_callback.call(wipe_tower_origin.x, wipe_tower_origin.y);
+}
+
+void GLCanvas3D::_on_select(int volume_idx)
+{
+ int id = -1;
+ if ((volume_idx != -1) && (volume_idx < (int)m_volumes.volumes.size()))
+ {
+ if (m_select_by == "volume")
+ id = m_volumes.volumes[volume_idx]->volume_idx();
+ else if (m_select_by == "object")
+ id = m_volumes.volumes[volume_idx]->object_idx();
+ }
+ m_on_select_object_callback.call(id);
+}
+
+void GLCanvas3D::_update_gizmos_data()
+{
+ int id = _get_first_selected_object_id();
+ if ((id != -1) && (m_model != nullptr))
+ {
+ ModelObject* model_object = m_model->objects[id];
+ if (model_object != nullptr)
+ {
+ ModelInstance* model_instance = model_object->instances[0];
+ if (model_instance != nullptr)
+ m_gizmos.update_data(model_instance->scaling_factor);
+ }
+ }
+}
+
+std::vector<float> GLCanvas3D::_parse_colors(const std::vector<std::string>& colors)
+{
+ static const float INV_255 = 1.0f / 255.0f;
+
+ std::vector<float> output(colors.size() * 4, 1.0f);
+ for (size_t i = 0; i < colors.size(); ++i)
+ {
+ 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;
+
+ output[i * 4 + j] = float(digit1 * 16 + digit2) * INV_255;
+ }
+ }
+ }
+ return output;
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp
new file mode 100644
index 000000000..c503d1845
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp
@@ -0,0 +1,639 @@
+#ifndef slic3r_GLCanvas3D_hpp_
+#define slic3r_GLCanvas3D_hpp_
+
+#include "../../slic3r/GUI/3DScene.hpp"
+#include "../../slic3r/GUI/GLTexture.hpp"
+
+class wxTimer;
+class wxSizeEvent;
+class wxIdleEvent;
+class wxKeyEvent;
+class wxMouseEvent;
+class wxTimerEvent;
+class wxPaintEvent;
+
+namespace Slic3r {
+
+class GLShader;
+class ExPolygon;
+
+namespace GUI {
+
+class GLGizmoBase;
+
+class GeometryBuffer
+{
+ std::vector<float> m_vertices;
+ std::vector<float> m_tex_coords;
+
+public:
+ bool set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords);
+ bool set_from_lines(const Lines& lines, float z);
+
+ const float* get_vertices() const;
+ const float* get_tex_coords() const;
+
+ unsigned int get_vertices_count() const;
+};
+
+class Size
+{
+ int m_width;
+ int m_height;
+
+public:
+ Size();
+ Size(int width, int height);
+
+ int get_width() const;
+ void set_width(int width);
+
+ int get_height() const;
+ void set_height(int height);
+};
+
+class Rect
+{
+ float m_left;
+ float m_top;
+ float m_right;
+ float m_bottom;
+
+public:
+ Rect();
+ Rect(float left, float top, float right, float bottom);
+
+ float get_left() const;
+ void set_left(float left);
+
+ float get_top() const;
+ void set_top(float top);
+
+ float get_right() const;
+ void set_right(float right);
+
+ float get_bottom() const;
+ void set_bottom(float bottom);
+};
+
+class GLCanvas3D
+{
+ struct GCodePreviewVolumeIndex
+ {
+ enum EType
+ {
+ Extrusion,
+ Travel,
+ Retraction,
+ Unretraction,
+ Shell,
+ Num_Geometry_Types
+ };
+
+ struct FirstVolume
+ {
+ EType type;
+ unsigned int flag;
+ // Index of the first volume in a GLVolumeCollection.
+ unsigned int id;
+
+ FirstVolume(EType type, unsigned int flag, unsigned int id) : type(type), flag(flag), id(id) {}
+ };
+
+ std::vector<FirstVolume> first_volumes;
+
+ void reset() { first_volumes.clear(); }
+ };
+
+public:
+ struct Camera
+ {
+ enum EType : unsigned char
+ {
+ Unknown,
+// Perspective,
+ Ortho,
+ Num_types
+ };
+
+ EType type;
+ float zoom;
+ float phi;
+// float distance;
+ Pointf3 target;
+
+ private:
+ float m_theta;
+
+ public:
+ Camera();
+
+ std::string get_type_as_string() const;
+
+ float get_theta() const;
+ void set_theta(float theta);
+ };
+
+ class Bed
+ {
+ public:
+ enum EType : unsigned char
+ {
+ MK2,
+ MK3,
+ Custom,
+ Num_Types
+ };
+
+ private:
+ EType m_type;
+ Pointfs m_shape;
+ BoundingBoxf3 m_bounding_box;
+ Polygon m_polygon;
+ GeometryBuffer m_triangles;
+ GeometryBuffer m_gridlines;
+ mutable GLTexture m_top_texture;
+ mutable GLTexture m_bottom_texture;
+
+ public:
+ Bed();
+
+ bool is_prusa() const;
+ bool is_custom() const;
+
+ const Pointfs& get_shape() const;
+ void set_shape(const Pointfs& shape);
+
+ const BoundingBoxf3& get_bounding_box() const;
+ bool contains(const Point& point) const;
+ Point point_projection(const Point& point) const;
+
+ void render(float theta) const;
+
+ private:
+ void _calc_bounding_box();
+ void _calc_triangles(const ExPolygon& poly);
+ void _calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox);
+ EType _detect_type() const;
+ void _render_mk2(float theta) const;
+ void _render_mk3(float theta) const;
+ void _render_prusa(float theta) const;
+ void _render_custom() const;
+ static bool _are_equal(const Pointfs& bed_1, const Pointfs& bed_2);
+ };
+
+ struct Axes
+ {
+ Pointf3 origin;
+ float length;
+
+ Axes();
+
+ void render(bool depth_test) const;
+ };
+
+ class CuttingPlane
+ {
+ float m_z;
+ GeometryBuffer m_lines;
+
+ public:
+ CuttingPlane();
+
+ bool set(float z, const ExPolygons& polygons);
+
+ void render(const BoundingBoxf3& bb) const;
+
+ private:
+ void _render_plane(const BoundingBoxf3& bb) const;
+ void _render_contour() const;
+ };
+
+ class Shader
+ {
+ GLShader* m_shader;
+
+ public:
+ Shader();
+ ~Shader();
+
+ bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename);
+
+ bool is_initialized() const;
+
+ bool start_using() const;
+ void stop_using() const;
+
+ void set_uniform(const std::string& name, float value) const;
+
+ const GLShader* get_shader() const;
+
+ private:
+ void _reset();
+ };
+
+ class LayersEditing
+ {
+ public:
+ enum EState : unsigned char
+ {
+ Unknown,
+ Editing,
+ Completed,
+ Num_States
+ };
+
+ private:
+ bool m_use_legacy_opengl;
+ bool m_enabled;
+ Shader m_shader;
+ unsigned int m_z_texture_id;
+ mutable GLTexture m_tooltip_texture;
+ mutable GLTexture m_reset_texture;
+
+ public:
+ EState state;
+ float band_width;
+ float strength;
+ int last_object_id;
+ float last_z;
+ unsigned int last_action;
+
+ LayersEditing();
+ ~LayersEditing();
+
+ bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename);
+
+ bool is_allowed() const;
+ void set_use_legacy_opengl(bool use_legacy_opengl);
+
+ bool is_enabled() const;
+ void set_enabled(bool enabled);
+
+ unsigned int get_z_texture_id() const;
+
+ void render(const GLCanvas3D& canvas, const PrintObject& print_object, const GLVolume& volume) const;
+
+ int get_shader_program_id() const;
+
+ static float get_cursor_z_relative(const GLCanvas3D& canvas);
+ static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y);
+ static bool reset_rect_contains(const GLCanvas3D& canvas, float x, float y);
+ static Rect get_bar_rect_screen(const GLCanvas3D& canvas);
+ static Rect get_reset_rect_screen(const GLCanvas3D& canvas);
+ static Rect get_bar_rect_viewport(const GLCanvas3D& canvas);
+ static Rect get_reset_rect_viewport(const GLCanvas3D& canvas);
+
+ private:
+ bool _is_initialized() const;
+ void _render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const;
+ void _render_reset_texture(const Rect& reset_rect) const;
+ void _render_active_object_annotations(const GLCanvas3D& canvas, const GLVolume& volume, const PrintObject& print_object, const Rect& bar_rect) const;
+ void _render_profile(const PrintObject& print_object, const Rect& bar_rect) const;
+ };
+
+ struct Mouse
+ {
+ struct Drag
+ {
+ static const Point Invalid_2D_Point;
+ static const Pointf3 Invalid_3D_Point;
+
+ Point start_position_2D;
+ Pointf3 start_position_3D;
+ Vectorf3 volume_center_offset;
+ int volume_idx;
+
+ public:
+ Drag();
+ };
+
+ bool dragging;
+ Pointf position;
+ Drag drag;
+
+ Mouse();
+
+ void set_start_position_2D_as_invalid();
+ void set_start_position_3D_as_invalid();
+
+ bool is_start_position_2D_defined() const;
+ bool is_start_position_3D_defined() const;
+ };
+
+ class Gizmos
+ {
+ static const float OverlayOffsetX;
+ static const float OverlayGapY;
+
+ public:
+ enum EType : unsigned char
+ {
+ Undefined,
+ Scale,
+ Rotate,
+ Num_Types
+ };
+
+ private:
+ bool m_enabled;
+ typedef std::map<EType, GLGizmoBase*> GizmosMap;
+ GizmosMap m_gizmos;
+ EType m_current;
+ bool m_dragging;
+
+ public:
+ Gizmos();
+ ~Gizmos();
+
+ bool init();
+
+ bool is_enabled() const;
+ void set_enabled(bool enable);
+
+ void update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos);
+ void update_on_off_state(const GLCanvas3D& canvas, const Pointf& mouse_pos);
+ void reset_all_states();
+
+ void set_hover_id(int id);
+
+ bool overlay_contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const;
+ bool grabber_contains_mouse() const;
+ void update(const Pointf& mouse_pos);
+ void update_data(float scale);
+
+ bool is_running() const;
+ bool is_dragging() const;
+ void start_dragging();
+ void stop_dragging();
+
+ float get_scale() const;
+
+ void render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const;
+ void render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const;
+
+ private:
+ void _reset();
+
+ void _render_overlay(const GLCanvas3D& canvas) const;
+ void _render_current_gizmo(const BoundingBoxf3& box) const;
+
+ float _get_total_overlay_height() const;
+ GLGizmoBase* _get_current() const;
+ };
+
+private:
+ wxGLCanvas* m_canvas;
+ wxGLContext* m_context;
+ wxTimer* m_timer;
+ Camera m_camera;
+ Bed m_bed;
+ Axes m_axes;
+ CuttingPlane m_cutting_plane;
+ LayersEditing m_layers_editing;
+ Shader m_shader;
+ Mouse m_mouse;
+ mutable Gizmos m_gizmos;
+
+ mutable GLVolumeCollection m_volumes;
+ DynamicPrintConfig* m_config;
+ Print* m_print;
+ Model* m_model;
+
+ bool m_dirty;
+ // the active member has been introduced to overcome a bug in wxWidgets method IsShownOnScreen() which always return true
+ // when a window is inside a wxNotebook
+ bool m_active;
+ bool m_initialized;
+ bool m_use_VBOs;
+ bool m_force_zoom_to_bed_enabled;
+ bool m_apply_zoom_to_volumes_filter;
+ mutable int m_hover_volume_id;
+ bool m_warning_texture_enabled;
+ bool m_legend_texture_enabled;
+ bool m_picking_enabled;
+ bool m_moving_enabled;
+ bool m_shader_enabled;
+ bool m_multisample_allowed;
+
+ std::string m_color_by;
+ std::string m_select_by;
+ std::string m_drag_by;
+
+ bool m_reload_delayed;
+ std::vector<std::vector<int>> m_objects_volumes_idxs;
+ std::vector<int> m_objects_selections;
+
+ GCodePreviewVolumeIndex m_gcode_preview_volume_index;
+
+ PerlCallback m_on_viewport_changed_callback;
+ PerlCallback m_on_double_click_callback;
+ PerlCallback m_on_right_click_callback;
+ PerlCallback m_on_select_object_callback;
+ PerlCallback m_on_model_update_callback;
+ PerlCallback m_on_remove_object_callback;
+ PerlCallback m_on_arrange_callback;
+ PerlCallback m_on_rotate_object_left_callback;
+ PerlCallback m_on_rotate_object_right_callback;
+ PerlCallback m_on_scale_object_uniformly_callback;
+ PerlCallback m_on_increase_objects_callback;
+ PerlCallback m_on_decrease_objects_callback;
+ PerlCallback m_on_instance_moved_callback;
+ PerlCallback m_on_wipe_tower_moved_callback;
+ PerlCallback m_on_enable_action_buttons_callback;
+ PerlCallback m_on_gizmo_scale_uniformly_callback;
+
+public:
+ GLCanvas3D(wxGLCanvas* canvas, wxGLContext* context);
+ ~GLCanvas3D();
+
+ bool init(bool useVBOs, bool use_legacy_opengl);
+
+ bool set_current();
+
+ void set_active(bool active);
+
+ unsigned int get_volumes_count() const;
+ void reset_volumes();
+ void deselect_volumes();
+ void select_volume(unsigned int id);
+ void update_volumes_selection(const std::vector<int>& selections);
+ bool check_volumes_outside_state(const DynamicPrintConfig* config) const;
+ bool move_volume_up(unsigned int id);
+ bool move_volume_down(unsigned int id);
+
+ void set_objects_selections(const std::vector<int>& selections);
+
+ void set_config(DynamicPrintConfig* config);
+ void set_print(Print* print);
+ void set_model(Model* model);
+
+ // Set the bed shape to a single closed 2D polygon(array of two element arrays),
+ // triangulate the bed and store the triangles into m_bed.m_triangles,
+ // fills the m_bed.m_grid_lines and sets m_bed.m_origin.
+ // Sets m_bed.m_polygon to limit the object placement.
+ void set_bed_shape(const Pointfs& shape);
+ // Used by ObjectCutDialog and ObjectPartsPanel to generate a rectangular ground plane to support the scene objects.
+ void set_auto_bed_shape();
+
+ void set_axes_length(float length);
+
+ void set_cutting_plane(float z, const ExPolygons& polygons);
+
+ void set_color_by(const std::string& value);
+ void set_select_by(const std::string& value);
+ void set_drag_by(const std::string& value);
+
+ float get_camera_zoom() const;
+
+ BoundingBoxf3 volumes_bounding_box() const;
+
+ bool is_layers_editing_enabled() const;
+ bool is_layers_editing_allowed() const;
+ bool is_shader_enabled() const;
+
+ bool is_reload_delayed() const;
+
+ void enable_layers_editing(bool enable);
+ void enable_warning_texture(bool enable);
+ void enable_legend_texture(bool enable);
+ void enable_picking(bool enable);
+ void enable_moving(bool enable);
+ void enable_gizmos(bool enable);
+ void enable_shader(bool enable);
+ void enable_force_zoom_to_bed(bool enable);
+ void allow_multisample(bool allow);
+
+ void zoom_to_bed();
+ void zoom_to_volumes();
+ void select_view(const std::string& direction);
+ void set_viewport_from_scene(const GLCanvas3D& other);
+
+ void update_volumes_colors_by_extruder();
+
+ void render();
+
+ std::vector<double> get_current_print_zs(bool active_only) const;
+ void set_toolpaths_range(double low, double high);
+
+ std::vector<int> load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs);
+ std::vector<int> load_object(const Model& model, int obj_idx);
+
+ void reload_scene(bool force);
+
+ // Create 3D thick extrusion lines for a skirt and brim.
+ // Adds a new Slic3r::GUI::3DScene::Volume to volumes.
+ void load_print_toolpaths();
+ // Create 3D thick extrusion lines for object forming extrusions.
+ // Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
+ // one for perimeters, one for infill and one for supports.
+ void load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors);
+ // Create 3D thick extrusion lines for wipe tower extrusions
+ void load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors);
+ void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors);
+
+ void register_on_viewport_changed_callback(void* callback);
+ void register_on_double_click_callback(void* callback);
+ void register_on_right_click_callback(void* callback);
+ void register_on_select_object_callback(void* callback);
+ void register_on_model_update_callback(void* callback);
+ void register_on_remove_object_callback(void* callback);
+ void register_on_arrange_callback(void* callback);
+ void register_on_rotate_object_left_callback(void* callback);
+ void register_on_rotate_object_right_callback(void* callback);
+ void register_on_scale_object_uniformly_callback(void* callback);
+ void register_on_increase_objects_callback(void* callback);
+ void register_on_decrease_objects_callback(void* callback);
+ void register_on_instance_moved_callback(void* callback);
+ void register_on_wipe_tower_moved_callback(void* callback);
+ void register_on_enable_action_buttons_callback(void* callback);
+ void register_on_gizmo_scale_uniformly_callback(void* callback);
+
+ void bind_event_handlers();
+ void unbind_event_handlers();
+
+ void on_size(wxSizeEvent& evt);
+ void on_idle(wxIdleEvent& evt);
+ void on_char(wxKeyEvent& evt);
+ void on_mouse_wheel(wxMouseEvent& evt);
+ void on_timer(wxTimerEvent& evt);
+ void on_mouse(wxMouseEvent& evt);
+ void on_paint(wxPaintEvent& evt);
+ void on_key_down(wxKeyEvent& evt);
+
+ Size get_canvas_size() const;
+ Point get_local_mouse_position() const;
+
+private:
+ bool _is_shown_on_screen() const;
+ void _force_zoom_to_bed();
+
+ void _resize(unsigned int w, unsigned int h);
+
+ BoundingBoxf3 _max_bounding_box() const;
+ BoundingBoxf3 _selected_volumes_bounding_box() const;
+
+ void _zoom_to_bounding_box(const BoundingBoxf3& bbox);
+ float _get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const;
+
+ void _deregister_callbacks();
+
+ void _mark_volumes_for_layer_height() const;
+ void _refresh_if_shown_on_screen();
+
+ void _camera_tranform() const;
+ void _picking_pass() const;
+ void _render_background() const;
+ void _render_bed(float theta) const;
+ void _render_axes(bool depth_test) const;
+ void _render_objects() const;
+ void _render_cutting_plane() const;
+ void _render_warning_texture() const;
+ void _render_legend_texture() const;
+ void _render_layer_editing_overlay() const;
+ void _render_volumes(bool fake_colors) const;
+ void _render_gizmo() const;
+
+ float _get_layers_editing_cursor_z_relative() const;
+ void _perform_layer_editing_action(wxMouseEvent* evt = nullptr);
+
+ // Convert the screen space coordinate to an object space coordinate.
+ // If the Z screen space coordinate is not provided, a depth buffer value is substituted.
+ Pointf3 _mouse_to_3d(const Point& mouse_pos, float* z = nullptr);
+
+ // Convert the screen space coordinate to world coordinate on the bed.
+ Pointf3 _mouse_to_bed_3d(const Point& mouse_pos);
+
+ void _start_timer();
+ void _stop_timer();
+
+ int _get_first_selected_object_id() const;
+
+ // generates gcode extrusion paths geometry
+ void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
+ // generates gcode travel paths geometry
+ void _load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
+ bool _travel_paths_by_type(const GCodePreviewData& preview_data);
+ bool _travel_paths_by_feedrate(const GCodePreviewData& preview_data);
+ bool _travel_paths_by_tool(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
+ // generates gcode retractions geometry
+ void _load_gcode_retractions(const GCodePreviewData& preview_data);
+ // generates gcode unretractions geometry
+ void _load_gcode_unretractions(const GCodePreviewData& preview_data);
+ // generates objects and wipe tower geometry
+ void _load_shells();
+ // sets gcode geometry visibility according to user selection
+ void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data);
+
+ void _on_move(const std::vector<int>& volume_idxs);
+ void _on_select(int volume_idx);
+
+ void _update_gizmos_data();
+
+ static std::vector<float> _parse_colors(const std::vector<std::string>& colors);
+};
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLCanvas3D_hpp_
diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp
new file mode 100644
index 000000000..f288ee456
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp
@@ -0,0 +1,707 @@
+#include "GLCanvas3DManager.hpp"
+#include "../../slic3r/GUI/GUI.hpp"
+#include "../../slic3r/GUI/AppConfig.hpp"
+#include "../../slic3r/GUI/GLCanvas3D.hpp"
+
+#include <GL/glew.h>
+
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
+
+#include <wx/glcanvas.h>
+#include <wx/timer.h>
+
+#include <vector>
+#include <string>
+#include <iostream>
+
+namespace Slic3r {
+namespace GUI {
+
+GLCanvas3DManager::GLInfo::GLInfo()
+ : version("")
+ , glsl_version("")
+ , vendor("")
+ , renderer("")
+{
+}
+
+bool GLCanvas3DManager::GLInfo::detect()
+{
+ const char* data = (const char*)::glGetString(GL_VERSION);
+ if (data == nullptr)
+ return false;
+
+ version = data;
+
+ data = (const char*)::glGetString(GL_SHADING_LANGUAGE_VERSION);
+ if (data == nullptr)
+ return false;
+
+ glsl_version = data;
+
+ data = (const char*)::glGetString(GL_VENDOR);
+ if (data == nullptr)
+ return false;
+
+ vendor = data;
+
+ data = (const char*)::glGetString(GL_RENDERER);
+ if (data == nullptr)
+ return false;
+
+ renderer = data;
+
+ return true;
+}
+
+bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
+{
+ std::vector<std::string> tokens;
+ boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on);
+
+ if (tokens.empty())
+ return false;
+
+ std::vector<std::string> numbers;
+ boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on);
+
+ unsigned int gl_major = 0;
+ unsigned int gl_minor = 0;
+
+ if (numbers.size() > 0)
+ gl_major = ::atoi(numbers[0].c_str());
+
+ if (numbers.size() > 1)
+ gl_minor = ::atoi(numbers[1].c_str());
+
+ if (gl_major < major)
+ return false;
+ else if (gl_major > major)
+ return true;
+ else
+ return gl_minor >= minor;
+}
+
+std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool extensions) const
+{
+ std::stringstream out;
+
+ std::string h2_start = format_as_html ? "<b>" : "";
+ std::string h2_end = format_as_html ? "</b>" : "";
+ std::string b_start = format_as_html ? "<b>" : "";
+ std::string b_end = format_as_html ? "</b>" : "";
+ std::string line_end = format_as_html ? "<br>" : "\n";
+
+ out << h2_start << "OpenGL installation" << h2_end << line_end;
+ out << b_start << "GL version: " << b_end << version << line_end;
+ out << b_start << "Vendor: " << b_end << vendor << line_end;
+ out << b_start << "Renderer: " << b_end << renderer << line_end;
+ out << b_start << "GLSL version: " << b_end << glsl_version << line_end;
+
+ if (extensions)
+ {
+ out << h2_start << "Installed extensions:" << h2_end << line_end;
+
+ std::vector<std::string> extensions_list;
+ GLint num_extensions;
+ ::glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
+
+ for (GLint i = 0; i < num_extensions; ++i)
+ {
+ const char* e = (const char*)::glGetStringi(GL_EXTENSIONS, i);
+ extensions_list.push_back(e);
+ }
+
+ std::sort(extensions_list.begin(), extensions_list.end());
+ for (const std::string& ext : extensions_list)
+ {
+ out << ext << line_end;
+ }
+ }
+
+ return out.str();
+}
+
+GLCanvas3DManager::GLCanvas3DManager()
+ : m_context(nullptr)
+ , m_gl_initialized(false)
+ , m_use_legacy_opengl(false)
+ , m_use_VBOs(false)
+{
+}
+
+GLCanvas3DManager::~GLCanvas3DManager()
+{
+ if (m_context != nullptr)
+ delete m_context;
+}
+
+bool GLCanvas3DManager::add(wxGLCanvas* canvas)
+{
+ if (canvas == nullptr)
+ return false;
+
+ if (_get_canvas(canvas) != m_canvases.end())
+ return false;
+
+ if (m_context == nullptr)
+ {
+ m_context = new wxGLContext(canvas);
+ if (m_context == nullptr)
+ return false;
+ }
+
+ GLCanvas3D* canvas3D = new GLCanvas3D(canvas, m_context);
+ if (canvas3D == nullptr)
+ return false;
+
+ canvas3D->bind_event_handlers();
+ m_canvases.insert(CanvasesMap::value_type(canvas, canvas3D));
+
+ return true;
+}
+
+bool GLCanvas3DManager::remove(wxGLCanvas* canvas)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it == m_canvases.end())
+ return false;
+
+ it->second->unbind_event_handlers();
+ delete it->second;
+ m_canvases.erase(it);
+
+ return true;
+}
+
+void GLCanvas3DManager::remove_all()
+{
+ for (CanvasesMap::value_type& item : m_canvases)
+ {
+ item.second->unbind_event_handlers();
+ delete item.second;
+ }
+ m_canvases.clear();
+}
+
+unsigned int GLCanvas3DManager::count() const
+{
+ return (unsigned int)m_canvases.size();
+}
+
+void GLCanvas3DManager::init_gl()
+{
+ if (!m_gl_initialized)
+ {
+ glewInit();
+ if (m_gl_info.detect())
+ {
+ const AppConfig* config = GUI::get_app_config();
+ m_use_legacy_opengl = (config == nullptr) || (config->get("use_legacy_opengl") == "1");
+ m_use_VBOs = !m_use_legacy_opengl && m_gl_info.is_version_greater_or_equal_to(2, 0);
+ m_gl_initialized = true;
+ }
+ else
+ throw std::runtime_error(std::string("Unable to initialize OpenGL driver\n"));
+ }
+}
+
+std::string GLCanvas3DManager::get_gl_info(bool format_as_html, bool extensions) const
+{
+ return m_gl_info.to_string(format_as_html, extensions);
+}
+
+bool GLCanvas3DManager::use_VBOs() const
+{
+ return m_use_VBOs;
+}
+
+bool GLCanvas3DManager::init(wxGLCanvas* canvas)
+{
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ return (it->second != nullptr) ? _init(*it->second) : false;
+ else
+ return false;
+}
+
+void GLCanvas3DManager::set_active(wxGLCanvas* canvas, bool active)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->set_active(active);
+}
+
+unsigned int GLCanvas3DManager::get_volumes_count(wxGLCanvas* canvas) const
+{
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ return (it != m_canvases.end()) ? it->second->get_volumes_count() : 0;
+}
+
+void GLCanvas3DManager::reset_volumes(wxGLCanvas* canvas)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->reset_volumes();
+}
+
+void GLCanvas3DManager::deselect_volumes(wxGLCanvas* canvas)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->deselect_volumes();
+}
+
+void GLCanvas3DManager::select_volume(wxGLCanvas* canvas, unsigned int id)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->select_volume(id);
+}
+
+void GLCanvas3DManager::update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->update_volumes_selection(selections);
+}
+
+bool GLCanvas3DManager::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const
+{
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ return (it != m_canvases.end()) ? it->second->check_volumes_outside_state(config) : false;
+}
+
+bool GLCanvas3DManager::move_volume_up(wxGLCanvas* canvas, unsigned int id)
+{
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ return (it != m_canvases.end()) ? it->second->move_volume_up(id) : false;
+}
+
+bool GLCanvas3DManager::move_volume_down(wxGLCanvas* canvas, unsigned int id)
+{
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ return (it != m_canvases.end()) ? it->second->move_volume_down(id) : false;
+}
+
+void GLCanvas3DManager::set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->set_objects_selections(selections);
+}
+
+void GLCanvas3DManager::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->set_config(config);
+}
+
+void GLCanvas3DManager::set_print(wxGLCanvas* canvas, Print* print)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->set_print(print);
+}
+
+void GLCanvas3DManager::set_model(wxGLCanvas* canvas, Model* model)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->set_model(model);
+}
+
+void GLCanvas3DManager::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->set_bed_shape(shape);
+}
+
+void GLCanvas3DManager::set_auto_bed_shape(wxGLCanvas* canvas)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->set_auto_bed_shape();
+}
+
+BoundingBoxf3 GLCanvas3DManager::get_volumes_bounding_box(wxGLCanvas* canvas)
+{
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ return (it != m_canvases.end()) ? it->second->volumes_bounding_box() : BoundingBoxf3();
+}
+
+void GLCanvas3DManager::set_axes_length(wxGLCanvas* canvas, float length)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->set_axes_length(length);
+}
+
+void GLCanvas3DManager::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->set_cutting_plane(z, polygons);
+}
+
+void GLCanvas3DManager::set_color_by(wxGLCanvas* canvas, const std::string& value)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->set_color_by(value);
+}
+
+void GLCanvas3DManager::set_select_by(wxGLCanvas* canvas, const std::string& value)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->set_select_by(value);
+}
+
+void GLCanvas3DManager::set_drag_by(wxGLCanvas* canvas, const std::string& value)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->set_drag_by(value);
+}
+
+bool GLCanvas3DManager::is_layers_editing_enabled(wxGLCanvas* canvas) const
+{
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ return (it != m_canvases.end()) ? it->second->is_layers_editing_enabled() : false;
+}
+
+bool GLCanvas3DManager::is_layers_editing_allowed(wxGLCanvas* canvas) const
+{
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ return (it != m_canvases.end()) ? it->second->is_layers_editing_allowed() : false;
+}
+
+bool GLCanvas3DManager::is_shader_enabled(wxGLCanvas* canvas) const
+{
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ return (it != m_canvases.end()) ? it->second->is_shader_enabled() : false;
+}
+
+bool GLCanvas3DManager::is_reload_delayed(wxGLCanvas* canvas) const
+{
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ return (it != m_canvases.end()) ? it->second->is_reload_delayed() : false;
+}
+
+void GLCanvas3DManager::enable_layers_editing(wxGLCanvas* canvas, bool enable)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->enable_layers_editing(enable);
+}
+
+void GLCanvas3DManager::enable_warning_texture(wxGLCanvas* canvas, bool enable)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->enable_warning_texture(enable);
+}
+
+void GLCanvas3DManager::enable_legend_texture(wxGLCanvas* canvas, bool enable)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->enable_legend_texture(enable);
+}
+
+void GLCanvas3DManager::enable_picking(wxGLCanvas* canvas, bool enable)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->enable_picking(enable);
+}
+
+void GLCanvas3DManager::enable_moving(wxGLCanvas* canvas, bool enable)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->enable_moving(enable);
+}
+
+void GLCanvas3DManager::enable_gizmos(wxGLCanvas* canvas, bool enable)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->enable_gizmos(enable);
+}
+
+void GLCanvas3DManager::enable_shader(wxGLCanvas* canvas, bool enable)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->enable_shader(enable);
+}
+
+void GLCanvas3DManager::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->enable_force_zoom_to_bed(enable);
+}
+
+void GLCanvas3DManager::allow_multisample(wxGLCanvas* canvas, bool allow)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->allow_multisample(allow);
+}
+
+void GLCanvas3DManager::zoom_to_bed(wxGLCanvas* canvas)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->zoom_to_bed();
+}
+
+void GLCanvas3DManager::zoom_to_volumes(wxGLCanvas* canvas)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->zoom_to_volumes();
+}
+
+void GLCanvas3DManager::select_view(wxGLCanvas* canvas, const std::string& direction)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->select_view(direction);
+}
+
+void GLCanvas3DManager::set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ {
+ CanvasesMap::iterator other_it = _get_canvas(other);
+ if (other_it != m_canvases.end())
+ it->second->set_viewport_from_scene(*other_it->second);
+ }
+}
+
+void GLCanvas3DManager::update_volumes_colors_by_extruder(wxGLCanvas* canvas)
+{
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->update_volumes_colors_by_extruder();
+}
+
+void GLCanvas3DManager::render(wxGLCanvas* canvas) const
+{
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->render();
+}
+
+std::vector<double> GLCanvas3DManager::get_current_print_zs(wxGLCanvas* canvas, bool active_only) const
+{
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ return (it != m_canvases.end()) ? it->second->get_current_print_zs(active_only) : std::vector<double>();
+}
+
+void GLCanvas3DManager::set_toolpaths_range(wxGLCanvas* canvas, double low, double high)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->set_toolpaths_range(low, high);
+}
+
+std::vector<int> GLCanvas3DManager::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs)
+{
+ if (model_object == nullptr)
+ return std::vector<int>();
+
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ return (it != m_canvases.end()) ? it->second->load_object(*model_object, obj_idx, instance_idxs) : std::vector<int>();
+}
+
+std::vector<int> GLCanvas3DManager::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx)
+{
+ if (model == nullptr)
+ return std::vector<int>();
+
+ CanvasesMap::const_iterator it = _get_canvas(canvas);
+ return (it != m_canvases.end()) ? it->second->load_object(*model, obj_idx) : std::vector<int>();
+}
+
+void GLCanvas3DManager::reload_scene(wxGLCanvas* canvas, bool force)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->reload_scene(force);
+}
+
+void GLCanvas3DManager::load_print_toolpaths(wxGLCanvas* canvas)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->load_print_toolpaths();
+}
+
+void GLCanvas3DManager::load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& tool_colors)
+{
+ if (print_object == nullptr)
+ return;
+
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->load_print_object_toolpaths(*print_object, tool_colors);
+}
+
+void GLCanvas3DManager::load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->load_wipe_tower_toolpaths(str_tool_colors);
+}
+
+void GLCanvas3DManager::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors)
+{
+ if (preview_data == nullptr)
+ return;
+
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->load_gcode_preview(*preview_data, str_tool_colors);
+}
+
+void GLCanvas3DManager::register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_viewport_changed_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_double_click_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_double_click_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_right_click_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_right_click_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_select_object_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_select_object_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_model_update_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_model_update_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_remove_object_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_remove_object_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_arrange_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_arrange_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_rotate_object_left_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_rotate_object_right_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_scale_object_uniformly_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_increase_objects_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_decrease_objects_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_instance_moved_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_wipe_tower_moved_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_enable_action_buttons_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback)
+{
+ CanvasesMap::iterator it = _get_canvas(canvas);
+ if (it != m_canvases.end())
+ it->second->register_on_gizmo_scale_uniformly_callback(callback);
+}
+
+GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas)
+{
+ return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas);
+}
+
+GLCanvas3DManager::CanvasesMap::const_iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) const
+{
+ return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas);
+}
+
+bool GLCanvas3DManager::_init(GLCanvas3D& canvas)
+{
+ if (!m_gl_initialized)
+ init_gl();
+
+ return canvas.init(m_use_VBOs, m_use_legacy_opengl);
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp
new file mode 100644
index 000000000..6989da791
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp
@@ -0,0 +1,167 @@
+#ifndef slic3r_GLCanvas3DManager_hpp_
+#define slic3r_GLCanvas3DManager_hpp_
+
+#include "../../libslic3r/BoundingBox.hpp"
+
+#include <map>
+#include <vector>
+
+class wxGLCanvas;
+class wxGLContext;
+
+namespace Slic3r {
+
+class DynamicPrintConfig;
+class Print;
+class Model;
+class ExPolygon;
+typedef std::vector<ExPolygon> ExPolygons;
+class ModelObject;
+class PrintObject;
+class GCodePreviewData;
+
+namespace GUI {
+
+class GLCanvas3D;
+
+class GLCanvas3DManager
+{
+ struct GLInfo
+ {
+ std::string version;
+ std::string glsl_version;
+ std::string vendor;
+ std::string renderer;
+
+ GLInfo();
+
+ bool detect();
+ bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const;
+
+ std::string to_string(bool format_as_html, bool extensions) const;
+ };
+
+ typedef std::map<wxGLCanvas*, GLCanvas3D*> CanvasesMap;
+
+ wxGLContext* m_context;
+ CanvasesMap m_canvases;
+ GLInfo m_gl_info;
+ bool m_gl_initialized;
+ bool m_use_legacy_opengl;
+ bool m_use_VBOs;
+
+public:
+ GLCanvas3DManager();
+ ~GLCanvas3DManager();
+
+ bool add(wxGLCanvas* canvas);
+ bool remove(wxGLCanvas* canvas);
+
+ void remove_all();
+
+ unsigned int count() const;
+
+ void init_gl();
+ std::string get_gl_info(bool format_as_html, bool extensions) const;
+
+ bool use_VBOs() const;
+ bool layer_editing_allowed() const;
+
+ bool init(wxGLCanvas* canvas);
+
+ void set_active(wxGLCanvas* canvas, bool active);
+
+ unsigned int get_volumes_count(wxGLCanvas* canvas) const;
+ void reset_volumes(wxGLCanvas* canvas);
+ void deselect_volumes(wxGLCanvas* canvas);
+ void select_volume(wxGLCanvas* canvas, unsigned int id);
+ void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections);
+ bool check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const;
+ bool move_volume_up(wxGLCanvas* canvas, unsigned int id);
+ bool move_volume_down(wxGLCanvas* canvas, unsigned int id);
+
+ void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections);
+
+ void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config);
+ void set_print(wxGLCanvas* canvas, Print* print);
+ void set_model(wxGLCanvas* canvas, Model* model);
+
+ void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape);
+ void set_auto_bed_shape(wxGLCanvas* canvas);
+
+ BoundingBoxf3 get_volumes_bounding_box(wxGLCanvas* canvas);
+
+ void set_axes_length(wxGLCanvas* canvas, float length);
+
+ void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons);
+
+ void set_color_by(wxGLCanvas* canvas, const std::string& value);
+ void set_select_by(wxGLCanvas* canvas, const std::string& value);
+ void set_drag_by(wxGLCanvas* canvas, const std::string& value);
+
+ bool is_layers_editing_enabled(wxGLCanvas* canvas) const;
+ bool is_layers_editing_allowed(wxGLCanvas* canvas) const;
+ bool is_shader_enabled(wxGLCanvas* canvas) const;
+
+ bool is_reload_delayed(wxGLCanvas* canvas) const;
+
+ void enable_layers_editing(wxGLCanvas* canvas, bool enable);
+ void enable_warning_texture(wxGLCanvas* canvas, bool enable);
+ void enable_legend_texture(wxGLCanvas* canvas, bool enable);
+ void enable_picking(wxGLCanvas* canvas, bool enable);
+ void enable_moving(wxGLCanvas* canvas, bool enable);
+ void enable_gizmos(wxGLCanvas* canvas, bool enable);
+ void enable_shader(wxGLCanvas* canvas, bool enable);
+ void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable);
+ void allow_multisample(wxGLCanvas* canvas, bool allow);
+
+ void zoom_to_bed(wxGLCanvas* canvas);
+ void zoom_to_volumes(wxGLCanvas* canvas);
+ void select_view(wxGLCanvas* canvas, const std::string& direction);
+ void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other);
+
+ void update_volumes_colors_by_extruder(wxGLCanvas* canvas);
+
+ void render(wxGLCanvas* canvas) const;
+
+ std::vector<double> get_current_print_zs(wxGLCanvas* canvas, bool active_only) const;
+ void set_toolpaths_range(wxGLCanvas* canvas, double low, double high);
+
+ std::vector<int> load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs);
+ std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx);
+
+ void reload_scene(wxGLCanvas* canvas, bool force);
+
+ void load_print_toolpaths(wxGLCanvas* canvas);
+ void load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& tool_colors);
+ void load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors);
+ void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors);
+
+ void register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_double_click_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_right_click_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_select_object_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_model_update_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_remove_object_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_arrange_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback);
+ void register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback);
+
+private:
+ CanvasesMap::iterator _get_canvas(wxGLCanvas* canvas);
+ CanvasesMap::const_iterator _get_canvas(wxGLCanvas* canvas) const;
+
+ bool _init(GLCanvas3D& canvas);
+};
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLCanvas3DManager_hpp_
diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
new file mode 100644
index 000000000..d3aae33e8
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLGizmo.cpp
@@ -0,0 +1,454 @@
+#include "GLGizmo.hpp"
+
+#include "../../libslic3r/Utils.hpp"
+#include "../../libslic3r/BoundingBox.hpp"
+
+#include <GL/glew.h>
+
+#include <iostream>
+
+namespace Slic3r {
+namespace GUI {
+
+const float GLGizmoBase::Grabber::HalfSize = 2.0f;
+const float GLGizmoBase::Grabber::HoverOffset = 0.5f;
+const float GLGizmoBase::BaseColor[3] = { 1.0f, 1.0f, 1.0f };
+const float GLGizmoBase::HighlightColor[3] = { 1.0f, 0.38f, 0.0f };
+
+GLGizmoBase::Grabber::Grabber()
+ : center(Pointf(0.0, 0.0))
+ , angle_z(0.0f)
+{
+ color[0] = 1.0f;
+ color[1] = 1.0f;
+ color[2] = 1.0f;
+}
+
+void GLGizmoBase::Grabber::render(bool hover) const
+{
+ float min_x = -HalfSize;
+ float max_x = +HalfSize;
+ float min_y = -HalfSize;
+ float max_y = +HalfSize;
+
+ ::glColor3f((GLfloat)color[0], (GLfloat)color[1], (GLfloat)color[2]);
+
+ float angle_z_in_deg = angle_z * 180.0f / (float)PI;
+ ::glPushMatrix();
+ ::glTranslatef((GLfloat)center.x, (GLfloat)center.y, 0.0f);
+ ::glRotatef((GLfloat)angle_z_in_deg, 0.0f, 0.0f, 1.0f);
+
+ ::glDisable(GL_CULL_FACE);
+ ::glBegin(GL_TRIANGLES);
+ ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f);
+ ::glVertex3f((GLfloat)max_x, (GLfloat)min_y, 0.0f);
+ ::glVertex3f((GLfloat)max_x, (GLfloat)max_y, 0.0f);
+ ::glVertex3f((GLfloat)max_x, (GLfloat)max_y, 0.0f);
+ ::glVertex3f((GLfloat)min_x, (GLfloat)max_y, 0.0f);
+ ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f);
+ ::glEnd();
+ ::glEnable(GL_CULL_FACE);
+
+ if (hover)
+ {
+ min_x -= HoverOffset;
+ max_x += HoverOffset;
+ min_y -= HoverOffset;
+ max_y += HoverOffset;
+
+ ::glBegin(GL_LINE_LOOP);
+ ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f);
+ ::glVertex3f((GLfloat)max_x, (GLfloat)min_y, 0.0f);
+ ::glVertex3f((GLfloat)max_x, (GLfloat)max_y, 0.0f);
+ ::glVertex3f((GLfloat)min_x, (GLfloat)max_y, 0.0f);
+ ::glEnd();
+ }
+
+ ::glPopMatrix();
+}
+
+GLGizmoBase::GLGizmoBase()
+ : m_state(Off)
+ , m_hover_id(-1)
+{
+}
+
+GLGizmoBase::~GLGizmoBase()
+{
+}
+
+bool GLGizmoBase::init()
+{
+ return on_init();
+}
+
+GLGizmoBase::EState GLGizmoBase::get_state() const
+{
+ return m_state;
+}
+
+void GLGizmoBase::set_state(GLGizmoBase::EState state)
+{
+ m_state = state;
+}
+
+unsigned int GLGizmoBase::get_textures_id() const
+{
+ return m_textures[m_state].get_id();
+}
+
+int GLGizmoBase::get_textures_size() const
+{
+ return m_textures[Off].get_width();
+}
+
+int GLGizmoBase::get_hover_id() const
+{
+ return m_hover_id;
+}
+
+void GLGizmoBase::set_hover_id(int id)
+{
+ if (id < (int)m_grabbers.size())
+ m_hover_id = id;
+}
+
+void GLGizmoBase::start_dragging()
+{
+ on_start_dragging();
+}
+
+void GLGizmoBase::update(const Pointf& mouse_pos)
+{
+ if (m_hover_id != -1)
+ on_update(mouse_pos);
+}
+
+void GLGizmoBase::render(const BoundingBoxf3& box) const
+{
+ on_render(box);
+}
+
+void GLGizmoBase::render_for_picking(const BoundingBoxf3& box) const
+{
+ on_render_for_picking(box);
+}
+
+void GLGizmoBase::on_start_dragging()
+{
+}
+
+void GLGizmoBase::render_grabbers() const
+{
+ for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i)
+ {
+ m_grabbers[i].render(m_hover_id == i);
+ }
+}
+
+const float GLGizmoRotate::Offset = 5.0f;
+const unsigned int GLGizmoRotate::CircleResolution = 64;
+const unsigned int GLGizmoRotate::AngleResolution = 64;
+const unsigned int GLGizmoRotate::ScaleStepsCount = 60;
+const float GLGizmoRotate::ScaleStepRad = 2.0f * (float)PI / GLGizmoRotate::ScaleStepsCount;
+const unsigned int GLGizmoRotate::ScaleLongEvery = 5;
+const float GLGizmoRotate::ScaleLongTooth = 2.0f;
+const float GLGizmoRotate::ScaleShortTooth = 1.0f;
+const unsigned int GLGizmoRotate::SnapRegionsCount = 8;
+const float GLGizmoRotate::GrabberOffset = 5.0f;
+
+GLGizmoRotate::GLGizmoRotate()
+ : GLGizmoBase()
+ , m_angle_z(0.0f)
+ , m_center(Pointf(0.0, 0.0))
+ , m_radius(0.0f)
+{
+}
+
+bool GLGizmoRotate::on_init()
+{
+ std::string path = resources_dir() + "/icons/overlay/";
+
+ std::string filename = path + "rotate_off.png";
+ if (!m_textures[Off].load_from_file(filename, false))
+ return false;
+
+ filename = path + "rotate_hover.png";
+ if (!m_textures[Hover].load_from_file(filename, false))
+ return false;
+
+ filename = path + "rotate_on.png";
+ if (!m_textures[On].load_from_file(filename, false))
+ return false;
+
+ m_grabbers.push_back(Grabber());
+
+ return true;
+}
+
+void GLGizmoRotate::on_update(const Pointf& mouse_pos)
+{
+ Vectorf orig_dir(1.0, 0.0);
+ Vectorf new_dir = normalize(mouse_pos - m_center);
+ coordf_t theta = ::acos(clamp(-1.0, 1.0, dot(new_dir, orig_dir)));
+ if (cross(orig_dir, new_dir) < 0.0)
+ theta = 2.0 * (coordf_t)PI - theta;
+
+ if (length(m_center.vector_to(mouse_pos)) < 2.0 * (double)m_radius / 3.0)
+ {
+ coordf_t step = 2.0 * (coordf_t)PI / (coordf_t)SnapRegionsCount;
+ theta = step * (coordf_t)std::round(theta / step);
+ }
+
+ if (theta == 2.0 * (coordf_t)PI)
+ theta = 0.0;
+
+ m_angle_z = (float)theta;
+}
+
+void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
+{
+ ::glDisable(GL_LIGHTING);
+ ::glDisable(GL_DEPTH_TEST);
+
+ const Pointf3& size = box.size();
+ m_center = box.center();
+ m_radius = Offset + ::sqrt(sqr(0.5f * size.x) + sqr(0.5f * size.y));
+
+ ::glLineWidth(2.0f);
+ ::glColor3fv(BaseColor);
+
+ _render_circle();
+ _render_scale();
+ _render_snap_radii();
+ _render_reference_radius();
+
+ ::glColor3fv(HighlightColor);
+ _render_angle_z();
+ _render_grabber();
+}
+
+void GLGizmoRotate::on_render_for_picking(const BoundingBoxf3& box) const
+{
+ ::glDisable(GL_LIGHTING);
+ ::glDisable(GL_DEPTH_TEST);
+
+ m_grabbers[0].color[0] = 1.0f;
+ m_grabbers[0].color[1] = 1.0f;
+ m_grabbers[0].color[2] = 254.0f / 255.0f;
+ render_grabbers();
+}
+
+void GLGizmoRotate::_render_circle() const
+{
+ ::glBegin(GL_LINE_LOOP);
+ for (unsigned int i = 0; i < ScaleStepsCount; ++i)
+ {
+ float angle = (float)i * ScaleStepRad;
+ float x = m_center.x + ::cos(angle) * m_radius;
+ float y = m_center.y + ::sin(angle) * m_radius;
+ ::glVertex3f((GLfloat)x, (GLfloat)y, 0.0f);
+ }
+ ::glEnd();
+}
+
+void GLGizmoRotate::_render_scale() const
+{
+ float out_radius_long = m_radius + ScaleLongTooth;
+ float out_radius_short = m_radius + ScaleShortTooth;
+
+ ::glBegin(GL_LINES);
+ for (unsigned int i = 0; i < ScaleStepsCount; ++i)
+ {
+ float angle = (float)i * ScaleStepRad;
+ float cosa = ::cos(angle);
+ float sina = ::sin(angle);
+ float in_x = m_center.x + cosa * m_radius;
+ float in_y = m_center.y + sina * m_radius;
+ float out_x = (i % ScaleLongEvery == 0) ? m_center.x + cosa * out_radius_long : m_center.x + cosa * out_radius_short;
+ float out_y = (i % ScaleLongEvery == 0) ? m_center.y + sina * out_radius_long : m_center.y + sina * out_radius_short;
+ ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, 0.0f);
+ ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, 0.0f);
+ }
+ ::glEnd();
+}
+
+void GLGizmoRotate::_render_snap_radii() const
+{
+ float step = 2.0f * (float)PI / (float)SnapRegionsCount;
+
+ float in_radius = m_radius / 3.0f;
+ float out_radius = 2.0f * in_radius;
+
+ ::glBegin(GL_LINES);
+ for (unsigned int i = 0; i < SnapRegionsCount; ++i)
+ {
+ float angle = (float)i * step;
+ float cosa = ::cos(angle);
+ float sina = ::sin(angle);
+ float in_x = m_center.x + cosa * in_radius;
+ float in_y = m_center.y + sina * in_radius;
+ float out_x = m_center.x + cosa * out_radius;
+ float out_y = m_center.y + sina * out_radius;
+ ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, 0.0f);
+ ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, 0.0f);
+ }
+ ::glEnd();
+}
+
+void GLGizmoRotate::_render_reference_radius() const
+{
+ ::glBegin(GL_LINES);
+ ::glVertex3f((GLfloat)m_center.x, (GLfloat)m_center.y, 0.0f);
+ ::glVertex3f((GLfloat)m_center.x + m_radius + GrabberOffset, (GLfloat)m_center.y, 0.0f);
+ ::glEnd();
+}
+
+void GLGizmoRotate::_render_angle_z() const
+{
+ float step_angle = m_angle_z / AngleResolution;
+ float ex_radius = m_radius + GrabberOffset;
+
+ ::glBegin(GL_LINE_STRIP);
+ for (unsigned int i = 0; i <= AngleResolution; ++i)
+ {
+ float angle = (float)i * step_angle;
+ float x = m_center.x + ::cos(angle) * ex_radius;
+ float y = m_center.y + ::sin(angle) * ex_radius;
+ ::glVertex3f((GLfloat)x, (GLfloat)y, 0.0f);
+ }
+ ::glEnd();
+}
+
+void GLGizmoRotate::_render_grabber() const
+{
+ float grabber_radius = m_radius + GrabberOffset;
+ m_grabbers[0].center.x = m_center.x + ::cos(m_angle_z) * grabber_radius;
+ m_grabbers[0].center.y = m_center.y + ::sin(m_angle_z) * grabber_radius;
+ m_grabbers[0].angle_z = m_angle_z;
+
+ ::glColor3fv(BaseColor);
+ ::glBegin(GL_LINES);
+ ::glVertex3f((GLfloat)m_center.x, (GLfloat)m_center.y, 0.0f);
+ ::glVertex3f((GLfloat)m_grabbers[0].center.x, (GLfloat)m_grabbers[0].center.y, 0.0f);
+ ::glEnd();
+
+ ::memcpy((void*)m_grabbers[0].color, (const void*)HighlightColor, 3 * sizeof(float));
+ render_grabbers();
+}
+
+const float GLGizmoScale::Offset = 5.0f;
+
+GLGizmoScale::GLGizmoScale()
+ : GLGizmoBase()
+ , m_scale(1.0f)
+ , m_starting_scale(1.0f)
+{
+}
+
+float GLGizmoScale::get_scale() const
+{
+ return m_scale;
+}
+
+void GLGizmoScale::set_scale(float scale)
+{
+ m_starting_scale = scale;
+}
+
+bool GLGizmoScale::on_init()
+{
+ std::string path = resources_dir() + "/icons/overlay/";
+
+ std::string filename = path + "scale_off.png";
+ if (!m_textures[Off].load_from_file(filename, false))
+ return false;
+
+ filename = path + "scale_hover.png";
+ if (!m_textures[Hover].load_from_file(filename, false))
+ return false;
+
+ filename = path + "scale_on.png";
+ if (!m_textures[On].load_from_file(filename, false))
+ return false;
+
+ for (unsigned int i = 0; i < 4; ++i)
+ {
+ m_grabbers.push_back(Grabber());
+ }
+
+ return true;
+}
+
+void GLGizmoScale::on_start_dragging()
+{
+ if (m_hover_id != -1)
+ m_starting_drag_position = m_grabbers[m_hover_id].center;
+}
+
+void GLGizmoScale::on_update(const Pointf& mouse_pos)
+{
+ Pointf center(0.5 * (m_grabbers[1].center.x + m_grabbers[0].center.x), 0.5 * (m_grabbers[3].center.y + m_grabbers[0].center.y));
+
+ coordf_t orig_len = length(m_starting_drag_position - center);
+ coordf_t new_len = length(mouse_pos - center);
+ coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0;
+
+ m_scale = m_starting_scale * (float)ratio;
+}
+
+void GLGizmoScale::on_render(const BoundingBoxf3& box) const
+{
+ ::glDisable(GL_LIGHTING);
+ ::glDisable(GL_DEPTH_TEST);
+
+ coordf_t min_x = box.min.x - (coordf_t)Offset;
+ coordf_t max_x = box.max.x + (coordf_t)Offset;
+ coordf_t min_y = box.min.y - (coordf_t)Offset;
+ coordf_t max_y = box.max.y + (coordf_t)Offset;
+
+ m_grabbers[0].center.x = min_x;
+ m_grabbers[0].center.y = min_y;
+ m_grabbers[1].center.x = max_x;
+ m_grabbers[1].center.y = min_y;
+ m_grabbers[2].center.x = max_x;
+ m_grabbers[2].center.y = max_y;
+ m_grabbers[3].center.x = min_x;
+ m_grabbers[3].center.y = max_y;
+
+ ::glLineWidth(2.0f);
+ ::glColor3fv(BaseColor);
+ // draw outline
+ ::glBegin(GL_LINE_LOOP);
+ for (unsigned int i = 0; i < 4; ++i)
+ {
+ ::glVertex3f((GLfloat)m_grabbers[i].center.x, (GLfloat)m_grabbers[i].center.y, 0.0f);
+ }
+ ::glEnd();
+
+ // draw grabbers
+ for (unsigned int i = 0; i < 4; ++i)
+ {
+ ::memcpy((void*)m_grabbers[i].color, (const void*)HighlightColor, 3 * sizeof(float));
+ }
+ render_grabbers();
+}
+
+void GLGizmoScale::on_render_for_picking(const BoundingBoxf3& box) const
+{
+ static const GLfloat INV_255 = 1.0f / 255.0f;
+
+ ::glDisable(GL_LIGHTING);
+ ::glDisable(GL_DEPTH_TEST);
+
+ for (unsigned int i = 0; i < 4; ++i)
+ {
+ m_grabbers[i].color[0] = 1.0f;
+ m_grabbers[i].color[1] = 1.0f;
+ m_grabbers[i].color[2] = (254.0f - (float)i) * INV_255;
+ }
+ render_grabbers();
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
new file mode 100644
index 000000000..2baec8f9b
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLGizmo.hpp
@@ -0,0 +1,145 @@
+#ifndef slic3r_GLGizmo_hpp_
+#define slic3r_GLGizmo_hpp_
+
+#include "../../slic3r/GUI/GLTexture.hpp"
+#include "../../libslic3r/Point.hpp"
+
+#include <vector>
+
+namespace Slic3r {
+
+class BoundingBoxf3;
+class Pointf3;
+
+namespace GUI {
+
+class GLGizmoBase
+{
+protected:
+ static const float BaseColor[3];
+ static const float HighlightColor[3];
+
+ struct Grabber
+ {
+ static const float HalfSize;
+ static const float HoverOffset;
+
+ Pointf center;
+ float angle_z;
+ float color[3];
+
+ Grabber();
+ void render(bool hover) const;
+ };
+
+public:
+ enum EState
+ {
+ Off,
+ Hover,
+ On,
+ Num_States
+ };
+
+protected:
+ EState m_state;
+ // textures are assumed to be square and all with the same size in pixels, no internal check is done
+ GLTexture m_textures[Num_States];
+ int m_hover_id;
+ mutable std::vector<Grabber> m_grabbers;
+
+public:
+ GLGizmoBase();
+ virtual ~GLGizmoBase();
+
+ bool init();
+
+ EState get_state() const;
+ void set_state(EState state);
+
+ unsigned int get_textures_id() const;
+ int get_textures_size() const;
+
+ int get_hover_id() const;
+ void set_hover_id(int id);
+
+ void start_dragging();
+ void update(const Pointf& mouse_pos);
+
+ void render(const BoundingBoxf3& box) const;
+ void render_for_picking(const BoundingBoxf3& box) const;
+
+protected:
+ virtual bool on_init() = 0;
+ virtual void on_start_dragging();
+ virtual void on_update(const Pointf& mouse_pos) = 0;
+ virtual void on_render(const BoundingBoxf3& box) const = 0;
+ virtual void on_render_for_picking(const BoundingBoxf3& box) const = 0;
+
+ void render_grabbers() const;
+};
+
+class GLGizmoRotate : public GLGizmoBase
+{
+ static const float Offset;
+ static const unsigned int CircleResolution;
+ static const unsigned int AngleResolution;
+ static const unsigned int ScaleStepsCount;
+ static const float ScaleStepRad;
+ static const unsigned int ScaleLongEvery;
+ static const float ScaleLongTooth;
+ static const float ScaleShortTooth;
+ static const unsigned int SnapRegionsCount;
+ static const float GrabberOffset;
+
+ float m_angle_z;
+
+ mutable Pointf m_center;
+ mutable float m_radius;
+
+public:
+ GLGizmoRotate();
+
+protected:
+ virtual bool on_init();
+ virtual void on_update(const Pointf& mouse_pos);
+ virtual void on_render(const BoundingBoxf3& box) const;
+ virtual void on_render_for_picking(const BoundingBoxf3& box) const;
+
+private:
+ void _render_circle() const;
+ void _render_scale() const;
+ void _render_snap_radii() const;
+ void _render_reference_radius() const;
+ void _render_angle_z() const;
+ void _render_grabber() const;
+};
+
+class GLGizmoScale : public GLGizmoBase
+{
+ static const float Offset;
+
+ float m_scale;
+
+ Pointf m_starting_drag_position;
+ float m_starting_scale;
+
+public:
+ GLGizmoScale();
+
+ float get_scale() const;
+ void set_scale(float scale);
+
+protected:
+ virtual bool on_init();
+ virtual void on_start_dragging();
+ virtual void on_update(const Pointf& mouse_pos);
+ virtual void on_render(const BoundingBoxf3& box) const;
+ virtual void on_render_for_picking(const BoundingBoxf3& box) const;
+};
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLGizmo_hpp_
+
diff --git a/xs/src/slic3r/GUI/GLShader.cpp b/xs/src/slic3r/GUI/GLShader.cpp
index ce9a80f05..903f6c347 100644
--- a/xs/src/slic3r/GUI/GLShader.cpp
+++ b/xs/src/slic3r/GUI/GLShader.cpp
@@ -2,6 +2,9 @@
#include "GLShader.hpp"
+#include "../../libslic3r/Utils.hpp"
+#include <boost/nowide/fstream.hpp>
+
#include <string>
#include <utility>
#include <assert.h>
@@ -22,7 +25,7 @@ inline std::string gl_get_string_safe(GLenum param)
return std::string(value ? value : "N/A");
}
-bool GLShader::load(const char *fragment_shader, const char *vertex_shader)
+bool GLShader::load_from_text(const char *fragment_shader, const char *vertex_shader)
{
std::string gl_version = gl_get_string_safe(GL_VERSION);
int major = atoi(gl_version.c_str());
@@ -123,6 +126,41 @@ bool GLShader::load(const char *fragment_shader, const char *vertex_shader)
return true;
}
+bool GLShader::load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename)
+{
+ const std::string& path = resources_dir() + "/shaders/";
+
+ boost::nowide::ifstream vs(path + std::string(vertex_shader_filename), boost::nowide::ifstream::binary);
+ if (!vs.good())
+ return false;
+
+ vs.seekg(0, vs.end);
+ int file_length = vs.tellg();
+ vs.seekg(0, vs.beg);
+ std::string vertex_shader(file_length, '\0');
+ vs.read(const_cast<char*>(vertex_shader.data()), file_length);
+ if (!vs.good())
+ return false;
+
+ vs.close();
+
+ boost::nowide::ifstream fs(path + std::string(fragment_shader_filename), boost::nowide::ifstream::binary);
+ if (!fs.good())
+ return false;
+
+ fs.seekg(0, fs.end);
+ file_length = fs.tellg();
+ fs.seekg(0, fs.beg);
+ std::string fragment_shader(file_length, '\0');
+ fs.read(const_cast<char*>(fragment_shader.data()), file_length);
+ if (!fs.good())
+ return false;
+
+ fs.close();
+
+ return load_from_text(fragment_shader.c_str(), vertex_shader.c_str());
+}
+
void GLShader::release()
{
if (this->shader_program_id) {
diff --git a/xs/src/slic3r/GUI/GLShader.hpp b/xs/src/slic3r/GUI/GLShader.hpp
index d91463f19..032640d8d 100644
--- a/xs/src/slic3r/GUI/GLShader.hpp
+++ b/xs/src/slic3r/GUI/GLShader.hpp
@@ -16,7 +16,9 @@ public:
{}
~GLShader();
- bool load(const char *fragment_shader, const char *vertex_shader);
+ bool load_from_text(const char *fragment_shader, const char *vertex_shader);
+ bool load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename);
+
void release();
int get_attrib_location(const char *name) const;
diff --git a/xs/src/slic3r/GUI/GLTexture.cpp b/xs/src/slic3r/GUI/GLTexture.cpp
new file mode 100644
index 000000000..593362e54
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLTexture.cpp
@@ -0,0 +1,185 @@
+#include "GLTexture.hpp"
+
+#include <GL/glew.h>
+
+#include <wx/image.h>
+
+#include <vector>
+#include <algorithm>
+
+namespace Slic3r {
+namespace GUI {
+
+GLTexture::GLTexture()
+ : m_id(0)
+ , m_width(0)
+ , m_height(0)
+ , m_source("")
+{
+}
+
+GLTexture::~GLTexture()
+{
+ reset();
+}
+
+bool GLTexture::load_from_file(const std::string& filename, bool generate_mipmaps)
+{
+ reset();
+
+ // Load a PNG with an alpha channel.
+ wxImage image;
+ if (!image.LoadFile(filename, wxBITMAP_TYPE_PNG))
+ {
+ reset();
+ return false;
+ }
+
+ m_width = image.GetWidth();
+ m_height = image.GetHeight();
+ int n_pixels = m_width * m_height;
+
+ if (n_pixels <= 0)
+ {
+ reset();
+ return false;
+ }
+
+ // Get RGB & alpha raw data from wxImage, pack them into an array.
+ unsigned char* img_rgb = image.GetData();
+ if (img_rgb == nullptr)
+ {
+ reset();
+ return false;
+ }
+
+ unsigned char* img_alpha = image.GetAlpha();
+
+ std::vector<unsigned char> data(n_pixels * 4, 0);
+ for (int i = 0; i < n_pixels; ++i)
+ {
+ int data_id = i * 4;
+ int img_id = i * 3;
+ data[data_id + 0] = img_rgb[img_id + 0];
+ data[data_id + 1] = img_rgb[img_id + 1];
+ data[data_id + 2] = img_rgb[img_id + 2];
+ data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
+ }
+
+ // sends data to gpu
+ ::glGenTextures(1, &m_id);
+ ::glBindTexture(GL_TEXTURE_2D, m_id);
+ ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data());
+ if (generate_mipmaps)
+ {
+ // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
+ _generate_mipmaps(image);
+ ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ }
+ else
+ {
+ ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
+ }
+ ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ ::glBindTexture(GL_TEXTURE_2D, 0);
+
+ m_source = filename;
+ return true;
+}
+
+void GLTexture::reset()
+{
+ if (m_id != 0)
+ ::glDeleteTextures(1, &m_id);
+
+ m_id = 0;
+ m_width = 0;
+ m_height = 0;
+ m_source = "";
+}
+
+unsigned int GLTexture::get_id() const
+{
+ return m_id;
+}
+
+int GLTexture::get_width() const
+{
+ return m_width;
+}
+
+int GLTexture::get_height() const
+{
+ return m_height;
+}
+
+const std::string& GLTexture::get_source() const
+{
+ return m_source;
+}
+
+void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top)
+{
+ ::glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+
+ ::glDisable(GL_LIGHTING);
+ ::glEnable(GL_BLEND);
+ ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ ::glEnable(GL_TEXTURE_2D);
+
+ ::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id);
+
+ ::glBegin(GL_QUADS);
+ ::glTexCoord2d(0.0f, 1.0f); ::glVertex3f(left, bottom, 0.0f);
+ ::glTexCoord2d(1.0f, 1.0f); ::glVertex3f(right, bottom, 0.0f);
+ ::glTexCoord2d(1.0f, 0.0f); ::glVertex3f(right, top, 0.0f);
+ ::glTexCoord2d(0.0f, 0.0f); ::glVertex3f(left, top, 0.0f);
+ ::glEnd();
+
+ ::glBindTexture(GL_TEXTURE_2D, 0);
+
+ ::glDisable(GL_TEXTURE_2D);
+ ::glDisable(GL_BLEND);
+ ::glEnable(GL_LIGHTING);
+}
+
+void GLTexture::_generate_mipmaps(wxImage& image)
+{
+ int w = image.GetWidth();
+ int h = image.GetHeight();
+ GLint level = 0;
+ std::vector<unsigned char> data(w * h * 4, 0);
+
+ while ((w > 1) && (h > 1))
+ {
+ ++level;
+
+ w = std::max(w / 2, 1);
+ h = std::max(h / 2, 1);
+
+ int n_pixels = w * h;
+
+ image = image.ResampleBicubic(w, h);
+
+ unsigned char* img_rgb = image.GetData();
+ unsigned char* img_alpha = image.GetAlpha();
+
+ data.resize(n_pixels * 4);
+ for (int i = 0; i < n_pixels; ++i)
+ {
+ int data_id = i * 4;
+ int img_id = i * 3;
+ data[data_id + 0] = img_rgb[img_id + 0];
+ data[data_id + 1] = img_rgb[img_id + 1];
+ data[data_id + 2] = img_rgb[img_id + 2];
+ data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
+ }
+
+ ::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data());
+ }
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/xs/src/slic3r/GUI/GLTexture.hpp b/xs/src/slic3r/GUI/GLTexture.hpp
new file mode 100644
index 000000000..70480c605
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLTexture.hpp
@@ -0,0 +1,41 @@
+#ifndef slic3r_GLTexture_hpp_
+#define slic3r_GLTexture_hpp_
+
+#include <string>
+
+class wxImage;
+
+namespace Slic3r {
+namespace GUI {
+
+ class GLTexture
+ {
+ private:
+ unsigned int m_id;
+ int m_width;
+ int m_height;
+ std::string m_source;
+
+ public:
+ GLTexture();
+ ~GLTexture();
+
+ bool load_from_file(const std::string& filename, bool generate_mipmaps);
+ void reset();
+
+ unsigned int get_id() const;
+ int get_width() const;
+ int get_height() const;
+ const std::string& get_source() const;
+
+ static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top);
+
+ private:
+ void _generate_mipmaps(wxImage& image);
+ };
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLTexture_hpp_
+
diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp
index 6a907291e..29f35293b 100644
--- a/xs/xsp/GUI_3DScene.xsp
+++ b/xs/xsp/GUI_3DScene.xsp
@@ -8,7 +8,8 @@
GLShader();
~GLShader();
- bool load(const char *fragment_shader, const char *vertex_shader);
+ bool load_from_text(const char *fragment_shader, const char *vertex_shader);
+ bool load_from_file(const char *fragment_shader, const char *vertex_shader);
void release();
int get_attrib_location(const char *name) const;
@@ -92,9 +93,6 @@
int count()
%code{% RETVAL = THIS->volumes.size(); %};
- std::vector<double> get_current_print_zs(bool active_only)
- %code{% RETVAL = THIS->get_current_print_zs(active_only); %};
-
void set_range(double low, double high);
void render_VBOs() const;
@@ -155,11 +153,457 @@ GLVolumeCollection::arrayref()
%package{Slic3r::GUI::_3DScene};
%{
+std::string
+get_gl_info(format_as_html, extensions)
+ bool format_as_html;
+ bool extensions;
+ CODE:
+ RETVAL = _3DScene::get_gl_info(format_as_html, extensions);
+ OUTPUT:
+ RETVAL
+
+bool
+use_VBOs()
+ CODE:
+ RETVAL = _3DScene::use_VBOs();
+ OUTPUT:
+ RETVAL
+
+bool
+add_canvas(canvas)
+ SV *canvas;
+ CODE:
+ RETVAL = _3DScene::add_canvas((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+ OUTPUT:
+ RETVAL
+
+bool
+remove_canvas(canvas)
+ SV *canvas;
+ CODE:
+ RETVAL = _3DScene::remove_canvas((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+ OUTPUT:
+ RETVAL
void
-_glew_init()
+remove_all_canvases()
CODE:
- _3DScene::_glew_init();
+ _3DScene::remove_all_canvases();
+
+void
+set_active(canvas, active)
+ SV *canvas;
+ bool active;
+ CODE:
+ _3DScene::set_active((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), active);
+
+unsigned int
+get_volumes_count(canvas)
+ SV *canvas;
+ CODE:
+ RETVAL = _3DScene::get_volumes_count((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+ OUTPUT:
+ RETVAL
+
+void
+reset_volumes(canvas)
+ SV *canvas;
+ CODE:
+ _3DScene::reset_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+
+void
+deselect_volumes(canvas)
+ SV *canvas;
+ CODE:
+ _3DScene::deselect_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+
+void
+select_volume(canvas, id)
+ SV *canvas;
+ unsigned int id;
+ CODE:
+ _3DScene::select_volume((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id);
+
+void
+update_volumes_selection(canvas, selections)
+ SV *canvas;
+ std::vector<int> selections;
+ CODE:
+ _3DScene::update_volumes_selection((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), selections);
+
+bool
+check_volumes_outside_state(canvas, config)
+ SV *canvas;
+ DynamicPrintConfig *config;
+ CODE:
+ RETVAL = _3DScene::check_volumes_outside_state((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), config);
+ OUTPUT:
+ RETVAL
+
+bool
+move_volume_up(canvas, id)
+ SV *canvas;
+ unsigned int id;
+ CODE:
+ RETVAL = _3DScene::move_volume_up((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id);
+ OUTPUT:
+ RETVAL
+
+bool
+move_volume_down(canvas, id)
+ SV *canvas;
+ unsigned int id;
+ CODE:
+ RETVAL = _3DScene::move_volume_down((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id);
+ OUTPUT:
+ RETVAL
+
+void
+set_objects_selections(canvas, selections)
+ SV *canvas;
+ std::vector<int> selections;
+ CODE:
+ _3DScene::set_objects_selections((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), selections);
+
+void
+set_config(canvas, config)
+ SV *canvas;
+ DynamicPrintConfig *config;
+ CODE:
+ _3DScene::set_config((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), config);
+
+void
+set_print(canvas, print)
+ SV *canvas;
+ Print *print;
+ CODE:
+ _3DScene::set_print((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), print);
+
+void
+set_model(canvas, model)
+ SV *canvas;
+ Model *model;
+ CODE:
+ _3DScene::set_model((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model);
+
+void
+set_bed_shape(canvas, shape)
+ SV *canvas;
+ Pointfs shape;
+ CODE:
+ _3DScene::set_bed_shape((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), shape);
+
+void
+set_auto_bed_shape(canvas)
+ SV *canvas;
+ CODE:
+ _3DScene::set_auto_bed_shape((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+
+Clone<BoundingBoxf3>
+get_volumes_bounding_box(canvas)
+ SV *canvas;
+ CODE:
+ RETVAL = _3DScene::get_volumes_bounding_box((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+ OUTPUT:
+ RETVAL
+
+void
+set_axes_length(canvas, length)
+ SV *canvas;
+ float length;
+ CODE:
+ _3DScene::set_axes_length((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), length);
+
+void
+set_cutting_plane(canvas, z, polygons)
+ SV *canvas;
+ float z;
+ ExPolygons polygons;
+ CODE:
+ _3DScene::set_cutting_plane((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), z, polygons);
+
+void
+set_color_by(canvas, value)
+ SV *canvas;
+ std::string value;
+ CODE:
+ _3DScene::set_color_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value);
+
+void
+set_select_by(canvas, value)
+ SV *canvas;
+ std::string value;
+ CODE:
+ _3DScene::set_select_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value);
+
+void
+set_drag_by(canvas, value)
+ SV *canvas;
+ std::string value;
+ CODE:
+ _3DScene::set_drag_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value);
+
+bool
+is_layers_editing_enabled(canvas)
+ SV *canvas;
+ CODE:
+ RETVAL = _3DScene::is_layers_editing_enabled((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+ OUTPUT:
+ RETVAL
+
+bool
+is_layers_editing_allowed(canvas)
+ SV *canvas;
+ CODE:
+ RETVAL = _3DScene::is_layers_editing_allowed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+ OUTPUT:
+ RETVAL
+
+bool
+is_shader_enabled(canvas)
+ SV *canvas;
+ CODE:
+ RETVAL = _3DScene::is_shader_enabled((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+ OUTPUT:
+ RETVAL
+
+bool
+is_reload_delayed(canvas)
+ SV *canvas;
+ CODE:
+ RETVAL = _3DScene::is_reload_delayed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+ OUTPUT:
+ RETVAL
+
+void
+enable_layers_editing(canvas, enable)
+ SV *canvas;
+ bool enable;
+ CODE:
+ _3DScene::enable_layers_editing((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+
+void
+enable_warning_texture(canvas, enable)
+ SV *canvas;
+ bool enable;
+ CODE:
+ _3DScene::enable_warning_texture((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+
+void
+enable_legend_texture(canvas, enable)
+ SV *canvas;
+ bool enable;
+ CODE:
+ _3DScene::enable_legend_texture((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+
+void
+enable_picking(canvas, enable)
+ SV *canvas;
+ bool enable;
+ CODE:
+ _3DScene::enable_picking((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+
+void
+enable_moving(canvas, enable)
+ SV *canvas;
+ bool enable;
+ CODE:
+ _3DScene::enable_moving((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+
+void
+enable_gizmos(canvas, enable)
+ SV *canvas;
+ bool enable;
+ CODE:
+ _3DScene::enable_gizmos((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+
+void
+enable_shader(canvas, enable)
+ SV *canvas;
+ bool enable;
+ CODE:
+ _3DScene::enable_shader((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+
+void
+enable_force_zoom_to_bed(canvas, enable)
+ SV *canvas;
+ bool enable;
+ CODE:
+ _3DScene::enable_force_zoom_to_bed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+
+void
+allow_multisample(canvas, allow)
+ SV *canvas;
+ bool allow;
+ CODE:
+ _3DScene::allow_multisample((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), allow);
+
+void
+zoom_to_bed(canvas)
+ SV *canvas;
+ CODE:
+ _3DScene::zoom_to_bed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+
+void
+zoom_to_volumes(canvas)
+ SV *canvas;
+ CODE:
+ _3DScene::zoom_to_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+
+void
+select_view(canvas, direction)
+ SV *canvas;
+ std::string direction;
+ CODE:
+ _3DScene::select_view((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), direction);
+
+void
+set_viewport_from_scene(canvas, other)
+ SV *canvas;
+ SV *other;
+ CODE:
+ _3DScene::set_viewport_from_scene((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (wxGLCanvas*)wxPli_sv_2_object(aTHX_ other, "Wx::GLCanvas"));
+
+void
+update_volumes_colors_by_extruder(canvas)
+ SV *canvas;
+ CODE:
+ _3DScene::update_volumes_colors_by_extruder((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+
+void
+render(canvas)
+ SV *canvas;
+ CODE:
+ _3DScene::render((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+
+std::vector<double>
+get_current_print_zs(canvas, active_only)
+ SV *canvas;
+ bool active_only;
+ CODE:
+ RETVAL = _3DScene::get_current_print_zs((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), active_only);
+ OUTPUT:
+ RETVAL
+
+void
+set_toolpaths_range(canvas, low, high)
+ SV *canvas;
+ double low;
+ double high;
+ CODE:
+ _3DScene::set_toolpaths_range((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), low, high);
+
+void
+register_on_viewport_changed_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_viewport_changed_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_double_click_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_double_click_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_right_click_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_right_click_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_select_object_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_select_object_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_model_update_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_model_update_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_remove_object_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_remove_object_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_arrange_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_arrange_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_rotate_object_left_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_rotate_object_left_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_rotate_object_right_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_rotate_object_right_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_scale_object_uniformly_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_scale_object_uniformly_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_increase_objects_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_increase_objects_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_decrease_objects_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_decrease_objects_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_instance_moved_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_instance_moved_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_wipe_tower_moved_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_wipe_tower_moved_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_enable_action_buttons_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_enable_action_buttons_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_gizmo_scale_uniformly_callback(canvas, callback)
+ SV *canvas;
+ SV *callback;
+ CODE:
+ _3DScene::register_on_gizmo_scale_uniformly_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
unsigned int
finalize_legend_texture()
@@ -218,41 +662,61 @@ reset_warning_texture()
CODE:
_3DScene::reset_warning_texture();
+std::vector<int>
+load_model_object(canvas, model_object, obj_idx, instance_idxs)
+ SV *canvas;
+ ModelObject *model_object;
+ int obj_idx;
+ std::vector<int> instance_idxs;
+ CODE:
+ RETVAL = _3DScene::load_object((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model_object, obj_idx, instance_idxs);
+ OUTPUT:
+ RETVAL
+
+std::vector<int>
+load_model(canvas, model, obj_idx)
+ SV *canvas;
+ Model *model;
+ int obj_idx;
+ CODE:
+ RETVAL = _3DScene::load_object((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model, obj_idx);
+ OUTPUT:
+ RETVAL
+
void
-_load_print_toolpaths(print, volumes, tool_colors, use_VBOs)
- Print *print;
- GLVolumeCollection *volumes;
- std::vector<std::string> tool_colors;
- int use_VBOs;
+reload_scene(canvas, force)
+ SV *canvas;
+ bool force;
+ CODE:
+ _3DScene::reload_scene((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), force);
+
+void
+load_print_toolpaths(canvas)
+ SV *canvas;
CODE:
- _3DScene::_load_print_toolpaths(print, volumes, tool_colors, use_VBOs != 0);
+ _3DScene::load_print_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
void
-_load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs)
- PrintObject *print_object;
- GLVolumeCollection *volumes;
+load_print_object_toolpaths(canvas, print_object, tool_colors)
+ SV *canvas;
+ PrintObject *print_object;
std::vector<std::string> tool_colors;
- int use_VBOs;
CODE:
- _3DScene::_load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs != 0);
+ _3DScene::load_print_object_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), print_object, tool_colors);
void
-_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs)
- Print *print;
- GLVolumeCollection *volumes;
+load_wipe_tower_toolpaths(canvas, tool_colors)
+ SV *canvas;
std::vector<std::string> tool_colors;
- int use_VBOs;
CODE:
- _3DScene::_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs != 0);
+ _3DScene::load_wipe_tower_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), tool_colors);
void
-load_gcode_preview(print, preview_data, volumes, str_tool_colors, use_VBOs)
- Print *print;
+load_gcode_preview(canvas, preview_data, str_tool_colors)
+ SV *canvas;
GCodePreviewData *preview_data;
- GLVolumeCollection *volumes;
std::vector<std::string> str_tool_colors;
- int use_VBOs;
CODE:
- _3DScene::load_gcode_preview(print, preview_data, volumes, str_tool_colors, use_VBOs != 0);
+ _3DScene::load_gcode_preview((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), preview_data, str_tool_colors);
%}
diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp
index ef9c5345f..e05112932 100644
--- a/xs/xsp/Print.xsp
+++ b/xs/xsp/Print.xsp
@@ -52,6 +52,9 @@ _constant()
int region_count()
%code%{ RETVAL = THIS->print()->regions.size(); %};
+ int region_volumes_count()
+ %code%{ RETVAL = THIS->region_volumes.size(); %};
+
Ref<Print> print();
Ref<ModelObject> model_object();
Ref<StaticPrintConfig> config()
@@ -119,15 +122,6 @@ _constant()
RETVAL.push_back(slicing_params.layer_height);
%};
- void adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action)
- %code%{
- THIS->update_layer_height_profile(THIS->model_object()->layer_height_profile);
- adjust_layer_height_profile(
- THIS->slicing_parameters(), THIS->model_object()->layer_height_profile, z, layer_thickness_delta, band_width, LayerHeightEditActionType(action));
- THIS->model_object()->layer_height_profile_valid = true;
- THIS->layer_height_profile_valid = false;
- %};
-
void reset_layer_height_profile();
int ptr()