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(); - } +#============================================================================================================================== +# 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); - }); +#============================================================================================================================== + 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'; +#=================================================================================================================================== +#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 -)); +#=================================================================================================================================== +#__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 Binary files /dev/null and b/resources/icons/bed/mk2_bottom.png differ diff --git a/resources/icons/bed/mk2_top.png b/resources/icons/bed/mk2_top.png new file mode 100644 index 000000000..142050c3a Binary files /dev/null and b/resources/icons/bed/mk2_top.png differ diff --git a/resources/icons/bed/mk3_bottom.png b/resources/icons/bed/mk3_bottom.png new file mode 100644 index 000000000..072c14dae Binary files /dev/null and b/resources/icons/bed/mk3_bottom.png differ diff --git a/resources/icons/bed/mk3_top.png b/resources/icons/bed/mk3_top.png new file mode 100644 index 000000000..0403c2f05 Binary files /dev/null and b/resources/icons/bed/mk3_top.png differ diff --git a/resources/icons/overlay/rotate_hover.png b/resources/icons/overlay/rotate_hover.png new file mode 100644 index 000000000..56d4fd277 Binary files /dev/null and b/resources/icons/overlay/rotate_hover.png differ diff --git a/resources/icons/overlay/rotate_off.png b/resources/icons/overlay/rotate_off.png new file mode 100644 index 000000000..8491f7e43 Binary files /dev/null and b/resources/icons/overlay/rotate_off.png differ diff --git a/resources/icons/overlay/rotate_on.png b/resources/icons/overlay/rotate_on.png new file mode 100644 index 000000000..e2db5120c Binary files /dev/null and b/resources/icons/overlay/rotate_on.png differ diff --git a/resources/icons/overlay/scale_hover.png b/resources/icons/overlay/scale_hover.png new file mode 100644 index 000000000..d620aee7a Binary files /dev/null and b/resources/icons/overlay/scale_hover.png differ diff --git a/resources/icons/overlay/scale_off.png b/resources/icons/overlay/scale_off.png new file mode 100644 index 000000000..1ae999bbe Binary files /dev/null and b/resources/icons/overlay/scale_off.png differ diff --git a/resources/icons/overlay/scale_on.png b/resources/icons/overlay/scale_on.png new file mode 100644 index 000000000..62e805f12 Binary files /dev/null and b/resources/icons/overlay/scale_on.png 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::center() const } template Pointf3 BoundingBox3Base::center() const; +template coordf_t +BoundingBox3Base::max_size() const +{ + PointClass s = size(); + return std::max(s.x, std::max(s.y, s.z)); +} +template coordf_t BoundingBox3Base::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::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 #include @@ -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 &ints); + void call() const; + void call(int i) const; + void call(int i, int j) const; + void call(const std::vector& 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 &ints) +void PerlCallback::call(const std::vector& ints) const { if (! m_callback) return; @@ -238,16 +237,51 @@ void PerlCallback::call(const std::vector &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 &widths, const std::vector &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& widths, const std::vector& 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 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 ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, const Point ©, 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 ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, float print_z, const Point ©, GLVolume &volume) { Lines lines; std::vector 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 ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_multi_path, float print_z, const Point ©, GLVolume &volume) { Lines lines; std::vector 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 ©, GLVolume &volume); - -static inline void extrusionentity_to_verts(const ExtrusionEntityCollection &extrusion_entity_collection, float print_z, const Point ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionEntityCollection &extrusion_entity_collection, float print_z, const Point ©, 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 ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point ©, GLVolume &volume) { if (extrusion_entity != nullptr) { auto *extrusion_path = dynamic_cast(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 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,9 +1718,329 @@ 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(); +} + +std::string _3DScene::get_gl_info(bool format_as_html, bool extensions) +{ + return s_canvas_mgr.get_gl_info(format_as_html, extensions); +} + +bool _3DScene::use_VBOs() +{ + return s_canvas_mgr.use_VBOs(); +} + +bool _3DScene::add_canvas(wxGLCanvas* canvas) +{ + return s_canvas_mgr.add(canvas); +} + +bool _3DScene::remove_canvas(wxGLCanvas* canvas) +{ + return s_canvas_mgr.remove(canvas); +} + +void _3DScene::remove_all_canvases() +{ + s_canvas_mgr.remove_all(); +} + +bool _3DScene::init(wxGLCanvas* canvas) +{ + return s_canvas_mgr.init(canvas); +} + +void _3DScene::set_active(wxGLCanvas* canvas, bool active) +{ + s_canvas_mgr.set_active(canvas, active); +} + +unsigned int _3DScene::get_volumes_count(wxGLCanvas* canvas) +{ + return s_canvas_mgr.get_volumes_count(canvas); +} + +void _3DScene::reset_volumes(wxGLCanvas* canvas) +{ + s_canvas_mgr.reset_volumes(canvas); +} + +void _3DScene::deselect_volumes(wxGLCanvas* canvas) +{ + s_canvas_mgr.deselect_volumes(canvas); +} + +void _3DScene::select_volume(wxGLCanvas* canvas, unsigned int id) +{ + s_canvas_mgr.select_volume(canvas, id); +} + +void _3DScene::update_volumes_selection(wxGLCanvas* canvas, const std::vector& selections) +{ + s_canvas_mgr.update_volumes_selection(canvas, selections); +} + +bool _3DScene::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) +{ + return s_canvas_mgr.check_volumes_outside_state(canvas, config); +} + +bool _3DScene::move_volume_up(wxGLCanvas* canvas, unsigned int id) +{ + return s_canvas_mgr.move_volume_up(canvas, id); +} + +bool _3DScene::move_volume_down(wxGLCanvas* canvas, unsigned int id) +{ + return s_canvas_mgr.move_volume_down(canvas, id); +} + +void _3DScene::set_objects_selections(wxGLCanvas* canvas, const std::vector& selections) +{ + s_canvas_mgr.set_objects_selections(canvas, selections); +} + +void _3DScene::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config) +{ + s_canvas_mgr.set_config(canvas, config); +} + +void _3DScene::set_print(wxGLCanvas* canvas, Print* print) +{ + s_canvas_mgr.set_print(canvas, print); +} + +void _3DScene::set_model(wxGLCanvas* canvas, Model* model) +{ + s_canvas_mgr.set_model(canvas, model); +} + +void _3DScene::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape) +{ + return s_canvas_mgr.set_bed_shape(canvas, shape); +} + +void _3DScene::set_auto_bed_shape(wxGLCanvas* canvas) +{ + return s_canvas_mgr.set_auto_bed_shape(canvas); +} + +BoundingBoxf3 _3DScene::get_volumes_bounding_box(wxGLCanvas* canvas) +{ + return s_canvas_mgr.get_volumes_bounding_box(canvas); +} + +void _3DScene::set_axes_length(wxGLCanvas* canvas, float length) +{ + s_canvas_mgr.set_axes_length(canvas, length); +} + +void _3DScene::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons) +{ + return s_canvas_mgr.set_cutting_plane(canvas, z, polygons); +} + +void _3DScene::set_color_by(wxGLCanvas* canvas, const std::string& value) +{ + return s_canvas_mgr.set_color_by(canvas, value); +} + +void _3DScene::set_select_by(wxGLCanvas* canvas, const std::string& value) +{ + return s_canvas_mgr.set_select_by(canvas, value); +} + +void _3DScene::set_drag_by(wxGLCanvas* canvas, const std::string& value) +{ + return s_canvas_mgr.set_drag_by(canvas, value); +} + +bool _3DScene::is_layers_editing_enabled(wxGLCanvas* canvas) +{ + return s_canvas_mgr.is_layers_editing_enabled(canvas); +} + +bool _3DScene::is_layers_editing_allowed(wxGLCanvas* canvas) +{ + return s_canvas_mgr.is_layers_editing_allowed(canvas); +} + +bool _3DScene::is_shader_enabled(wxGLCanvas* canvas) +{ + return s_canvas_mgr.is_shader_enabled(canvas); +} + +bool _3DScene::is_reload_delayed(wxGLCanvas* canvas) +{ + return s_canvas_mgr.is_reload_delayed(canvas); +} + +void _3DScene::enable_layers_editing(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_layers_editing(canvas, enable); +} + +void _3DScene::enable_warning_texture(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_warning_texture(canvas, enable); +} + +void _3DScene::enable_legend_texture(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_legend_texture(canvas, enable); +} + +void _3DScene::enable_picking(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_picking(canvas, enable); +} + +void _3DScene::enable_moving(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_moving(canvas, enable); +} + +void _3DScene::enable_gizmos(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_gizmos(canvas, enable); +} + +void _3DScene::enable_shader(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_shader(canvas, enable); +} + +void _3DScene::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_force_zoom_to_bed(canvas, enable); +} + +void _3DScene::allow_multisample(wxGLCanvas* canvas, bool allow) +{ + s_canvas_mgr.allow_multisample(canvas, allow); +} + +void _3DScene::zoom_to_bed(wxGLCanvas* canvas) +{ + s_canvas_mgr.zoom_to_bed(canvas); +} + +void _3DScene::zoom_to_volumes(wxGLCanvas* canvas) +{ + s_canvas_mgr.zoom_to_volumes(canvas); +} + +void _3DScene::select_view(wxGLCanvas* canvas, const std::string& direction) +{ + s_canvas_mgr.select_view(canvas, direction); +} + +void _3DScene::set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other) +{ + s_canvas_mgr.set_viewport_from_scene(canvas, other); +} + +void _3DScene::update_volumes_colors_by_extruder(wxGLCanvas* canvas) +{ + s_canvas_mgr.update_volumes_colors_by_extruder(canvas); +} + +void _3DScene::render(wxGLCanvas* canvas) +{ + s_canvas_mgr.render(canvas); +} + +std::vector _3DScene::get_current_print_zs(wxGLCanvas* canvas, bool active_only) +{ + return s_canvas_mgr.get_current_print_zs(canvas, active_only); +} + +void _3DScene::set_toolpaths_range(wxGLCanvas* canvas, double low, double high) +{ + s_canvas_mgr.set_toolpaths_range(canvas, low, high); +} + +void _3DScene::register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_viewport_changed_callback(canvas, callback); +} + +void _3DScene::register_on_double_click_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_double_click_callback(canvas, callback); +} + +void _3DScene::register_on_right_click_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_right_click_callback(canvas, callback); +} + +void _3DScene::register_on_select_object_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_select_object_callback(canvas, callback); +} + +void _3DScene::register_on_model_update_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_model_update_callback(canvas, callback); +} + +void _3DScene::register_on_remove_object_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_remove_object_callback(canvas, callback); +} + +void _3DScene::register_on_arrange_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_arrange_callback(canvas, callback); +} + +void _3DScene::register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_rotate_object_left_callback(canvas, callback); +} + +void _3DScene::register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_rotate_object_right_callback(canvas, callback); +} + +void _3DScene::register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_scale_object_uniformly_callback(canvas, callback); +} + +void _3DScene::register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_increase_objects_callback(canvas, callback); +} + +void _3DScene::register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_decrease_objects_callback(canvas, callback); +} + +void _3DScene::register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_instance_moved_callback(canvas, callback); +} + +void _3DScene::register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_wipe_tower_moved_callback(canvas, callback); +} + +void _3DScene::register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_enable_action_buttons_callback(canvas, callback); +} + +void _3DScene::register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_gizmo_scale_uniformly_callback(canvas, callback); } static inline int hex_digit_to_int(const char c) @@ -1752,38 +2070,44 @@ static inline std::vector parse_colors(const std::vector &sc return output; } -void _3DScene::load_gcode_preview(const Print* print, const GCodePreviewData* preview_data, GLVolumeCollection* volumes, const std::vector& str_tool_colors, bool use_VBOs) +std::vector _3DScene::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector instance_idxs) { - if ((preview_data == nullptr) || (volumes == nullptr)) - return; + return s_canvas_mgr.load_object(canvas, model_object, obj_idx, instance_idxs); +} - if (volumes->empty()) - { - std::vector tool_colors = parse_colors(str_tool_colors); +std::vector _3DScene::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx) +{ + return s_canvas_mgr.load_object(canvas, model, obj_idx); +} - s_gcode_preview_volume_index.reset(); +void _3DScene::reload_scene(wxGLCanvas* canvas, bool force) +{ + s_canvas_mgr.reload_scene(canvas, force); +} - _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); +void _3DScene::load_print_toolpaths(wxGLCanvas* canvas) +{ + s_canvas_mgr.load_print_toolpaths(canvas); +} - if (volumes->empty()) - reset_legend_texture(); - else - { - _generate_legend_texture(*preview_data, tool_colors); +void _3DScene::load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector& str_tool_colors) +{ + s_canvas_mgr.load_print_object_toolpaths(canvas, print_object, str_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()); +void _3DScene::load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector& str_tool_colors) +{ + s_canvas_mgr.load_wipe_tower_toolpaths(canvas, str_tool_colors); +} - _load_shells(*print, *volumes, use_VBOs); - } - } +void _3DScene::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector& str_tool_colors) +{ + s_canvas_mgr.load_gcode_preview(canvas, preview_data, str_tool_colors); +} - _update_gcode_volumes_visibility(*preview_data, *volumes); +void _3DScene::generate_legend_texture(const GCodePreviewData& preview_data, const std::vector& tool_colors) +{ + s_legend_texture.generate(preview_data, tool_colors); } unsigned int _3DScene::get_legend_texture_width() @@ -1831,938 +2155,4 @@ unsigned int _3DScene::finalize_warning_texture() return s_warning_texture.finalize(); } -// 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 &tool_colors, - bool use_VBOs) -{ - 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(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 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); -} - -// 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 &tool_colors_str, - bool use_VBOs) -{ - std::vector tool_colors = parse_colors(tool_colors_str); - - struct Ctxt - { - const Points *shifted_copies; - std::vector layers; - bool has_perimeters; - bool has_infill; - bool has_support; - const std::vector* 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(this->number_tools() - 1, std::max(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 volumes_per_thread(ctxt.layers.size()); - tbb::parallel_for( - tbb::blocked_range(0, ctxt.layers.size(), grain_size), - [&ctxt, &new_volume](const tbb::blocked_range& range) { - std::vector 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 ©: *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(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(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 &tool_colors_str, - bool use_VBOs) -{ - if (print->m_wipe_tower_tool_changes.empty()) - return; - - std::vector tool_colors = parse_colors(tool_colors_str); - - struct Ctxt - { - const Print *print; - const std::vector *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(this->number_tools() - 1, std::max(tool, 0)) : feature; } - - const std::vector& 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 priming; - std::vector 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 volumes_per_thread(n_items); - tbb::parallel_for( - tbb::blocked_range(0, n_items, grain_size), - [&ctxt, &new_volume](const tbb::blocked_range& range) { - // Bounding box of this slab of a wipe tower. - std::vector 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 &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 widths; - std::vector 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"; -} - -void _3DScene::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector& tool_colors, bool use_VBOs) -{ - // 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& 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; - } - }; - - typedef std::vector FiltersList; - size_t initial_volumes_count = 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) - { - 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::iterator begin = volumes.volumes.begin() + initial_volumes_count; - std::vector::iterator end = volumes.volumes.end(); - for (std::vector::iterator it = begin; it < end; ++it) - { - GLVolume* volume = *it; - delete volume; - } - 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()); - - extrusionentity_to_verts(path, layer.z, *filter->volume); - } - } - } - - // 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::_load_gcode_travel_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector& tool_colors, bool use_VBOs) -{ - 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); - - 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; - } - } - - if (!res) - { - // an error occourred - restore to previous state and return - if (initial_volumes_count != volumes.volumes.size()) - { - std::vector::iterator begin = volumes.volumes.begin() + initial_volumes_count; - std::vector::iterator end = volumes.volumes.end(); - for (std::vector::iterator it = begin; it < end; ++it) - { - GLVolume* volume = *it; - delete volume; - } - volumes.volumes.erase(begin, end); - } - - return; - } - - // 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); - } - } -} - -bool _3DScene::_travel_paths_by_type(const GCodePreviewData& preview_data, GLVolumeCollection& volumes) -{ - // 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 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; - 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()); - - polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *type->volume); - } - } - - return true; -} - -bool _3DScene::_travel_paths_by_feedrate(const GCodePreviewData& preview_data, GLVolumeCollection& volumes) -{ - // 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 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; - 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()); - - polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *feedrate->volume); - } - } - - return true; -} - -bool _3DScene::_travel_paths_by_tool(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector& 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 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; - 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()); - - polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *tool->volume); - } - } - - return true; -} - -void _3DScene::_load_gcode_retractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs) -{ - s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Retraction, 0, (unsigned int)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) - { - 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()); - - 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(use_VBOs); - } -} - -void _3DScene::_load_gcode_unretractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs) -{ - 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; - - GLVolume* volume = new GLVolume(preview_data.unretraction.color.rgba); - if (volume != nullptr) - { - 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()); - - 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(use_VBOs); - } -} - -void _3DScene::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data, GLVolumeCollection& volumes) -{ - unsigned int size = (unsigned int)s_gcode_preview_volume_index.first_volumes.size(); - for (unsigned int i = 0; i < size; ++i) - { - std::vector::iterator begin = volumes.volumes.begin() + s_gcode_preview_volume_index.first_volumes[i].id; - std::vector::iterator end = (i + 1 < size) ? volumes.volumes.begin() + s_gcode_preview_volume_index.first_volumes[i + 1].id : volumes.volumes.end(); - - for (std::vector::iterator it = begin; it != end; ++it) - { - GLVolume* volume = *it; - volume->outside_printer_detection_enabled = false; - - 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; - } - } - } - } -} - -void _3DScene::_generate_legend_texture(const GCodePreviewData& preview_data, const std::vector& tool_colors) -{ - s_legend_texture.generate(preview_data, tool_colors); -} - -void _3DScene::_load_shells(const Print& print, GLVolumeCollection& volumes, bool use_VBOs) -{ - 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); - - if (print.objects.empty()) - // nothing to render, return - return; - - // adds objects' volumes - unsigned int object_id = 0; - for (PrintObject* obj : print.objects) - { - ModelObject* model_obj = obj->model_object(); - - std::vector instance_ids(model_obj->instances.size()); - for (int i = 0; i < model_obj->instances.size(); ++i) - { - instance_ids[i] = i; - } - - for (ModelInstance* instance : model_obj->instances) - { - volumes.load_object(model_obj, object_id, instance_ids, "object", "object", "object", use_VBOs); - } - - ++object_id; - } - - // 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); - } -} - } // 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 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 void load_gcode_preview(const Print* print, const GCodePreviewData* preview_data, GLVolumeCollection* volumes, const std::vector& str_tool_colors, 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& 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& 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 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 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 load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector instance_idxs); + static std::vector 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& str_tool_colors); + static void load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector& str_tool_colors); + static void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector& 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& 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 &tool_colors, - bool use_VBOs); - - static void _load_print_object_toolpaths( - const PrintObject *print_object, - GLVolumeCollection *volumes, - const std::vector &tool_colors, - bool use_VBOs); - - static void _load_wipe_tower_toolpaths( - const Print *print, - GLVolumeCollection *volumes, - const std::vector &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& 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& 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& 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& 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& widths, const std::vector& heights, bool closed, double top_z, GLVolume& volume); + static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& 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 + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +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(v_size, 0.0f); + if (generate_tex_coords) + m_tex_coords = std::vector(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(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(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& nozzle_diameters = dynamic_cast(print_config.option("nozzle_diameter"))->values; + const std::vector& layer_heights_min = dynamic_cast(print_config.option("min_layer_height"))->values; + const std::vector& layer_heights_max = dynamic_cast(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(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& 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(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(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& 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& 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& 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 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 GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector 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 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()); + } + + return std::vector(); +} + +void GLCanvas3D::reload_scene(bool force) +{ + if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr)) + return; + + reset_volumes(); + set_bed_shape(dynamic_cast(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(m_config->option("nozzle_diameter"))->values.size(); + + bool semm = dynamic_cast(m_config->option("single_extruder_multi_material"))->value; + bool wt = dynamic_cast(m_config->option("wipe_tower"))->value; + bool co = dynamic_cast(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(m_config->option("wipe_tower_x"))->value; + float y = dynamic_cast(m_config->option("wipe_tower_y"))->value; + float w = dynamic_cast(m_config->option("wipe_tower_width"))->value; + float a = dynamic_cast(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(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 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& str_tool_colors) +{ + std::vector tool_colors = _parse_colors(str_tool_colors); + + struct Ctxt + { + const Points *shifted_copies; + std::vector layers; + bool has_perimeters; + bool has_infill; + bool has_support; + const std::vector* 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(this->number_tools() - 1, std::max(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 volumes_per_thread(ctxt.layers.size()); + tbb::parallel_for( + tbb::blocked_range(0, ctxt.layers.size(), grain_size), + [&ctxt, &new_volume](const tbb::blocked_range& range) { + std::vector 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 © : *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(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(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& 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 tool_colors = _parse_colors(str_tool_colors); + + struct Ctxt + { + const Print *print; + const std::vector *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(this->number_tools() - 1, std::max(tool, 0)) : feature; + } + + const std::vector& 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 priming; + std::vector 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 volumes_per_thread(n_items); + tbb::parallel_for( + tbb::blocked_range(0, n_items, grain_size), + [&ctxt, &new_volume](const tbb::blocked_range& range) { + // Bounding box of this slab of a wipe tower. + std::vector 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 &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 widths; + std::vector 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& 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 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 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 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 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& 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& 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 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::iterator begin = m_volumes.volumes.begin() + initial_volumes_count; + std::vector::iterator end = m_volumes.volumes.end(); + for (std::vector::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& 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::iterator begin = m_volumes.volumes.begin() + initial_volumes_count; + std::vector::iterator end = m_volumes.volumes.end(); + for (std::vector::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 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 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& 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 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 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::iterator begin = m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i].id; + std::vector::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::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& volume_idxs) +{ + if (m_model == nullptr) + return; + + std::set 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 GLCanvas3D::_parse_colors(const std::vector& colors) +{ + static const float INV_255 = 1.0f / 255.0f; + + std::vector 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 m_vertices; + std::vector 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 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 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> m_objects_volumes_idxs; + std::vector 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& 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& 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 get_current_print_zs(bool active_only) const; + void set_toolpaths_range(double low, double high); + + std::vector load_object(const ModelObject& model_object, int obj_idx, std::vector instance_idxs); + std::vector 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& str_tool_colors); + // Create 3D thick extrusion lines for wipe tower extrusions + void load_wipe_tower_toolpaths(const std::vector& str_tool_colors); + void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector& 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& tool_colors); + // generates gcode travel paths geometry + void _load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector& 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& 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& volume_idxs); + void _on_select(int volume_idx); + + void _update_gizmos_data(); + + static std::vector _parse_colors(const std::vector& 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 + +#include +#include + +#include +#include + +#include +#include +#include + +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 tokens; + boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on); + + if (tokens.empty()) + return false; + + std::vector 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 ? "" : ""; + std::string h2_end = format_as_html ? "" : ""; + std::string b_start = format_as_html ? "" : ""; + std::string b_end = format_as_html ? "" : ""; + std::string line_end = format_as_html ? "
" : "\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 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& 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& 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 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(); +} + +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 GLCanvas3DManager::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector instance_idxs) +{ + if (model_object == nullptr) + return std::vector(); + + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->load_object(*model_object, obj_idx, instance_idxs) : std::vector(); +} + +std::vector GLCanvas3DManager::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx) +{ + if (model == nullptr) + return std::vector(); + + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->load_object(*model, obj_idx) : std::vector(); +} + +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& 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& 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& 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 +#include + +class wxGLCanvas; +class wxGLContext; + +namespace Slic3r { + +class DynamicPrintConfig; +class Print; +class Model; +class ExPolygon; +typedef std::vector 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 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& 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& 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 get_current_print_zs(wxGLCanvas* canvas, bool active_only) const; + void set_toolpaths_range(wxGLCanvas* canvas, double low, double high); + + std::vector load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector instance_idxs); + std::vector 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& tool_colors); + void load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector& str_tool_colors); + void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector& 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 + +#include + +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 + +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 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 + #include #include #include @@ -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(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(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 + +#include + +#include +#include + +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 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 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 + +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 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 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 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 +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 +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(); -void -_load_print_toolpaths(print, volumes, tool_colors, use_VBOs) - Print *print; - GLVolumeCollection *volumes; - std::vector tool_colors; - int use_VBOs; +std::vector +load_model_object(canvas, model_object, obj_idx, instance_idxs) + SV *canvas; + ModelObject *model_object; + int obj_idx; + std::vector instance_idxs; CODE: - _3DScene::_load_print_toolpaths(print, volumes, tool_colors, use_VBOs != 0); + RETVAL = _3DScene::load_object((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model_object, obj_idx, instance_idxs); + OUTPUT: + RETVAL -void -_load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs) - PrintObject *print_object; - GLVolumeCollection *volumes; - std::vector tool_colors; - int use_VBOs; +std::vector +load_model(canvas, model, obj_idx) + SV *canvas; + Model *model; + int obj_idx; CODE: - _3DScene::_load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs != 0); + RETVAL = _3DScene::load_object((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model, obj_idx); + OUTPUT: + RETVAL void -_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs) - Print *print; - GLVolumeCollection *volumes; - std::vector tool_colors; - int use_VBOs; +reload_scene(canvas, force) + SV *canvas; + bool force; CODE: - _3DScene::_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs != 0); + _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((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); void -load_gcode_preview(print, preview_data, volumes, str_tool_colors, use_VBOs) - Print *print; +load_print_object_toolpaths(canvas, print_object, tool_colors) + SV *canvas; + PrintObject *print_object; + std::vector tool_colors; + CODE: + _3DScene::load_print_object_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), print_object, tool_colors); + +void +load_wipe_tower_toolpaths(canvas, tool_colors) + SV *canvas; + std::vector tool_colors; + CODE: + _3DScene::load_wipe_tower_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), tool_colors); + +void +load_gcode_preview(canvas, preview_data, str_tool_colors) + SV *canvas; GCodePreviewData *preview_data; - GLVolumeCollection *volumes; std::vector 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(); Ref model_object(); Ref 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()