diff options
author | bubnikv <bubnikv@gmail.com> | 2018-07-16 18:07:49 +0300 |
---|---|---|
committer | bubnikv <bubnikv@gmail.com> | 2018-07-16 18:07:49 +0300 |
commit | 6dc1bf5c08b7935dfbc55d35dea31148d09732f8 (patch) | |
tree | 862e4d4149e6cd7fceb03208c3f3bcbfa1625301 | |
parent | 0bbd1dbc4f80ee4909f84d91931c13d757ba0af7 (diff) | |
parent | d99b484ac6e67f00666530a1d7b78e7506e2ce6c (diff) |
Merge branch 'master' into stable
100 files changed, 12542 insertions, 4744 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index e8b2a6faa..60d67553b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,22 @@ if(NOT DEFINED CMAKE_PREFIX_PATH) endif() endif() +# WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory. +# We pick it from environment if it is not defined in another way +if(WIN32) + if(NOT DEFINED WIN10SDK_PATH) + if(DEFINED ENV{WIN10SDK_PATH}) + set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") + endif() + endif() + if(DEFINED WIN10SDK_PATH AND NOT EXISTS "${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h") + message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}") + message("${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h was not found") + message("STL fixing by the Netfabb service will not be compiled") + unset(WIN10SDK_PATH) + endif() +endif() + add_subdirectory(xs) get_filename_component(PERL_BIN_PATH "${PERL_EXECUTABLE}" DIRECTORY) diff --git a/cmake/modules/FindAlienWx.cmake b/cmake/modules/FindAlienWx.cmake index a96c29195..65221172b 100644 --- a/cmake/modules/FindAlienWx.cmake +++ b/cmake/modules/FindAlienWx.cmake @@ -49,6 +49,7 @@ my \$defines = ' ' . Alien::wxWidgets->defines; my \$cflags = Alien::wxWidgets->c_flags; my \$linkflags = Alien::wxWidgets->link_flags; my \$libraries = ' ' . Alien::wxWidgets->libraries(@components); +my \$gui_toolkit = Alien::wxWidgets->config->{toolkit}; #my @libraries = Alien::wxWidgets->link_libraries(@components); #my @implib = Alien::wxWidgets->import_libraries(@components); #my @shrlib = Alien::wxWidgets->shared_libraries(@components); @@ -82,6 +83,7 @@ cmake_set_var('LIBRARIES', \$libraries); cmake_set_var('DEFINITIONS', \$defines); #cmake_set_var('DEFINITIONS_DEBUG', ); cmake_set_var('CXX_FLAGS', \$cflags); +cmake_set_var('GUI_TOOLKIT', \$gui_toolkit); close \$fh; ") include(${AlienWx_TEMP_INCLUDE}) @@ -96,6 +98,7 @@ if (AlienWx_DEBUG) message(STATUS " AlienWx_DEFINITIONS = ${AlienWx_DEFINITIONS}") message(STATUS " AlienWx_DEFINITIONS_DEBUG = ${AlienWx_DEFINITIONS_DEBUG}") message(STATUS " AlienWx_CXX_FLAGS = ${AlienWx_CXX_FLAGS}") + message(STATUS " AlienWx_GUI_TOOLKIT = ${AlienWx_GUI_TOOLKIT}") endif() include(FindPackageHandleStandardArgs) diff --git a/doc/updating/Updatig.md b/doc/updating/Updatig.md index 3ce1f109c..13a2de144 100644 --- a/doc/updating/Updatig.md +++ b/doc/updating/Updatig.md @@ -17,7 +17,7 @@ A derived User preset keeps track of wich settings are inherited from the parent This system ensures that we don't overwrite user's settings when there is an update to the built in presets. Slic3r GUI now displays accurately which settings are inherited and which are modified. -A setting derived from a System preset is represeted by green label and a locked lock icon: +A setting derived from a System preset is represented by green label and a locked lock icon: ![a system setting](setting_sys.png) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index f2bf3baf8..80130fefe 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -7,7 +7,6 @@ use File::Basename qw(basename); use FindBin; use List::Util qw(first); use Slic3r::GUI::2DBed; -use Slic3r::GUI::BedShapeDialog; use Slic3r::GUI::Controller; use Slic3r::GUI::Controller::ManualControlDialog; use Slic3r::GUI::Controller::PrinterPanel; @@ -223,8 +222,8 @@ sub system_info { my $opengl_info_txt = ''; if (defined($self->{mainframe}) && defined($self->{mainframe}->{plater}) && defined($self->{mainframe}->{plater}->{canvas3D})) { - $opengl_info = $self->{mainframe}->{plater}->{canvas3D}->opengl_info(format => 'html'); - $opengl_info_txt = $self->{mainframe}->{plater}->{canvas3D}->opengl_info; + $opengl_info = Slic3r::GUI::_3DScene::get_gl_info(1, 1); + $opengl_info_txt = Slic3r::GUI::_3DScene::get_gl_info(0, 1); } my $about = Slic3r::GUI::SystemInfo->new( parent => undef, diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 33c0e8d37..157e7229c 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -16,97 +16,102 @@ use strict; use warnings; use Wx qw(wxTheApp :timer :bitmap :icon :dialog); -use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS EVT_CHAR EVT_TIMER); +#============================================================================================================================== +#use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS EVT_CHAR EVT_TIMER); # must load OpenGL *before* Wx::GLCanvas use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants); use base qw(Wx::GLCanvas Class::Accessor); -use Math::Trig qw(asin tan); -use List::Util qw(reduce min max first); -use Slic3r::Geometry qw(X Y normalize scale unscale scaled_epsilon); -use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl JT_ROUND); +#============================================================================================================================== +#use Math::Trig qw(asin tan); +#use List::Util qw(reduce min max first); +#use Slic3r::Geometry qw(X Y normalize scale unscale scaled_epsilon); +#use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl JT_ROUND); +#============================================================================================================================== use Wx::GLCanvas qw(:all); -use Slic3r::Geometry qw(PI); +#============================================================================================================================== +#use Slic3r::Geometry qw(PI); +#============================================================================================================================== -# _dirty: boolean flag indicating, that the screen has to be redrawn on EVT_IDLE. # volumes: reference to vector of Slic3r::GUI::3DScene::Volume. -# _camera_type: 'perspective' or 'ortho' -__PACKAGE__->mk_accessors( qw(_quat _dirty init - enable_picking - enable_moving - use_plain_shader - on_viewport_changed - on_hover - on_select - on_double_click - on_right_click - on_move - on_model_update - volumes - _sphi _stheta - cutting_plane_z - cut_lines_vertices - bed_shape - bed_triangles - bed_grid_lines - bed_polygon - background - origin - _mouse_pos - _hover_volume_idx - - _drag_volume_idx - _drag_start_pos - _drag_volume_center_offset - _drag_start_xy - _dragged - - _layer_height_edited - - _camera_type - _camera_target - _camera_distance - _zoom - - _legend_enabled - _warning_enabled - _apply_zoom_to_volumes_filter - _mouse_dragging - - ) ); - -use constant TRACKBALLSIZE => 0.8; -use constant TURNTABLE_MODE => 1; -use constant GROUND_Z => -0.02; -# For mesh selection: Not selected - bright yellow. -use constant DEFAULT_COLOR => [1,1,0]; -# For mesh selection: Selected - bright green. -use constant SELECTED_COLOR => [0,1,0,1]; -# For mesh selection: Mouse hovers over the object, but object not selected yet - dark green. -use constant HOVER_COLOR => [0.4,0.9,0,1]; - -# phi / theta angles to orient the camera. -use constant VIEW_DEFAULT => [45.0,45.0]; -use constant VIEW_LEFT => [90.0,90.0]; -use constant VIEW_RIGHT => [-90.0,90.0]; -use constant VIEW_TOP => [0.0,0.0]; -use constant VIEW_BOTTOM => [0.0,180.0]; -use constant VIEW_FRONT => [0.0,90.0]; -use constant VIEW_REAR => [180.0,90.0]; - -use constant MANIPULATION_IDLE => 0; -use constant MANIPULATION_DRAGGING => 1; -use constant MANIPULATION_LAYER_HEIGHT => 2; - -use constant GIMBALL_LOCK_THETA_MAX => 180; - -use constant VARIABLE_LAYER_THICKNESS_BAR_WIDTH => 70; -use constant VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT => 22; - -# make OpenGL::Array thread-safe -{ - no warnings 'redefine'; - *OpenGL::Array::CLONE_SKIP = sub { 1 }; -} +#============================================================================================================================== +#__PACKAGE__->mk_accessors( qw(_quat _dirty init +# enable_picking +# enable_moving +# use_plain_shader +# on_viewport_changed +# on_hover +# on_select +# on_double_click +# on_right_click +# on_move +# on_model_update +# volumes +# _sphi _stheta +# cutting_plane_z +# cut_lines_vertices +# bed_shape +# bed_triangles +# bed_grid_lines +# bed_polygon +# background +# origin +# _mouse_pos +# _hover_volume_idx +# +# _drag_volume_idx +# _drag_start_pos +# _drag_volume_center_offset +# _drag_start_xy +# _dragged +# +# _layer_height_edited +# +# _camera_type +# _camera_target +# _camera_distance +# _zoom +# +# _legend_enabled +# _warning_enabled +# _apply_zoom_to_volumes_filter +# _mouse_dragging +# +# ) ); +# +#use constant TRACKBALLSIZE => 0.8; +#use constant TURNTABLE_MODE => 1; +#use constant GROUND_Z => -0.02; +## For mesh selection: Not selected - bright yellow. +#use constant DEFAULT_COLOR => [1,1,0]; +## For mesh selection: Selected - bright green. +#use constant SELECTED_COLOR => [0,1,0,1]; +## For mesh selection: Mouse hovers over the object, but object not selected yet - dark green. +#use constant HOVER_COLOR => [0.4,0.9,0,1]; +# +## phi / theta angles to orient the camera. +#use constant VIEW_DEFAULT => [45.0,45.0]; +#use constant VIEW_LEFT => [90.0,90.0]; +#use constant VIEW_RIGHT => [-90.0,90.0]; +#use constant VIEW_TOP => [0.0,0.0]; +#use constant VIEW_BOTTOM => [0.0,180.0]; +#use constant VIEW_FRONT => [0.0,90.0]; +#use constant VIEW_REAR => [180.0,90.0]; +# +#use constant MANIPULATION_IDLE => 0; +#use constant MANIPULATION_DRAGGING => 1; +#use constant MANIPULATION_LAYER_HEIGHT => 2; +# +#use constant GIMBALL_LOCK_THETA_MAX => 180; +# +#use constant VARIABLE_LAYER_THICKNESS_BAR_WIDTH => 70; +#use constant VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT => 22; +# +## make OpenGL::Array thread-safe +#{ +# no warnings 'redefine'; +# *OpenGL::Array::CLONE_SKIP = sub { 1 }; +#} +#============================================================================================================================== sub new { my ($class, $parent) = @_; @@ -130,2070 +135,2097 @@ sub new { # we request a depth buffer explicitely because it looks like it's not created by # default on Linux, causing transparency issues my $self = $class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "", $attrib); - if (Wx::wxVERSION >= 3.000003) { - # Wx 3.0.3 contains an ugly hack to support some advanced OpenGL attributes through the attribute list. - # The attribute list is transferred between the wxGLCanvas and wxGLContext constructors using a single static array s_wglContextAttribs. - # Immediatelly force creation of the OpenGL context to consume the static variable s_wglContextAttribs. - $self->GetContext(); - } - - $self->{can_multisample} = $can_multisample; - $self->background(1); - $self->_quat((0, 0, 0, 1)); - $self->_stheta(45); - $self->_sphi(45); - $self->_zoom(1); - $self->_legend_enabled(0); - $self->_warning_enabled(0); - $self->use_plain_shader(0); - $self->_apply_zoom_to_volumes_filter(0); - $self->_mouse_dragging(0); - - # Collection of GLVolume objects - $self->volumes(Slic3r::GUI::_3DScene::GLVolume::Collection->new); - - # 3D point in model space - $self->_camera_type('ortho'); -# $self->_camera_type('perspective'); - $self->_camera_target(Slic3r::Pointf3->new(0,0,0)); - $self->_camera_distance(0.); - - $self->layer_editing_enabled(0); - $self->{layer_height_edit_band_width} = 2.; - $self->{layer_height_edit_strength} = 0.005; - $self->{layer_height_edit_last_object_id} = -1; - $self->{layer_height_edit_last_z} = 0.; - $self->{layer_height_edit_last_action} = 0; - - $self->reset_objects; - - EVT_PAINT($self, sub { - my $dc = Wx::PaintDC->new($self); - $self->Render($dc); - }); - EVT_SIZE($self, sub { $self->_dirty(1) }); - EVT_IDLE($self, sub { - return unless $self->_dirty; - return if !$self->IsShownOnScreen; - $self->Resize( $self->GetSizeWH ); - $self->Refresh; - }); - EVT_MOUSEWHEEL($self, \&mouse_wheel_event); - EVT_MOUSE_EVENTS($self, \&mouse_event); -# EVT_KEY_DOWN($self, sub { - EVT_CHAR($self, sub { - my ($s, $event) = @_; - if ($event->HasModifiers) { - $event->Skip; - } else { - my $key = $event->GetKeyCode; - if ($key == ord('0')) { - $self->select_view('iso'); - } elsif ($key == ord('1')) { - $self->select_view('top'); - } elsif ($key == ord('2')) { - $self->select_view('bottom'); - } elsif ($key == ord('3')) { - $self->select_view('front'); - } elsif ($key == ord('4')) { - $self->select_view('rear'); - } elsif ($key == ord('5')) { - $self->select_view('left'); - } elsif ($key == ord('6')) { - $self->select_view('right'); - } elsif ($key == ord('z')) { - $self->zoom_to_volumes; - } elsif ($key == ord('b')) { - $self->zoom_to_bed; - } else { - $event->Skip; - } - } - }); - - $self->{layer_height_edit_timer_id} = &Wx::NewId(); - $self->{layer_height_edit_timer} = Wx::Timer->new($self, $self->{layer_height_edit_timer_id}); - EVT_TIMER($self, $self->{layer_height_edit_timer_id}, sub { - my ($self, $event) = @_; - return if $self->_layer_height_edited != 1; - return if $self->{layer_height_edit_last_object_id} == -1; - $self->_variable_layer_thickness_action(undef); - }); +#============================================================================================================================== +# if (Wx::wxVERSION >= 3.000003) { +# # Wx 3.0.3 contains an ugly hack to support some advanced OpenGL attributes through the attribute list. +# # The attribute list is transferred between the wxGLCanvas and wxGLContext constructors using a single static array s_wglContextAttribs. +# # Immediatelly force creation of the OpenGL context to consume the static variable s_wglContextAttribs. +# $self->GetContext(); +# } +#============================================================================================================================== + +#============================================================================================================================== + Slic3r::GUI::_3DScene::add_canvas($self); + Slic3r::GUI::_3DScene::allow_multisample($self, $can_multisample); +# my $context = $self->GetContext; +# $self->SetCurrent($context); +# Slic3r::GUI::_3DScene::add_canvas($self, $context); +# +# $self->{can_multisample} = $can_multisample; +# $self->background(1); +# $self->_quat((0, 0, 0, 1)); +# $self->_stheta(45); +# $self->_sphi(45); +# $self->_zoom(1); +# $self->_legend_enabled(0); +# $self->_warning_enabled(0); +# $self->use_plain_shader(0); +# $self->_apply_zoom_to_volumes_filter(0); +# $self->_mouse_dragging(0); +# +# # Collection of GLVolume objects +# $self->volumes(Slic3r::GUI::_3DScene::GLVolume::Collection->new); +# +# # 3D point in model space +# $self->_camera_type('ortho'); +## $self->_camera_type('perspective'); +# $self->_camera_target(Slic3r::Pointf3->new(0,0,0)); +# $self->_camera_distance(0.); +# $self->layer_editing_enabled(0); +# $self->{layer_height_edit_band_width} = 2.; +# $self->{layer_height_edit_strength} = 0.005; +# $self->{layer_height_edit_last_object_id} = -1; +# $self->{layer_height_edit_last_z} = 0.; +# $self->{layer_height_edit_last_action} = 0; +# +# $self->reset_objects; +# +# EVT_PAINT($self, sub { +# my $dc = Wx::PaintDC->new($self); +# $self->Render($dc); +# }); +# EVT_SIZE($self, sub { $self->_dirty(1) }); +# EVT_IDLE($self, sub { +# return unless $self->_dirty; +# return if !$self->IsShownOnScreen; +# $self->Resize( $self->GetSizeWH ); +# $self->Refresh; +# }); +# EVT_MOUSEWHEEL($self, \&mouse_wheel_event); +# EVT_MOUSE_EVENTS($self, \&mouse_event); +## EVT_KEY_DOWN($self, sub { +# EVT_CHAR($self, sub { +# my ($s, $event) = @_; +# if ($event->HasModifiers) { +# $event->Skip; +# } else { +# my $key = $event->GetKeyCode; +# if ($key == ord('0')) { +# $self->select_view('iso'); +# } elsif ($key == ord('1')) { +# $self->select_view('top'); +# } elsif ($key == ord('2')) { +# $self->select_view('bottom'); +# } elsif ($key == ord('3')) { +# $self->select_view('front'); +# } elsif ($key == ord('4')) { +# $self->select_view('rear'); +# } elsif ($key == ord('5')) { +# $self->select_view('left'); +# } elsif ($key == ord('6')) { +# $self->select_view('right'); +# } elsif ($key == ord('z')) { +# $self->zoom_to_volumes; +# } elsif ($key == ord('b')) { +# $self->zoom_to_bed; +# } else { +# $event->Skip; +# } +# } +# }); +# +# $self->{layer_height_edit_timer_id} = &Wx::NewId(); +# $self->{layer_height_edit_timer} = Wx::Timer->new($self, $self->{layer_height_edit_timer_id}); +# EVT_TIMER($self, $self->{layer_height_edit_timer_id}, sub { +# my ($self, $event) = @_; +# return if $self->_layer_height_edited != 1; +# $self->_variable_layer_thickness_action(undef); +# }); +#============================================================================================================================== return $self; } -sub set_legend_enabled { - my ($self, $value) = @_; - $self->_legend_enabled($value); -} - -sub set_warning_enabled { - my ($self, $value) = @_; - $self->_warning_enabled($value); -} +#============================================================================================================================== +#sub set_legend_enabled { +# my ($self, $value) = @_; +# $self->_legend_enabled($value); +#} +# +#sub set_warning_enabled { +# my ($self, $value) = @_; +# $self->_warning_enabled($value); +#} +#============================================================================================================================== sub Destroy { my ($self) = @_; - $self->{layer_height_edit_timer}->Stop; - $self->DestroyGL; +#============================================================================================================================== + Slic3r::GUI::_3DScene::remove_canvas($self); +# $self->{layer_height_edit_timer}->Stop; +# $self->DestroyGL; +#============================================================================================================================== return $self->SUPER::Destroy; } -sub layer_editing_enabled { - my ($self, $value) = @_; - if (@_ == 2) { - $self->{layer_editing_enabled} = $value; - if ($value) { - if (! $self->{layer_editing_initialized}) { - # Enabling the layer editing for the first time. This triggers compilation of the necessary OpenGL shaders. - # If compilation fails, a message box is shown with the error codes. - $self->SetCurrent($self->GetContext); - my $shader = new Slic3r::GUI::_3DScene::GLShader; - my $error_message; - if (! $shader->load($self->_fragment_shader_variable_layer_height, $self->_vertex_shader_variable_layer_height)) { - # Compilation or linking of the shaders failed. - $error_message = "Cannot compile an OpenGL Shader, therefore the Variable Layer Editing will be disabled.\n\n" - . $shader->last_error; - $shader = undef; - } else { - $self->{layer_height_edit_shader} = $shader; - ($self->{layer_preview_z_texture_id}) = glGenTextures_p(1); - glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id}); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); - glBindTexture(GL_TEXTURE_2D, 0); - } - if (defined($error_message)) { - # Don't enable the layer editing tool. - $self->{layer_editing_enabled} = 0; - # 2 means failed - $self->{layer_editing_initialized} = 2; - # Show the error message. - Wx::MessageBox($error_message, "Slic3r Error", wxOK | wxICON_EXCLAMATION, $self); - } else { - $self->{layer_editing_initialized} = 1; - } - } elsif ($self->{layer_editing_initialized} == 2) { - # Initilization failed before. Don't try to initialize and disable layer editing. - $self->{layer_editing_enabled} = 0; - } - } - } - return $self->{layer_editing_enabled}; -} - -sub layer_editing_allowed { - my ($self) = @_; - # Allow layer editing if either the shaders were not initialized yet and we don't know - # whether it will be possible to initialize them, - # or if the initialization was done already and it failed. - return ! (defined($self->{layer_editing_initialized}) && $self->{layer_editing_initialized} == 2); -} - -sub _first_selected_object_id_for_variable_layer_height_editing { - my ($self) = @_; - for my $i (0..$#{$self->volumes}) { - if ($self->volumes->[$i]->selected) { - my $object_id = int($self->volumes->[$i]->select_group_id / 1000000); - # Objects with object_id >= 1000 have a specific meaning, for example the wipe tower proxy. - return ($object_id >= $self->{print}->object_count) ? -1 : $object_id - if $object_id < 10000; - } - } - return -1; -} - -# Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen. -sub _variable_layer_thickness_bar_rect_screen { - my ($self) = @_; - my ($cw, $ch) = $self->GetSizeWH; - return ($cw - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, 0, $cw, $ch - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT); -} - -sub _variable_layer_thickness_bar_rect_viewport { - my ($self) = @_; - my ($cw, $ch) = $self->GetSizeWH; - return ((0.5*$cw-VARIABLE_LAYER_THICKNESS_BAR_WIDTH)/$self->_zoom, (-0.5*$ch+VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT)/$self->_zoom, $cw/(2*$self->_zoom), $ch/(2*$self->_zoom)); -} - -# Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen. -sub _variable_layer_thickness_reset_rect_screen { - my ($self) = @_; - my ($cw, $ch) = $self->GetSizeWH; - return ($cw - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, $ch - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT, $cw, $ch); -} - -sub _variable_layer_thickness_reset_rect_viewport { - my ($self) = @_; - my ($cw, $ch) = $self->GetSizeWH; - return ((0.5*$cw-VARIABLE_LAYER_THICKNESS_BAR_WIDTH)/$self->_zoom, -$ch/(2*$self->_zoom), $cw/(2*$self->_zoom), (-0.5*$ch+VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT)/$self->_zoom); -} - -sub _variable_layer_thickness_bar_rect_mouse_inside { - my ($self, $mouse_evt) = @_; - my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen; - return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom; -} - -sub _variable_layer_thickness_reset_rect_mouse_inside { - my ($self, $mouse_evt) = @_; - my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_reset_rect_screen; - return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom; -} - -sub _variable_layer_thickness_bar_mouse_cursor_z_relative { - my ($self) = @_; - my $mouse_pos = $self->ScreenToClientPoint(Wx::GetMousePosition()); - my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen; - return ($mouse_pos->x >= $bar_left && $mouse_pos->x <= $bar_right && $mouse_pos->y >= $bar_top && $mouse_pos->y <= $bar_bottom) ? - # Inside the bar. - ($bar_bottom - $mouse_pos->y - 1.) / ($bar_bottom - $bar_top - 1) : - # Outside the bar. - -1000.; -} - -sub _variable_layer_thickness_action { - my ($self, $mouse_event, $do_modification) = @_; - # A volume is selected. Test, whether hovering over a layer thickness bar. - return if $self->{layer_height_edit_last_object_id} == -1; - if (defined($mouse_event)) { - my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen; - $self->{layer_height_edit_last_z} = unscale($self->{print}->get_object($self->{layer_height_edit_last_object_id})->size->z) - * ($bar_bottom - $mouse_event->GetY - 1.) / ($bar_bottom - $bar_top); - $self->{layer_height_edit_last_action} = $mouse_event->ShiftDown ? ($mouse_event->RightIsDown ? 3 : 2) : ($mouse_event->RightIsDown ? 0 : 1); - } - # Mark the volume as modified, so Print will pick its layer height profile? Where to mark it? - # Start a timer to refresh the print? schedule_background_process() ? - # The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself. - $self->{print}->get_object($self->{layer_height_edit_last_object_id})->adjust_layer_height_profile( - $self->{layer_height_edit_last_z}, - $self->{layer_height_edit_strength}, - $self->{layer_height_edit_band_width}, - $self->{layer_height_edit_last_action}); - $self->volumes->[$self->{layer_height_edit_last_object_id}]->generate_layer_height_texture( - $self->{print}->get_object($self->{layer_height_edit_last_object_id}), 1); - $self->Refresh; - # Automatic action on mouse down with the same coordinate. - $self->{layer_height_edit_timer}->Start(100, wxTIMER_CONTINUOUS); -} - -sub mouse_event { - my ($self, $e) = @_; - - my $pos = Slic3r::Pointf->new($e->GetPositionXY); - my $object_idx_selected = $self->{layer_height_edit_last_object_id} = ($self->layer_editing_enabled && $self->{print}) ? $self->_first_selected_object_id_for_variable_layer_height_editing : -1; - - $self->_mouse_dragging($e->Dragging); - - if ($e->Entering && (&Wx::wxMSW || $^O eq 'linux')) { - # wxMSW needs focus in order to catch mouse wheel events - $self->SetFocus; - $self->_drag_start_xy(undef); - } elsif ($e->LeftDClick) { - if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) { - } elsif ($self->on_double_click) { - $self->on_double_click->(); - } - } elsif ($e->LeftDown || $e->RightDown) { - # If user pressed left or right button we first check whether this happened - # on a volume or not. - my $volume_idx = $self->_hover_volume_idx // -1; - $self->_layer_height_edited(0); - if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) { - # A volume is selected and the mouse is hovering over a layer thickness bar. - # Start editing the layer height. - $self->_layer_height_edited(1); - $self->_variable_layer_thickness_action($e); - } elsif ($object_idx_selected != -1 && $self->_variable_layer_thickness_reset_rect_mouse_inside($e)) { - $self->{print}->get_object($object_idx_selected)->reset_layer_height_profile; - # Index 2 means no editing, just wait for mouse up event. - $self->_layer_height_edited(2); - $self->Refresh; - $self->Update; - } else { - # The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate $pos->x,y, - # an converts the screen space coordinate to unscaled object space. - my $pos3d = ($volume_idx == -1) ? undef : $self->mouse_to_3d(@$pos); - - # Select volume in this 3D canvas. - # Don't deselect a volume if layer editing is enabled. We want the object to stay selected - # during the scene manipulation. - if ($self->enable_picking && ($volume_idx != -1 || ! $self->layer_editing_enabled)) { - $self->deselect_volumes; - $self->select_volume($volume_idx); - - if ($volume_idx != -1) { - my $group_id = $self->volumes->[$volume_idx]->select_group_id; - my @volumes; - if ($group_id != -1) { - $self->select_volume($_) - for grep $self->volumes->[$_]->select_group_id == $group_id, - 0..$#{$self->volumes}; - } - } - - $self->Refresh; - $self->Update; - } - - # propagate event through callback - $self->on_select->($volume_idx) - if $self->on_select; - - if ($volume_idx != -1) { - if ($e->LeftDown && $self->enable_moving) { - # Only accept the initial position, if it is inside the volume bounding box. - my $volume_bbox = $self->volumes->[$volume_idx]->transformed_bounding_box; - $volume_bbox->offset(1.); - if ($volume_bbox->contains_point($pos3d)) { - # The dragging operation is initiated. - $self->_drag_volume_idx($volume_idx); - $self->_drag_start_pos($pos3d); - # Remember the shift to to the object center. The object center will later be used - # to limit the object placement close to the bed. - $self->_drag_volume_center_offset($pos3d->vector_to($volume_bbox->center)); - } - } elsif ($e->RightDown) { - # if right clicking on volume, propagate event through callback - $self->on_right_click->($e->GetPosition) - if $self->on_right_click; - } - } - } - } elsif ($e->Dragging && $e->LeftIsDown && ! $self->_layer_height_edited && defined($self->_drag_volume_idx)) { - # Get new position at the same Z of the initial click point. - my $cur_pos = Slic3r::Linef3->new( - $self->mouse_to_3d($e->GetX, $e->GetY, 0), - $self->mouse_to_3d($e->GetX, $e->GetY, 1)) - ->intersect_plane($self->_drag_start_pos->z); - # Clip the new position, so the object center remains close to the bed. - { - $cur_pos->translate(@{$self->_drag_volume_center_offset}); - my $cur_pos2 = Slic3r::Point->new(scale($cur_pos->x), scale($cur_pos->y)); - if (! $self->bed_polygon->contains_point($cur_pos2)) { - my $ip = $self->bed_polygon->point_projection($cur_pos2); - $cur_pos->set_x(unscale($ip->x)); - $cur_pos->set_y(unscale($ip->y)); - } - $cur_pos->translate(@{$self->_drag_volume_center_offset->negative}); - } - # Calculate the translation vector. - my $vector = $self->_drag_start_pos->vector_to($cur_pos); - # Get the volume being dragged. - my $volume = $self->volumes->[$self->_drag_volume_idx]; - # Get all volumes belonging to the same group, if any. - my @volumes = ($volume->drag_group_id == -1) ? - ($volume) : - grep $_->drag_group_id == $volume->drag_group_id, @{$self->volumes}; - # Apply new temporary volume origin and ignore Z. - $_->translate($vector->x, $vector->y, 0) for @volumes; - $self->_drag_start_pos($cur_pos); - $self->_dragged(1); - $self->Refresh; - $self->Update; - } elsif ($e->Dragging) { - if ($self->_layer_height_edited && $object_idx_selected != -1) { - $self->_variable_layer_thickness_action($e) if ($self->_layer_height_edited == 1); - } elsif ($e->LeftIsDown) { - # if dragging over blank area with left button, rotate - if (defined $self->_drag_start_pos) { - my $orig = $self->_drag_start_pos; - if (TURNTABLE_MODE) { - # Turntable mode is enabled by default. - $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE); - $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #- - $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_LOCK_THETA_MAX; - $self->_stheta(0) if $self->_stheta < 0; - } else { - my $size = $self->GetClientSize; - my @quat = trackball( - $orig->x / ($size->width / 2) - 1, - 1 - $orig->y / ($size->height / 2), #/ - $pos->x / ($size->width / 2) - 1, - 1 - $pos->y / ($size->height / 2), #/ - ); - $self->_quat(mulquats($self->_quat, \@quat)); - } - $self->on_viewport_changed->() if $self->on_viewport_changed; - $self->Refresh; - $self->Update; - } - $self->_drag_start_pos($pos); - } elsif ($e->MiddleIsDown || $e->RightIsDown) { - # If dragging over blank area with right button, pan. - if (defined $self->_drag_start_xy) { - # get point in model space at Z = 0 - my $cur_pos = $self->mouse_to_3d($e->GetX, $e->GetY, 0); - my $orig = $self->mouse_to_3d($self->_drag_start_xy->x, $self->_drag_start_xy->y, 0); - $self->_camera_target->translate(@{$orig->vector_to($cur_pos)->negative}); - $self->on_viewport_changed->() if $self->on_viewport_changed; - $self->Refresh; - $self->Update; - } - $self->_drag_start_xy($pos); - } - } elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) { - if ($self->_layer_height_edited) { - $self->_layer_height_edited(undef); - $self->{layer_height_edit_timer}->Stop; - $self->on_model_update->() - if ($object_idx_selected != -1 && $self->on_model_update); - } elsif ($self->on_move && defined($self->_drag_volume_idx) && $self->_dragged) { - # get all volumes belonging to the same group, if any - my @volume_idxs; - my $group_id = $self->volumes->[$self->_drag_volume_idx]->drag_group_id; - if ($group_id == -1) { - @volume_idxs = ($self->_drag_volume_idx); - } else { - @volume_idxs = grep $self->volumes->[$_]->drag_group_id == $group_id, - 0..$#{$self->volumes}; - } - $self->on_move->(@volume_idxs); - } - $self->_drag_volume_idx(undef); - $self->_drag_start_pos(undef); - $self->_drag_start_xy(undef); - $self->_dragged(undef); - } elsif ($e->Moving) { - $self->_mouse_pos($pos); - # Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor - # hovers over. - if ($self->enable_picking) { - $self->Update; - $self->Refresh; - } - } else { - $e->Skip(); - } -} - -sub mouse_wheel_event { - my ($self, $e) = @_; - - if ($e->MiddleIsDown) { - # Ignore the wheel events if the middle button is pressed. - return; - } - - if ($self->layer_editing_enabled && $self->{print}) { - my $object_idx_selected = $self->_first_selected_object_id_for_variable_layer_height_editing; - if ($object_idx_selected != -1) { - # A volume is selected. Test, whether hovering over a layer thickness bar. - if ($self->_variable_layer_thickness_bar_rect_mouse_inside($e)) { - # Adjust the width of the selection. - $self->{layer_height_edit_band_width} = max(min($self->{layer_height_edit_band_width} * (1 + 0.1 * $e->GetWheelRotation() / $e->GetWheelDelta()), 10.), 1.5); - $self->Refresh; - return; - } - } - } - - # Calculate the zoom delta and apply it to the current zoom factor - my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta(); - $zoom = max(min($zoom, 4), -4); - $zoom /= 10; - $zoom = $self->_zoom / (1-$zoom); - # Don't allow to zoom too far outside the scene. - my $zoom_min = $self->get_zoom_to_bounding_box_factor($self->max_bounding_box); - $zoom_min *= 0.4 if defined $zoom_min; - $zoom = $zoom_min if defined $zoom_min && $zoom < $zoom_min; - $self->_zoom($zoom); - -# # In order to zoom around the mouse point we need to translate -# # the camera target -# my $size = Slic3r::Pointf->new($self->GetSizeWH); -# my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #- -# $self->_camera_target->translate( -# # ($pos - $size/2) represents the vector from the viewport center -# # to the mouse point. By multiplying it by $zoom we get the new, -# # transformed, length of such vector. -# # Since we want that point to stay fixed, we move our camera target -# # in the opposite direction by the delta of the length of such vector -# # ($zoom - 1). We then scale everything by 1/$self->_zoom since -# # $self->_camera_target is expressed in terms of model units. -# -($pos->x - $size->x/2) * ($zoom) / $self->_zoom, -# -($pos->y - $size->y/2) * ($zoom) / $self->_zoom, -# 0, -# ) if 0; - - $self->on_viewport_changed->() if $self->on_viewport_changed; - $self->Resize($self->GetSizeWH) if $self->IsShownOnScreen; - $self->Refresh; -} - -# Reset selection. -sub reset_objects { - my ($self) = @_; - if ($self->GetContext) { - $self->SetCurrent($self->GetContext); - $self->volumes->release_geometry; - } - $self->volumes->erase; - $self->_dirty(1); -} - -# Setup camera to view all objects. -sub set_viewport_from_scene { - my ($self, $scene) = @_; - - $self->_sphi($scene->_sphi); - $self->_stheta($scene->_stheta); - $self->_camera_target($scene->_camera_target); - $self->_zoom($scene->_zoom); - $self->_quat($scene->_quat); - $self->_dirty(1); -} - -# Set the camera to a default orientation, -# zoom to volumes. -sub select_view { - my ($self, $direction) = @_; - my $dirvec; - if (ref($direction)) { - $dirvec = $direction; - } else { - if ($direction eq 'iso') { - $dirvec = VIEW_DEFAULT; - } elsif ($direction eq 'left') { - $dirvec = VIEW_LEFT; - } elsif ($direction eq 'right') { - $dirvec = VIEW_RIGHT; - } elsif ($direction eq 'top') { - $dirvec = VIEW_TOP; - } elsif ($direction eq 'bottom') { - $dirvec = VIEW_BOTTOM; - } elsif ($direction eq 'front') { - $dirvec = VIEW_FRONT; - } elsif ($direction eq 'rear') { - $dirvec = VIEW_REAR; - } - } - my $bb = $self->volumes_bounding_box; - if (! $bb->empty) { - $self->_sphi($dirvec->[0]); - $self->_stheta($dirvec->[1]); - # Avoid gimball lock. - $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_LOCK_THETA_MAX; - $self->_stheta(0) if $self->_stheta < 0; - $self->on_viewport_changed->() if $self->on_viewport_changed; - $self->Refresh; - } -} - -sub get_zoom_to_bounding_box_factor { - my ($self, $bb) = @_; - my $max_bb_size = max(@{ $bb->size }); - return undef if ($max_bb_size == 0); - - # project the bbox vertices on a plane perpendicular to the camera forward axis - # then calculates the vertices coordinate on this plane along the camera xy axes - - # we need the view matrix, we let opengl calculate it (same as done in render sub) - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - if (!TURNTABLE_MODE) { - # Shift the perspective camera. - my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance); - glTranslatef(@$camera_pos); - } - - if (TURNTABLE_MODE) { - # Turntable mode is enabled by default. - glRotatef(-$self->_stheta, 1, 0, 0); # pitch - glRotatef($self->_sphi, 0, 0, 1); # yaw - } else { - # Shift the perspective camera. - my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance); - glTranslatef(@$camera_pos); - my @rotmat = quat_to_rotmatrix($self->quat); - glMultMatrixd_p(@rotmat[0..15]); - } - glTranslatef(@{ $self->_camera_target->negative }); - - # get the view matrix back from opengl - my @matrix = glGetFloatv_p(GL_MODELVIEW_MATRIX); - - # camera axes - my $right = Slic3r::Pointf3->new($matrix[0], $matrix[4], $matrix[8]); - my $up = Slic3r::Pointf3->new($matrix[1], $matrix[5], $matrix[9]); - my $forward = Slic3r::Pointf3->new($matrix[2], $matrix[6], $matrix[10]); - - my $bb_min = $bb->min_point(); - my $bb_max = $bb->max_point(); - my $bb_center = $bb->center(); - - # bbox vertices in world space - my @vertices = (); - push(@vertices, $bb_min); - push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_min->y(), $bb_min->z())); - push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_max->y(), $bb_min->z())); - push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_max->y(), $bb_min->z())); - push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_min->y(), $bb_max->z())); - push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_min->y(), $bb_max->z())); - push(@vertices, $bb_max); - push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_max->y(), $bb_max->z())); - - my $max_x = 0.0; - my $max_y = 0.0; - - # margin factor to give some empty space around the bbox - my $margin_factor = 1.25; - - foreach my $v (@vertices) { - # project vertex on the plane perpendicular to camera forward axis - my $pos = Slic3r::Pointf3->new($v->x() - $bb_center->x(), $v->y() - $bb_center->y(), $v->z() - $bb_center->z()); - my $proj_on_normal = $pos->x() * $forward->x() + $pos->y() * $forward->y() + $pos->z() * $forward->z(); - my $proj_on_plane = Slic3r::Pointf3->new($pos->x() - $proj_on_normal * $forward->x(), $pos->y() - $proj_on_normal * $forward->y(), $pos->z() - $proj_on_normal * $forward->z()); - - # calculates vertex coordinate along camera xy axes - my $x_on_plane = $proj_on_plane->x() * $right->x() + $proj_on_plane->y() * $right->y() + $proj_on_plane->z() * $right->z(); - my $y_on_plane = $proj_on_plane->x() * $up->x() + $proj_on_plane->y() * $up->y() + $proj_on_plane->z() * $up->z(); - - $max_x = max($max_x, $margin_factor * 2 * abs($x_on_plane)); - $max_y = max($max_y, $margin_factor * 2 * abs($y_on_plane)); - } - - return undef if (($max_x == 0) || ($max_y == 0)); - - my ($cw, $ch) = $self->GetSizeWH; - my $min_ratio = min($cw / $max_x, $ch / $max_y); - - return $min_ratio; -} - -sub zoom_to_bounding_box { - my ($self, $bb) = @_; - # Calculate the zoom factor needed to adjust viewport to bounding box. - my $zoom = $self->get_zoom_to_bounding_box_factor($bb); - if (defined $zoom) { - $self->_zoom($zoom); - # center view around bounding box center - $self->_camera_target($bb->center); - $self->on_viewport_changed->() if $self->on_viewport_changed; - $self->Resize($self->GetSizeWH) if $self->IsShownOnScreen; - $self->Refresh; - } -} - -sub zoom_to_bed { - my ($self) = @_; - - if ($self->bed_shape) { - $self->zoom_to_bounding_box($self->bed_bounding_box); - } -} - -sub zoom_to_volume { - my ($self, $volume_idx) = @_; - - my $volume = $self->volumes->[$volume_idx]; - my $bb = $volume->transformed_bounding_box; - $self->zoom_to_bounding_box($bb); -} - -sub zoom_to_volumes { - my ($self) = @_; - $self->_apply_zoom_to_volumes_filter(1); - $self->zoom_to_bounding_box($self->volumes_bounding_box); - $self->_apply_zoom_to_volumes_filter(0); -} - -sub volumes_bounding_box { - my ($self) = @_; - - my $bb = Slic3r::Geometry::BoundingBoxf3->new; - foreach my $v (@{$self->volumes}) { - $bb->merge($v->transformed_bounding_box) if (! $self->_apply_zoom_to_volumes_filter || $v->zoom_to_volumes); - } - return $bb; -} - -sub bed_bounding_box { - my ($self) = @_; - - my $bb = Slic3r::Geometry::BoundingBoxf3->new; - if ($self->bed_shape) { - $bb->merge_point(Slic3r::Pointf3->new(@$_, 0)) for @{$self->bed_shape}; - } - return $bb; -} - -sub max_bounding_box { - my ($self) = @_; - - my $bb = $self->bed_bounding_box; - $bb->merge($self->volumes_bounding_box); - return $bb; -} - -# Used by ObjectCutDialog and ObjectPartsPanel to generate a rectangular ground plane -# to support the scene objects. -sub set_auto_bed_shape { - my ($self, $bed_shape) = @_; - - # draw a default square bed around object center - my $max_size = max(@{ $self->volumes_bounding_box->size }); - my $center = $self->volumes_bounding_box->center; - $self->set_bed_shape([ - [ $center->x - $max_size, $center->y - $max_size ], #-- - [ $center->x + $max_size, $center->y - $max_size ], #-- - [ $center->x + $max_size, $center->y + $max_size ], #++ - [ $center->x - $max_size, $center->y + $max_size ], #++ - ]); - # Set the origin for painting of the coordinate system axes. - $self->origin(Slic3r::Pointf->new(@$center[X,Y])); -} - -# Set the bed shape to a single closed 2D polygon (array of two element arrays), -# triangulate the bed and store the triangles into $self->bed_triangles, -# fills the $self->bed_grid_lines and sets $self->origin. -# Sets $self->bed_polygon to limit the object placement. -sub set_bed_shape { - my ($self, $bed_shape) = @_; - - $self->bed_shape($bed_shape); - - # triangulate bed - my $expolygon = Slic3r::ExPolygon->new([ map [map scale($_), @$_], @$bed_shape ]); - my $bed_bb = $expolygon->bounding_box; - - { - my @points = (); - foreach my $triangle (@{ $expolygon->triangulate }) { - push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$triangle; - } - $self->bed_triangles(OpenGL::Array->new_list(GL_FLOAT, @points)); - } - - { - my @polylines = (); - for (my $x = $bed_bb->x_min; $x <= $bed_bb->x_max; $x += scale 10) { - push @polylines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]); - } - for (my $y = $bed_bb->y_min; $y <= $bed_bb->y_max; $y += scale 10) { - push @polylines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]); - } - # clip with a slightly grown expolygon because our lines lay on the contours and - # may get erroneously clipped - my @lines = map Slic3r::Line->new(@$_[0,-1]), - @{intersection_pl(\@polylines, [ @{$expolygon->offset(+scaled_epsilon)} ])}; - - # append bed contours - push @lines, map @{$_->lines}, @$expolygon; - - my @points = (); - foreach my $line (@lines) { - push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$line; #)) - } - $self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points)); - } - - # Set the origin for painting of the coordinate system axes. - $self->origin(Slic3r::Pointf->new(0,0)); - - $self->bed_polygon(offset_ex([$expolygon->contour], $bed_bb->radius * 1.7, JT_ROUND, scale(0.5))->[0]->contour->clone); -} - -sub deselect_volumes { - my ($self) = @_; - $_->set_selected(0) for @{$self->volumes}; -} - -sub select_volume { - my ($self, $volume_idx) = @_; - - return if ($volume_idx >= scalar(@{$self->volumes})); - - $self->volumes->[$volume_idx]->set_selected(1) - if $volume_idx != -1; -} - -sub SetCuttingPlane { - my ($self, $z, $expolygons) = @_; - - $self->cutting_plane_z($z); - - # grow slices in order to display them better - $expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1); - - my @verts = (); - foreach my $line (map @{$_->lines}, map @$_, @$expolygons) { - push @verts, ( - unscale($line->a->x), unscale($line->a->y), $z, #)) - unscale($line->b->x), unscale($line->b->y), $z, #)) - ); - } - $self->cut_lines_vertices(OpenGL::Array->new_list(GL_FLOAT, @verts)); -} - -# Given an axis and angle, compute quaternion. -sub axis_to_quat { - my ($ax, $phi) = @_; - - my $lena = sqrt(reduce { $a + $b } (map { $_ * $_ } @$ax)); - my @q = map { $_ * (1 / $lena) } @$ax; - @q = map { $_ * sin($phi / 2.0) } @q; - $q[$#q + 1] = cos($phi / 2.0); - return @q; -} - -# Project a point on the virtual trackball. -# If it is inside the sphere, map it to the sphere, if it outside map it -# to a hyperbola. -sub project_to_sphere { - my ($r, $x, $y) = @_; - - my $d = sqrt($x * $x + $y * $y); - if ($d < $r * 0.70710678118654752440) { # Inside sphere - return sqrt($r * $r - $d * $d); - } else { # On hyperbola - my $t = $r / 1.41421356237309504880; - return $t * $t / $d; - } -} - -sub cross { - my ($v1, $v2) = @_; - - return (@$v1[1] * @$v2[2] - @$v1[2] * @$v2[1], - @$v1[2] * @$v2[0] - @$v1[0] * @$v2[2], - @$v1[0] * @$v2[1] - @$v1[1] * @$v2[0]); -} - -# Simulate a track-ball. Project the points onto the virtual trackball, -# then figure out the axis of rotation, which is the cross product of -# P1 P2 and O P1 (O is the center of the ball, 0,0,0) Note: This is a -# deformed trackball-- is a trackball in the center, but is deformed -# into a hyperbolic sheet of rotation away from the center. -# It is assumed that the arguments to this routine are in the range -# (-1.0 ... 1.0). -sub trackball { - my ($p1x, $p1y, $p2x, $p2y) = @_; - - if ($p1x == $p2x && $p1y == $p2y) { - # zero rotation - return (0.0, 0.0, 0.0, 1.0); - } - - # First, figure out z-coordinates for projection of P1 and P2 to - # deformed sphere - my @p1 = ($p1x, $p1y, project_to_sphere(TRACKBALLSIZE, $p1x, $p1y)); - my @p2 = ($p2x, $p2y, project_to_sphere(TRACKBALLSIZE, $p2x, $p2y)); - - # axis of rotation (cross product of P1 and P2) - my @a = cross(\@p2, \@p1); - - # Figure out how much to rotate around that axis. - my @d = map { $_ * $_ } (map { $p1[$_] - $p2[$_] } 0 .. $#p1); - my $t = sqrt(reduce { $a + $b } @d) / (2.0 * TRACKBALLSIZE); - - # Avoid problems with out-of-control values... - $t = 1.0 if ($t > 1.0); - $t = -1.0 if ($t < -1.0); - my $phi = 2.0 * asin($t); - - return axis_to_quat(\@a, $phi); -} - -# Build a rotation matrix, given a quaternion rotation. -sub quat_to_rotmatrix { - my ($q) = @_; - - my @m = (); - - $m[0] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[2] * @$q[2]); - $m[1] = 2.0 * (@$q[0] * @$q[1] - @$q[2] * @$q[3]); - $m[2] = 2.0 * (@$q[2] * @$q[0] + @$q[1] * @$q[3]); - $m[3] = 0.0; - - $m[4] = 2.0 * (@$q[0] * @$q[1] + @$q[2] * @$q[3]); - $m[5] = 1.0 - 2.0 * (@$q[2] * @$q[2] + @$q[0] * @$q[0]); - $m[6] = 2.0 * (@$q[1] * @$q[2] - @$q[0] * @$q[3]); - $m[7] = 0.0; - - $m[8] = 2.0 * (@$q[2] * @$q[0] - @$q[1] * @$q[3]); - $m[9] = 2.0 * (@$q[1] * @$q[2] + @$q[0] * @$q[3]); - $m[10] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[0] * @$q[0]); - $m[11] = 0.0; - - $m[12] = 0.0; - $m[13] = 0.0; - $m[14] = 0.0; - $m[15] = 1.0; - - return @m; -} - -sub mulquats { - my ($q1, $rq) = @_; - - return (@$q1[3] * @$rq[0] + @$q1[0] * @$rq[3] + @$q1[1] * @$rq[2] - @$q1[2] * @$rq[1], - @$q1[3] * @$rq[1] + @$q1[1] * @$rq[3] + @$q1[2] * @$rq[0] - @$q1[0] * @$rq[2], - @$q1[3] * @$rq[2] + @$q1[2] * @$rq[3] + @$q1[0] * @$rq[1] - @$q1[1] * @$rq[0], - @$q1[3] * @$rq[3] - @$q1[0] * @$rq[0] - @$q1[1] * @$rq[1] - @$q1[2] * @$rq[2]) -} - -# Convert the screen space coordinate to an object space coordinate. -# If the Z screen space coordinate is not provided, a depth buffer value is substituted. -sub mouse_to_3d { - my ($self, $x, $y, $z) = @_; - - return unless $self->GetContext; - $self->SetCurrent($self->GetContext); - - my @viewport = glGetIntegerv_p(GL_VIEWPORT); # 4 items - my @mview = glGetDoublev_p(GL_MODELVIEW_MATRIX); # 16 items - my @proj = glGetDoublev_p(GL_PROJECTION_MATRIX); # 16 items - - $y = $viewport[3] - $y; - $z //= glReadPixels_p($x, $y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT); - my @projected = gluUnProject_p($x, $y, $z, @mview, @proj, @viewport); - return Slic3r::Pointf3->new(@projected); -} - -sub GetContext { - my ($self) = @_; - return $self->{context} ||= Wx::GLContext->new($self); -} - -sub SetCurrent { - my ($self, $context) = @_; - return $self->SUPER::SetCurrent($context); -} - -sub UseVBOs { - my ($self) = @_; - - if (! defined ($self->{use_VBOs})) { - my $use_legacy = wxTheApp->{app_config}->get('use_legacy_opengl'); - if ($use_legacy eq '1') { - # Disable OpenGL 2.0 rendering. - $self->{use_VBOs} = 0; - # Don't enable the layer editing tool. - $self->{layer_editing_enabled} = 0; - # 2 means failed - $self->{layer_editing_initialized} = 2; - return 0; - } - # This is a special path for wxWidgets on GTK, where an OpenGL context is initialized - # first when an OpenGL widget is shown for the first time. How ugly. - return 0 if (! $self->init && $^O eq 'linux'); - # Don't use VBOs if anything fails. - $self->{use_VBOs} = 0; - if ($self->GetContext) { - $self->SetCurrent($self->GetContext); - Slic3r::GUI::_3DScene::_glew_init; - my @gl_version = split(/\./, glGetString(GL_VERSION)); - $self->{use_VBOs} = int($gl_version[0]) >= 2; - # print "UseVBOs $self OpenGL major: $gl_version[0], minor: $gl_version[1]. Use VBOs: ", $self->{use_VBOs}, "\n"; - } - } - return $self->{use_VBOs}; -} - -sub Resize { - my ($self, $x, $y) = @_; - - return unless $self->GetContext; - $self->_dirty(0); - - $self->SetCurrent($self->GetContext); - glViewport(0, 0, $x, $y); - - $x /= $self->_zoom; - $y /= $self->_zoom; - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - if ($self->_camera_type eq 'ortho') { - #FIXME setting the size of the box 10x larger than necessary - # is only a workaround for an incorrectly set camera. - # This workaround harms Z-buffer accuracy! -# my $depth = 1.05 * $self->max_bounding_box->radius(); - my $depth = 5.0 * max(@{ $self->max_bounding_box->size }); - glOrtho( - -$x/2, $x/2, -$y/2, $y/2, - -$depth, $depth, - ); - } else { - die "Invalid camera type: ", $self->_camera_type, "\n" if ($self->_camera_type ne 'perspective'); - my $bbox_r = $self->max_bounding_box->radius(); - my $fov = PI * 45. / 180.; - my $fov_tan = tan(0.5 * $fov); - my $cam_distance = 0.5 * $bbox_r / $fov_tan; - $self->_camera_distance($cam_distance); - my $nr = $cam_distance - $bbox_r * 1.1; - my $fr = $cam_distance + $bbox_r * 1.1; - $nr = 1 if ($nr < 1); - $fr = $nr + 1 if ($fr < $nr + 1); - my $h2 = $fov_tan * $nr; - my $w2 = $h2 * $x / $y; - glFrustum(-$w2, $w2, -$h2, $h2, $nr, $fr); - } - - glMatrixMode(GL_MODELVIEW); -} - -sub InitGL { - my $self = shift; - - return if $self->init; - return unless $self->GetContext; - $self->init(1); - - # This is a special path for wxWidgets on GTK, where an OpenGL context is initialized - # first when an OpenGL widget is shown for the first time. How ugly. - # In that case the volumes are wainting to be moved to Vertex Buffer Objects - # after the OpenGL context is being initialized. - $self->volumes->finalize_geometry(1) - if ($^O eq 'linux' && $self->UseVBOs); - - $self->zoom_to_bed; - - glClearColor(0, 0, 0, 1); - glColor3f(1, 0, 0); - glEnable(GL_DEPTH_TEST); - glClearDepth(1.0); - glDepthFunc(GL_LEQUAL); - glEnable(GL_CULL_FACE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - # Set antialiasing/multisampling - glDisable(GL_LINE_SMOOTH); - glDisable(GL_POLYGON_SMOOTH); - - # See "GL_MULTISAMPLE and GL_ARRAY_BUFFER_ARB messages on failed launch" - # https://github.com/alexrj/Slic3r/issues/4085 - eval { - # Disable the multi sampling by default, so the picking by color will work correctly. - glDisable(GL_MULTISAMPLE); - }; - # Disable multi sampling if the eval failed. - $self->{can_multisample} = 0 if $@; - - # ambient lighting - glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.3, 0.3, 0.3, 1); - - glEnable(GL_LIGHTING); - glEnable(GL_LIGHT0); - glEnable(GL_LIGHT1); - - # light from camera - glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0); - glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.3, 0.3, 0.3, 1); - glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 0.2, 0.2, 0.2, 1); - - # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. - glShadeModel(GL_SMOOTH); - -# glMaterialfv_p(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 0.5, 0.3, 0.3, 1); -# glMaterialfv_p(GL_FRONT_AND_BACK, GL_SPECULAR, 1, 1, 1, 1); -# glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50); -# glMaterialfv_p(GL_FRONT_AND_BACK, GL_EMISSION, 0.1, 0, 0, 0.9); - - # A handy trick -- have surface material mirror the color. - glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); - glEnable(GL_COLOR_MATERIAL); - glEnable(GL_MULTISAMPLE) if ($self->{can_multisample}); - - if ($self->UseVBOs) { - my $shader = new Slic3r::GUI::_3DScene::GLShader; - if (! $shader->load($self->_fragment_shader_Gouraud, $self->_vertex_shader_Gouraud)) { -# if (! $shader->load($self->_fragment_shader_Phong, $self->_vertex_shader_Phong)) { - print "Compilaton of path shader failed: \n" . $shader->last_error . "\n"; - $shader = undef; - } else { - $self->{plain_shader} = $shader; - } - } -} - -sub DestroyGL { - my $self = shift; - if ($self->GetContext) { - $self->SetCurrent($self->GetContext); - if ($self->{plain_shader}) { - $self->{plain_shader}->release; - delete $self->{plain_shader}; - } - if ($self->{layer_height_edit_shader}) { - $self->{layer_height_edit_shader}->release; - delete $self->{layer_height_edit_shader}; - } - $self->volumes->release_geometry; - } -} - -sub Render { - my ($self, $dc) = @_; - - # prevent calling SetCurrent() when window is not shown yet - return unless $self->IsShownOnScreen; - return unless my $context = $self->GetContext; - $self->SetCurrent($context); - $self->InitGL; - - glClearColor(1, 1, 1, 1); - glClearDepth(1); - glDepthFunc(GL_LESS); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - if (!TURNTABLE_MODE) { - # Shift the perspective camera. - my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance); - glTranslatef(@$camera_pos); - } - - if (TURNTABLE_MODE) { - # Turntable mode is enabled by default. - glRotatef(-$self->_stheta, 1, 0, 0); # pitch - glRotatef($self->_sphi, 0, 0, 1); # yaw - } else { - my @rotmat = quat_to_rotmatrix($self->quat); - glMultMatrixd_p(@rotmat[0..15]); - } - glTranslatef(@{ $self->_camera_target->negative }); - - # light from above - glLightfv_p(GL_LIGHT0, GL_POSITION, -0.5, -0.5, 1, 0); - glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.2, 0.2, 0.2, 1); - glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.5, 0.5, 0.5, 1); - - # Head light - glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0); - - if ($self->enable_picking && !$self->_mouse_dragging) { - if (my $pos = $self->_mouse_pos) { - # Render the object for picking. - # FIXME This cannot possibly work in a multi-sampled context as the color gets mangled by the anti-aliasing. - # Better to use software ray-casting on a bounding-box hierarchy. - glPushAttrib(GL_ENABLE_BIT); - glDisable(GL_MULTISAMPLE) if ($self->{can_multisample}); - glDisable(GL_LIGHTING); - glDisable(GL_BLEND); - $self->draw_volumes(1); - glPopAttrib(); - glFlush(); - my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ]; - my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256; - $self->_hover_volume_idx(undef); - $_->set_hover(0) for @{$self->volumes}; - if ($volume_idx <= $#{$self->volumes}) { - $self->_hover_volume_idx($volume_idx); - - $self->volumes->[$volume_idx]->set_hover(1); - my $group_id = $self->volumes->[$volume_idx]->select_group_id; - if ($group_id != -1) { - $_->set_hover(1) for grep { $_->select_group_id == $group_id } @{$self->volumes}; - } - - $self->on_hover->($volume_idx) if $self->on_hover; - } - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - } - } - - # draw fixed background - if ($self->background) { - glDisable(GL_LIGHTING); - glPushMatrix(); - glLoadIdentity(); - - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glLoadIdentity(); - - # Draws a bluish bottom to top gradient over the complete screen. - glDisable(GL_DEPTH_TEST); - glBegin(GL_QUADS); - glColor3f(0.0,0.0,0.0); - glVertex3f(-1.0,-1.0, 1.0); - glVertex3f( 1.0,-1.0, 1.0); - glColor3f(10/255,98/255,144/255); - glVertex3f( 1.0, 1.0, 1.0); - glVertex3f(-1.0, 1.0, 1.0); - glEnd(); - glPopMatrix(); - glEnable(GL_DEPTH_TEST); - - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); - glEnable(GL_LIGHTING); - } - - # draw ground and axes - glDisable(GL_LIGHTING); - - # draw ground - my $ground_z = GROUND_Z; - if ($self->bed_triangles) { - glDisable(GL_DEPTH_TEST); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - glEnableClientState(GL_VERTEX_ARRAY); - glColor4f(0.8, 0.6, 0.5, 0.4); - glNormal3d(0,0,1); - glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_triangles->ptr()); - glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3); - glDisableClientState(GL_VERTEX_ARRAY); - - # we need depth test for grid, otherwise it would disappear when looking - # the object from below - glEnable(GL_DEPTH_TEST); - - # draw grid - glLineWidth(3); - glColor4f(0.2, 0.2, 0.2, 0.4); - glEnableClientState(GL_VERTEX_ARRAY); - glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_grid_lines->ptr()); - glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3); - glDisableClientState(GL_VERTEX_ARRAY); - - glDisable(GL_BLEND); - } - - my $volumes_bb = $self->volumes_bounding_box; - - { - # draw axes - # disable depth testing so that axes are not covered by ground - glDisable(GL_DEPTH_TEST); - my $origin = $self->origin; - my $axis_len = $self->use_plain_shader ? 0.3 * max(@{ $self->bed_bounding_box->size }) : 2 * max(@{ $volumes_bb->size }); - glLineWidth(2); - glBegin(GL_LINES); - # draw line for x axis - glColor3f(1, 0, 0); - glVertex3f(@$origin, $ground_z); - glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,, - # draw line for y axis - glColor3f(0, 1, 0); - glVertex3f(@$origin, $ground_z); - glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++ - glEnd(); - # draw line for Z axis - # (re-enable depth test so that axis is correctly shown when objects are behind it) - glEnable(GL_DEPTH_TEST); - glBegin(GL_LINES); - glColor3f(0, 0, 1); - glVertex3f(@$origin, $ground_z); - glVertex3f(@$origin, $ground_z+$axis_len); - glEnd(); - } - - glEnable(GL_LIGHTING); - - # draw objects - if (! $self->use_plain_shader) { - $self->draw_volumes; - } elsif ($self->UseVBOs) { - if ($self->enable_picking) { - $self->mark_volumes_for_layer_height; - $self->volumes->set_print_box($self->bed_bounding_box->x_min, $self->bed_bounding_box->y_min, 0.0, $self->bed_bounding_box->x_max, $self->bed_bounding_box->y_max, $self->{config}->get('max_print_height')); - $self->volumes->check_outside_state($self->{config}); - # do not cull backfaces to show broken geometry, if any - glDisable(GL_CULL_FACE); - } - $self->{plain_shader}->enable if $self->{plain_shader}; - $self->volumes->render_VBOs; - $self->{plain_shader}->disable; - glEnable(GL_CULL_FACE) if ($self->enable_picking); - } else { - # do not cull backfaces to show broken geometry, if any - glDisable(GL_CULL_FACE) if ($self->enable_picking); - $self->volumes->render_legacy; - glEnable(GL_CULL_FACE) if ($self->enable_picking); - } - - if (defined $self->cutting_plane_z) { - # draw cutting plane - my $plane_z = $self->cutting_plane_z; - my $bb = $volumes_bb; - glDisable(GL_CULL_FACE); - glDisable(GL_LIGHTING); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glBegin(GL_QUADS); - glColor4f(0.8, 0.8, 0.8, 0.5); - glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z); - glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z); - glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z); - glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z); - glEnd(); - glEnable(GL_CULL_FACE); - glDisable(GL_BLEND); - - # draw cutting contours - glEnableClientState(GL_VERTEX_ARRAY); - glLineWidth(2); - glColor3f(0, 0, 0); - glVertexPointer_c(3, GL_FLOAT, 0, $self->cut_lines_vertices->ptr()); - glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3); - glVertexPointer_c(3, GL_FLOAT, 0, 0); - glDisableClientState(GL_VERTEX_ARRAY); - } - - # draw warning message - $self->draw_warning; - - # draw gcode preview legend - $self->draw_legend; - - $self->draw_active_object_annotations; - - $self->SwapBuffers(); -} - -sub draw_volumes { - # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking. - my ($self, $fakecolor) = @_; - - # do not cull backfaces to show broken geometry, if any - glDisable(GL_CULL_FACE); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_NORMAL_ARRAY); - - foreach my $volume_idx (0..$#{$self->volumes}) { - my $volume = $self->volumes->[$volume_idx]; - - if ($fakecolor) { - # Object picking mode. Render the object with a color encoding the object index. - my $r = ($volume_idx & 0x000000FF) >> 0; - my $g = ($volume_idx & 0x0000FF00) >> 8; - my $b = ($volume_idx & 0x00FF0000) >> 16; - glColor4f($r/255.0, $g/255.0, $b/255.0, 1); - } elsif ($volume->selected) { - glColor4f(@{ &SELECTED_COLOR }); - } elsif ($volume->hover) { - glColor4f(@{ &HOVER_COLOR }); - } else { - glColor4f(@{ $volume->color }); - } - - $volume->render; - } - glDisableClientState(GL_NORMAL_ARRAY); - glDisableClientState(GL_VERTEX_ARRAY); - - glDisable(GL_BLEND); - glEnable(GL_CULL_FACE); -} - -sub mark_volumes_for_layer_height { - my ($self) = @_; - - foreach my $volume_idx (0..$#{$self->volumes}) { - my $volume = $self->volumes->[$volume_idx]; - my $object_id = int($volume->select_group_id / 1000000); - if ($self->layer_editing_enabled && $volume->selected && $self->{layer_height_edit_shader} && - $volume->has_layer_height_texture && $object_id < $self->{print}->object_count) { - $volume->set_layer_height_texture_data($self->{layer_preview_z_texture_id}, $self->{layer_height_edit_shader}->shader_program_id, - $self->{print}->get_object($object_id), $self->_variable_layer_thickness_bar_mouse_cursor_z_relative, $self->{layer_height_edit_band_width}); - } else { - $volume->reset_layer_height_texture_data(); - } - } -} - -sub _load_image_set_texture { - my ($self, $file_name) = @_; - # Load a PNG with an alpha channel. - my $img = Wx::Image->new; - $img->LoadFile(Slic3r::var($file_name), wxBITMAP_TYPE_PNG); - # Get RGB & alpha raw data from wxImage, interleave them into a Perl array. - my @rgb = unpack 'C*', $img->GetData(); - my @alpha = $img->HasAlpha ? unpack 'C*', $img->GetAlpha() : (255) x (int(@rgb) / 3); - my $n_pixels = int(@alpha); - my @data = (0)x($n_pixels * 4); - for (my $i = 0; $i < $n_pixels; $i += 1) { - $data[$i*4 ] = $rgb[$i*3]; - $data[$i*4+1] = $rgb[$i*3+1]; - $data[$i*4+2] = $rgb[$i*3+2]; - $data[$i*4+3] = $alpha[$i]; - } - # Initialize a raw bitmap data. - my $params = { - loaded => 1, - valid => $n_pixels > 0, - width => $img->GetWidth, - height => $img->GetHeight, - data => OpenGL::Array->new_list(GL_UNSIGNED_BYTE, @data), - texture_id => glGenTextures_p(1) - }; - # Create and initialize a texture with the raw data. - glBindTexture(GL_TEXTURE_2D, $params->{texture_id}); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); - glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $params->{width}, $params->{height}, 0, GL_RGBA, GL_UNSIGNED_BYTE, $params->{data}->ptr); - glBindTexture(GL_TEXTURE_2D, 0); - return $params; -} - -sub _variable_layer_thickness_load_overlay_image { - my ($self) = @_; - $self->{layer_preview_annotation} = $self->_load_image_set_texture('variable_layer_height_tooltip.png') - if (! $self->{layer_preview_annotation}->{loaded}); - return $self->{layer_preview_annotation}->{valid}; -} - -sub _variable_layer_thickness_load_reset_image { - my ($self) = @_; - $self->{layer_preview_reset_image} = $self->_load_image_set_texture('variable_layer_height_reset.png') - if (! $self->{layer_preview_reset_image}->{loaded}); - return $self->{layer_preview_reset_image}->{valid}; -} - -# Paint the tooltip. -sub _render_image { - my ($self, $image, $l, $r, $b, $t) = @_; - $self->_render_texture($image->{texture_id}, $l, $r, $b, $t); -} - -sub _render_texture { - my ($self, $tex_id, $l, $r, $b, $t) = @_; - - glColor4f(1.,1.,1.,1.); - glDisable(GL_LIGHTING); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, $tex_id); - glBegin(GL_QUADS); - glTexCoord2d(0.,1.); glVertex3f($l, $b, 0); - glTexCoord2d(1.,1.); glVertex3f($r, $b, 0); - glTexCoord2d(1.,0.); glVertex3f($r, $t, 0); - glTexCoord2d(0.,0.); glVertex3f($l, $t, 0); - glEnd(); - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); - glDisable(GL_BLEND); - glEnable(GL_LIGHTING); -} - -sub draw_active_object_annotations { - # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking. - my ($self) = @_; - - return if (! $self->{layer_height_edit_shader} || ! $self->layer_editing_enabled); - - # Find the selected volume, over which the layer editing is active. - my $volume; - foreach my $volume_idx (0..$#{$self->volumes}) { - my $v = $self->volumes->[$volume_idx]; - if ($v->selected && $v->has_layer_height_texture) { - $volume = $v; - last; - } - } - return if (! $volume); - - # If the active object was not allocated at the Print, go away. This should only be a momentary case between an object addition / deletion - # and an update by Platter::async_apply_config. - my $object_idx = int($volume->select_group_id / 1000000); - return if $object_idx >= $self->{print}->object_count; - - # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth), - # where x, y is the window size divided by $self->_zoom. - my ($bar_left, $bar_bottom, $bar_right, $bar_top) = $self->_variable_layer_thickness_bar_rect_viewport; - my ($reset_left, $reset_bottom, $reset_right, $reset_top) = $self->_variable_layer_thickness_reset_rect_viewport; - my $z_cursor_relative = $self->_variable_layer_thickness_bar_mouse_cursor_z_relative; - - my $print_object = $self->{print}->get_object($object_idx); - my $z_max = $print_object->model_object->bounding_box->z_max; - - $self->{layer_height_edit_shader}->enable; - $self->{layer_height_edit_shader}->set_uniform('z_to_texture_row', $volume->layer_height_texture_z_to_row_id); - $self->{layer_height_edit_shader}->set_uniform('z_texture_row_to_normalized', 1. / $volume->layer_height_texture_height); - $self->{layer_height_edit_shader}->set_uniform('z_cursor', $z_max * $z_cursor_relative); - $self->{layer_height_edit_shader}->set_uniform('z_cursor_band_width', $self->{layer_height_edit_band_width}); - glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id}); - glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $volume->layer_height_texture_width, $volume->layer_height_texture_height, - 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2, - 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - glTexSubImage2D_c(GL_TEXTURE_2D, 0, 0, 0, $volume->layer_height_texture_width, $volume->layer_height_texture_height, - GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level0); - glTexSubImage2D_c(GL_TEXTURE_2D, 1, 0, 0, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2, - GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level1); - - # Render the color bar. - glDisable(GL_DEPTH_TEST); - # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth), - # where x, y is the window size divided by $self->_zoom. - glPushMatrix(); - glLoadIdentity(); - # Paint the overlay. - glBegin(GL_QUADS); - glVertex3f($bar_left, $bar_bottom, 0); - glVertex3f($bar_right, $bar_bottom, 0); - glVertex3f($bar_right, $bar_top, $z_max); - glVertex3f($bar_left, $bar_top, $z_max); - glEnd(); - glBindTexture(GL_TEXTURE_2D, 0); - $self->{layer_height_edit_shader}->disable; - - # Paint the tooltip. - if ($self->_variable_layer_thickness_load_overlay_image) { - my $gap = 10/$self->_zoom; - my ($l, $r, $b, $t) = ($bar_left - $self->{layer_preview_annotation}->{width}/$self->_zoom - $gap, $bar_left - $gap, $reset_bottom + $self->{layer_preview_annotation}->{height}/$self->_zoom + $gap, $reset_bottom + $gap); - $self->_render_image($self->{layer_preview_annotation}, $l, $r, $t, $b); - } - - # Paint the reset button. - if ($self->_variable_layer_thickness_load_reset_image) { - $self->_render_image($self->{layer_preview_reset_image}, $reset_left, $reset_right, $reset_bottom, $reset_top); - } - - # Paint the graph. - #FIXME show some kind of legend. - my $max_z = unscale($print_object->size->z); - my $profile = $print_object->model_object->layer_height_profile; - my $layer_height = $print_object->config->get('layer_height'); - my $layer_height_max = 10000000000.; - { - # Get a maximum layer height value. - #FIXME This is a duplicate code of Slicing.cpp. - my $nozzle_diameters = $print_object->print->config->get('nozzle_diameter'); - my $layer_heights_min = $print_object->print->config->get('min_layer_height'); - my $layer_heights_max = $print_object->print->config->get('max_layer_height'); - for (my $i = 0; $i < scalar(@{$nozzle_diameters}); $i += 1) { - my $lh_min = ($layer_heights_min->[$i] == 0.) ? 0.07 : max(0.01, $layer_heights_min->[$i]); - my $lh_max = ($layer_heights_max->[$i] == 0.) ? (0.75 * $nozzle_diameters->[$i]) : $layer_heights_max->[$i]; - $layer_height_max = min($layer_height_max, max($lh_min, $lh_max)); - } - } - # Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. - $layer_height_max *= 1.12; - # Baseline - glColor3f(0., 0., 0.); - glBegin(GL_LINE_STRIP); - glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / $layer_height_max, $bar_bottom); - glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / $layer_height_max, $bar_top); - glEnd(); - # Curve - glColor3f(0., 0., 1.); - glBegin(GL_LINE_STRIP); - for (my $i = 0; $i < int(@{$profile}); $i += 2) { - my $z = $profile->[$i]; - my $h = $profile->[$i+1]; - glVertex3f($bar_left + $h * ($bar_right - $bar_left) / $layer_height_max, $bar_bottom + $z * ($bar_top - $bar_bottom) / $max_z, $z); - } - glEnd(); - # Revert the matrices. - glPopMatrix(); - glEnable(GL_DEPTH_TEST); -} - -sub draw_legend { - my ($self) = @_; - - if (!$self->_legend_enabled) { - return; - } - - # If the legend texture has not been loaded into the GPU, do it now. - my $tex_id = Slic3r::GUI::_3DScene::finalize_legend_texture; - if ($tex_id > 0) - { - my $tex_w = Slic3r::GUI::_3DScene::get_legend_texture_width; - my $tex_h = Slic3r::GUI::_3DScene::get_legend_texture_height; - if (($tex_w > 0) && ($tex_h > 0)) - { - glDisable(GL_DEPTH_TEST); - glPushMatrix(); - glLoadIdentity(); - - my ($cw, $ch) = $self->GetSizeWH; - - my $l = (-0.5 * $cw) / $self->_zoom; - my $t = (0.5 * $ch) / $self->_zoom; - my $r = $l + $tex_w / $self->_zoom; - my $b = $t - $tex_h / $self->_zoom; - $self->_render_texture($tex_id, $l, $r, $b, $t); - - glPopMatrix(); - glEnable(GL_DEPTH_TEST); - } - } -} - -sub draw_warning { - my ($self) = @_; - - if (!$self->_warning_enabled) { - return; - } - - # If the warning texture has not been loaded into the GPU, do it now. - my $tex_id = Slic3r::GUI::_3DScene::finalize_warning_texture; - if ($tex_id > 0) - { - my $tex_w = Slic3r::GUI::_3DScene::get_warning_texture_width; - my $tex_h = Slic3r::GUI::_3DScene::get_warning_texture_height; - if (($tex_w > 0) && ($tex_h > 0)) - { - glDisable(GL_DEPTH_TEST); - glPushMatrix(); - glLoadIdentity(); - - my ($cw, $ch) = $self->GetSizeWH; - - my $l = (-0.5 * $tex_w) / $self->_zoom; - my $t = (-0.5 * $ch + $tex_h) / $self->_zoom; - my $r = $l + $tex_w / $self->_zoom; - my $b = $t - $tex_h / $self->_zoom; - $self->_render_texture($tex_id, $l, $r, $b, $t); - - glPopMatrix(); - glEnable(GL_DEPTH_TEST); - } - } -} - -sub update_volumes_colors_by_extruder { - my ($self, $config) = @_; - $self->volumes->update_colors_by_extruder($config); -} - -sub opengl_info -{ - my ($self, %params) = @_; - my %tag = Slic3r::tags($params{format}); - - my $gl_version = glGetString(GL_VERSION); - my $gl_vendor = glGetString(GL_VENDOR); - my $gl_renderer = glGetString(GL_RENDERER); - my $glsl_version = glGetString(GL_SHADING_LANGUAGE_VERSION); - - my $out = ''; - $out .= "$tag{h2start}OpenGL installation$tag{h2end}$tag{eol}"; - $out .= " $tag{bstart}Using POGL$tag{bend} v$OpenGL::BUILD_VERSION$tag{eol}"; - $out .= " $tag{bstart}GL version: $tag{bend}${gl_version}$tag{eol}"; - $out .= " $tag{bstart}vendor: $tag{bend}${gl_vendor}$tag{eol}"; - $out .= " $tag{bstart}renderer: $tag{bend}${gl_renderer}$tag{eol}"; - $out .= " $tag{bstart}GLSL version: $tag{bend}${glsl_version}$tag{eol}"; - - # Check for other OpenGL extensions - $out .= "$tag{h2start}Installed extensions (* implemented in the module):$tag{h2end}$tag{eol}"; - my $extensions = glGetString(GL_EXTENSIONS); - my @extensions = split(' ',$extensions); - foreach my $ext (sort @extensions) { - my $stat = glpCheckExtension($ext); - $out .= sprintf("%s ${ext}$tag{eol}", $stat?' ':'*'); - $out .= sprintf(" ${stat}$tag{eol}") if ($stat && $stat !~ m|^$ext |); - } - - return $out; -} - -sub _report_opengl_state -{ - my ($self, $comment) = @_; - my $err = glGetError(); - return 0 if ($err == 0); - - # gluErrorString() hangs. Don't use it. -# my $errorstr = gluErrorString(); - my $errorstr = ''; - if ($err == 0x0500) { - $errorstr = 'GL_INVALID_ENUM'; - } elsif ($err == GL_INVALID_VALUE) { - $errorstr = 'GL_INVALID_VALUE'; - } elsif ($err == GL_INVALID_OPERATION) { - $errorstr = 'GL_INVALID_OPERATION'; - } elsif ($err == GL_STACK_OVERFLOW) { - $errorstr = 'GL_STACK_OVERFLOW'; - } elsif ($err == GL_OUT_OF_MEMORY) { - $errorstr = 'GL_OUT_OF_MEMORY'; - } else { - $errorstr = 'unknown'; - } - if (defined($comment)) { - printf("OpenGL error at %s, nr %d (0x%x): %s\n", $comment, $err, $err, $errorstr); - } else { - printf("OpenGL error nr %d (0x%x): %s\n", $err, $err, $errorstr); - } -} - -sub _vertex_shader_Gouraud { - return <<'VERTEX'; -#version 110 - -#define INTENSITY_CORRECTION 0.6 - -// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) -const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); -#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SHININESS 20.0 - -// normalized values for (1./1.43, 0.2/1.43, 1./1.43) -const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); -#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) -//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION) -//#define LIGHT_FRONT_SHININESS 5.0 - -#define INTENSITY_AMBIENT 0.3 - -const vec3 ZERO = vec3(0.0, 0.0, 0.0); - -struct PrintBoxDetection -{ - vec3 min; - vec3 max; - // xyz contains the offset, if w == 1.0 detection needs to be performed - vec4 volume_origin; -}; - -uniform PrintBoxDetection print_box; - -// x = tainted, y = specular; -varying vec2 intensity; - -varying vec3 delta_box_min; -varying vec3 delta_box_max; - -void main() -{ - // First transform the normal into camera space and normalize the result. - vec3 normal = normalize(gl_NormalMatrix * gl_Normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); - - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - intensity.y = 0.0; - - if (NdotL > 0.0) - intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - - // compute deltas for out of print volume detection (world coordinates) - if (print_box.volume_origin.w == 1.0) - { - vec3 v = gl_Vertex.xyz + print_box.volume_origin.xyz; - delta_box_min = v - print_box.min; - delta_box_max = v - print_box.max; - } - else - { - delta_box_min = ZERO; - delta_box_max = ZERO; - } - - gl_Position = ftransform(); -} - -VERTEX -} - -sub _fragment_shader_Gouraud { - return <<'FRAGMENT'; -#version 110 - -const vec3 ZERO = vec3(0.0, 0.0, 0.0); - -// x = tainted, y = specular; -varying vec2 intensity; - -varying vec3 delta_box_min; -varying vec3 delta_box_max; - -uniform vec4 uniform_color; - -void main() -{ - // if the fragment is outside the print volume -> use darker color - vec3 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(uniform_color.rgb, ZERO, 0.3333) : uniform_color.rgb; - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + color * intensity.x, uniform_color.a); -} - -FRAGMENT -} - -sub _vertex_shader_Phong { - return <<'VERTEX'; -#version 110 - -varying vec3 normal; -varying vec3 eye; -void main(void) -{ - eye = normalize(vec3(gl_ModelViewMatrix * gl_Vertex)); - normal = normalize(gl_NormalMatrix * gl_Normal); - gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; -} -VERTEX -} - -sub _fragment_shader_Phong { - return <<'FRAGMENT'; -#version 110 - -#define INTENSITY_CORRECTION 0.7 - -#define LIGHT_TOP_DIR -0.6/1.31, 0.6/1.31, 1./1.31 -#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.5 * INTENSITY_CORRECTION) -//#define LIGHT_TOP_SHININESS 50. -#define LIGHT_TOP_SHININESS 10. - -#define LIGHT_FRONT_DIR 1./1.43, 0.2/1.43, 1./1.43 -#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) -#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION) -#define LIGHT_FRONT_SHININESS 50. - -#define INTENSITY_AMBIENT 0.0 - -varying vec3 normal; -varying vec3 eye; -uniform vec4 uniform_color; -void main() { - - float intensity_specular = 0.; - float intensity_tainted = 0.; - float intensity = max(dot(normal,vec3(LIGHT_TOP_DIR)), 0.0); - // if the vertex is lit compute the specular color - if (intensity > 0.0) { - intensity_tainted = LIGHT_TOP_DIFFUSE * intensity; - // compute the half vector - vec3 h = normalize(vec3(LIGHT_TOP_DIR) + eye); - // compute the specular term into spec - intensity_specular = LIGHT_TOP_SPECULAR * pow(max(dot(h, normal), 0.0), LIGHT_TOP_SHININESS); - } - intensity = max(dot(normal,vec3(LIGHT_FRONT_DIR)), 0.0); - // if the vertex is lit compute the specular color - if (intensity > 0.0) { - intensity_tainted += LIGHT_FRONT_DIFFUSE * intensity; - // compute the half vector -// vec3 h = normalize(vec3(LIGHT_FRONT_DIR) + eye); - // compute the specular term into spec -// intensity_specular += LIGHT_FRONT_SPECULAR * pow(max(dot(h,normal), 0.0), LIGHT_FRONT_SHININESS); - } - - gl_FragColor = max( - vec4(intensity_specular, intensity_specular, intensity_specular, 0.) + uniform_color * intensity_tainted, - INTENSITY_AMBIENT * uniform_color); - gl_FragColor.a = uniform_color.a; -} -FRAGMENT -} - -sub _vertex_shader_variable_layer_height { - return <<'VERTEX'; -#version 110 - -#define INTENSITY_CORRECTION 0.6 - -const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); -#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SHININESS 20.0 - -const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); -#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) -//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION) -//#define LIGHT_FRONT_SHININESS 5.0 - -#define INTENSITY_AMBIENT 0.3 - -// x = tainted, y = specular; -varying vec2 intensity; - -varying float object_z; - -void main() -{ - // First transform the normal into camera space and normalize the result. - vec3 normal = normalize(gl_NormalMatrix * gl_Normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); - - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - intensity.y = 0.0; - - if (NdotL > 0.0) - intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular) - NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); - - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - - // Scaled to widths of the Z texture. - object_z = gl_Vertex.z; - - gl_Position = ftransform(); -} - -VERTEX -} - -sub _fragment_shader_variable_layer_height { - return <<'FRAGMENT'; -#version 110 - -#define M_PI 3.1415926535897932384626433832795 - -// 2D texture (1D texture split by the rows) of color along the object Z axis. -uniform sampler2D z_texture; -// Scaling from the Z texture rows coordinate to the normalized texture row coordinate. -uniform float z_to_texture_row; -uniform float z_texture_row_to_normalized; -uniform float z_cursor; -uniform float z_cursor_band_width; - -// x = tainted, y = specular; -varying vec2 intensity; - -varying float object_z; - -void main() -{ - float object_z_row = z_to_texture_row * object_z; - // Index of the row in the texture. - float z_texture_row = floor(object_z_row); - // Normalized coordinate from 0. to 1. - float z_texture_col = object_z_row - z_texture_row; - float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25; - // Calculate level of detail from the object Z coordinate. - // This makes the slowly sloping surfaces to be show with high detail (with stripes), - // and the vertical surfaces to be shown with low detail (no stripes) - float z_in_cells = object_z_row * 190.; - // Gradient of Z projected on the screen. - float dx_vtc = dFdx(z_in_cells); - float dy_vtc = dFdy(z_in_cells); - float lod = clamp(0.5 * log2(max(dx_vtc*dx_vtc, dy_vtc*dy_vtc)), 0., 1.); - // Sample the Z texture. Texture coordinates are normalized to <0, 1>. - vec4 color = - mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.), - texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod); - - // Mix the final color. - gl_FragColor = - vec4(intensity.y, intensity.y, intensity.y, 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend); -} - -FRAGMENT -} +#============================================================================================================================== +#sub layer_editing_enabled { +# my ($self, $value) = @_; +# if (@_ == 2) { +# $self->{layer_editing_enabled} = $value; +# if ($value) { +# if (! $self->{layer_editing_initialized}) { +# # Enabling the layer editing for the first time. This triggers compilation of the necessary OpenGL shaders. +# # If compilation fails, a message box is shown with the error codes. +# $self->SetCurrent($self->GetContext); +# my $shader = new Slic3r::GUI::_3DScene::GLShader; +# my $error_message; +# if (! $shader->load_from_text($self->_fragment_shader_variable_layer_height, $self->_vertex_shader_variable_layer_height)) { +# # Compilation or linking of the shaders failed. +# $error_message = "Cannot compile an OpenGL Shader, therefore the Variable Layer Editing will be disabled.\n\n" +# . $shader->last_error; +# $shader = undef; +# } else { +# $self->{layer_height_edit_shader} = $shader; +# ($self->{layer_preview_z_texture_id}) = glGenTextures_p(1); +# glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id}); +# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); +# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); +# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); +# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); +# glBindTexture(GL_TEXTURE_2D, 0); +# } +# if (defined($error_message)) { +# # Don't enable the layer editing tool. +# $self->{layer_editing_enabled} = 0; +# # 2 means failed +# $self->{layer_editing_initialized} = 2; +# # Show the error message. +# Wx::MessageBox($error_message, "Slic3r Error", wxOK | wxICON_EXCLAMATION, $self); +# } else { +# $self->{layer_editing_initialized} = 1; +# } +# } elsif ($self->{layer_editing_initialized} == 2) { +# # Initilization failed before. Don't try to initialize and disable layer editing. +# $self->{layer_editing_enabled} = 0; +# } +# } +# } +# return $self->{layer_editing_enabled}; +#} +# +#sub layer_editing_allowed { +# my ($self) = @_; +# # Allow layer editing if either the shaders were not initialized yet and we don't know +# # whether it will be possible to initialize them, +# # or if the initialization was done already and it failed. +# return ! (defined($self->{layer_editing_initialized}) && $self->{layer_editing_initialized} == 2); +#} +# +#sub _first_selected_object_id_for_variable_layer_height_editing { +# my ($self) = @_; +# for my $i (0..$#{$self->volumes}) { +# if ($self->volumes->[$i]->selected) { +# my $object_id = int($self->volumes->[$i]->select_group_id / 1000000); +# # Objects with object_id >= 1000 have a specific meaning, for example the wipe tower proxy. +# return ($object_id >= $self->{print}->object_count) ? -1 : $object_id +# if $object_id < 10000; +# } +# } +# return -1; +#} +# +## Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen. +#sub _variable_layer_thickness_bar_rect_screen { +# my ($self) = @_; +# my ($cw, $ch) = $self->GetSizeWH; +# return ($cw - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, 0, $cw, $ch - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT); +#} +# +#sub _variable_layer_thickness_bar_rect_viewport { +# my ($self) = @_; +# my ($cw, $ch) = $self->GetSizeWH; +# return ((0.5*$cw-VARIABLE_LAYER_THICKNESS_BAR_WIDTH)/$self->_zoom, (-0.5*$ch+VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT)/$self->_zoom, $cw/(2*$self->_zoom), $ch/(2*$self->_zoom)); +#} +# +## Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen. +#sub _variable_layer_thickness_reset_rect_screen { +# my ($self) = @_; +# my ($cw, $ch) = $self->GetSizeWH; +# return ($cw - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, $ch - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT, $cw, $ch); +#} +# +#sub _variable_layer_thickness_reset_rect_viewport { +# my ($self) = @_; +# my ($cw, $ch) = $self->GetSizeWH; +# return ((0.5*$cw-VARIABLE_LAYER_THICKNESS_BAR_WIDTH)/$self->_zoom, -$ch/(2*$self->_zoom), $cw/(2*$self->_zoom), (-0.5*$ch+VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT)/$self->_zoom); +#} +# +#sub _variable_layer_thickness_bar_rect_mouse_inside { +# my ($self, $mouse_evt) = @_; +# my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen; +# return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom; +#} +# +#sub _variable_layer_thickness_reset_rect_mouse_inside { +# my ($self, $mouse_evt) = @_; +# my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_reset_rect_screen; +# return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom; +#} +# +#sub _variable_layer_thickness_bar_mouse_cursor_z_relative { +# my ($self) = @_; +# my $mouse_pos = $self->ScreenToClientPoint(Wx::GetMousePosition()); +# my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen; +# return ($mouse_pos->x >= $bar_left && $mouse_pos->x <= $bar_right && $mouse_pos->y >= $bar_top && $mouse_pos->y <= $bar_bottom) ? +# # Inside the bar. +# ($bar_bottom - $mouse_pos->y - 1.) / ($bar_bottom - $bar_top - 1) : +# # Outside the bar. +# -1000.; +#} +# +#sub _variable_layer_thickness_action { +# my ($self, $mouse_event, $do_modification) = @_; +# # A volume is selected. Test, whether hovering over a layer thickness bar. +# return if $self->{layer_height_edit_last_object_id} == -1; +# if (defined($mouse_event)) { +# my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen; +# $self->{layer_height_edit_last_z} = unscale($self->{print}->get_object($self->{layer_height_edit_last_object_id})->size->z) +# * ($bar_bottom - $mouse_event->GetY - 1.) / ($bar_bottom - $bar_top); +# $self->{layer_height_edit_last_action} = $mouse_event->ShiftDown ? ($mouse_event->RightIsDown ? 3 : 2) : ($mouse_event->RightIsDown ? 0 : 1); +# } +# # Mark the volume as modified, so Print will pick its layer height profile? Where to mark it? +# # Start a timer to refresh the print? schedule_background_process() ? +# # The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself. +# $self->{print}->get_object($self->{layer_height_edit_last_object_id})->adjust_layer_height_profile( +# $self->{layer_height_edit_last_z}, +# $self->{layer_height_edit_strength}, +# $self->{layer_height_edit_band_width}, +# $self->{layer_height_edit_last_action}); +# +# $self->volumes->[$self->{layer_height_edit_last_object_id}]->generate_layer_height_texture( +# $self->{print}->get_object($self->{layer_height_edit_last_object_id}), 1); +# $self->Refresh; +# # Automatic action on mouse down with the same coordinate. +# $self->{layer_height_edit_timer}->Start(100, wxTIMER_CONTINUOUS); +#} +# +#sub mouse_event { +# my ($self, $e) = @_; +# +# my $pos = Slic3r::Pointf->new($e->GetPositionXY); +# my $object_idx_selected = $self->{layer_height_edit_last_object_id} = ($self->layer_editing_enabled && $self->{print}) ? $self->_first_selected_object_id_for_variable_layer_height_editing : -1; +# +# $self->_mouse_dragging($e->Dragging); +# +# if ($e->Entering && (&Wx::wxMSW || $^O eq 'linux')) { +# # wxMSW needs focus in order to catch mouse wheel events +# $self->SetFocus; +# $self->_drag_start_xy(undef); +# } elsif ($e->LeftDClick) { +# if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) { +# } elsif ($self->on_double_click) { +# $self->on_double_click->(); +# } +# } elsif ($e->LeftDown || $e->RightDown) { +# # If user pressed left or right button we first check whether this happened +# # on a volume or not. +# my $volume_idx = $self->_hover_volume_idx // -1; +# $self->_layer_height_edited(0); +# if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) { +# # A volume is selected and the mouse is hovering over a layer thickness bar. +# # Start editing the layer height. +# $self->_layer_height_edited(1); +# $self->_variable_layer_thickness_action($e); +# } elsif ($object_idx_selected != -1 && $self->_variable_layer_thickness_reset_rect_mouse_inside($e)) { +# $self->{print}->get_object($object_idx_selected)->reset_layer_height_profile; +# # Index 2 means no editing, just wait for mouse up event. +# $self->_layer_height_edited(2); +# $self->Refresh; +# $self->Update; +# } else { +# # The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate $pos->x,y, +# # an converts the screen space coordinate to unscaled object space. +# my $pos3d = ($volume_idx == -1) ? undef : $self->mouse_to_3d(@$pos); +# +# # Select volume in this 3D canvas. +# # Don't deselect a volume if layer editing is enabled. We want the object to stay selected +# # during the scene manipulation. +# +# if ($self->enable_picking && ($volume_idx != -1 || ! $self->layer_editing_enabled)) { +# $self->deselect_volumes; +# $self->select_volume($volume_idx); +# +# if ($volume_idx != -1) { +# my $group_id = $self->volumes->[$volume_idx]->select_group_id; +# my @volumes; +# if ($group_id != -1) { +# $self->select_volume($_) +# for grep $self->volumes->[$_]->select_group_id == $group_id, +# 0..$#{$self->volumes}; +# } +# } +# +# $self->Refresh; +# $self->Update; +# } +# +# # propagate event through callback +# $self->on_select->($volume_idx) +# if $self->on_select; +# +# if ($volume_idx != -1) { +# if ($e->LeftDown && $self->enable_moving) { +# # Only accept the initial position, if it is inside the volume bounding box. +# my $volume_bbox = $self->volumes->[$volume_idx]->transformed_bounding_box; +# $volume_bbox->offset(1.); +# if ($volume_bbox->contains_point($pos3d)) { +# # The dragging operation is initiated. +# $self->_drag_volume_idx($volume_idx); +# $self->_drag_start_pos($pos3d); +# # Remember the shift to to the object center. The object center will later be used +# # to limit the object placement close to the bed. +# $self->_drag_volume_center_offset($pos3d->vector_to($volume_bbox->center)); +# } +# } elsif ($e->RightDown) { +# # if right clicking on volume, propagate event through callback +# $self->on_right_click->($e->GetPosition) +# if $self->on_right_click; +# } +# } +# } +# } elsif ($e->Dragging && $e->LeftIsDown && ! $self->_layer_height_edited && defined($self->_drag_volume_idx)) { +# # Get new position at the same Z of the initial click point. +# my $cur_pos = Slic3r::Linef3->new( +# $self->mouse_to_3d($e->GetX, $e->GetY, 0), +# $self->mouse_to_3d($e->GetX, $e->GetY, 1)) +# ->intersect_plane($self->_drag_start_pos->z); +# +# # Clip the new position, so the object center remains close to the bed. +# { +# $cur_pos->translate(@{$self->_drag_volume_center_offset}); +# my $cur_pos2 = Slic3r::Point->new(scale($cur_pos->x), scale($cur_pos->y)); +# if (! $self->bed_polygon->contains_point($cur_pos2)) { +# my $ip = $self->bed_polygon->point_projection($cur_pos2); +# $cur_pos->set_x(unscale($ip->x)); +# $cur_pos->set_y(unscale($ip->y)); +# } +# $cur_pos->translate(@{$self->_drag_volume_center_offset->negative}); +# } +# # Calculate the translation vector. +# my $vector = $self->_drag_start_pos->vector_to($cur_pos); +# # Get the volume being dragged. +# my $volume = $self->volumes->[$self->_drag_volume_idx]; +# # Get all volumes belonging to the same group, if any. +# my @volumes = ($volume->drag_group_id == -1) ? +# ($volume) : +# grep $_->drag_group_id == $volume->drag_group_id, @{$self->volumes}; +# # Apply new temporary volume origin and ignore Z. +# $_->translate($vector->x, $vector->y, 0) for @volumes; +# $self->_drag_start_pos($cur_pos); +# $self->_dragged(1); +# $self->Refresh; +# $self->Update; +# } elsif ($e->Dragging) { +# if ($self->_layer_height_edited && $object_idx_selected != -1) { +# $self->_variable_layer_thickness_action($e) if ($self->_layer_height_edited == 1); +# } elsif ($e->LeftIsDown) { +# # if dragging over blank area with left button, rotate +# if (defined $self->_drag_start_pos) { +# my $orig = $self->_drag_start_pos; +# if (TURNTABLE_MODE) { +# # Turntable mode is enabled by default. +# $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE); +# $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #- +# $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_LOCK_THETA_MAX; +# $self->_stheta(0) if $self->_stheta < 0; +# } else { +# my $size = $self->GetClientSize; +# my @quat = trackball( +# $orig->x / ($size->width / 2) - 1, +# 1 - $orig->y / ($size->height / 2), #/ +# $pos->x / ($size->width / 2) - 1, +# 1 - $pos->y / ($size->height / 2), #/ +# ); +# $self->_quat(mulquats($self->_quat, \@quat)); +# } +# $self->on_viewport_changed->() if $self->on_viewport_changed; +# $self->Refresh; +# $self->Update; +# } +# $self->_drag_start_pos($pos); +# } elsif ($e->MiddleIsDown || $e->RightIsDown) { +# # If dragging over blank area with right button, pan. +# if (defined $self->_drag_start_xy) { +# # get point in model space at Z = 0 +# my $cur_pos = $self->mouse_to_3d($e->GetX, $e->GetY, 0); +# my $orig = $self->mouse_to_3d($self->_drag_start_xy->x, $self->_drag_start_xy->y, 0); +# $self->_camera_target->translate(@{$orig->vector_to($cur_pos)->negative}); +# $self->on_viewport_changed->() if $self->on_viewport_changed; +# $self->Refresh; +# $self->Update; +# } +# $self->_drag_start_xy($pos); +# } +# } elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) { +# if ($self->_layer_height_edited) { +# $self->_layer_height_edited(undef); +# $self->{layer_height_edit_timer}->Stop; +# $self->on_model_update->() +# if ($object_idx_selected != -1 && $self->on_model_update); +# } elsif ($self->on_move && defined($self->_drag_volume_idx) && $self->_dragged) { +# # get all volumes belonging to the same group, if any +# my @volume_idxs; +# my $group_id = $self->volumes->[$self->_drag_volume_idx]->drag_group_id; +# if ($group_id == -1) { +# @volume_idxs = ($self->_drag_volume_idx); +# } else { +# @volume_idxs = grep $self->volumes->[$_]->drag_group_id == $group_id, +# 0..$#{$self->volumes}; +# } +# $self->on_move->(@volume_idxs); +# } +# $self->_drag_volume_idx(undef); +# $self->_drag_start_pos(undef); +# $self->_drag_start_xy(undef); +# $self->_dragged(undef); +# } elsif ($e->Moving) { +# $self->_mouse_pos($pos); +# # Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor +# # hovers over. +# if ($self->enable_picking) { +# $self->Update; +# $self->Refresh; +# } +# } else { +# $e->Skip(); +# } +#} +# +#sub mouse_wheel_event { +# my ($self, $e) = @_; +# +# if ($e->MiddleIsDown) { +# # Ignore the wheel events if the middle button is pressed. +# return; +# } +# if ($self->layer_editing_enabled && $self->{print}) { +# my $object_idx_selected = $self->_first_selected_object_id_for_variable_layer_height_editing; +# if ($object_idx_selected != -1) { +# # A volume is selected. Test, whether hovering over a layer thickness bar. +# if ($self->_variable_layer_thickness_bar_rect_mouse_inside($e)) { +# # Adjust the width of the selection. +# $self->{layer_height_edit_band_width} = max(min($self->{layer_height_edit_band_width} * (1 + 0.1 * $e->GetWheelRotation() / $e->GetWheelDelta()), 10.), 1.5); +# $self->Refresh; +# return; +# } +# } +# } +# +# # Calculate the zoom delta and apply it to the current zoom factor +# my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta(); +# $zoom = max(min($zoom, 4), -4); +# $zoom /= 10; +# $zoom = $self->_zoom / (1-$zoom); +# # Don't allow to zoom too far outside the scene. +# my $zoom_min = $self->get_zoom_to_bounding_box_factor($self->max_bounding_box); +# $zoom_min *= 0.4 if defined $zoom_min; +# $zoom = $zoom_min if defined $zoom_min && $zoom < $zoom_min; +# $self->_zoom($zoom); +# +## # In order to zoom around the mouse point we need to translate +## # the camera target +## my $size = Slic3r::Pointf->new($self->GetSizeWH); +## my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #- +## $self->_camera_target->translate( +## # ($pos - $size/2) represents the vector from the viewport center +## # to the mouse point. By multiplying it by $zoom we get the new, +## # transformed, length of such vector. +## # Since we want that point to stay fixed, we move our camera target +## # in the opposite direction by the delta of the length of such vector +## # ($zoom - 1). We then scale everything by 1/$self->_zoom since +## # $self->_camera_target is expressed in terms of model units. +## -($pos->x - $size->x/2) * ($zoom) / $self->_zoom, +## -($pos->y - $size->y/2) * ($zoom) / $self->_zoom, +## 0, +## ) if 0; +# +# $self->on_viewport_changed->() if $self->on_viewport_changed; +# $self->Resize($self->GetSizeWH) if $self->IsShownOnScreen; +# $self->Refresh; +#} +# +## Reset selection. +#sub reset_objects { +# my ($self) = @_; +# if ($self->GetContext) { +# $self->SetCurrent($self->GetContext); +# $self->volumes->release_geometry; +# } +# $self->volumes->erase; +# $self->_dirty(1); +#} +# +## Setup camera to view all objects. +#sub set_viewport_from_scene { +# my ($self, $scene) = @_; +# +# $self->_sphi($scene->_sphi); +# $self->_stheta($scene->_stheta); +# $self->_camera_target($scene->_camera_target); +# $self->_zoom($scene->_zoom); +# $self->_quat($scene->_quat); +# $self->_dirty(1); +#} +# +## Set the camera to a default orientation, +## zoom to volumes. +#sub select_view { +# my ($self, $direction) = @_; +# +# my $dirvec; +# if (ref($direction)) { +# $dirvec = $direction; +# } else { +# if ($direction eq 'iso') { +# $dirvec = VIEW_DEFAULT; +# } elsif ($direction eq 'left') { +# $dirvec = VIEW_LEFT; +# } elsif ($direction eq 'right') { +# $dirvec = VIEW_RIGHT; +# } elsif ($direction eq 'top') { +# $dirvec = VIEW_TOP; +# } elsif ($direction eq 'bottom') { +# $dirvec = VIEW_BOTTOM; +# } elsif ($direction eq 'front') { +# $dirvec = VIEW_FRONT; +# } elsif ($direction eq 'rear') { +# $dirvec = VIEW_REAR; +# } +# } +# my $bb = $self->volumes_bounding_box; +# if (! $bb->empty) { +# $self->_sphi($dirvec->[0]); +# $self->_stheta($dirvec->[1]); +# # Avoid gimball lock. +# $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_LOCK_THETA_MAX; +# $self->_stheta(0) if $self->_stheta < 0; +# $self->on_viewport_changed->() if $self->on_viewport_changed; +# $self->Refresh; +# } +#} +# +#sub get_zoom_to_bounding_box_factor { +# my ($self, $bb) = @_; +# my $max_bb_size = max(@{ $bb->size }); +# return undef if ($max_bb_size == 0); +# +# # project the bbox vertices on a plane perpendicular to the camera forward axis +# # then calculates the vertices coordinate on this plane along the camera xy axes +# +# # we need the view matrix, we let opengl calculate it (same as done in render sub) +# glMatrixMode(GL_MODELVIEW); +# glLoadIdentity(); +# +# if (!TURNTABLE_MODE) { +# # Shift the perspective camera. +# my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance); +# glTranslatef(@$camera_pos); +# } +# +# if (TURNTABLE_MODE) { +# # Turntable mode is enabled by default. +# glRotatef(-$self->_stheta, 1, 0, 0); # pitch +# glRotatef($self->_sphi, 0, 0, 1); # yaw +# } else { +# # Shift the perspective camera. +# my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance); +# glTranslatef(@$camera_pos); +# my @rotmat = quat_to_rotmatrix($self->quat); +# glMultMatrixd_p(@rotmat[0..15]); +# } +# glTranslatef(@{ $self->_camera_target->negative }); +# +# # get the view matrix back from opengl +# my @matrix = glGetFloatv_p(GL_MODELVIEW_MATRIX); +# +# # camera axes +# my $right = Slic3r::Pointf3->new($matrix[0], $matrix[4], $matrix[8]); +# my $up = Slic3r::Pointf3->new($matrix[1], $matrix[5], $matrix[9]); +# my $forward = Slic3r::Pointf3->new($matrix[2], $matrix[6], $matrix[10]); +# +# my $bb_min = $bb->min_point(); +# my $bb_max = $bb->max_point(); +# my $bb_center = $bb->center(); +# +# # bbox vertices in world space +# my @vertices = (); +# push(@vertices, $bb_min); +# push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_min->y(), $bb_min->z())); +# push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_max->y(), $bb_min->z())); +# push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_max->y(), $bb_min->z())); +# push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_min->y(), $bb_max->z())); +# push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_min->y(), $bb_max->z())); +# push(@vertices, $bb_max); +# push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_max->y(), $bb_max->z())); +# +# my $max_x = 0.0; +# my $max_y = 0.0; +# +# # margin factor to give some empty space around the bbox +# my $margin_factor = 1.25; +# +# foreach my $v (@vertices) { +# # project vertex on the plane perpendicular to camera forward axis +# my $pos = Slic3r::Pointf3->new($v->x() - $bb_center->x(), $v->y() - $bb_center->y(), $v->z() - $bb_center->z()); +# my $proj_on_normal = $pos->x() * $forward->x() + $pos->y() * $forward->y() + $pos->z() * $forward->z(); +# my $proj_on_plane = Slic3r::Pointf3->new($pos->x() - $proj_on_normal * $forward->x(), $pos->y() - $proj_on_normal * $forward->y(), $pos->z() - $proj_on_normal * $forward->z()); +# +# # calculates vertex coordinate along camera xy axes +# my $x_on_plane = $proj_on_plane->x() * $right->x() + $proj_on_plane->y() * $right->y() + $proj_on_plane->z() * $right->z(); +# my $y_on_plane = $proj_on_plane->x() * $up->x() + $proj_on_plane->y() * $up->y() + $proj_on_plane->z() * $up->z(); +# +# $max_x = max($max_x, $margin_factor * 2 * abs($x_on_plane)); +# $max_y = max($max_y, $margin_factor * 2 * abs($y_on_plane)); +# } +# +# return undef if (($max_x == 0) || ($max_y == 0)); +# +# my ($cw, $ch) = $self->GetSizeWH; +# my $min_ratio = min($cw / $max_x, $ch / $max_y); +# +# return $min_ratio; +#} +# +#sub zoom_to_bounding_box { +# my ($self, $bb) = @_; +# # Calculate the zoom factor needed to adjust viewport to bounding box. +# my $zoom = $self->get_zoom_to_bounding_box_factor($bb); +# if (defined $zoom) { +# $self->_zoom($zoom); +# # center view around bounding box center +# $self->_camera_target($bb->center); +# $self->on_viewport_changed->() if $self->on_viewport_changed; +# $self->Resize($self->GetSizeWH) if $self->IsShownOnScreen; +# $self->Refresh; +# } +#} +# +#sub zoom_to_bed { +# my ($self) = @_; +# +# if ($self->bed_shape) { +# $self->zoom_to_bounding_box($self->bed_bounding_box); +# } +#} +# +#sub zoom_to_volume { +# my ($self, $volume_idx) = @_; +# +# my $volume = $self->volumes->[$volume_idx]; +# my $bb = $volume->transformed_bounding_box; +# $self->zoom_to_bounding_box($bb); +#} +# +#sub zoom_to_volumes { +# my ($self) = @_; +# +# $self->_apply_zoom_to_volumes_filter(1); +# $self->zoom_to_bounding_box($self->volumes_bounding_box); +# $self->_apply_zoom_to_volumes_filter(0); +#} +# +#sub volumes_bounding_box { +# my ($self) = @_; +# +# my $bb = Slic3r::Geometry::BoundingBoxf3->new; +# foreach my $v (@{$self->volumes}) { +# $bb->merge($v->transformed_bounding_box) if (! $self->_apply_zoom_to_volumes_filter || $v->zoom_to_volumes); +# } +# return $bb; +#} +# +#sub bed_bounding_box { +# my ($self) = @_; +# +# my $bb = Slic3r::Geometry::BoundingBoxf3->new; +# if ($self->bed_shape) { +# $bb->merge_point(Slic3r::Pointf3->new(@$_, 0)) for @{$self->bed_shape}; +# } +# return $bb; +#} +# +#sub max_bounding_box { +# my ($self) = @_; +# +# my $bb = $self->bed_bounding_box; +# $bb->merge($self->volumes_bounding_box); +# return $bb; +#} +# +## Used by ObjectCutDialog and ObjectPartsPanel to generate a rectangular ground plane +## to support the scene objects. +#sub set_auto_bed_shape { +# my ($self, $bed_shape) = @_; +# +# # draw a default square bed around object center +# my $max_size = max(@{ $self->volumes_bounding_box->size }); +# my $center = $self->volumes_bounding_box->center; +# $self->set_bed_shape([ +# [ $center->x - $max_size, $center->y - $max_size ], #-- +# [ $center->x + $max_size, $center->y - $max_size ], #-- +# [ $center->x + $max_size, $center->y + $max_size ], #++ +# [ $center->x - $max_size, $center->y + $max_size ], #++ +# ]); +# # Set the origin for painting of the coordinate system axes. +# $self->origin(Slic3r::Pointf->new(@$center[X,Y])); +#} +# +## Set the bed shape to a single closed 2D polygon (array of two element arrays), +## triangulate the bed and store the triangles into $self->bed_triangles, +## fills the $self->bed_grid_lines and sets $self->origin. +## Sets $self->bed_polygon to limit the object placement. +#sub set_bed_shape { +# my ($self, $bed_shape) = @_; +# +# $self->bed_shape($bed_shape); +# +# # triangulate bed +# my $expolygon = Slic3r::ExPolygon->new([ map [map scale($_), @$_], @$bed_shape ]); +# my $bed_bb = $expolygon->bounding_box; +# +# { +# my @points = (); +# foreach my $triangle (@{ $expolygon->triangulate }) { +# push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$triangle; +# } +# $self->bed_triangles(OpenGL::Array->new_list(GL_FLOAT, @points)); +# } +# +# { +# my @polylines = (); +# for (my $x = $bed_bb->x_min; $x <= $bed_bb->x_max; $x += scale 10) { +# push @polylines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]); +# } +# for (my $y = $bed_bb->y_min; $y <= $bed_bb->y_max; $y += scale 10) { +# push @polylines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]); +# } +# # clip with a slightly grown expolygon because our lines lay on the contours and +# # may get erroneously clipped +# my @lines = map Slic3r::Line->new(@$_[0,-1]), +# @{intersection_pl(\@polylines, [ @{$expolygon->offset(+scaled_epsilon)} ])}; +# +# # append bed contours +# push @lines, map @{$_->lines}, @$expolygon; +# +# my @points = (); +# foreach my $line (@lines) { +# push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$line; #)) +# } +# $self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points)); +# } +# +# # Set the origin for painting of the coordinate system axes. +# $self->origin(Slic3r::Pointf->new(0,0)); +# +# $self->bed_polygon(offset_ex([$expolygon->contour], $bed_bb->radius * 1.7, JT_ROUND, scale(0.5))->[0]->contour->clone); +#} +# +#sub deselect_volumes { +# my ($self) = @_; +# $_->set_selected(0) for @{$self->volumes}; +#} +# +#sub select_volume { +# my ($self, $volume_idx) = @_; +# +# return if ($volume_idx >= scalar(@{$self->volumes})); +# +# $self->volumes->[$volume_idx]->set_selected(1) +# if $volume_idx != -1; +#} +# +#sub SetCuttingPlane { +# my ($self, $z, $expolygons) = @_; +# +# $self->cutting_plane_z($z); +# +# # grow slices in order to display them better +# $expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1); +# +# my @verts = (); +# foreach my $line (map @{$_->lines}, map @$_, @$expolygons) { +# push @verts, ( +# unscale($line->a->x), unscale($line->a->y), $z, #)) +# unscale($line->b->x), unscale($line->b->y), $z, #)) +# ); +# } +# $self->cut_lines_vertices(OpenGL::Array->new_list(GL_FLOAT, @verts)); +#} +# +## Given an axis and angle, compute quaternion. +#sub axis_to_quat { +# my ($ax, $phi) = @_; +# +# my $lena = sqrt(reduce { $a + $b } (map { $_ * $_ } @$ax)); +# my @q = map { $_ * (1 / $lena) } @$ax; +# @q = map { $_ * sin($phi / 2.0) } @q; +# $q[$#q + 1] = cos($phi / 2.0); +# return @q; +#} +# +## Project a point on the virtual trackball. +## If it is inside the sphere, map it to the sphere, if it outside map it +## to a hyperbola. +#sub project_to_sphere { +# my ($r, $x, $y) = @_; +# +# my $d = sqrt($x * $x + $y * $y); +# if ($d < $r * 0.70710678118654752440) { # Inside sphere +# return sqrt($r * $r - $d * $d); +# } else { # On hyperbola +# my $t = $r / 1.41421356237309504880; +# return $t * $t / $d; +# } +#} +# +#sub cross { +# my ($v1, $v2) = @_; +# +# return (@$v1[1] * @$v2[2] - @$v1[2] * @$v2[1], +# @$v1[2] * @$v2[0] - @$v1[0] * @$v2[2], +# @$v1[0] * @$v2[1] - @$v1[1] * @$v2[0]); +#} +# +## Simulate a track-ball. Project the points onto the virtual trackball, +## then figure out the axis of rotation, which is the cross product of +## P1 P2 and O P1 (O is the center of the ball, 0,0,0) Note: This is a +## deformed trackball-- is a trackball in the center, but is deformed +## into a hyperbolic sheet of rotation away from the center. +## It is assumed that the arguments to this routine are in the range +## (-1.0 ... 1.0). +#sub trackball { +# my ($p1x, $p1y, $p2x, $p2y) = @_; +# +# if ($p1x == $p2x && $p1y == $p2y) { +# # zero rotation +# return (0.0, 0.0, 0.0, 1.0); +# } +# +# # First, figure out z-coordinates for projection of P1 and P2 to +# # deformed sphere +# my @p1 = ($p1x, $p1y, project_to_sphere(TRACKBALLSIZE, $p1x, $p1y)); +# my @p2 = ($p2x, $p2y, project_to_sphere(TRACKBALLSIZE, $p2x, $p2y)); +# +# # axis of rotation (cross product of P1 and P2) +# my @a = cross(\@p2, \@p1); +# +# # Figure out how much to rotate around that axis. +# my @d = map { $_ * $_ } (map { $p1[$_] - $p2[$_] } 0 .. $#p1); +# my $t = sqrt(reduce { $a + $b } @d) / (2.0 * TRACKBALLSIZE); +# +# # Avoid problems with out-of-control values... +# $t = 1.0 if ($t > 1.0); +# $t = -1.0 if ($t < -1.0); +# my $phi = 2.0 * asin($t); +# +# return axis_to_quat(\@a, $phi); +#} +# +## Build a rotation matrix, given a quaternion rotation. +#sub quat_to_rotmatrix { +# my ($q) = @_; +# +# my @m = (); +# +# $m[0] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[2] * @$q[2]); +# $m[1] = 2.0 * (@$q[0] * @$q[1] - @$q[2] * @$q[3]); +# $m[2] = 2.0 * (@$q[2] * @$q[0] + @$q[1] * @$q[3]); +# $m[3] = 0.0; +# +# $m[4] = 2.0 * (@$q[0] * @$q[1] + @$q[2] * @$q[3]); +# $m[5] = 1.0 - 2.0 * (@$q[2] * @$q[2] + @$q[0] * @$q[0]); +# $m[6] = 2.0 * (@$q[1] * @$q[2] - @$q[0] * @$q[3]); +# $m[7] = 0.0; +# +# $m[8] = 2.0 * (@$q[2] * @$q[0] - @$q[1] * @$q[3]); +# $m[9] = 2.0 * (@$q[1] * @$q[2] + @$q[0] * @$q[3]); +# $m[10] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[0] * @$q[0]); +# $m[11] = 0.0; +# +# $m[12] = 0.0; +# $m[13] = 0.0; +# $m[14] = 0.0; +# $m[15] = 1.0; +# +# return @m; +#} +# +#sub mulquats { +# my ($q1, $rq) = @_; +# +# return (@$q1[3] * @$rq[0] + @$q1[0] * @$rq[3] + @$q1[1] * @$rq[2] - @$q1[2] * @$rq[1], +# @$q1[3] * @$rq[1] + @$q1[1] * @$rq[3] + @$q1[2] * @$rq[0] - @$q1[0] * @$rq[2], +# @$q1[3] * @$rq[2] + @$q1[2] * @$rq[3] + @$q1[0] * @$rq[1] - @$q1[1] * @$rq[0], +# @$q1[3] * @$rq[3] - @$q1[0] * @$rq[0] - @$q1[1] * @$rq[1] - @$q1[2] * @$rq[2]) +#} +# +## Convert the screen space coordinate to an object space coordinate. +## If the Z screen space coordinate is not provided, a depth buffer value is substituted. +#sub mouse_to_3d { +# my ($self, $x, $y, $z) = @_; +# +# return unless $self->GetContext; +# $self->SetCurrent($self->GetContext); +# +# my @viewport = glGetIntegerv_p(GL_VIEWPORT); # 4 items +# my @mview = glGetDoublev_p(GL_MODELVIEW_MATRIX); # 16 items +# my @proj = glGetDoublev_p(GL_PROJECTION_MATRIX); # 16 items +# +# $y = $viewport[3] - $y; +# $z //= glReadPixels_p($x, $y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT); +# my @projected = gluUnProject_p($x, $y, $z, @mview, @proj, @viewport); +# return Slic3r::Pointf3->new(@projected); +#} +# +#sub GetContext { +# my ($self) = @_; +# return $self->{context} ||= Wx::GLContext->new($self); +#} +# +#sub SetCurrent { +# my ($self, $context) = @_; +# return $self->SUPER::SetCurrent($context); +#} +# +#sub UseVBOs { +# my ($self) = @_; +# +# if (! defined ($self->{use_VBOs})) { +# my $use_legacy = wxTheApp->{app_config}->get('use_legacy_opengl'); +# if ($use_legacy eq '1') { +# # Disable OpenGL 2.0 rendering. +# $self->{use_VBOs} = 0; +# # Don't enable the layer editing tool. +# $self->{layer_editing_enabled} = 0; +# # 2 means failed +# $self->{layer_editing_initialized} = 2; +# return 0; +# } +# # This is a special path for wxWidgets on GTK, where an OpenGL context is initialized +# # first when an OpenGL widget is shown for the first time. How ugly. +# return 0 if (! $self->init && $^O eq 'linux'); +# # Don't use VBOs if anything fails. +# $self->{use_VBOs} = 0; +# if ($self->GetContext) { +# $self->SetCurrent($self->GetContext); +# Slic3r::GUI::_3DScene::_glew_init; +# my @gl_version = split(/\./, glGetString(GL_VERSION)); +# $self->{use_VBOs} = int($gl_version[0]) >= 2; +# # print "UseVBOs $self OpenGL major: $gl_version[0], minor: $gl_version[1]. Use VBOs: ", $self->{use_VBOs}, "\n"; +# } +# } +# return $self->{use_VBOs}; +#} +# +#sub Resize { +# my ($self, $x, $y) = @_; +# +# return unless $self->GetContext; +# $self->_dirty(0); +# +# $self->SetCurrent($self->GetContext); +# glViewport(0, 0, $x, $y); +# +# $x /= $self->_zoom; +# $y /= $self->_zoom; +# +# glMatrixMode(GL_PROJECTION); +# glLoadIdentity(); +# if ($self->_camera_type eq 'ortho') { +# #FIXME setting the size of the box 10x larger than necessary +# # is only a workaround for an incorrectly set camera. +# # This workaround harms Z-buffer accuracy! +## my $depth = 1.05 * $self->max_bounding_box->radius(); +# my $depth = 5.0 * max(@{ $self->max_bounding_box->size }); +# glOrtho( +# -$x/2, $x/2, -$y/2, $y/2, +# -$depth, $depth, +# ); +# } else { +# die "Invalid camera type: ", $self->_camera_type, "\n" if ($self->_camera_type ne 'perspective'); +# my $bbox_r = $self->max_bounding_box->radius(); +# my $fov = PI * 45. / 180.; +# my $fov_tan = tan(0.5 * $fov); +# my $cam_distance = 0.5 * $bbox_r / $fov_tan; +# $self->_camera_distance($cam_distance); +# my $nr = $cam_distance - $bbox_r * 1.1; +# my $fr = $cam_distance + $bbox_r * 1.1; +# $nr = 1 if ($nr < 1); +# $fr = $nr + 1 if ($fr < $nr + 1); +# my $h2 = $fov_tan * $nr; +# my $w2 = $h2 * $x / $y; +# glFrustum(-$w2, $w2, -$h2, $h2, $nr, $fr); +# } +# glMatrixMode(GL_MODELVIEW); +#} +# +#sub InitGL { +# my $self = shift; +# +# return if $self->init; +# return unless $self->GetContext; +# $self->init(1); +# +## # This is a special path for wxWidgets on GTK, where an OpenGL context is initialized +## # first when an OpenGL widget is shown for the first time. How ugly. +## # In that case the volumes are wainting to be moved to Vertex Buffer Objects +## # after the OpenGL context is being initialized. +## $self->volumes->finalize_geometry(1) +## if ($^O eq 'linux' && $self->UseVBOs); +# +# $self->zoom_to_bed; +# +# glClearColor(0, 0, 0, 1); +# glColor3f(1, 0, 0); +# glEnable(GL_DEPTH_TEST); +# glClearDepth(1.0); +# glDepthFunc(GL_LEQUAL); +# glEnable(GL_CULL_FACE); +# glEnable(GL_BLEND); +# glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +# +# # Set antialiasing/multisampling +# glDisable(GL_LINE_SMOOTH); +# glDisable(GL_POLYGON_SMOOTH); +# +# # See "GL_MULTISAMPLE and GL_ARRAY_BUFFER_ARB messages on failed launch" +# # https://github.com/alexrj/Slic3r/issues/4085 +# eval { +# # Disable the multi sampling by default, so the picking by color will work correctly. +# glDisable(GL_MULTISAMPLE); +# }; +# # Disable multi sampling if the eval failed. +# $self->{can_multisample} = 0 if $@; +# +# # ambient lighting +# glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.3, 0.3, 0.3, 1); +# +# glEnable(GL_LIGHTING); +# glEnable(GL_LIGHT0); +# glEnable(GL_LIGHT1); +# +# # light from camera +# glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0); +# glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.3, 0.3, 0.3, 1); +# glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 0.2, 0.2, 0.2, 1); +# +# # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. +# glShadeModel(GL_SMOOTH); +# +## glMaterialfv_p(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 0.5, 0.3, 0.3, 1); +## glMaterialfv_p(GL_FRONT_AND_BACK, GL_SPECULAR, 1, 1, 1, 1); +## glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50); +## glMaterialfv_p(GL_FRONT_AND_BACK, GL_EMISSION, 0.1, 0, 0, 0.9); +# +# # A handy trick -- have surface material mirror the color. +# glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); +# glEnable(GL_COLOR_MATERIAL); +# glEnable(GL_MULTISAMPLE) if ($self->{can_multisample}); +# +# if ($self->UseVBOs) { +# my $shader = new Slic3r::GUI::_3DScene::GLShader; +## if (! $shader->load($self->_fragment_shader_Phong, $self->_vertex_shader_Phong)) { +# print "Compilaton of path shader failed: \n" . $shader->last_error . "\n"; +# $shader = undef; +# } else { +# $self->{plain_shader} = $shader; +# } +# } +#} +# +#sub DestroyGL { +# my $self = shift; +# if ($self->GetContext) { +# $self->SetCurrent($self->GetContext); +# if ($self->{plain_shader}) { +# $self->{plain_shader}->release; +# delete $self->{plain_shader}; +# } +# if ($self->{layer_height_edit_shader}) { +# $self->{layer_height_edit_shader}->release; +# delete $self->{layer_height_edit_shader}; +# } +# $self->volumes->release_geometry; +# } +#} +# +#sub Render { +# my ($self, $dc) = @_; +# +# # prevent calling SetCurrent() when window is not shown yet +# return unless $self->IsShownOnScreen; +# return unless my $context = $self->GetContext; +# $self->SetCurrent($context); +# $self->InitGL; +# +# glClearColor(1, 1, 1, 1); +# glClearDepth(1); +# glDepthFunc(GL_LESS); +# glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +# +# glMatrixMode(GL_MODELVIEW); +# glLoadIdentity(); +# +# if (!TURNTABLE_MODE) { +# # Shift the perspective camera. +# my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance); +# glTranslatef(@$camera_pos); +# } +# +# if (TURNTABLE_MODE) { +# # Turntable mode is enabled by default. +# glRotatef(-$self->_stheta, 1, 0, 0); # pitch +# glRotatef($self->_sphi, 0, 0, 1); # yaw +# } else { +# my @rotmat = quat_to_rotmatrix($self->quat); +# glMultMatrixd_p(@rotmat[0..15]); +# } +# +# glTranslatef(@{ $self->_camera_target->negative }); +# +# # light from above +# glLightfv_p(GL_LIGHT0, GL_POSITION, -0.5, -0.5, 1, 0); +# glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.2, 0.2, 0.2, 1); +# glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.5, 0.5, 0.5, 1); +# +# # Head light +# glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0); +# +# if ($self->enable_picking && !$self->_mouse_dragging) { +# if (my $pos = $self->_mouse_pos) { +# # Render the object for picking. +# # FIXME This cannot possibly work in a multi-sampled context as the color gets mangled by the anti-aliasing. +# # Better to use software ray-casting on a bounding-box hierarchy. +# glPushAttrib(GL_ENABLE_BIT); +# glDisable(GL_MULTISAMPLE) if ($self->{can_multisample}); +# glDisable(GL_LIGHTING); +# glDisable(GL_BLEND); +# $self->draw_volumes(1); +# glPopAttrib(); +# glFlush(); +# my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ]; +# my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256; +# $self->_hover_volume_idx(undef); +# $_->set_hover(0) for @{$self->volumes}; +# if ($volume_idx <= $#{$self->volumes}) { +# $self->_hover_volume_idx($volume_idx); +# +# $self->volumes->[$volume_idx]->set_hover(1); +# my $group_id = $self->volumes->[$volume_idx]->select_group_id; +# if ($group_id != -1) { +# $_->set_hover(1) for grep { $_->select_group_id == $group_id } @{$self->volumes}; +# } +# +# $self->on_hover->($volume_idx) if $self->on_hover; +# } +# glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +# } +# } +# +# # draw fixed background +# if ($self->background) { +# glDisable(GL_LIGHTING); +# glPushMatrix(); +# glLoadIdentity(); +# +# glMatrixMode(GL_PROJECTION); +# glPushMatrix(); +# glLoadIdentity(); +# +# # Draws a bluish bottom to top gradient over the complete screen. +# glDisable(GL_DEPTH_TEST); +# glBegin(GL_QUADS); +# glColor3f(0.0,0.0,0.0); +# glVertex3f(-1.0,-1.0, 1.0); +# glVertex3f( 1.0,-1.0, 1.0); +# glColor3f(10/255,98/255,144/255); +# glVertex3f( 1.0, 1.0, 1.0); +# glVertex3f(-1.0, 1.0, 1.0); +# glEnd(); +# glPopMatrix(); +# glEnable(GL_DEPTH_TEST); +# +# glMatrixMode(GL_MODELVIEW); +# glPopMatrix(); +# glEnable(GL_LIGHTING); +# } +# +# # draw ground and axes +# glDisable(GL_LIGHTING); +# +# # draw ground +# my $ground_z = GROUND_Z; +# +# if ($self->bed_triangles) { +# glDisable(GL_DEPTH_TEST); +# +# glEnable(GL_BLEND); +# glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +# +# glEnableClientState(GL_VERTEX_ARRAY); +# glColor4f(0.8, 0.6, 0.5, 0.4); +# glNormal3d(0,0,1); +# glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_triangles->ptr()); +# glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3); +# glDisableClientState(GL_VERTEX_ARRAY); +# +# # we need depth test for grid, otherwise it would disappear when looking +# # the object from below +# glEnable(GL_DEPTH_TEST); +# +# # draw grid +# glLineWidth(3); +# glColor4f(0.2, 0.2, 0.2, 0.4); +# glEnableClientState(GL_VERTEX_ARRAY); +# glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_grid_lines->ptr()); +# glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3); +# glDisableClientState(GL_VERTEX_ARRAY); +# +# glDisable(GL_BLEND); +# } +# +# my $volumes_bb = $self->volumes_bounding_box; +# +# { +# # draw axes +# # disable depth testing so that axes are not covered by ground +# glDisable(GL_DEPTH_TEST); +# my $origin = $self->origin; +# my $axis_len = $self->use_plain_shader ? 0.3 * max(@{ $self->bed_bounding_box->size }) : 2 * max(@{ $volumes_bb->size }); +# glLineWidth(2); +# glBegin(GL_LINES); +# # draw line for x axis +# glColor3f(1, 0, 0); +# glVertex3f(@$origin, $ground_z); +# glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,, +# # draw line for y axis +# glColor3f(0, 1, 0); +# glVertex3f(@$origin, $ground_z); +# glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++ +# glEnd(); +# # draw line for Z axis +# # (re-enable depth test so that axis is correctly shown when objects are behind it) +# glEnable(GL_DEPTH_TEST); +# glBegin(GL_LINES); +# glColor3f(0, 0, 1); +# glVertex3f(@$origin, $ground_z); +# glVertex3f(@$origin, $ground_z+$axis_len); +# glEnd(); +# } +# +# glEnable(GL_LIGHTING); +# +# # draw objects +# if (! $self->use_plain_shader) { +# $self->draw_volumes; +# } elsif ($self->UseVBOs) { +# if ($self->enable_picking) { +# $self->mark_volumes_for_layer_height; +# $self->volumes->set_print_box($self->bed_bounding_box->x_min, $self->bed_bounding_box->y_min, 0.0, $self->bed_bounding_box->x_max, $self->bed_bounding_box->y_max, $self->{config}->get('max_print_height')); +# $self->volumes->check_outside_state($self->{config}); +# # do not cull backfaces to show broken geometry, if any +# glDisable(GL_CULL_FACE); +# } +# $self->{plain_shader}->enable if $self->{plain_shader}; +# $self->volumes->render_VBOs; +# $self->{plain_shader}->disable; +# glEnable(GL_CULL_FACE) if ($self->enable_picking); +# } else { +# # do not cull backfaces to show broken geometry, if any +# glDisable(GL_CULL_FACE) if ($self->enable_picking); +# $self->volumes->render_legacy; +# glEnable(GL_CULL_FACE) if ($self->enable_picking); +# } +# +# if (defined $self->cutting_plane_z) { +# # draw cutting plane +# my $plane_z = $self->cutting_plane_z; +# my $bb = $volumes_bb; +# glDisable(GL_CULL_FACE); +# glDisable(GL_LIGHTING); +# glEnable(GL_BLEND); +# glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +# glBegin(GL_QUADS); +# glColor4f(0.8, 0.8, 0.8, 0.5); +# glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z); +# glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z); +# glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z); +# glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z); +# glEnd(); +# glEnable(GL_CULL_FACE); +# glDisable(GL_BLEND); +# +# # draw cutting contours +# glEnableClientState(GL_VERTEX_ARRAY); +# glLineWidth(2); +# glColor3f(0, 0, 0); +# glVertexPointer_c(3, GL_FLOAT, 0, $self->cut_lines_vertices->ptr()); +# glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3); +# glVertexPointer_c(3, GL_FLOAT, 0, 0); +# glDisableClientState(GL_VERTEX_ARRAY); +# } +# +# # draw warning message +# $self->draw_warning; +# +# # draw gcode preview legend +# $self->draw_legend; +# +# $self->draw_active_object_annotations; +# +# $self->SwapBuffers(); +#} +# +#sub draw_volumes { +# # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking. +# my ($self, $fakecolor) = @_; +# +# # do not cull backfaces to show broken geometry, if any +# glDisable(GL_CULL_FACE); +# +# glEnable(GL_BLEND); +# glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +# +# glEnableClientState(GL_VERTEX_ARRAY); +# glEnableClientState(GL_NORMAL_ARRAY); +# +# foreach my $volume_idx (0..$#{$self->volumes}) { +# my $volume = $self->volumes->[$volume_idx]; +# +# if ($fakecolor) { +# # Object picking mode. Render the object with a color encoding the object index. +# my $r = ($volume_idx & 0x000000FF) >> 0; +# my $g = ($volume_idx & 0x0000FF00) >> 8; +# my $b = ($volume_idx & 0x00FF0000) >> 16; +# glColor4f($r/255.0, $g/255.0, $b/255.0, 1); +# } elsif ($volume->selected) { +# glColor4f(@{ &SELECTED_COLOR }); +# } elsif ($volume->hover) { +# glColor4f(@{ &HOVER_COLOR }); +# } else { +# glColor4f(@{ $volume->color }); +# } +# +# $volume->render; +# } +# glDisableClientState(GL_NORMAL_ARRAY); +# glDisableClientState(GL_VERTEX_ARRAY); +# +# glDisable(GL_BLEND); +# glEnable(GL_CULL_FACE); +#} +# +#sub mark_volumes_for_layer_height { +# my ($self) = @_; +# +# foreach my $volume_idx (0..$#{$self->volumes}) { +# my $volume = $self->volumes->[$volume_idx]; +# my $object_id = int($volume->select_group_id / 1000000); +# if ($self->layer_editing_enabled && $volume->selected && $self->{layer_height_edit_shader} && +# $volume->has_layer_height_texture && $object_id < $self->{print}->object_count) { +# $volume->set_layer_height_texture_data($self->{layer_preview_z_texture_id}, $self->{layer_height_edit_shader}->shader_program_id, +# $self->{print}->get_object($object_id), $self->_variable_layer_thickness_bar_mouse_cursor_z_relative, $self->{layer_height_edit_band_width}); +# } else { +# $volume->reset_layer_height_texture_data(); +# } +# } +#} +# +#sub _load_image_set_texture { +# my ($self, $file_name) = @_; +# # Load a PNG with an alpha channel. +# my $img = Wx::Image->new; +# $img->LoadFile(Slic3r::var($file_name), wxBITMAP_TYPE_PNG); +# # Get RGB & alpha raw data from wxImage, interleave them into a Perl array. +# my @rgb = unpack 'C*', $img->GetData(); +# my @alpha = $img->HasAlpha ? unpack 'C*', $img->GetAlpha() : (255) x (int(@rgb) / 3); +# my $n_pixels = int(@alpha); +# my @data = (0)x($n_pixels * 4); +# for (my $i = 0; $i < $n_pixels; $i += 1) { +# $data[$i*4 ] = $rgb[$i*3]; +# $data[$i*4+1] = $rgb[$i*3+1]; +# $data[$i*4+2] = $rgb[$i*3+2]; +# $data[$i*4+3] = $alpha[$i]; +# } +# # Initialize a raw bitmap data. +# my $params = { +# loaded => 1, +# valid => $n_pixels > 0, +# width => $img->GetWidth, +# height => $img->GetHeight, +# data => OpenGL::Array->new_list(GL_UNSIGNED_BYTE, @data), +# texture_id => glGenTextures_p(1) +# }; +# # Create and initialize a texture with the raw data. +# glBindTexture(GL_TEXTURE_2D, $params->{texture_id}); +# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); +# glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $params->{width}, $params->{height}, 0, GL_RGBA, GL_UNSIGNED_BYTE, $params->{data}->ptr); +# glBindTexture(GL_TEXTURE_2D, 0); +# return $params; +#} +# +#sub _variable_layer_thickness_load_overlay_image { +# my ($self) = @_; +# $self->{layer_preview_annotation} = $self->_load_image_set_texture('variable_layer_height_tooltip.png') +# if (! $self->{layer_preview_annotation}->{loaded}); +# return $self->{layer_preview_annotation}->{valid}; +#} +# +#sub _variable_layer_thickness_load_reset_image { +# my ($self) = @_; +# $self->{layer_preview_reset_image} = $self->_load_image_set_texture('variable_layer_height_reset.png') +# if (! $self->{layer_preview_reset_image}->{loaded}); +# return $self->{layer_preview_reset_image}->{valid}; +#} +# +## Paint the tooltip. +#sub _render_image { +# my ($self, $image, $l, $r, $b, $t) = @_; +# $self->_render_texture($image->{texture_id}, $l, $r, $b, $t); +#} +# +#sub _render_texture { +# my ($self, $tex_id, $l, $r, $b, $t) = @_; +# +# glColor4f(1.,1.,1.,1.); +# glDisable(GL_LIGHTING); +# glEnable(GL_BLEND); +# glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +# glEnable(GL_TEXTURE_2D); +# glBindTexture(GL_TEXTURE_2D, $tex_id); +# glBegin(GL_QUADS); +# glTexCoord2d(0.,1.); glVertex3f($l, $b, 0); +# glTexCoord2d(1.,1.); glVertex3f($r, $b, 0); +# glTexCoord2d(1.,0.); glVertex3f($r, $t, 0); +# glTexCoord2d(0.,0.); glVertex3f($l, $t, 0); +# glEnd(); +# glBindTexture(GL_TEXTURE_2D, 0); +# glDisable(GL_TEXTURE_2D); +# glDisable(GL_BLEND); +# glEnable(GL_LIGHTING); +#} +# +#sub draw_active_object_annotations { +# # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking. +# my ($self) = @_; +# +# return if (! $self->{layer_height_edit_shader} || ! $self->layer_editing_enabled); +# +# # Find the selected volume, over which the layer editing is active. +# my $volume; +# foreach my $volume_idx (0..$#{$self->volumes}) { +# my $v = $self->volumes->[$volume_idx]; +# if ($v->selected && $v->has_layer_height_texture) { +# $volume = $v; +# last; +# } +# } +# return if (! $volume); +# +# # If the active object was not allocated at the Print, go away. This should only be a momentary case between an object addition / deletion +# # and an update by Platter::async_apply_config. +# my $object_idx = int($volume->select_group_id / 1000000); +# return if $object_idx >= $self->{print}->object_count; +# +# # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth), +# # where x, y is the window size divided by $self->_zoom. +# my ($bar_left, $bar_bottom, $bar_right, $bar_top) = $self->_variable_layer_thickness_bar_rect_viewport; +# my ($reset_left, $reset_bottom, $reset_right, $reset_top) = $self->_variable_layer_thickness_reset_rect_viewport; +# my $z_cursor_relative = $self->_variable_layer_thickness_bar_mouse_cursor_z_relative; +# +# my $print_object = $self->{print}->get_object($object_idx); +# my $z_max = $print_object->model_object->bounding_box->z_max; +# +# $self->{layer_height_edit_shader}->enable; +# $self->{layer_height_edit_shader}->set_uniform('z_to_texture_row', $volume->layer_height_texture_z_to_row_id); +# $self->{layer_height_edit_shader}->set_uniform('z_texture_row_to_normalized', 1. / $volume->layer_height_texture_height); +# $self->{layer_height_edit_shader}->set_uniform('z_cursor', $z_max * $z_cursor_relative); +# $self->{layer_height_edit_shader}->set_uniform('z_cursor_band_width', $self->{layer_height_edit_band_width}); +# glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id}); +# glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $volume->layer_height_texture_width, $volume->layer_height_texture_height, +# 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +# glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2, +# 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +# glTexSubImage2D_c(GL_TEXTURE_2D, 0, 0, 0, $volume->layer_height_texture_width, $volume->layer_height_texture_height, +# GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level0); +# glTexSubImage2D_c(GL_TEXTURE_2D, 1, 0, 0, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2, +# GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level1); +# +# # Render the color bar. +# glDisable(GL_DEPTH_TEST); +# # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth), +# # where x, y is the window size divided by $self->_zoom. +# glPushMatrix(); +# glLoadIdentity(); +# # Paint the overlay. +# glBegin(GL_QUADS); +# glVertex3f($bar_left, $bar_bottom, 0); +# glVertex3f($bar_right, $bar_bottom, 0); +# glVertex3f($bar_right, $bar_top, $z_max); +# glVertex3f($bar_left, $bar_top, $z_max); +# glEnd(); +# glBindTexture(GL_TEXTURE_2D, 0); +# $self->{layer_height_edit_shader}->disable; +# +# # Paint the tooltip. +# if ($self->_variable_layer_thickness_load_overlay_image) +# my $gap = 10/$self->_zoom; +# my ($l, $r, $b, $t) = ($bar_left - $self->{layer_preview_annotation}->{width}/$self->_zoom - $gap, $bar_left - $gap, $reset_bottom + $self->{layer_preview_annotation}->{height}/$self->_zoom + $gap, $reset_bottom + $gap); +# $self->_render_image($self->{layer_preview_annotation}, $l, $r, $t, $b); +# } +# +# # Paint the reset button. +# if ($self->_variable_layer_thickness_load_reset_image) { +# $self->_render_image($self->{layer_preview_reset_image}, $reset_left, $reset_right, $reset_bottom, $reset_top); +# } +# +# # Paint the graph. +# #FIXME show some kind of legend. +# my $max_z = unscale($print_object->size->z); +# my $profile = $print_object->model_object->layer_height_profile; +# my $layer_height = $print_object->config->get('layer_height'); +# my $layer_height_max = 10000000000.; +# { +# # Get a maximum layer height value. +# #FIXME This is a duplicate code of Slicing.cpp. +# my $nozzle_diameters = $print_object->print->config->get('nozzle_diameter'); +# my $layer_heights_min = $print_object->print->config->get('min_layer_height'); +# my $layer_heights_max = $print_object->print->config->get('max_layer_height'); +# for (my $i = 0; $i < scalar(@{$nozzle_diameters}); $i += 1) { +# my $lh_min = ($layer_heights_min->[$i] == 0.) ? 0.07 : max(0.01, $layer_heights_min->[$i]); +# my $lh_max = ($layer_heights_max->[$i] == 0.) ? (0.75 * $nozzle_diameters->[$i]) : $layer_heights_max->[$i]; +# $layer_height_max = min($layer_height_max, max($lh_min, $lh_max)); +# } +# } +# # Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. +# $layer_height_max *= 1.12; +# # Baseline +# glColor3f(0., 0., 0.); +# glBegin(GL_LINE_STRIP); +# glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / $layer_height_max, $bar_bottom); +# glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / $layer_height_max, $bar_top); +# glEnd(); +# # Curve +# glColor3f(0., 0., 1.); +# glBegin(GL_LINE_STRIP); +# for (my $i = 0; $i < int(@{$profile}); $i += 2) { +# my $z = $profile->[$i]; +# my $h = $profile->[$i+1]; +# glVertex3f($bar_left + $h * ($bar_right - $bar_left) / $layer_height_max, $bar_bottom + $z * ($bar_top - $bar_bottom) / $max_z, $z); +# } +# glEnd(); +# # Revert the matrices. +# glPopMatrix(); +# glEnable(GL_DEPTH_TEST); +#} +# +#sub draw_legend { +# my ($self) = @_; +# +# if (!$self->_legend_enabled) { +# return; +# } +# +# # If the legend texture has not been loaded into the GPU, do it now. +# my $tex_id = Slic3r::GUI::_3DScene::finalize_legend_texture; +# if ($tex_id > 0) +# { +# my $tex_w = Slic3r::GUI::_3DScene::get_legend_texture_width; +# my $tex_h = Slic3r::GUI::_3DScene::get_legend_texture_height; +# if (($tex_w > 0) && ($tex_h > 0)) +# { +# glDisable(GL_DEPTH_TEST); +# glPushMatrix(); +# glLoadIdentity(); +# +# my ($cw, $ch) = $self->GetSizeWH; +# +# my $l = (-0.5 * $cw) / $self->_zoom; +# my $t = (0.5 * $ch) / $self->_zoom; +# my $r = $l + $tex_w / $self->_zoom; +# my $b = $t - $tex_h / $self->_zoom; +# $self->_render_texture($tex_id, $l, $r, $b, $t); +# +# glPopMatrix(); +# glEnable(GL_DEPTH_TEST); +# } +# } +#} +# +#sub draw_warning { +# my ($self) = @_; +# +# if (!$self->_warning_enabled) { +# return; +# } +# +# # If the warning texture has not been loaded into the GPU, do it now. +# my $tex_id = Slic3r::GUI::_3DScene::finalize_warning_texture; +# if ($tex_id > 0) +# { +# my $tex_w = Slic3r::GUI::_3DScene::get_warning_texture_width; +# my $tex_h = Slic3r::GUI::_3DScene::get_warning_texture_height; +# if (($tex_w > 0) && ($tex_h > 0)) +# { +# glDisable(GL_DEPTH_TEST); +# glPushMatrix(); +# glLoadIdentity(); +# +# my ($cw, $ch) = $self->GetSizeWH; +# +# my $l = (-0.5 * $tex_w) / $self->_zoom; +# my $t = (-0.5 * $ch + $tex_h) / $self->_zoom; +# my $r = $l + $tex_w / $self->_zoom; +# my $b = $t - $tex_h / $self->_zoom; +# $self->_render_texture($tex_id, $l, $r, $b, $t); +# +# glPopMatrix(); +# glEnable(GL_DEPTH_TEST); +# } +# } +#} +# +#sub update_volumes_colors_by_extruder { +# my ($self, $config) = @_; +# $self->volumes->update_colors_by_extruder($config); +#} +# +#sub opengl_info +#{ +# my ($self, %params) = @_; +# my %tag = Slic3r::tags($params{format}); +# +# my $gl_version = glGetString(GL_VERSION); +# my $gl_vendor = glGetString(GL_VENDOR); +# my $gl_renderer = glGetString(GL_RENDERER); +# my $glsl_version = glGetString(GL_SHADING_LANGUAGE_VERSION); +# +# my $out = ''; +# $out .= "$tag{h2start}OpenGL installation$tag{h2end}$tag{eol}"; +# $out .= " $tag{bstart}Using POGL$tag{bend} v$OpenGL::BUILD_VERSION$tag{eol}"; +# $out .= " $tag{bstart}GL version: $tag{bend}${gl_version}$tag{eol}"; +# $out .= " $tag{bstart}vendor: $tag{bend}${gl_vendor}$tag{eol}"; +# $out .= " $tag{bstart}renderer: $tag{bend}${gl_renderer}$tag{eol}"; +# $out .= " $tag{bstart}GLSL version: $tag{bend}${glsl_version}$tag{eol}"; +# +# # Check for other OpenGL extensions +# $out .= "$tag{h2start}Installed extensions (* implemented in the module):$tag{h2end}$tag{eol}"; +# my $extensions = glGetString(GL_EXTENSIONS); +# my @extensions = split(' ',$extensions); +# foreach my $ext (sort @extensions) { +# my $stat = glpCheckExtension($ext); +# $out .= sprintf("%s ${ext}$tag{eol}", $stat?' ':'*'); +# $out .= sprintf(" ${stat}$tag{eol}") if ($stat && $stat !~ m|^$ext |); +# } +# +# return $out; +#} +# +#sub _report_opengl_state +#{ +# my ($self, $comment) = @_; +# my $err = glGetError(); +# return 0 if ($err == 0); +# +# # gluErrorString() hangs. Don't use it. +## my $errorstr = gluErrorString(); +# my $errorstr = ''; +# if ($err == 0x0500) { +# $errorstr = 'GL_INVALID_ENUM'; +# } elsif ($err == GL_INVALID_VALUE) { +# $errorstr = 'GL_INVALID_VALUE'; +# } elsif ($err == GL_INVALID_OPERATION) { +# $errorstr = 'GL_INVALID_OPERATION'; +# } elsif ($err == GL_STACK_OVERFLOW) { +# $errorstr = 'GL_STACK_OVERFLOW'; +# } elsif ($err == GL_OUT_OF_MEMORY) { +# $errorstr = 'GL_OUT_OF_MEMORY'; +# } else { +# $errorstr = 'unknown'; +# } +# if (defined($comment)) { +# printf("OpenGL error at %s, nr %d (0x%x): %s\n", $comment, $err, $err, $errorstr); +# } else { +# printf("OpenGL error nr %d (0x%x): %s\n", $err, $err, $errorstr); +# } +#} +# +#sub _vertex_shader_Gouraud { +# return <<'VERTEX'; +##version 110 +# +##define INTENSITY_CORRECTION 0.6 +# +#// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +#const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +##define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +##define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +##define LIGHT_TOP_SHININESS 20.0 +# +#// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +#const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +##define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) +#//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION) +#//#define LIGHT_FRONT_SHININESS 5.0 +# +##define INTENSITY_AMBIENT 0.3 +# +#const vec3 ZERO = vec3(0.0, 0.0, 0.0); +# +#struct PrintBoxDetection +#{ +# vec3 min; +# vec3 max; +# // xyz contains the offset, if w == 1.0 detection needs to be performed +# vec4 volume_origin; +#}; +# +#uniform PrintBoxDetection print_box; +# +#// x = tainted, y = specular; +#varying vec2 intensity; +# +#varying vec3 delta_box_min; +#varying vec3 delta_box_max; +# +#void main() +#{ +# // First transform the normal into camera space and normalize the result. +# vec3 normal = normalize(gl_NormalMatrix * gl_Normal); +# +# // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. +# // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. +# float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); +# +# intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; +# intensity.y = 0.0; +# +# if (NdotL > 0.0) +# intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); +# +# // Perform the same lighting calculation for the 2nd light source (no specular applied). +# NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); +# intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; +# +# // compute deltas for out of print volume detection (world coordinates) +# if (print_box.volume_origin.w == 1.0) +# { +# vec3 v = gl_Vertex.xyz + print_box.volume_origin.xyz; +# delta_box_min = v - print_box.min; +# delta_box_max = v - print_box.max; +# } +# else +# { +# delta_box_min = ZERO; +# delta_box_max = ZERO; +# } +# +# gl_Position = ftransform(); +#} +# +#VERTEX +#} +# +#sub _fragment_shader_Gouraud { +# return <<'FRAGMENT'; +##version 110 +# +#const vec3 ZERO = vec3(0.0, 0.0, 0.0); +# +#// x = tainted, y = specular; +#varying vec2 intensity; +# +#varying vec3 delta_box_min; +#varying vec3 delta_box_max; +# +#uniform vec4 uniform_color; +# +#void main() +#{ +# // if the fragment is outside the print volume -> use darker color +# vec3 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(uniform_color.rgb, ZERO, 0.3333) : uniform_color.rgb; +# gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + color * intensity.x, uniform_color.a); +#} +# +#FRAGMENT +#} +# +#sub _vertex_shader_Phong { +# return <<'VERTEX'; +##version 110 +# +#varying vec3 normal; +#varying vec3 eye; +#void main(void) +#{ +# eye = normalize(vec3(gl_ModelViewMatrix * gl_Vertex)); +# normal = normalize(gl_NormalMatrix * gl_Normal); +# gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; +#} +#VERTEX +#} +# +#sub _fragment_shader_Phong { +# return <<'FRAGMENT'; +##version 110 +# +##define INTENSITY_CORRECTION 0.7 +# +##define LIGHT_TOP_DIR -0.6/1.31, 0.6/1.31, 1./1.31 +##define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +##define LIGHT_TOP_SPECULAR (0.5 * INTENSITY_CORRECTION) +#//#define LIGHT_TOP_SHININESS 50. +##define LIGHT_TOP_SHININESS 10. +# +##define LIGHT_FRONT_DIR 1./1.43, 0.2/1.43, 1./1.43 +##define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) +##define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION) +##define LIGHT_FRONT_SHININESS 50. +# +##define INTENSITY_AMBIENT 0.0 +# +#varying vec3 normal; +#varying vec3 eye; +#uniform vec4 uniform_color; +#void main() { +# +# float intensity_specular = 0.; +# float intensity_tainted = 0.; +# float intensity = max(dot(normal,vec3(LIGHT_TOP_DIR)), 0.0); +# // if the vertex is lit compute the specular color +# if (intensity > 0.0) { +# intensity_tainted = LIGHT_TOP_DIFFUSE * intensity; +# // compute the half vector +# vec3 h = normalize(vec3(LIGHT_TOP_DIR) + eye); +# // compute the specular term into spec +# intensity_specular = LIGHT_TOP_SPECULAR * pow(max(dot(h, normal), 0.0), LIGHT_TOP_SHININESS); +# } +# intensity = max(dot(normal,vec3(LIGHT_FRONT_DIR)), 0.0); +# // if the vertex is lit compute the specular color +# if (intensity > 0.0) { +# intensity_tainted += LIGHT_FRONT_DIFFUSE * intensity; +# // compute the half vector +#// vec3 h = normalize(vec3(LIGHT_FRONT_DIR) + eye); +# // compute the specular term into spec +#// intensity_specular += LIGHT_FRONT_SPECULAR * pow(max(dot(h,normal), 0.0), LIGHT_FRONT_SHININESS); +# } +# +# gl_FragColor = max( +# vec4(intensity_specular, intensity_specular, intensity_specular, 0.) + uniform_color * intensity_tainted, +# INTENSITY_AMBIENT * uniform_color); +# gl_FragColor.a = uniform_color.a; +#} +#FRAGMENT +#} +# +#sub _vertex_shader_variable_layer_height { +# return <<'VERTEX'; +##version 110 +# +##define INTENSITY_CORRECTION 0.6 +# +#const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +##define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +##define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +##define LIGHT_TOP_SHININESS 20.0 +# +#const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +##define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) +#//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION) +#//#define LIGHT_FRONT_SHININESS 5.0 +# +##define INTENSITY_AMBIENT 0.3 +# +#// x = tainted, y = specular; +#varying vec2 intensity; +# +#varying float object_z; +# +#void main() +#{ +# // First transform the normal into camera space and normalize the result. +# vec3 normal = normalize(gl_NormalMatrix * gl_Normal); +# +# // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. +# // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. +# float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); +# +# intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; +# intensity.y = 0.0; +# +# if (NdotL > 0.0) +# intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); +# +# // Perform the same lighting calculation for the 2nd light source (no specular) +# NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); +# +# intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; +# +# // Scaled to widths of the Z texture. +# object_z = gl_Vertex.z; +# +# gl_Position = ftransform(); +#} +# +#VERTEX +#} +# +#sub _fragment_shader_variable_layer_height { +# return <<'FRAGMENT'; +##version 110 +# +##define M_PI 3.1415926535897932384626433832795 +# +#// 2D texture (1D texture split by the rows) of color along the object Z axis. +#uniform sampler2D z_texture; +#// Scaling from the Z texture rows coordinate to the normalized texture row coordinate. +#uniform float z_to_texture_row; +#uniform float z_texture_row_to_normalized; +#uniform float z_cursor; +#uniform float z_cursor_band_width; +# +#// x = tainted, y = specular; +#varying vec2 intensity; +# +#varying float object_z; +# +#void main() +#{ +# float object_z_row = z_to_texture_row * object_z; +# // Index of the row in the texture. +# float z_texture_row = floor(object_z_row); +# // Normalized coordinate from 0. to 1. +# float z_texture_col = object_z_row - z_texture_row; +# float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25; +# // Calculate level of detail from the object Z coordinate. +# // This makes the slowly sloping surfaces to be show with high detail (with stripes), +# // and the vertical surfaces to be shown with low detail (no stripes) +# float z_in_cells = object_z_row * 190.; +# // Gradient of Z projected on the screen. +# float dx_vtc = dFdx(z_in_cells); +# float dy_vtc = dFdy(z_in_cells); +# float lod = clamp(0.5 * log2(max(dx_vtc*dx_vtc, dy_vtc*dy_vtc)), 0., 1.); +# // Sample the Z texture. Texture coordinates are normalized to <0, 1>. +# vec4 color = +# mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.), +# texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod); +# +# // Mix the final color. +# gl_FragColor = +# vec4(intensity.y, intensity.y, intensity.y, 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend); +#} +# +#FRAGMENT +#} +#=================================================================================================================================== # The 3D canvas to display objects and tool paths. package Slic3r::GUI::3DScene; use base qw(Slic3r::GUI::3DScene::Base); -use OpenGL qw(:glconstants :gluconstants :glufunctions); -use List::Util qw(first min max); -use Slic3r::Geometry qw(scale unscale epsilon); -use Slic3r::Print::State ':steps'; - -__PACKAGE__->mk_accessors(qw( - color_by - select_by - drag_by -)); +#=================================================================================================================================== +#use OpenGL qw(:glconstants :gluconstants :glufunctions); +#use List::Util qw(first min max); +#use Slic3r::Geometry qw(scale unscale epsilon); +#use Slic3r::Print::State ':steps'; +#=================================================================================================================================== + +#=================================================================================================================================== +#__PACKAGE__->mk_accessors(qw( +# color_by +# select_by +# drag_by +#)); +#=================================================================================================================================== sub new { my $class = shift; my $self = $class->SUPER::new(@_); - $self->color_by('volume'); # object | volume - $self->select_by('object'); # object | volume | instance - $self->drag_by('instance'); # object | instance +#=================================================================================================================================== +# $self->color_by('volume'); # object | volume +# $self->select_by('object'); # object | volume | instance +# $self->drag_by('instance'); # object | instance +#=================================================================================================================================== return $self; } -sub load_object { - my ($self, $model, $print, $obj_idx, $instance_idxs) = @_; - - $self->SetCurrent($self->GetContext) if $self->UseVBOs; - - my $model_object; - if ($model->isa('Slic3r::Model::Object')) { - $model_object = $model; - $model = $model_object->model; - $obj_idx = 0; - } else { - $model_object = $model->get_object($obj_idx); - } - - $instance_idxs ||= [0..$#{$model_object->instances}]; - my $volume_indices = $self->volumes->load_object( - $model_object, $obj_idx, $instance_idxs, $self->color_by, $self->select_by, $self->drag_by, - $self->UseVBOs); - return @{$volume_indices}; -} - -# Create 3D thick extrusion lines for a skirt and brim. -# Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes. -sub load_print_toolpaths { - my ($self, $print, $colors) = @_; - - $self->SetCurrent($self->GetContext) if $self->UseVBOs; - Slic3r::GUI::_3DScene::_load_print_toolpaths($print, $self->volumes, $colors, $self->UseVBOs) - if ($print->step_done(STEP_SKIRT) && $print->step_done(STEP_BRIM)); -} - -# Create 3D thick extrusion lines for object forming extrusions. -# Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes, -# one for perimeters, one for infill and one for supports. -sub load_print_object_toolpaths { - my ($self, $object, $colors) = @_; - - $self->SetCurrent($self->GetContext) if $self->UseVBOs; - Slic3r::GUI::_3DScene::_load_print_object_toolpaths($object, $self->volumes, $colors, $self->UseVBOs); -} - -# Create 3D thick extrusion lines for wipe tower extrusions. -sub load_wipe_tower_toolpaths { - my ($self, $print, $colors) = @_; - - $self->SetCurrent($self->GetContext) if $self->UseVBOs; - Slic3r::GUI::_3DScene::_load_wipe_tower_toolpaths($print, $self->volumes, $colors, $self->UseVBOs) - if ($print->step_done(STEP_WIPE_TOWER)); -} - -sub load_gcode_preview { - my ($self, $print, $gcode_preview_data, $colors) = @_; - - $self->SetCurrent($self->GetContext) if $self->UseVBOs; - Slic3r::GUI::_3DScene::load_gcode_preview($print, $gcode_preview_data, $self->volumes, $colors, $self->UseVBOs); -} - -sub set_toolpaths_range { - my ($self, $min_z, $max_z) = @_; - $self->volumes->set_range($min_z, $max_z); -} - -sub reset_legend_texture { - Slic3r::GUI::_3DScene::reset_legend_texture(); -} - -sub get_current_print_zs { - my ($self, $active_only) = @_; - return $self->volumes->get_current_print_zs($active_only); -} +#============================================================================================================================== +#sub load_object { +# my ($self, $model, $print, $obj_idx, $instance_idxs) = @_; +# +# $self->SetCurrent($self->GetContext) if $useVBOs; +# +# my $model_object; +# if ($model->isa('Slic3r::Model::Object')) { +# $model_object = $model; +# $model = $model_object->model; +# $obj_idx = 0; +# } else { +# $model_object = $model->get_object($obj_idx); +# } +# +# $instance_idxs ||= [0..$#{$model_object->instances}]; +# my $volume_indices = $self->volumes->load_object( +# $model_object, $obj_idx, $instance_idxs, $self->color_by, $self->select_by, $self->drag_by, +# $self->UseVBOs); +# return @{$volume_indices}; +#} +# +## Create 3D thick extrusion lines for a skirt and brim. +## Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes. +#sub load_print_toolpaths { +# my ($self, $print, $colors) = @_; +# +# $self->SetCurrent($self->GetContext) if $self->UseVBOs; +# Slic3r::GUI::_3DScene::_load_print_toolpaths($print, $self->volumes, $colors, $self->UseVBOs) +# if ($print->step_done(STEP_SKIRT) && $print->step_done(STEP_BRIM)); +#} +# +## Create 3D thick extrusion lines for object forming extrusions. +## Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes, +## one for perimeters, one for infill and one for supports. +#sub load_print_object_toolpaths { +# my ($self, $object, $colors) = @_; +# +# $self->SetCurrent($self->GetContext) if $self->UseVBOs; +# Slic3r::GUI::_3DScene::_load_print_object_toolpaths($object, $self->volumes, $colors, $self->UseVBOs); +#} +# +## Create 3D thick extrusion lines for wipe tower extrusions. +#sub load_wipe_tower_toolpaths { +# my ($self, $print, $colors) = @_; +# +# $self->SetCurrent($self->GetContext) if $self->UseVBOs; +# Slic3r::GUI::_3DScene::_load_wipe_tower_toolpaths($print, $self->volumes, $colors, $self->UseVBOs) +# if ($print->step_done(STEP_WIPE_TOWER)); +#} +# +#sub load_gcode_preview { +# my ($self, $print, $gcode_preview_data, $colors) = @_; +# +# $self->SetCurrent($self->GetContext) if $self->UseVBOs; +# Slic3r::GUI::_3DScene::load_gcode_preview($print, $gcode_preview_data, $self->volumes, $colors, $self->UseVBOs); +#} +# +#sub set_toolpaths_range { +# my ($self, $min_z, $max_z) = @_; +# $self->volumes->set_range($min_z, $max_z); +#} +# +#sub reset_legend_texture { +# Slic3r::GUI::_3DScene::reset_legend_texture(); +#} +# +#sub get_current_print_zs { +# my ($self, $active_only) = @_; +# return $self->volumes->get_current_print_zs($active_only); +#} +#============================================================================================================================== 1; diff --git a/lib/Slic3r/GUI/BedShapeDialog.pm b/lib/Slic3r/GUI/BedShapeDialog.pm deleted file mode 100644 index 70c8e0256..000000000 --- a/lib/Slic3r/GUI/BedShapeDialog.pm +++ /dev/null @@ -1,316 +0,0 @@ -# The bed shape dialog. -# The dialog opens from Print Settins tab -> Bed Shape: Set... - -package Slic3r::GUI::BedShapeDialog; -use strict; -use warnings; -use utf8; - -use List::Util qw(min max); -use Slic3r::Geometry qw(X Y unscale); -use Wx qw(:dialog :id :misc :sizer :choicebook wxTAB_TRAVERSAL); -use Wx::Event qw(EVT_CLOSE); -use base 'Wx::Dialog'; - -sub new { - my $class = shift; - my ($parent, $default) = @_; - my $self = $class->SUPER::new($parent, -1, "Bed Shape", wxDefaultPosition, [350,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); - - $self->{panel} = my $panel = Slic3r::GUI::BedShapePanel->new($self, $default); - - my $main_sizer = Wx::BoxSizer->new(wxVERTICAL); - $main_sizer->Add($panel, 1, wxEXPAND); - $main_sizer->Add($self->CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND); - - $self->SetSizer($main_sizer); - $self->SetMinSize($self->GetSize); - $main_sizer->SetSizeHints($self); - - # needed to actually free memory - EVT_CLOSE($self, sub { - $self->EndModal(wxID_OK); - $self->Destroy; - }); - - return $self; -} - -sub GetValue { - my ($self) = @_; - return $self->{panel}->GetValue; -} - -package Slic3r::GUI::BedShapePanel; - -use List::Util qw(min max sum first); -use Scalar::Util qw(looks_like_number); -use Slic3r::Geometry qw(PI X Y unscale scaled_epsilon); -use Wx qw(:font :id :misc :sizer :choicebook :filedialog :pen :brush wxTAB_TRAVERSAL); -use Wx::Event qw(EVT_CLOSE EVT_CHOICEBOOK_PAGE_CHANGED EVT_BUTTON); -use base 'Wx::Panel'; - -use constant SHAPE_RECTANGULAR => 0; -use constant SHAPE_CIRCULAR => 1; -use constant SHAPE_CUSTOM => 2; - -sub new { - my $class = shift; - my ($parent, $default) = @_; - my $self = $class->SUPER::new($parent, -1); - - $self->on_change(undef); - - my $box = Wx::StaticBox->new($self, -1, "Shape"); - my $sbsizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); - - # shape options - $self->{shape_options_book} = Wx::Choicebook->new($self, -1, wxDefaultPosition, [300,-1], wxCHB_TOP); - $sbsizer->Add($self->{shape_options_book}); - - $self->{optgroups} = []; - { - my $optgroup = $self->_init_shape_options_page('Rectangular'); - $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( - opt_id => 'rect_size', - type => 'point', - label => 'Size', - tooltip => 'Size in X and Y of the rectangular plate.', - default => [200,200], - )); - $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( - opt_id => 'rect_origin', - type => 'point', - label => 'Origin', - tooltip => 'Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.', - default => [0,0], - )); - } - { - my $optgroup = $self->_init_shape_options_page('Circular'); - $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( - opt_id => 'diameter', - type => 'f', - label => 'Diameter', - tooltip => 'Diameter of the print bed. It is assumed that origin (0,0) is located in the center.', - sidetext => 'mm', - default => 200, - )); - } - { - my $optgroup = $self->_init_shape_options_page('Custom'); - $optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new( - full_width => 1, - widget => sub { - my ($parent) = @_; - - my $btn = Wx::Button->new($parent, -1, "Load shape from STL...", wxDefaultPosition, wxDefaultSize); - EVT_BUTTON($self, $btn, sub { $self->_load_stl }); - return $btn; - } - )); - } - - EVT_CHOICEBOOK_PAGE_CHANGED($self, -1, sub { - $self->_update_shape; - }); - - # right pane with preview canvas - my $canvas = $self->{canvas} = Slic3r::GUI::2DBed->new($self); - - # main sizer - my $top_sizer = Wx::BoxSizer->new(wxHORIZONTAL); - $top_sizer->Add($sbsizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); - $top_sizer->Add($canvas, 1, wxEXPAND | wxALL, 10) if $canvas; - - $self->SetSizerAndFit($top_sizer); - - $self->_set_shape($default); - $self->_update_preview; - - return $self; -} - -sub on_change { - my ($self, $cb) = @_; - $self->{on_change} = $cb // sub {}; -} - -# Called from the constructor. -# Set the initial bed shape from a list of points. -# Deduce the bed shape type (rect, circle, custom) -# This routine shall be smart enough if the user messes up -# with the list of points in the ini file directly. -sub _set_shape { - my ($self, $points) = @_; - - # is this a rectangle? - if (@$points == 4) { - my $polygon = Slic3r::Polygon->new_scale(@$points); - my $lines = $polygon->lines; - if ($lines->[0]->parallel_to_line($lines->[2]) && $lines->[1]->parallel_to_line($lines->[3])) { - # okay, it's a rectangle - - # find origin - # the || 0 hack prevents "-0" which might confuse the user - my $x_min = min(map $_->[X], @$points) || 0; - my $x_max = max(map $_->[X], @$points) || 0; - my $y_min = min(map $_->[Y], @$points) || 0; - my $y_max = max(map $_->[Y], @$points) || 0; - my $origin = [-$x_min, -$y_min]; - - $self->{shape_options_book}->SetSelection(SHAPE_RECTANGULAR); - my $optgroup = $self->{optgroups}[SHAPE_RECTANGULAR]; - $optgroup->set_value('rect_size', [ $x_max-$x_min, $y_max-$y_min ]); - $optgroup->set_value('rect_origin', $origin); - $self->_update_shape; - return; - } - } - - # is this a circle? - { - # Analyze the array of points. Do they reside on a circle? - my $polygon = Slic3r::Polygon->new_scale(@$points); - my $center = $polygon->bounding_box->center; - my @vertex_distances = map $center->distance_to($_), @$polygon; - my $avg_dist = sum(@vertex_distances)/@vertex_distances; - if (!defined first { abs($_ - $avg_dist) > 10*scaled_epsilon } @vertex_distances) { - # all vertices are equidistant to center - $self->{shape_options_book}->SetSelection(SHAPE_CIRCULAR); - my $optgroup = $self->{optgroups}[SHAPE_CIRCULAR]; - $optgroup->set_value('diameter', sprintf("%.0f", unscale($avg_dist*2))); - $self->_update_shape; - return; - } - } - - if (@$points < 3) { - # Invalid polygon. Revert to default bed dimensions. - $self->{shape_options_book}->SetSelection(SHAPE_RECTANGULAR); - my $optgroup = $self->{optgroups}[SHAPE_RECTANGULAR]; - $optgroup->set_value('rect_size', [200, 200]); - $optgroup->set_value('rect_origin', [0, 0]); - $self->_update_shape; - return; - } - - # This is a custom bed shape, use the polygon provided. - $self->{shape_options_book}->SetSelection(SHAPE_CUSTOM); - # Copy the polygon to the canvas, make a copy of the array. - $self->{canvas}->bed_shape([@$points]); - $self->_update_shape; -} - -# Update the bed shape from the dialog fields. -sub _update_shape { - my ($self) = @_; - - my $page_idx = $self->{shape_options_book}->GetSelection; - if ($page_idx == SHAPE_RECTANGULAR) { - my $rect_size = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_size'); - my $rect_origin = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_origin'); - my ($x, $y) = @$rect_size; - return if !looks_like_number($x) || !looks_like_number($y); # empty strings or '-' or other things - return if !$x || !$y or $x == 0 or $y == 0; - my ($x0, $y0) = (0,0); - my ($x1, $y1) = ($x ,$y); - { - my ($dx, $dy) = @$rect_origin; - return if !looks_like_number($dx) || !looks_like_number($dy); # empty strings or '-' or other things - $x0 -= $dx; - $x1 -= $dx; - $y0 -= $dy; - $y1 -= $dy; - } - $self->{canvas}->bed_shape([ - [$x0,$y0], - [$x1,$y0], - [$x1,$y1], - [$x0,$y1], - ]); - } elsif ($page_idx == SHAPE_CIRCULAR) { - my $diameter = $self->{optgroups}[SHAPE_CIRCULAR]->get_value('diameter'); - return if !$diameter or $diameter == 0; - my $r = $diameter/2; - my $twopi = 2*PI; - my $edges = 60; - my $polygon = Slic3r::Polygon->new_scale( - map [ $r * cos $_, $r * sin $_ ], - map { $twopi/$edges*$_ } 1..$edges - ); - $self->{canvas}->bed_shape([ - map [ unscale($_->x), unscale($_->y) ], @$polygon #)) - ]); - } - - $self->{on_change}->(); - $self->_update_preview; -} - -sub _update_preview { - my ($self) = @_; - $self->{canvas}->Refresh if $self->{canvas}; - $self->Refresh; -} - -# Called from the constructor. -# Create a panel for a rectangular / circular / custom bed shape. -sub _init_shape_options_page { - my ($self, $title) = @_; - - my $panel = Wx::Panel->new($self->{shape_options_book}); - my $optgroup; - push @{$self->{optgroups}}, $optgroup = Slic3r::GUI::OptionsGroup->new( - parent => $panel, - title => 'Settings', - label_width => 100, - on_change => sub { - my ($opt_id) = @_; - #$self->{"_$opt_id"} = $optgroup->get_value($opt_id); - $self->_update_shape; - }, - ); - $panel->SetSizerAndFit($optgroup->sizer); - $self->{shape_options_book}->AddPage($panel, $title); - - return $optgroup; -} - -# Loads an stl file, projects it to the XY plane and calculates a polygon. -sub _load_stl { - my ($self) = @_; - - my $dialog = Wx::FileDialog->new($self, 'Choose a file to import bed shape from (STL/OBJ/AMF/PRUSA):', "", "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if ($dialog->ShowModal != wxID_OK) { - $dialog->Destroy; - return; - } - my $input_file = $dialog->GetPaths; - $dialog->Destroy; - - my $model = Slic3r::Model->read_from_file($input_file); - my $mesh = $model->mesh; - my $expolygons = $mesh->horizontal_projection; - - if (@$expolygons == 0) { - Slic3r::GUI::show_error($self, "The selected file contains no geometry."); - return; - } - if (@$expolygons > 1) { - Slic3r::GUI::show_error($self, "The selected file contains several disjoint areas. This is not supported."); - return; - } - - my $polygon = $expolygons->[0]->contour; - $self->{canvas}->bed_shape([ map [ unscale($_->x), unscale($_->y) ], @$polygon ]); - $self->_update_preview(); -} - -# Returns the resulting bed shape polygon. This value will be stored to the ini file. -sub GetValue { - my ($self) = @_; - return $self->{canvas}->bed_shape; -} - -1; diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 2b93351f8..ea4a158f4 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; }); @@ -108,6 +110,10 @@ sub _init_tabpanel { EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{tabpanel}, sub { my $panel = $self->{tabpanel}->GetCurrentPage; $panel->OnActivate if $panel->can('OnActivate'); + + for my $tab_name (qw(print filament printer)) { + Slic3r::GUI::get_preset_tab("$tab_name")->OnActivate if ("$tab_name" eq $panel->GetName); + } }); if (!$self->{no_plater}) { diff --git a/lib/Slic3r/GUI/OptionsGroup/Field.pm b/lib/Slic3r/GUI/OptionsGroup/Field.pm index 4ef2ce2ca..1a53daeb5 100644 --- a/lib/Slic3r/GUI/OptionsGroup/Field.pm +++ b/lib/Slic3r/GUI/OptionsGroup/Field.pm @@ -552,8 +552,9 @@ sub BUILD { $sizer->Add($textctrl, 0, wxALIGN_CENTER_VERTICAL, 0); EVT_SLIDER($self->parent, $slider, sub { - if (! $self->disable_change_event) { - $self->textctrl->SetLabel($self->get_value); + if (! $self->disable_change_event) { + # wxTextCtrl::SetLabel() does not work on Linux, use wxTextCtrl::SetValue() instead + $self->textctrl->SetValue($self->get_value); $self->_on_change($self->option->opt_id); } }); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 8d4a1a269..3f069c0e4 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -53,7 +53,7 @@ sub new { bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height serial_port serial_speed octoprint_host octoprint_apikey octoprint_cafile nozzle_diameter single_extruder_multi_material wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width - wipe_tower_rotation_angle extruder_colour filament_colour max_print_height + wipe_tower_rotation_angle extruder_colour filament_colour max_print_height printer_model )]); # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm $self->{model} = Slic3r::Model->new; @@ -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::enable_legend_texture($self->{preview3D}->canvas, 1); + 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; } @@ -169,15 +200,21 @@ sub new { EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{preview_notebook}, sub { my $preview = $self->{preview_notebook}->GetCurrentPage; - if ($preview == $self->{preview3D}) - { - $self->{preview3D}->canvas->set_legend_enabled(1); - $self->{preview3D}->load_print(1); - } else { - $self->{preview3D}->canvas->set_legend_enabled(0); + if (($preview != $self->{preview3D}) && ($preview != $self->{canvas3D})) { + $preview->OnActivate if $preview->can('OnActivate'); + } elsif ($preview == $self->{preview3D}) { + $self->{preview3D}->reload_print; + # sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) + Slic3r::GUI::_3DScene::set_as_dirty($self->{preview3D}->canvas); + } elsif ($preview == $self->{canvas3D}) { + 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); + } + # sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) + Slic3r::GUI::_3DScene::set_as_dirty($self->{canvas3D}); } - - $preview->OnActivate if $preview->can('OnActivate'); }); # toolbar for object manipulation @@ -314,7 +351,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 +406,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 +627,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 +862,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 +1253,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; @@ -1619,6 +1654,34 @@ sub export_object_stl { $self->statusbar->SetStatusText(L("STL file exported to ").$output_file); } +sub fix_through_netfabb { + my ($self) = @_; + my ($obj_idx, $object) = $self->selected_object; + return if !defined $obj_idx; + my $model_object = $self->{model}->objects->[$obj_idx]; + my $model_fixed = Slic3r::Model->new; + Slic3r::GUI::fix_model_by_win10_sdk_gui($model_object, $self->{print}, $model_fixed); + + my @new_obj_idx = $self->load_model_objects(@{$model_fixed->objects}); + return if !@new_obj_idx; + + foreach my $new_obj_idx (@new_obj_idx) { + my $o = $self->{model}->objects->[$new_obj_idx]; + $o->clear_instances; + $o->add_instance($_) for @{$model_object->instances}; + #$o->invalidate_bounding_box; + + if ($o->volumes_count == $model_object->volumes_count) { + for my $i (0..($o->volumes_count-1)) { + $o->get_volume($i)->config->apply($model_object->get_volume($i)->config); + } + } + #FIXME restore volumes and their configs, layer_height_ranges, layer_height_profile, layer_height_profile_valid, + } + + $self->remove($obj_idx); +} + sub export_amf { my ($self) = @_; return if !@{$self->{objects}}; @@ -1723,7 +1786,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}; } @@ -1778,9 +1843,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; @@ -1799,10 +1863,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); @@ -1816,6 +1880,11 @@ sub on_config_change { $self->{preview3D}->set_number_extruders(scalar(@{$extruder_colors})); } elsif ($opt_key eq 'max_print_height') { $update_scheduled = 1; + } elsif ($opt_key eq 'printer_model') { + # update to force bed selection (for texturing) + 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; } } @@ -1834,8 +1903,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}; } @@ -1847,11 +1916,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) = @_; @@ -1908,7 +1989,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}; } } @@ -1944,7 +2025,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; } @@ -1957,7 +2040,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) @@ -1972,7 +2055,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); @@ -2177,6 +2260,11 @@ sub object_menu { $frame->_append_menu_item($menu, L("Export object as STL…"), L('Export this single object as STL file'), sub { $self->export_object_stl; }, undef, 'brick_go.png'); + if (Slic3r::GUI::is_windows10) { + $frame->_append_menu_item($menu, L("Fix STL through Netfabb"), L('Fix the model by sending it to a Netfabb cloud service through Windows 10 API'), sub { + $self->fix_through_netfabb; + }, undef, 'brick_go.png'); + } return $menu; } @@ -2187,11 +2275,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..88a05c292 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; @@ -231,8 +231,9 @@ sub mouse_event { } $self->Refresh; } elsif ($event->LeftUp) { - $self->{on_instances_moved}->() - if $self->{drag_object}; + if ($self->{drag_object}) { + $self->{on_instances_moved}->(); + } $self->{drag_start_pos} = undef; $self->{drag_object} = undef; $self->SetCursor(wxSTANDARD_CURSOR); diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index 96a252a08..382310f24 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -199,11 +199,11 @@ sub new { my $old_zoom = $self->_zoom; # Calculate the zoom delta and apply it to the current zoom factor - my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta(); + my $zoom = -$e->GetWheelRotation() / $e->GetWheelDelta(); $zoom = max(min($zoom, 4), -4); $zoom /= 10; $self->_zoom($self->_zoom / (1-$zoom)); - $self->_zoom(1) if $self->_zoom > 1; # prevent from zooming out too much + $self->_zoom(1.25) if $self->_zoom > 1.25; # prevent from zooming out too much { # In order to zoom around the mouse point we need to translate @@ -227,7 +227,6 @@ sub new { } $self->_dirty(1); - $self->Refresh; }); EVT_MOUSE_EVENTS($self, \&mouse_event); @@ -255,8 +254,8 @@ sub mouse_event { return if !$self->GetParent->enabled; my $pos = Slic3r::Pointf->new($e->GetPositionXY); - if ($e->Entering && &Wx::wxMSW) { - # wxMSW needs focus in order to catch mouse wheel events + if ($e->Entering && (&Wx::wxMSW || $^O eq 'linux')) { + # wxMSW and Linux needs focus in order to catch key events $self->SetFocus; } elsif ($e->Dragging) { if ($e->LeftIsDown || $e->MiddleIsDown || $e->RightIsDown) { @@ -276,7 +275,6 @@ sub mouse_event { ); $self->_dirty(1); - $self->Refresh; } $self->_drag_start_xy($pos); } @@ -631,6 +629,27 @@ sub Resize { glLoadIdentity(); my $bb = $self->bb->clone; + + # rescale in dependence of window aspect ratio + my $bb_size = $bb->size; + my $ratio_x = ($x != 0.0) ? $bb_size->x / $x : 1.0; + my $ratio_y = ($y != 0.0) ? $bb_size->y / $y : 1.0; + + if ($ratio_y < $ratio_x) { + if ($ratio_y != 0.0) { + my $new_size_y = $bb_size->y * $ratio_x / $ratio_y; + my $half_delta_size_y = 0.5 * ($new_size_y - $bb_size->y); + $bb->set_y_min($bb->y_min - $half_delta_size_y); + $bb->set_y_max($bb->y_max + $half_delta_size_y); + } + } elsif ($ratio_x < $ratio_y) { + if ($ratio_x != 0.0) { + my $new_size_x = $bb_size->x * $ratio_y / $ratio_x; + my $half_delta_size_x = 0.5 * ($new_size_x - $bb_size->x); + $bb->set_x_min($bb->x_min - $half_delta_size_x); + $bb->set_x_max($bb->x_max + $half_delta_size_x); + } + } # center bounding box around origin before scaling it my $bb_center = $bb->center; @@ -645,25 +664,25 @@ sub Resize { # translate camera $bb->translate(@{$self->_camera_target}); - # keep camera_bb within total bb - # (i.e. prevent user from panning outside the bounding box) - { - my @translate = (0,0); - if ($bb->x_min < $self->bb->x_min) { - $translate[X] += $self->bb->x_min - $bb->x_min; - } - if ($bb->y_min < $self->bb->y_min) { - $translate[Y] += $self->bb->y_min - $bb->y_min; - } - if ($bb->x_max > $self->bb->x_max) { - $translate[X] -= $bb->x_max - $self->bb->x_max; - } - if ($bb->y_max > $self->bb->y_max) { - $translate[Y] -= $bb->y_max - $self->bb->y_max; - } - $self->_camera_target->translate(@translate); - $bb->translate(@translate); - } +# # keep camera_bb within total bb +# # (i.e. prevent user from panning outside the bounding box) +# { +# my @translate = (0,0); +# if ($bb->x_min < $self->bb->x_min) { +# $translate[X] += $self->bb->x_min - $bb->x_min; +# } +# if ($bb->y_min < $self->bb->y_min) { +# $translate[Y] += $self->bb->y_min - $bb->y_min; +# } +# if ($bb->x_max > $self->bb->x_max) { +# $translate[X] -= $bb->x_max - $self->bb->x_max; +# } +# if ($bb->y_max > $self->bb->y_max) { +# $translate[Y] -= $bb->y_max - $self->bb->y_max; +# } +# $self->_camera_target->translate(@translate); +# $bb->translate(@translate); +# } # save camera $self->_camera_bb($bb); 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/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 8300fdbed..a9763a824 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -101,7 +101,12 @@ sub export_gcode { die "The configured post-processing script is not executable: check permissions. ($script)\n"; } if ($^O eq 'MSWin32' && $script =~ /\.[pP][lL]/) { - system($^X, $script, $output_file); + # The current process (^X) may be slic3r.exe or slic3r-console.exe. + # Replace it with the current perl interpreter. + my($filename, $directories, $suffix) = fileparse($^X); + $filename =~ s/^slic3r.*$/perl5\.24\.0\.exe/; + my $interpreter = $directories . $filename; + system($interpreter, $script, $output_file); } else { system($script, $output_file); } diff --git a/resources/icons/bed/mk2_bottom.png b/resources/icons/bed/mk2_bottom.png Binary files differnew file mode 100644 index 000000000..d06de22eb --- /dev/null +++ b/resources/icons/bed/mk2_bottom.png diff --git a/resources/icons/bed/mk2_top.png b/resources/icons/bed/mk2_top.png Binary files differnew file mode 100644 index 000000000..142050c3a --- /dev/null +++ b/resources/icons/bed/mk2_top.png diff --git a/resources/icons/bed/mk3_bottom.png b/resources/icons/bed/mk3_bottom.png Binary files differnew file mode 100644 index 000000000..072c14dae --- /dev/null +++ b/resources/icons/bed/mk3_bottom.png diff --git a/resources/icons/bed/mk3_top.png b/resources/icons/bed/mk3_top.png Binary files differnew file mode 100644 index 000000000..0403c2f05 --- /dev/null +++ b/resources/icons/bed/mk3_top.png diff --git a/resources/icons/overlay/rotate_hover.png b/resources/icons/overlay/rotate_hover.png Binary files differnew file mode 100644 index 000000000..56d4fd277 --- /dev/null +++ b/resources/icons/overlay/rotate_hover.png diff --git a/resources/icons/overlay/rotate_off.png b/resources/icons/overlay/rotate_off.png Binary files differnew file mode 100644 index 000000000..8491f7e43 --- /dev/null +++ b/resources/icons/overlay/rotate_off.png diff --git a/resources/icons/overlay/rotate_on.png b/resources/icons/overlay/rotate_on.png Binary files differnew file mode 100644 index 000000000..e2db5120c --- /dev/null +++ b/resources/icons/overlay/rotate_on.png diff --git a/resources/icons/overlay/scale_hover.png b/resources/icons/overlay/scale_hover.png Binary files differnew file mode 100644 index 000000000..d620aee7a --- /dev/null +++ b/resources/icons/overlay/scale_hover.png diff --git a/resources/icons/overlay/scale_off.png b/resources/icons/overlay/scale_off.png Binary files differnew file mode 100644 index 000000000..1ae999bbe --- /dev/null +++ b/resources/icons/overlay/scale_off.png diff --git a/resources/icons/overlay/scale_on.png b/resources/icons/overlay/scale_on.png Binary files differnew file mode 100644 index 000000000..62e805f12 --- /dev/null +++ b/resources/icons/overlay/scale_on.png diff --git a/resources/localization/Slic3rPE.pot b/resources/localization/Slic3rPE.pot index 782f94c49..029e7c4b2 100644 --- a/resources/localization/Slic3rPE.pot +++ b/resources/localization/Slic3rPE.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-06-11 13:34+0200\n" +"POT-Creation-Date: 2018-06-26 11:11+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -33,7 +33,7 @@ msgid "Rectangular" msgstr "" #: xs/src/slic3r/GUI/BedShapeDialog.cpp:50 xs/src/slic3r/GUI/Tab.cpp:1745 -#: lib/Slic3r/GUI/Plater.pm:432 +#: lib/Slic3r/GUI/Plater.pm:477 msgid "Size" msgstr "" @@ -56,8 +56,10 @@ msgid "Circular" msgstr "" #: xs/src/slic3r/GUI/BedShapeDialog.cpp:65 -#: xs/src/slic3r/GUI/ConfigWizard.cpp:87 xs/src/slic3r/GUI/ConfigWizard.cpp:434 -#: xs/src/slic3r/GUI/ConfigWizard.cpp:448 xs/src/slic3r/GUI/RammingChart.cpp:81 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:87 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:439 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:453 +#: xs/src/slic3r/GUI/RammingChart.cpp:81 #: xs/src/slic3r/GUI/WipeTowerDialog.cpp:79 #: xs/src/libslic3r/PrintConfig.cpp:130 xs/src/libslic3r/PrintConfig.cpp:173 #: xs/src/libslic3r/PrintConfig.cpp:181 xs/src/libslic3r/PrintConfig.cpp:229 @@ -79,7 +81,8 @@ msgstr "" msgid "mm" msgstr "" -#: xs/src/slic3r/GUI/BedShapeDialog.cpp:66 xs/src/libslic3r/PrintConfig.cpp:494 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:66 +#: xs/src/libslic3r/PrintConfig.cpp:494 msgid "Diameter" msgstr "" @@ -121,7 +124,7 @@ msgid "" msgstr "" #: xs/src/slic3r/GUI/BedShapeDialog.hpp:44 -#: xs/src/slic3r/GUI/ConfigWizard.cpp:397 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:402 msgid "Bed Shape" msgstr "" @@ -239,7 +242,7 @@ msgstr "" msgid "Activate" msgstr "" -#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:96 xs/src/slic3r/GUI/GUI.cpp:321 +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:96 xs/src/slic3r/GUI/GUI.cpp:323 msgid "Configuration Snapshots" msgstr "" @@ -255,54 +258,54 @@ msgstr "" msgid "Select none" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:207 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:212 #, possible-c-format msgid "Welcome to the Slic3r %s" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:207 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:212 msgid "Welcome" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:213 xs/src/slic3r/GUI/GUI.cpp:318 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:218 xs/src/slic3r/GUI/GUI.cpp:320 #, possible-c-format msgid "Run %s" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:215 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:220 #, possible-c-format msgid "" "Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial " "configuration; just a few settings and you will be ready to print." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:219 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:224 msgid "" "Remove user profiles - install from scratch (a snapshot will be taken " "beforehand)" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:240 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:245 msgid "Other vendors" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:242 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:247 msgid "Custom setup" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:266 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:271 msgid "Automatic updates" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:266 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:271 msgid "Updates" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:274 xs/src/slic3r/GUI/Preferences.cpp:59 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:279 xs/src/slic3r/GUI/Preferences.cpp:59 msgid "Check for application updates" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:277 xs/src/slic3r/GUI/Preferences.cpp:61 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:282 xs/src/slic3r/GUI/Preferences.cpp:61 msgid "" "If enabled, Slic3r checks for new versions of Slic3r PE online. When a new " "version becomes available a notification is displayed at the next " @@ -310,11 +313,11 @@ msgid "" "notification mechanisms, no automatic installation is done." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:281 xs/src/slic3r/GUI/Preferences.cpp:67 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:286 xs/src/slic3r/GUI/Preferences.cpp:67 msgid "Update built-in Presets automatically" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:284 xs/src/slic3r/GUI/Preferences.cpp:69 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:289 xs/src/slic3r/GUI/Preferences.cpp:69 msgid "" "If enabled, Slic3r downloads updates of built-in system presets in the " "background. These updates are downloaded into a separate temporary location. " @@ -322,327 +325,339 @@ msgid "" "startup." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:285 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:290 msgid "" "Updates are never applied without user's consent and never overwrite user's " "customized settings." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:290 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:295 msgid "" "Additionally a backup snapshot of the whole configuration is created before " "an update is applied." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:297 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:302 msgid "Other Vendors" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:299 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:304 msgid "Pick another vendor supported by Slic3r PE:" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:358 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:363 msgid "Firmware Type" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:358 xs/src/slic3r/GUI/Tab.cpp:1628 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:363 xs/src/slic3r/GUI/Tab.cpp:1628 msgid "Firmware" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:362 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:367 msgid "Choose the type of firmware used by your printer." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:397 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:402 msgid "Bed Shape and Size" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:400 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:405 msgid "Set the shape of your printer's bed." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:414 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:419 msgid "Filament and Nozzle Diameters" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:414 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:419 msgid "Print Diameters" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:430 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:435 msgid "Enter the diameter of your printer's hot end nozzle." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:433 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:438 msgid "Nozzle Diameter:" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:443 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:448 msgid "Enter the diameter of your filament." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:444 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:449 msgid "" "Good precision is required, so use a caliper and do multiple measurements " "along the filament, then compute the average." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:447 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:452 msgid "Filament Diameter:" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:465 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:470 msgid "Extruder and Bed Temperatures" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:465 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:470 msgid "Temperatures" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:481 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:486 msgid "Enter the temperature needed for extruding your filament." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:482 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:487 msgid "A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:485 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:490 msgid "Extrusion Temperature:" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:486 -#: xs/src/slic3r/GUI/ConfigWizard.cpp:500 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:491 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:505 msgid "°C" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:495 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:500 msgid "" "Enter the bed temperature needed for getting your filament to stick to your " "heated bed." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:496 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:501 msgid "" "A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have " "no heated bed." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:499 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:504 msgid "Bed Temperature:" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:807 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:817 +msgid "< &Back" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:818 +msgid "&Next >" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:819 msgid "&Finish" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:865 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:889 msgid "Configuration Wizard" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:867 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:891 msgid "Configuration Assistant" msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:84 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:87 msgid "Flash!" msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:85 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:88 msgid "Cancel" msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:123 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:128 msgid "Flashing in progress. Please do not disconnect the printer!" msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:145 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:155 msgid "Flashing succeeded!" msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:146 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:156 msgid "Flashing failed. Please see the avrdude log below." msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:147 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:157 msgid "Flashing cancelled." msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:213 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:294 msgid "Cancelling..." msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:266 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:347 msgid "Firmware flasher" msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:286 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:367 msgid "Serial port:" msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:288 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:369 msgid "Rescan" msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:293 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:374 msgid "Firmware image:" msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:296 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:377 msgid "Status:" msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:297 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:378 msgid "Ready" msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:300 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:381 msgid "Progress:" msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:319 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:400 msgid "Advanced: avrdude output log" msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:365 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:446 msgid "" "Are you sure you want to cancel firmware flashing?\n" "This could leave your printer in an unusable state!" msgstr "" -#: xs/src/slic3r/GUI/FirmwareDialog.cpp:366 +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:447 msgid "Confirmation" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:206 +#: xs/src/slic3r/GUI/GLCanvas3D.cpp:1990 +msgid "Detected object outside print volume" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:208 msgid "Array of language names and identifiers should have the same size." msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:217 +#: xs/src/slic3r/GUI/GUI.cpp:219 msgid "Select the language" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:217 +#: xs/src/slic3r/GUI/GUI.cpp:219 msgid "Language" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:279 xs/src/libslic3r/PrintConfig.cpp:187 +#: xs/src/slic3r/GUI/GUI.cpp:281 xs/src/libslic3r/PrintConfig.cpp:187 msgid "Default" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:321 +#: xs/src/slic3r/GUI/GUI.cpp:323 msgid "Inspect / activate configuration snapshots" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:322 +#: xs/src/slic3r/GUI/GUI.cpp:324 msgid "Take Configuration Snapshot" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:322 +#: xs/src/slic3r/GUI/GUI.cpp:324 msgid "Capture a configuration snapshot" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:325 xs/src/slic3r/GUI/Preferences.cpp:9 +#: xs/src/slic3r/GUI/GUI.cpp:327 xs/src/slic3r/GUI/Preferences.cpp:9 msgid "Preferences" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:325 +#: xs/src/slic3r/GUI/GUI.cpp:327 msgid "Application preferences" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:326 +#: xs/src/slic3r/GUI/GUI.cpp:328 msgid "Change Application Language" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:328 +#: xs/src/slic3r/GUI/GUI.cpp:330 msgid "Flash printer firmware" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:328 +#: xs/src/slic3r/GUI/GUI.cpp:330 msgid "Upload a firmware image into an Arduino based printer" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:340 +#: xs/src/slic3r/GUI/GUI.cpp:342 msgid "Taking configuration snapshot" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:340 +#: xs/src/slic3r/GUI/GUI.cpp:342 msgid "Snapshot name" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:378 +#: xs/src/slic3r/GUI/GUI.cpp:380 msgid "Application will be restarted" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:378 +#: xs/src/slic3r/GUI/GUI.cpp:380 msgid "Attention!" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:393 +#: xs/src/slic3r/GUI/GUI.cpp:396 msgid "&Configuration" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:417 +#: xs/src/slic3r/GUI/GUI.cpp:420 msgid "You have unsaved changes " msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:417 +#: xs/src/slic3r/GUI/GUI.cpp:420 msgid ". Discard changes and continue anyway?" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:418 +#: xs/src/slic3r/GUI/GUI.cpp:421 msgid "Unsaved Presets" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:626 +#: xs/src/slic3r/GUI/GUI.cpp:629 msgid "Notice" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:631 +#: xs/src/slic3r/GUI/GUI.cpp:634 msgid "Attempt to free unreferenced scalar" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:633 xs/src/slic3r/GUI/WipeTowerDialog.cpp:39 +#: xs/src/slic3r/GUI/GUI.cpp:636 xs/src/slic3r/GUI/WipeTowerDialog.cpp:39 #: xs/src/slic3r/GUI/WipeTowerDialog.cpp:321 msgid "Warning" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:822 +#: xs/src/slic3r/GUI/GUI.cpp:825 msgid "Support" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:825 +#: xs/src/slic3r/GUI/GUI.cpp:828 msgid "Select what kind of support do you need" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:826 xs/src/libslic3r/GCode/PreviewData.cpp:157 +#: xs/src/slic3r/GUI/GUI.cpp:829 xs/src/libslic3r/GCode/PreviewData.cpp:157 msgid "None" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:827 xs/src/libslic3r/PrintConfig.cpp:1516 +#: xs/src/slic3r/GUI/GUI.cpp:830 xs/src/libslic3r/PrintConfig.cpp:1516 msgid "Support on build plate only" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:828 +#: xs/src/slic3r/GUI/GUI.cpp:831 msgid "Everywhere" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:840 xs/src/slic3r/GUI/Tab.cpp:872 +#: xs/src/slic3r/GUI/GUI.cpp:843 xs/src/slic3r/GUI/Tab.cpp:872 msgid "Brim" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:842 +#: xs/src/slic3r/GUI/GUI.cpp:845 msgid "" "This flag enables the brim that will be printed around each object on the " "first layer." msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:851 +#: xs/src/slic3r/GUI/GUI.cpp:854 msgid "Purging volumes" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:893 +#: xs/src/slic3r/GUI/GUI.cpp:896 msgid "Export print config" msgstr "" @@ -891,12 +906,12 @@ msgid "Profile dependencies" msgstr "" #: xs/src/slic3r/GUI/Tab.cpp:1011 xs/src/slic3r/GUI/Tab.cpp:1362 -#: xs/src/slic3r/GUI/Tab.cpp:2248 xs/src/libslic3r/PrintConfig.cpp:144 +#: xs/src/slic3r/GUI/Tab.cpp:2261 xs/src/libslic3r/PrintConfig.cpp:144 msgid "Compatible printers" msgstr "" #: xs/src/slic3r/GUI/Tab.cpp:1044 -#, possible-c-format +#, no-c-format msgid "" "The Spiral Vase mode requires:\n" "- one perimeter\n" @@ -952,7 +967,7 @@ msgid "The " msgstr "" #: xs/src/slic3r/GUI/Tab.cpp:1153 -#, possible-c-format +#, no-c-format msgid "" " infill pattern is not supposed to work at 100% density.\n" "\n" @@ -960,7 +975,7 @@ msgid "" msgstr "" #: xs/src/slic3r/GUI/Tab.cpp:1258 xs/src/slic3r/GUI/Tab.cpp:1259 -#: lib/Slic3r/GUI/Plater.pm:388 +#: lib/Slic3r/GUI/Plater.pm:433 msgid "Filament" msgstr "" @@ -1043,7 +1058,7 @@ msgstr "" msgid "Bed shape" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1446 xs/src/slic3r/GUI/Tab.cpp:2216 +#: xs/src/slic3r/GUI/Tab.cpp:1446 xs/src/slic3r/GUI/Tab.cpp:2229 msgid " Set " msgstr "" @@ -1083,7 +1098,7 @@ msgstr "" msgid "Connection failed." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1542 xs/src/slic3r/Utils/OctoPrint.cpp:50 +#: xs/src/slic3r/GUI/Tab.cpp:1542 xs/src/slic3r/Utils/OctoPrint.cpp:110 msgid "OctoPrint upload" msgstr "" @@ -1168,8 +1183,8 @@ msgid "" "setups)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1776 lib/Slic3r/GUI/Plater.pm:160 -#: lib/Slic3r/GUI/Plater.pm:2189 +#: xs/src/slic3r/GUI/Tab.cpp:1776 lib/Slic3r/GUI/Plater.pm:192 +#: lib/Slic3r/GUI/Plater.pm:2283 msgid "Preview" msgstr "" @@ -1228,74 +1243,74 @@ msgstr "" msgid "Unsaved Changes" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2126 +#: xs/src/slic3r/GUI/Tab.cpp:2139 msgid "The supplied name is empty. It can't be saved." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2131 +#: xs/src/slic3r/GUI/Tab.cpp:2144 msgid "Cannot overwrite a system profile." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2135 +#: xs/src/slic3r/GUI/Tab.cpp:2148 msgid "Cannot overwrite an external profile." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2159 +#: xs/src/slic3r/GUI/Tab.cpp:2172 msgid "remove" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2159 +#: xs/src/slic3r/GUI/Tab.cpp:2172 msgid "delete" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2160 +#: xs/src/slic3r/GUI/Tab.cpp:2173 msgid "Are you sure you want to " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2160 +#: xs/src/slic3r/GUI/Tab.cpp:2173 msgid " the selected preset?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2161 +#: xs/src/slic3r/GUI/Tab.cpp:2174 msgid "Remove" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2161 lib/Slic3r/GUI/Plater.pm:188 -#: lib/Slic3r/GUI/Plater.pm:206 lib/Slic3r/GUI/Plater.pm:2085 +#: xs/src/slic3r/GUI/Tab.cpp:2174 lib/Slic3r/GUI/Plater.pm:233 +#: lib/Slic3r/GUI/Plater.pm:251 lib/Slic3r/GUI/Plater.pm:2174 msgid "Delete" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2162 +#: xs/src/slic3r/GUI/Tab.cpp:2175 msgid " Preset" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2215 +#: xs/src/slic3r/GUI/Tab.cpp:2228 msgid "All" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2247 +#: xs/src/slic3r/GUI/Tab.cpp:2260 msgid "Select the printers this profile is compatible with." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2293 xs/src/slic3r/GUI/Tab.cpp:2379 +#: xs/src/slic3r/GUI/Tab.cpp:2306 xs/src/slic3r/GUI/Tab.cpp:2392 #: xs/src/slic3r/GUI/Preset.cpp:613 xs/src/slic3r/GUI/Preset.cpp:653 #: xs/src/slic3r/GUI/Preset.cpp:678 xs/src/slic3r/GUI/Preset.cpp:710 #: xs/src/slic3r/GUI/PresetBundle.cpp:1119 -#: xs/src/slic3r/GUI/PresetBundle.cpp:1172 lib/Slic3r/GUI/Plater.pm:573 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1172 lib/Slic3r/GUI/Plater.pm:618 msgid "System presets" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2294 xs/src/slic3r/GUI/Tab.cpp:2380 +#: xs/src/slic3r/GUI/Tab.cpp:2307 xs/src/slic3r/GUI/Tab.cpp:2393 msgid "Default presets" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2449 +#: xs/src/slic3r/GUI/Tab.cpp:2462 msgid "" "LOCKED LOCK;indicates that the settings are the same as the system values " "for the current option group" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2452 +#: xs/src/slic3r/GUI/Tab.cpp:2465 msgid "" "UNLOCKED LOCK;indicates that some settings were changed and are not equal to " "the system values for the current option group.\n" @@ -1303,13 +1318,13 @@ msgid "" "to the system values." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2458 +#: xs/src/slic3r/GUI/Tab.cpp:2471 msgid "" "WHITE BULLET;for the left button: \tindicates a non-system preset,\n" "for the right button: \tindicates that the settings hasn't been modified." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2462 +#: xs/src/slic3r/GUI/Tab.cpp:2475 msgid "" "BACK ARROW;indicates that the settings were changed and are not equal to the " "last saved preset for the current option group.\n" @@ -1317,30 +1332,30 @@ msgid "" "to the last saved preset." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2488 +#: xs/src/slic3r/GUI/Tab.cpp:2501 msgid "" "LOCKED LOCK icon indicates that the settings are the same as the system " "values for the current option group" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2490 +#: xs/src/slic3r/GUI/Tab.cpp:2503 msgid "" "UNLOCKED LOCK icon indicates that some settings were changed and are not " "equal to the system values for the current option group.\n" "Click to reset all settings for current option group to the system values." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2493 +#: xs/src/slic3r/GUI/Tab.cpp:2506 msgid "WHITE BULLET icon indicates a non system preset." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2496 +#: xs/src/slic3r/GUI/Tab.cpp:2509 msgid "" "WHITE BULLET icon indicates that the settings are the same as in the last " "saved preset for the current option group." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2498 +#: xs/src/slic3r/GUI/Tab.cpp:2511 msgid "" "BACK ARROW icon indicates that the settings were changed and are not equal " "to the last saved preset for the current option group.\n" @@ -1348,46 +1363,53 @@ msgid "" "preset." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2504 +#: xs/src/slic3r/GUI/Tab.cpp:2517 msgid "" "LOCKED LOCK icon indicates that the value is the same as the system value." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2505 +#: xs/src/slic3r/GUI/Tab.cpp:2518 msgid "" "UNLOCKED LOCK icon indicates that the value was changed and is not equal to " "the system value.\n" "Click to reset current value to the system value." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2511 +#: xs/src/slic3r/GUI/Tab.cpp:2524 msgid "" "WHITE BULLET icon indicates that the value is the same as in the last saved " "preset." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2512 +#: xs/src/slic3r/GUI/Tab.cpp:2525 msgid "" "BACK ARROW icon indicates that the value was changed and is not equal to the " "last saved preset.\n" "Click to reset current value to the last saved preset." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2582 lib/Slic3r/GUI/MainFrame.pm:448 -#: lib/Slic3r/GUI/Plater.pm:1685 +#: xs/src/slic3r/GUI/Tab.cpp:2595 lib/Slic3r/GUI/MainFrame.pm:450 +#: lib/Slic3r/GUI/Plater.pm:1756 msgid "Save " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2582 +#: xs/src/slic3r/GUI/Tab.cpp:2595 msgid " as:" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2616 -msgid "" -"The supplied name is not valid; the following characters are not allowed:" +#: xs/src/slic3r/GUI/Tab.cpp:2634 xs/src/slic3r/GUI/Tab.cpp:2638 +msgid "The supplied name is not valid;" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2635 +msgid "the following characters are not allowed:" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2619 +#: xs/src/slic3r/GUI/Tab.cpp:2639 +msgid "the following postfix are not allowed:" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2642 msgid "The supplied name is not available." msgstr "" @@ -1407,21 +1429,21 @@ msgstr "" msgid "Save preset" msgstr "" -#: xs/src/slic3r/GUI/Field.cpp:72 +#: xs/src/slic3r/GUI/Field.cpp:82 msgid "default" msgstr "" -#: xs/src/slic3r/GUI/Field.cpp:102 +#: xs/src/slic3r/GUI/Field.cpp:112 #, possible-c-format msgid "%s doesn't support percentage" msgstr "" -#: xs/src/slic3r/GUI/Field.cpp:111 +#: xs/src/slic3r/GUI/Field.cpp:121 msgid "Input value is out of range" msgstr "" #: xs/src/slic3r/GUI/Preset.cpp:657 xs/src/slic3r/GUI/Preset.cpp:714 -#: xs/src/slic3r/GUI/PresetBundle.cpp:1177 lib/Slic3r/GUI/Plater.pm:574 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1177 lib/Slic3r/GUI/Plater.pm:619 msgid "User presets" msgstr "" @@ -1834,19 +1856,35 @@ msgstr "" msgid "Show advanced settings" msgstr "" -#: xs/src/slic3r/Utils/OctoPrint.cpp:47 +#: xs/src/slic3r/Utils/OctoPrint.cpp:33 +msgid "Send G-Code to printer" +msgstr "" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:33 +msgid "Upload to OctoPrint with the following filename:" +msgstr "" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:35 +msgid "Start printing after upload" +msgstr "" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:37 +msgid "Use forward slashes ( / ) as a directory separator if needed." +msgstr "" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:98 msgid "Error while uploading to the OctoPrint server" msgstr "" -#: xs/src/slic3r/Utils/OctoPrint.cpp:51 lib/Slic3r/GUI/Plater.pm:1516 +#: xs/src/slic3r/Utils/OctoPrint.cpp:111 lib/Slic3r/GUI/Plater.pm:1559 msgid "Sending G-code file to the OctoPrint server..." msgstr "" -#: xs/src/slic3r/Utils/OctoPrint.cpp:120 +#: xs/src/slic3r/Utils/OctoPrint.cpp:192 msgid "Invalid API key" msgstr "" -#: xs/src/slic3r/Utils/PresetUpdater.cpp:514 +#: xs/src/slic3r/Utils/PresetUpdater.cpp:533 #, possible-c-format msgid "requires min. %s and max. %s" msgstr "" @@ -1893,11 +1931,11 @@ msgid "" "default extruder and bed temperature are reset using non-wait command; " "however if M104, M109, M140 or M190 are detected in this custom code, Slic3r " "will not add temperature commands. Note that you can use placeholder " -"variables for all Slic3r settings, so you can put a \"M109 " -"S[first_layer_temperature]\" command wherever you want." +"variables for all Slic3r settings, so you can put a \"M109 S" +"[first_layer_temperature]\" command wherever you want." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:68 lib/Slic3r/GUI/MainFrame.pm:307 +#: xs/src/libslic3r/PrintConfig.cpp:68 lib/Slic3r/GUI/MainFrame.pm:309 msgid "Bottom" msgstr "" @@ -2226,7 +2264,7 @@ msgid "Extra perimeters if needed" msgstr "" #: xs/src/libslic3r/PrintConfig.cpp:328 -#, possible-c-format +#, no-c-format msgid "" "Add more perimeters when needed for avoiding gaps in sloping walls. Slic3r " "keeps adding perimeters, until more than 70% of the loop immediately above " @@ -2457,7 +2495,7 @@ msgstr "" msgid "Soluble material is most likely used for a soluble support." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:539 lib/Slic3r/GUI/Plater.pm:474 +#: xs/src/libslic3r/PrintConfig.cpp:539 lib/Slic3r/GUI/Plater.pm:519 msgid "Cost" msgstr "" @@ -2487,6 +2525,7 @@ msgid "Fill density" msgstr "" #: xs/src/libslic3r/PrintConfig.cpp:566 +#, no-c-format msgid "Density of internal infill, expressed in the range 0% - 100%." msgstr "" @@ -2710,7 +2749,7 @@ msgid "This setting represents the maximum speed of your fan." msgstr "" #: xs/src/libslic3r/PrintConfig.cpp:867 -#, possible-c-format +#, no-c-format msgid "" "This is the highest printable layer height for this extruder, used to cap " "the variable layer height and support layer height. Maximum recommended " @@ -3324,8 +3363,8 @@ msgid "" "detects M104, M109, M140 or M190 in your custom codes, such commands will " "not be prepended automatically so you're free to customize the order of " "heating commands and other custom actions. Note that you can use placeholder " -"variables for all Slic3r settings, so you can put a \"M109 " -"S[first_layer_temperature]\" command wherever you want. If you have multiple " +"variables for all Slic3r settings, so you can put a \"M109 S" +"[first_layer_temperature]\" command wherever you want. If you have multiple " "extruders, the gcode is processed in extruder order." msgstr "" @@ -3580,7 +3619,7 @@ msgid "" "for auto." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1744 lib/Slic3r/GUI/MainFrame.pm:306 +#: xs/src/libslic3r/PrintConfig.cpp:1744 lib/Slic3r/GUI/MainFrame.pm:308 msgid "Top" msgstr "" @@ -3592,7 +3631,8 @@ msgstr "" msgid "Top solid layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1753 lib/Slic3r/GUI/Plater/3DPreview.pm:105 +#: xs/src/libslic3r/PrintConfig.cpp:1753 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:105 msgid "Travel" msgstr "" @@ -3803,7 +3843,7 @@ msgstr "" msgid "Tool" msgstr "" -#: lib/Slic3r/GUI.pm:308 +#: lib/Slic3r/GUI.pm:307 msgid "Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):" msgstr "" @@ -3816,891 +3856,898 @@ msgid "" " - Remember to check for updates at http://github.com/prusa3d/slic3r/releases" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:114 +#: lib/Slic3r/GUI/MainFrame.pm:116 msgid "Plater" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:116 +#: lib/Slic3r/GUI/MainFrame.pm:118 msgid "Controller" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:194 +#: lib/Slic3r/GUI/MainFrame.pm:196 msgid "Open STL/OBJ/AMF/3MF…\tCtrl+O" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:194 +#: lib/Slic3r/GUI/MainFrame.pm:196 msgid "Open a model" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:197 +#: lib/Slic3r/GUI/MainFrame.pm:199 msgid "&Load Config…\tCtrl+L" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:197 +#: lib/Slic3r/GUI/MainFrame.pm:199 msgid "Load exported configuration file" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:200 +#: lib/Slic3r/GUI/MainFrame.pm:202 msgid "&Export Config…\tCtrl+E" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:200 +#: lib/Slic3r/GUI/MainFrame.pm:202 msgid "Export current configuration to file" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:203 +#: lib/Slic3r/GUI/MainFrame.pm:205 msgid "&Load Config Bundle…" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:203 +#: lib/Slic3r/GUI/MainFrame.pm:205 msgid "Load presets from a bundle" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:206 +#: lib/Slic3r/GUI/MainFrame.pm:208 msgid "&Export Config Bundle…" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:206 +#: lib/Slic3r/GUI/MainFrame.pm:208 msgid "Export all presets to file" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:211 +#: lib/Slic3r/GUI/MainFrame.pm:213 msgid "Q&uick Slice…\tCtrl+U" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:211 +#: lib/Slic3r/GUI/MainFrame.pm:213 msgid "Slice a file into a G-code" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:217 +#: lib/Slic3r/GUI/MainFrame.pm:219 msgid "Quick Slice and Save &As…\tCtrl+Alt+U" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:217 +#: lib/Slic3r/GUI/MainFrame.pm:219 msgid "Slice a file into a G-code, save as" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:223 +#: lib/Slic3r/GUI/MainFrame.pm:225 msgid "&Repeat Last Quick Slice\tCtrl+Shift+U" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:223 +#: lib/Slic3r/GUI/MainFrame.pm:225 msgid "Repeat last quick slice" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:230 +#: lib/Slic3r/GUI/MainFrame.pm:232 msgid "Slice to SV&G…\tCtrl+G" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:230 +#: lib/Slic3r/GUI/MainFrame.pm:232 msgid "Slice file to a multi-layer SVG" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:234 +#: lib/Slic3r/GUI/MainFrame.pm:236 msgid "(&Re)Slice Now\tCtrl+S" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:234 +#: lib/Slic3r/GUI/MainFrame.pm:236 msgid "Start new slicing process" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:237 +#: lib/Slic3r/GUI/MainFrame.pm:239 msgid "Repair STL file…" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:237 +#: lib/Slic3r/GUI/MainFrame.pm:239 msgid "Automatically repair an STL file" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:241 +#: lib/Slic3r/GUI/MainFrame.pm:243 msgid "&Quit" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:241 +#: lib/Slic3r/GUI/MainFrame.pm:243 msgid "Quit Slic3r" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:251 +#: lib/Slic3r/GUI/MainFrame.pm:253 msgid "Export G-code..." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:251 +#: lib/Slic3r/GUI/MainFrame.pm:253 msgid "Export current plate as G-code" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:254 +#: lib/Slic3r/GUI/MainFrame.pm:256 msgid "Export plate as STL..." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:254 +#: lib/Slic3r/GUI/MainFrame.pm:256 msgid "Export current plate as STL" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:257 +#: lib/Slic3r/GUI/MainFrame.pm:259 msgid "Export plate as AMF..." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:257 +#: lib/Slic3r/GUI/MainFrame.pm:259 msgid "Export current plate as AMF" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:260 +#: lib/Slic3r/GUI/MainFrame.pm:262 msgid "Export plate as 3MF..." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:260 +#: lib/Slic3r/GUI/MainFrame.pm:262 msgid "Export current plate as 3MF" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:273 +#: lib/Slic3r/GUI/MainFrame.pm:275 msgid "Select &Plater Tab\tCtrl+1" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:273 +#: lib/Slic3r/GUI/MainFrame.pm:275 msgid "Show the plater" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:279 +#: lib/Slic3r/GUI/MainFrame.pm:281 msgid "Select &Controller Tab\tCtrl+T" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:279 +#: lib/Slic3r/GUI/MainFrame.pm:281 msgid "Show the printer controller" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:287 +#: lib/Slic3r/GUI/MainFrame.pm:289 msgid "Select P&rint Settings Tab\tCtrl+2" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:287 +#: lib/Slic3r/GUI/MainFrame.pm:289 msgid "Show the print settings" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:290 +#: lib/Slic3r/GUI/MainFrame.pm:292 msgid "Select &Filament Settings Tab\tCtrl+3" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:290 +#: lib/Slic3r/GUI/MainFrame.pm:292 msgid "Show the filament settings" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:293 +#: lib/Slic3r/GUI/MainFrame.pm:295 msgid "Select Print&er Settings Tab\tCtrl+4" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:293 +#: lib/Slic3r/GUI/MainFrame.pm:295 msgid "Show the printer settings" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:305 +#: lib/Slic3r/GUI/MainFrame.pm:307 msgid "Iso" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:305 +#: lib/Slic3r/GUI/MainFrame.pm:307 msgid "Iso View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:306 +#: lib/Slic3r/GUI/MainFrame.pm:308 msgid "Top View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:307 +#: lib/Slic3r/GUI/MainFrame.pm:309 msgid "Bottom View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:308 +#: lib/Slic3r/GUI/MainFrame.pm:310 msgid "Front" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:308 +#: lib/Slic3r/GUI/MainFrame.pm:310 msgid "Front View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:309 +#: lib/Slic3r/GUI/MainFrame.pm:311 msgid "Rear" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:309 +#: lib/Slic3r/GUI/MainFrame.pm:311 msgid "Rear View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:310 +#: lib/Slic3r/GUI/MainFrame.pm:312 msgid "Left" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:310 +#: lib/Slic3r/GUI/MainFrame.pm:312 msgid "Left View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:311 +#: lib/Slic3r/GUI/MainFrame.pm:313 msgid "Right" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:311 +#: lib/Slic3r/GUI/MainFrame.pm:313 msgid "Right View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:317 +#: lib/Slic3r/GUI/MainFrame.pm:319 msgid "Prusa 3D Drivers" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:317 +#: lib/Slic3r/GUI/MainFrame.pm:319 msgid "Open the Prusa3D drivers download page in your browser" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:320 +#: lib/Slic3r/GUI/MainFrame.pm:322 msgid "Prusa Edition Releases" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:320 +#: lib/Slic3r/GUI/MainFrame.pm:322 msgid "Open the Prusa Edition releases page in your browser" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:327 +#: lib/Slic3r/GUI/MainFrame.pm:329 msgid "Slic3r &Website" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:327 +#: lib/Slic3r/GUI/MainFrame.pm:329 msgid "Open the Slic3r website in your browser" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:330 +#: lib/Slic3r/GUI/MainFrame.pm:332 msgid "Slic3r &Manual" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:330 +#: lib/Slic3r/GUI/MainFrame.pm:332 msgid "Open the Slic3r manual in your browser" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:334 +#: lib/Slic3r/GUI/MainFrame.pm:336 msgid "System Info" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:334 +#: lib/Slic3r/GUI/MainFrame.pm:336 msgid "Show system information" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:337 +#: lib/Slic3r/GUI/MainFrame.pm:339 msgid "Show &Configuration Folder" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:337 +#: lib/Slic3r/GUI/MainFrame.pm:339 msgid "Show user configuration folder (datadir)" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:340 +#: lib/Slic3r/GUI/MainFrame.pm:342 msgid "Report an Issue" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:340 +#: lib/Slic3r/GUI/MainFrame.pm:342 msgid "Report an issue on the Slic3r Prusa Edition" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:343 +#: lib/Slic3r/GUI/MainFrame.pm:345 msgid "&About Slic3r" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:343 +#: lib/Slic3r/GUI/MainFrame.pm:345 msgid "Show about dialog" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:353 +#: lib/Slic3r/GUI/MainFrame.pm:355 msgid "&File" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:354 +#: lib/Slic3r/GUI/MainFrame.pm:356 msgid "&Plater" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:355 +#: lib/Slic3r/GUI/MainFrame.pm:357 msgid "&Object" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:356 +#: lib/Slic3r/GUI/MainFrame.pm:358 msgid "&Window" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:357 +#: lib/Slic3r/GUI/MainFrame.pm:359 msgid "&View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:360 +#: lib/Slic3r/GUI/MainFrame.pm:362 msgid "&Help" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:391 +#: lib/Slic3r/GUI/MainFrame.pm:393 msgid "Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:403 +#: lib/Slic3r/GUI/MainFrame.pm:405 msgid "No previously sliced file." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:404 lib/Slic3r/GUI/Plater.pm:1363 +#: lib/Slic3r/GUI/MainFrame.pm:406 lib/Slic3r/GUI/Plater.pm:1406 msgid "Error" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:408 +#: lib/Slic3r/GUI/MainFrame.pm:410 msgid "Previously sliced file (" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:408 +#: lib/Slic3r/GUI/MainFrame.pm:410 msgid ") not found." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:409 +#: lib/Slic3r/GUI/MainFrame.pm:411 msgid "File Not Found" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:448 +#: lib/Slic3r/GUI/MainFrame.pm:450 msgid "SVG" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:448 +#: lib/Slic3r/GUI/MainFrame.pm:450 msgid "G-code" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:448 lib/Slic3r/GUI/Plater.pm:1685 +#: lib/Slic3r/GUI/MainFrame.pm:450 lib/Slic3r/GUI/Plater.pm:1756 msgid " file as:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:462 +#: lib/Slic3r/GUI/MainFrame.pm:464 msgid "Slicing…" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:462 +#: lib/Slic3r/GUI/MainFrame.pm:464 msgid "Processing " msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:482 +#: lib/Slic3r/GUI/MainFrame.pm:484 msgid " was successfully sliced." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:484 +#: lib/Slic3r/GUI/MainFrame.pm:486 msgid "Slicing Done!" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:500 +#: lib/Slic3r/GUI/MainFrame.pm:502 msgid "Select the STL file to repair:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:514 +#: lib/Slic3r/GUI/MainFrame.pm:516 msgid "Save OBJ file (less prone to coordinate errors than STL) as:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:528 +#: lib/Slic3r/GUI/MainFrame.pm:530 msgid "Your file was repaired." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:528 +#: lib/Slic3r/GUI/MainFrame.pm:530 msgid "Repair" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:539 +#: lib/Slic3r/GUI/MainFrame.pm:541 msgid "Save configuration as:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:557 lib/Slic3r/GUI/MainFrame.pm:601 +#: lib/Slic3r/GUI/MainFrame.pm:559 lib/Slic3r/GUI/MainFrame.pm:603 msgid "Select configuration to load:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:580 +#: lib/Slic3r/GUI/MainFrame.pm:582 msgid "Save presets bundle as:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:621 +#: lib/Slic3r/GUI/MainFrame.pm:623 #, possible-perl-format msgid "%d presets successfully imported." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:112 lib/Slic3r/GUI/Plater.pm:2188 +#: lib/Slic3r/GUI/Plater.pm:140 lib/Slic3r/GUI/Plater.pm:2282 msgid "3D" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:148 +#: lib/Slic3r/GUI/Plater.pm:180 msgid "2D" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:167 +#: lib/Slic3r/GUI/Plater.pm:199 msgid "Layers" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:187 lib/Slic3r/GUI/Plater.pm:205 +#: lib/Slic3r/GUI/Plater.pm:232 lib/Slic3r/GUI/Plater.pm:250 msgid "Add…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:189 lib/Slic3r/GUI/Plater.pm:207 +#: lib/Slic3r/GUI/Plater.pm:234 lib/Slic3r/GUI/Plater.pm:252 msgid "Delete All" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:190 lib/Slic3r/GUI/Plater.pm:208 +#: lib/Slic3r/GUI/Plater.pm:235 lib/Slic3r/GUI/Plater.pm:253 msgid "Arrange" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:192 +#: lib/Slic3r/GUI/Plater.pm:237 msgid "More" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:193 +#: lib/Slic3r/GUI/Plater.pm:238 msgid "Fewer" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:195 +#: lib/Slic3r/GUI/Plater.pm:240 msgid "45° ccw" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:196 +#: lib/Slic3r/GUI/Plater.pm:241 msgid "45° cw" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:197 lib/Slic3r/GUI/Plater.pm:213 +#: lib/Slic3r/GUI/Plater.pm:242 lib/Slic3r/GUI/Plater.pm:258 msgid "Scale…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:198 lib/Slic3r/GUI/Plater.pm:214 -#: lib/Slic3r/GUI/Plater.pm:2163 +#: lib/Slic3r/GUI/Plater.pm:243 lib/Slic3r/GUI/Plater.pm:259 +#: lib/Slic3r/GUI/Plater.pm:2252 msgid "Split" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:199 lib/Slic3r/GUI/Plater.pm:215 -#: lib/Slic3r/GUI/Plater.pm:2166 +#: lib/Slic3r/GUI/Plater.pm:244 lib/Slic3r/GUI/Plater.pm:260 +#: lib/Slic3r/GUI/Plater.pm:2255 msgid "Cut…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:201 lib/Slic3r/GUI/Plater.pm:216 -#: lib/Slic3r/GUI/Plater.pm:2170 +#: lib/Slic3r/GUI/Plater.pm:246 lib/Slic3r/GUI/Plater.pm:261 +#: lib/Slic3r/GUI/Plater.pm:2259 msgid "Settings…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:202 +#: lib/Slic3r/GUI/Plater.pm:247 msgid "Layer Editing" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:217 +#: lib/Slic3r/GUI/Plater.pm:262 msgid "Layer editing" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:240 +#: lib/Slic3r/GUI/Plater.pm:285 msgid "Name" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:241 lib/Slic3r/GUI/Plater.pm:963 +#: lib/Slic3r/GUI/Plater.pm:286 lib/Slic3r/GUI/Plater.pm:1007 msgid "Copies" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:242 lib/Slic3r/GUI/Plater.pm:1119 -#: lib/Slic3r/GUI/Plater.pm:1124 lib/Slic3r/GUI/Plater.pm:2132 +#: lib/Slic3r/GUI/Plater.pm:287 lib/Slic3r/GUI/Plater.pm:1163 +#: lib/Slic3r/GUI/Plater.pm:1168 lib/Slic3r/GUI/Plater.pm:2221 msgid "Scale" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:256 +#: lib/Slic3r/GUI/Plater.pm:301 msgid "Export G-code…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:257 +#: lib/Slic3r/GUI/Plater.pm:302 msgid "Slice now" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:258 +#: lib/Slic3r/GUI/Plater.pm:303 msgid "Print…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:259 +#: lib/Slic3r/GUI/Plater.pm:304 msgid "Send to printer" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:260 +#: lib/Slic3r/GUI/Plater.pm:305 msgid "Export STL…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:387 +#: lib/Slic3r/GUI/Plater.pm:432 msgid "Print settings" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:389 +#: lib/Slic3r/GUI/Plater.pm:434 msgid "Printer" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:422 +#: lib/Slic3r/GUI/Plater.pm:467 msgid "Info" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:433 +#: lib/Slic3r/GUI/Plater.pm:478 msgid "Volume" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:434 +#: lib/Slic3r/GUI/Plater.pm:479 msgid "Facets" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:435 +#: lib/Slic3r/GUI/Plater.pm:480 msgid "Materials" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:436 +#: lib/Slic3r/GUI/Plater.pm:481 msgid "Manifold" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:462 +#: lib/Slic3r/GUI/Plater.pm:507 msgid "Sliced Info" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:471 +#: lib/Slic3r/GUI/Plater.pm:516 msgid "Used Filament (m)" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:472 +#: lib/Slic3r/GUI/Plater.pm:517 msgid "Used Filament (mm³)" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:473 +#: lib/Slic3r/GUI/Plater.pm:518 msgid "Used Filament (g)" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:475 +#: lib/Slic3r/GUI/Plater.pm:520 msgid "Estimated printing time" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:683 +#: lib/Slic3r/GUI/Plater.pm:728 msgid "Loading…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:683 lib/Slic3r/GUI/Plater.pm:697 +#: lib/Slic3r/GUI/Plater.pm:728 lib/Slic3r/GUI/Plater.pm:742 msgid "Processing input file\n" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:720 +#: lib/Slic3r/GUI/Plater.pm:765 msgid "" "This file contains several objects positioned at multiple heights. Instead " "of considering them as multiple objects, should I consider\n" "this file as a single object having multiple parts?\n" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:723 lib/Slic3r/GUI/Plater.pm:740 +#: lib/Slic3r/GUI/Plater.pm:768 lib/Slic3r/GUI/Plater.pm:785 msgid "Multi-part object detected" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:737 +#: lib/Slic3r/GUI/Plater.pm:782 msgid "" "Multiple objects were loaded for a multi-material printer.\n" "Instead of considering them as multiple objects, should I consider\n" "these files to represent a single object having multiple parts?\n" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:749 +#: lib/Slic3r/GUI/Plater.pm:794 msgid "Loaded " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:807 +#: lib/Slic3r/GUI/Plater.pm:852 msgid "" "Your object appears to be too large, so it was automatically scaled down to " "fit your print bed." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:808 +#: lib/Slic3r/GUI/Plater.pm:853 msgid "Object too large?" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:963 +#: lib/Slic3r/GUI/Plater.pm:1007 msgid "Enter the number of copies of the selected object:" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:990 +#: lib/Slic3r/GUI/Plater.pm:1034 msgid "" "\n" "Non-positive value." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:991 +#: lib/Slic3r/GUI/Plater.pm:1035 msgid "" "\n" "Not a numeric value." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:992 +#: lib/Slic3r/GUI/Plater.pm:1036 msgid "Slic3r Error" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1093 +#: lib/Slic3r/GUI/Plater.pm:1057 +msgid "Enter the rotation angle:" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:1057 +msgid "Rotate around " +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:1057 +msgid "Invalid rotation angle entered" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:1137 #, possible-perl-format msgid "Enter the new size for the selected object (print bed: %smm):" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1094 lib/Slic3r/GUI/Plater.pm:1098 +#: lib/Slic3r/GUI/Plater.pm:1138 lib/Slic3r/GUI/Plater.pm:1142 msgid "Scale along " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1094 lib/Slic3r/GUI/Plater.pm:1098 -#: lib/Slic3r/GUI/Plater.pm:1119 lib/Slic3r/GUI/Plater.pm:1124 +#: lib/Slic3r/GUI/Plater.pm:1138 lib/Slic3r/GUI/Plater.pm:1142 +#: lib/Slic3r/GUI/Plater.pm:1163 lib/Slic3r/GUI/Plater.pm:1168 msgid "Invalid scaling value entered" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1098 lib/Slic3r/GUI/Plater.pm:1124 -#, possible-perl-format +#: lib/Slic3r/GUI/Plater.pm:1142 lib/Slic3r/GUI/Plater.pm:1168 +#, no-perl-format msgid "Enter the scale % for the selected object:" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1119 +#: lib/Slic3r/GUI/Plater.pm:1163 msgid "Enter the new max size for the selected object:" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1175 +#: lib/Slic3r/GUI/Plater.pm:1219 msgid "" "The selected object can't be split because it contains more than one volume/" "material." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1184 +#: lib/Slic3r/GUI/Plater.pm:1228 msgid "" "The selected object couldn't be split because it contains only one part." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1349 +#: lib/Slic3r/GUI/Plater.pm:1392 msgid "Slicing cancelled" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1363 +#: lib/Slic3r/GUI/Plater.pm:1406 msgid "Another export job is currently running." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1399 -msgid "Save G-code file as:" -msgstr "" - -#: lib/Slic3r/GUI/Plater.pm:1416 -msgid "Export cancelled" -msgstr "" - -#: lib/Slic3r/GUI/Plater.pm:1513 +#: lib/Slic3r/GUI/Plater.pm:1556 msgid "File added to print queue" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1519 +#: lib/Slic3r/GUI/Plater.pm:1562 msgid "G-code file exported to " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1522 +#: lib/Slic3r/GUI/Plater.pm:1565 msgid "Export failed" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1534 +#: lib/Slic3r/GUI/Plater.pm:1577 msgid "OctoPrint upload finished." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1577 lib/Slic3r/GUI/Plater.pm:1619 +#: lib/Slic3r/GUI/Plater.pm:1620 lib/Slic3r/GUI/Plater.pm:1662 msgid "STL file exported to " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1630 +#: lib/Slic3r/GUI/Plater.pm:1701 msgid "AMF file exported to " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1634 +#: lib/Slic3r/GUI/Plater.pm:1705 msgid "Error exporting AMF file " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1646 +#: lib/Slic3r/GUI/Plater.pm:1717 msgid "3MF file exported to " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1650 +#: lib/Slic3r/GUI/Plater.pm:1721 msgid "Error exporting 3MF file " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1897 -msgid "" -"Please install the OpenGL modules to use this feature (see build " -"instructions)." -msgstr "" - -#: lib/Slic3r/GUI/Plater.pm:2010 +#: lib/Slic3r/GUI/Plater.pm:2099 #, possible-perl-format msgid "%d (%d shells)" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2012 +#: lib/Slic3r/GUI/Plater.pm:2101 #, possible-perl-format msgid "Auto-repaired (%d errors)" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2017 +#: lib/Slic3r/GUI/Plater.pm:2106 #, possible-perl-format msgid "" "%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d " "facets reversed, %d backwards edges" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2022 +#: lib/Slic3r/GUI/Plater.pm:2111 msgid "Yes" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2085 +#: lib/Slic3r/GUI/Plater.pm:2174 msgid "Remove the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2088 +#: lib/Slic3r/GUI/Plater.pm:2177 msgid "Increase copies" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2088 +#: lib/Slic3r/GUI/Plater.pm:2177 msgid "Place one more copy of the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2091 +#: lib/Slic3r/GUI/Plater.pm:2180 msgid "Decrease copies" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2091 +#: lib/Slic3r/GUI/Plater.pm:2180 msgid "Remove one copy of the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2094 +#: lib/Slic3r/GUI/Plater.pm:2183 msgid "Set number of copies…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2094 +#: lib/Slic3r/GUI/Plater.pm:2183 msgid "Change the number of copies of the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2098 +#: lib/Slic3r/GUI/Plater.pm:2187 msgid "Rotate 45° clockwise" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2098 +#: lib/Slic3r/GUI/Plater.pm:2187 msgid "Rotate the selected object by 45° clockwise" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2101 +#: lib/Slic3r/GUI/Plater.pm:2190 msgid "Rotate 45° counter-clockwise" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2101 +#: lib/Slic3r/GUI/Plater.pm:2190 msgid "Rotate the selected object by 45° counter-clockwise" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2106 +#: lib/Slic3r/GUI/Plater.pm:2195 msgid "Rotate" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2106 +#: lib/Slic3r/GUI/Plater.pm:2195 msgid "Rotate the selected object by an arbitrary angle" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2108 +#: lib/Slic3r/GUI/Plater.pm:2197 msgid "Around X axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2108 +#: lib/Slic3r/GUI/Plater.pm:2197 msgid "Rotate the selected object by an arbitrary angle around X axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2111 +#: lib/Slic3r/GUI/Plater.pm:2200 msgid "Around Y axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2111 +#: lib/Slic3r/GUI/Plater.pm:2200 msgid "Rotate the selected object by an arbitrary angle around Y axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2114 +#: lib/Slic3r/GUI/Plater.pm:2203 msgid "Around Z axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2114 +#: lib/Slic3r/GUI/Plater.pm:2203 msgid "Rotate the selected object by an arbitrary angle around Z axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2119 +#: lib/Slic3r/GUI/Plater.pm:2208 msgid "Mirror" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2119 +#: lib/Slic3r/GUI/Plater.pm:2208 msgid "Mirror the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2121 lib/Slic3r/GUI/Plater.pm:2137 -#: lib/Slic3r/GUI/Plater.pm:2153 +#: lib/Slic3r/GUI/Plater.pm:2210 lib/Slic3r/GUI/Plater.pm:2226 +#: lib/Slic3r/GUI/Plater.pm:2242 msgid "Along X axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2121 +#: lib/Slic3r/GUI/Plater.pm:2210 msgid "Mirror the selected object along the X axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2124 lib/Slic3r/GUI/Plater.pm:2140 -#: lib/Slic3r/GUI/Plater.pm:2156 +#: lib/Slic3r/GUI/Plater.pm:2213 lib/Slic3r/GUI/Plater.pm:2229 +#: lib/Slic3r/GUI/Plater.pm:2245 msgid "Along Y axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2124 +#: lib/Slic3r/GUI/Plater.pm:2213 msgid "Mirror the selected object along the Y axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2127 lib/Slic3r/GUI/Plater.pm:2143 -#: lib/Slic3r/GUI/Plater.pm:2159 +#: lib/Slic3r/GUI/Plater.pm:2216 lib/Slic3r/GUI/Plater.pm:2232 +#: lib/Slic3r/GUI/Plater.pm:2248 msgid "Along Z axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2127 +#: lib/Slic3r/GUI/Plater.pm:2216 msgid "Mirror the selected object along the Z axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2132 lib/Slic3r/GUI/Plater.pm:2148 +#: lib/Slic3r/GUI/Plater.pm:2221 lib/Slic3r/GUI/Plater.pm:2237 msgid "Scale the selected object along a single axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2134 lib/Slic3r/GUI/Plater.pm:2150 +#: lib/Slic3r/GUI/Plater.pm:2223 lib/Slic3r/GUI/Plater.pm:2239 msgid "Uniformly…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2134 lib/Slic3r/GUI/Plater.pm:2150 +#: lib/Slic3r/GUI/Plater.pm:2223 lib/Slic3r/GUI/Plater.pm:2239 msgid "Scale the selected object along the XYZ axes" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2137 lib/Slic3r/GUI/Plater.pm:2153 +#: lib/Slic3r/GUI/Plater.pm:2226 lib/Slic3r/GUI/Plater.pm:2242 msgid "Scale the selected object along the X axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2140 lib/Slic3r/GUI/Plater.pm:2156 +#: lib/Slic3r/GUI/Plater.pm:2229 lib/Slic3r/GUI/Plater.pm:2245 msgid "Scale the selected object along the Y axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2143 lib/Slic3r/GUI/Plater.pm:2159 +#: lib/Slic3r/GUI/Plater.pm:2232 lib/Slic3r/GUI/Plater.pm:2248 msgid "Scale the selected object along the Z axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2148 +#: lib/Slic3r/GUI/Plater.pm:2237 msgid "Scale to size" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2163 +#: lib/Slic3r/GUI/Plater.pm:2252 msgid "Split the selected object into individual parts" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2166 +#: lib/Slic3r/GUI/Plater.pm:2255 msgid "Open the 3D cutting tool" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2170 +#: lib/Slic3r/GUI/Plater.pm:2259 msgid "Open the object editor dialog" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2174 +#: lib/Slic3r/GUI/Plater.pm:2263 msgid "Reload from Disk" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2174 +#: lib/Slic3r/GUI/Plater.pm:2263 msgid "Reload the selected file from Disk" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2177 +#: lib/Slic3r/GUI/Plater.pm:2266 msgid "Export object as STL…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2177 +#: lib/Slic3r/GUI/Plater.pm:2266 msgid "Export this single object as STL file" msgstr "" +#: lib/Slic3r/GUI/Plater.pm:2270 +msgid "Fix STL through Netfabb" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:2270 +msgid "" +"Fix the model by sending it to a Netfabb cloud service through Windows 10 API" +msgstr "" + #: lib/Slic3r/GUI/Plater/2D.pm:131 msgid "What do you want to print today? â„¢" msgstr "" diff --git a/resources/localization/fr_FR/Slic3rPE.mo b/resources/localization/fr_FR/Slic3rPE.mo Binary files differindex 8b971356b..3718025ba 100644 --- a/resources/localization/fr_FR/Slic3rPE.mo +++ b/resources/localization/fr_FR/Slic3rPE.mo diff --git a/resources/localization/fr_FR/Slic3rPE_fr.po b/resources/localization/fr_FR/Slic3rPE_fr.po index 217e97494..81b5b2bb2 100644 --- a/resources/localization/fr_FR/Slic3rPE_fr.po +++ b/resources/localization/fr_FR/Slic3rPE_fr.po @@ -5,7 +5,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Poedit 2.0.6\n" +"X-Generator: Poedit 2.0.8\n" "Project-Id-Version: \n" "POT-Creation-Date: \n" "PO-Revision-Date: \n" @@ -361,6 +361,10 @@ msgstr "Ajouter…" msgid "Advanced" msgstr "Avancé" +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:319 +msgid "Advanced: avrdude output log" +msgstr "Avancé : journal de sortie avrdude" + #: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1138 #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:766 msgid "After layer change G-code" @@ -421,6 +425,14 @@ msgstr "secondes approximatives" msgid "Are you sure you want to " msgstr "Êtes-vous sûr de vouloir " +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:365 +msgid "" +"Are you sure you want to cancel firmware flashing?\n" +"This could leave your printer in an unusable state!" +msgstr "" +"Êtes-vous certain de vouloir annuler le processus de flash du firmware ?\n" +"Cela pourrait rendre votre imprimante inutilisable !" + #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2044 msgid "Around X axis…" msgstr "Autour de l'axe X…" @@ -615,6 +627,14 @@ msgstr "Description des Boutons et des Couleurs de Texte" msgid "by the print profile maximum" msgstr "par le maximum du profil de l'imprimante" +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:85 +msgid "Cancel" +msgstr "Annuler" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:213 +msgid "Cancelling..." +msgstr "Annulation..." + #: xs/src/slic3r/GUI/Tab.cpp:2124 msgid "Cannot overwrite a system profile." msgstr "Impossible d'écraser un profil système." @@ -647,14 +667,6 @@ msgstr "Changer le nombre de copies de l'objet sélectionné" msgid "Check for application updates" msgstr "Vérifier les mises à jour de l'application" -#: xs/src/slic3r/GUI/GUI.cpp:404 -msgid "Check for configuration updates" -msgstr "Vérifier les mises à jour de configuration" - -#: xs/src/slic3r/GUI/GUI.cpp:404 -msgid "Check for updates" -msgstr "Vérifier les mises à jour" - #: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:298 msgid "Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):" msgstr "Choisir un fichier à partir duquel importer la forme du plateau (STL/OBJ/AMF/3MF/PRUSA) :" @@ -731,6 +743,10 @@ msgstr "Une mise à jour de la configuration est disponible" msgid "Configuration Wizard" msgstr "Assistant de Configuration" +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:366 +msgid "Confirmation" +msgstr "Confirmation" + #: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1020 msgid "Connection failed." msgstr "La connexion a échoué." @@ -1073,10 +1089,6 @@ msgstr "Entrez la nouvelle taille pour l'objet sélectionné (plateau d'impressi msgid "Enter the number of copies of the selected object:" msgstr "Entrez le nombre de copies de l'objet sélectionné :" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:950 -msgid "Enter the rotation angle:" -msgstr "Entrez l'angle de rotation :" - #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1035 #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1061 #, no-perl-format @@ -1154,6 +1166,10 @@ msgstr "Option expérimentale qui ajuste le flux pour les surplombs (le flux pou msgid "Export all presets to file" msgstr "Exporter tous les préréglage vers un fichier" +#: lib/Slic3r/GUI/Plater.pm:1416 +msgid "Export cancelled" +msgstr "Exportation annulée" + #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:257 msgid "Export current configuration to file" msgstr "Exporter la configuration actuelle vers un fichier" @@ -1423,6 +1439,14 @@ msgstr "Terminé" msgid "Firmware" msgstr "Firmware" +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:266 +msgid "Firmware flasher" +msgstr "Outil de flash du firmware" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:293 +msgid "Firmware image:" +msgstr "Image du firmware :" + #: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1314 msgid "Firmware Retraction" msgstr "Rétraction du Firmware" @@ -1450,6 +1474,30 @@ msgstr "Vitesse de la première couche" msgid "First layer volumetric" msgstr "Volume de la première couche" +#: xs/src/slic3r/GUI/GUI.cpp:326 +msgid "Flash printer firmware" +msgstr "Flasher le firmware de l'imprimante" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:84 +msgid "Flash!" +msgstr "Flash !" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:147 +msgid "Flashing cancelled." +msgstr "Processus de flash annulé." + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:146 +msgid "Flashing failed. Please see the avrdude log below." +msgstr "Le processus de flash a échoué. Veuillez consulter le journal avrdude ci-dessous." + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:123 +msgid "Flashing in progress. Please do not disconnect the printer!" +msgstr "Processus de flash en cours. Veuillez ne pas déconnecter l'imprimante !" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:145 +msgid "Flashing succeeded!" +msgstr "Flash effectué avec succès !" + #: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:466 msgid "Flow" msgstr "Flux" @@ -1555,10 +1603,9 @@ msgstr "Hauteur de la jupe exprimée en couches. Mettez une valeur élevée pour msgid "Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial configuration; just a few settings and you will be ready to print." msgstr "Bonjour, bienvenu dans Slic3r Prusa Edition ! Ce %s vous aide à la configuration initiale ; juste quelques paramètres et vous serez prêt à imprimer." -#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:223 -#, c-format -msgid "Here you can adjust required purging volume (mm%s) for any given pair of tools." -msgstr "Ici, vous pouvez ajuster le volume de purge requis (mm%s) pour chaque paire d'outils." +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:218 +msgid "Here you can adjust required purging volume (mm³) for any given pair of tools." +msgstr "Ici vous pouvez ajuster le volume de purge nécessaire (mm³) pour une paire d'outils donnée." #: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:329 msgid "Horizontal shells" @@ -1745,10 +1792,6 @@ msgstr "Remplissage interne" msgid "Invalid API key" msgstr "Clé API invalide" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:950 -msgid "Invalid rotation angle entered" -msgstr "Angle de rotation entré invalide" - #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1031 #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1035 #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1056 @@ -1966,14 +2009,14 @@ msgstr "Vitesse d'impression minimale" msgid "min slic3r version" msgstr "version minimale de slic3r" +#: xs/src/libslic3r/PrintConfig.cpp:951 +msgid "Minimal filament extrusion length" +msgstr "Longueur minimale d'extrusion de filament" + #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1040 msgid "Minimum detail resolution, used to simplify the input file for speeding up the slicing job and reducing memory usage. High-resolution models often carry more detail than printers can render. Set to zero to disable any simplification and use full resolution from input." msgstr "Résolution minimale pour les détails, utilisée pour simplifier le fichier d'entrée afin d'accélérer le découpage et de réduire l'utilisation de la mémoire. Les modèles haute-résolution possèdent souvent plus de détails que ce que les imprimantes peuvent produire. Mettez à zéro pour désactiver toute simplification et utiliser la résolution complète de l'entrée." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:874 -msgid "Minimum extrusion length" -msgstr "Longueur d'extrusion minimale" - #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1050 msgid "Minimum travel after retraction" msgstr "Trajet minimal après une rétraction" @@ -2284,9 +2327,9 @@ msgstr "Ouvrir un modèle" msgid "Open CA certificate file" msgstr "Ouvrir le fichier de certificat CA" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:251 -msgid "Open STL/OBJ/AMF…\tCtrl+O" -msgstr "Ouvrir STL/OBJ/AMF…\tCtrl+O" +#: lib/Slic3r/GUI/MainFrame.pm:194 +msgid "Open STL/OBJ/AMF/3MF…\tCtrl+O" +msgstr "Ouvrir STL/OBJ/AMF/3MF…\tCtrl+O" #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2102 msgid "Open the 3D cutting tool" @@ -2414,6 +2457,10 @@ msgstr "Placer une copie supplémentaire de l'objet sélectionné" msgid "Plater" msgstr "Plateau" +#: lib/Slic3r/GUI/Plater.pm:1897 +msgid "Please install the OpenGL modules to use this feature (see build instructions)." +msgstr "Veuillez installer les modules OpenGL pour pouvoir utiliser cette fonctionnalité (voir les instructions de montage)." + #: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1199 msgid "Position (for multi-extruder printers)" msgstr "Position (pour les imprimantes multi-extrudeurs)" @@ -2531,6 +2578,10 @@ msgstr "Traitement du fichier d'entrée\n" msgid "Profile dependencies" msgstr "Dépendances du profil" +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:300 +msgid "Progress:" +msgstr "Progression :" + #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:385 msgid "Prusa 3D Drivers" msgstr "Drivers Prusa 3D" @@ -2617,6 +2668,10 @@ msgstr "Réglages de l'expulsion" msgid "Re-configure" msgstr "Reconfigurer" +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:297 +msgid "Ready" +msgstr "Prêt" + #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:372 msgid "Rear" msgstr "Arrière" @@ -2694,6 +2749,15 @@ msgstr "Signaler un problème" msgid "Report an issue on the Slic3r Prusa Edition" msgstr "Signaler un problème sur la Prusa Edition de Slic3r" +#: xs/src/slic3r/Utils/PresetUpdater.cpp:514 +#, c-format +msgid "requires min. %s and max. %s" +msgstr "nécessite min. %s et max. %s" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:288 +msgid "Rescan" +msgstr "Scanner à nouveau" + #: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:995 msgid "Rescan serial ports" msgstr "Rescanner les ports série" @@ -2759,10 +2823,6 @@ msgstr "Pivoter de 45° dans le sens des aiguilles d'une montre" msgid "Rotate 45° counter-clockwise" msgstr "Pivoter de 45° dans le sens inverse des aiguilles d'une montre" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:950 -msgid "Rotate around " -msgstr "Pivoter autour de " - #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2034 msgid "Rotate the selected object by 45° clockwise" msgstr "Pivoter l'objet sélectionné de 45° dans le sens des aiguilles d'une montre" @@ -2812,6 +2872,10 @@ msgstr "Enregistrer la configuration sous :" msgid "Save current " msgstr "Enregistrer l'état actuel " +#: lib/Slic3r/GUI/Plater.pm:1399 +msgid "Save G-code file as:" +msgstr "Sauvegarder le fichier G-code en tant que :" + #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:580 msgid "Save OBJ file (less prone to coordinate errors than STL) as:" msgstr "Enregistrer le fichier OBJ (moins enclin aux erreurs de coordonnées que le STL) sous :" @@ -2956,6 +3020,10 @@ msgstr "Port série" msgid "Serial port speed" msgstr "Vitesse du port série" +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:286 +msgid "Serial port:" +msgstr "Port série :" + #: xs/src/slic3r/GUI/BonjourDialog.cpp:68 msgid "Service name" msgstr "Nom du service" @@ -3034,6 +3102,10 @@ msgstr "Coques" msgid "Show" msgstr "Afficher" +#: lib/Slic3r/GUI/MainFrame.pm:337 +msgid "Show &Configuration Folder" +msgstr "Afficher le Répertoire de &Configuration" + #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:408 msgid "Show about dialog" msgstr "Afficher la boîte de dialogue à propos" @@ -3074,6 +3146,10 @@ msgstr "Afficher le contrôleur de l'imprimante" msgid "Show the printer settings" msgstr "Afficher les réglages de l'imprimante" +#: lib/Slic3r/GUI/MainFrame.pm:337 +msgid "Show user configuration folder (datadir)" +msgstr "Afficher le répertoire de configuration utilisateur (datadir)" + #: xs/src/slic3r/GUI/Tab.cpp:1716 xs/src/slic3r/GUI/Tab.cpp:1722 msgid "Single extruder MM setup" msgstr "Réglage MM pour extrudeur unique" @@ -3382,6 +3458,10 @@ msgstr "G-code de début" msgid "Start new slicing process" msgstr "Démarrer un nouveau processus de découpe" +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:296 +msgid "Status:" +msgstr "État :" + #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1539 #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1581 msgid "STL file exported to " @@ -4045,6 +4125,10 @@ msgstr "Les mises à jour ne sont jamais appliquées sans l'accord de l'utilisat msgid "Upgrade" msgstr "Mise à jour" +#: xs/src/slic3r/GUI/GUI.cpp:326 +msgid "Upload a firmware image into an Arduino based printer" +msgstr "Charger un firmware dans une imprimante basée sur un Arduino" + #: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:989 msgid "USB/Serial connection" msgstr "Port USB/Série" @@ -4142,10 +4226,9 @@ msgstr "Vue" msgid "Volume" msgstr "Volume" -#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:246 -#, c-format -msgid "Volume to purge (mm%s) when the filament is being" -msgstr "Volume à purger (mm%s) quand le filament est en train d'être" +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:241 +msgid "Volume to purge (mm³) when the filament is being" +msgstr "Volume à purger (mm³) lorsque le filament est" #: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:214 msgid "Volumetric" diff --git a/resources/localization/list.txt b/resources/localization/list.txt index 0fd528994..a4d07b617 100644 --- a/resources/localization/list.txt +++ b/resources/localization/list.txt @@ -6,6 +6,7 @@ xs/src/slic3r/GUI/ButtonsDescription.cpp xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp xs/src/slic3r/GUI/ConfigWizard.cpp xs/src/slic3r/GUI/FirmwareDialog.cpp +xs/src/slic3r/GUI/GLCanvas3D.cpp xs/src/slic3r/GUI/GUI.cpp xs/src/slic3r/GUI/MsgDialog.cpp xs/src/slic3r/GUI/Tab.cpp diff --git a/resources/localization/pl/Slic3rPE.mo b/resources/localization/pl/Slic3rPE.mo Binary files differindex 103d4a5ee..93a42160b 100644 --- a/resources/localization/pl/Slic3rPE.mo +++ b/resources/localization/pl/Slic3rPE.mo diff --git a/resources/localization/pl/Slic3rPE_pl.po b/resources/localization/pl/Slic3rPE_pl.po index 282cb8459..9ab5f61b7 100644 --- a/resources/localization/pl/Slic3rPE_pl.po +++ b/resources/localization/pl/Slic3rPE_pl.po @@ -97,7 +97,7 @@ msgstr " jako:" #: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:226 #, c-format msgid " at filament speed %3.2f mm/s." -msgstr " z prÄ™dkociÄ… filamentu %3.2f mm/s." +msgstr " z prÄ™dkoÅ›ciÄ… filamentu %3.2f mm/s." #: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1035 msgid " Browse " @@ -574,7 +574,7 @@ msgstr "G-code wykonywany przy przejÅ›ciach pomiÄ™dzy modelami (druk sekwencyjny #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:68 #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:370 msgid "Bottom" -msgstr "Spód" +msgstr "Dolne" #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:72 msgid "Bottom solid layers" @@ -834,7 +834,7 @@ msgstr "WÅ‚asne ustawienia" #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:205 #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2102 msgid "Cut…" -msgstr "Tnij…" +msgstr "Obcinanie..." #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2027 msgid "Decrease copies" @@ -859,7 +859,7 @@ msgstr "DomyÅ›lny kÄ…t linii wypeÅ‚nienia. Mosty bÄ™dÄ… wypeÅ‚niane z użyciem n #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:376 msgid "Default extrusion width" -msgstr "DomyÅ›lna szerokość ekstrudowanej linii" +msgstr "DomyÅ›lna szerokość linii" #: xs/src/slic3r/GUI/Tab.cpp:767 msgid "default filament profile" @@ -1166,7 +1166,7 @@ msgstr "Funkcja eksperymentalna majÄ…ca zapobiegać tworzeniu podpór pod mostam #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:942 msgid "Experimental option to adjust flow for overhangs (bridge flow will be used), to apply bridge speed to them and enable fan." -msgstr "Opcja eksperymentalna dostosowujÄ…ca przepÅ‚yw przy zwisach (zostanie zastosowany przepÅ‚yw taki jak dla mostów), zastosuje również prÄ™dkość takÄ… jak dla mostów i chÅ‚odzenie." +msgstr "Opcja eksperymentalna dostosowujÄ…ca przepÅ‚yw przy zwisach (zostanie zastosowany przepÅ‚yw taki jak dla mostów), zastosuje również prÄ™dkość i chÅ‚odzenie takie jak dla mostów." #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:263 msgid "Export all presets to file" @@ -1248,7 +1248,7 @@ msgstr "Obrysy zewnÄ™trzne" #: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:151 msgid "external perimeters" -msgstr "obrysy zewnÄ™trzne" +msgstr "obrysów zewnÄ™trznych" #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:289 msgid "External perimeters first" @@ -1399,7 +1399,7 @@ msgstr "WÅ‚aÅ›ciwoÅ›ci filamentu" #: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.hpp:202 msgid "Filament Settings" -msgstr "Ustawienia filamentu" +msgstr "Ustawienia Filamentu" #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:450 msgid "Filament type" @@ -1478,7 +1478,7 @@ msgstr "PrÄ™dkość pierwszej warstwy" #: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:214 msgid "First layer volumetric" -msgstr "ObjÄ™toÅ›ciowa pierwszej warstwy" +msgstr "Na pierwszej warstwie" #: xs/src/slic3r/GUI/GUI.cpp:326 msgid "Flash printer firmware" @@ -1651,7 +1651,7 @@ msgstr "WÅ‚Ä…czenie powoduje pobieranie wbudowanych systemowych zestawów ustawi #: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:26 #, c-format msgid "If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s)." -msgstr "JeÅ›li szacowany czas druku warstwy jest mniejszy niż ~%d s, wentylator bÄ™dzie pracowaÅ‚ na %d %% a prÄ™dkość druku zostanie obniżona tak, aby warstwa byÅ‚a drukowana przez nie mniej niż %d s (jednakże prÄ™dkość nie zostanie obniżona poniżej %d mm/s)." +msgstr "JeÅ›li szacowany czas druku warstwy jest niższy niż ~%d s, wentylator bÄ™dzie pracowaÅ‚ na %d %% a prÄ™dkość druku zostanie obniżona tak, aby warstwa byÅ‚a drukowana przez nie mniej niż %d s (jednakże prÄ™dkość nie zejdzie poniżej %d mm/s)." #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:616 msgid "If expressed as absolute value in mm/s, this speed will be applied to all the print moves of the first layer, regardless of their type. If expressed as a percentage (for example: 40%) it will scale the default speeds." @@ -1659,11 +1659,11 @@ msgstr "JeÅ›li ustawisz wartość bezwzglÄ™dnÄ… wyrażonÄ… w mm/s, taka prÄ™dkoÅ #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:395 msgid "If layer print time is estimated below this number of seconds, fan will be enabled and its speed will be calculated by interpolating the minimum and maximum speeds." -msgstr "JeÅ›li szacowany czas druku warstwy bÄ™dzie mniejszy niż ta wartość to wentylator bÄ™dzie wÅ‚Ä…czony a jego prÄ™dkość bÄ™dzie interpolowana na podstawie górnego i dolnego limitu prÄ™dkoÅ›ci." +msgstr "JeÅ›li szacowany czas druku warstwy bÄ™dzie niższy niż ta wartość to wentylator bÄ™dzie wÅ‚Ä…czony a jego prÄ™dkość bÄ™dzie interpolowana na podstawie górnego i dolnego limitu prÄ™dkoÅ›ci." #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1240 msgid "If layer print time is estimated below this number of seconds, print moves speed will be scaled down to extend duration to this value." -msgstr "JeÅ›li szacowany czas druku warstwy bÄ™dzie mniejszy niż ta wartość to prÄ™dkość ruchów drukujÄ…cych bÄ™dzie zmniejszona aby wydÅ‚użyć czas druku." +msgstr "JeÅ›li szacowany czas druku warstwy bÄ™dzie niższy niż ta wartość to prÄ™dkość ruchów drukujÄ…cych bÄ™dzie zmniejszona aby wydÅ‚użyć czas druku." #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:388 msgid "If this is enabled, fan will never be disabled and will be kept running at least at its minimum speed. Useful for PLA, harmful for ABS." @@ -1742,7 +1742,7 @@ msgstr "WypeÅ‚nienie" #: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:169 msgid "infill" -msgstr "wypeÅ‚nienie" +msgstr "wypeÅ‚nienia" #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:720 msgid "Infill before perimeters" @@ -1987,7 +1987,7 @@ msgstr "Maksymalny objÄ™toÅ›ciowo kÄ…t pozytywny" #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:421 #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:817 msgid "Max volumetric speed" -msgstr "Maksymalna prÄ™dkość objÄ™toÅ›ciowa" +msgstr "Maksymalny przepÅ‚yw" #: xs/src/libslic3r/PrintConfig.cpp:1854 msgid "Maximal bridging distance" @@ -2220,7 +2220,7 @@ msgstr "Nowa wersja:" #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:469 msgid "No previously sliced file." -msgstr "Brak poprzednio pociÄ™tych pliku." +msgstr "Brak poprzednio pociÄ™tych plików." #: xs/src/slic3r/GUI/RammingChart.cpp:28 msgid "NO RAMMING AT ALL" @@ -2325,7 +2325,7 @@ msgstr "Retrakcja tylko przy przechodzeniu nad obrysami" #: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:438 msgid "Ooze prevention" -msgstr "Zapobieganie wyciekom" +msgstr "Zapobieganie wyciekom (ooze)" #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:251 msgid "Open a model" @@ -2341,7 +2341,7 @@ msgstr "Otwórz plik STL/OBJ/AMF/3MF... \tCrtl+O" #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2102 msgid "Open the 3D cutting tool" -msgstr "Otwórz narzÄ™dzie do przecinania 3D" +msgstr "Otwórz narzÄ™dzie do wycinania 3D" #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2106 msgid "Open the object editor dialog" @@ -2463,7 +2463,7 @@ msgstr "Umieść jednÄ… kopiÄ™ zaznaczonego modelu" #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:118 msgid "Plater" -msgstr "Zawartość stoÅ‚u" +msgstr "Zawartość StoÅ‚u" #: lib/Slic3r/GUI/Plater.pm:1897 msgid "Please install the OpenGL modules to use this feature (see build instructions)." @@ -2691,7 +2691,7 @@ msgstr "Widok z tyÅ‚u" #: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:262 #, c-format msgid "Recommended object thin wall thickness for layer height %.2f and " -msgstr "Zalecana grubość Å›ciany obiektu dla danej wysokoÅ›ci warstwy %.2f i " +msgstr "Zalecana grubość Å›ciany modelu dla wysokoÅ›ci warstwy %.2f i " #: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:245 msgid "Recommended object thin wall thickness: Not available due to invalid layer height." @@ -3265,23 +3265,23 @@ msgstr "Slic3r nie bÄ™dzie skalowaÅ‚ prÄ™dkoÅ›ci poniżej tej wartoÅ›ci." #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:268 msgid "Slice a file into a G-code" -msgstr "Potnij plik jako G-code" +msgstr "CiÄ™cie jako G-code" #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:274 msgid "Slice a file into a G-code, save as" -msgstr "Potnij plik jako G-code, zapisz jako" +msgstr "CiÄ™cie jako G-code, zapisz jako" #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:287 msgid "Slice file to a multi-layer SVG" -msgstr "Potnij plik jako wielowarstwowy SVG" +msgstr "CiÄ™cie jako wielowarstwowy SVG" #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:237 msgid "Slice now" -msgstr "Potnij teraz" +msgstr "CiÄ™cie" #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:287 msgid "Slice to SV&G…\tCtrl+G" -msgstr "Tnij do SV&G...\tCtrl+G" +msgstr "CiÄ™cie do SV&G...\tCtrl+G" #: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:438 msgid "Sliced Info" @@ -3485,7 +3485,7 @@ msgstr "podpora" #: xs/src/slic3r/GUI/GUI.cpp:879 msgid "Support" -msgstr "Wsparcie" +msgstr "Podpory" #: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:620 msgid "Support Generator" @@ -4011,7 +4011,7 @@ msgstr "Parametry zmiany narzÄ™dzia dla drukarek MM z jednym ekstruderem" #: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1638 #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:369 msgid "Top" -msgstr "Szczyt" +msgstr "Górne" #: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:187 msgid "top solid infill" diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index fdeefb8c9..e6d5d8187 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -211,7 +211,7 @@ fill_pattern = grid top_infill_extrusion_width = 0.4 [print:0.05mm ULTRADETAIL 0.25 nozzle] -inherits = *0.05mm* +inherits = *0.05mm*; *0.25nozzle* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 external_perimeter_extrusion_width = 0 extrusion_width = 0.28 @@ -271,7 +271,7 @@ top_infill_extrusion_width = 0.4 top_solid_infill_speed = 50 [print:0.10mm DETAIL 0.25 nozzle] -inherits = *0.10mm* +inherits = *0.10mm*; *0.25nozzle* bridge_acceleration = 600 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 external_perimeter_speed = 20 @@ -284,7 +284,7 @@ solid_infill_speed = 40 top_solid_infill_speed = 30 [print:0.10mm DETAIL 0.25 nozzle MK3] -inherits = *0.10mm* +inherits = *0.10mm*; *0.25nozzle* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 external_perimeter_speed = 35 @@ -298,7 +298,7 @@ top_infill_extrusion_width = 0.4 top_solid_infill_speed = 50 [print:0.10mm DETAIL 0.6 nozzle MK3] -inherits = *0.10mm* +inherits = *0.10mm*; *0.6nozzle* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 @@ -398,7 +398,7 @@ support_material_with_sheath = 0 support_material_xy_spacing = 80% [print:0.15mm OPTIMAL 0.25 nozzle MK3] -inherits = *0.15mm* +inherits = *0.15mm*; *0.25nozzle* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 external_perimeter_speed = 35 @@ -424,7 +424,7 @@ top_infill_extrusion_width = 0.4 top_solid_layers = 5 [print:0.15mm OPTIMAL 0.6 nozzle MK3] -inherits = *0.15mm* +inherits = *0.15mm*; *0.6nozzle* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 @@ -491,7 +491,7 @@ support_material_with_sheath = 0 support_material_xy_spacing = 80% [print:0.20mm FAST 0.6 nozzle MK3] -inherits = *0.20mm* +inherits = *0.20mm*; *0.6nozzle* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 @@ -1005,7 +1005,7 @@ printer_variant = 0.25 default_print_profile = 0.10mm DETAIL 0.25 nozzle [printer:Original Prusa i3 MK2 0.6 nozzle] -inherits = *common* +inherits = *common*; *0.6nozzle* max_layer_height = 0.35 min_layer_height = 0.1 nozzle_diameter = 0.6 @@ -1020,7 +1020,7 @@ default_print_profile = 0.20mm NORMAL 0.6 nozzle inherits = *mm-single* [printer:Original Prusa i3 MK2 MM Single Mode 0.6 nozzle] -inherits = *mm-single* +inherits = *mm-single*; *0.6nozzle* nozzle_diameter = 0.6 printer_variant = 0.6 default_print_profile = 0.20mm NORMAL 0.6 nozzle @@ -1030,7 +1030,7 @@ inherits = *mm-multi* nozzle_diameter = 0.4,0.4,0.4,0.4 [printer:Original Prusa i3 MK2 MultiMaterial 0.6 nozzle] -inherits = *mm-multi* +inherits = *mm-multi*; *0.6nozzle* nozzle_diameter = 0.6,0.6,0.6,0.6 printer_variant = 0.6 default_print_profile = 0.20mm NORMAL 0.6 nozzle 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 9c61fa729..f4d0b17ea 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -8,6 +8,7 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/) if (CMAKE_SYSTEM_NAME STREQUAL "Linux") # Workaround for an old CMake, which does not understand CMAKE_CXX_STANDARD. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall" ) + find_package(PkgConfig REQUIRED) endif() if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUXX) @@ -27,6 +28,13 @@ if(WIN32) # BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking. add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB) # -D_ITERATOR_DEBUG_LEVEL) + if(WIN10SDK_PATH) + message("Building with Win10 Netfabb STL fixing service support") + add_definitions(-DHAS_WIN10SDK) + include_directories("${WIN10SDK_PATH}/Include") + else() + message("Building without Win10 Netfabb STL fixing service support") + endif() endif() add_definitions(-DwxUSE_UNICODE -D_UNICODE -DUNICODE -DWXINTL_NO_GETTEXT_MACRO) @@ -181,7 +189,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 @@ -232,6 +248,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/FirmwareDialog.hpp ${LIBDIR}/slic3r/Utils/Http.cpp ${LIBDIR}/slic3r/Utils/Http.hpp + ${LIBDIR}/slic3r/Utils/FixModelByWin10.cpp + ${LIBDIR}/slic3r/Utils/FixModelByWin10.hpp ${LIBDIR}/slic3r/Utils/OctoPrint.cpp ${LIBDIR}/slic3r/Utils/OctoPrint.hpp ${LIBDIR}/slic3r/Utils/Bonjour.cpp @@ -336,8 +354,6 @@ add_library(semver STATIC ) -add_subdirectory(src/avrdude) - # Generate the Slic3r Perl module (XS) typemap file. set(MyTypemap ${CMAKE_CURRENT_BINARY_DIR}/typemap) add_custom_command( @@ -500,12 +516,12 @@ if (WIN32 AND ";${PerlEmbed_CCFLAGS};" MATCHES ";[-/]Od;") message("Old CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}") message("Old CMAKE_CXX_FLAGS_RELWITHDEBINFO: ${CMAKE_CXX_FLAGS_RELEASE}") message("Old CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS_RELEASE}") - set(CMAKE_CXX_FLAGS_RELEASE "/MD /Od /Zi /EHsc /DNDEBUG") - set(CMAKE_C_FLAGS_RELEASE "/MD /Od /Zi /DNDEBUG") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /EHsc /DNDEBUG") - set(CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /DNDEBUG") - set(CMAKE_CXX_FLAGS "/MD /Od /Zi /EHsc /DNDEBUG") - set(CMAKE_C_FLAGS "/MD /Od /Zi /DNDEBUG") + set(CMAKE_CXX_FLAGS_RELEASE "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32") + set(CMAKE_C_FLAGS_RELEASE "/MD /Od /Zi /DNDEBUG /DWIN32") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32") + set(CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /DNDEBUG /DWIN32") + set(CMAKE_CXX_FLAGS "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32") + set(CMAKE_C_FLAGS "/MD /Od /Zi /DNDEBUG /DWIN32") endif() # The following line will add -fPIC on Linux to make the XS.so rellocable. add_definitions(${PerlEmbed_CCCDLFLAGS}) @@ -513,6 +529,8 @@ if (WIN32) target_link_libraries(XS ${PERL_LIBRARY}) endif() +add_subdirectory(src/avrdude) + ## REQUIRED packages # Find and configure boost @@ -557,13 +575,22 @@ 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}) + # On Linux / gtk, we need to have a direct access to gtk+ for some workarounds. + if (AlienWx_GUI_TOOLKIT STREQUAL "gtk2") + pkg_check_modules(GTK2 gtk+-2.0) + include_directories(${GTK2_INCLUDE_DIRS}) + endif() + if (AlienWx_GUI_TOOLKIT STREQUAL "gtk3") + pkg_check_modules(GTK3 gtk+-3.0) + include_directories(${GTK3_INCLUDE_DIRS}) + endif() 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/avrdude/arduino.c b/xs/src/avrdude/arduino.c index 566f56abd..5a9cb465e 100644 --- a/xs/src/avrdude/arduino.c +++ b/xs/src/avrdude/arduino.c @@ -80,6 +80,49 @@ static int arduino_read_sig_bytes(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m) return 3; } +static int prusa_init_external_flash(PROGRAMMER * pgm) +{ + // Note: send/receive as in _the firmare_ send & receives + const char entry_magic_send [] = "start\n"; + const char entry_magic_receive[] = "w25x20cl_enter\n"; + const char entry_magic_cfm [] = "w25x20cl_cfm\n"; + const size_t buffer_len = 32; // Should be large enough for the above messages + + int res; + size_t recv_size; + char *buffer = alloca(buffer_len); + + // 1. receive the "start" command + recv_size = sizeof(entry_magic_send) - 1; + res = serial_recv(&pgm->fd, buffer, recv_size); + if (res < 0) { + avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer did not boot up on time or serial communication failed\n", progname); + return -1; + } else if (strncmp(buffer, entry_magic_send, recv_size) != 0) { + avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer emitted incorrect start code: `%*s`\n", progname, recv_size, buffer); + return -1; + } + + // 2. Send the external flash programmer enter command + if (serial_send(&pgm->fd, entry_magic_receive, sizeof(entry_magic_receive) - 1) < 0) { + avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): Failed to send command to the printer\n",progname); + return -1; + } + + // 3. Receive the entry confirmation command + recv_size = sizeof(entry_magic_cfm) - 1; + res = serial_recv(&pgm->fd, buffer, recv_size); + if (res < 0) { + avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer did not boot up on time or serial communication failed\n", progname); + return -1; + } else if (strncmp(buffer, entry_magic_cfm, recv_size) != 0) { + avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer emitted incorrect cfm code: `%*s`\n", progname, recv_size, buffer); + return -1; + } + + return 0; +} + static int arduino_open(PROGRAMMER * pgm, char * port) { union pinfo pinfo; @@ -97,11 +140,24 @@ static int arduino_open(PROGRAMMER * pgm, char * port) serial_set_dtr_rts(&pgm->fd, 1); usleep(50*1000); + // Sometimes there may be line noise generating input on the printer's USB-to-serial IC + // Here we try to clean its input buffer with a sequence of newlines (a minimum of 9 is needed): + const char cleanup_newlines[] = "\n\n\n\n\n\n\n\n\n\n"; + if (serial_send(&pgm->fd, cleanup_newlines, sizeof(cleanup_newlines) - 1) < 0) { + return -1; + } + /* * drain any extraneous input */ stk500_drain(pgm, 0); + // Initialization sequence for programming the external FLASH on the Prusa MK3 + if (prusa_init_external_flash(pgm) < 0) { + avrdude_message(MSG_INFO, "%s: arduino_open(): Failed to initialize MK3 external flash programming mode\n", progname); + return -1; + } + if (stk500_getsync(pgm) < 0) return -1; diff --git a/xs/src/avrdude/avrdude-slic3r.cpp b/xs/src/avrdude/avrdude-slic3r.cpp index a859200fb..030353413 100644 --- a/xs/src/avrdude/avrdude-slic3r.cpp +++ b/xs/src/avrdude/avrdude-slic3r.cpp @@ -1,5 +1,6 @@ #include "avrdude-slic3r.hpp" +#include <deque> #include <thread> extern "C" { @@ -33,17 +34,22 @@ static void avrdude_progress_handler_closure(const char *task, unsigned progress struct AvrDude::priv { std::string sys_config; - std::vector<std::string> args; + std::deque<std::vector<std::string>> args; + size_t current_args_set = 0; + RunFn run_fn; MessageFn message_fn; ProgressFn progress_fn; CompleteFn complete_fn; std::thread avrdude_thread; + priv(std::string &&sys_config) : sys_config(sys_config) {} + + int run_one(const std::vector<std::string> &args); int run(); }; -int AvrDude::priv::run() { +int AvrDude::priv::run_one(const std::vector<std::string> &args) { std::vector<char*> c_args {{ const_cast<char*>(PACKAGE_NAME) }}; for (const auto &arg : args) { c_args.push_back(const_cast<char*>(arg.data())); @@ -68,10 +74,22 @@ int AvrDude::priv::run() { return res; } +int AvrDude::priv::run() { + for (; args.size() > 0; current_args_set++) { + int res = run_one(args.front()); + args.pop_front(); + if (res != 0) { + return res; + } + } + + return 0; +} + // Public -AvrDude::AvrDude() : p(new priv()) {} +AvrDude::AvrDude(std::string sys_config) : p(new priv(std::move(sys_config))) {} AvrDude::AvrDude(AvrDude &&other) : p(std::move(other.p)) {} @@ -82,15 +100,15 @@ AvrDude::~AvrDude() } } -AvrDude& AvrDude::sys_config(std::string sys_config) +AvrDude& AvrDude::push_args(std::vector<std::string> args) { - if (p) { p->sys_config = std::move(sys_config); } + if (p) { p->args.push_back(std::move(args)); } return *this; } -AvrDude& AvrDude::args(std::vector<std::string> args) +AvrDude& AvrDude::on_run(RunFn fn) { - if (p) { p->args = std::move(args); } + if (p) { p->run_fn = std::move(fn); } return *this; } @@ -123,11 +141,17 @@ AvrDude::Ptr AvrDude::run() if (self->p) { auto avrdude_thread = std::thread([self]() { - auto res = self->p->run(); - if (self->p->complete_fn) { - self->p->complete_fn(res); - } - }); + if (self->p->run_fn) { + self->p->run_fn(); + } + + auto res = self->p->run(); + + if (self->p->complete_fn) { + self->p->complete_fn(res, self->p->current_args_set); + } + }); + self->p->avrdude_thread = std::move(avrdude_thread); } diff --git a/xs/src/avrdude/avrdude-slic3r.hpp b/xs/src/avrdude/avrdude-slic3r.hpp index 8d881b094..273aa2378 100644 --- a/xs/src/avrdude/avrdude-slic3r.hpp +++ b/xs/src/avrdude/avrdude-slic3r.hpp @@ -12,22 +12,28 @@ class AvrDude { public: typedef std::shared_ptr<AvrDude> Ptr; + typedef std::function<void()> RunFn; typedef std::function<void(const char * /* msg */, unsigned /* size */)> MessageFn; typedef std::function<void(const char * /* task */, unsigned /* progress */)> ProgressFn; - typedef std::function<void(int /* exit status */)> CompleteFn; + typedef std::function<void(int /* exit status */, size_t /* args_id */)> CompleteFn; - AvrDude(); + // Main c-tor, sys_config is the location of avrdude's main configuration file + AvrDude(std::string sys_config); AvrDude(AvrDude &&); AvrDude(const AvrDude &) = delete; AvrDude &operator=(AvrDude &&) = delete; AvrDude &operator=(const AvrDude &) = delete; ~AvrDude(); - // Set location of avrdude's main configuration file - AvrDude& sys_config(std::string sys_config); + // Push a set of avrdude cli arguments + // Each set makes one avrdude invocation - use this method multiple times to push + // more than one avrdude invocations. + AvrDude& push_args(std::vector<std::string> args); - // Set avrdude cli arguments - AvrDude& args(std::vector<std::string> args); + // Set a callback to be called just after run() before avrdude is ran + // This can be used to perform any needed setup tasks from the background thread. + // This has no effect when using run_sync(). + AvrDude& on_run(RunFn fn); // Set message output callback AvrDude& on_message(MessageFn fn); @@ -36,7 +42,10 @@ public: // Progress is reported per each task (reading / writing) in percents. AvrDude& on_progress(ProgressFn fn); - // Called when avrdude's main function finishes + // Called when the last avrdude invocation finishes with the exit status of zero, + // or earlier, if one of the invocations return a non-zero status. + // The second argument contains the sequential id of the last avrdude invocation argument set. + // This has no effect when using run_sync(). AvrDude& on_complete(CompleteFn fn); int run_sync(); diff --git a/xs/src/avrdude/avrpart.c b/xs/src/avrdude/avrpart.c index 621a85b98..b04851ac1 100644 --- a/xs/src/avrdude/avrpart.c +++ b/xs/src/avrdude/avrpart.c @@ -378,7 +378,7 @@ void avr_mem_display(const char * prefix, FILE * f, AVRMEM * m, int type, char * optr; if (m == NULL) { - fprintf(f, + avrdude_message(MSG_INFO, "%s Block Poll Page Polled\n" "%sMemory Type Mode Delay Size Indx Paged Size Size #Pages MinW MaxW ReadBack\n" "%s----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------\n", @@ -386,13 +386,13 @@ void avr_mem_display(const char * prefix, FILE * f, AVRMEM * m, int type, } else { if (verbose > 2) { - fprintf(f, + avrdude_message(MSG_INFO, "%s Block Poll Page Polled\n" "%sMemory Type Mode Delay Size Indx Paged Size Size #Pages MinW MaxW ReadBack\n" "%s----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------\n", prefix, prefix, prefix); } - fprintf(f, + avrdude_message(MSG_INFO, "%s%-11s %4d %5d %5d %4d %-6s %6d %4d %6d %5d %5d 0x%02x 0x%02x\n", prefix, m->desc, m->mode, m->delay, m->blocksize, m->pollindex, m->paged ? "yes" : "no", @@ -415,7 +415,7 @@ void avr_mem_display(const char * prefix, FILE * f, AVRMEM * m, int type, optr = avr_op_str(i); else optr = " "; - fprintf(f, + avrdude_message(MSG_INFO, "%s %-11s %8d %8s %5d %5d\n", prefix, optr, j, bittype(m->op[i]->bit[j].type), @@ -620,7 +620,7 @@ void avr_display(FILE * f, AVRPART * p, const char * prefix, int verbose) LNODEID ln; AVRMEM * m; - fprintf(f, + avrdude_message(MSG_INFO, "%sAVR Part : %s\n" "%sChip Erase delay : %d us\n" "%sPAGEL : P%02X\n" diff --git a/xs/src/avrdude/fileio.c b/xs/src/avrdude/fileio.c index f2d617823..aa57f5587 100644 --- a/xs/src/avrdude/fileio.c +++ b/xs/src/avrdude/fileio.c @@ -45,6 +45,8 @@ #define MAX_LINE_LEN 256 /* max line length for ASCII format input files */ +#define MAX_MODE_LEN 32 // For fopen_and_seek() + struct ihexrec { unsigned char reclen; @@ -96,8 +98,40 @@ static int fileio_num(struct fioparms * fio, char * filename, FILE * f, AVRMEM * mem, int size, FILEFMT fmt); -static int fmt_autodetect(char * fname); +static int fmt_autodetect(char * fname, size_t offset); + + + +static FILE *fopen_and_seek(const char *filename, const char *mode, size_t offset) +{ + FILE *file; + // On Windows we need to convert the filename to UTF-16 +#if defined(WIN32NATIVE) + static wchar_t fname_buffer[PATH_MAX]; + static wchar_t mode_buffer[MAX_MODE_LEN]; + + if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, fname_buffer, PATH_MAX) == 0) { return NULL; } + if (MultiByteToWideChar(CP_ACP, 0, mode, -1, mode_buffer, MAX_MODE_LEN) == 0) { return NULL; } + + file = _wfopen(fname_buffer, mode_buffer); +#else + file = fopen(filename, mode); +#endif + + if (file != NULL) { + // Some systems allow seeking past the end of file, so we need check for that first and disallow + if (fseek(file, 0, SEEK_END) != 0 + || offset >= ftell(file) + || fseek(file, offset, SEEK_SET) != 0 + ) { + fclose(file); + file = NULL; + errno = EINVAL; + } + } + return file; +} char * fmtstr(FILEFMT format) @@ -1358,7 +1392,7 @@ int fileio_setparms(int op, struct fioparms * fp, -static int fmt_autodetect(char * fname) +static int fmt_autodetect(char * fname, size_t offset) { FILE * f; unsigned char buf[MAX_LINE_LEN]; @@ -1368,10 +1402,11 @@ static int fmt_autodetect(char * fname) int first = 1; #if defined(WIN32NATIVE) - f = fopen(fname, "r"); + f = fopen_and_seek(fname, "r", offset); #else - f = fopen(fname, "rb"); + f = fopen_and_seek(fname, "rb", offset); #endif + if (f == NULL) { avrdude_message(MSG_INFO, "%s: error opening %s: %s\n", progname, fname, strerror(errno)); @@ -1445,7 +1480,7 @@ static int fmt_autodetect(char * fname) int fileio(int op, char * filename, FILEFMT format, - struct avrpart * p, char * memtype, int size) + struct avrpart * p, char * memtype, int size, size_t offset) { int rc; FILE * f; @@ -1477,15 +1512,17 @@ int fileio(int op, char * filename, FILEFMT format, using_stdio = 0; if (strcmp(filename, "-")==0) { - if (fio.op == FIO_READ) { - fname = "<stdin>"; - f = stdin; - } - else { - fname = "<stdout>"; - f = stdout; - } - using_stdio = 1; + return -1; + // Note: we don't want to read stdin or write to stdout as part of Slic3r + // if (fio.op == FIO_READ) { + // fname = "<stdin>"; + // f = stdin; + // } + // else { + // fname = "<stdout>"; + // f = stdout; + // } + // using_stdio = 1; } else { fname = filename; @@ -1502,7 +1539,7 @@ int fileio(int op, char * filename, FILEFMT format, return -1; } - format_detect = fmt_autodetect(fname); + format_detect = fmt_autodetect(fname, offset); if (format_detect < 0) { avrdude_message(MSG_INFO, "%s: can't determine file format for %s, specify explicitly\n", progname, fname); @@ -1533,7 +1570,7 @@ int fileio(int op, char * filename, FILEFMT format, if (format != FMT_IMM) { if (!using_stdio) { - f = fopen(fname, fio.mode); + f = fopen_and_seek(fname, fio.mode, offset); if (f == NULL) { avrdude_message(MSG_INFO, "%s: can't open %s file %s: %s\n", progname, fio.iodesc, fname, strerror(errno)); diff --git a/xs/src/avrdude/libavrdude.h b/xs/src/avrdude/libavrdude.h index e8197f9c2..536f1a2f7 100644 --- a/xs/src/avrdude/libavrdude.h +++ b/xs/src/avrdude/libavrdude.h @@ -737,7 +737,7 @@ extern bool cancel_flag; #define RETURN_IF_CANCEL() \ do { \ if (cancel_flag) { \ - avrdude_message(MSG_INFO, "%s(): Cancelled, exiting...\n", __func__); \ + avrdude_message(MSG_INFO, "avrdude: %s(): Cancelled, exiting...\n", __func__); \ return -99; \ } \ } while (0) @@ -821,7 +821,7 @@ extern "C" { char * fmtstr(FILEFMT format); int fileio(int op, char * filename, FILEFMT format, - struct avrpart * p, char * memtype, int size); + struct avrpart * p, char * memtype, int size, size_t offset); #ifdef __cplusplus } @@ -870,6 +870,7 @@ enum updateflags { typedef struct update_t { char * memtype; int op; + size_t offset; char * filename; int format; } UPDATE; @@ -881,7 +882,7 @@ extern "C" { extern UPDATE * parse_op(char * s); extern UPDATE * dup_update(UPDATE * upd); extern UPDATE * new_update(int op, char * memtype, int filefmt, - char * filename); + char * filename, size_t offset); extern void free_update(UPDATE * upd); extern int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags flags); diff --git a/xs/src/avrdude/main.c b/xs/src/avrdude/main.c index 0550ceff1..d4c34fe44 100644 --- a/xs/src/avrdude/main.c +++ b/xs/src/avrdude/main.c @@ -194,7 +194,7 @@ static void usage(void) " -F Override invalid signature check.\n" " -e Perform a chip erase.\n" " -O Perform RC oscillator calibration (see AVR053). \n" - " -U <memtype>:r|w|v:<filename>[:format]\n" + " -U <memtype>:r|w|v:<offset>:<filename>[:format]\n" " Memory operation specification.\n" " Multiple -U options are allowed, each request\n" " is performed in the order specified.\n" @@ -374,7 +374,7 @@ static void list_parts(FILE * f, const char *prefix, LISTID avrparts) static int cleanup_main(int status) { - if (pgm_setup && pgm->teardown) { + if (pgm_setup && pgm != NULL && pgm->teardown) { pgm->teardown(pgm); } diff --git a/xs/src/avrdude/ser_posix.c b/xs/src/avrdude/ser_posix.c index 91b18e945..cb0fc0385 100644 --- a/xs/src/avrdude/ser_posix.c +++ b/xs/src/avrdude/ser_posix.c @@ -376,6 +376,10 @@ static int ser_recv(union filedescriptor *fd, unsigned char * buf, size_t buflen FD_SET(fd->ifd, &rfds); nfds = select(fd->ifd + 1, &rfds, NULL, NULL, &to2); + // FIXME: The timeout has different behaviour on Linux vs other Unices + // On Linux, the timeout is modified by subtracting the time spent, + // on OS X (for example), it is not modified. + // POSIX recommends re-initializing it before selecting. if (nfds == 0) { avrdude_message(MSG_NOTICE2, "%s: ser_recv(): programmer is not responding\n", progname); diff --git a/xs/src/avrdude/stk500.c b/xs/src/avrdude/stk500.c index 5d2d3c1df..63deb228f 100644 --- a/xs/src/avrdude/stk500.c +++ b/xs/src/avrdude/stk500.c @@ -716,11 +716,14 @@ static int stk500_loadaddr(PROGRAMMER * pgm, AVRMEM * mem, unsigned int addr) } buf[0] = Cmnd_STK_LOAD_ADDRESS; - buf[1] = addr & 0xff; - buf[2] = (addr >> 8) & 0xff; - buf[3] = Sync_CRC_EOP; - - stk500_send(pgm, buf, 4); + // Workaround for the infamous ';' bug in the Prusa3D usb to serial converter. + // Send the binary data by nibbles to avoid transmitting the ';' character. + buf[1] = addr & 0x0f; + buf[2] = addr & 0xf0; + buf[3] = (addr >> 8) & 0x0f; + buf[4] = (addr >> 8) & 0xf0; + buf[5] = Sync_CRC_EOP; + stk500_send(pgm, buf, 6); if (stk500_recv(pgm, buf, 1) < 0) return -1; @@ -765,7 +768,9 @@ static int stk500_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, int block_size; int tries; unsigned int n; - unsigned int i; + unsigned int i, j; + unsigned int prusa3d_semicolon_workaround_round = 0; + bool has_semicolon = false; if (strcmp(m->desc, "flash") == 0) { memtype = 'F'; @@ -806,44 +811,64 @@ static int stk500_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, tries++; stk500_loadaddr(pgm, m, addr/a_div); - /* build command block and avoid multiple send commands as it leads to a crash - of the silabs usb serial driver on mac os x */ - i = 0; - buf[i++] = Cmnd_STK_PROG_PAGE; - buf[i++] = (block_size >> 8) & 0xff; - buf[i++] = block_size & 0xff; - buf[i++] = memtype; - memcpy(&buf[i], &m->buf[addr], block_size); - i += block_size; - buf[i++] = Sync_CRC_EOP; - stk500_send( pgm, buf, i); + for (i = 0; i < n_bytes; ++ i) + if (m->buf[addr + i] == ';') { + has_semicolon = true; + break; + } - if (stk500_recv(pgm, buf, 1) < 0) - return -1; - if (buf[0] == Resp_STK_NOSYNC) { - if (tries > 33) { - avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): can't get into sync\n", - progname); - return -3; + for (prusa3d_semicolon_workaround_round = 0; prusa3d_semicolon_workaround_round < (has_semicolon ? 2 : 1); ++ prusa3d_semicolon_workaround_round) { + /* build command block and avoid multiple send commands as it leads to a crash + of the silabs usb serial driver on mac os x */ + i = 0; + buf[i++] = Cmnd_STK_PROG_PAGE; + // Workaround for the infamous ';' bug in the Prusa3D usb to serial converter. + // Send the binary data by nibbles to avoid transmitting the ';' character. + buf[i++] = (block_size >> 8) & 0xf0; + buf[i++] = (block_size >> 8) & 0x0f; + buf[i++] = block_size & 0xf0; + buf[i++] = block_size & 0x0f; + buf[i++] = memtype; + if (has_semicolon) { + for (j = 0; j < block_size; ++i, ++ j) { + buf[i] = m->buf[addr + j]; + if (buf[i] == ';') + buf[i] |= (prusa3d_semicolon_workaround_round ? 0xf0 : 0x0f); + } + } else { + memcpy(&buf[i], &m->buf[addr], block_size); + i += block_size; + } + buf[i++] = Sync_CRC_EOP; + stk500_send( pgm, buf, i); + + if (stk500_recv(pgm, buf, 1) < 0) + return -1; + if (buf[0] == Resp_STK_NOSYNC) { + if (tries > 33) { + avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): can't get into sync\n", + progname); + return -3; + } + if (stk500_getsync(pgm) < 0) + return -1; + goto retry; + } + else if (buf[0] != Resp_STK_INSYNC) { + avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): (a) protocol error, " + "expect=0x%02x, resp=0x%02x\n", + progname, Resp_STK_INSYNC, buf[0]); + return -4; + } + + if (stk500_recv(pgm, buf, 1) < 0) + return -1; + if (buf[0] != Resp_STK_OK) { + avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): (a) protocol error, " + "expect=0x%02x, resp=0x%02x\n", + progname, Resp_STK_INSYNC, buf[0]); + return -5; } - if (stk500_getsync(pgm) < 0) - return -1; - goto retry; - } - else if (buf[0] != Resp_STK_INSYNC) { - avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): (a) protocol error, " - "expect=0x%02x, resp=0x%02x\n", - progname, Resp_STK_INSYNC, buf[0]); - return -4; - } - - if (stk500_recv(pgm, buf, 1) < 0) - return -1; - if (buf[0] != Resp_STK_OK) { - avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): (a) protocol error, " - "expect=0x%02x, resp=0x%02x\n", - progname, Resp_STK_INSYNC, buf[0]); - return -5; } } @@ -893,11 +918,15 @@ static int stk500_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, tries++; stk500_loadaddr(pgm, m, addr/a_div); buf[0] = Cmnd_STK_READ_PAGE; - buf[1] = (block_size >> 8) & 0xff; - buf[2] = block_size & 0xff; - buf[3] = memtype; - buf[4] = Sync_CRC_EOP; - stk500_send(pgm, buf, 5); + // Workaround for the infamous ';' bug in the Prusa3D usb to serial converter. + // Send the binary data by nibbles to avoid transmitting the ';' character. + buf[1] = (block_size >> 8) & 0xf0; + buf[2] = (block_size >> 8) & 0x0f; + buf[3] = block_size & 0xf0; + buf[4] = block_size & 0x0f; + buf[5] = memtype; + buf[6] = Sync_CRC_EOP; + stk500_send(pgm, buf, 7); if (stk500_recv(pgm, buf, 1) < 0) return -1; diff --git a/xs/src/avrdude/stk500v2.c b/xs/src/avrdude/stk500v2.c index d3acb639c..4d62640c0 100644 --- a/xs/src/avrdude/stk500v2.c +++ b/xs/src/avrdude/stk500v2.c @@ -79,7 +79,7 @@ #define SERIAL_TIMEOUT 2 // Retry count -#define RETRIES 5 +#define RETRIES 0 #if 0 #define DEBUG(...) avrdude_message(MSG_INFO, __VA_ARGS__) @@ -745,7 +745,7 @@ static int stk500v2_recv(PROGRAMMER * pgm, unsigned char *msg, size_t maxsize) { -static int stk500v2_getsync_internal(PROGRAMMER * pgm, int retries) { +int stk500v2_getsync(PROGRAMMER * pgm) { int tries = 0; unsigned char buf[1], resp[32]; int status; @@ -804,7 +804,7 @@ retry: progname, pgmname[PDATA(pgm)->pgmtype]); return 0; } else { - if (tries > retries) { + if (tries > RETRIES) { avrdude_message(MSG_INFO, "%s: stk500v2_getsync(): can't communicate with device: resp=0x%02x\n", progname, resp[0]); return -6; @@ -814,7 +814,7 @@ retry: // or if we got a timeout } else if (status == -1) { - if (tries > retries) { + if (tries > RETRIES) { avrdude_message(MSG_INFO, "%s: stk500v2_getsync(): timeout communicating with programmer\n", progname); return -1; @@ -823,7 +823,7 @@ retry: // or any other error } else { - if (tries > retries) { + if (tries > RETRIES) { avrdude_message(MSG_INFO, "%s: stk500v2_getsync(): error communicating with programmer: (%d)\n", progname,status); } else @@ -833,11 +833,6 @@ retry: return 0; } -int stk500v2_getsync(PROGRAMMER * pgm) { - // This is to avoid applying RETRIES exponentially - return stk500v2_getsync_internal(pgm, RETRIES); -} - static int stk500v2_command(PROGRAMMER * pgm, unsigned char * buf, size_t len, size_t maxlen) { int i; @@ -947,7 +942,7 @@ retry: } // otherwise try to sync up again - status = stk500v2_getsync_internal(pgm, 1); + status = stk500v2_getsync(pgm); if (status != 0) { if (tries > RETRIES) { avrdude_message(MSG_INFO, "%s: stk500v2_command(): failed miserably to execute command 0x%02x\n", diff --git a/xs/src/avrdude/update.c b/xs/src/avrdude/update.c index a73461dfa..e9dd6e325 100644 --- a/xs/src/avrdude/update.c +++ b/xs/src/avrdude/update.c @@ -101,6 +101,24 @@ UPDATE * parse_op(char * s) p++; + // Extension: Parse file contents offset + size_t offset = 0; + + for (; *p != ':'; p++) { + if (*p >= '0' && *p <= '9') { + offset *= 10; + offset += *p - 0x30; + } else { + avrdude_message(MSG_INFO, "%s: invalid update specification: offset is not a number\n", progname); + free(upd->memtype); + free(upd); + return NULL; + } + } + + upd->offset = offset; + p++; + /* * Now, parse the filename component. Instead of looking for the * leftmost possible colon delimiter, we look for the rightmost one. @@ -176,7 +194,7 @@ UPDATE * dup_update(UPDATE * upd) return u; } -UPDATE * new_update(int op, char * memtype, int filefmt, char * filename) +UPDATE * new_update(int op, char * memtype, int filefmt, char * filename, size_t offset) { UPDATE * u; @@ -190,6 +208,7 @@ UPDATE * new_update(int op, char * memtype, int filefmt, char * filename) u->filename = strdup(filename); u->op = op; u->format = filefmt; + u->offset = offset; return u; } @@ -250,7 +269,7 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f progname, strcmp(upd->filename, "-")==0 ? "<stdout>" : upd->filename); } - rc = fileio(FIO_WRITE, upd->filename, upd->format, p, upd->memtype, size); + rc = fileio(FIO_WRITE, upd->filename, upd->format, p, upd->memtype, size, 0); if (rc < 0) { avrdude_message(MSG_INFO, "%s: write to file '%s' failed\n", progname, upd->filename); @@ -267,7 +286,7 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f progname, strcmp(upd->filename, "-")==0 ? "<stdin>" : upd->filename); } - rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1); + rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->offset); if (rc < 0) { avrdude_message(MSG_INFO, "%s: read from file '%s' failed\n", progname, upd->filename); @@ -296,11 +315,11 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f report_progress(1,1,NULL); } else { - /* - * test mode, don't actually write to the chip, output the buffer - * to stdout in intel hex instead - */ - rc = fileio(FIO_WRITE, "-", FMT_IHEX, p, upd->memtype, size); + // /* + // * test mode, don't actually write to the chip, output the buffer + // * to stdout in intel hex instead + // */ + // rc = fileio(FIO_WRITE, "-", FMT_IHEX, p, upd->memtype, size, 0); } if (rc < 0) { @@ -332,7 +351,7 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f progname, mem->desc, upd->filename); } - rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1); + rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->offset); if (rc < 0) { avrdude_message(MSG_INFO, "%s: read from file '%s' failed\n", progname, upd->filename); diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp index 91ba88d84..ceb968a50 100644 --- a/xs/src/libslic3r/BoundingBox.cpp +++ b/xs/src/libslic3r/BoundingBox.cpp @@ -222,6 +222,14 @@ BoundingBox3Base<PointClass>::center() const } template Pointf3 BoundingBox3Base<Pointf3>::center() const; +template <class PointClass> coordf_t +BoundingBox3Base<PointClass>::max_size() const +{ + PointClass s = size(); + return std::max(s.x, std::max(s.y, s.z)); +} +template coordf_t BoundingBox3Base<Pointf3>::max_size() const; + // Align a coordinate to a grid. The coordinate may be negative, // the aligned value will never be bigger than the original one. static inline coord_t _align_to_grid(const coord_t coord, const coord_t spacing) { diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp index 92a2bd451..5de94aa9c 100644 --- a/xs/src/libslic3r/BoundingBox.hpp +++ b/xs/src/libslic3r/BoundingBox.hpp @@ -94,6 +94,7 @@ public: void translate(const Pointf3 &pos) { this->translate(pos.x, pos.y, pos.z); } void offset(coordf_t delta); PointClass center() const; + coordf_t max_size() const; bool contains(const PointClass &point) const { return BoundingBoxBase<PointClass>::contains(point) && point.z >= this->min.z && point.z <= this->max.z; diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp index 0467962c3..2c32db1a6 100644 --- a/xs/src/libslic3r/Format/3mf.cpp +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -1271,6 +1271,7 @@ namespace Slic3r { if ((std::abs(sx - sy) > 0.00001) || (std::abs(sx - sz) > 0.00001)) return; +#if 0 // use quaternions // rotations (extracted using quaternion) double inv_sx = 1.0 / sx; double inv_sy = 1.0 / sy; @@ -1331,6 +1332,25 @@ namespace Slic3r { if (angle_z < 0.0) angle_z += 2.0 * PI; } +#else // use eigen library + double inv_sx = 1.0 / sx; + double inv_sy = 1.0 / sy; + double inv_sz = 1.0 / sz; + + Eigen::Matrix3d m3x3; + m3x3 << (double)matrix(0, 0) * inv_sx, (double)matrix(0, 1) * inv_sy, (double)matrix(0, 2) * inv_sz, + (double)matrix(1, 0) * inv_sx, (double)matrix(1, 1) * inv_sy, (double)matrix(1, 2) * inv_sz, + (double)matrix(2, 0) * inv_sx, (double)matrix(2, 1) * inv_sy, (double)matrix(2, 2) * inv_sz; + + Eigen::AngleAxisd rotation; + rotation.fromRotationMatrix(m3x3); + + // invalid rotation axis, we currently handle only rotations around Z axis + if ((rotation.angle() != 0.0) && (rotation.axis() != Eigen::Vector3d::UnitZ()) && (rotation.axis() != -Eigen::Vector3d::UnitZ())) + return; + + double angle_z = (rotation.axis() == Eigen::Vector3d::UnitZ()) ? rotation.angle() : -rotation.angle(); +#endif instance.offset.x = offset_x; instance.offset.y = offset_y; diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp index 83b50ec9e..263363756 100644 --- a/xs/src/libslic3r/Format/AMF.cpp +++ b/xs/src/libslic3r/Format/AMF.cpp @@ -13,6 +13,9 @@ #include <boost/filesystem/operations.hpp> #include <boost/algorithm/string.hpp> +//############################################################################################################################################ +#include <boost/nowide/fstream.hpp> +//############################################################################################################################################ #include <miniz/miniz_zip.h> #if 0 @@ -666,10 +669,21 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) // If bundle is not a null pointer, updates it if the amf file/archive contains config data bool load_amf(const char *path, PresetBundle* bundle, Model *model) { - if (boost::iends_with(path, ".zip.amf")) - return load_amf_archive(path, bundle, model); - else if (boost::iends_with(path, ".amf") || boost::iends_with(path, ".amf.xml")) + if (boost::iends_with(path, ".amf.xml")) + // backward compatibility with older slic3r output return load_amf_file(path, bundle, model); + else if (boost::iends_with(path, ".amf")) + { + boost::nowide::ifstream file(path, boost::nowide::ifstream::binary); + if (!file.good()) + return false; + + std::string zip_mask(2, '\0'); + file.read(const_cast<char*>(zip_mask.data()), 2); + file.close(); + + return (zip_mask == "PK") ? load_amf_archive(path, bundle, model) : load_amf_file(path, bundle, model); + } else return false; } diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 6486f2917..5aa6470a2 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -142,24 +142,21 @@ public: } m_gcode += "G1"; - if (rot.x != rotated_current_pos.x) { - m_gcode += set_format_X(rot.x); // Transform current position back to wipe tower coordinates (was updated by set_format_X) - m_current_pos.x = x; - } - if (rot.y != rotated_current_pos.y) { + if (std::abs(rot.x - rotated_current_pos.x) > EPSILON) + m_gcode += set_format_X(rot.x); + + if (std::abs(rot.y - rotated_current_pos.y) > EPSILON) m_gcode += set_format_Y(rot.y); - m_current_pos.y = y; - } if (e != 0.f) m_gcode += set_format_E(e); if (f != 0.f && f != m_current_feedrate) m_gcode += set_format_F(f); - - - - + + m_current_pos.x = x; + m_current_pos.y = y; + // Update the elapsed time with a rough estimate. m_elapsed_time += ((len == 0) ? std::abs(e) : len) / m_current_feedrate * 60.f; m_gcode += "\n"; 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/Polygon.cpp b/xs/src/libslic3r/Polygon.cpp index 27f9a2ca1..b5fd7e64f 100644 --- a/xs/src/libslic3r/Polygon.cpp +++ b/xs/src/libslic3r/Polygon.cpp @@ -103,7 +103,7 @@ double Polygon::area() const double a = 0.; for (size_t i = 0, j = n - 1; i < n; ++i) { - a += double(points[j].x + points[i].x) * double(points[i].y - points[j].y); + a += ((double)points[j].x + (double)points[i].x) * ((double)points[i].y - (double)points[j].y); j = i; } return 0.5 * a; diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index c56e64c6c..86c15b679 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -184,6 +184,8 @@ public: void reset_layer_height_profile(); + void adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action); + // Collect the slicing parameters, to be used by variable layer thickness algorithm, // by the interactive layer height editor and by the printing process itself. // The slicing parameters are dependent on various configuration values diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index b0341db16..ba0876a85 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -4,6 +4,7 @@ #include "Geometry.hpp" #include "SupportMaterial.hpp" #include "Surface.hpp" +#include "Slicing.hpp" #include <utility> #include <boost/log/trivial.hpp> @@ -1961,4 +1962,12 @@ void PrintObject::reset_layer_height_profile() this->model_object()->layer_height_profile_valid = false; } +void PrintObject::adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action) +{ + update_layer_height_profile(_model_object->layer_height_profile); + Slic3r::adjust_layer_height_profile(slicing_parameters(), _model_object->layer_height_profile, z, layer_thickness_delta, band_width, LayerHeightEditActionType(action)); + _model_object->layer_height_profile_valid = true; + layer_height_profile_valid = false; +} + } // namespace Slic3r diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index e5157741e..921841a27 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -91,10 +91,13 @@ public: ~PerlCallback() { this->deregister_callback(); } void register_callback(void *sv); void deregister_callback(); - void call(); - void call(int i); - void call(int i, int j); -// void call(const std::vector<int> &ints); + void call() const; + void call(int i) const; + void call(int i, int j) const; + void call(const std::vector<int>& ints) const; + void call(double d) const; + void call(double x, double y) const; + void call(bool b) const; private: void *m_callback; }; diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 4aef4d5c1..b0c144efe 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -14,7 +14,7 @@ #include <boost/thread.hpp> #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" -#define SLIC3R_VERSION "1.40.0" +#define SLIC3R_VERSION "1.40.1" #define SLIC3R_BUILD "UNKNOWN" typedef int32_t coord_t; diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 582488c5a..745d07fcd 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -184,7 +184,7 @@ void PerlCallback::deregister_callback() } } -void PerlCallback::call() +void PerlCallback::call() const { if (! m_callback) return; @@ -198,7 +198,7 @@ void PerlCallback::call() LEAVE; } -void PerlCallback::call(int i) +void PerlCallback::call(int i) const { if (! m_callback) return; @@ -213,7 +213,7 @@ void PerlCallback::call(int i) LEAVE; } -void PerlCallback::call(int i, int j) +void PerlCallback::call(int i, int j) const { if (! m_callback) return; @@ -229,8 +229,7 @@ void PerlCallback::call(int i, int j) LEAVE; } -/* -void PerlCallback::call(const std::vector<int> &ints) +void PerlCallback::call(const std::vector<int>& ints) const { if (! m_callback) return; @@ -238,16 +237,51 @@ void PerlCallback::call(const std::vector<int> &ints) ENTER; SAVETMPS; PUSHMARK(SP); - AV* av = newAV(); for (int i : ints) - av_push(av, newSViv(i)); - XPUSHs(av); + { + XPUSHs(sv_2mortal(newSViv(i))); + } + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +void PerlCallback::call(double d) const +{ + if (!m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVnv(d))); + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +void PerlCallback::call(double x, double y) const +{ + if (!m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVnv(x))); + XPUSHs(sv_2mortal(newSVnv(y))); PUTBACK; perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); FREETMPS; LEAVE; } -*/ + +void PerlCallback::call(bool b) const +{ + call(b ? 1 : 0); +} #ifdef WIN32 #ifndef NOMINMAX diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index cb2e08e1f..09e10ac28 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -433,6 +433,7 @@ std::vector<int> GLVolumeCollection::load_object( v.extruder_id = extruder_id; } v.is_modifier = model_volume->modifier; + v.outside_printer_detection_enabled = !model_volume->modifier; } } @@ -663,7 +664,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config) bool contained = true; for (GLVolume* volume : this->volumes) { - if (volume != nullptr) + if ((volume != nullptr) && !volume->is_modifier) { bool state = print_volume.contains(volume->transformed_bounding_box()); contained &= state; @@ -1348,8 +1349,8 @@ static void point_to_indexed_vertex_array(const Point3& point, volume.push_triangle(idxs[0], idxs[3], idxs[4]); } -static void thick_lines_to_verts( - const Lines &lines, +void _3DScene::thick_lines_to_verts( + const Lines &lines, const std::vector<double> &widths, const std::vector<double> &heights, bool closed, @@ -1359,7 +1360,7 @@ static void thick_lines_to_verts( thick_lines_to_indexed_vertex_array(lines, widths, heights, closed, top_z, volume.indexed_vertex_array); } -static void thick_lines_to_verts(const Lines3& lines, +void _3DScene::thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, @@ -1377,7 +1378,7 @@ static void thick_point_to_verts(const Point3& point, } // Fill in the qverts and tverts with quads and triangles for the extrusion_path. -static inline void extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, GLVolume &volume) { Lines lines = extrusion_path.polyline.lines(); std::vector<double> widths(lines.size(), extrusion_path.width); @@ -1386,7 +1387,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 +1399,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<double> widths; @@ -1416,7 +1417,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<double> widths; @@ -1433,15 +1434,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<const ExtrusionPath*>(extrusion_entity); @@ -1468,7 +1467,7 @@ static void extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, fl } } -static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume) +void _3DScene::polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume) { Lines3 lines = polyline.lines(); std::vector<double> widths(lines.size(), width); @@ -1476,14 +1475,14 @@ static void polyline3_to_verts(const Polyline3& polyline, double width, double h thick_lines_to_verts(lines, widths, heights, false, volume); } -static void point3_to_verts(const Point3& point, double width, double height, GLVolume& volume) +void _3DScene::point3_to_verts(const Point3& point, double width, double height, GLVolume& volume) { thick_point_to_verts(point, width, height, volume); } -_3DScene::GCodePreviewVolumeIndex _3DScene::s_gcode_preview_volume_index; _3DScene::LegendTexture _3DScene::s_legend_texture; _3DScene::WarningTexture _3DScene::s_warning_texture; +GUI::GLCanvas3DManager _3DScene::s_canvas_mgr; unsigned int _3DScene::TextureBase::finalize() { @@ -1720,1049 +1719,441 @@ bool _3DScene::LegendTexture::generate(const GCodePreviewData& preview_data, con return true; } -void _3DScene::_glew_init() -{ - glewInit(); +void _3DScene::init_gl() +{ + s_canvas_mgr.init_gl(); } -static inline int hex_digit_to_int(const char c) +std::string _3DScene::get_gl_info(bool format_as_html, bool extensions) { - return - (c >= '0' && c <= '9') ? int(c - '0') : - (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : - (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; + return s_canvas_mgr.get_gl_info(format_as_html, extensions); } -static inline std::vector<float> parse_colors(const std::vector<std::string> &scolors) +bool _3DScene::use_VBOs() { - std::vector<float> output(scolors.size() * 4, 1.f); - for (size_t i = 0; i < scolors.size(); ++ i) { - const std::string &scolor = scolors[i]; - const char *c = scolor.data() + 1; - if (scolor.size() == 7 && scolor.front() == '#') { - for (size_t j = 0; j < 3; ++j) { - int digit1 = hex_digit_to_int(*c ++); - int digit2 = hex_digit_to_int(*c ++); - if (digit1 == -1 || digit2 == -1) - break; - output[i * 4 + j] = float(digit1 * 16 + digit2) / 255.f; - } - } - } - return output; + return s_canvas_mgr.use_VBOs(); } -void _3DScene::load_gcode_preview(const Print* print, const GCodePreviewData* preview_data, GLVolumeCollection* volumes, const std::vector<std::string>& str_tool_colors, bool use_VBOs) +bool _3DScene::add_canvas(wxGLCanvas* canvas) { - if ((preview_data == nullptr) || (volumes == nullptr)) - return; - - if (volumes->empty()) - { - std::vector<float> tool_colors = parse_colors(str_tool_colors); - - s_gcode_preview_volume_index.reset(); - - _load_gcode_extrusion_paths(*preview_data, *volumes, tool_colors, use_VBOs); - _load_gcode_travel_paths(*preview_data, *volumes, tool_colors, use_VBOs); - _load_gcode_retractions(*preview_data, *volumes, use_VBOs); - _load_gcode_unretractions(*preview_data, *volumes, use_VBOs); - - if (volumes->empty()) - reset_legend_texture(); - else - { - _generate_legend_texture(*preview_data, tool_colors); - - // removes empty volumes - volumes->volumes.erase(std::remove_if(volumes->volumes.begin(), volumes->volumes.end(), - [](const GLVolume *volume) { return volume->print_zs.empty(); }), - volumes->volumes.end()); - - _load_shells(*print, *volumes, use_VBOs); - } - } - - _update_gcode_volumes_visibility(*preview_data, *volumes); + return s_canvas_mgr.add(canvas); } -unsigned int _3DScene::get_legend_texture_width() +bool _3DScene::remove_canvas(wxGLCanvas* canvas) { - return s_legend_texture.get_texture_width(); + return s_canvas_mgr.remove(canvas); } -unsigned int _3DScene::get_legend_texture_height() +void _3DScene::remove_all_canvases() { - return s_legend_texture.get_texture_height(); + s_canvas_mgr.remove_all(); } -void _3DScene::reset_legend_texture() +bool _3DScene::init(wxGLCanvas* canvas) { - s_legend_texture.reset_texture(); + return s_canvas_mgr.init(canvas); } -unsigned int _3DScene::finalize_legend_texture() +void _3DScene::set_as_dirty(wxGLCanvas* canvas) { - return s_legend_texture.finalize(); + s_canvas_mgr.set_as_dirty(canvas); } -unsigned int _3DScene::get_warning_texture_width() +unsigned int _3DScene::get_volumes_count(wxGLCanvas* canvas) { - return s_warning_texture.get_texture_width(); + return s_canvas_mgr.get_volumes_count(canvas); } -unsigned int _3DScene::get_warning_texture_height() +void _3DScene::reset_volumes(wxGLCanvas* canvas) { - return s_warning_texture.get_texture_height(); + s_canvas_mgr.reset_volumes(canvas); } -void _3DScene::generate_warning_texture(const std::string& msg) +void _3DScene::deselect_volumes(wxGLCanvas* canvas) { - s_warning_texture.generate(msg); + s_canvas_mgr.deselect_volumes(canvas); } -void _3DScene::reset_warning_texture() +void _3DScene::select_volume(wxGLCanvas* canvas, unsigned int id) { - s_warning_texture.reset_texture(); + s_canvas_mgr.select_volume(canvas, id); } -unsigned int _3DScene::finalize_warning_texture() +void _3DScene::update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections) { - return s_warning_texture.finalize(); + s_canvas_mgr.update_volumes_selection(canvas, selections); } -// Create 3D thick extrusion lines for a skirt and brim. -// Adds a new Slic3r::GUI::3DScene::Volume to volumes. -void _3DScene::_load_print_toolpaths( - const Print *print, - GLVolumeCollection *volumes, - const std::vector<std::string> &tool_colors, - bool use_VBOs) +bool _3DScene::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) { - if (!print->has_skirt() && print->config.brim_width.value == 0) - return; - - const float color[] = { 0.5f, 1.0f, 0.5f, 1.f }; // greenish - - // number of skirt layers - size_t total_layer_count = 0; - for (const PrintObject *print_object : print->objects) - total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); - size_t skirt_height = print->has_infinite_skirt() ? - total_layer_count : - std::min<size_t>(print->config.skirt_height.value, total_layer_count); - if (skirt_height == 0 && print->config.brim_width.value > 0) - skirt_height = 1; - - // get first skirt_height layers (maybe this should be moved to a PrintObject method?) - const PrintObject *object0 = print->objects.front(); - std::vector<float> print_zs; - print_zs.reserve(skirt_height * 2); - for (size_t i = 0; i < std::min(skirt_height, object0->layers.size()); ++ i) - print_zs.push_back(float(object0->layers[i]->print_z)); - //FIXME why there are support layers? - for (size_t i = 0; i < std::min(skirt_height, object0->support_layers.size()); ++ i) - print_zs.push_back(float(object0->support_layers[i]->print_z)); - sort_remove_duplicates(print_zs); - if (print_zs.size() > skirt_height) - print_zs.erase(print_zs.begin() + skirt_height, print_zs.end()); - - volumes->volumes.emplace_back(new GLVolume(color)); - GLVolume &volume = *volumes->volumes.back(); - for (size_t i = 0; i < skirt_height; ++ i) { - volume.print_zs.push_back(print_zs[i]); - volume.offsets.push_back(volume.indexed_vertex_array.quad_indices.size()); - volume.offsets.push_back(volume.indexed_vertex_array.triangle_indices.size()); - if (i == 0) - extrusionentity_to_verts(print->brim, print_zs[i], Point(0, 0), volume); - extrusionentity_to_verts(print->skirt, print_zs[i], Point(0, 0), volume); - } - volume.bounding_box = volume.indexed_vertex_array.bounding_box(); - volume.indexed_vertex_array.finalize_geometry(use_VBOs); + return s_canvas_mgr.check_volumes_outside_state(canvas, config); } -// Create 3D thick extrusion lines for object forming extrusions. -// Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes, -// one for perimeters, one for infill and one for supports. -void _3DScene::_load_print_object_toolpaths( - const PrintObject *print_object, - GLVolumeCollection *volumes, - const std::vector<std::string> &tool_colors_str, - bool use_VBOs) +bool _3DScene::move_volume_up(wxGLCanvas* canvas, unsigned int id) { - std::vector<float> tool_colors = parse_colors(tool_colors_str); - - struct Ctxt - { - const Points *shifted_copies; - std::vector<const Layer*> layers; - bool has_perimeters; - bool has_infill; - bool has_support; - const std::vector<float>* tool_colors; - - // Number of vertices (each vertex is 6x4=24 bytes long) - static const size_t alloc_size_max () { return 131072; } // 3.15MB -// static const size_t alloc_size_max () { return 65536; } // 1.57MB -// static const size_t alloc_size_max () { return 32768; } // 786kB - static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } - - static const float* color_perimeters () { static float color[4] = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow - static const float* color_infill () { static float color[4] = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish - static const float* color_support () { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish - - // For cloring by a tool, return a parsed color. - bool color_by_tool() const { return tool_colors != nullptr; } - size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } - const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } - int volume_idx(int extruder, int feature) const - { return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(extruder - 1, 0)) : feature; } - } ctxt; - - ctxt.shifted_copies = &print_object->_shifted_copies; - - // order layers by print_z - ctxt.layers.reserve(print_object->layers.size() + print_object->support_layers.size()); - for (const Layer *layer : print_object->layers) - ctxt.layers.push_back(layer); - for (const Layer *layer : print_object->support_layers) - ctxt.layers.push_back(layer); - std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; }); - - // Maximum size of an allocation block: 32MB / sizeof(float) - ctxt.has_perimeters = print_object->state.is_done(posPerimeters); - ctxt.has_infill = print_object->state.is_done(posInfill); - ctxt.has_support = print_object->state.is_done(posSupportMaterial); - ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start"; - - //FIXME Improve the heuristics for a grain size. - size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); - tbb::spin_mutex new_volume_mutex; - auto new_volume = [volumes, &new_volume_mutex](const float *color) -> GLVolume* { - auto *volume = new GLVolume(color); - new_volume_mutex.lock(); - volume->outside_printer_detection_enabled = false; - volumes->volumes.emplace_back(volume); - new_volume_mutex.unlock(); - return volume; - }; - const size_t volumes_cnt_initial = volumes->volumes.size(); - std::vector<GLVolumeCollection> volumes_per_thread(ctxt.layers.size()); - tbb::parallel_for( - tbb::blocked_range<size_t>(0, ctxt.layers.size(), grain_size), - [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) { - std::vector<GLVolume*> vols; - if (ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++ i) - vols.emplace_back(new_volume(ctxt.color_tool(i))); - } else - vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; - for (GLVolume *vol : vols) - vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { - const Layer *layer = ctxt.layers[idx_layer]; - for (size_t i = 0; i < vols.size(); ++ i) { - GLVolume &vol = *vols[i]; - if (vol.print_zs.empty() || vol.print_zs.back() != layer->print_z) { - vol.print_zs.push_back(layer->print_z); - vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); - vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); - } - } - for (const Point ©: *ctxt.shifted_copies) { - for (const LayerRegion *layerm : layer->regions) { - if (ctxt.has_perimeters) - extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, - *vols[ctxt.volume_idx(layerm->region()->config.perimeter_extruder.value, 0)]); - if (ctxt.has_infill) { - for (const ExtrusionEntity *ee : layerm->fills.entities) { - // fill represents infill extrusions of a single island. - const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee); - if (! fill->entities.empty()) - extrusionentity_to_verts(*fill, float(layer->print_z), copy, - *vols[ctxt.volume_idx( - is_solid_infill(fill->entities.front()->role()) ? - layerm->region()->config.solid_infill_extruder : - layerm->region()->config.infill_extruder, - 1)]); - } - } - } - if (ctxt.has_support) { - const SupportLayer *support_layer = dynamic_cast<const SupportLayer*>(layer); - if (support_layer) { - for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) - extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, - *vols[ctxt.volume_idx( - (extrusion_entity->role() == erSupportMaterial) ? - support_layer->object()->config.support_material_extruder : - support_layer->object()->config.support_material_interface_extruder, - 2)]); - } - } - } - for (size_t i = 0; i < vols.size(); ++ i) { - GLVolume &vol = *vols[i]; - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { - // Store the vertex arrays and restart their containers, - vols[i] = new_volume(vol.color); - GLVolume &vol_new = *vols[i]; - // Assign the large pre-allocated buffers to the new GLVolume. - vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); - // Copy the content back to the old GLVolume. - vol.indexed_vertex_array = vol_new.indexed_vertex_array; - // Finalize a bounding box of the old GLVolume. - vol.bounding_box = vol.indexed_vertex_array.bounding_box(); - // Clear the buffers, but keep them pre-allocated. - vol_new.indexed_vertex_array.clear(); - // Just make sure that clear did not clear the reserved memory. - vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - } - } - } - for (GLVolume *vol : vols) { - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); - vol->indexed_vertex_array.shrink_to_fit(); - } - }); - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results"; - // Remove empty volumes from the newly added volumes. - volumes->volumes.erase( - std::remove_if(volumes->volumes.begin() + volumes_cnt_initial, volumes->volumes.end(), - [](const GLVolume *volume) { return volume->empty(); }), - volumes->volumes.end()); - for (size_t i = volumes_cnt_initial; i < volumes->volumes.size(); ++ i) - volumes->volumes[i]->indexed_vertex_array.finalize_geometry(use_VBOs); - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; -} - -void _3DScene::_load_wipe_tower_toolpaths( - const Print *print, - GLVolumeCollection *volumes, - const std::vector<std::string> &tool_colors_str, - bool use_VBOs) -{ - if (print->m_wipe_tower_tool_changes.empty()) - return; - - std::vector<float> tool_colors = parse_colors(tool_colors_str); - - struct Ctxt - { - const Print *print; - const std::vector<float> *tool_colors; - - // Number of vertices (each vertex is 6x4=24 bytes long) - static const size_t alloc_size_max () { return 131072; } // 3.15MB - static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } - - static const float* color_support () { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish - - // For cloring by a tool, return a parsed color. - bool color_by_tool() const { return tool_colors != nullptr; } - size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } - const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } - int volume_idx(int tool, int feature) const - { return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(tool, 0)) : feature; } - - const std::vector<WipeTower::ToolChangeResult>& tool_change(size_t idx) { - return priming.empty() ? - ((idx == print->m_wipe_tower_tool_changes.size()) ? final : print->m_wipe_tower_tool_changes[idx]) : - ((idx == 0) ? priming : (idx == print->m_wipe_tower_tool_changes.size() + 1) ? final : print->m_wipe_tower_tool_changes[idx - 1]); - } - std::vector<WipeTower::ToolChangeResult> priming; - std::vector<WipeTower::ToolChangeResult> final; - } ctxt; - - ctxt.print = print; - ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - if (print->m_wipe_tower_priming) - ctxt.priming.emplace_back(*print->m_wipe_tower_priming.get()); - if (print->m_wipe_tower_final_purge) - ctxt.final.emplace_back(*print->m_wipe_tower_final_purge.get()); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start"; - - //FIXME Improve the heuristics for a grain size. - size_t n_items = print->m_wipe_tower_tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); - size_t grain_size = std::max(n_items / 128, size_t(1)); - tbb::spin_mutex new_volume_mutex; - auto new_volume = [volumes, &new_volume_mutex](const float *color) -> GLVolume* { - auto *volume = new GLVolume(color); - new_volume_mutex.lock(); - volume->outside_printer_detection_enabled = false; - volumes->volumes.emplace_back(volume); - new_volume_mutex.unlock(); - return volume; - }; - const size_t volumes_cnt_initial = volumes->volumes.size(); - std::vector<GLVolumeCollection> volumes_per_thread(n_items); - tbb::parallel_for( - tbb::blocked_range<size_t>(0, n_items, grain_size), - [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) { - // Bounding box of this slab of a wipe tower. - std::vector<GLVolume*> vols; - if (ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++ i) - vols.emplace_back(new_volume(ctxt.color_tool(i))); - } else - vols = { new_volume(ctxt.color_support()) }; - for (GLVolume *volume : vols) - volume->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { - const std::vector<WipeTower::ToolChangeResult> &layer = ctxt.tool_change(idx_layer); - for (size_t i = 0; i < vols.size(); ++ i) { - GLVolume &vol = *vols[i]; - if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { - vol.print_zs.push_back(layer.front().print_z); - vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); - vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); - } - } - for (const WipeTower::ToolChangeResult &extrusions : layer) { - for (size_t i = 1; i < extrusions.extrusions.size();) { - const WipeTower::Extrusion &e = extrusions.extrusions[i]; - if (e.width == 0.) { - ++ i; - continue; - } - size_t j = i + 1; - if (ctxt.color_by_tool()) - for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++ j) ; - else - for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++ j) ; - size_t n_lines = j - i; - Lines lines; - std::vector<double> widths; - std::vector<double> heights; - lines.reserve(n_lines); - widths.reserve(n_lines); - heights.assign(n_lines, extrusions.layer_height); - for (; i < j; ++ i) { - const WipeTower::Extrusion &e = extrusions.extrusions[i]; - assert(e.width > 0.f); - const WipeTower::Extrusion &e_prev = *(&e - 1); - lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y)); - widths.emplace_back(e.width); - } - thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, - *vols[ctxt.volume_idx(e.tool, 0)]); - } - } - } - for (size_t i = 0; i < vols.size(); ++ i) { - GLVolume &vol = *vols[i]; - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { - // Store the vertex arrays and restart their containers, - vols[i] = new_volume(vol.color); - GLVolume &vol_new = *vols[i]; - // Assign the large pre-allocated buffers to the new GLVolume. - vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); - // Copy the content back to the old GLVolume. - vol.indexed_vertex_array = vol_new.indexed_vertex_array; - // Finalize a bounding box of the old GLVolume. - vol.bounding_box = vol.indexed_vertex_array.bounding_box(); - // Clear the buffers, but keep them pre-allocated. - vol_new.indexed_vertex_array.clear(); - // Just make sure that clear did not clear the reserved memory. - vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - } - } - for (GLVolume *vol : vols) { - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); - vol->indexed_vertex_array.shrink_to_fit(); - } - }); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results"; - // Remove empty volumes from the newly added volumes. - volumes->volumes.erase( - std::remove_if(volumes->volumes.begin() + volumes_cnt_initial, volumes->volumes.end(), - [](const GLVolume *volume) { return volume->empty(); }), - volumes->volumes.end()); - for (size_t i = volumes_cnt_initial; i < volumes->volumes.size(); ++ i) - volumes->volumes[i]->indexed_vertex_array.finalize_geometry(use_VBOs); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; + return s_canvas_mgr.move_volume_up(canvas, id); } -void _3DScene::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs) +bool _3DScene::move_volume_down(wxGLCanvas* canvas, unsigned int id) { - // helper functions to select data in dependence of the extrusion view type - struct Helper - { - static float path_filter(GCodePreviewData::Extrusion::EViewType type, const ExtrusionPath& path) - { - switch (type) - { - case GCodePreviewData::Extrusion::FeatureType: - return (float)path.role(); - case GCodePreviewData::Extrusion::Height: - return path.height; - case GCodePreviewData::Extrusion::Width: - return path.width; - case GCodePreviewData::Extrusion::Feedrate: - return path.feedrate; - case GCodePreviewData::Extrusion::VolumetricRate: - return path.feedrate * (float)path.mm3_per_mm; - case GCodePreviewData::Extrusion::Tool: - return (float)path.extruder_id; - } - - return 0.0f; - } - - static GCodePreviewData::Color path_color(const GCodePreviewData& data, const std::vector<float>& tool_colors, float value) - { - switch (data.extrusion.view_type) - { - case GCodePreviewData::Extrusion::FeatureType: - return data.get_extrusion_role_color((ExtrusionRole)(int)value); - case GCodePreviewData::Extrusion::Height: - return data.get_height_color(value); - case GCodePreviewData::Extrusion::Width: - return data.get_width_color(value); - case GCodePreviewData::Extrusion::Feedrate: - return data.get_feedrate_color(value); - case GCodePreviewData::Extrusion::VolumetricRate: - return data.get_volumetric_rate_color(value); - case GCodePreviewData::Extrusion::Tool: - { - GCodePreviewData::Color color; - ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + (unsigned int)value * 4), 4 * sizeof(float)); - return color; - } - } - - return GCodePreviewData::Color::Dummy; - } - }; - - // Helper structure for filters - struct Filter - { - float value; - ExtrusionRole role; - GLVolume* volume; - - Filter(float value, ExtrusionRole role) - : value(value) - , role(role) - , volume(nullptr) - { - } - - bool operator == (const Filter& other) const - { - if (value != other.value) - return false; - - if (role != other.role) - return false; - - return true; - } - }; + return s_canvas_mgr.move_volume_down(canvas, id); +} - typedef std::vector<Filter> FiltersList; - size_t initial_volumes_count = volumes.volumes.size(); +void _3DScene::set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections) +{ + s_canvas_mgr.set_objects_selections(canvas, selections); +} - // detects filters - FiltersList filters; - for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) - { - for (const ExtrusionPath& path : layer.paths) - { - ExtrusionRole role = path.role(); - float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path); - if (std::find(filters.begin(), filters.end(), Filter(path_filter, role)) == filters.end()) - filters.emplace_back(path_filter, role); - } - } +void _3DScene::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config) +{ + s_canvas_mgr.set_config(canvas, config); +} - // nothing to render, return - if (filters.empty()) - return; +void _3DScene::set_print(wxGLCanvas* canvas, Print* print) +{ + s_canvas_mgr.set_print(canvas, print); +} - // creates a new volume for each filter - for (Filter& filter : filters) - { - s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Extrusion, (unsigned int)filter.role, (unsigned int)volumes.volumes.size()); - GLVolume* volume = new GLVolume(Helper::path_color(preview_data, tool_colors, filter.value).rgba); - if (volume != nullptr) - { - filter.volume = volume; - volumes.volumes.emplace_back(volume); - } - else - { - // an error occourred - restore to previous state and return - s_gcode_preview_volume_index.first_volumes.pop_back(); - if (initial_volumes_count != volumes.volumes.size()) - { - std::vector<GLVolume*>::iterator begin = volumes.volumes.begin() + initial_volumes_count; - std::vector<GLVolume*>::iterator end = volumes.volumes.end(); - for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it) - { - GLVolume* volume = *it; - delete volume; - } - volumes.volumes.erase(begin, end); - return; - } - } - } +void _3DScene::set_model(wxGLCanvas* canvas, Model* model) +{ + s_canvas_mgr.set_model(canvas, model); +} - // populates volumes - for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) - { - for (const ExtrusionPath& path : layer.paths) - { - float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path); - FiltersList::iterator filter = std::find(filters.begin(), filters.end(), Filter(path_filter, path.role())); - if (filter != filters.end()) - { - filter->volume->print_zs.push_back(layer.z); - filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.quad_indices.size()); - filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.triangle_indices.size()); +void _3DScene::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape) +{ + return s_canvas_mgr.set_bed_shape(canvas, shape); +} - extrusionentity_to_verts(path, layer.z, *filter->volume); - } - } - } +void _3DScene::set_auto_bed_shape(wxGLCanvas* canvas) +{ + return s_canvas_mgr.set_auto_bed_shape(canvas); +} - // finalize volumes and sends geometry to gpu - if (volumes.volumes.size() > initial_volumes_count) - { - for (size_t i = initial_volumes_count; i < volumes.volumes.size(); ++i) - { - GLVolume* volume = volumes.volumes[i]; - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(use_VBOs); - } - } +BoundingBoxf3 _3DScene::get_volumes_bounding_box(wxGLCanvas* canvas) +{ + return s_canvas_mgr.get_volumes_bounding_box(canvas); } -void _3DScene::_load_gcode_travel_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs) +void _3DScene::set_axes_length(wxGLCanvas* canvas, float length) { - size_t initial_volumes_count = volumes.volumes.size(); - s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Travel, 0, (unsigned int)initial_volumes_count); + s_canvas_mgr.set_axes_length(canvas, length); +} - bool res = true; - switch (preview_data.extrusion.view_type) - { - case GCodePreviewData::Extrusion::Feedrate: - { - res = _travel_paths_by_feedrate(preview_data, volumes); - break; - } - case GCodePreviewData::Extrusion::Tool: - { - res = _travel_paths_by_tool(preview_data, volumes, tool_colors); - break; - } - default: - { - res = _travel_paths_by_type(preview_data, volumes); - break; - } - } +void _3DScene::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons) +{ + return s_canvas_mgr.set_cutting_plane(canvas, z, polygons); +} - if (!res) - { - // an error occourred - restore to previous state and return - if (initial_volumes_count != volumes.volumes.size()) - { - std::vector<GLVolume*>::iterator begin = volumes.volumes.begin() + initial_volumes_count; - std::vector<GLVolume*>::iterator end = volumes.volumes.end(); - for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it) - { - GLVolume* volume = *it; - delete volume; - } - volumes.volumes.erase(begin, end); - } +void _3DScene::set_color_by(wxGLCanvas* canvas, const std::string& value) +{ + return s_canvas_mgr.set_color_by(canvas, value); +} - return; - } +void _3DScene::set_select_by(wxGLCanvas* canvas, const std::string& value) +{ + return s_canvas_mgr.set_select_by(canvas, value); +} - // finalize volumes and sends geometry to gpu - if (volumes.volumes.size() > initial_volumes_count) - { - for (size_t i = initial_volumes_count; i < volumes.volumes.size(); ++i) - { - GLVolume* volume = volumes.volumes[i]; - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(use_VBOs); - } - } +void _3DScene::set_drag_by(wxGLCanvas* canvas, const std::string& value) +{ + return s_canvas_mgr.set_drag_by(canvas, value); } -bool _3DScene::_travel_paths_by_type(const GCodePreviewData& preview_data, GLVolumeCollection& volumes) +bool _3DScene::is_layers_editing_enabled(wxGLCanvas* canvas) { - // Helper structure for types - struct Type - { - GCodePreviewData::Travel::EType value; - GLVolume* volume; + return s_canvas_mgr.is_layers_editing_enabled(canvas); +} - explicit Type(GCodePreviewData::Travel::EType value) - : value(value) - , volume(nullptr) - { - } +bool _3DScene::is_layers_editing_allowed(wxGLCanvas* canvas) +{ + return s_canvas_mgr.is_layers_editing_allowed(canvas); +} - bool operator == (const Type& other) const - { - return value == other.value; - } - }; +bool _3DScene::is_shader_enabled(wxGLCanvas* canvas) +{ + return s_canvas_mgr.is_shader_enabled(canvas); +} - typedef std::vector<Type> TypesList; +bool _3DScene::is_reload_delayed(wxGLCanvas* canvas) +{ + return s_canvas_mgr.is_reload_delayed(canvas); +} - // colors travels by travel type +void _3DScene::enable_layers_editing(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_layers_editing(canvas, enable); +} - // detects types - TypesList types; - for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) - { - if (std::find(types.begin(), types.end(), Type(polyline.type)) == types.end()) - types.emplace_back(polyline.type); - } +void _3DScene::enable_warning_texture(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_warning_texture(canvas, enable); +} - // nothing to render, return - if (types.empty()) - return true; +void _3DScene::enable_legend_texture(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_legend_texture(canvas, enable); +} - // creates a new volume for each type - for (Type& type : types) - { - GLVolume* volume = new GLVolume(preview_data.travel.type_colors[type.value].rgba); - if (volume == nullptr) - return false; - else - { - type.volume = volume; - volumes.volumes.emplace_back(volume); - } - } +void _3DScene::enable_picking(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_picking(canvas, enable); +} - // populates volumes - for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) - { - TypesList::iterator type = std::find(types.begin(), types.end(), Type(polyline.type)); - if (type != types.end()) - { - type->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z)); - type->volume->offsets.push_back(type->volume->indexed_vertex_array.quad_indices.size()); - type->volume->offsets.push_back(type->volume->indexed_vertex_array.triangle_indices.size()); +void _3DScene::enable_moving(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_moving(canvas, enable); +} - polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *type->volume); - } - } +void _3DScene::enable_gizmos(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_gizmos(canvas, enable); +} - return true; +void _3DScene::enable_shader(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_shader(canvas, enable); } -bool _3DScene::_travel_paths_by_feedrate(const GCodePreviewData& preview_data, GLVolumeCollection& volumes) +void _3DScene::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable) { - // Helper structure for feedrate - struct Feedrate - { - float value; - GLVolume* volume; + s_canvas_mgr.enable_force_zoom_to_bed(canvas, enable); +} - explicit Feedrate(float value) - : value(value) - , volume(nullptr) - { - } +void _3DScene::allow_multisample(wxGLCanvas* canvas, bool allow) +{ + s_canvas_mgr.allow_multisample(canvas, allow); +} - bool operator == (const Feedrate& other) const - { - return value == other.value; - } - }; +void _3DScene::zoom_to_bed(wxGLCanvas* canvas) +{ + s_canvas_mgr.zoom_to_bed(canvas); +} - typedef std::vector<Feedrate> FeedratesList; +void _3DScene::zoom_to_volumes(wxGLCanvas* canvas) +{ + s_canvas_mgr.zoom_to_volumes(canvas); +} - // colors travels by feedrate +void _3DScene::select_view(wxGLCanvas* canvas, const std::string& direction) +{ + s_canvas_mgr.select_view(canvas, direction); +} - // detects feedrates - FeedratesList feedrates; - for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) - { - if (std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate)) == feedrates.end()) - feedrates.emplace_back(polyline.feedrate); - } +void _3DScene::set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other) +{ + s_canvas_mgr.set_viewport_from_scene(canvas, other); +} - // nothing to render, return - if (feedrates.empty()) - return true; +void _3DScene::update_volumes_colors_by_extruder(wxGLCanvas* canvas) +{ + s_canvas_mgr.update_volumes_colors_by_extruder(canvas); +} - // creates a new volume for each feedrate - for (Feedrate& feedrate : feedrates) - { - GLVolume* volume = new GLVolume(preview_data.get_feedrate_color(feedrate.value).rgba); - if (volume == nullptr) - return false; - else - { - feedrate.volume = volume; - volumes.volumes.emplace_back(volume); - } - } +void _3DScene::render(wxGLCanvas* canvas) +{ + s_canvas_mgr.render(canvas); +} - // populates volumes - for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) - { - FeedratesList::iterator feedrate = std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate)); - if (feedrate != feedrates.end()) - { - feedrate->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z)); - feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.quad_indices.size()); - feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.triangle_indices.size()); +std::vector<double> _3DScene::get_current_print_zs(wxGLCanvas* canvas, bool active_only) +{ + return s_canvas_mgr.get_current_print_zs(canvas, active_only); +} - polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *feedrate->volume); - } - } +void _3DScene::set_toolpaths_range(wxGLCanvas* canvas, double low, double high) +{ + s_canvas_mgr.set_toolpaths_range(canvas, low, high); +} - return true; +void _3DScene::register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_viewport_changed_callback(canvas, callback); } -bool _3DScene::_travel_paths_by_tool(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors) +void _3DScene::register_on_double_click_callback(wxGLCanvas* canvas, void* callback) { - // Helper structure for tool - struct Tool - { - unsigned int value; - GLVolume* volume; + s_canvas_mgr.register_on_double_click_callback(canvas, callback); +} - explicit Tool(unsigned int value) - : value(value) - , volume(nullptr) - { - } +void _3DScene::register_on_right_click_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_right_click_callback(canvas, callback); +} - bool operator == (const Tool& other) const - { - return value == other.value; - } - }; +void _3DScene::register_on_select_object_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_select_object_callback(canvas, callback); +} - typedef std::vector<Tool> ToolsList; +void _3DScene::register_on_model_update_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_model_update_callback(canvas, callback); +} - // colors travels by tool +void _3DScene::register_on_remove_object_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_remove_object_callback(canvas, callback); +} - // detects tools - ToolsList tools; - for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) - { - if (std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id)) == tools.end()) - tools.emplace_back(polyline.extruder_id); - } +void _3DScene::register_on_arrange_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_arrange_callback(canvas, callback); +} - // nothing to render, return - if (tools.empty()) - return true; +void _3DScene::register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_rotate_object_left_callback(canvas, callback); +} - // creates a new volume for each tool - for (Tool& tool : tools) - { - GLVolume* volume = new GLVolume(tool_colors.data() + tool.value * 4); - if (volume == nullptr) - return false; - else - { - tool.volume = volume; - volumes.volumes.emplace_back(volume); - } - } +void _3DScene::register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_rotate_object_right_callback(canvas, callback); +} - // populates volumes - for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) - { - ToolsList::iterator tool = std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id)); - if (tool != tools.end()) - { - tool->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z)); - tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.quad_indices.size()); - tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.triangle_indices.size()); +void _3DScene::register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_scale_object_uniformly_callback(canvas, callback); +} - polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *tool->volume); - } - } +void _3DScene::register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_increase_objects_callback(canvas, callback); +} - return true; +void _3DScene::register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_decrease_objects_callback(canvas, callback); } -void _3DScene::_load_gcode_retractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs) +void _3DScene::register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback) { - s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Retraction, 0, (unsigned int)volumes.volumes.size()); + s_canvas_mgr.register_on_instance_moved_callback(canvas, callback); +} - // nothing to render, return - if (preview_data.retraction.positions.empty()) - return; +void _3DScene::register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_wipe_tower_moved_callback(canvas, callback); +} - GLVolume* volume = new GLVolume(preview_data.retraction.color.rgba); - if (volume != nullptr) - { - volumes.volumes.emplace_back(volume); +void _3DScene::register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_enable_action_buttons_callback(canvas, callback); +} - GCodePreviewData::Retraction::PositionsList copy(preview_data.retraction.positions); - std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; }); +void _3DScene::register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_gizmo_scale_uniformly_callback(canvas, callback); +} - for (const GCodePreviewData::Retraction::Position& position : copy) - { - volume->print_zs.push_back(unscale(position.position.z)); - volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size()); - volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size()); +static inline int hex_digit_to_int(const char c) +{ + return + (c >= '0' && c <= '9') ? int(c - '0') : + (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : + (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; +} - point3_to_verts(position.position, position.width, position.height, *volume); +static inline std::vector<float> parse_colors(const std::vector<std::string> &scolors) +{ + std::vector<float> output(scolors.size() * 4, 1.f); + for (size_t i = 0; i < scolors.size(); ++ i) { + const std::string &scolor = scolors[i]; + const char *c = scolor.data() + 1; + if (scolor.size() == 7 && scolor.front() == '#') { + for (size_t j = 0; j < 3; ++j) { + int digit1 = hex_digit_to_int(*c ++); + int digit2 = hex_digit_to_int(*c ++); + if (digit1 == -1 || digit2 == -1) + break; + output[i * 4 + j] = float(digit1 * 16 + digit2) / 255.f; + } } - - // finalize volumes and sends geometry to gpu - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(use_VBOs); } + return output; } -void _3DScene::_load_gcode_unretractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs) +std::vector<int> _3DScene::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs) { - s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Unretraction, 0, (unsigned int)volumes.volumes.size()); - - // nothing to render, return - if (preview_data.unretraction.positions.empty()) - return; + return s_canvas_mgr.load_object(canvas, model_object, obj_idx, instance_idxs); +} - GLVolume* volume = new GLVolume(preview_data.unretraction.color.rgba); - if (volume != nullptr) - { - volumes.volumes.emplace_back(volume); +std::vector<int> _3DScene::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx) +{ + return s_canvas_mgr.load_object(canvas, model, obj_idx); +} - GCodePreviewData::Retraction::PositionsList copy(preview_data.unretraction.positions); - std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; }); +void _3DScene::reload_scene(wxGLCanvas* canvas, bool force) +{ + s_canvas_mgr.reload_scene(canvas, force); +} - for (const GCodePreviewData::Retraction::Position& position : copy) - { - volume->print_zs.push_back(unscale(position.position.z)); - volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size()); - volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size()); +void _3DScene::load_print_toolpaths(wxGLCanvas* canvas) +{ + s_canvas_mgr.load_print_toolpaths(canvas); +} - point3_to_verts(position.position, position.width, position.height, *volume); - } +void _3DScene::load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& str_tool_colors) +{ + s_canvas_mgr.load_print_object_toolpaths(canvas, print_object, str_tool_colors); +} - // finalize volumes and sends geometry to gpu - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(use_VBOs); - } +void _3DScene::load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors) +{ + s_canvas_mgr.load_wipe_tower_toolpaths(canvas, str_tool_colors); } -void _3DScene::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data, GLVolumeCollection& volumes) +void _3DScene::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors) { - unsigned int size = (unsigned int)s_gcode_preview_volume_index.first_volumes.size(); - for (unsigned int i = 0; i < size; ++i) - { - std::vector<GLVolume*>::iterator begin = volumes.volumes.begin() + s_gcode_preview_volume_index.first_volumes[i].id; - std::vector<GLVolume*>::iterator end = (i + 1 < size) ? volumes.volumes.begin() + s_gcode_preview_volume_index.first_volumes[i + 1].id : volumes.volumes.end(); + s_canvas_mgr.load_gcode_preview(canvas, preview_data, str_tool_colors); +} - for (std::vector<GLVolume*>::iterator it = begin; it != end; ++it) - { - GLVolume* volume = *it; - volume->outside_printer_detection_enabled = false; +void _3DScene::generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors) +{ + s_legend_texture.generate(preview_data, tool_colors); +} - switch (s_gcode_preview_volume_index.first_volumes[i].type) - { - case GCodePreviewVolumeIndex::Extrusion: - { - if ((ExtrusionRole)s_gcode_preview_volume_index.first_volumes[i].flag == erCustom) - volume->zoom_to_volumes = false; - - volume->is_active = preview_data.extrusion.is_role_flag_set((ExtrusionRole)s_gcode_preview_volume_index.first_volumes[i].flag); - break; - } - case GCodePreviewVolumeIndex::Travel: - { - volume->is_active = preview_data.travel.is_visible; - volume->zoom_to_volumes = false; - break; - } - case GCodePreviewVolumeIndex::Retraction: - { - volume->is_active = preview_data.retraction.is_visible; - volume->zoom_to_volumes = false; - break; - } - case GCodePreviewVolumeIndex::Unretraction: - { - volume->is_active = preview_data.unretraction.is_visible; - volume->zoom_to_volumes = false; - break; - } - case GCodePreviewVolumeIndex::Shell: - { - volume->is_active = preview_data.shell.is_visible; - volume->color[3] = 0.25f; - volume->zoom_to_volumes = false; - break; - } - default: - { - volume->is_active = false; - volume->zoom_to_volumes = false; - break; - } - } - } - } +unsigned int _3DScene::get_legend_texture_width() +{ + return s_legend_texture.get_texture_width(); } -void _3DScene::_generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors) +unsigned int _3DScene::get_legend_texture_height() { - s_legend_texture.generate(preview_data, tool_colors); + return s_legend_texture.get_texture_height(); } -void _3DScene::_load_shells(const Print& print, GLVolumeCollection& volumes, bool use_VBOs) +void _3DScene::reset_legend_texture() { - size_t initial_volumes_count = volumes.volumes.size(); - s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Shell, 0, (unsigned int)initial_volumes_count); + s_legend_texture.reset_texture(); +} - if (print.objects.empty()) - // nothing to render, return - return; +unsigned int _3DScene::finalize_legend_texture() +{ + return s_legend_texture.finalize(); +} - // adds objects' volumes - unsigned int object_id = 0; - for (PrintObject* obj : print.objects) - { - ModelObject* model_obj = obj->model_object(); +unsigned int _3DScene::get_warning_texture_width() +{ + return s_warning_texture.get_texture_width(); +} - std::vector<int> instance_ids(model_obj->instances.size()); - for (int i = 0; i < model_obj->instances.size(); ++i) - { - instance_ids[i] = i; - } +unsigned int _3DScene::get_warning_texture_height() +{ + return s_warning_texture.get_texture_height(); +} - for (ModelInstance* instance : model_obj->instances) - { - volumes.load_object(model_obj, object_id, instance_ids, "object", "object", "object", use_VBOs); - } +void _3DScene::generate_warning_texture(const std::string& msg) +{ + s_warning_texture.generate(msg); +} - ++object_id; - } +void _3DScene::reset_warning_texture() +{ + s_warning_texture.reset_texture(); +} - // adds wipe tower's volume - coordf_t max_z = print.objects[0]->model_object()->get_model()->bounding_box().max.z; - const PrintConfig& config = print.config; - unsigned int extruders_count = config.nozzle_diameter.size(); - if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { - const float width_per_extruder = 15.f; // a simple workaround after wipe_tower_per_color_wipe got obsolete - volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, width_per_extruder * (extruders_count - 1), max_z, config.wipe_tower_rotation_angle, use_VBOs); - } +unsigned int _3DScene::finalize_warning_texture() +{ + return s_warning_texture.finalize(); } } // namespace Slic3r diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index a417f5f9d..9016f984d 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -6,8 +6,10 @@ #include "../../libslic3r/Line.hpp" #include "../../libslic3r/TriangleMesh.hpp" #include "../../libslic3r/Utils.hpp" +#include "../../slic3r/GUI/GLCanvas3DManager.hpp" class wxBitmap; +class wxWindow; namespace Slic3r { @@ -17,6 +19,11 @@ class Model; class ModelObject; class GCodePreviewData; class DynamicPrintConfig; +class ExtrusionPath; +class ExtrusionMultiPath; +class ExtrusionLoop; +class ExtrusionEntity; +class ExtrusionEntityCollection; // A container for interleaved arrays of 3D vertices and normals, // possibly indexed by triangles and / or quads. @@ -305,9 +312,9 @@ public: // Boolean: Is mouse over this object? bool hover; // Wheter or not this volume has been generated from a modifier - bool is_modifier; + bool is_modifier; // Wheter or not this volume has been generated from the wipe tower - bool is_wipe_tower; + bool is_wipe_tower; // Interleaved triangles & normals with indexed triangles & quads. GLIndexedVertexArray indexed_vertex_array; @@ -437,35 +444,6 @@ private: class _3DScene { - struct GCodePreviewVolumeIndex - { - enum EType - { - Extrusion, - Travel, - Retraction, - Unretraction, - Shell, - Num_Geometry_Types - }; - - struct FirstVolume - { - EType type; - unsigned int flag; - // Index of the first volume in a GLVolumeCollection. - unsigned int id; - - FirstVolume(EType type, unsigned int flag, unsigned int id) : type(type), flag(flag), id(id) {} - }; - - std::vector<FirstVolume> first_volumes; - - void reset() { first_volumes.clear(); } - }; - - static GCodePreviewVolumeIndex s_gcode_preview_volume_index; - class TextureBase { protected: @@ -525,12 +503,106 @@ class _3DScene static LegendTexture s_legend_texture; static WarningTexture s_warning_texture; + static GUI::GLCanvas3DManager s_canvas_mgr; public: - static void _glew_init(); + static void init_gl(); + static std::string get_gl_info(bool format_as_html, bool extensions); + static bool use_VBOs(); + + static bool add_canvas(wxGLCanvas* canvas); + static bool remove_canvas(wxGLCanvas* canvas); + static void remove_all_canvases(); + + static bool init(wxGLCanvas* canvas); + + static void set_as_dirty(wxGLCanvas* canvas); + + static unsigned int get_volumes_count(wxGLCanvas* canvas); + static void reset_volumes(wxGLCanvas* canvas); + static void deselect_volumes(wxGLCanvas* canvas); + static void select_volume(wxGLCanvas* canvas, unsigned int id); + static void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections); + static bool check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config); + static bool move_volume_up(wxGLCanvas* canvas, unsigned int id); + static bool move_volume_down(wxGLCanvas* canvas, unsigned int id); + + static void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections); + + static void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config); + static void set_print(wxGLCanvas* canvas, Print* print); + static void set_model(wxGLCanvas* canvas, Model* model); + + static void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape); + static void set_auto_bed_shape(wxGLCanvas* canvas); + + static BoundingBoxf3 get_volumes_bounding_box(wxGLCanvas* canvas); + + static void set_axes_length(wxGLCanvas* canvas, float length); - static void load_gcode_preview(const Print* print, const GCodePreviewData* preview_data, GLVolumeCollection* volumes, const std::vector<std::string>& str_tool_colors, bool use_VBOs); + static void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons); + static void set_color_by(wxGLCanvas* canvas, const std::string& value); + static void set_select_by(wxGLCanvas* canvas, const std::string& value); + static void set_drag_by(wxGLCanvas* canvas, const std::string& value); + + static bool is_layers_editing_enabled(wxGLCanvas* canvas); + static bool is_layers_editing_allowed(wxGLCanvas* canvas); + static bool is_shader_enabled(wxGLCanvas* canvas); + + static bool is_reload_delayed(wxGLCanvas* canvas); + + static void enable_layers_editing(wxGLCanvas* canvas, bool enable); + static void enable_warning_texture(wxGLCanvas* canvas, bool enable); + static void enable_legend_texture(wxGLCanvas* canvas, bool enable); + static void enable_picking(wxGLCanvas* canvas, bool enable); + static void enable_moving(wxGLCanvas* canvas, bool enable); + static void enable_gizmos(wxGLCanvas* canvas, bool enable); + static void enable_shader(wxGLCanvas* canvas, bool enable); + static void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable); + static void allow_multisample(wxGLCanvas* canvas, bool allow); + + static void zoom_to_bed(wxGLCanvas* canvas); + static void zoom_to_volumes(wxGLCanvas* canvas); + static void select_view(wxGLCanvas* canvas, const std::string& direction); + static void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other); + + static void update_volumes_colors_by_extruder(wxGLCanvas* canvas); + + static void render(wxGLCanvas* canvas); + + static std::vector<double> get_current_print_zs(wxGLCanvas* canvas, bool active_only); + static void set_toolpaths_range(wxGLCanvas* canvas, double low, double high); + + static void register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback); + static void register_on_double_click_callback(wxGLCanvas* canvas, void* callback); + static void register_on_right_click_callback(wxGLCanvas* canvas, void* callback); + static void register_on_select_object_callback(wxGLCanvas* canvas, void* callback); + static void register_on_model_update_callback(wxGLCanvas* canvas, void* callback); + static void register_on_remove_object_callback(wxGLCanvas* canvas, void* callback); + static void register_on_arrange_callback(wxGLCanvas* canvas, void* callback); + static void register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback); + static void register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback); + static void register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback); + static void register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback); + static void register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback); + static void register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback); + static void register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback); + static void register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback); + static void register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback); + + static std::vector<int> load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs); + static std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx); + + static void reload_scene(wxGLCanvas* canvas, bool force); + + static void load_print_toolpaths(wxGLCanvas* canvas); + static void load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& str_tool_colors); + static void load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors); + static void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors); + + // generates the legend texture in dependence of the current shown view type + static void generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors); static unsigned int get_legend_texture_width(); static unsigned int get_legend_texture_height(); @@ -545,42 +617,16 @@ public: static void reset_warning_texture(); static unsigned int finalize_warning_texture(); - static void _load_print_toolpaths( - const Print *print, - GLVolumeCollection *volumes, - const std::vector<std::string> &tool_colors, - bool use_VBOs); - - static void _load_print_object_toolpaths( - const PrintObject *print_object, - GLVolumeCollection *volumes, - const std::vector<std::string> &tool_colors, - bool use_VBOs); - - static void _load_wipe_tower_toolpaths( - const Print *print, - GLVolumeCollection *volumes, - const std::vector<std::string> &tool_colors_str, - bool use_VBOs); - -private: - // generates gcode extrusion paths geometry - static void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs); - // generates gcode travel paths geometry - static void _load_gcode_travel_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs); - static bool _travel_paths_by_type(const GCodePreviewData& preview_data, GLVolumeCollection& volumes); - static bool _travel_paths_by_feedrate(const GCodePreviewData& preview_data, GLVolumeCollection& volumes); - static bool _travel_paths_by_tool(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors); - // generates gcode retractions geometry - static void _load_gcode_retractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs); - // generates gcode unretractions geometry - static void _load_gcode_unretractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs); - // sets gcode geometry visibility according to user selection - static void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data, GLVolumeCollection& volumes); - // generates the legend texture in dependence of the current shown view type - static void _generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors); - // generates objects and wipe tower geometry - static void _load_shells(const Print& print, GLVolumeCollection& volumes, bool use_VBOs); + static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GLVolume& volume); + static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume); + static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume); + static void point3_to_verts(const Point3& point, double width, double height, GLVolume& volume); }; } diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index aed0c3534..2e315a70b 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -83,8 +83,11 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, cons const auto model_id = model.id; + bool default_variant = true; // Mark the first variant as default in the GUI for (const auto &variant : model.variants) { - const auto label = wxString::Format("%s %s %s", variant.name, _(L("mm")), _(L("nozzle"))); + const auto label = wxString::Format("%s %s %s %s", variant.name, _(L("mm")), _(L("nozzle")), + (default_variant ? _(L("(default)")) : wxString())); + default_variant = false; auto *cbox = new Checkbox(panel, label, model_id, variant.name); const size_t idx = cboxes.size(); cboxes.push_back(cbox); @@ -125,6 +128,14 @@ void PrinterPicker::select_all(bool select) } } +void PrinterPicker::select_one(size_t i, bool select) +{ + if (i < cboxes.size() && cboxes[i]->GetValue() != select) { + cboxes[i]->SetValue(select); + on_checkbox(cboxes[i], select); + } +} + void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked) { variants_checked += checked ? 1 : -1; @@ -227,6 +238,7 @@ PageWelcome::PageWelcome(ConfigWizard *parent) : AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors; printer_picker = new PrinterPicker(this, vendor_prusa->second, appconfig_vendors); + printer_picker->select_one(0, true); // Select the default (first) model/variant on the Prusa vendor printer_picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) { appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); this->on_variant_checked(); @@ -598,10 +610,10 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) static const std::unordered_map<std::string, std::pair<std::string, std::string>> legacy_preset_map {{ { "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") }, - { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2S", "0.4") }, - { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, - { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2S", "0.4") }, - { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, + { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") }, + { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, + { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") }, + { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, { "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") }, { "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, { "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") }, @@ -809,8 +821,8 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) : topsizer->AddSpacer(INDEX_MARGIN); topsizer->Add(p->hscroll, 1, wxEXPAND); - p->btn_prev = new wxButton(this, wxID_BACKWARD); - p->btn_next = new wxButton(this, wxID_FORWARD); + p->btn_prev = new wxButton(this, wxID_NONE, _(L("< &Back"))); + p->btn_next = new wxButton(this, wxID_NONE, _(L("&Next >"))); p->btn_finish = new wxButton(this, wxID_APPLY, _(L("&Finish"))); p->btn_cancel = new wxButton(this, wxID_CANCEL); p->btnsizer->AddStretchSpacer(); @@ -881,9 +893,9 @@ const wxString& ConfigWizard::name() { // A different naming convention is used for the Wizard on Windows vs. OSX & GTK. #if WIN32 - static const wxString config_wizard_name = _(L("Configuration Wizard")); + static const wxString config_wizard_name = L("Configuration Wizard"); #else - static const wxString config_wizard_name = _(L("Configuration Assistant")); + static const wxString config_wizard_name = L("Configuration Assistant"); #endif return config_wizard_name; } diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp index 72cb88655..04319a1b4 100644 --- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -56,6 +56,7 @@ struct PrinterPicker: wxPanel PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors); void select_all(bool select); + void select_one(size_t i, bool select); void on_checkbox(const Checkbox *cbox, bool checked); }; diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 43c9e7db9..85247b41b 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -12,10 +12,20 @@ namespace Slic3r { namespace GUI { wxString double_to_string(double const value) { - int precision = 10 * value - int(10 * value) == 0 ? 1 : 2; - return value - int(value) == 0 ? - wxString::Format(_T("%i"), int(value)) : - wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None); + if (value - int(value) == 0) + return wxString::Format(_T("%i"), int(value)); + else { + int precision = 4; + for (size_t p = 1; p < 4; p++) + { + double cur_val = pow(10, p)*value; + if (cur_val - int(cur_val) == 0) { + precision = p; + break; + } + } + return wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None); + } } void Field::PostInitialize(){ diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index 8ea9d2d6e..d74743055 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -4,12 +4,14 @@ #include <algorithm> #include <boost/format.hpp> #include <boost/filesystem/path.hpp> +#include <boost/filesystem/fstream.hpp> #include <boost/log/trivial.hpp> #include <wx/app.h> #include <wx/event.h> #include <wx/sizer.h> #include <wx/settings.h> +#include <wx/timer.h> #include <wx/panel.h> #include <wx/button.h> #include <wx/filepicker.h> @@ -36,7 +38,7 @@ namespace Slic3r { enum AvrdudeEvent { AE_MESSAGE, - AE_PRORGESS, + AE_PROGRESS, AE_EXIT, }; @@ -62,7 +64,6 @@ struct FirmwareDialog::priv std::vector<Utils::SerialPortInfo> ports; wxFilePickerCtrl *hex_picker; wxStaticText *txt_status; - wxStaticText *txt_progress; wxGauge *progressbar; wxCollapsiblePane *spoiler; wxTextCtrl *txt_stdout; @@ -72,6 +73,8 @@ struct FirmwareDialog::priv wxString btn_flash_label_ready; wxString btn_flash_label_flashing; + wxTimer timer_pulse; + // This is a shared pointer holding the background AvrDude task // also serves as a status indication (it is set _iff_ the background task is running, otherwise it is reset). AvrDude::Ptr avrdude; @@ -83,13 +86,16 @@ struct FirmwareDialog::priv q(q), btn_flash_label_ready(_(L("Flash!"))), btn_flash_label_flashing(_(L("Cancel"))), + timer_pulse(q), avrdude_config((fs::path(::Slic3r::resources_dir()) / "avrdude" / "avrdude.conf").string()), progress_tasks_done(0), cancelled(false) {} void find_serial_ports(); - void flashing_status(bool flashing, AvrDudeComplete complete = AC_NONE); + void flashing_start(bool flashing_l10n); + void flashing_done(AvrDudeComplete complete); + size_t hex_lang_offset(const wxString &path); void perform_upload(); void cancel(); void on_avrdude(const wxCommandEvent &evt); @@ -116,42 +122,76 @@ void FirmwareDialog::priv::find_serial_ports() } } -void FirmwareDialog::priv::flashing_status(bool value, AvrDudeComplete complete) +void FirmwareDialog::priv::flashing_start(bool flashing_l10n) +{ + txt_stdout->Clear(); + txt_status->SetLabel(_(L("Flashing in progress. Please do not disconnect the printer!"))); + txt_status->SetForegroundColour(GUI::get_label_clr_modified()); + port_picker->Disable(); + btn_rescan->Disable(); + hex_picker->Disable(); + btn_close->Disable(); + btn_flash->SetLabel(btn_flash_label_flashing); + progressbar->SetRange(flashing_l10n ? 500 : 200); // See progress callback below + progressbar->SetValue(0); + progress_tasks_done = 0; + cancelled = false; + timer_pulse.Start(50); +} + +void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete) +{ + auto text_color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + port_picker->Enable(); + btn_rescan->Enable(); + hex_picker->Enable(); + btn_close->Enable(); + btn_flash->SetLabel(btn_flash_label_ready); + txt_status->SetForegroundColour(text_color); + timer_pulse.Stop(); + progressbar->SetValue(progressbar->GetRange()); + + switch (complete) { + case AC_SUCCESS: txt_status->SetLabel(_(L("Flashing succeeded!"))); break; + case AC_FAILURE: txt_status->SetLabel(_(L("Flashing failed. Please see the avrdude log below."))); break; + case AC_CANCEL: txt_status->SetLabel(_(L("Flashing cancelled."))); break; + } +} + +size_t FirmwareDialog::priv::hex_lang_offset(const wxString &path) { - if (value) { - txt_stdout->Clear(); - txt_status->SetLabel(_(L("Flashing in progress. Please do not disconnect the printer!"))); - txt_status->SetForegroundColour(GUI::get_label_clr_modified()); - port_picker->Disable(); - btn_rescan->Disable(); - hex_picker->Disable(); - btn_close->Disable(); - btn_flash->SetLabel(btn_flash_label_flashing); - progressbar->SetRange(200); // See progress callback below - progressbar->SetValue(0); - progress_tasks_done = 0; - cancelled = false; - } else { - auto text_color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); - port_picker->Enable(); - btn_rescan->Enable(); - hex_picker->Enable(); - btn_close->Enable(); - btn_flash->SetLabel(btn_flash_label_ready); - txt_status->SetForegroundColour(text_color); - progressbar->SetValue(200); - - switch (complete) { - case AC_SUCCESS: txt_status->SetLabel(_(L("Flashing succeeded!"))); break; - case AC_FAILURE: txt_status->SetLabel(_(L("Flashing failed. Please see the avrdude log below."))); break; - case AC_CANCEL: txt_status->SetLabel(_(L("Flashing cancelled."))); break; + fs::ifstream file(fs::path(path.wx_str())); + if (! file.good()) { + return 0; + } + + static const char *hex_terminator = ":00000001FF\r"; + size_t res = 0; + std::string line; + while (getline(file, line, '\n').good()) { + // Account for LF vs CRLF + if (!line.empty() && line.back() != '\r') { + line.push_back('\r'); + } + + if (line == hex_terminator) { + if (res == 0) { + // This is the first terminator seen, save the position + res = file.tellg(); + } else { + // We've found another terminator, return the offset just after the first one + // which is the start of the second 'section'. + return res; + } } } + + return 0; } void FirmwareDialog::priv::perform_upload() { - auto filename = hex_picker->GetPath(); + auto filename = hex_picker->GetPath(); std::string port = port_picker->GetValue().ToStdString(); int selection = port_picker->GetSelection(); if (selection != -1) { @@ -161,16 +201,32 @@ void FirmwareDialog::priv::perform_upload() } if (filename.IsEmpty() || port.empty()) { return; } - flashing_status(true); + const bool extra_verbose = false; // For debugging + const auto lang_offset = hex_lang_offset(filename); + const auto filename_utf8 = filename.utf8_str(); + flashing_start(lang_offset > 0); + + // It is ok here to use the q-pointer to the FirmwareDialog + // because the dialog ensures it doesn't exit before the background thread is done. + auto q = this->q; + + // Init the avrdude object + AvrDude avrdude(avrdude_config); + + // Build argument list(s) std::vector<std::string> args {{ - "-v", + extra_verbose ? "-vvvvv" : "-v", "-p", "atmega2560", + // Using the "Wiring" mode to program Rambo or Einsy, using the STK500v2 protocol (not the STK500). + // The Prusa's avrdude is patched to never send semicolons inside the data packets, as the USB to serial chip + // is flashed with a buggy firmware. "-c", "wiring", "-P", port, - "-b", "115200", // XXX: is this ok to hardcode? + "-b", "115200", // TODO: Allow other rates? Ditto below. "-D", - "-U", (boost::format("flash:w:%1%:i") % filename.ToStdString()).str() + // XXX: Safe mode? + "-U", (boost::format("flash:w:0:%1%:i") % filename_utf8.data()).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " @@ -178,26 +234,51 @@ void FirmwareDialog::priv::perform_upload() return a + ' ' + b; }); - // It is ok here to use the q-pointer to the FirmwareDialog - // because the dialog ensures it doesn't exit before the background thread is done. - auto q = this->q; + avrdude.push_args(std::move(args)); + + if (lang_offset > 0) { + // The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy) + // This is done via another avrdude invocation, here we build arg list for that: + std::vector<std::string> args_l10n {{ + extra_verbose ? "-vvvvv" : "-v", + "-p", "atmega2560", + // Using the "Arduino" mode to program Einsy's external flash with languages, using the STK500 protocol (not the STK500v2). + // The Prusa's avrdude is patched again to never send semicolons inside the data packets. + "-c", "arduino", + "-P", port, + "-b", "115200", + "-D", + "-u", // disable safe mode + "-U", (boost::format("flash:w:%1%:%2%:i") % lang_offset % filename_utf8.data()).str(), + }}; + + BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: " + << std::accumulate(std::next(args_l10n.begin()), args_l10n.end(), args_l10n[0], [](std::string a, const std::string &b) { + return a + ' ' + b; + }); + + avrdude.push_args(std::move(args_l10n)); + } + + this->avrdude = avrdude + .on_message(std::move([q, extra_verbose](const char *msg, unsigned /* size */) { + if (extra_verbose) { + BOOST_LOG_TRIVIAL(debug) << "avrdude: " << msg; + } - avrdude = AvrDude() - .sys_config(avrdude_config) - .args(args) - .on_message(std::move([q](const char *msg, unsigned /* size */) { auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId()); + auto wxmsg = wxString::FromUTF8(msg); evt->SetExtraLong(AE_MESSAGE); - evt->SetString(msg); + evt->SetString(std::move(wxmsg)); wxQueueEvent(q, evt); })) .on_progress(std::move([q](const char * /* task */, unsigned progress) { auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId()); - evt->SetExtraLong(AE_PRORGESS); + evt->SetExtraLong(AE_PROGRESS); evt->SetInt(progress); wxQueueEvent(q, evt); })) - .on_complete(std::move([q](int status) { + .on_complete(std::move([q](int status, size_t /* args_id */) { auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId()); evt->SetExtraLong(AE_EXIT); evt->SetInt(status); @@ -224,19 +305,19 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) txt_stdout->AppendText(evt.GetString()); break; - case AE_PRORGESS: + case AE_PROGRESS: // We try to track overall progress here. - // When uploading the firmware, avrdude first reads a littlebit of status data, - // then performs write, then reading (verification). - // We Pulse() during the first read and combine progress of the latter two tasks. + // Avrdude performs 3 tasks per one memory operation ("-U" arg), + // first of which is reading of status data (very short). + // We use the timer_pulse during the very first task to indicate intialization + // and then display overall progress during the latter tasks. - if (progress_tasks_done == 0) { - progressbar->Pulse(); - } else { + if (progress_tasks_done > 0) { progressbar->SetValue(progress_tasks_done - 100 + evt.GetInt()); } if (evt.GetInt() == 100) { + timer_pulse.Stop(); progress_tasks_done += 100; } @@ -246,7 +327,7 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) BOOST_LOG_TRIVIAL(info) << "avrdude exit code: " << evt.GetInt(); complete_kind = cancelled ? AC_CANCEL : (evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE); - flashing_status(false, complete_kind); + flashing_done(complete_kind); // Make sure the background thread is collected and the AvrDude object reset if (avrdude) { avrdude->join(); } @@ -374,6 +455,8 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : } }); + Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->p->progressbar->Pulse(); }); + Bind(EVT_AVRDUDE, [this](wxCommandEvent &evt) { this->p->on_avrdude(evt); }); Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt) { diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp new file mode 100644 index 000000000..adea27fa4 --- /dev/null +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -0,0 +1,4346 @@ +#include "GLCanvas3D.hpp" + +#include "../../slic3r/GUI/3DScene.hpp" +#include "../../slic3r/GUI/GLShader.hpp" +#include "../../slic3r/GUI/GUI.hpp" +#include "../../slic3r/GUI/PresetBundle.hpp" +#include "../../slic3r/GUI/GLGizmo.hpp" +#include "../../libslic3r/ClipperUtils.hpp" +#include "../../libslic3r/PrintConfig.hpp" +#include "../../libslic3r/Print.hpp" +#include "../../libslic3r/GCode/PreviewData.hpp" + +#include <GL/glew.h> + +#include <wx/glcanvas.h> +#include <wx/timer.h> + +#include <tbb/parallel_for.h> +#include <tbb/spin_mutex.h> + +#include <boost/log/trivial.hpp> +#include <boost/algorithm/string/predicate.hpp> + +#include <iostream> +#include <float.h> +#include <algorithm> + +static const float TRACKBALLSIZE = 0.8f; +static const float GIMBALL_LOCK_THETA_MAX = 180.0f; +static const float GROUND_Z = -0.02f; + +// phi / theta angles to orient the camera. +static const float VIEW_DEFAULT[2] = { 45.0f, 45.0f }; +static const float VIEW_LEFT[2] = { 90.0f, 90.0f }; +static const float VIEW_RIGHT[2] = { -90.0f, 90.0f }; +static const float VIEW_TOP[2] = { 0.0f, 0.0f }; +static const float VIEW_BOTTOM[2] = { 0.0f, 180.0f }; +static const float VIEW_FRONT[2] = { 0.0f, 90.0f }; +static const float VIEW_REAR[2] = { 180.0f, 90.0f }; + +static const float VARIABLE_LAYER_THICKNESS_BAR_WIDTH = 70.0f; +static const float VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT = 22.0f; + +namespace Slic3r { +namespace GUI { + +bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords) +{ + m_vertices.clear(); + m_tex_coords.clear(); + + unsigned int v_size = 9 * (unsigned int)triangles.size(); + unsigned int t_size = 6 * (unsigned int)triangles.size(); + if (v_size == 0) + return false; + + m_vertices = std::vector<float>(v_size, 0.0f); + if (generate_tex_coords) + m_tex_coords = std::vector<float>(t_size, 0.0f); + + float min_x = (float)unscale(triangles[0].points[0].x); + float min_y = (float)unscale(triangles[0].points[0].y); + float max_x = min_x; + float max_y = min_y; + + unsigned int v_coord = 0; + unsigned int t_coord = 0; + for (const Polygon& t : triangles) + { + for (unsigned int v = 0; v < 3; ++v) + { + const Point& p = t.points[v]; + float x = (float)unscale(p.x); + float y = (float)unscale(p.y); + + m_vertices[v_coord++] = x; + m_vertices[v_coord++] = y; + m_vertices[v_coord++] = z; + + if (generate_tex_coords) + { + m_tex_coords[t_coord++] = x; + m_tex_coords[t_coord++] = y; + + min_x = std::min(min_x, x); + max_x = std::max(max_x, x); + min_y = std::min(min_y, y); + max_y = std::max(max_y, y); + } + } + } + + if (generate_tex_coords) + { + float size_x = max_x - min_x; + float size_y = max_y - min_y; + + if ((size_x != 0.0f) && (size_y != 0.0f)) + { + float inv_size_x = 1.0f / size_x; + float inv_size_y = -1.0f / size_y; + for (unsigned int i = 0; i < m_tex_coords.size(); i += 2) + { + m_tex_coords[i] *= inv_size_x; + m_tex_coords[i + 1] *= inv_size_y; + } + } + } + + return true; +} + +bool GeometryBuffer::set_from_lines(const Lines& lines, float z) +{ + m_vertices.clear(); + m_tex_coords.clear(); + + unsigned int size = 6 * (unsigned int)lines.size(); + if (size == 0) + return false; + + m_vertices = std::vector<float>(size, 0.0f); + + unsigned int coord = 0; + for (const Line& l : lines) + { + m_vertices[coord++] = (float)unscale(l.a.x); + m_vertices[coord++] = (float)unscale(l.a.y); + m_vertices[coord++] = z; + m_vertices[coord++] = (float)unscale(l.b.x); + m_vertices[coord++] = (float)unscale(l.b.y); + m_vertices[coord++] = z; + } + + return true; +} + +const float* GeometryBuffer::get_vertices() const +{ + return m_vertices.data(); +} + +const float* GeometryBuffer::get_tex_coords() const +{ + return m_tex_coords.data(); +} + +unsigned int GeometryBuffer::get_vertices_count() const +{ + return (unsigned int)m_vertices.size() / 3; +} + +Size::Size() + : m_width(0) + , m_height(0) +{ +} + +Size::Size(int width, int height) + : m_width(width) + , m_height(height) +{ +} + +int Size::get_width() const +{ + return m_width; +} + +void Size::set_width(int width) +{ + m_width = width; +} + +int Size::get_height() const +{ + return m_height; +} + +void Size::set_height(int height) +{ + m_height = height; +} + +Rect::Rect() + : m_left(0.0f) + , m_top(0.0f) + , m_right(0.0f) + , m_bottom(0.0f) +{ +} + +Rect::Rect(float left, float top, float right, float bottom) + : m_left(left) + , m_top(top) + , m_right(right) + , m_bottom(bottom) +{ +} + +float Rect::get_left() const +{ + return m_left; +} + +void Rect::set_left(float left) +{ + m_left = left; +} + +float Rect::get_top() const +{ + return m_top; +} + +void Rect::set_top(float top) +{ + m_top = top; +} + +float Rect::get_right() const +{ + return m_right; +} + +void Rect::set_right(float right) +{ + m_right = right; +} + +float Rect::get_bottom() const +{ + return m_bottom; +} + +void Rect::set_bottom(float bottom) +{ + m_bottom = bottom; +} + +GLCanvas3D::Camera::Camera() + : type(Ortho) + , zoom(1.0f) + , phi(45.0f) +// , distance(0.0f) + , target(0.0, 0.0, 0.0) + , m_theta(45.0f) +{ +} + +std::string GLCanvas3D::Camera::get_type_as_string() const +{ + switch (type) + { + default: + case Unknown: + return "unknown"; +// case Perspective: +// return "perspective"; + case Ortho: + return "ortho"; + }; +} + +float GLCanvas3D::Camera::get_theta() const +{ + return m_theta; +} + +void GLCanvas3D::Camera::set_theta(float theta) +{ + m_theta = clamp(0.0f, GIMBALL_LOCK_THETA_MAX, theta); +} + +GLCanvas3D::Bed::Bed() + : m_type(Custom) +{ +} + +bool GLCanvas3D::Bed::is_prusa() const +{ + return (m_type == MK2) || (m_type == MK3); +} + +bool GLCanvas3D::Bed::is_custom() const +{ + return m_type == Custom; +} + +const Pointfs& GLCanvas3D::Bed::get_shape() const +{ + return m_shape; +} + +void GLCanvas3D::Bed::set_shape(const Pointfs& shape) +{ + m_shape = shape; + m_type = _detect_type(); + + _calc_bounding_box(); + + ExPolygon poly; + for (const Pointf& p : m_shape) + { + poly.contour.append(Point(scale_(p.x), scale_(p.y))); + } + + _calc_triangles(poly); + + const BoundingBox& bed_bbox = poly.contour.bounding_box(); + _calc_gridlines(poly, bed_bbox); + + m_polygon = offset_ex(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0].contour; +} + +const BoundingBoxf3& GLCanvas3D::Bed::get_bounding_box() const +{ + return m_bounding_box; +} + +bool GLCanvas3D::Bed::contains(const Point& point) const +{ + return m_polygon.contains(point); +} + +Point GLCanvas3D::Bed::point_projection(const Point& point) const +{ + return m_polygon.point_projection(point); +} + +void GLCanvas3D::Bed::render(float theta) const +{ + switch (m_type) + { + case MK2: + { + _render_mk2(theta); + break; + } + case MK3: + { + _render_mk3(theta); + break; + } + default: + case Custom: + { + _render_custom(); + break; + } + } +} + +void GLCanvas3D::Bed::_calc_bounding_box() +{ + m_bounding_box = BoundingBoxf3(); + for (const Pointf& p : m_shape) + { + m_bounding_box.merge(Pointf3(p.x, p.y, 0.0)); + } +} + +void GLCanvas3D::Bed::_calc_triangles(const ExPolygon& poly) +{ + Polygons triangles; + poly.triangulate(&triangles); + + if (!m_triangles.set_from_triangles(triangles, GROUND_Z, m_type != Custom)) + printf("Unable to create bed triangles\n"); +} + +void GLCanvas3D::Bed::_calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox) +{ + Polylines axes_lines; + for (coord_t x = bed_bbox.min.x; x <= bed_bbox.max.x; x += scale_(10.0)) + { + Polyline line; + line.append(Point(x, bed_bbox.min.y)); + line.append(Point(x, bed_bbox.max.y)); + axes_lines.push_back(line); + } + for (coord_t y = bed_bbox.min.y; y <= bed_bbox.max.y; y += scale_(10.0)) + { + Polyline line; + line.append(Point(bed_bbox.min.x, y)); + line.append(Point(bed_bbox.max.x, y)); + axes_lines.push_back(line); + } + + // clip with a slightly grown expolygon because our lines lay on the contours and may get erroneously clipped + Lines gridlines = to_lines(intersection_pl(axes_lines, offset(poly, SCALED_EPSILON))); + + // append bed contours + Lines contour_lines = to_lines(poly); + std::copy(contour_lines.begin(), contour_lines.end(), std::back_inserter(gridlines)); + + if (!m_gridlines.set_from_lines(gridlines, GROUND_Z)) + printf("Unable to create bed grid lines\n"); +} + +GLCanvas3D::Bed::EType GLCanvas3D::Bed::_detect_type() const +{ + EType type = Custom; + + const PresetBundle* bundle = get_preset_bundle(); + if (bundle != nullptr) + { + const Preset* curr = &bundle->printers.get_selected_preset(); + while (curr != nullptr) + { + if (curr->config.has("bed_shape") && _are_equal(m_shape, dynamic_cast<const ConfigOptionPoints*>(curr->config.option("bed_shape"))->values)) + { + if ((curr->vendor != nullptr) && (curr->vendor->name == "Prusa Research")) + { + if (boost::contains(curr->name, "MK2")) + { + type = MK2; + break; + } + else if (boost::contains(curr->name, "MK3")) + { + type = MK3; + break; + } + } + } + + curr = bundle->printers.get_preset_parent(*curr); + } + } + + return type; +} + +void GLCanvas3D::Bed::_render_mk2(float theta) const +{ + std::string filename = resources_dir() + "/icons/bed/mk2_top.png"; + if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) + { + if (!m_top_texture.load_from_file(filename, true)) + { + _render_custom(); + return; + } + } + + filename = resources_dir() + "/icons/bed/mk2_bottom.png"; + if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename)) + { + if (!m_bottom_texture.load_from_file(filename, true)) + { + _render_custom(); + return; + } + } + + _render_prusa(theta); +} + +void GLCanvas3D::Bed::_render_mk3(float theta) const +{ + std::string filename = resources_dir() + "/icons/bed/mk3_top.png"; + if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) + { + if (!m_top_texture.load_from_file(filename, true)) + { + _render_custom(); + return; + } + } + + filename = resources_dir() + "/icons/bed/mk3_bottom.png"; + if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename)) + { + if (!m_bottom_texture.load_from_file(filename, true)) + { + _render_custom(); + return; + } + } + + _render_prusa(theta); +} + +void GLCanvas3D::Bed::_render_prusa(float theta) const +{ + unsigned int triangles_vcount = m_triangles.get_vertices_count(); + if (triangles_vcount > 0) + { + ::glEnable(GL_DEPTH_TEST); + + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + ::glEnable(GL_TEXTURE_2D); + + ::glEnableClientState(GL_VERTEX_ARRAY); + ::glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + if (theta > 90.0f) + ::glFrontFace(GL_CW); + + ::glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + ::glBindTexture(GL_TEXTURE_2D, (theta <= 90.0f) ? (GLuint)m_top_texture.get_id() : (GLuint)m_bottom_texture.get_id()); + ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices()); + ::glTexCoordPointer(2, GL_FLOAT, 0, (GLvoid*)m_triangles.get_tex_coords()); + ::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount); + + if (theta > 90.0f) + ::glFrontFace(GL_CCW); + + ::glBindTexture(GL_TEXTURE_2D, 0); + ::glDisableClientState(GL_TEXTURE_COORD_ARRAY); + ::glDisableClientState(GL_VERTEX_ARRAY); + + ::glDisable(GL_TEXTURE_2D); + + ::glDisable(GL_BLEND); + } +} + +void GLCanvas3D::Bed::_render_custom() const +{ + m_top_texture.reset(); + m_bottom_texture.reset(); + + unsigned int triangles_vcount = m_triangles.get_vertices_count(); + if (triangles_vcount > 0) + { + ::glEnable(GL_LIGHTING); + ::glDisable(GL_DEPTH_TEST); + + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + ::glEnableClientState(GL_VERTEX_ARRAY); + + ::glColor4f(0.8f, 0.6f, 0.5f, 0.4f); + ::glNormal3d(0.0f, 0.0f, 1.0f); + ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices()); + ::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount); + + // draw grid + unsigned int gridlines_vcount = m_gridlines.get_vertices_count(); + + // we need depth test for grid, otherwise it would disappear when looking the object from below + ::glEnable(GL_DEPTH_TEST); + ::glLineWidth(3.0f); + ::glColor4f(0.2f, 0.2f, 0.2f, 0.4f); + ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_gridlines.get_vertices()); + ::glDrawArrays(GL_LINES, 0, (GLsizei)gridlines_vcount); + + ::glDisableClientState(GL_VERTEX_ARRAY); + + ::glDisable(GL_BLEND); + } +} + +bool GLCanvas3D::Bed::_are_equal(const Pointfs& bed_1, const Pointfs& bed_2) +{ + if (bed_1.size() != bed_2.size()) + return false; + + for (unsigned int i = 0; i < (unsigned int)bed_1.size(); ++i) + { + if (bed_1[i] != bed_2[i]) + return false; + } + + return true; +} + +GLCanvas3D::Axes::Axes() + : length(0.0f) +{ +} + +void GLCanvas3D::Axes::render(bool depth_test) const +{ + ::glDisable(GL_LIGHTING); + if (depth_test) + ::glEnable(GL_DEPTH_TEST); + else + ::glDisable(GL_DEPTH_TEST); + + ::glLineWidth(2.0f); + ::glBegin(GL_LINES); + // draw line for x axis + ::glColor3f(1.0f, 0.0f, 0.0f); + ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z); + ::glVertex3f((GLfloat)origin.x + length, (GLfloat)origin.y, (GLfloat)origin.z); + // draw line for y axis + ::glColor3f(0.0f, 1.0f, 0.0f); + ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z); + ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y + length, (GLfloat)origin.z); + ::glEnd(); + // draw line for Z axis + // (re-enable depth test so that axis is correctly shown when objects are behind it) + if (!depth_test) + ::glEnable(GL_DEPTH_TEST); + + ::glBegin(GL_LINES); + ::glColor3f(0.0f, 0.0f, 1.0f); + ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z); + ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z + length); + ::glEnd(); +} + +GLCanvas3D::CuttingPlane::CuttingPlane() + : m_z(-1.0f) +{ +} + +bool GLCanvas3D::CuttingPlane::set(float z, const ExPolygons& polygons) +{ + m_z = z; + + // grow slices in order to display them better + ExPolygons expolygons = offset_ex(polygons, scale_(0.1)); + Lines lines = to_lines(expolygons); + return m_lines.set_from_lines(lines, m_z); +} + +void GLCanvas3D::CuttingPlane::render(const BoundingBoxf3& bb) const +{ + ::glDisable(GL_LIGHTING); + _render_plane(bb); + _render_contour(); +} + +void GLCanvas3D::CuttingPlane::_render_plane(const BoundingBoxf3& bb) const +{ + if (m_z >= 0.0f) + { + ::glDisable(GL_CULL_FACE); + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + float margin = 20.0f; + float min_x = bb.min.x - margin; + float max_x = bb.max.x + margin; + float min_y = bb.min.y - margin; + float max_y = bb.max.y + margin; + + ::glBegin(GL_QUADS); + ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glVertex3f(min_x, min_y, m_z); + ::glVertex3f(max_x, min_y, m_z); + ::glVertex3f(max_x, max_y, m_z); + ::glVertex3f(min_x, max_y, m_z); + ::glEnd(); + + ::glEnable(GL_CULL_FACE); + ::glDisable(GL_BLEND); + } +} + +void GLCanvas3D::CuttingPlane::_render_contour() const +{ + ::glEnableClientState(GL_VERTEX_ARRAY); + + if (m_z >= 0.0f) + { + unsigned int lines_vcount = m_lines.get_vertices_count(); + + ::glLineWidth(2.0f); + ::glColor3f(0.0f, 0.0f, 0.0f); + ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_lines.get_vertices()); + ::glDrawArrays(GL_LINES, 0, (GLsizei)lines_vcount); + } + + ::glDisableClientState(GL_VERTEX_ARRAY); +} + +GLCanvas3D::Shader::Shader() + : m_shader(nullptr) +{ +} + +GLCanvas3D::Shader::~Shader() +{ + _reset(); +} + +bool GLCanvas3D::Shader::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename) +{ + if (is_initialized()) + return true; + + m_shader = new GLShader(); + if (m_shader != nullptr) + { + if (!m_shader->load_from_file(fragment_shader_filename.c_str(), vertex_shader_filename.c_str())) + { + std::cout << "Compilaton of shader failed:" << std::endl; + std::cout << m_shader->last_error << std::endl; + _reset(); + return false; + } + } + + return true; +} + +bool GLCanvas3D::Shader::is_initialized() const +{ + return (m_shader != nullptr); +} + +bool GLCanvas3D::Shader::start_using() const +{ + if (is_initialized()) + { + m_shader->enable(); + return true; + } + else + return false; +} + +void GLCanvas3D::Shader::stop_using() const +{ + if (m_shader != nullptr) + m_shader->disable(); +} + +void GLCanvas3D::Shader::set_uniform(const std::string& name, float value) const +{ + if (m_shader != nullptr) + m_shader->set_uniform(name.c_str(), value); +} + +const GLShader* GLCanvas3D::Shader::get_shader() const +{ + return m_shader; +} + +void GLCanvas3D::Shader::_reset() +{ + if (m_shader != nullptr) + { + m_shader->release(); + delete m_shader; + m_shader = nullptr; + } +} + +GLCanvas3D::LayersEditing::LayersEditing() + : m_use_legacy_opengl(false) + , m_enabled(false) + , m_z_texture_id(0) + , state(Unknown) + , band_width(2.0f) + , strength(0.005f) + , last_object_id(-1) + , last_z(0.0f) + , last_action(0) +{ +} + +GLCanvas3D::LayersEditing::~LayersEditing() +{ + if (m_z_texture_id != 0) + { + ::glDeleteTextures(1, &m_z_texture_id); + m_z_texture_id = 0; + } +} + +bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename) +{ + if (!m_shader.init(vertex_shader_filename, fragment_shader_filename)) + return false; + + ::glGenTextures(1, (GLuint*)&m_z_texture_id); + ::glBindTexture(GL_TEXTURE_2D, m_z_texture_id); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + ::glBindTexture(GL_TEXTURE_2D, 0); + + return true; +} + +bool GLCanvas3D::LayersEditing::is_allowed() const +{ + return !m_use_legacy_opengl && m_shader.is_initialized(); +} + +void GLCanvas3D::LayersEditing::set_use_legacy_opengl(bool use_legacy_opengl) +{ + m_use_legacy_opengl = use_legacy_opengl; +} + +bool GLCanvas3D::LayersEditing::is_enabled() const +{ + return m_enabled; +} + +void GLCanvas3D::LayersEditing::set_enabled(bool enabled) +{ + m_enabled = is_allowed() && enabled; +} + +unsigned int GLCanvas3D::LayersEditing::get_z_texture_id() const +{ + return m_z_texture_id; +} + +void GLCanvas3D::LayersEditing::render(const GLCanvas3D& canvas, const PrintObject& print_object, const GLVolume& volume) const +{ + if (!m_enabled) + return; + + const Rect& bar_rect = get_bar_rect_viewport(canvas); + const Rect& reset_rect = get_reset_rect_viewport(canvas); + + ::glDisable(GL_DEPTH_TEST); + + // The viewport and camera are set to complete view and glOrtho(-$x / 2, $x / 2, -$y / 2, $y / 2, -$depth, $depth), + // where x, y is the window size divided by $self->_zoom. + ::glPushMatrix(); + ::glLoadIdentity(); + + _render_tooltip_texture(canvas, bar_rect, reset_rect); + _render_reset_texture(reset_rect); + _render_active_object_annotations(canvas, volume, print_object, bar_rect); + _render_profile(print_object, bar_rect); + + // Revert the matrices. + ::glPopMatrix(); + + ::glEnable(GL_DEPTH_TEST); +} + +int GLCanvas3D::LayersEditing::get_shader_program_id() const +{ + const GLShader* shader = m_shader.get_shader(); + return (shader != nullptr) ? shader->shader_program_id : -1; +} + +float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) +{ + const Point& mouse_pos = canvas.get_local_mouse_position(); + const Rect& rect = get_bar_rect_screen(canvas); + float x = (float)mouse_pos.x; + float y = (float)mouse_pos.y; + float t = rect.get_top(); + float b = rect.get_bottom(); + + return ((rect.get_left() <= x) && (x <= rect.get_right()) && (t <= y) && (y <= b)) ? + // Inside the bar. + (b - y - 1.0f) / (b - t - 1.0f) : + // Outside the bar. + -1000.0f; +} + +bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, float x, float y) +{ + const Rect& rect = get_bar_rect_screen(canvas); + return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom()); +} + +bool GLCanvas3D::LayersEditing::reset_rect_contains(const GLCanvas3D& canvas, float x, float y) +{ + const Rect& rect = get_reset_rect_screen(canvas); + return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom()); +} + +Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas) +{ + const Size& cnv_size = canvas.get_canvas_size(); + float w = (float)cnv_size.get_width(); + float h = (float)cnv_size.get_height(); + + return Rect(w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, 0.0f, w, h - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT); +} + +Rect GLCanvas3D::LayersEditing::get_reset_rect_screen(const GLCanvas3D& canvas) +{ + const Size& cnv_size = canvas.get_canvas_size(); + float w = (float)cnv_size.get_width(); + float h = (float)cnv_size.get_height(); + + return Rect(w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, h - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT, w, h); +} + +Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) +{ + const Size& cnv_size = canvas.get_canvas_size(); + float half_w = 0.5f * (float)cnv_size.get_width(); + float half_h = 0.5f * (float)cnv_size.get_height(); + + float zoom = canvas.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + return Rect((half_w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, (-half_h + VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT) * inv_zoom); +} + +Rect GLCanvas3D::LayersEditing::get_reset_rect_viewport(const GLCanvas3D& canvas) +{ + const Size& cnv_size = canvas.get_canvas_size(); + float half_w = 0.5f * (float)cnv_size.get_width(); + float half_h = 0.5f * (float)cnv_size.get_height(); + + float zoom = canvas.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + return Rect((half_w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH) * inv_zoom, (-half_h + VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT) * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom); +} + + +bool GLCanvas3D::LayersEditing::_is_initialized() const +{ + return m_shader.is_initialized(); +} + +void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const +{ + if (m_tooltip_texture.get_id() == 0) + { + std::string filename = resources_dir() + "/icons/variable_layer_height_tooltip.png"; + if (!m_tooltip_texture.load_from_file(filename, false)) + return; + } + + float zoom = canvas.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float gap = 10.0f * inv_zoom; + + float bar_left = bar_rect.get_left(); + float reset_bottom = reset_rect.get_bottom(); + + float l = bar_left - (float)m_tooltip_texture.get_width() * inv_zoom - gap; + float r = bar_left - gap; + float t = reset_bottom + (float)m_tooltip_texture.get_height() * inv_zoom + gap; + float b = reset_bottom + gap; + + GLTexture::render_texture(m_tooltip_texture.get_id(), l, r, b, t); +} + +void GLCanvas3D::LayersEditing::_render_reset_texture(const Rect& reset_rect) const +{ + if (m_reset_texture.get_id() == 0) + { + std::string filename = resources_dir() + "/icons/variable_layer_height_reset.png"; + if (!m_reset_texture.load_from_file(filename, false)) + return; + } + + GLTexture::render_texture(m_reset_texture.get_id(), reset_rect.get_left(), reset_rect.get_right(), reset_rect.get_bottom(), reset_rect.get_top()); +} + +void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas3D& canvas, const GLVolume& volume, const PrintObject& print_object, const Rect& bar_rect) const +{ + float max_z = print_object.model_object()->bounding_box().max.z; + + m_shader.start_using(); + + m_shader.set_uniform("z_to_texture_row", (float)volume.layer_height_texture_z_to_row_id()); + m_shader.set_uniform("z_texture_row_to_normalized", 1.0f / (float)volume.layer_height_texture_height()); + m_shader.set_uniform("z_cursor", max_z * get_cursor_z_relative(canvas)); + m_shader.set_uniform("z_cursor_band_width", band_width); + + GLsizei w = (GLsizei)volume.layer_height_texture_width(); + GLsizei h = (GLsizei)volume.layer_height_texture_height(); + GLsizei half_w = w / 2; + GLsizei half_h = h / 2; + + ::glBindTexture(GL_TEXTURE_2D, m_z_texture_id); + ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + ::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + ::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, volume.layer_height_texture_data_ptr_level0()); + ::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, volume.layer_height_texture_data_ptr_level1()); + + // Render the color bar + float l = bar_rect.get_left(); + float r = bar_rect.get_right(); + float t = bar_rect.get_top(); + float b = bar_rect.get_bottom(); + + ::glBegin(GL_QUADS); + ::glVertex3f(l, b, 0.0f); + ::glVertex3f(r, b, 0.0f); + ::glVertex3f(r, t, max_z); + ::glVertex3f(l, t, max_z); + ::glEnd(); + ::glBindTexture(GL_TEXTURE_2D, 0); + + m_shader.stop_using(); +} + +void GLCanvas3D::LayersEditing::_render_profile(const PrintObject& print_object, const Rect& bar_rect) const +{ + // FIXME show some kind of legend. + + // Get a maximum layer height value. + // FIXME This is a duplicate code of Slicing.cpp. + double layer_height_max = DBL_MAX; + const PrintConfig& print_config = print_object.print()->config; + const std::vector<double>& nozzle_diameters = dynamic_cast<const ConfigOptionFloats*>(print_config.option("nozzle_diameter"))->values; + const std::vector<double>& layer_heights_min = dynamic_cast<const ConfigOptionFloats*>(print_config.option("min_layer_height"))->values; + const std::vector<double>& layer_heights_max = dynamic_cast<const ConfigOptionFloats*>(print_config.option("max_layer_height"))->values; + for (unsigned int i = 0; i < (unsigned int)nozzle_diameters.size(); ++i) + { + double lh_min = (layer_heights_min[i] == 0.0) ? 0.07 : std::max(0.01, layer_heights_min[i]); + double lh_max = (layer_heights_max[i] == 0.0) ? (0.75 * nozzle_diameters[i]) : layer_heights_max[i]; + layer_height_max = std::min(layer_height_max, std::max(lh_min, lh_max)); + } + + // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. + layer_height_max *= 1.12; + + coordf_t max_z = unscale(print_object.size.z); + double layer_height = dynamic_cast<const ConfigOptionFloat*>(print_object.config.option("layer_height"))->value; + float l = bar_rect.get_left(); + float w = bar_rect.get_right() - l; + float b = bar_rect.get_bottom(); + float t = bar_rect.get_top(); + float h = t - b; + float scale_x = w / (float)layer_height_max; + float scale_y = h / (float)max_z; + float x = l + (float)layer_height * scale_x; + + // Baseline + ::glColor3f(0.0f, 0.0f, 0.0f); + ::glBegin(GL_LINE_STRIP); + ::glVertex2f(x, b); + ::glVertex2f(x, t); + ::glEnd(); + + // Curve + const ModelObject* model_object = print_object.model_object(); + if (model_object->layer_height_profile_valid) + { + const std::vector<coordf_t>& profile = model_object->layer_height_profile; + + ::glColor3f(0.0f, 0.0f, 1.0f); + ::glBegin(GL_LINE_STRIP); + for (unsigned int i = 0; i < profile.size(); i += 2) + { + ::glVertex2f(l + (float)profile[i + 1] * scale_x, b + (float)profile[i] * scale_y); + } + ::glEnd(); + } +} + +const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX); +const Pointf3 GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX); + +GLCanvas3D::Mouse::Drag::Drag() + : start_position_2D(Invalid_2D_Point) + , start_position_3D(Invalid_3D_Point) + , volume_idx(-1) +{ +} + +GLCanvas3D::Mouse::Mouse() + : dragging(false) + , position(DBL_MAX, DBL_MAX) +{ +} + +void GLCanvas3D::Mouse::set_start_position_2D_as_invalid() +{ + drag.start_position_2D = Drag::Invalid_2D_Point; +} + +void GLCanvas3D::Mouse::set_start_position_3D_as_invalid() +{ + drag.start_position_3D = Drag::Invalid_3D_Point; +} + +bool GLCanvas3D::Mouse::is_start_position_2D_defined() const +{ + return (drag.start_position_2D != Drag::Invalid_2D_Point); +} + +bool GLCanvas3D::Mouse::is_start_position_3D_defined() const +{ + return (drag.start_position_3D != Drag::Invalid_3D_Point); +} + +const float GLCanvas3D::Gizmos::OverlayOffsetX = 10.0f; +const float GLCanvas3D::Gizmos::OverlayGapY = 10.0f; + +GLCanvas3D::Gizmos::Gizmos() + : m_enabled(false) + , m_current(Undefined) + , m_dragging(false) +{ +} + +GLCanvas3D::Gizmos::~Gizmos() +{ + _reset(); +} + +bool GLCanvas3D::Gizmos::init() +{ + GLGizmoBase* gizmo = new GLGizmoScale; + if (gizmo == nullptr) + return false; + + if (!gizmo->init()) + return false; + + m_gizmos.insert(GizmosMap::value_type(Scale, gizmo)); + + gizmo = new GLGizmoRotate; + if (gizmo == nullptr) + { + _reset(); + return false; + } + + if (!gizmo->init()) + { + _reset(); + return false; + } + + m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo)); + + return true; +} + +bool GLCanvas3D::Gizmos::is_enabled() const +{ + return m_enabled; +} + +void GLCanvas3D::Gizmos::set_enabled(bool enable) +{ + m_enabled = enable; +} + +void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos) +{ + if (!m_enabled) + return; + + float cnv_h = (float)canvas.get_canvas_size().get_height(); + float height = _get_total_overlay_height(); + float top_y = 0.5f * (cnv_h - height); + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if (it->second == nullptr) + continue; + + float tex_size = (float)it->second->get_textures_size(); + float half_tex_size = 0.5f * tex_size; + + // we currently use circular icons for gizmo, so we check the radius + if (it->second->get_state() != GLGizmoBase::On) + { + bool inside = length(Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size).vector_to(mouse_pos)) < half_tex_size; + it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off); + } + top_y += (tex_size + OverlayGapY); + } +} + +void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Pointf& mouse_pos) +{ + if (!m_enabled) + return; + + float cnv_h = (float)canvas.get_canvas_size().get_height(); + float height = _get_total_overlay_height(); + float top_y = 0.5f * (cnv_h - height); + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if (it->second == nullptr) + continue; + + float tex_size = (float)it->second->get_textures_size(); + float half_tex_size = 0.5f * tex_size; + + // we currently use circular icons for gizmo, so we check the radius + if (length(Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size).vector_to(mouse_pos)) < half_tex_size) + { + if ((it->second->get_state() == GLGizmoBase::On)) + { + it->second->set_state(GLGizmoBase::Off); + m_current = Undefined; + } + else + { + it->second->set_state(GLGizmoBase::On); + m_current = it->first; + } + } + else + it->second->set_state(GLGizmoBase::Off); + + top_y += (tex_size + OverlayGapY); + } +} + +void GLCanvas3D::Gizmos::reset_all_states() +{ + if (!m_enabled) + return; + + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if (it->second != nullptr) + { + it->second->set_state(GLGizmoBase::Off); + it->second->set_hover_id(-1); + } + } + + m_current = Undefined; +} + +void GLCanvas3D::Gizmos::set_hover_id(int id) +{ + if (!m_enabled) + return; + + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if ((it->second != nullptr) && (it->second->get_state() == GLGizmoBase::On)) + it->second->set_hover_id(id); + } +} + +bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const +{ + if (!m_enabled) + return false; + + float cnv_h = (float)canvas.get_canvas_size().get_height(); + float height = _get_total_overlay_height(); + float top_y = 0.5f * (cnv_h - height); + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if (it->second == nullptr) + continue; + + float tex_size = (float)it->second->get_textures_size(); + float half_tex_size = 0.5f * tex_size; + + // we currently use circular icons for gizmo, so we check the radius + if (length(Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size).vector_to(mouse_pos)) < half_tex_size) + return true; + + top_y += (tex_size + OverlayGapY); + } + + return false; +} + +bool GLCanvas3D::Gizmos::grabber_contains_mouse() const +{ + if (!m_enabled) + return false; + + GLGizmoBase* curr = _get_current(); + return (curr != nullptr) ? (curr->get_hover_id() != -1) : false; +} + +void GLCanvas3D::Gizmos::update(const Pointf& mouse_pos) +{ + if (!m_enabled) + return; + + GLGizmoBase* curr = _get_current(); + if (curr != nullptr) + curr->update(mouse_pos); +} + +void GLCanvas3D::Gizmos::update_data(float scale) +{ + if (!m_enabled) + return; + + GizmosMap::const_iterator it = m_gizmos.find(Scale); + if (it != m_gizmos.end()) + reinterpret_cast<GLGizmoScale*>(it->second)->set_scale(scale); +} + +bool GLCanvas3D::Gizmos::is_running() const +{ + if (!m_enabled) + return false; + + GLGizmoBase* curr = _get_current(); + return (curr != nullptr) ? (curr->get_state() == GLGizmoBase::On) : false; +} + +bool GLCanvas3D::Gizmos::is_dragging() const +{ + return m_dragging; +} + +void GLCanvas3D::Gizmos::start_dragging() +{ + m_dragging = true; + GLGizmoBase* curr = _get_current(); + if (curr != nullptr) + curr->start_dragging(); +} + +void GLCanvas3D::Gizmos::stop_dragging() +{ + m_dragging = false; +} + +float GLCanvas3D::Gizmos::get_scale() const +{ + if (!m_enabled) + return 1.0f; + + GizmosMap::const_iterator it = m_gizmos.find(Scale); + return (it != m_gizmos.end()) ? reinterpret_cast<GLGizmoScale*>(it->second)->get_scale() : 1.0f; +} + +void GLCanvas3D::Gizmos::render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const +{ + if (!m_enabled) + return; + + ::glDisable(GL_DEPTH_TEST); + + _render_current_gizmo(box); + + ::glPushMatrix(); + ::glLoadIdentity(); + + _render_overlay(canvas); + + ::glPopMatrix(); +} + +void GLCanvas3D::Gizmos::render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const +{ + if (!m_enabled) + return; + + ::glDisable(GL_DEPTH_TEST); + + GLGizmoBase* curr = _get_current(); + if (curr != nullptr) + curr->render_for_picking(box); +} + +void GLCanvas3D::Gizmos::_reset() +{ + for (GizmosMap::value_type& gizmo : m_gizmos) + { + delete gizmo.second; + gizmo.second = nullptr; + } + + m_gizmos.clear(); +} + +void GLCanvas3D::Gizmos::_render_overlay(const GLCanvas3D& canvas) const +{ + if (m_gizmos.empty()) + return; + + float cnv_w = (float)canvas.get_canvas_size().get_width(); + float zoom = canvas.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + float height = _get_total_overlay_height(); + float top_x = (OverlayOffsetX - 0.5f * cnv_w) * inv_zoom; + float top_y = 0.5f * height * inv_zoom; + float scaled_gap_y = OverlayGapY * inv_zoom; + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + float tex_size = (float)it->second->get_textures_size() * inv_zoom; + GLTexture::render_texture(it->second->get_textures_id(), top_x, top_x + tex_size, top_y - tex_size, top_y); + top_y -= (tex_size + scaled_gap_y); + } +} + +void GLCanvas3D::Gizmos::_render_current_gizmo(const BoundingBoxf3& box) const +{ + GLGizmoBase* curr = _get_current(); + if (curr != nullptr) + curr->render(box); +} + +float GLCanvas3D::Gizmos::_get_total_overlay_height() const +{ + float height = 0.0f; + + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + height += (float)it->second->get_textures_size(); + if (std::distance(it, m_gizmos.end()) > 1) + height += OverlayGapY; + } + + return height; +} + +GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const +{ + GizmosMap::const_iterator it = m_gizmos.find(m_current); + return (it != m_gizmos.end()) ? it->second : nullptr; +} + +GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) + : m_canvas(canvas) + , m_context(nullptr) + , m_timer(nullptr) + , m_config(nullptr) + , m_print(nullptr) + , m_model(nullptr) + , m_dirty(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_context = new wxGLContext(m_canvas); + m_timer = new wxTimer(m_canvas); + } +} + +GLCanvas3D::~GLCanvas3D() +{ + reset_volumes(); + + if (m_timer != nullptr) + { + delete m_timer; + m_timer = nullptr; + } + + if (m_context != nullptr) + { + delete m_context; + m_context = nullptr; + } + + _deregister_callbacks(); +} + +bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) +{ + if (m_initialized) + return true; + + if ((m_canvas == nullptr) || (m_context == nullptr)) + return false; + + ::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)) + return m_canvas->SetCurrent(*m_context); + + return false; +} + +void GLCanvas3D::set_as_dirty() +{ + m_dirty = true; +} + +unsigned int GLCanvas3D::get_volumes_count() const +{ + return (unsigned int)m_volumes.volumes.size(); +} + +void GLCanvas3D::reset_volumes() +{ + + if (!m_volumes.empty()) + { + // ensures this canvas is current + if (!set_current()) + return; + + m_volumes.release_geometry(); + m_volumes.clear(); + m_dirty = true; + } +} + +void GLCanvas3D::deselect_volumes() +{ + for (GLVolume* vol : m_volumes.volumes) + { + if (vol != nullptr) + vol->selected = false; + } +} + +void GLCanvas3D::select_volume(unsigned int id) +{ + if (id < (unsigned int)m_volumes.volumes.size()) + { + GLVolume* vol = m_volumes.volumes[id]; + if (vol != nullptr) + vol->selected = true; + } +} + +void GLCanvas3D::update_volumes_selection(const std::vector<int>& selections) +{ + if (m_model == nullptr) + return; + + for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx) + { + if ((selections[obj_idx] == 1) && (obj_idx < (unsigned int)m_objects_volumes_idxs.size())) + { + const std::vector<int>& volume_idxs = m_objects_volumes_idxs[obj_idx]; + for (int v : volume_idxs) + { + select_volume(v); + } + } + } +} + +bool GLCanvas3D::check_volumes_outside_state(const DynamicPrintConfig* config) const +{ + return m_volumes.check_outside_state(config); +} + +bool GLCanvas3D::move_volume_up(unsigned int id) +{ + if ((id > 0) && (id < (unsigned int)m_volumes.volumes.size())) + { + std::swap(m_volumes.volumes[id - 1], m_volumes.volumes[id]); + std::swap(m_volumes.volumes[id - 1]->composite_id, m_volumes.volumes[id]->composite_id); + std::swap(m_volumes.volumes[id - 1]->select_group_id, m_volumes.volumes[id]->select_group_id); + std::swap(m_volumes.volumes[id - 1]->drag_group_id, m_volumes.volumes[id]->drag_group_id); + return true; + } + + return false; +} + +bool GLCanvas3D::move_volume_down(unsigned int id) +{ + if ((id >= 0) && (id + 1 < (unsigned int)m_volumes.volumes.size())) + { + std::swap(m_volumes.volumes[id + 1], m_volumes.volumes[id]); + std::swap(m_volumes.volumes[id + 1]->composite_id, m_volumes.volumes[id]->composite_id); + std::swap(m_volumes.volumes[id + 1]->select_group_id, m_volumes.volumes[id]->select_group_id); + std::swap(m_volumes.volumes[id + 1]->drag_group_id, m_volumes.volumes[id]->drag_group_id); + return true; + } + + return false; +} + +void GLCanvas3D::set_objects_selections(const std::vector<int>& selections) +{ + m_objects_selections = selections; +} + +void GLCanvas3D::set_config(DynamicPrintConfig* config) +{ + m_config = config; +} + +void GLCanvas3D::set_print(Print* print) +{ + m_print = print; +} + +void GLCanvas3D::set_model(Model* model) +{ + m_model = model; +} + +void GLCanvas3D::set_bed_shape(const Pointfs& shape) +{ + m_bed.set_shape(shape); + + // Set the origin and size for painting of the coordinate system axes. + m_axes.origin = Pointf3(0.0, 0.0, (coordf_t)GROUND_Z); + set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size()); + + m_dirty = true; +} + +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 this canvas is current and initialized + if (!set_current() || !_3DScene::init(m_canvas)) + return; + + if (m_force_zoom_to_bed_enabled) + _force_zoom_to_bed(); + + _camera_tranform(); + + GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f }; + ::glLightfv(GL_LIGHT1, GL_POSITION, position_cam); + GLfloat position_top[4] = { -0.5f, -0.5f, 1.0f, 0.0f }; + ::glLightfv(GL_LIGHT0, GL_POSITION, position_top); + + float theta = m_camera.get_theta(); + bool is_custom_bed = m_bed.is_custom(); + + _picking_pass(); + _render_background(); + // untextured bed needs to be rendered before objects + if (is_custom_bed) + { + _render_bed(theta); + // disable depth testing so that axes are not covered by ground + _render_axes(false); + } + _render_objects(); + // textured bed needs to be rendered after objects + if (!is_custom_bed) + { + _render_axes(true); + _render_bed(theta); + } + _render_cutting_plane(); + _render_warning_texture(); + _render_legend_texture(); + _render_gizmo(); + _render_layer_editing_overlay(); + + m_canvas->SwapBuffers(); +} + +std::vector<double> GLCanvas3D::get_current_print_zs(bool active_only) const +{ + return m_volumes.get_current_print_zs(active_only); +} + +void GLCanvas3D::set_toolpaths_range(double low, double high) +{ + m_volumes.set_range(low, high); +} + +std::vector<int> GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs) +{ + if (instance_idxs.empty()) + { + for (unsigned int i = 0; i < model_object.instances.size(); ++i) + { + instance_idxs.push_back(i); + } + } + return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_select_by, m_drag_by, m_use_VBOs && m_initialized); +} + +std::vector<int> GLCanvas3D::load_object(const Model& model, int obj_idx) +{ + if ((0 <= obj_idx) && (obj_idx < (int)model.objects.size())) + { + const ModelObject* model_object = model.objects[obj_idx]; + if (model_object != nullptr) + return load_object(*model_object, obj_idx, std::vector<int>()); + } + + return std::vector<int>(); +} + +void GLCanvas3D::reload_scene(bool force) +{ + if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr)) + return; + + reset_volumes(); + + // ensures this canvas is current + if (!set_current()) + return; + + set_bed_shape(dynamic_cast<const ConfigOptionPoints*>(m_config->option("bed_shape"))->values); + + if (!m_canvas->IsShown() && !force) + { + m_reload_delayed = true; + return; + } + + m_reload_delayed = false; + + m_objects_volumes_idxs.clear(); + + for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx) + { + m_objects_volumes_idxs.push_back(load_object(*m_model, obj_idx)); + } + + update_volumes_selection(m_objects_selections); + + if (m_config->has("nozzle_diameter")) + { + // Should the wipe tower be visualized ? + unsigned int extruders_count = (unsigned int)dynamic_cast<const ConfigOptionFloats*>(m_config->option("nozzle_diameter"))->values.size(); + + bool semm = dynamic_cast<const ConfigOptionBool*>(m_config->option("single_extruder_multi_material"))->value; + bool wt = dynamic_cast<const ConfigOptionBool*>(m_config->option("wipe_tower"))->value; + bool co = dynamic_cast<const ConfigOptionBool*>(m_config->option("complete_objects"))->value; + + if ((extruders_count > 1) && semm && wt && !co) + { + // Height of a print (Show at least a slab) + coordf_t height = std::max(m_model->bounding_box().max.z, 10.0); + + float x = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_x"))->value; + float y = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_y"))->value; + float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_width"))->value; + float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value; + + m_volumes.load_wipe_tower_preview(1000, x, y, w, 15.0f * (float)(extruders_count - 1), (float)height, a, m_use_VBOs && m_initialized); + } + } + + update_volumes_colors_by_extruder(); + + // checks for geometry outside the print volume to render it accordingly + if (!m_volumes.empty()) + { + bool contained = m_volumes.check_outside_state(m_config); + if (!contained) + { + enable_warning_texture(true); + _3DScene::generate_warning_texture(L("Detected object outside print volume")); + m_on_enable_action_buttons_callback.call(false); + } + else + { + enable_warning_texture(false); + m_volumes.reset_outside_state(); + _3DScene::reset_warning_texture(); + m_on_enable_action_buttons_callback.call(!m_model->objects.empty()); + } + } + else + { + enable_warning_texture(false); + _3DScene::reset_warning_texture(); + } +} + +void GLCanvas3D::load_print_toolpaths() +{ + // ensures this canvas is current + if (!set_current()) + return; + + if (m_print == nullptr) + return; + + if (!m_print->state.is_done(psSkirt) || !m_print->state.is_done(psBrim)) + return; + + if (!m_print->has_skirt() && (m_print->config.brim_width.value == 0)) + return; + + const float color[] = { 0.5f, 1.0f, 0.5f, 1.0f }; // greenish + + // number of skirt layers + size_t total_layer_count = 0; + for (const PrintObject* print_object : m_print->objects) + { + total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); + } + size_t skirt_height = m_print->has_infinite_skirt() ? total_layer_count : std::min<size_t>(m_print->config.skirt_height.value, total_layer_count); + if ((skirt_height == 0) && (m_print->config.brim_width.value > 0)) + skirt_height = 1; + + // get first skirt_height layers (maybe this should be moved to a PrintObject method?) + const PrintObject* object0 = m_print->objects.front(); + std::vector<float> print_zs; + print_zs.reserve(skirt_height * 2); + for (size_t i = 0; i < std::min(skirt_height, object0->layers.size()); ++i) + { + print_zs.push_back(float(object0->layers[i]->print_z)); + } + //FIXME why there are support layers? + for (size_t i = 0; i < std::min(skirt_height, object0->support_layers.size()); ++i) + { + print_zs.push_back(float(object0->support_layers[i]->print_z)); + } + sort_remove_duplicates(print_zs); + if (print_zs.size() > skirt_height) + print_zs.erase(print_zs.begin() + skirt_height, print_zs.end()); + + m_volumes.volumes.emplace_back(new GLVolume(color)); + GLVolume& volume = *m_volumes.volumes.back(); + for (size_t i = 0; i < skirt_height; ++i) { + volume.print_zs.push_back(print_zs[i]); + volume.offsets.push_back(volume.indexed_vertex_array.quad_indices.size()); + volume.offsets.push_back(volume.indexed_vertex_array.triangle_indices.size()); + if (i == 0) + _3DScene::extrusionentity_to_verts(m_print->brim, print_zs[i], Point(0, 0), volume); + + _3DScene::extrusionentity_to_verts(m_print->skirt, print_zs[i], Point(0, 0), volume); + } + volume.bounding_box = volume.indexed_vertex_array.bounding_box(); + volume.indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); +} + +void GLCanvas3D::load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors) +{ + std::vector<float> tool_colors = _parse_colors(str_tool_colors); + + struct Ctxt + { + const Points *shifted_copies; + std::vector<const Layer*> layers; + bool has_perimeters; + bool has_infill; + bool has_support; + const std::vector<float>* tool_colors; + + // Number of vertices (each vertex is 6x4=24 bytes long) + static const size_t alloc_size_max() { return 131072; } // 3.15MB + // static const size_t alloc_size_max () { return 65536; } // 1.57MB + // static const size_t alloc_size_max () { return 32768; } // 786kB + static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } + + static const float* color_perimeters() { static float color[4] = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow + static const float* color_infill() { static float color[4] = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish + static const float* color_support() { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish + + // For cloring by a tool, return a parsed color. + bool color_by_tool() const { return tool_colors != nullptr; } + size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } + const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } + int volume_idx(int extruder, int feature) const + { + return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(extruder - 1, 0)) : feature; + } + } ctxt; + + ctxt.shifted_copies = &print_object._shifted_copies; + + // order layers by print_z + ctxt.layers.reserve(print_object.layers.size() + print_object.support_layers.size()); + for (const Layer *layer : print_object.layers) + ctxt.layers.push_back(layer); + for (const Layer *layer : print_object.support_layers) + ctxt.layers.push_back(layer); + std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; }); + + // Maximum size of an allocation block: 32MB / sizeof(float) + ctxt.has_perimeters = print_object.state.is_done(posPerimeters); + ctxt.has_infill = print_object.state.is_done(posInfill); + ctxt.has_support = print_object.state.is_done(posSupportMaterial); + ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; + + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start"; + + //FIXME Improve the heuristics for a grain size. + size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); + tbb::spin_mutex new_volume_mutex; + auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { + auto *volume = new GLVolume(color); + new_volume_mutex.lock(); + volume->outside_printer_detection_enabled = false; + m_volumes.volumes.emplace_back(volume); + new_volume_mutex.unlock(); + return volume; + }; + const size_t volumes_cnt_initial = m_volumes.volumes.size(); + std::vector<GLVolumeCollection> volumes_per_thread(ctxt.layers.size()); + tbb::parallel_for( + tbb::blocked_range<size_t>(0, ctxt.layers.size(), grain_size), + [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) { + std::vector<GLVolume*> vols; + if (ctxt.color_by_tool()) { + for (size_t i = 0; i < ctxt.number_tools(); ++i) + vols.emplace_back(new_volume(ctxt.color_tool(i))); + } + else + vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; + for (GLVolume *vol : vols) + vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { + const Layer *layer = ctxt.layers[idx_layer]; + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.print_zs.empty() || vol.print_zs.back() != layer->print_z) { + vol.print_zs.push_back(layer->print_z); + vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); + vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); + } + } + for (const Point © : *ctxt.shifted_copies) { + for (const LayerRegion *layerm : layer->regions) { + if (ctxt.has_perimeters) + _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, + *vols[ctxt.volume_idx(layerm->region()->config.perimeter_extruder.value, 0)]); + if (ctxt.has_infill) { + for (const ExtrusionEntity *ee : layerm->fills.entities) { + // fill represents infill extrusions of a single island. + const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee); + if (!fill->entities.empty()) + _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, + *vols[ctxt.volume_idx( + is_solid_infill(fill->entities.front()->role()) ? + layerm->region()->config.solid_infill_extruder : + layerm->region()->config.infill_extruder, + 1)]); + } + } + } + if (ctxt.has_support) { + const SupportLayer *support_layer = dynamic_cast<const SupportLayer*>(layer); + if (support_layer) { + for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) + _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, + *vols[ctxt.volume_idx( + (extrusion_entity->role() == erSupportMaterial) ? + support_layer->object()->config.support_material_extruder : + support_layer->object()->config.support_material_interface_extruder, + 2)]); + } + } + } + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { + // Store the vertex arrays and restart their containers, + vols[i] = new_volume(vol.color); + GLVolume &vol_new = *vols[i]; + // Assign the large pre-allocated buffers to the new GLVolume. + vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); + // Copy the content back to the old GLVolume. + vol.indexed_vertex_array = vol_new.indexed_vertex_array; + // Finalize a bounding box of the old GLVolume. + vol.bounding_box = vol.indexed_vertex_array.bounding_box(); + // Clear the buffers, but keep them pre-allocated. + vol_new.indexed_vertex_array.clear(); + // Just make sure that clear did not clear the reserved memory. + vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + } + } + } + for (GLVolume *vol : vols) { + vol->bounding_box = vol->indexed_vertex_array.bounding_box(); + vol->indexed_vertex_array.shrink_to_fit(); + } + }); + + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results"; + // Remove empty volumes from the newly added volumes. + m_volumes.volumes.erase( + std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), + [](const GLVolume *volume) { return volume->empty(); }), + m_volumes.volumes.end()); + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; +} + +void GLCanvas3D::load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors) +{ + if ((m_print == nullptr) || m_print->m_wipe_tower_tool_changes.empty()) + return; + + if (!m_print->state.is_done(psWipeTower)) + return; + + std::vector<float> tool_colors = _parse_colors(str_tool_colors); + + struct Ctxt + { + const Print *print; + const std::vector<float> *tool_colors; + + // Number of vertices (each vertex is 6x4=24 bytes long) + static const size_t alloc_size_max() { return 131072; } // 3.15MB + static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } + + static const float* color_support() { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish + + // For cloring by a tool, return a parsed color. + bool color_by_tool() const { return tool_colors != nullptr; } + size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } + const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } + int volume_idx(int tool, int feature) const + { + return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(tool, 0)) : feature; + } + + const std::vector<WipeTower::ToolChangeResult>& tool_change(size_t idx) { + return priming.empty() ? + ((idx == print->m_wipe_tower_tool_changes.size()) ? final : print->m_wipe_tower_tool_changes[idx]) : + ((idx == 0) ? priming : (idx == print->m_wipe_tower_tool_changes.size() + 1) ? final : print->m_wipe_tower_tool_changes[idx - 1]); + } + std::vector<WipeTower::ToolChangeResult> priming; + std::vector<WipeTower::ToolChangeResult> final; + } ctxt; + + ctxt.print = m_print; + ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; + if (m_print->m_wipe_tower_priming) + ctxt.priming.emplace_back(*m_print->m_wipe_tower_priming.get()); + if (m_print->m_wipe_tower_final_purge) + ctxt.final.emplace_back(*m_print->m_wipe_tower_final_purge.get()); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start"; + + //FIXME Improve the heuristics for a grain size. + size_t n_items = m_print->m_wipe_tower_tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); + size_t grain_size = std::max(n_items / 128, size_t(1)); + tbb::spin_mutex new_volume_mutex; + auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { + auto *volume = new GLVolume(color); + new_volume_mutex.lock(); + volume->outside_printer_detection_enabled = false; + m_volumes.volumes.emplace_back(volume); + new_volume_mutex.unlock(); + return volume; + }; + const size_t volumes_cnt_initial = m_volumes.volumes.size(); + std::vector<GLVolumeCollection> volumes_per_thread(n_items); + tbb::parallel_for( + tbb::blocked_range<size_t>(0, n_items, grain_size), + [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) { + // Bounding box of this slab of a wipe tower. + std::vector<GLVolume*> vols; + if (ctxt.color_by_tool()) { + for (size_t i = 0; i < ctxt.number_tools(); ++i) + vols.emplace_back(new_volume(ctxt.color_tool(i))); + } + else + vols = { new_volume(ctxt.color_support()) }; + for (GLVolume *volume : vols) + volume->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { + const std::vector<WipeTower::ToolChangeResult> &layer = ctxt.tool_change(idx_layer); + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { + vol.print_zs.push_back(layer.front().print_z); + vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); + vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); + } + } + for (const WipeTower::ToolChangeResult &extrusions : layer) { + for (size_t i = 1; i < extrusions.extrusions.size();) { + const WipeTower::Extrusion &e = extrusions.extrusions[i]; + if (e.width == 0.) { + ++i; + continue; + } + size_t j = i + 1; + if (ctxt.color_by_tool()) + for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++j); + else + for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++j); + size_t n_lines = j - i; + Lines lines; + std::vector<double> widths; + std::vector<double> heights; + lines.reserve(n_lines); + widths.reserve(n_lines); + heights.assign(n_lines, extrusions.layer_height); + for (; i < j; ++i) { + const WipeTower::Extrusion &e = extrusions.extrusions[i]; + assert(e.width > 0.f); + const WipeTower::Extrusion &e_prev = *(&e - 1); + lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y)); + widths.emplace_back(e.width); + } + _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, + *vols[ctxt.volume_idx(e.tool, 0)]); + } + } + } + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { + // Store the vertex arrays and restart their containers, + vols[i] = new_volume(vol.color); + GLVolume &vol_new = *vols[i]; + // Assign the large pre-allocated buffers to the new GLVolume. + vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); + // Copy the content back to the old GLVolume. + vol.indexed_vertex_array = vol_new.indexed_vertex_array; + // Finalize a bounding box of the old GLVolume. + vol.bounding_box = vol.indexed_vertex_array.bounding_box(); + // Clear the buffers, but keep them pre-allocated. + vol_new.indexed_vertex_array.clear(); + // Just make sure that clear did not clear the reserved memory. + vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + } + } + for (GLVolume *vol : vols) { + vol->bounding_box = vol->indexed_vertex_array.bounding_box(); + vol->indexed_vertex_array.shrink_to_fit(); + } + }); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results"; + // Remove empty volumes from the newly added volumes. + m_volumes.volumes.erase( + std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), + [](const GLVolume *volume) { return volume->empty(); }), + m_volumes.volumes.end()); + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; +} + +void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors) +{ + if ((m_canvas != nullptr) && (m_print != nullptr)) + { + // ensures that this canvas is current + if (!set_current()) + return; + + if (m_volumes.empty()) + { + std::vector<float> tool_colors = _parse_colors(str_tool_colors); + + m_gcode_preview_volume_index.reset(); + + _load_gcode_extrusion_paths(preview_data, tool_colors); + _load_gcode_travel_paths(preview_data, tool_colors); + _load_gcode_retractions(preview_data); + _load_gcode_unretractions(preview_data); + + if (m_volumes.empty()) + _3DScene::reset_legend_texture(); + else + { + _3DScene::generate_legend_texture(preview_data, tool_colors); + + // removes empty volumes + m_volumes.volumes.erase(std::remove_if(m_volumes.volumes.begin(), m_volumes.volumes.end(), + [](const GLVolume* volume) { return volume->print_zs.empty(); }), m_volumes.volumes.end()); + + _load_shells(); + } + } + + _update_gcode_volumes_visibility(preview_data); + } +} + +void GLCanvas3D::register_on_viewport_changed_callback(void* callback) +{ + if (callback != nullptr) + m_on_viewport_changed_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_double_click_callback(void* callback) +{ + if (callback != nullptr) + m_on_double_click_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_right_click_callback(void* callback) +{ + if (callback != nullptr) + m_on_right_click_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_select_object_callback(void* callback) +{ + if (callback != nullptr) + m_on_select_object_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_model_update_callback(void* callback) +{ + if (callback != nullptr) + m_on_model_update_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_remove_object_callback(void* callback) +{ + if (callback != nullptr) + m_on_remove_object_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_arrange_callback(void* callback) +{ + if (callback != nullptr) + m_on_arrange_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_rotate_object_left_callback(void* callback) +{ + if (callback != nullptr) + m_on_rotate_object_left_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_rotate_object_right_callback(void* callback) +{ + if (callback != nullptr) + m_on_rotate_object_right_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_scale_object_uniformly_callback(void* callback) +{ + if (callback != nullptr) + m_on_scale_object_uniformly_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_increase_objects_callback(void* callback) +{ + if (callback != nullptr) + m_on_increase_objects_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_decrease_objects_callback(void* callback) +{ + if (callback != nullptr) + m_on_decrease_objects_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_instance_moved_callback(void* callback) +{ + if (callback != nullptr) + m_on_instance_moved_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_wipe_tower_moved_callback(void* callback) +{ + if (callback != nullptr) + m_on_wipe_tower_moved_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_enable_action_buttons_callback(void* callback) +{ + if (callback != nullptr) + m_on_enable_action_buttons_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_gizmo_scale_uniformly_callback(void* callback) +{ + if (callback != nullptr) + m_on_gizmo_scale_uniformly_callback.register_callback(callback); +} + +void GLCanvas3D::bind_event_handlers() +{ + if (m_canvas != nullptr) + { + m_canvas->Bind(wxEVT_SIZE, &GLCanvas3D::on_size, this); + m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); + m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, this); + m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); + m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); + m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); + m_canvas->Bind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key_down, this); + } +} + +void GLCanvas3D::unbind_event_handlers() +{ + if (m_canvas != nullptr) + { + m_canvas->Unbind(wxEVT_SIZE, &GLCanvas3D::on_size, this); + m_canvas->Unbind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); + m_canvas->Unbind(wxEVT_CHAR, &GLCanvas3D::on_char, this); + m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); + m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); + m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); + m_canvas->Unbind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key_down, this); + } +} + +void GLCanvas3D::on_size(wxSizeEvent& evt) +{ + m_dirty = true; +} + +void GLCanvas3D::on_idle(wxIdleEvent& evt) +{ + if (!m_dirty) + return; + + _refresh_if_shown_on_screen(); +} + +void GLCanvas3D::on_char(wxKeyEvent& evt) +{ + if (evt.HasModifiers()) + evt.Skip(); + else + { + int keyCode = evt.GetKeyCode(); + switch (keyCode - 48) + { + // numerical input + case 0: { select_view("iso"); break; } + case 1: { select_view("top"); break; } + case 2: { select_view("bottom"); break; } + case 3: { select_view("front"); break; } + case 4: { select_view("rear"); break; } + case 5: { select_view("left"); break; } + case 6: { select_view("right"); break; } + default: + { + // text input + switch (keyCode) + { + // key + + case 43: { m_on_increase_objects_callback.call(); break; } + // key - + case 45: { m_on_decrease_objects_callback.call(); break; } + // key A/a + case 65: + case 97: { m_on_arrange_callback.call(); break; } + // key B/b + case 66: + case 98: { zoom_to_bed(); break; } + // key L/l + case 76: + case 108: { m_on_rotate_object_left_callback.call(); break; } + // key R/r + case 82: + case 114: { m_on_rotate_object_right_callback.call(); break; } + // key S/s + case 83: + case 115: { m_on_scale_object_uniformly_callback.call(); break; } + // key Z/z + case 90: + case 122: { zoom_to_volumes(); break; } + default: + { + evt.Skip(); + break; + } + } + } + } + } +} + +void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) +{ + // Ignore the wheel events if the middle button is pressed. + if (evt.MiddleIsDown()) + return; + + // Performs layers editing updates, if enabled + if (is_layers_editing_enabled()) + { + int object_idx_selected = _get_first_selected_object_id(); + if (object_idx_selected != -1) + { + // A volume is selected. Test, whether hovering over a layer thickness bar. + if (m_layers_editing.bar_rect_contains(*this, (float)evt.GetX(), (float)evt.GetY())) + { + // Adjust the width of the selection. + m_layers_editing.band_width = std::max(std::min(m_layers_editing.band_width * (1.0f + 0.1f * (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta()), 10.0f), 1.5f); + if (m_canvas != nullptr) + m_canvas->Refresh(); + + return; + } + } + } + + // Calculate the zoom delta and apply it to the current zoom factor + float zoom = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta(); + zoom = std::max(std::min(zoom, 4.0f), -4.0f) / 10.0f; + zoom = get_camera_zoom() / (1.0f - zoom); + + // Don't allow to zoom too far outside the scene. + float zoom_min = _get_zoom_to_bounding_box_factor(_max_bounding_box()); + if (zoom_min > 0.0f) + zoom = std::max(zoom, zoom_min * 0.8f); + + m_camera.zoom = zoom; + m_on_viewport_changed_callback.call(); + + _refresh_if_shown_on_screen(); +} + +void GLCanvas3D::on_timer(wxTimerEvent& evt) +{ + if (m_layers_editing.state != LayersEditing::Editing) + return; + + _perform_layer_editing_action(); +} + +void GLCanvas3D::on_mouse(wxMouseEvent& evt) +{ + Point pos(evt.GetX(), evt.GetY()); + + int selected_object_idx = _get_first_selected_object_id(); + int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1; + m_layers_editing.last_object_id = layer_editing_object_idx; + bool gizmos_overlay_contains_mouse = m_gizmos.overlay_contains_mouse(*this, m_mouse.position); + + if (evt.Entering()) + { +#if defined(__WXMSW__) || defined(__linux__) + // On Windows and Linux needs focus in order to catch key events + if (m_canvas != nullptr) + m_canvas->SetFocus(); + + m_mouse.set_start_position_2D_as_invalid(); +#endif + } + else if (evt.LeftDClick() && (m_hover_volume_id != -1)) + m_on_double_click_callback.call(); + else if (evt.LeftDown() || evt.RightDown()) + { + // If user pressed left or right button we first check whether this happened + // on a volume or not. + int volume_idx = m_hover_volume_id; + m_layers_editing.state = LayersEditing::Unknown; + if ((layer_editing_object_idx != -1) && m_layers_editing.bar_rect_contains(*this, pos.x, pos.y)) + { + // A volume is selected and the mouse is inside the layer thickness bar. + // Start editing the layer height. + m_layers_editing.state = LayersEditing::Editing; + _perform_layer_editing_action(&evt); + } + else if ((layer_editing_object_idx != -1) && m_layers_editing.reset_rect_contains(*this, pos.x, pos.y)) + { + if (evt.LeftDown()) + { + // A volume is selected and the mouse is inside the reset button. + m_print->get_object(layer_editing_object_idx)->reset_layer_height_profile(); + // Index 2 means no editing, just wait for mouse up event. + m_layers_editing.state = LayersEditing::Completed; + + m_dirty = true; + } + } + else if ((selected_object_idx != -1) && gizmos_overlay_contains_mouse) + { + m_gizmos.update_on_off_state(*this, m_mouse.position); + _update_gizmos_data(); + m_dirty = true; + } + else if ((selected_object_idx != -1) && m_gizmos.grabber_contains_mouse()) + { + _update_gizmos_data(); + m_gizmos.start_dragging(); + m_dirty = true; + } + else + { + // Select volume in this 3D canvas. + // Don't deselect a volume if layer editing is enabled. We want the object to stay selected + // during the scene manipulation. + + if (m_picking_enabled && ((volume_idx != -1) || !is_layers_editing_enabled())) + { + if (volume_idx != -1) + { + deselect_volumes(); + select_volume(volume_idx); + int group_id = m_volumes.volumes[volume_idx]->select_group_id; + if (group_id != -1) + { + for (GLVolume* vol : m_volumes.volumes) + { + if ((vol != nullptr) && (vol->select_group_id == group_id)) + vol->selected = true; + } + } + + if (m_gizmos.is_running()) + _update_gizmos_data(); + + m_dirty = true; + } + } + + // propagate event through callback + if (m_picking_enabled && (volume_idx != -1)) + _on_select(volume_idx); + + if (volume_idx != -1) + { + if (evt.LeftDown() && m_moving_enabled) + { + // The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate pos x, y, + // an converts the screen space coordinate to unscaled object space. + Pointf3 pos3d = (volume_idx == -1) ? Pointf3(DBL_MAX, DBL_MAX) : _mouse_to_3d(pos); + + // Only accept the initial position, if it is inside the volume bounding box. + BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box(); + volume_bbox.offset(1.0); + if (volume_bbox.contains(pos3d)) + { + // The dragging operation is initiated. + m_mouse.drag.volume_idx = volume_idx; + m_mouse.drag.start_position_3D = pos3d; + // Remember the shift to to the object center.The object center will later be used + // to limit the object placement close to the bed. + m_mouse.drag.volume_center_offset = pos3d.vector_to(volume_bbox.center()); + } + } + else if (evt.RightDown()) + { + // if right clicking on volume, propagate event through callback + if (m_volumes.volumes[volume_idx]->hover) + m_on_right_click_callback.call(pos.x, pos.y); + } + } + } + } + else if (evt.Dragging() && evt.LeftIsDown() && !gizmos_overlay_contains_mouse && (m_layers_editing.state == LayersEditing::Unknown) && (m_mouse.drag.volume_idx != -1)) + { + m_mouse.dragging = true; + + // Get new position at the same Z of the initial click point. + float z0 = 0.0f; + float z1 = 1.0f; + Pointf3 cur_pos = Linef3(_mouse_to_3d(pos, &z0), _mouse_to_3d(pos, &z1)).intersect_plane(m_mouse.drag.start_position_3D.z); + + // Clip the new position, so the object center remains close to the bed. + cur_pos.translate(m_mouse.drag.volume_center_offset); + Point cur_pos2(scale_(cur_pos.x), scale_(cur_pos.y)); + if (!m_bed.contains(cur_pos2)) + { + Point ip = m_bed.point_projection(cur_pos2); + cur_pos.x = unscale(ip.x); + cur_pos.y = unscale(ip.y); + } + cur_pos.translate(m_mouse.drag.volume_center_offset.negative()); + + // Calculate the translation vector. + Vectorf3 vector = m_mouse.drag.start_position_3D.vector_to(cur_pos); + // Get the volume being dragged. + GLVolume* volume = m_volumes.volumes[m_mouse.drag.volume_idx]; + // Get all volumes belonging to the same group, if any. + std::vector<GLVolume*> volumes; + if (volume->drag_group_id == -1) + volumes.push_back(volume); + else + { + for (GLVolume* v : m_volumes.volumes) + { + if ((v != nullptr) && (v->drag_group_id == volume->drag_group_id)) + volumes.push_back(v); + } + } + + // Apply new temporary volume origin and ignore Z. + for (GLVolume* v : volumes) + { + v->origin.translate(vector.x, vector.y, 0.0); + } + + m_mouse.drag.start_position_3D = cur_pos; + + m_dirty = true; + } + else if (evt.Dragging() && m_gizmos.is_dragging()) + { + m_mouse.dragging = true; + + const Pointf3& cur_pos = _mouse_to_bed_3d(pos); + m_gizmos.update(Pointf(cur_pos.x, cur_pos.y)); + + m_on_gizmo_scale_uniformly_callback.call((double)m_gizmos.get_scale()); + m_dirty = true; + } + else if (evt.Dragging() && !gizmos_overlay_contains_mouse) + { + m_mouse.dragging = true; + + if ((m_layers_editing.state != LayersEditing::Unknown) && (layer_editing_object_idx != -1)) + { + if (m_layers_editing.state == LayersEditing::Editing) + _perform_layer_editing_action(&evt); + } + else if (evt.LeftIsDown()) + { + // if dragging over blank area with left button, rotate + if (m_mouse.is_start_position_3D_defined()) + { + const Pointf3& orig = m_mouse.drag.start_position_3D; + m_camera.phi += (((float)pos.x - (float)orig.x) * TRACKBALLSIZE); + m_camera.set_theta(m_camera.get_theta() - ((float)pos.y - (float)orig.y) * TRACKBALLSIZE); + + m_on_viewport_changed_callback.call(); + + m_dirty = true; + } + m_mouse.drag.start_position_3D = Pointf3((coordf_t)pos.x, (coordf_t)pos.y, 0.0); + } + else if (evt.MiddleIsDown() || evt.RightIsDown()) + { + // If dragging over blank area with right button, pan. + if (m_mouse.is_start_position_2D_defined()) + { + // get point in model space at Z = 0 + float z = 0.0f; + const Pointf3& cur_pos = _mouse_to_3d(pos, &z); + Pointf3 orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z); + Pointf3 camera_target = m_camera.target; + camera_target.translate(orig.vector_to(cur_pos).negative()); + m_camera.target = camera_target; + + m_on_viewport_changed_callback.call(); + + m_dirty = true; + } + + m_mouse.drag.start_position_2D = pos; + } + } + else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) + { + if (m_layers_editing.state != LayersEditing::Unknown) + { + m_layers_editing.state = LayersEditing::Unknown; + _stop_timer(); + + if (layer_editing_object_idx != -1) + m_on_model_update_callback.call(); + } + else if ((m_mouse.drag.volume_idx != -1) && m_mouse.dragging) + { + // get all volumes belonging to the same group, if any + std::vector<int> volume_idxs; + int vol_id = m_mouse.drag.volume_idx; + int group_id = m_volumes.volumes[vol_id]->drag_group_id; + if (group_id == -1) + volume_idxs.push_back(vol_id); + else + { + for (int i = 0; i < (int)m_volumes.volumes.size(); ++i) + { + if (m_volumes.volumes[i]->drag_group_id == group_id) + volume_idxs.push_back(i); + } + } + + _on_move(volume_idxs); + } + else if (!m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled()) + { + // deselect and propagate event through callback + if (m_picking_enabled) + { + deselect_volumes(); + _on_select(-1); + } + } + else if (evt.LeftUp() && m_gizmos.is_dragging()) + { + m_gizmos.stop_dragging(); + } + + m_mouse.drag.volume_idx = -1; + m_mouse.set_start_position_3D_as_invalid(); + m_mouse.set_start_position_2D_as_invalid(); + m_mouse.dragging = false; + } + else if (evt.Moving()) + { + m_mouse.position = Pointf((coordf_t)pos.x, (coordf_t)pos.y); + // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over. + if (m_picking_enabled) + m_dirty = true; + } + else + evt.Skip(); +} + +void GLCanvas3D::on_paint(wxPaintEvent& evt) +{ + render(); +} + +void GLCanvas3D::on_key_down(wxKeyEvent& evt) +{ + if (evt.HasModifiers()) + evt.Skip(); + else + { + int key = evt.GetKeyCode(); + if (key == WXK_DELETE) + m_on_remove_object_callback.call(); + else + evt.Skip(); + } +} + +Size GLCanvas3D::get_canvas_size() const +{ + int w = 0; + int h = 0; + + if (m_canvas != nullptr) + m_canvas->GetSize(&w, &h); + + return Size(w, h); +} + +Point GLCanvas3D::get_local_mouse_position() const +{ + if (m_canvas == nullptr) + return Point(); + + wxPoint mouse_pos = m_canvas->ScreenToClient(wxGetMousePosition()); + return Point(mouse_pos.x, mouse_pos.y); +} + +bool GLCanvas3D::_is_shown_on_screen() const +{ + return (m_canvas != nullptr) ? m_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_canvas == nullptr) && (m_context == nullptr)) + return; + + // ensures that this canvas is current + set_current(); + ::glViewport(0, 0, w, h); + + ::glMatrixMode(GL_PROJECTION); + ::glLoadIdentity(); + + const BoundingBoxf3& bbox = _max_bounding_box(); + + switch (m_camera.type) + { + case Camera::Ortho: + { + float w2 = w; + float h2 = h; + float two_zoom = 2.0f * get_camera_zoom(); + if (two_zoom != 0.0f) + { + float inv_two_zoom = 1.0f / two_zoom; + w2 *= inv_two_zoom; + h2 *= inv_two_zoom; + } + + // FIXME: calculate a tighter value for depth will improve z-fighting + float depth = 5.0f * (float)bbox.max_size(); + ::glOrtho(-w2, w2, -h2, h2, -depth, depth); + + break; + } +// case Camera::Perspective: +// { +// float bbox_r = (float)bbox.radius(); +// float fov = PI * 45.0f / 180.0f; +// float fov_tan = tan(0.5f * fov); +// float cam_distance = 0.5f * bbox_r / fov_tan; +// m_camera.distance = cam_distance; +// +// float nr = cam_distance - bbox_r * 1.1f; +// float fr = cam_distance + bbox_r * 1.1f; +// if (nr < 1.0f) +// nr = 1.0f; +// +// if (fr < nr + 1.0f) +// fr = nr + 1.0f; +// +// float h2 = fov_tan * nr; +// float w2 = h2 * w / h; +// ::glFrustum(-w2, w2, -h2, h2, nr, fr); +// +// break; +// } + default: + { + throw std::runtime_error("Invalid camera type."); + break; + } + } + + ::glMatrixMode(GL_MODELVIEW); + + m_dirty = false; +} + +BoundingBoxf3 GLCanvas3D::_max_bounding_box() const +{ + BoundingBoxf3 bb = m_bed.get_bounding_box(); + bb.merge(volumes_bounding_box()); + return bb; +} + +BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const +{ + BoundingBoxf3 bb; + for (const GLVolume* volume : m_volumes.volumes) + { + if ((volume != nullptr) && volume->selected) + bb.merge(volume->transformed_bounding_box()); + } + return bb; +} + +void GLCanvas3D::_zoom_to_bounding_box(const BoundingBoxf3& bbox) +{ + // Calculate the zoom factor needed to adjust viewport to bounding box. + float zoom = _get_zoom_to_bounding_box_factor(bbox); + if (zoom > 0.0f) + { + m_camera.zoom = zoom; + // center view around bounding box center + m_camera.target = bbox.center(); + + m_on_viewport_changed_callback.call(); + + _refresh_if_shown_on_screen(); + } +} + +float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const +{ + float max_bb_size = bbox.max_size(); + if (max_bb_size == 0.0f) + return -1.0f; + + // project the bbox vertices on a plane perpendicular to the camera forward axis + // then calculates the vertices coordinate on this plane along the camera xy axes + + // we need the view matrix, we let opengl calculate it (same as done in render()) + _camera_tranform(); + + // get the view matrix back from opengl + GLfloat matrix[16]; + ::glGetFloatv(GL_MODELVIEW_MATRIX, matrix); + + // camera axes + Pointf3 right((coordf_t)matrix[0], (coordf_t)matrix[4], (coordf_t)matrix[8]); + Pointf3 up((coordf_t)matrix[1], (coordf_t)matrix[5], (coordf_t)matrix[9]); + Pointf3 forward((coordf_t)matrix[2], (coordf_t)matrix[6], (coordf_t)matrix[10]); + + Pointf3 bb_min = bbox.min; + Pointf3 bb_max = bbox.max; + Pointf3 bb_center = bbox.center(); + + // bbox vertices in world space + std::vector<Pointf3> vertices; + vertices.reserve(8); + vertices.push_back(bb_min); + vertices.emplace_back(bb_max.x, bb_min.y, bb_min.z); + vertices.emplace_back(bb_max.x, bb_max.y, bb_min.z); + vertices.emplace_back(bb_min.x, bb_max.y, bb_min.z); + vertices.emplace_back(bb_min.x, bb_min.y, bb_max.z); + vertices.emplace_back(bb_max.x, bb_min.y, bb_max.z); + vertices.push_back(bb_max); + vertices.emplace_back(bb_min.x, bb_max.y, bb_max.z); + + coordf_t max_x = 0.0; + coordf_t max_y = 0.0; + + // margin factor to give some empty space around the bbox + coordf_t margin_factor = 1.25; + + for (const Pointf3 v : vertices) + { + // project vertex on the plane perpendicular to camera forward axis + Pointf3 pos(v.x - bb_center.x, v.y - bb_center.y, v.z - bb_center.z); + Pointf3 proj_on_plane = pos - dot(pos, forward) * forward; + + // calculates vertex coordinate along camera xy axes + coordf_t x_on_plane = dot(proj_on_plane, right); + coordf_t y_on_plane = dot(proj_on_plane, up); + + max_x = std::max(max_x, margin_factor * std::abs(x_on_plane)); + max_y = std::max(max_y, margin_factor * std::abs(y_on_plane)); + } + + if ((max_x == 0.0) || (max_y == 0.0)) + return -1.0f; + + max_x *= 2.0; + max_y *= 2.0; + + const Size& cnv_size = get_canvas_size(); + return (float)std::min((coordf_t)cnv_size.get_width() / max_x, (coordf_t)cnv_size.get_height() / max_y); +} + +void GLCanvas3D::_deregister_callbacks() +{ + m_on_viewport_changed_callback.deregister_callback(); + m_on_double_click_callback.deregister_callback(); + m_on_right_click_callback.deregister_callback(); + m_on_select_object_callback.deregister_callback(); + m_on_model_update_callback.deregister_callback(); + m_on_remove_object_callback.deregister_callback(); + m_on_arrange_callback.deregister_callback(); + m_on_rotate_object_left_callback.deregister_callback(); + m_on_rotate_object_right_callback.deregister_callback(); + m_on_scale_object_uniformly_callback.deregister_callback(); + m_on_increase_objects_callback.deregister_callback(); + m_on_decrease_objects_callback.deregister_callback(); + m_on_instance_moved_callback.deregister_callback(); + m_on_wipe_tower_moved_callback.deregister_callback(); + m_on_enable_action_buttons_callback.deregister_callback(); + m_on_gizmo_scale_uniformly_callback.deregister_callback(); +} + +void GLCanvas3D::_mark_volumes_for_layer_height() const +{ + if (m_print == nullptr) + return; + + for (GLVolume* vol : m_volumes.volumes) + { + int object_id = int(vol->select_group_id / 1000000); + int shader_id = m_layers_editing.get_shader_program_id(); + + if (is_layers_editing_enabled() && (shader_id != -1) && vol->selected && + vol->has_layer_height_texture() && (object_id < (int)m_print->objects.size())) + { + vol->set_layer_height_texture_data(m_layers_editing.get_z_texture_id(), shader_id, + m_print->get_object(object_id), _get_layers_editing_cursor_z_relative(), m_layers_editing.band_width); + } + else + vol->reset_layer_height_texture_data(); + } +} + +void GLCanvas3D::_refresh_if_shown_on_screen() +{ + if (_is_shown_on_screen()) + { + const Size& cnv_size = get_canvas_size(); + _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); + if (m_canvas != nullptr) + m_canvas->Refresh(); + } +} + +void GLCanvas3D::_camera_tranform() const +{ + ::glMatrixMode(GL_MODELVIEW); + ::glLoadIdentity(); + + ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch + ::glRotatef(m_camera.phi, 0.0f, 0.0f, 1.0f); // yaw + + Pointf3 neg_target = m_camera.target.negative(); + ::glTranslatef((GLfloat)neg_target.x, (GLfloat)neg_target.y, (GLfloat)neg_target.z); +} + +void GLCanvas3D::_picking_pass() const +{ + const Pointf& pos = m_mouse.position; + + if (m_picking_enabled && !m_mouse.dragging && (pos != Pointf(DBL_MAX, DBL_MAX))) + { + // Render the object for picking. + // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing. + // Better to use software ray - casting on a bounding - box hierarchy. + + if (m_multisample_allowed) + ::glDisable(GL_MULTISAMPLE); + + ::glDisable(GL_LIGHTING); + ::glDisable(GL_BLEND); + + ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + _render_volumes(true); + m_gizmos.render_current_gizmo_for_picking_pass(_selected_volumes_bounding_box()); + + if (m_multisample_allowed) + ::glEnable(GL_MULTISAMPLE); + + const Size& cnv_size = get_canvas_size(); + + GLubyte color[4]; + ::glReadPixels(pos.x, cnv_size.get_height() - pos.y - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color); + int volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256; + + m_hover_volume_id = -1; + + for (GLVolume* vol : m_volumes.volumes) + { + vol->hover = false; + } + + if (volume_id < (int)m_volumes.volumes.size()) + { + m_hover_volume_id = volume_id; + m_volumes.volumes[volume_id]->hover = true; + int group_id = m_volumes.volumes[volume_id]->select_group_id; + if (group_id != -1) + { + for (GLVolume* vol : m_volumes.volumes) + { + if (vol->select_group_id == group_id) + vol->hover = true; + } + } + m_gizmos.set_hover_id(-1); + } + else + m_gizmos.set_hover_id(254 - (int)color[2]); + + // updates gizmos overlay + if (_get_first_selected_object_id() != -1) + m_gizmos.update_hover_state(*this, pos); + else + m_gizmos.reset_all_states(); + } +} + +void GLCanvas3D::_render_background() const +{ + ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + static const float COLOR[3] = { 10.0f / 255.0f, 98.0f / 255.0f, 144.0f / 255.0f }; + + ::glDisable(GL_LIGHTING); + + ::glPushMatrix(); + ::glLoadIdentity(); + ::glMatrixMode(GL_PROJECTION); + ::glPushMatrix(); + ::glLoadIdentity(); + + // Draws a bluish bottom to top gradient over the complete screen. + ::glDisable(GL_DEPTH_TEST); + + ::glBegin(GL_QUADS); + ::glColor3f(0.0f, 0.0f, 0.0f); + ::glVertex3f(-1.0f, -1.0f, 1.0f); + ::glVertex3f(1.0f, -1.0f, 1.0f); + ::glColor3f(COLOR[0], COLOR[1], COLOR[2]); + ::glVertex3f(1.0f, 1.0f, 1.0f); + ::glVertex3f(-1.0f, 1.0f, 1.0f); + ::glEnd(); + + ::glEnable(GL_DEPTH_TEST); + + ::glPopMatrix(); + ::glMatrixMode(GL_MODELVIEW); + ::glPopMatrix(); +} + +void GLCanvas3D::_render_bed(float theta) const +{ + m_bed.render(theta); +} + +void GLCanvas3D::_render_axes(bool depth_test) const +{ + m_axes.render(depth_test); +} + +void GLCanvas3D::_render_objects() const +{ + if (m_volumes.empty()) + return; + + ::glEnable(GL_LIGHTING); + + if (!m_shader_enabled) + _render_volumes(false); + else if (m_use_VBOs) + { + if (m_picking_enabled) + { + _mark_volumes_for_layer_height(); + + if (m_config != nullptr) + { + const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(); + m_volumes.set_print_box((float)bed_bb.min.x, (float)bed_bb.min.y, 0.0f, (float)bed_bb.max.x, (float)bed_bb.max.y, (float)m_config->opt_float("max_print_height")); + m_volumes.check_outside_state(m_config); + } + // do not cull backfaces to show broken geometry, if any + ::glDisable(GL_CULL_FACE); + } + + m_shader.start_using(); + m_volumes.render_VBOs(); + m_shader.stop_using(); + + if (m_picking_enabled) + ::glEnable(GL_CULL_FACE); + } + else + { + // do not cull backfaces to show broken geometry, if any + if (m_picking_enabled) + ::glDisable(GL_CULL_FACE); + + m_volumes.render_legacy(); + + if (m_picking_enabled) + ::glEnable(GL_CULL_FACE); + } +} + +void GLCanvas3D::_render_cutting_plane() const +{ + m_cutting_plane.render(volumes_bounding_box()); +} + +void GLCanvas3D::_render_warning_texture() const +{ + if (!m_warning_texture_enabled) + return; + + // If the warning texture has not been loaded into the GPU, do it now. + unsigned int tex_id = _3DScene::finalize_warning_texture(); + if (tex_id > 0) + { + unsigned int w = _3DScene::get_warning_texture_width(); + unsigned int h = _3DScene::get_warning_texture_height(); + if ((w > 0) && (h > 0)) + { + ::glDisable(GL_DEPTH_TEST); + ::glPushMatrix(); + ::glLoadIdentity(); + + const Size& cnv_size = get_canvas_size(); + float zoom = get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float l = (-0.5f * (float)w) * inv_zoom; + float t = (-0.5f * (float)cnv_size.get_height() + (float)h) * inv_zoom; + float r = l + (float)w * inv_zoom; + float b = t - (float)h * inv_zoom; + + GLTexture::render_texture(tex_id, l, r, b, t); + + ::glPopMatrix(); + ::glEnable(GL_DEPTH_TEST); + } + } +} + +void GLCanvas3D::_render_legend_texture() const +{ + if (!m_legend_texture_enabled) + return; + + // If the legend texture has not been loaded into the GPU, do it now. + unsigned int tex_id = _3DScene::finalize_legend_texture(); + if (tex_id > 0) + { + unsigned int w = _3DScene::get_legend_texture_width(); + unsigned int h = _3DScene::get_legend_texture_height(); + if ((w > 0) && (h > 0)) + { + ::glDisable(GL_DEPTH_TEST); + ::glPushMatrix(); + ::glLoadIdentity(); + + const Size& cnv_size = get_canvas_size(); + float zoom = get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float l = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; + float t = (0.5f * (float)cnv_size.get_height()) * inv_zoom; + float r = l + (float)w * inv_zoom; + float b = t - (float)h * inv_zoom; + GLTexture::render_texture(tex_id, l, r, b, t); + + ::glPopMatrix(); + ::glEnable(GL_DEPTH_TEST); + } + } +} + +void GLCanvas3D::_render_layer_editing_overlay() const +{ + if (m_print == nullptr) + return; + + GLVolume* volume = nullptr; + + for (GLVolume* vol : m_volumes.volumes) + { + if ((vol != nullptr) && vol->selected && vol->has_layer_height_texture()) + { + volume = vol; + break; + } + } + + if (volume == nullptr) + return; + + // If the active object was not allocated at the Print, go away.This should only be a momentary case between an object addition / deletion + // and an update by Platter::async_apply_config. + int object_idx = int(volume->select_group_id / 1000000); + if ((int)m_print->objects.size() < object_idx) + return; + + const PrintObject* print_object = m_print->get_object(object_idx); + if (print_object == nullptr) + return; + + m_layers_editing.render(*this, *print_object, *volume); +} + +void GLCanvas3D::_render_volumes(bool fake_colors) const +{ + static const GLfloat INV_255 = 1.0f / 255.0f; + + if (fake_colors) + ::glDisable(GL_LIGHTING); + else + ::glEnable(GL_LIGHTING); + + // do not cull backfaces to show broken geometry, if any + ::glDisable(GL_CULL_FACE); + + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + ::glEnableClientState(GL_VERTEX_ARRAY); + ::glEnableClientState(GL_NORMAL_ARRAY); + + unsigned int volume_id = 0; + for (GLVolume* vol : m_volumes.volumes) + { + if (fake_colors) + { + // Object picking mode. Render the object with a color encoding the object index. + unsigned int r = (volume_id & 0x000000FF) >> 0; + unsigned int g = (volume_id & 0x0000FF00) >> 8; + unsigned int b = (volume_id & 0x00FF0000) >> 16; + ::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255); + } + else + { + vol->set_render_color(); + ::glColor4f(vol->render_color[0], vol->render_color[1], vol->render_color[2], vol->render_color[3]); + } + + vol->render(); + ++volume_id; + } + + ::glDisableClientState(GL_NORMAL_ARRAY); + ::glDisableClientState(GL_VERTEX_ARRAY); + ::glDisable(GL_BLEND); + + ::glEnable(GL_CULL_FACE); +} + +void GLCanvas3D::_render_gizmo() const +{ + m_gizmos.render(*this, _selected_volumes_bounding_box()); +} + +float GLCanvas3D::_get_layers_editing_cursor_z_relative() const +{ + return m_layers_editing.get_cursor_z_relative(*this); +} + +void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt) +{ + int object_idx_selected = m_layers_editing.last_object_id; + if (object_idx_selected == -1) + return; + + if (m_print == nullptr) + return; + + PrintObject* selected_obj = m_print->get_object(object_idx_selected); + if (selected_obj == nullptr) + return; + + // A volume is selected. Test, whether hovering over a layer thickness bar. + if (evt != nullptr) + { + const Rect& rect = LayersEditing::get_bar_rect_screen(*this); + float b = rect.get_bottom(); + m_layers_editing.last_z = unscale(selected_obj->size.z) * (b - evt->GetY() - 1.0f) / (b - rect.get_top()); + m_layers_editing.last_action = evt->ShiftDown() ? (evt->RightIsDown() ? 3 : 2) : (evt->RightIsDown() ? 0 : 1); + } + + // Mark the volume as modified, so Print will pick its layer height profile ? Where to mark it ? + // Start a timer to refresh the print ? schedule_background_process() ? + // The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself. + selected_obj->adjust_layer_height_profile(m_layers_editing.last_z, m_layers_editing.strength, m_layers_editing.band_width, m_layers_editing.last_action); + + // searches the id of the first volume of the selected object + int volume_idx = 0; + for (int i = 0; i < object_idx_selected; ++i) + { + PrintObject* obj = m_print->get_object(i); + if (obj != nullptr) + { + for (int j = 0; j < (int)obj->region_volumes.size(); ++j) + { + volume_idx += (int)obj->region_volumes[j].size(); + } + } + } + + m_volumes.volumes[volume_idx]->generate_layer_height_texture(selected_obj, 1); + _refresh_if_shown_on_screen(); + + // Automatic action on mouse down with the same coordinate. + _start_timer(); +} + +Pointf3 GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z) +{ + if (m_canvas == nullptr) + return Pointf3(DBL_MAX, DBL_MAX, DBL_MAX); + + _camera_tranform(); + + GLint viewport[4]; + ::glGetIntegerv(GL_VIEWPORT, viewport); + GLdouble modelview_matrix[16]; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix); + GLdouble projection_matrix[16]; + ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix); + + GLint y = viewport[3] - (GLint)mouse_pos.y; + GLfloat mouse_z; + if (z == nullptr) + ::glReadPixels((GLint)mouse_pos.x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (void*)&mouse_z); + else + mouse_z = *z; + + GLdouble out_x, out_y, out_z; + ::gluUnProject((GLdouble)mouse_pos.x, (GLdouble)y, (GLdouble)mouse_z, modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z); + return Pointf3((coordf_t)out_x, (coordf_t)out_y, (coordf_t)out_z); +} + +Pointf3 GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos) +{ + float z0 = 0.0f; + float z1 = 1.0f; + return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1)).intersect_plane(0.0); +} + +void GLCanvas3D::_start_timer() +{ + if (m_timer != nullptr) + m_timer->Start(100, wxTIMER_CONTINUOUS); +} + +void GLCanvas3D::_stop_timer() +{ + if (m_timer != nullptr) + m_timer->Stop(); +} + +int GLCanvas3D::_get_first_selected_object_id() const +{ + if (m_print != nullptr) + { + int objects_count = (int)m_print->objects.size(); + + for (const GLVolume* vol : m_volumes.volumes) + { + if ((vol != nullptr) && vol->selected) + { + int object_id = vol->select_group_id / 1000000; + // Objects with object_id >= 1000 have a specific meaning, for example the wipe tower proxy. + if (object_id < 10000) + return (object_id >= objects_count) ? -1 : object_id; + } + } + } + return -1; +} + +static inline int hex_digit_to_int(const char c) +{ + return + (c >= '0' && c <= '9') ? int(c - '0') : + (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : + (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; +} + +void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors) +{ + // helper functions to select data in dependence of the extrusion view type + struct Helper + { + static float path_filter(GCodePreviewData::Extrusion::EViewType type, const ExtrusionPath& path) + { + switch (type) + { + case GCodePreviewData::Extrusion::FeatureType: + return (float)path.role(); + case GCodePreviewData::Extrusion::Height: + return path.height; + case GCodePreviewData::Extrusion::Width: + return path.width; + case GCodePreviewData::Extrusion::Feedrate: + return path.feedrate; + case GCodePreviewData::Extrusion::VolumetricRate: + return path.feedrate * (float)path.mm3_per_mm; + case GCodePreviewData::Extrusion::Tool: + return (float)path.extruder_id; + default: + return 0.0f; + } + + return 0.0f; + } + + static GCodePreviewData::Color path_color(const GCodePreviewData& data, const std::vector<float>& tool_colors, float value) + { + switch (data.extrusion.view_type) + { + case GCodePreviewData::Extrusion::FeatureType: + return data.get_extrusion_role_color((ExtrusionRole)(int)value); + case GCodePreviewData::Extrusion::Height: + return data.get_height_color(value); + case GCodePreviewData::Extrusion::Width: + return data.get_width_color(value); + case GCodePreviewData::Extrusion::Feedrate: + return data.get_feedrate_color(value); + case GCodePreviewData::Extrusion::VolumetricRate: + return data.get_volumetric_rate_color(value); + case GCodePreviewData::Extrusion::Tool: + { + GCodePreviewData::Color color; + ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + (unsigned int)value * 4), 4 * sizeof(float)); + return color; + } + default: + return GCodePreviewData::Color::Dummy; + } + + return GCodePreviewData::Color::Dummy; + } + }; + + // Helper structure for filters + struct Filter + { + float value; + ExtrusionRole role; + GLVolume* volume; + + Filter(float value, ExtrusionRole role) + : value(value) + , role(role) + , volume(nullptr) + { + } + + bool operator == (const Filter& other) const + { + if (value != other.value) + return false; + + if (role != other.role) + return false; + + return true; + } + }; + + typedef std::vector<Filter> FiltersList; + size_t initial_volumes_count = m_volumes.volumes.size(); + + // detects filters + FiltersList filters; + for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) + { + for (const ExtrusionPath& path : layer.paths) + { + ExtrusionRole role = path.role(); + float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path); + if (std::find(filters.begin(), filters.end(), Filter(path_filter, role)) == filters.end()) + filters.emplace_back(path_filter, role); + } + } + + // nothing to render, return + if (filters.empty()) + return; + + // creates a new volume for each filter + for (Filter& filter : filters) + { + m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Extrusion, (unsigned int)filter.role, (unsigned int)m_volumes.volumes.size()); + GLVolume* volume = new GLVolume(Helper::path_color(preview_data, tool_colors, filter.value).rgba); + if (volume != nullptr) + { + filter.volume = volume; + m_volumes.volumes.emplace_back(volume); + } + else + { + // an error occourred - restore to previous state and return + m_gcode_preview_volume_index.first_volumes.pop_back(); + if (initial_volumes_count != m_volumes.volumes.size()) + { + std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + initial_volumes_count; + std::vector<GLVolume*>::iterator end = m_volumes.volumes.end(); + for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it) + { + GLVolume* volume = *it; + delete volume; + } + m_volumes.volumes.erase(begin, end); + return; + } + } + } + + // populates volumes + for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) + { + for (const ExtrusionPath& path : layer.paths) + { + float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path); + FiltersList::iterator filter = std::find(filters.begin(), filters.end(), Filter(path_filter, path.role())); + if (filter != filters.end()) + { + filter->volume->print_zs.push_back(layer.z); + filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.quad_indices.size()); + filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.triangle_indices.size()); + + _3DScene::extrusionentity_to_verts(path, layer.z, *filter->volume); + } + } + } + + // finalize volumes and sends geometry to gpu + if (m_volumes.volumes.size() > initial_volumes_count) + { + for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) + { + GLVolume* volume = m_volumes.volumes[i]; + volume->bounding_box = volume->indexed_vertex_array.bounding_box(); + volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + } + } +} + +void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors) +{ + size_t initial_volumes_count = m_volumes.volumes.size(); + m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Travel, 0, (unsigned int)initial_volumes_count); + + bool res = true; + switch (preview_data.extrusion.view_type) + { + case GCodePreviewData::Extrusion::Feedrate: + { + res = _travel_paths_by_feedrate(preview_data); + break; + } + case GCodePreviewData::Extrusion::Tool: + { + res = _travel_paths_by_tool(preview_data, tool_colors); + break; + } + default: + { + res = _travel_paths_by_type(preview_data); + break; + } + } + + if (!res) + { + // an error occourred - restore to previous state and return + if (initial_volumes_count != m_volumes.volumes.size()) + { + std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + initial_volumes_count; + std::vector<GLVolume*>::iterator end = m_volumes.volumes.end(); + for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it) + { + GLVolume* volume = *it; + delete volume; + } + m_volumes.volumes.erase(begin, end); + } + + return; + } + + // finalize volumes and sends geometry to gpu + if (m_volumes.volumes.size() > initial_volumes_count) + { + for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) + { + GLVolume* volume = m_volumes.volumes[i]; + volume->bounding_box = volume->indexed_vertex_array.bounding_box(); + volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + } + } +} + +bool GLCanvas3D::_travel_paths_by_type(const GCodePreviewData& preview_data) +{ + // Helper structure for types + struct Type + { + GCodePreviewData::Travel::EType value; + GLVolume* volume; + + explicit Type(GCodePreviewData::Travel::EType value) + : value(value) + , volume(nullptr) + { + } + + bool operator == (const Type& other) const + { + return value == other.value; + } + }; + + typedef std::vector<Type> TypesList; + + // colors travels by travel type + + // detects types + TypesList types; + for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) + { + if (std::find(types.begin(), types.end(), Type(polyline.type)) == types.end()) + types.emplace_back(polyline.type); + } + + // nothing to render, return + if (types.empty()) + return true; + + // creates a new volume for each type + for (Type& type : types) + { + GLVolume* volume = new GLVolume(preview_data.travel.type_colors[type.value].rgba); + if (volume == nullptr) + return false; + else + { + type.volume = volume; + m_volumes.volumes.emplace_back(volume); + } + } + + // populates volumes + for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) + { + TypesList::iterator type = std::find(types.begin(), types.end(), Type(polyline.type)); + if (type != types.end()) + { + type->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z)); + type->volume->offsets.push_back(type->volume->indexed_vertex_array.quad_indices.size()); + type->volume->offsets.push_back(type->volume->indexed_vertex_array.triangle_indices.size()); + + _3DScene::polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *type->volume); + } + } + + return true; +} + +bool GLCanvas3D::_travel_paths_by_feedrate(const GCodePreviewData& preview_data) +{ + // Helper structure for feedrate + struct Feedrate + { + float value; + GLVolume* volume; + + explicit Feedrate(float value) + : value(value) + , volume(nullptr) + { + } + + bool operator == (const Feedrate& other) const + { + return value == other.value; + } + }; + + typedef std::vector<Feedrate> FeedratesList; + + // colors travels by feedrate + + // detects feedrates + FeedratesList feedrates; + for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) + { + if (std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate)) == feedrates.end()) + feedrates.emplace_back(polyline.feedrate); + } + + // nothing to render, return + if (feedrates.empty()) + return true; + + // creates a new volume for each feedrate + for (Feedrate& feedrate : feedrates) + { + GLVolume* volume = new GLVolume(preview_data.get_feedrate_color(feedrate.value).rgba); + if (volume == nullptr) + return false; + else + { + feedrate.volume = volume; + m_volumes.volumes.emplace_back(volume); + } + } + + // populates volumes + for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) + { + FeedratesList::iterator feedrate = std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate)); + if (feedrate != feedrates.end()) + { + feedrate->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z)); + feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.quad_indices.size()); + feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.triangle_indices.size()); + + _3DScene::polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *feedrate->volume); + } + } + + return true; +} + +bool GLCanvas3D::_travel_paths_by_tool(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors) +{ + // Helper structure for tool + struct Tool + { + unsigned int value; + GLVolume* volume; + + explicit Tool(unsigned int value) + : value(value) + , volume(nullptr) + { + } + + bool operator == (const Tool& other) const + { + return value == other.value; + } + }; + + typedef std::vector<Tool> ToolsList; + + // colors travels by tool + + // detects tools + ToolsList tools; + for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) + { + if (std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id)) == tools.end()) + tools.emplace_back(polyline.extruder_id); + } + + // nothing to render, return + if (tools.empty()) + return true; + + // creates a new volume for each tool + for (Tool& tool : tools) + { + GLVolume* volume = new GLVolume(tool_colors.data() + tool.value * 4); + if (volume == nullptr) + return false; + else + { + tool.volume = volume; + m_volumes.volumes.emplace_back(volume); + } + } + + // populates volumes + for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) + { + ToolsList::iterator tool = std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id)); + if (tool != tools.end()) + { + tool->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z)); + tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.quad_indices.size()); + tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.triangle_indices.size()); + + _3DScene::polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *tool->volume); + } + } + + return true; +} + +void GLCanvas3D::_load_gcode_retractions(const GCodePreviewData& preview_data) +{ + m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Retraction, 0, (unsigned int)m_volumes.volumes.size()); + + // nothing to render, return + if (preview_data.retraction.positions.empty()) + return; + + GLVolume* volume = new GLVolume(preview_data.retraction.color.rgba); + if (volume != nullptr) + { + m_volumes.volumes.emplace_back(volume); + + GCodePreviewData::Retraction::PositionsList copy(preview_data.retraction.positions); + std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; }); + + for (const GCodePreviewData::Retraction::Position& position : copy) + { + volume->print_zs.push_back(unscale(position.position.z)); + volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size()); + volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size()); + + _3DScene::point3_to_verts(position.position, position.width, position.height, *volume); + } + + // finalize volumes and sends geometry to gpu + volume->bounding_box = volume->indexed_vertex_array.bounding_box(); + volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + } +} + +void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data) +{ + m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Unretraction, 0, (unsigned int)m_volumes.volumes.size()); + + // nothing to render, return + if (preview_data.unretraction.positions.empty()) + return; + + GLVolume* volume = new GLVolume(preview_data.unretraction.color.rgba); + if (volume != nullptr) + { + m_volumes.volumes.emplace_back(volume); + + GCodePreviewData::Retraction::PositionsList copy(preview_data.unretraction.positions); + std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; }); + + for (const GCodePreviewData::Retraction::Position& position : copy) + { + volume->print_zs.push_back(unscale(position.position.z)); + volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size()); + volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size()); + + _3DScene::point3_to_verts(position.position, position.width, position.height, *volume); + } + + // finalize volumes and sends geometry to gpu + volume->bounding_box = volume->indexed_vertex_array.bounding_box(); + volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + } +} + +void GLCanvas3D::_load_shells() +{ + size_t initial_volumes_count = m_volumes.volumes.size(); + m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Shell, 0, (unsigned int)initial_volumes_count); + + if (m_print->objects.empty()) + // nothing to render, return + return; + + // adds objects' volumes + unsigned int object_id = 0; + for (PrintObject* obj : m_print->objects) + { + ModelObject* model_obj = obj->model_object(); + + std::vector<int> instance_ids(model_obj->instances.size()); + for (int i = 0; i < (int)model_obj->instances.size(); ++i) + { + instance_ids[i] = i; + } + + m_volumes.load_object(model_obj, object_id, instance_ids, "object", "object", "object", m_use_VBOs && m_initialized); + + ++object_id; + } + + // adds wipe tower's volume + coordf_t max_z = m_print->objects[0]->model_object()->get_model()->bounding_box().max.z; + const PrintConfig& config = m_print->config; + unsigned int extruders_count = config.nozzle_diameter.size(); + if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { + const float width_per_extruder = 15.0f; // a simple workaround after wipe_tower_per_color_wipe got obsolete + m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, width_per_extruder * (extruders_count - 1), max_z, config.wipe_tower_rotation_angle, m_use_VBOs && m_initialized); + } +} + +void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data) +{ + unsigned int size = (unsigned int)m_gcode_preview_volume_index.first_volumes.size(); + for (unsigned int i = 0; i < size; ++i) + { + std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i].id; + std::vector<GLVolume*>::iterator end = (i + 1 < size) ? m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i + 1].id : m_volumes.volumes.end(); + + for (std::vector<GLVolume*>::iterator it = begin; it != end; ++it) + { + GLVolume* volume = *it; + volume->outside_printer_detection_enabled = false; + + switch (m_gcode_preview_volume_index.first_volumes[i].type) + { + case GCodePreviewVolumeIndex::Extrusion: + { + if ((ExtrusionRole)m_gcode_preview_volume_index.first_volumes[i].flag == erCustom) + volume->zoom_to_volumes = false; + + volume->is_active = preview_data.extrusion.is_role_flag_set((ExtrusionRole)m_gcode_preview_volume_index.first_volumes[i].flag); + break; + } + case GCodePreviewVolumeIndex::Travel: + { + volume->is_active = preview_data.travel.is_visible; + volume->zoom_to_volumes = false; + break; + } + case GCodePreviewVolumeIndex::Retraction: + { + volume->is_active = preview_data.retraction.is_visible; + volume->zoom_to_volumes = false; + break; + } + case GCodePreviewVolumeIndex::Unretraction: + { + volume->is_active = preview_data.unretraction.is_visible; + volume->zoom_to_volumes = false; + break; + } + case GCodePreviewVolumeIndex::Shell: + { + volume->is_active = preview_data.shell.is_visible; + volume->color[3] = 0.25f; + volume->zoom_to_volumes = false; + break; + } + default: + { + volume->is_active = false; + volume->zoom_to_volumes = false; + break; + } + } + } + } +} + +void GLCanvas3D::_on_move(const std::vector<int>& volume_idxs) +{ + if (m_model == nullptr) + return; + + std::set<std::string> done; // prevent moving instances twice + bool object_moved = false; + Pointf3 wipe_tower_origin(0.0, 0.0, 0.0); + for (int volume_idx : volume_idxs) + { + GLVolume* volume = m_volumes.volumes[volume_idx]; + int obj_idx = volume->object_idx(); + int instance_idx = volume->instance_idx(); + + // prevent moving instances twice + char done_id[64]; + ::sprintf(done_id, "%d_%d", obj_idx, instance_idx); + if (done.find(done_id) != done.end()) + continue; + + done.insert(done_id); + + if (obj_idx < 1000) + { + // Move a regular object. + ModelObject* model_object = m_model->objects[obj_idx]; + model_object->instances[instance_idx]->offset.translate(volume->origin.x, volume->origin.y); + model_object->invalidate_bounding_box(); + object_moved = true; + } + else if (obj_idx == 1000) + // Move a wipe tower proxy. + wipe_tower_origin = volume->origin; + } + + if (object_moved) + m_on_instance_moved_callback.call(); + + if (wipe_tower_origin != Pointf3(0.0, 0.0, 0.0)) + m_on_wipe_tower_moved_callback.call(wipe_tower_origin.x, wipe_tower_origin.y); +} + +void GLCanvas3D::_on_select(int volume_idx) +{ + int id = -1; + if ((volume_idx != -1) && (volume_idx < (int)m_volumes.volumes.size())) + { + if (m_select_by == "volume") + id = m_volumes.volumes[volume_idx]->volume_idx(); + else if (m_select_by == "object") + id = m_volumes.volumes[volume_idx]->object_idx(); + } + m_on_select_object_callback.call(id); +} + +void GLCanvas3D::_update_gizmos_data() +{ + int id = _get_first_selected_object_id(); + if ((id != -1) && (m_model != nullptr)) + { + ModelObject* model_object = m_model->objects[id]; + if (model_object != nullptr) + { + ModelInstance* model_instance = model_object->instances[0]; + if (model_instance != nullptr) + m_gizmos.update_data(model_instance->scaling_factor); + } + } +} + +std::vector<float> GLCanvas3D::_parse_colors(const std::vector<std::string>& colors) +{ + static const float INV_255 = 1.0f / 255.0f; + + std::vector<float> output(colors.size() * 4, 1.0f); + for (size_t i = 0; i < colors.size(); ++i) + { + const std::string& color = colors[i]; + const char* c = color.data() + 1; + if ((color.size() == 7) && (color.front() == '#')) + { + for (size_t j = 0; j < 3; ++j) + { + int digit1 = hex_digit_to_int(*c++); + int digit2 = hex_digit_to_int(*c++); + if ((digit1 == -1) || (digit2 == -1)) + break; + + output[i * 4 + j] = float(digit1 * 16 + digit2) * INV_255; + } + } + } + return output; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp new file mode 100644 index 000000000..b0706a05d --- /dev/null +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -0,0 +1,636 @@ +#ifndef slic3r_GLCanvas3D_hpp_ +#define slic3r_GLCanvas3D_hpp_ + +#include "../../slic3r/GUI/3DScene.hpp" +#include "../../slic3r/GUI/GLTexture.hpp" + +class wxTimer; +class wxSizeEvent; +class wxIdleEvent; +class wxKeyEvent; +class wxMouseEvent; +class wxTimerEvent; +class wxPaintEvent; + +namespace Slic3r { + +class GLShader; +class ExPolygon; + +namespace GUI { + +class GLGizmoBase; + +class GeometryBuffer +{ + std::vector<float> m_vertices; + std::vector<float> m_tex_coords; + +public: + bool set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords); + bool set_from_lines(const Lines& lines, float z); + + const float* get_vertices() const; + const float* get_tex_coords() const; + + unsigned int get_vertices_count() const; +}; + +class Size +{ + int m_width; + int m_height; + +public: + Size(); + Size(int width, int height); + + int get_width() const; + void set_width(int width); + + int get_height() const; + void set_height(int height); +}; + +class Rect +{ + float m_left; + float m_top; + float m_right; + float m_bottom; + +public: + Rect(); + Rect(float left, float top, float right, float bottom); + + float get_left() const; + void set_left(float left); + + float get_top() const; + void set_top(float top); + + float get_right() const; + void set_right(float right); + + float get_bottom() const; + void set_bottom(float bottom); +}; + +class GLCanvas3D +{ + struct GCodePreviewVolumeIndex + { + enum EType + { + Extrusion, + Travel, + Retraction, + Unretraction, + Shell, + Num_Geometry_Types + }; + + struct FirstVolume + { + EType type; + unsigned int flag; + // Index of the first volume in a GLVolumeCollection. + unsigned int id; + + FirstVolume(EType type, unsigned int flag, unsigned int id) : type(type), flag(flag), id(id) {} + }; + + std::vector<FirstVolume> first_volumes; + + void reset() { first_volumes.clear(); } + }; + +public: + struct Camera + { + enum EType : unsigned char + { + Unknown, +// Perspective, + Ortho, + Num_types + }; + + EType type; + float zoom; + float phi; +// float distance; + Pointf3 target; + + private: + float m_theta; + + public: + Camera(); + + std::string get_type_as_string() const; + + float get_theta() const; + void set_theta(float theta); + }; + + class Bed + { + public: + enum EType : unsigned char + { + MK2, + MK3, + Custom, + Num_Types + }; + + private: + EType m_type; + Pointfs m_shape; + BoundingBoxf3 m_bounding_box; + Polygon m_polygon; + GeometryBuffer m_triangles; + GeometryBuffer m_gridlines; + mutable GLTexture m_top_texture; + mutable GLTexture m_bottom_texture; + + public: + Bed(); + + bool is_prusa() const; + bool is_custom() const; + + const Pointfs& get_shape() const; + void set_shape(const Pointfs& shape); + + const BoundingBoxf3& get_bounding_box() const; + bool contains(const Point& point) const; + Point point_projection(const Point& point) const; + + void render(float theta) const; + + private: + void _calc_bounding_box(); + void _calc_triangles(const ExPolygon& poly); + void _calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); + EType _detect_type() const; + void _render_mk2(float theta) const; + void _render_mk3(float theta) const; + void _render_prusa(float theta) const; + void _render_custom() const; + static bool _are_equal(const Pointfs& bed_1, const Pointfs& bed_2); + }; + + struct Axes + { + Pointf3 origin; + float length; + + Axes(); + + void render(bool depth_test) const; + }; + + class CuttingPlane + { + float m_z; + GeometryBuffer m_lines; + + public: + CuttingPlane(); + + bool set(float z, const ExPolygons& polygons); + + void render(const BoundingBoxf3& bb) const; + + private: + void _render_plane(const BoundingBoxf3& bb) const; + void _render_contour() const; + }; + + class Shader + { + GLShader* m_shader; + + public: + Shader(); + ~Shader(); + + bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename); + + bool is_initialized() const; + + bool start_using() const; + void stop_using() const; + + void set_uniform(const std::string& name, float value) const; + + const GLShader* get_shader() const; + + private: + void _reset(); + }; + + class LayersEditing + { + public: + enum EState : unsigned char + { + Unknown, + Editing, + Completed, + Num_States + }; + + private: + bool m_use_legacy_opengl; + bool m_enabled; + Shader m_shader; + unsigned int m_z_texture_id; + mutable GLTexture m_tooltip_texture; + mutable GLTexture m_reset_texture; + + public: + EState state; + float band_width; + float strength; + int last_object_id; + float last_z; + unsigned int last_action; + + LayersEditing(); + ~LayersEditing(); + + bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename); + + bool is_allowed() const; + void set_use_legacy_opengl(bool use_legacy_opengl); + + bool is_enabled() const; + void set_enabled(bool enabled); + + unsigned int get_z_texture_id() const; + + void render(const GLCanvas3D& canvas, const PrintObject& print_object, const GLVolume& volume) const; + + int get_shader_program_id() const; + + static float get_cursor_z_relative(const GLCanvas3D& canvas); + static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y); + static bool reset_rect_contains(const GLCanvas3D& canvas, float x, float y); + static Rect get_bar_rect_screen(const GLCanvas3D& canvas); + static Rect get_reset_rect_screen(const GLCanvas3D& canvas); + static Rect get_bar_rect_viewport(const GLCanvas3D& canvas); + static Rect get_reset_rect_viewport(const GLCanvas3D& canvas); + + private: + bool _is_initialized() const; + void _render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const; + void _render_reset_texture(const Rect& reset_rect) const; + void _render_active_object_annotations(const GLCanvas3D& canvas, const GLVolume& volume, const PrintObject& print_object, const Rect& bar_rect) const; + void _render_profile(const PrintObject& print_object, const Rect& bar_rect) const; + }; + + struct Mouse + { + struct Drag + { + static const Point Invalid_2D_Point; + static const Pointf3 Invalid_3D_Point; + + Point start_position_2D; + Pointf3 start_position_3D; + Vectorf3 volume_center_offset; + int volume_idx; + + public: + Drag(); + }; + + bool dragging; + Pointf position; + Drag drag; + + Mouse(); + + void set_start_position_2D_as_invalid(); + void set_start_position_3D_as_invalid(); + + bool is_start_position_2D_defined() const; + bool is_start_position_3D_defined() const; + }; + + class Gizmos + { + static const float OverlayOffsetX; + static const float OverlayGapY; + + public: + enum EType : unsigned char + { + Undefined, + Scale, + Rotate, + Num_Types + }; + + private: + bool m_enabled; + typedef std::map<EType, GLGizmoBase*> GizmosMap; + GizmosMap m_gizmos; + EType m_current; + bool m_dragging; + + public: + Gizmos(); + ~Gizmos(); + + bool init(); + + bool is_enabled() const; + void set_enabled(bool enable); + + void update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos); + void update_on_off_state(const GLCanvas3D& canvas, const Pointf& mouse_pos); + void reset_all_states(); + + void set_hover_id(int id); + + bool overlay_contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const; + bool grabber_contains_mouse() const; + void update(const Pointf& mouse_pos); + void update_data(float scale); + + bool is_running() const; + bool is_dragging() const; + void start_dragging(); + void stop_dragging(); + + float get_scale() const; + + void render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const; + void render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const; + + private: + void _reset(); + + void _render_overlay(const GLCanvas3D& canvas) const; + void _render_current_gizmo(const BoundingBoxf3& box) const; + + float _get_total_overlay_height() const; + GLGizmoBase* _get_current() const; + }; + +private: + wxGLCanvas* m_canvas; + wxGLContext* m_context; + wxTimer* m_timer; + Camera m_camera; + Bed m_bed; + Axes m_axes; + CuttingPlane m_cutting_plane; + LayersEditing m_layers_editing; + Shader m_shader; + Mouse m_mouse; + mutable Gizmos m_gizmos; + + mutable GLVolumeCollection m_volumes; + DynamicPrintConfig* m_config; + Print* m_print; + Model* m_model; + + bool m_dirty; + bool m_initialized; + bool m_use_VBOs; + bool m_force_zoom_to_bed_enabled; + bool m_apply_zoom_to_volumes_filter; + mutable int m_hover_volume_id; + bool m_warning_texture_enabled; + bool m_legend_texture_enabled; + bool m_picking_enabled; + bool m_moving_enabled; + bool m_shader_enabled; + bool m_multisample_allowed; + + std::string m_color_by; + std::string m_select_by; + std::string m_drag_by; + + bool m_reload_delayed; + std::vector<std::vector<int>> m_objects_volumes_idxs; + std::vector<int> m_objects_selections; + + GCodePreviewVolumeIndex m_gcode_preview_volume_index; + + PerlCallback m_on_viewport_changed_callback; + PerlCallback m_on_double_click_callback; + PerlCallback m_on_right_click_callback; + PerlCallback m_on_select_object_callback; + PerlCallback m_on_model_update_callback; + PerlCallback m_on_remove_object_callback; + PerlCallback m_on_arrange_callback; + PerlCallback m_on_rotate_object_left_callback; + PerlCallback m_on_rotate_object_right_callback; + PerlCallback m_on_scale_object_uniformly_callback; + PerlCallback m_on_increase_objects_callback; + PerlCallback m_on_decrease_objects_callback; + PerlCallback m_on_instance_moved_callback; + PerlCallback m_on_wipe_tower_moved_callback; + PerlCallback m_on_enable_action_buttons_callback; + PerlCallback m_on_gizmo_scale_uniformly_callback; + +public: + GLCanvas3D(wxGLCanvas* canvas); + ~GLCanvas3D(); + + bool init(bool useVBOs, bool use_legacy_opengl); + + bool set_current(); + + void set_as_dirty(); + + unsigned int get_volumes_count() const; + void reset_volumes(); + void deselect_volumes(); + void select_volume(unsigned int id); + void update_volumes_selection(const std::vector<int>& selections); + bool check_volumes_outside_state(const DynamicPrintConfig* config) const; + bool move_volume_up(unsigned int id); + bool move_volume_down(unsigned int id); + + void set_objects_selections(const std::vector<int>& selections); + + void set_config(DynamicPrintConfig* config); + void set_print(Print* print); + void set_model(Model* model); + + // Set the bed shape to a single closed 2D polygon(array of two element arrays), + // triangulate the bed and store the triangles into m_bed.m_triangles, + // fills the m_bed.m_grid_lines and sets m_bed.m_origin. + // Sets m_bed.m_polygon to limit the object placement. + void set_bed_shape(const Pointfs& shape); + // Used by ObjectCutDialog and ObjectPartsPanel to generate a rectangular ground plane to support the scene objects. + void set_auto_bed_shape(); + + void set_axes_length(float length); + + void set_cutting_plane(float z, const ExPolygons& polygons); + + void set_color_by(const std::string& value); + void set_select_by(const std::string& value); + void set_drag_by(const std::string& value); + + float get_camera_zoom() const; + + BoundingBoxf3 volumes_bounding_box() const; + + bool is_layers_editing_enabled() const; + bool is_layers_editing_allowed() const; + bool is_shader_enabled() const; + + bool is_reload_delayed() const; + + void enable_layers_editing(bool enable); + void enable_warning_texture(bool enable); + void enable_legend_texture(bool enable); + void enable_picking(bool enable); + void enable_moving(bool enable); + void enable_gizmos(bool enable); + void enable_shader(bool enable); + void enable_force_zoom_to_bed(bool enable); + void allow_multisample(bool allow); + + void zoom_to_bed(); + void zoom_to_volumes(); + void select_view(const std::string& direction); + void set_viewport_from_scene(const GLCanvas3D& other); + + void update_volumes_colors_by_extruder(); + + void render(); + + std::vector<double> get_current_print_zs(bool active_only) const; + void set_toolpaths_range(double low, double high); + + std::vector<int> load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs); + std::vector<int> load_object(const Model& model, int obj_idx); + + void reload_scene(bool force); + + // Create 3D thick extrusion lines for a skirt and brim. + // Adds a new Slic3r::GUI::3DScene::Volume to volumes. + void load_print_toolpaths(); + // Create 3D thick extrusion lines for object forming extrusions. + // Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes, + // one for perimeters, one for infill and one for supports. + void load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors); + // Create 3D thick extrusion lines for wipe tower extrusions + void load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors); + void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors); + + void register_on_viewport_changed_callback(void* callback); + void register_on_double_click_callback(void* callback); + void register_on_right_click_callback(void* callback); + void register_on_select_object_callback(void* callback); + void register_on_model_update_callback(void* callback); + void register_on_remove_object_callback(void* callback); + void register_on_arrange_callback(void* callback); + void register_on_rotate_object_left_callback(void* callback); + void register_on_rotate_object_right_callback(void* callback); + void register_on_scale_object_uniformly_callback(void* callback); + void register_on_increase_objects_callback(void* callback); + void register_on_decrease_objects_callback(void* callback); + void register_on_instance_moved_callback(void* callback); + void register_on_wipe_tower_moved_callback(void* callback); + void register_on_enable_action_buttons_callback(void* callback); + void register_on_gizmo_scale_uniformly_callback(void* callback); + + void bind_event_handlers(); + void unbind_event_handlers(); + + void on_size(wxSizeEvent& evt); + void on_idle(wxIdleEvent& evt); + void on_char(wxKeyEvent& evt); + void on_mouse_wheel(wxMouseEvent& evt); + void on_timer(wxTimerEvent& evt); + void on_mouse(wxMouseEvent& evt); + void on_paint(wxPaintEvent& evt); + void on_key_down(wxKeyEvent& evt); + + Size get_canvas_size() const; + Point get_local_mouse_position() const; + +private: + bool _is_shown_on_screen() const; + void _force_zoom_to_bed(); + + void _resize(unsigned int w, unsigned int h); + + BoundingBoxf3 _max_bounding_box() const; + BoundingBoxf3 _selected_volumes_bounding_box() const; + + void _zoom_to_bounding_box(const BoundingBoxf3& bbox); + float _get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const; + + void _deregister_callbacks(); + + void _mark_volumes_for_layer_height() const; + void _refresh_if_shown_on_screen(); + + void _camera_tranform() const; + void _picking_pass() const; + void _render_background() const; + void _render_bed(float theta) const; + void _render_axes(bool depth_test) const; + void _render_objects() const; + void _render_cutting_plane() const; + void _render_warning_texture() const; + void _render_legend_texture() const; + void _render_layer_editing_overlay() const; + void _render_volumes(bool fake_colors) const; + void _render_gizmo() const; + + float _get_layers_editing_cursor_z_relative() const; + void _perform_layer_editing_action(wxMouseEvent* evt = nullptr); + + // Convert the screen space coordinate to an object space coordinate. + // If the Z screen space coordinate is not provided, a depth buffer value is substituted. + Pointf3 _mouse_to_3d(const Point& mouse_pos, float* z = nullptr); + + // Convert the screen space coordinate to world coordinate on the bed. + Pointf3 _mouse_to_bed_3d(const Point& mouse_pos); + + void _start_timer(); + void _stop_timer(); + + int _get_first_selected_object_id() const; + + // generates gcode extrusion paths geometry + void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors); + // generates gcode travel paths geometry + void _load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors); + bool _travel_paths_by_type(const GCodePreviewData& preview_data); + bool _travel_paths_by_feedrate(const GCodePreviewData& preview_data); + bool _travel_paths_by_tool(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors); + // generates gcode retractions geometry + void _load_gcode_retractions(const GCodePreviewData& preview_data); + // generates gcode unretractions geometry + void _load_gcode_unretractions(const GCodePreviewData& preview_data); + // generates objects and wipe tower geometry + void _load_shells(); + // sets gcode geometry visibility according to user selection + void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data); + + void _on_move(const std::vector<int>& volume_idxs); + void _on_select(int volume_idx); + + void _update_gizmos_data(); + + static std::vector<float> _parse_colors(const std::vector<std::string>& colors); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLCanvas3D_hpp_ diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp new file mode 100644 index 000000000..ec4ac1606 --- /dev/null +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -0,0 +1,677 @@ +#include "GLCanvas3DManager.hpp" +#include "../../slic3r/GUI/GUI.hpp" +#include "../../slic3r/GUI/AppConfig.hpp" +#include "../../slic3r/GUI/GLCanvas3D.hpp" + +#include <GL/glew.h> + +#include <boost/algorithm/string/split.hpp> +#include <boost/algorithm/string/classification.hpp> + +#include <wx/glcanvas.h> +#include <wx/timer.h> + +#include <vector> +#include <string> +#include <iostream> + +namespace Slic3r { +namespace GUI { + +GLCanvas3DManager::GLInfo::GLInfo() + : version("") + , glsl_version("") + , vendor("") + , renderer("") +{ +} + +void GLCanvas3DManager::GLInfo::detect() +{ + const char* data = (const char*)::glGetString(GL_VERSION); + if (data != nullptr) + version = data; + + data = (const char*)::glGetString(GL_SHADING_LANGUAGE_VERSION); + if (data != nullptr) + glsl_version = data; + + data = (const char*)::glGetString(GL_VENDOR); + if (data != nullptr) + vendor = data; + + data = (const char*)::glGetString(GL_RENDERER); + if (data != nullptr) + renderer = data; +} + +bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const +{ + std::vector<std::string> tokens; + boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on); + + if (tokens.empty()) + return false; + + std::vector<std::string> numbers; + boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on); + + unsigned int gl_major = 0; + unsigned int gl_minor = 0; + + if (numbers.size() > 0) + gl_major = ::atoi(numbers[0].c_str()); + + if (numbers.size() > 1) + gl_minor = ::atoi(numbers[1].c_str()); + + if (gl_major < major) + return false; + else if (gl_major > major) + return true; + else + return gl_minor >= minor; +} + +std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool extensions) const +{ + std::stringstream out; + + std::string h2_start = format_as_html ? "<b>" : ""; + std::string h2_end = format_as_html ? "</b>" : ""; + std::string b_start = format_as_html ? "<b>" : ""; + std::string b_end = format_as_html ? "</b>" : ""; + std::string line_end = format_as_html ? "<br>" : "\n"; + + out << h2_start << "OpenGL installation" << h2_end << line_end; + out << b_start << "GL version: " << b_end << (version.empty() ? "N/A" : version) << line_end; + out << b_start << "Vendor: " << b_end << (vendor.empty() ? "N/A" : vendor) << line_end; + out << b_start << "Renderer: " << b_end << (renderer.empty() ? "N/A" : renderer) << line_end; + out << b_start << "GLSL version: " << b_end << (glsl_version.empty() ? "N/A" : glsl_version) << line_end; + + if (extensions) + { + std::vector<std::string> extensions_list; + std::string extensions_str = (const char*)::glGetString(GL_EXTENSIONS); + boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_off); + + if (!extensions_list.empty()) + { + out << h2_start << "Installed extensions:" << h2_end << line_end; + + 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_current(nullptr) + , m_gl_initialized(false) + , m_use_legacy_opengl(false) + , m_use_VBOs(false) +{ +} + +bool GLCanvas3DManager::add(wxGLCanvas* canvas) +{ + if (canvas == nullptr) + return false; + + if (_get_canvas(canvas) != m_canvases.end()) + return false; + + GLCanvas3D* canvas3D = new GLCanvas3D(canvas); + 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(); + 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; + } +} + +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_as_dirty(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_as_dirty(); +} + +unsigned int GLCanvas3DManager::get_volumes_count(wxGLCanvas* canvas) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->get_volumes_count() : 0; +} + +void GLCanvas3DManager::reset_volumes(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->reset_volumes(); +} + +void GLCanvas3DManager::deselect_volumes(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->deselect_volumes(); +} + +void GLCanvas3DManager::select_volume(wxGLCanvas* canvas, unsigned int id) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->select_volume(id); +} + +void GLCanvas3DManager::update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->update_volumes_selection(selections); +} + +bool GLCanvas3DManager::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->check_volumes_outside_state(config) : false; +} + +bool GLCanvas3DManager::move_volume_up(wxGLCanvas* canvas, unsigned int id) +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->move_volume_up(id) : false; +} + +bool GLCanvas3DManager::move_volume_down(wxGLCanvas* canvas, unsigned int id) +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->move_volume_down(id) : false; +} + +void GLCanvas3DManager::set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_objects_selections(selections); +} + +void GLCanvas3DManager::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_config(config); +} + +void GLCanvas3DManager::set_print(wxGLCanvas* canvas, Print* print) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_print(print); +} + +void GLCanvas3DManager::set_model(wxGLCanvas* canvas, Model* model) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_model(model); +} + +void GLCanvas3DManager::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_bed_shape(shape); +} + +void GLCanvas3DManager::set_auto_bed_shape(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_auto_bed_shape(); +} + +BoundingBoxf3 GLCanvas3DManager::get_volumes_bounding_box(wxGLCanvas* canvas) +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->volumes_bounding_box() : BoundingBoxf3(); +} + +void GLCanvas3DManager::set_axes_length(wxGLCanvas* canvas, float length) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_axes_length(length); +} + +void GLCanvas3DManager::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_cutting_plane(z, polygons); +} + +void GLCanvas3DManager::set_color_by(wxGLCanvas* canvas, const std::string& value) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_color_by(value); +} + +void GLCanvas3DManager::set_select_by(wxGLCanvas* canvas, const std::string& value) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_select_by(value); +} + +void GLCanvas3DManager::set_drag_by(wxGLCanvas* canvas, const std::string& value) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_drag_by(value); +} + +bool GLCanvas3DManager::is_layers_editing_enabled(wxGLCanvas* canvas) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->is_layers_editing_enabled() : false; +} + +bool GLCanvas3DManager::is_layers_editing_allowed(wxGLCanvas* canvas) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->is_layers_editing_allowed() : false; +} + +bool GLCanvas3DManager::is_shader_enabled(wxGLCanvas* canvas) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->is_shader_enabled() : false; +} + +bool GLCanvas3DManager::is_reload_delayed(wxGLCanvas* canvas) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->is_reload_delayed() : false; +} + +void GLCanvas3DManager::enable_layers_editing(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_layers_editing(enable); +} + +void GLCanvas3DManager::enable_warning_texture(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_warning_texture(enable); +} + +void GLCanvas3DManager::enable_legend_texture(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_legend_texture(enable); +} + +void GLCanvas3DManager::enable_picking(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_picking(enable); +} + +void GLCanvas3DManager::enable_moving(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_moving(enable); +} + +void GLCanvas3DManager::enable_gizmos(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_gizmos(enable); +} + +void GLCanvas3DManager::enable_shader(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_shader(enable); +} + +void GLCanvas3DManager::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_force_zoom_to_bed(enable); +} + +void GLCanvas3DManager::allow_multisample(wxGLCanvas* canvas, bool allow) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->allow_multisample(allow); +} + +void GLCanvas3DManager::zoom_to_bed(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->zoom_to_bed(); +} + +void GLCanvas3DManager::zoom_to_volumes(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->zoom_to_volumes(); +} + +void GLCanvas3DManager::select_view(wxGLCanvas* canvas, const std::string& direction) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->select_view(direction); +} + +void GLCanvas3DManager::set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + { + CanvasesMap::iterator other_it = _get_canvas(other); + if (other_it != m_canvases.end()) + it->second->set_viewport_from_scene(*other_it->second); + } +} + +void GLCanvas3DManager::update_volumes_colors_by_extruder(wxGLCanvas* canvas) +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->update_volumes_colors_by_extruder(); +} + +void GLCanvas3DManager::render(wxGLCanvas* canvas) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->render(); +} + +std::vector<double> GLCanvas3DManager::get_current_print_zs(wxGLCanvas* canvas, bool active_only) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->get_current_print_zs(active_only) : std::vector<double>(); +} + +void GLCanvas3DManager::set_toolpaths_range(wxGLCanvas* canvas, double low, double high) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_toolpaths_range(low, high); +} + +std::vector<int> GLCanvas3DManager::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs) +{ + if (model_object == nullptr) + return std::vector<int>(); + + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->load_object(*model_object, obj_idx, instance_idxs) : std::vector<int>(); +} + +std::vector<int> GLCanvas3DManager::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx) +{ + if (model == nullptr) + return std::vector<int>(); + + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->load_object(*model, obj_idx) : std::vector<int>(); +} + +void GLCanvas3DManager::reload_scene(wxGLCanvas* canvas, bool force) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->reload_scene(force); +} + +void GLCanvas3DManager::load_print_toolpaths(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->load_print_toolpaths(); +} + +void GLCanvas3DManager::load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& tool_colors) +{ + if (print_object == nullptr) + return; + + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->load_print_object_toolpaths(*print_object, tool_colors); +} + +void GLCanvas3DManager::load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->load_wipe_tower_toolpaths(str_tool_colors); +} + +void GLCanvas3DManager::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors) +{ + if (preview_data == nullptr) + return; + + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->load_gcode_preview(*preview_data, str_tool_colors); +} + +void GLCanvas3DManager::register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_viewport_changed_callback(callback); +} + +void GLCanvas3DManager::register_on_double_click_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_double_click_callback(callback); +} + +void GLCanvas3DManager::register_on_right_click_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_right_click_callback(callback); +} + +void GLCanvas3DManager::register_on_select_object_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_select_object_callback(callback); +} + +void GLCanvas3DManager::register_on_model_update_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_model_update_callback(callback); +} + +void GLCanvas3DManager::register_on_remove_object_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_remove_object_callback(callback); +} + +void GLCanvas3DManager::register_on_arrange_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_arrange_callback(callback); +} + +void GLCanvas3DManager::register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_rotate_object_left_callback(callback); +} + +void GLCanvas3DManager::register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_rotate_object_right_callback(callback); +} + +void GLCanvas3DManager::register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_scale_object_uniformly_callback(callback); +} + +void GLCanvas3DManager::register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_increase_objects_callback(callback); +} + +void GLCanvas3DManager::register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_decrease_objects_callback(callback); +} + +void GLCanvas3DManager::register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_instance_moved_callback(callback); +} + +void GLCanvas3DManager::register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_wipe_tower_moved_callback(callback); +} + +void GLCanvas3DManager::register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_enable_action_buttons_callback(callback); +} + +void GLCanvas3DManager::register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_gizmo_scale_uniformly_callback(callback); +} + +GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) +{ + return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas); +} + +GLCanvas3DManager::CanvasesMap::const_iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) const +{ + return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas); +} + +bool GLCanvas3DManager::_init(GLCanvas3D& canvas) +{ + if (!m_gl_initialized) + init_gl(); + + return canvas.init(m_use_VBOs, m_use_legacy_opengl); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp new file mode 100644 index 000000000..9d9285601 --- /dev/null +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -0,0 +1,166 @@ +#ifndef slic3r_GLCanvas3DManager_hpp_ +#define slic3r_GLCanvas3DManager_hpp_ + +#include "../../libslic3r/BoundingBox.hpp" + +#include <map> +#include <vector> + +class wxGLCanvas; +class wxGLContext; + +namespace Slic3r { + +class DynamicPrintConfig; +class Print; +class Model; +class ExPolygon; +typedef std::vector<ExPolygon> ExPolygons; +class ModelObject; +class PrintObject; +class GCodePreviewData; + +namespace GUI { + +class GLCanvas3D; + +class GLCanvas3DManager +{ + struct GLInfo + { + std::string version; + std::string glsl_version; + std::string vendor; + std::string renderer; + + GLInfo(); + + void detect(); + bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const; + + std::string to_string(bool format_as_html, bool extensions) const; + }; + + typedef std::map<wxGLCanvas*, GLCanvas3D*> CanvasesMap; + + CanvasesMap m_canvases; + wxGLCanvas* m_current; + GLInfo m_gl_info; + bool m_gl_initialized; + bool m_use_legacy_opengl; + bool m_use_VBOs; + +public: + 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_as_dirty(wxGLCanvas* canvas); + + unsigned int get_volumes_count(wxGLCanvas* canvas) const; + void reset_volumes(wxGLCanvas* canvas); + void deselect_volumes(wxGLCanvas* canvas); + void select_volume(wxGLCanvas* canvas, unsigned int id); + void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections); + bool check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const; + bool move_volume_up(wxGLCanvas* canvas, unsigned int id); + bool move_volume_down(wxGLCanvas* canvas, unsigned int id); + + void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections); + + void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config); + void set_print(wxGLCanvas* canvas, Print* print); + void set_model(wxGLCanvas* canvas, Model* model); + + void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape); + void set_auto_bed_shape(wxGLCanvas* canvas); + + BoundingBoxf3 get_volumes_bounding_box(wxGLCanvas* canvas); + + void set_axes_length(wxGLCanvas* canvas, float length); + + void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons); + + void set_color_by(wxGLCanvas* canvas, const std::string& value); + void set_select_by(wxGLCanvas* canvas, const std::string& value); + void set_drag_by(wxGLCanvas* canvas, const std::string& value); + + bool is_layers_editing_enabled(wxGLCanvas* canvas) const; + bool is_layers_editing_allowed(wxGLCanvas* canvas) const; + bool is_shader_enabled(wxGLCanvas* canvas) const; + + bool is_reload_delayed(wxGLCanvas* canvas) const; + + void enable_layers_editing(wxGLCanvas* canvas, bool enable); + void enable_warning_texture(wxGLCanvas* canvas, bool enable); + void enable_legend_texture(wxGLCanvas* canvas, bool enable); + void enable_picking(wxGLCanvas* canvas, bool enable); + void enable_moving(wxGLCanvas* canvas, bool enable); + void enable_gizmos(wxGLCanvas* canvas, bool enable); + void enable_shader(wxGLCanvas* canvas, bool enable); + void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable); + void allow_multisample(wxGLCanvas* canvas, bool allow); + + void zoom_to_bed(wxGLCanvas* canvas); + void zoom_to_volumes(wxGLCanvas* canvas); + void select_view(wxGLCanvas* canvas, const std::string& direction); + void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other); + + void update_volumes_colors_by_extruder(wxGLCanvas* canvas); + + void render(wxGLCanvas* canvas) const; + + std::vector<double> get_current_print_zs(wxGLCanvas* canvas, bool active_only) const; + void set_toolpaths_range(wxGLCanvas* canvas, double low, double high); + + std::vector<int> load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs); + std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx); + + void reload_scene(wxGLCanvas* canvas, bool force); + + void load_print_toolpaths(wxGLCanvas* canvas); + void load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& tool_colors); + void load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors); + void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors); + + void register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback); + void register_on_double_click_callback(wxGLCanvas* canvas, void* callback); + void register_on_right_click_callback(wxGLCanvas* canvas, void* callback); + void register_on_select_object_callback(wxGLCanvas* canvas, void* callback); + void register_on_model_update_callback(wxGLCanvas* canvas, void* callback); + void register_on_remove_object_callback(wxGLCanvas* canvas, void* callback); + void register_on_arrange_callback(wxGLCanvas* canvas, void* callback); + void register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback); + void register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback); + void register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback); + void register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback); + void register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback); + void register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback); + void register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback); + void register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback); + void register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback); + +private: + CanvasesMap::iterator _get_canvas(wxGLCanvas* canvas); + CanvasesMap::const_iterator _get_canvas(wxGLCanvas* canvas) const; + + bool _init(GLCanvas3D& canvas); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLCanvas3DManager_hpp_ diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp new file mode 100644 index 000000000..d3aae33e8 --- /dev/null +++ b/xs/src/slic3r/GUI/GLGizmo.cpp @@ -0,0 +1,454 @@ +#include "GLGizmo.hpp" + +#include "../../libslic3r/Utils.hpp" +#include "../../libslic3r/BoundingBox.hpp" + +#include <GL/glew.h> + +#include <iostream> + +namespace Slic3r { +namespace GUI { + +const float GLGizmoBase::Grabber::HalfSize = 2.0f; +const float GLGizmoBase::Grabber::HoverOffset = 0.5f; +const float GLGizmoBase::BaseColor[3] = { 1.0f, 1.0f, 1.0f }; +const float GLGizmoBase::HighlightColor[3] = { 1.0f, 0.38f, 0.0f }; + +GLGizmoBase::Grabber::Grabber() + : center(Pointf(0.0, 0.0)) + , angle_z(0.0f) +{ + color[0] = 1.0f; + color[1] = 1.0f; + color[2] = 1.0f; +} + +void GLGizmoBase::Grabber::render(bool hover) const +{ + float min_x = -HalfSize; + float max_x = +HalfSize; + float min_y = -HalfSize; + float max_y = +HalfSize; + + ::glColor3f((GLfloat)color[0], (GLfloat)color[1], (GLfloat)color[2]); + + float angle_z_in_deg = angle_z * 180.0f / (float)PI; + ::glPushMatrix(); + ::glTranslatef((GLfloat)center.x, (GLfloat)center.y, 0.0f); + ::glRotatef((GLfloat)angle_z_in_deg, 0.0f, 0.0f, 1.0f); + + ::glDisable(GL_CULL_FACE); + ::glBegin(GL_TRIANGLES); + ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f); + ::glVertex3f((GLfloat)max_x, (GLfloat)min_y, 0.0f); + ::glVertex3f((GLfloat)max_x, (GLfloat)max_y, 0.0f); + ::glVertex3f((GLfloat)max_x, (GLfloat)max_y, 0.0f); + ::glVertex3f((GLfloat)min_x, (GLfloat)max_y, 0.0f); + ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f); + ::glEnd(); + ::glEnable(GL_CULL_FACE); + + if (hover) + { + min_x -= HoverOffset; + max_x += HoverOffset; + min_y -= HoverOffset; + max_y += HoverOffset; + + ::glBegin(GL_LINE_LOOP); + ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f); + ::glVertex3f((GLfloat)max_x, (GLfloat)min_y, 0.0f); + ::glVertex3f((GLfloat)max_x, (GLfloat)max_y, 0.0f); + ::glVertex3f((GLfloat)min_x, (GLfloat)max_y, 0.0f); + ::glEnd(); + } + + ::glPopMatrix(); +} + +GLGizmoBase::GLGizmoBase() + : m_state(Off) + , m_hover_id(-1) +{ +} + +GLGizmoBase::~GLGizmoBase() +{ +} + +bool GLGizmoBase::init() +{ + return on_init(); +} + +GLGizmoBase::EState GLGizmoBase::get_state() const +{ + return m_state; +} + +void GLGizmoBase::set_state(GLGizmoBase::EState state) +{ + m_state = state; +} + +unsigned int GLGizmoBase::get_textures_id() const +{ + return m_textures[m_state].get_id(); +} + +int GLGizmoBase::get_textures_size() const +{ + return m_textures[Off].get_width(); +} + +int GLGizmoBase::get_hover_id() const +{ + return m_hover_id; +} + +void GLGizmoBase::set_hover_id(int id) +{ + if (id < (int)m_grabbers.size()) + m_hover_id = id; +} + +void GLGizmoBase::start_dragging() +{ + on_start_dragging(); +} + +void GLGizmoBase::update(const Pointf& mouse_pos) +{ + if (m_hover_id != -1) + on_update(mouse_pos); +} + +void GLGizmoBase::render(const BoundingBoxf3& box) const +{ + on_render(box); +} + +void GLGizmoBase::render_for_picking(const BoundingBoxf3& box) const +{ + on_render_for_picking(box); +} + +void GLGizmoBase::on_start_dragging() +{ +} + +void GLGizmoBase::render_grabbers() const +{ + for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) + { + m_grabbers[i].render(m_hover_id == i); + } +} + +const float GLGizmoRotate::Offset = 5.0f; +const unsigned int GLGizmoRotate::CircleResolution = 64; +const unsigned int GLGizmoRotate::AngleResolution = 64; +const unsigned int GLGizmoRotate::ScaleStepsCount = 60; +const float GLGizmoRotate::ScaleStepRad = 2.0f * (float)PI / GLGizmoRotate::ScaleStepsCount; +const unsigned int GLGizmoRotate::ScaleLongEvery = 5; +const float GLGizmoRotate::ScaleLongTooth = 2.0f; +const float GLGizmoRotate::ScaleShortTooth = 1.0f; +const unsigned int GLGizmoRotate::SnapRegionsCount = 8; +const float GLGizmoRotate::GrabberOffset = 5.0f; + +GLGizmoRotate::GLGizmoRotate() + : GLGizmoBase() + , m_angle_z(0.0f) + , m_center(Pointf(0.0, 0.0)) + , m_radius(0.0f) +{ +} + +bool GLGizmoRotate::on_init() +{ + std::string path = resources_dir() + "/icons/overlay/"; + + std::string filename = path + "rotate_off.png"; + if (!m_textures[Off].load_from_file(filename, false)) + return false; + + filename = path + "rotate_hover.png"; + if (!m_textures[Hover].load_from_file(filename, false)) + return false; + + filename = path + "rotate_on.png"; + if (!m_textures[On].load_from_file(filename, false)) + return false; + + m_grabbers.push_back(Grabber()); + + return true; +} + +void GLGizmoRotate::on_update(const Pointf& mouse_pos) +{ + Vectorf orig_dir(1.0, 0.0); + Vectorf new_dir = normalize(mouse_pos - m_center); + coordf_t theta = ::acos(clamp(-1.0, 1.0, dot(new_dir, orig_dir))); + if (cross(orig_dir, new_dir) < 0.0) + theta = 2.0 * (coordf_t)PI - theta; + + if (length(m_center.vector_to(mouse_pos)) < 2.0 * (double)m_radius / 3.0) + { + coordf_t step = 2.0 * (coordf_t)PI / (coordf_t)SnapRegionsCount; + theta = step * (coordf_t)std::round(theta / step); + } + + if (theta == 2.0 * (coordf_t)PI) + theta = 0.0; + + m_angle_z = (float)theta; +} + +void GLGizmoRotate::on_render(const BoundingBoxf3& box) const +{ + ::glDisable(GL_LIGHTING); + ::glDisable(GL_DEPTH_TEST); + + const Pointf3& size = box.size(); + m_center = box.center(); + m_radius = Offset + ::sqrt(sqr(0.5f * size.x) + sqr(0.5f * size.y)); + + ::glLineWidth(2.0f); + ::glColor3fv(BaseColor); + + _render_circle(); + _render_scale(); + _render_snap_radii(); + _render_reference_radius(); + + ::glColor3fv(HighlightColor); + _render_angle_z(); + _render_grabber(); +} + +void GLGizmoRotate::on_render_for_picking(const BoundingBoxf3& box) const +{ + ::glDisable(GL_LIGHTING); + ::glDisable(GL_DEPTH_TEST); + + m_grabbers[0].color[0] = 1.0f; + m_grabbers[0].color[1] = 1.0f; + m_grabbers[0].color[2] = 254.0f / 255.0f; + render_grabbers(); +} + +void GLGizmoRotate::_render_circle() const +{ + ::glBegin(GL_LINE_LOOP); + for (unsigned int i = 0; i < ScaleStepsCount; ++i) + { + float angle = (float)i * ScaleStepRad; + float x = m_center.x + ::cos(angle) * m_radius; + float y = m_center.y + ::sin(angle) * m_radius; + ::glVertex3f((GLfloat)x, (GLfloat)y, 0.0f); + } + ::glEnd(); +} + +void GLGizmoRotate::_render_scale() const +{ + float out_radius_long = m_radius + ScaleLongTooth; + float out_radius_short = m_radius + ScaleShortTooth; + + ::glBegin(GL_LINES); + for (unsigned int i = 0; i < ScaleStepsCount; ++i) + { + float angle = (float)i * ScaleStepRad; + float cosa = ::cos(angle); + float sina = ::sin(angle); + float in_x = m_center.x + cosa * m_radius; + float in_y = m_center.y + sina * m_radius; + float out_x = (i % ScaleLongEvery == 0) ? m_center.x + cosa * out_radius_long : m_center.x + cosa * out_radius_short; + float out_y = (i % ScaleLongEvery == 0) ? m_center.y + sina * out_radius_long : m_center.y + sina * out_radius_short; + ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, 0.0f); + ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, 0.0f); + } + ::glEnd(); +} + +void GLGizmoRotate::_render_snap_radii() const +{ + float step = 2.0f * (float)PI / (float)SnapRegionsCount; + + float in_radius = m_radius / 3.0f; + float out_radius = 2.0f * in_radius; + + ::glBegin(GL_LINES); + for (unsigned int i = 0; i < SnapRegionsCount; ++i) + { + float angle = (float)i * step; + float cosa = ::cos(angle); + float sina = ::sin(angle); + float in_x = m_center.x + cosa * in_radius; + float in_y = m_center.y + sina * in_radius; + float out_x = m_center.x + cosa * out_radius; + float out_y = m_center.y + sina * out_radius; + ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, 0.0f); + ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, 0.0f); + } + ::glEnd(); +} + +void GLGizmoRotate::_render_reference_radius() const +{ + ::glBegin(GL_LINES); + ::glVertex3f((GLfloat)m_center.x, (GLfloat)m_center.y, 0.0f); + ::glVertex3f((GLfloat)m_center.x + m_radius + GrabberOffset, (GLfloat)m_center.y, 0.0f); + ::glEnd(); +} + +void GLGizmoRotate::_render_angle_z() const +{ + float step_angle = m_angle_z / AngleResolution; + float ex_radius = m_radius + GrabberOffset; + + ::glBegin(GL_LINE_STRIP); + for (unsigned int i = 0; i <= AngleResolution; ++i) + { + float angle = (float)i * step_angle; + float x = m_center.x + ::cos(angle) * ex_radius; + float y = m_center.y + ::sin(angle) * ex_radius; + ::glVertex3f((GLfloat)x, (GLfloat)y, 0.0f); + } + ::glEnd(); +} + +void GLGizmoRotate::_render_grabber() const +{ + float grabber_radius = m_radius + GrabberOffset; + m_grabbers[0].center.x = m_center.x + ::cos(m_angle_z) * grabber_radius; + m_grabbers[0].center.y = m_center.y + ::sin(m_angle_z) * grabber_radius; + m_grabbers[0].angle_z = m_angle_z; + + ::glColor3fv(BaseColor); + ::glBegin(GL_LINES); + ::glVertex3f((GLfloat)m_center.x, (GLfloat)m_center.y, 0.0f); + ::glVertex3f((GLfloat)m_grabbers[0].center.x, (GLfloat)m_grabbers[0].center.y, 0.0f); + ::glEnd(); + + ::memcpy((void*)m_grabbers[0].color, (const void*)HighlightColor, 3 * sizeof(float)); + render_grabbers(); +} + +const float GLGizmoScale::Offset = 5.0f; + +GLGizmoScale::GLGizmoScale() + : GLGizmoBase() + , m_scale(1.0f) + , m_starting_scale(1.0f) +{ +} + +float GLGizmoScale::get_scale() const +{ + return m_scale; +} + +void GLGizmoScale::set_scale(float scale) +{ + m_starting_scale = scale; +} + +bool GLGizmoScale::on_init() +{ + std::string path = resources_dir() + "/icons/overlay/"; + + std::string filename = path + "scale_off.png"; + if (!m_textures[Off].load_from_file(filename, false)) + return false; + + filename = path + "scale_hover.png"; + if (!m_textures[Hover].load_from_file(filename, false)) + return false; + + filename = path + "scale_on.png"; + if (!m_textures[On].load_from_file(filename, false)) + return false; + + for (unsigned int i = 0; i < 4; ++i) + { + m_grabbers.push_back(Grabber()); + } + + return true; +} + +void GLGizmoScale::on_start_dragging() +{ + if (m_hover_id != -1) + m_starting_drag_position = m_grabbers[m_hover_id].center; +} + +void GLGizmoScale::on_update(const Pointf& mouse_pos) +{ + Pointf center(0.5 * (m_grabbers[1].center.x + m_grabbers[0].center.x), 0.5 * (m_grabbers[3].center.y + m_grabbers[0].center.y)); + + coordf_t orig_len = length(m_starting_drag_position - center); + coordf_t new_len = length(mouse_pos - center); + coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0; + + m_scale = m_starting_scale * (float)ratio; +} + +void GLGizmoScale::on_render(const BoundingBoxf3& box) const +{ + ::glDisable(GL_LIGHTING); + ::glDisable(GL_DEPTH_TEST); + + coordf_t min_x = box.min.x - (coordf_t)Offset; + coordf_t max_x = box.max.x + (coordf_t)Offset; + coordf_t min_y = box.min.y - (coordf_t)Offset; + coordf_t max_y = box.max.y + (coordf_t)Offset; + + m_grabbers[0].center.x = min_x; + m_grabbers[0].center.y = min_y; + m_grabbers[1].center.x = max_x; + m_grabbers[1].center.y = min_y; + m_grabbers[2].center.x = max_x; + m_grabbers[2].center.y = max_y; + m_grabbers[3].center.x = min_x; + m_grabbers[3].center.y = max_y; + + ::glLineWidth(2.0f); + ::glColor3fv(BaseColor); + // draw outline + ::glBegin(GL_LINE_LOOP); + for (unsigned int i = 0; i < 4; ++i) + { + ::glVertex3f((GLfloat)m_grabbers[i].center.x, (GLfloat)m_grabbers[i].center.y, 0.0f); + } + ::glEnd(); + + // draw grabbers + for (unsigned int i = 0; i < 4; ++i) + { + ::memcpy((void*)m_grabbers[i].color, (const void*)HighlightColor, 3 * sizeof(float)); + } + render_grabbers(); +} + +void GLGizmoScale::on_render_for_picking(const BoundingBoxf3& box) const +{ + static const GLfloat INV_255 = 1.0f / 255.0f; + + ::glDisable(GL_LIGHTING); + ::glDisable(GL_DEPTH_TEST); + + for (unsigned int i = 0; i < 4; ++i) + { + m_grabbers[i].color[0] = 1.0f; + m_grabbers[i].color[1] = 1.0f; + m_grabbers[i].color[2] = (254.0f - (float)i) * INV_255; + } + render_grabbers(); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp new file mode 100644 index 000000000..2baec8f9b --- /dev/null +++ b/xs/src/slic3r/GUI/GLGizmo.hpp @@ -0,0 +1,145 @@ +#ifndef slic3r_GLGizmo_hpp_ +#define slic3r_GLGizmo_hpp_ + +#include "../../slic3r/GUI/GLTexture.hpp" +#include "../../libslic3r/Point.hpp" + +#include <vector> + +namespace Slic3r { + +class BoundingBoxf3; +class Pointf3; + +namespace GUI { + +class GLGizmoBase +{ +protected: + static const float BaseColor[3]; + static const float HighlightColor[3]; + + struct Grabber + { + static const float HalfSize; + static const float HoverOffset; + + Pointf center; + float angle_z; + float color[3]; + + Grabber(); + void render(bool hover) const; + }; + +public: + enum EState + { + Off, + Hover, + On, + Num_States + }; + +protected: + EState m_state; + // textures are assumed to be square and all with the same size in pixels, no internal check is done + GLTexture m_textures[Num_States]; + int m_hover_id; + mutable std::vector<Grabber> m_grabbers; + +public: + GLGizmoBase(); + virtual ~GLGizmoBase(); + + bool init(); + + EState get_state() const; + void set_state(EState state); + + unsigned int get_textures_id() const; + int get_textures_size() const; + + int get_hover_id() const; + void set_hover_id(int id); + + void start_dragging(); + void update(const Pointf& mouse_pos); + + void render(const BoundingBoxf3& box) const; + void render_for_picking(const BoundingBoxf3& box) const; + +protected: + virtual bool on_init() = 0; + virtual void on_start_dragging(); + virtual void on_update(const Pointf& mouse_pos) = 0; + virtual void on_render(const BoundingBoxf3& box) const = 0; + virtual void on_render_for_picking(const BoundingBoxf3& box) const = 0; + + void render_grabbers() const; +}; + +class GLGizmoRotate : public GLGizmoBase +{ + static const float Offset; + static const unsigned int CircleResolution; + static const unsigned int AngleResolution; + static const unsigned int ScaleStepsCount; + static const float ScaleStepRad; + static const unsigned int ScaleLongEvery; + static const float ScaleLongTooth; + static const float ScaleShortTooth; + static const unsigned int SnapRegionsCount; + static const float GrabberOffset; + + float m_angle_z; + + mutable Pointf m_center; + mutable float m_radius; + +public: + GLGizmoRotate(); + +protected: + virtual bool on_init(); + virtual void on_update(const Pointf& mouse_pos); + virtual void on_render(const BoundingBoxf3& box) const; + virtual void on_render_for_picking(const BoundingBoxf3& box) const; + +private: + void _render_circle() const; + void _render_scale() const; + void _render_snap_radii() const; + void _render_reference_radius() const; + void _render_angle_z() const; + void _render_grabber() const; +}; + +class GLGizmoScale : public GLGizmoBase +{ + static const float Offset; + + float m_scale; + + Pointf m_starting_drag_position; + float m_starting_scale; + +public: + GLGizmoScale(); + + float get_scale() const; + void set_scale(float scale); + +protected: + virtual bool on_init(); + virtual void on_start_dragging(); + virtual void on_update(const Pointf& mouse_pos); + virtual void on_render(const BoundingBoxf3& box) const; + virtual void on_render_for_picking(const BoundingBoxf3& box) const; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmo_hpp_ + diff --git a/xs/src/slic3r/GUI/GLShader.cpp b/xs/src/slic3r/GUI/GLShader.cpp index ce9a80f05..903f6c347 100644 --- a/xs/src/slic3r/GUI/GLShader.cpp +++ b/xs/src/slic3r/GUI/GLShader.cpp @@ -2,6 +2,9 @@ #include "GLShader.hpp" +#include "../../libslic3r/Utils.hpp" +#include <boost/nowide/fstream.hpp> + #include <string> #include <utility> #include <assert.h> @@ -22,7 +25,7 @@ inline std::string gl_get_string_safe(GLenum param) return std::string(value ? value : "N/A"); } -bool GLShader::load(const char *fragment_shader, const char *vertex_shader) +bool GLShader::load_from_text(const char *fragment_shader, const char *vertex_shader) { std::string gl_version = gl_get_string_safe(GL_VERSION); int major = atoi(gl_version.c_str()); @@ -123,6 +126,41 @@ bool GLShader::load(const char *fragment_shader, const char *vertex_shader) return true; } +bool GLShader::load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename) +{ + const std::string& path = resources_dir() + "/shaders/"; + + boost::nowide::ifstream vs(path + std::string(vertex_shader_filename), boost::nowide::ifstream::binary); + if (!vs.good()) + return false; + + vs.seekg(0, vs.end); + int file_length = vs.tellg(); + vs.seekg(0, vs.beg); + std::string vertex_shader(file_length, '\0'); + vs.read(const_cast<char*>(vertex_shader.data()), file_length); + if (!vs.good()) + return false; + + vs.close(); + + boost::nowide::ifstream fs(path + std::string(fragment_shader_filename), boost::nowide::ifstream::binary); + if (!fs.good()) + return false; + + fs.seekg(0, fs.end); + file_length = fs.tellg(); + fs.seekg(0, fs.beg); + std::string fragment_shader(file_length, '\0'); + fs.read(const_cast<char*>(fragment_shader.data()), file_length); + if (!fs.good()) + return false; + + fs.close(); + + return load_from_text(fragment_shader.c_str(), vertex_shader.c_str()); +} + void GLShader::release() { if (this->shader_program_id) { diff --git a/xs/src/slic3r/GUI/GLShader.hpp b/xs/src/slic3r/GUI/GLShader.hpp index d91463f19..032640d8d 100644 --- a/xs/src/slic3r/GUI/GLShader.hpp +++ b/xs/src/slic3r/GUI/GLShader.hpp @@ -16,7 +16,9 @@ public: {} ~GLShader(); - bool load(const char *fragment_shader, const char *vertex_shader); + bool load_from_text(const char *fragment_shader, const char *vertex_shader); + bool load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename); + void release(); int get_attrib_location(const char *name) const; diff --git a/xs/src/slic3r/GUI/GLTexture.cpp b/xs/src/slic3r/GUI/GLTexture.cpp new file mode 100644 index 000000000..924920bd8 --- /dev/null +++ b/xs/src/slic3r/GUI/GLTexture.cpp @@ -0,0 +1,190 @@ +#include "GLTexture.hpp" + +#include <GL/glew.h> + +#include <wx/image.h> + +#include <boost/filesystem.hpp> + +#include <vector> +#include <algorithm> + +namespace Slic3r { +namespace GUI { + +GLTexture::GLTexture() + : m_id(0) + , m_width(0) + , m_height(0) + , m_source("") +{ +} + +GLTexture::~GLTexture() +{ + reset(); +} + +bool GLTexture::load_from_file(const std::string& filename, bool generate_mipmaps) +{ + reset(); + + if (!boost::filesystem::exists(filename)) + return false; + + // Load a PNG with an alpha channel. + wxImage image; + if (!image.LoadFile(filename, wxBITMAP_TYPE_PNG)) + { + reset(); + return false; + } + + m_width = image.GetWidth(); + m_height = image.GetHeight(); + int n_pixels = m_width * m_height; + + if (n_pixels <= 0) + { + reset(); + return false; + } + + // Get RGB & alpha raw data from wxImage, pack them into an array. + unsigned char* img_rgb = image.GetData(); + if (img_rgb == nullptr) + { + reset(); + return false; + } + + unsigned char* img_alpha = image.GetAlpha(); + + std::vector<unsigned char> data(n_pixels * 4, 0); + for (int i = 0; i < n_pixels; ++i) + { + int data_id = i * 4; + int img_id = i * 3; + data[data_id + 0] = img_rgb[img_id + 0]; + data[data_id + 1] = img_rgb[img_id + 1]; + data[data_id + 2] = img_rgb[img_id + 2]; + data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255; + } + + // sends data to gpu + ::glGenTextures(1, &m_id); + ::glBindTexture(GL_TEXTURE_2D, m_id); + ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); + if (generate_mipmaps) + { + // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards + _generate_mipmaps(image); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } + else + { + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + } + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + ::glBindTexture(GL_TEXTURE_2D, 0); + + m_source = filename; + return true; +} + +void GLTexture::reset() +{ + if (m_id != 0) + ::glDeleteTextures(1, &m_id); + + m_id = 0; + m_width = 0; + m_height = 0; + m_source = ""; +} + +unsigned int GLTexture::get_id() const +{ + return m_id; +} + +int GLTexture::get_width() const +{ + return m_width; +} + +int GLTexture::get_height() const +{ + return m_height; +} + +const std::string& GLTexture::get_source() const +{ + return m_source; +} + +void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top) +{ + ::glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + + ::glDisable(GL_LIGHTING); + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + ::glEnable(GL_TEXTURE_2D); + + ::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id); + + ::glBegin(GL_QUADS); + ::glTexCoord2d(0.0f, 1.0f); ::glVertex3f(left, bottom, 0.0f); + ::glTexCoord2d(1.0f, 1.0f); ::glVertex3f(right, bottom, 0.0f); + ::glTexCoord2d(1.0f, 0.0f); ::glVertex3f(right, top, 0.0f); + ::glTexCoord2d(0.0f, 0.0f); ::glVertex3f(left, top, 0.0f); + ::glEnd(); + + ::glBindTexture(GL_TEXTURE_2D, 0); + + ::glDisable(GL_TEXTURE_2D); + ::glDisable(GL_BLEND); + ::glEnable(GL_LIGHTING); +} + +void GLTexture::_generate_mipmaps(wxImage& image) +{ + int w = image.GetWidth(); + int h = image.GetHeight(); + GLint level = 0; + std::vector<unsigned char> data(w * h * 4, 0); + + while ((w > 1) && (h > 1)) + { + ++level; + + w = std::max(w / 2, 1); + h = std::max(h / 2, 1); + + int n_pixels = w * h; + + image = image.ResampleBicubic(w, h); + + unsigned char* img_rgb = image.GetData(); + unsigned char* img_alpha = image.GetAlpha(); + + data.resize(n_pixels * 4); + for (int i = 0; i < n_pixels; ++i) + { + int data_id = i * 4; + int img_id = i * 3; + data[data_id + 0] = img_rgb[img_id + 0]; + data[data_id + 1] = img_rgb[img_id + 1]; + data[data_id + 2] = img_rgb[img_id + 2]; + data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255; + } + + ::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); + } +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/GLTexture.hpp b/xs/src/slic3r/GUI/GLTexture.hpp new file mode 100644 index 000000000..70480c605 --- /dev/null +++ b/xs/src/slic3r/GUI/GLTexture.hpp @@ -0,0 +1,41 @@ +#ifndef slic3r_GLTexture_hpp_ +#define slic3r_GLTexture_hpp_ + +#include <string> + +class wxImage; + +namespace Slic3r { +namespace GUI { + + class GLTexture + { + private: + unsigned int m_id; + int m_width; + int m_height; + std::string m_source; + + public: + GLTexture(); + ~GLTexture(); + + bool load_from_file(const std::string& filename, bool generate_mipmaps); + void reset(); + + unsigned int get_id() const; + int get_width() const; + int get_height() const; + const std::string& get_source() const; + + static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); + + private: + void _generate_mipmaps(wxImage& image); + }; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLTexture_hpp_ + diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 974c554b6..12af36d19 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -56,6 +56,7 @@ #include "../Utils/PresetUpdater.hpp" #include "../Config/Snapshot.hpp" +#include "3DScene.hpp" namespace Slic3r { namespace GUI { @@ -316,10 +317,11 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l auto local_menu = new wxMenu(); wxWindowID config_id_base = wxWindow::NewControlId((int)ConfigMenuCnt); - const auto config_wizard_tooltip = wxString::Format(_(L("Run %s")), ConfigWizard::name()); + auto config_wizard_name = _(ConfigWizard::name().wx_str()); + const auto config_wizard_tooltip = wxString::Format(_(L("Run %s")), config_wizard_name); // Cmd+, is standard on OS X - what about other operating systems? - local_menu->Append(config_id_base + ConfigMenuWizard, ConfigWizard::name() + dots, config_wizard_tooltip); - local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots"))+dots, _(L("Inspect / activate configuration snapshots"))); + local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); + local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots"))+dots, _(L("Inspect / activate configuration snapshots"))); local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _(L("Take Configuration Snapshot")), _(L("Capture a configuration snapshot"))); // local_menu->Append(config_id_base + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates"))); local_menu->AppendSeparator(); @@ -378,6 +380,7 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l save_language(); show_info(g_wxTabPanel, _(L("Application will be restarted")), _(L("Attention!"))); if (event_language_change > 0) { + _3DScene::remove_all_canvases();// remove all canvas before recreate GUI wxCommandEvent event(event_language_change); g_wxApp->ProcessEvent(event); } @@ -423,7 +426,7 @@ bool check_unsaved_changes() bool config_wizard_startup(bool app_config_exists) { - if (! app_config_exists || g_PresetBundle->has_defauls_only()) { + if (! app_config_exists || g_PresetBundle->printers.size() <= 1) { config_wizard(ConfigWizard::RR_DATA_EMPTY); return true; } else if (g_AppConfig->legacy_datadir()) { diff --git a/xs/src/slic3r/GUI/MsgDialog.hpp b/xs/src/slic3r/GUI/MsgDialog.hpp index 2d570a0bf..ca349eb5c 100644 --- a/xs/src/slic3r/GUI/MsgDialog.hpp +++ b/xs/src/slic3r/GUI/MsgDialog.hpp @@ -28,6 +28,8 @@ struct MsgDialog : wxDialog MsgDialog &operator=(const MsgDialog &) = delete; virtual ~MsgDialog(); + // TODO: refactor with CreateStdDialogButtonSizer usage + protected: enum { CONTENT_WIDTH = 500, diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 57659d03d..629a9f3a0 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -150,8 +150,15 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // Build a label if we have it wxStaticText* label=nullptr; if (label_width != 0) { + long label_style = staticbox ? 0 : wxALIGN_RIGHT; +#ifdef __WXGTK__ + // workaround for correct text align of the StaticBox on Linux + // flags wxALIGN_RIGHT and wxALIGN_CENTRE don't work when Ellipsize flags are _not_ given. + // Text is properly aligned only when Ellipsize is checked. + label_style |= staticbox ? 0 : wxST_ELLIPSIZE_END; +#endif /* __WXGTK__ */ label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ":"), - wxDefaultPosition, wxSize(label_width, -1), staticbox ? 0 : wxALIGN_RIGHT); + wxDefaultPosition, wxSize(label_width, -1), label_style); label->SetFont(label_font); label->Wrap(label_width); // avoid a Linux/GTK bug grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5); diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index d36ef7b6f..0a280eee1 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -264,36 +264,38 @@ void PresetBundle::load_selections(const AppConfig &config) this->load_installed_printers(config); // Parse the initial print / filament / printer profile names. - std::string initial_print_profile_name = remove_ini_suffix(config.get("presets", "print")); - std::vector<std::string> initial_filament_profile_names; - std::string initial_printer_profile_name = remove_ini_suffix(config.get("presets", "printer")); + std::string initial_print_profile_name = remove_ini_suffix(config.get("presets", "print")); + std::string initial_filament_profile_name = remove_ini_suffix(config.get("presets", "filament")); + std::string initial_printer_profile_name = remove_ini_suffix(config.get("presets", "printer")); + // Activate print / filament / printer profiles from the config. + // If the printer profile enumerated by the config are not visible, select an alternate preset. + // Do not select alternate profiles for the print / filament profiles as those presets + // will be selected by the following call of this->update_compatible_with_printer(true). + prints.select_preset_by_name_strict(initial_print_profile_name); + filaments.select_preset_by_name_strict(initial_filament_profile_name); + printers.select_preset_by_name(initial_printer_profile_name, true); + + // Load the names of the other filament profiles selected for a multi-material printer. auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(printers.get_selected_preset().config.option("nozzle_diameter")); - size_t num_extruders = nozzle_diameter->values.size(); - initial_filament_profile_names.emplace_back(remove_ini_suffix(config.get("presets", "filament"))); - this->set_filament_preset(0, initial_filament_profile_names.back()); + size_t num_extruders = nozzle_diameter->values.size(); + this->filament_presets = { initial_filament_profile_name }; for (unsigned int i = 1; i < (unsigned int)num_extruders; ++ i) { char name[64]; sprintf(name, "filament_%d", i); if (! config.has("presets", name)) break; - initial_filament_profile_names.emplace_back(remove_ini_suffix(config.get("presets", name))); - this->set_filament_preset(i, initial_filament_profile_names.back()); + this->filament_presets.emplace_back(remove_ini_suffix(config.get("presets", name))); } - - // Activate print / filament / printer profiles from the config. - // If the printer profile enumerated by the config are not visible, select an alternate preset. - // Do not select alternate profiles for the print / filament profiles as those presets - // will be selected by the following call of this->update_compatible_with_printer(true). - prints.select_preset_by_name_strict(initial_print_profile_name); - filaments.select_preset_by_name_strict(initial_filament_profile_names.front()); - printers.select_preset_by_name(initial_printer_profile_name, true); + // Do not define the missing filaments, so that the update_compatible_with_printer() will use the preferred filaments. + this->filament_presets.resize(num_extruders, ""); // Update visibility of presets based on their compatibility with the active printer. // Always try to select a compatible print and filament preset to the current printer preset, // as the application may have been closed with an active "external" preset, which does not // exist. this->update_compatible_with_printer(true); + this->update_multi_material_filament_presets(); } // Export selections (current print, current filaments, current printer) into config.ini @@ -946,9 +948,7 @@ void PresetBundle::update_multi_material_filament_presets() for (size_t i = 0; i < std::min(this->filament_presets.size(), num_extruders); ++ i) this->filament_presets[i] = this->filaments.find_preset(this->filament_presets[i], true)->name; // Append the rest of filament presets. -// if (this->filament_presets.size() < num_extruders) - this->filament_presets.resize(num_extruders, this->filament_presets.empty() ? this->filaments.first_visible().name : this->filament_presets.back()); - + this->filament_presets.resize(num_extruders, this->filament_presets.empty() ? this->filaments.first_visible().name : this->filament_presets.back()); // Now verify if wiping_volumes_matrix has proper size (it is used to deduce number of extruders in wipe tower generator): std::vector<double> old_matrix = this->project_config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values; diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 6eabc2f47..9e0e4fc27 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -40,49 +40,30 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_preset_bundle = preset_bundle; // Vertical sizer to hold the choice menu and the rest of the page. +#ifdef __WXOSX__ + auto *main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->SetSizeHints(this); + this->SetSizer(main_sizer); + + // Create additional panel to Fit() it from OnActivate() + // It's needed for tooltip showing on OSX + m_tmp_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); + auto panel = m_tmp_panel; + auto sizer = new wxBoxSizer(wxVERTICAL); + m_tmp_panel->SetSizer(sizer); + m_tmp_panel->Layout(); + + main_sizer->Add(m_tmp_panel, 1, wxEXPAND | wxALL, 0); +#else Tab *panel = this; auto *sizer = new wxBoxSizer(wxVERTICAL); sizer->SetSizeHints(panel); panel->SetSizer(sizer); +#endif //__WXOSX__ // preset chooser m_presets_choice = new wxBitmapComboBox(panel, wxID_ANY, "", wxDefaultPosition, wxSize(270, -1), 0, 0,wxCB_READONLY); - /* - m_cc_presets_choice = new wxComboCtrl(panel, wxID_ANY, L(""), wxDefaultPosition, wxDefaultSize, wxCB_READONLY); - wxDataViewTreeCtrlComboPopup* popup = new wxDataViewTreeCtrlComboPopup; - if (popup != nullptr) - { - // FIXME If the following line is removed, the combo box popup list will not react to mouse clicks. - // On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10. -// m_cc_presets_choice->UseAltPopupWindow(); -// m_cc_presets_choice->EnablePopupAnimation(false); - m_cc_presets_choice->SetPopupControl(popup); - popup->SetStringValue(from_u8("Text1")); - - popup->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this, popup](wxCommandEvent& evt) - { - auto selected = popup->GetItemText(popup->GetSelection()); - if (selected != _(L("System presets")) && selected != _(L("Default presets"))) - { - m_cc_presets_choice->SetText(selected); - std::string selected_string = selected.ToUTF8().data(); -#ifdef __APPLE__ -#else - select_preset(selected_string); -#endif - } - }); - -// popup->Bind(wxEVT_KEY_DOWN, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); }); -// popup->Bind(wxEVT_KEY_UP, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); }); - - auto icons = new wxImageList(16, 16, true, 1); - popup->SetImageList(icons); - icons->Add(*new wxIcon(from_u8(Slic3r::var("flag-green-icon.png")), wxBITMAP_TYPE_PNG)); - icons->Add(*new wxIcon(from_u8(Slic3r::var("flag-red-icon.png")), wxBITMAP_TYPE_PNG)); - } -*/ auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); //buttons @@ -173,37 +154,6 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_hsizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_hsizer, 1, wxEXPAND, 0); - -/* - - - //temporary left vertical sizer - m_left_sizer = new wxBoxSizer(wxVERTICAL); - m_hsizer->Add(m_left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3); - - // tree - m_presetctrl = new wxDataViewTreeCtrl(panel, wxID_ANY, wxDefaultPosition, wxSize(200, -1), wxDV_NO_HEADER); - m_left_sizer->Add(m_presetctrl, 1, wxEXPAND); - m_preset_icons = new wxImageList(16, 16, true, 1); - m_presetctrl->SetImageList(m_preset_icons); - m_preset_icons->Add(*new wxIcon(from_u8(Slic3r::var("flag-green-icon.png")), wxBITMAP_TYPE_PNG)); - m_preset_icons->Add(*new wxIcon(from_u8(Slic3r::var("flag-red-icon.png")), wxBITMAP_TYPE_PNG)); - - m_presetctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxCommandEvent& evt) - { - auto selected = m_presetctrl->GetItemText(m_presetctrl->GetSelection()); - if (selected != _(L("System presets")) && selected != _(L("Default presets"))) - { - std::string selected_string = selected.ToUTF8().data(); -#ifdef __APPLE__ -#else - select_preset(selected_string); -#endif - } - }); - -*/ - //left vertical sizer m_left_sizer = new wxBoxSizer(wxVERTICAL); m_hsizer->Add(m_left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3); @@ -233,7 +183,7 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) return; if (selected_item >= 0){ std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); - if (selected_string.find_first_of("-------") == 0 + if (selected_string.find("-------") == 0 /*selected_string == "------- System presets -------" || selected_string == "------- User presets -------"*/){ m_presets_choice->SetSelection(m_selected_preset_item); @@ -279,7 +229,12 @@ PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bo } } // Initialize the page. - PageShp page(new Page(this, title, icon_idx)); +#ifdef __WXOSX__ + auto panel = m_tmp_panel; +#else + auto panel = this; +#endif + PageShp page(new Page(panel, title, icon_idx)); page->SetScrollbars(1, 1, 1, 1); page->Hide(); m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); @@ -290,6 +245,18 @@ PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bo return page; } +void Tab::OnActivate() +{ +#ifdef __WXOSX__ + wxWindowUpdateLocker noUpdates(this); + + auto size = GetSizer()->GetSize(); + m_tmp_panel->GetSizer()->SetMinSize(size.x + m_size_move, size.y); + Fit(); + m_size_move *= -1; +#endif // __WXOSX__ +} + void Tab::update_labels_colour() { Freeze(); @@ -487,8 +454,13 @@ void Tab::update_changed_tree_ui() get_sys_and_mod_flags(opt_key, sys_page, modified_page); } } - if (title == _("Dependencies") && name() != "printer"){ - get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); + if (title == _("Dependencies")){ + if (name() != "printer") + get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); + else { + sys_page = m_presets->get_selected_preset_parent() ? true:false; + modified_page = false; + } } for (auto group : page->m_optgroups) { @@ -1248,6 +1220,7 @@ void TabPrint::OnActivate() { m_recommended_thin_wall_thickness_description_line->SetText( from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); + Tab::OnActivate(); } void TabFilament::build() @@ -1405,6 +1378,7 @@ void TabFilament::update() void TabFilament::OnActivate() { m_volumetric_speed_description_line->SetText(from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle))); + Tab::OnActivate(); } wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticText) @@ -1518,7 +1492,7 @@ void TabPrinter::build() sizer->Add(btn); btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e){ - auto sender = new GCodeSender(); + auto sender = Slic3r::make_unique<GCodeSender>(); auto res = sender->connect( m_config->opt_string("serial_port"), m_config->opt_int("serial_speed") @@ -1894,6 +1868,8 @@ void Tab::load_current_preset() m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; + m_undo_to_sys_btn->Enable(!preset.is_default); + // use CallAfter because some field triggers schedule on_change calls using CallAfter, // and we don't want them to be called after this update_dirty() as they would mark the // preset dirty again @@ -2158,6 +2134,8 @@ void Tab::save_preset(std::string name /*= ""*/) update_tab_ui(); // Update the selection boxes at the platter. on_presets_changed(); + // If current profile is saved, "delete preset" button have to be enabled + m_btn_delete_preset->Enable(true); if (m_name == "printer") static_cast<TabPrinter*>(this)->m_initial_extruders_count = static_cast<TabPrinter*>(this)->m_extruders_count; @@ -2560,28 +2538,33 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la if (noncommon_label_width >= 0) optgroup->label_width = noncommon_label_width; - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ +#ifdef __WXOSX__ + auto tab = GetParent()->GetParent(); +#else + auto tab = GetParent(); +#endif + optgroup->m_on_change = [this, tab](t_config_option_key opt_key, boost::any value){ //! This function will be called from OptionGroup. //! Using of CallAfter is redundant. //! And in some cases it causes update() function to be recalled again //! wxTheApp->CallAfter([this, opt_key, value]() { - static_cast<Tab*>(GetParent())->update_dirty(); - static_cast<Tab*>(GetParent())->on_value_change(opt_key, value); + static_cast<Tab*>(tab)->update_dirty(); + static_cast<Tab*>(tab)->on_value_change(opt_key, value); //! }); }; - optgroup->m_get_initial_config = [this](){ - DynamicPrintConfig config = static_cast<Tab*>(GetParent())->m_presets->get_selected_preset().config; + optgroup->m_get_initial_config = [this, tab](){ + DynamicPrintConfig config = static_cast<Tab*>(tab)->m_presets->get_selected_preset().config; return config; }; - optgroup->m_get_sys_config = [this](){ - DynamicPrintConfig config = static_cast<Tab*>(GetParent())->m_presets->get_selected_preset_parent()->config; + optgroup->m_get_sys_config = [this, tab](){ + DynamicPrintConfig config = static_cast<Tab*>(tab)->m_presets->get_selected_preset_parent()->config; return config; }; - optgroup->have_sys_config = [this](){ - return static_cast<Tab*>(GetParent())->m_presets->get_selected_preset_parent() != nullptr; + optgroup->have_sys_config = [this, tab](){ + return static_cast<Tab*>(tab)->m_presets->get_selected_preset_parent() != nullptr; }; vsizer()->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); @@ -2619,14 +2602,24 @@ void SavePresetWindow::accept() if (!m_chosen_name.empty()) { const char* unusable_symbols = "<>:/\\|?*\""; bool is_unusable_symbol = false; + bool is_unusable_postfix = false; + const std::string unusable_postfix = "(modified)"; for (size_t i = 0; i < std::strlen(unusable_symbols); i++){ if (m_chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos){ is_unusable_symbol = true; break; } } + if (m_chosen_name.find(unusable_postfix) != std::string::npos) + is_unusable_postfix = true; + if (is_unusable_symbol) { - show_error(this, _(L("The supplied name is not valid; the following characters are not allowed:"))+" <>:/\\|?*\""); + show_error(this,_(L("The supplied name is not valid;")) + "\n" + + _(L("the following characters are not allowed:")) + " <>:/\\|?*\""); + } + else if (is_unusable_postfix){ + show_error(this, _(L("The supplied name is not valid;")) + "\n" + + _(L("the following postfix are not allowed:")) + "\n\t" + unusable_postfix); } else if (m_chosen_name.compare("- default -") == 0) { show_error(this, _(L("The supplied name is not available."))); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index d6bf2cf43..c755f91f1 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -102,6 +102,10 @@ using PageShp = std::shared_ptr<Page>; class Tab: public wxPanel { wxNotebook* m_parent; +#ifdef __WXOSX__ + wxPanel* m_tmp_panel; + int m_size_move = -1; +#endif // __WXOSX__ protected: std::string m_name; const wxString m_title; @@ -118,7 +122,6 @@ protected: wxButton* m_undo_btn; wxButton* m_undo_to_sys_btn; wxButton* m_question_btn; - wxComboCtrl* m_cc_presets_choice; wxDataViewTreeCtrl* m_presetctrl; wxImageList* m_preset_icons; @@ -198,7 +201,7 @@ public: Tab() {} Tab(wxNotebook* parent, const wxString& title, const char* name, bool no_controller) : m_parent(parent), m_title(title), m_name(name), m_no_controller(no_controller) { - Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); + Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL, name); get_tabs_list().push_back(this); } ~Tab(){ @@ -242,7 +245,7 @@ public: PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false); - virtual void OnActivate(){} + virtual void OnActivate(); virtual void on_preset_loaded(){} virtual void build() = 0; virtual void update() = 0; diff --git a/xs/src/slic3r/Utils/FixModelByWin10.cpp b/xs/src/slic3r/Utils/FixModelByWin10.cpp new file mode 100644 index 000000000..556035a5b --- /dev/null +++ b/xs/src/slic3r/Utils/FixModelByWin10.cpp @@ -0,0 +1,402 @@ +#ifdef HAS_WIN10SDK + +#ifndef NOMINMAX +# define NOMINMAX +#endif + +#include "FixModelByWin10.hpp" + +#include <atomic> +#include <chrono> +#include <cstdint> +#include <condition_variable> +#include <exception> +#include <string> +#include <thread> + +#include <boost/filesystem.hpp> +#include <boost/nowide/convert.hpp> +#include <boost/nowide/cstdio.hpp> + +#include <roapi.h> +// for ComPtr +#include <wrl/client.h> +// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/ +#include <winrt/robuffer.h> +#include <winrt/windows.storage.provider.h> +#include <winrt/windows.graphics.printing3d.h> + +#include "libslic3r/Model.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/Format/3mf.hpp" +#include "../GUI/GUI.hpp" +#include "../GUI/PresetBundle.hpp" + +#include <wx/msgdlg.h> +#include <wx/progdlg.h> + +extern "C"{ + // from rapi.h + typedef HRESULT (__stdcall* FunctionRoInitialize)(int); + typedef HRESULT (__stdcall* FunctionRoUninitialize)(); + typedef HRESULT (__stdcall* FunctionRoActivateInstance)(HSTRING activatableClassId, IInspectable **instance); + typedef HRESULT (__stdcall* FunctionRoGetActivationFactory)(HSTRING activatableClassId, REFIID iid, void **factory); + // from winstring.h + typedef HRESULT (__stdcall* FunctionWindowsCreateString)(LPCWSTR sourceString, UINT32 length, HSTRING *string); + typedef HRESULT (__stdcall* FunctionWindowsDelteString)(HSTRING string); +} + +namespace Slic3r { + +HMODULE s_hRuntimeObjectLibrary = nullptr; +FunctionRoInitialize s_RoInitialize = nullptr; +FunctionRoUninitialize s_RoUninitialize = nullptr; +FunctionRoActivateInstance s_RoActivateInstance = nullptr; +FunctionRoGetActivationFactory s_RoGetActivationFactory = nullptr; +FunctionWindowsCreateString s_WindowsCreateString = nullptr; +FunctionWindowsDelteString s_WindowsDeleteString = nullptr; + +bool winrt_load_runtime_object_library() +{ + if (s_hRuntimeObjectLibrary == nullptr) + s_hRuntimeObjectLibrary = LoadLibrary(L"ComBase.dll"); + if (s_hRuntimeObjectLibrary != nullptr) { + s_RoInitialize = (FunctionRoInitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoInitialize"); + s_RoUninitialize = (FunctionRoUninitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoUninitialize"); + s_RoActivateInstance = (FunctionRoActivateInstance) GetProcAddress(s_hRuntimeObjectLibrary, "RoActivateInstance"); + s_RoGetActivationFactory = (FunctionRoGetActivationFactory) GetProcAddress(s_hRuntimeObjectLibrary, "RoGetActivationFactory"); + s_WindowsCreateString = (FunctionWindowsCreateString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsCreateString"); + s_WindowsDeleteString = (FunctionWindowsDelteString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsDeleteString"); + } + return s_RoInitialize && s_RoUninitialize && s_RoActivateInstance && s_WindowsCreateString && s_WindowsDeleteString; +} + +static HRESULT winrt_activate_instance(const std::wstring &class_name, IInspectable **pinst) +{ + HSTRING hClassName; + HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName); + if (S_OK != hr) + return hr; + hr = (*s_RoActivateInstance)(hClassName, pinst); + (*s_WindowsDeleteString)(hClassName); + return hr; +} + +template<typename TYPE> +static HRESULT winrt_activate_instance(const std::wstring &class_name, TYPE **pinst) +{ + IInspectable *pinspectable = nullptr; + HRESULT hr = winrt_activate_instance(class_name, &pinspectable); + if (S_OK != hr) + return hr; + hr = pinspectable->QueryInterface(__uuidof(TYPE), (void**)pinst); + pinspectable->Release(); + return hr; +} + +static HRESULT winrt_get_activation_factory(const std::wstring &class_name, REFIID iid, void **pinst) +{ + HSTRING hClassName; + HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName); + if (S_OK != hr) + return hr; + hr = (*s_RoGetActivationFactory)(hClassName, iid, pinst); + (*s_WindowsDeleteString)(hClassName); + return hr; +} + +template<typename TYPE> +static HRESULT winrt_get_activation_factory(const std::wstring &class_name, TYPE **pinst) +{ + return winrt_get_activation_factory(class_name, __uuidof(TYPE), reinterpret_cast<void**>(pinst)); +} + +// To be called often to test whether to cancel the operation. +typedef std::function<void ()> ThrowOnCancelFn; + +template<typename T> +static AsyncStatus winrt_async_await(const Microsoft::WRL::ComPtr<T> &asyncAction, ThrowOnCancelFn throw_on_cancel, int blocking_tick_ms = 100) +{ + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo; + asyncAction.As(&asyncInfo); + AsyncStatus status; + // Ugly blocking loop until the RepairAsync call finishes. +//FIXME replace with a callback. +// https://social.msdn.microsoft.com/Forums/en-US/a5038fb4-b7b7-4504-969d-c102faa389fb/trying-to-block-an-async-operation-and-wait-for-a-particular-time?forum=vclanguage + for (;;) { + asyncInfo->get_Status(&status); + if (status != AsyncStatus::Started) + return status; + throw_on_cancel(); + ::Sleep(blocking_tick_ms); + } +} + +static HRESULT winrt_open_file_stream( + const std::wstring &path, + ABI::Windows::Storage::FileAccessMode mode, + ABI::Windows::Storage::Streams::IRandomAccessStream **fileStream, + ThrowOnCancelFn throw_on_cancel) +{ + // Get the file factory. + Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFileStatics> fileFactory; + HRESULT hr = winrt_get_activation_factory(L"Windows.Storage.StorageFile", fileFactory.GetAddressOf()); + if (FAILED(hr)) return hr; + + // Open the file asynchronously. + HSTRING hstr_path; + hr = (*s_WindowsCreateString)(path.c_str(), path.size(), &hstr_path); + if (FAILED(hr)) return hr; + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::StorageFile*>> fileOpenAsync; + hr = fileFactory->GetFileFromPathAsync(hstr_path, fileOpenAsync.GetAddressOf()); + if (FAILED(hr)) return hr; + (*s_WindowsDeleteString)(hstr_path); + + // Wait until the file gets open, get the actual file. + AsyncStatus status = winrt_async_await(fileOpenAsync, throw_on_cancel); + Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFile> storageFile; + if (status == AsyncStatus::Completed) { + hr = fileOpenAsync->GetResults(storageFile.GetAddressOf()); + } else { + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo; + hr = fileOpenAsync.As(&asyncInfo); + if (FAILED(hr)) return hr; + HRESULT err; + hr = asyncInfo->get_ErrorCode(&err); + return FAILED(hr) ? hr : err; + } + + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> fileStreamAsync; + hr = storageFile->OpenAsync(mode, fileStreamAsync.GetAddressOf()); + if (FAILED(hr)) return hr; + + status = winrt_async_await(fileStreamAsync, throw_on_cancel); + if (status == AsyncStatus::Completed) { + hr = fileStreamAsync->GetResults(fileStream); + } else { + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo; + hr = fileStreamAsync.As(&asyncInfo); + if (FAILED(hr)) return hr; + HRESULT err; + hr = asyncInfo->get_ErrorCode(&err); + if (!FAILED(hr)) + hr = err; + } + return hr; +} + +bool is_windows10() +{ + HKEY hKey; + LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey); + if (lRes == ERROR_SUCCESS) { + WCHAR szBuffer[512]; + DWORD dwBufferSize = sizeof(szBuffer); + lRes = RegQueryValueExW(hKey, L"ProductName", 0, nullptr, (LPBYTE)szBuffer, &dwBufferSize); + if (lRes == ERROR_SUCCESS) + return wcsncmp(szBuffer, L"Windows 10", 10) == 0; + RegCloseKey(hKey); + } + return false; +} + +// Progress function, to be called regularly to update the progress. +typedef std::function<void (const char * /* message */, unsigned /* progress */)> ProgressFn; + +void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path_dst, ProgressFn on_progress, ThrowOnCancelFn throw_on_cancel) +{ + if (! is_windows10()) + throw std::runtime_error("fix_model_by_win10_sdk called on non Windows 10 system"); + + if (! winrt_load_runtime_object_library()) + throw std::runtime_error("Failed to initialize the WinRT library."); + + HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED); + { + on_progress(L("Exporting the source model"), 20); + + Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> fileStream; + hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel); + + Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3D3MFPackage> printing3d3mfpackage; + hr = winrt_activate_instance(L"Windows.Graphics.Printing3D.Printing3D3MFPackage", printing3d3mfpackage.GetAddressOf()); + + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Graphics::Printing3D::Printing3DModel*>> modelAsync; + hr = printing3d3mfpackage->LoadModelFromPackageAsync(fileStream.Get(), modelAsync.GetAddressOf()); + + AsyncStatus status = winrt_async_await(modelAsync, throw_on_cancel); + Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3DModel> model; + if (status == AsyncStatus::Completed) + hr = modelAsync->GetResults(model.GetAddressOf()); + else + throw std::runtime_error(L("Failed loading the input model.")); + + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::Collections::IVector<ABI::Windows::Graphics::Printing3D::Printing3DMesh*>> meshes; + hr = model->get_Meshes(meshes.GetAddressOf()); + unsigned num_meshes = 0; + hr = meshes->get_Size(&num_meshes); + + on_progress(L("Repairing the model by the Netfabb service"), 40); + + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> repairAsync; + hr = model->RepairAsync(repairAsync.GetAddressOf()); + status = winrt_async_await(repairAsync, throw_on_cancel); + if (status != AsyncStatus::Completed) + throw std::runtime_error(L("Mesh repair failed.")); + repairAsync->GetResults(); + + on_progress(L("Loading the repaired model"), 60); + + // Verify the number of meshes returned after the repair action. + meshes.Reset(); + hr = model->get_Meshes(meshes.GetAddressOf()); + hr = meshes->get_Size(&num_meshes); + + // Save model to this class' Printing3D3MFPackage. + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> saveToPackageAsync; + hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf()); + status = winrt_async_await(saveToPackageAsync, throw_on_cancel); + if (status != AsyncStatus::Completed) + throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + hr = saveToPackageAsync->GetResults(); + + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> generatorStreamAsync; + hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf()); + status = winrt_async_await(generatorStreamAsync, throw_on_cancel); + if (status != AsyncStatus::Completed) + throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> generatorStream; + hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf()); + + // Go to the beginning of the stream. + generatorStream->Seek(0); + Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IInputStream> inputStream; + hr = generatorStream.As(&inputStream); + + // Get the buffer factory. + Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory; + hr = winrt_get_activation_factory(L"Windows.Storage.Streams.Buffer", bufferFactory.GetAddressOf()); + + // Open the destination file. + FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb"); + + Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; + byte *buffer_ptr; + bufferFactory->Create(65536 * 2048, buffer.GetAddressOf()); + { + Microsoft::WRL::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess; + buffer.As(&bufferByteAccess); + hr = bufferByteAccess->Buffer(&buffer_ptr); + } + uint32_t length; + hr = buffer->get_Length(&length); + + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer*, UINT32>> asyncRead; + for (;;) { + hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf()); + status = winrt_async_await(asyncRead, throw_on_cancel); + if (status != AsyncStatus::Completed) + throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + hr = buffer->get_Length(&length); + if (length == 0) + break; + fwrite(buffer_ptr, length, 1, fout); + } + fclose(fout); + // Here all the COM objects will be released through the ComPtr destructors. + } + (*s_RoUninitialize)(); +} + +class RepairCanceledException : public std::exception { +public: + const char* what() const throw() { return "Model repair has been canceled"; } +}; + +void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result) +{ + std::mutex mutex; + std::condition_variable condition; + std::unique_lock<std::mutex> lock(mutex); + struct Progress { + std::string message; + int percent = 0; + bool updated = false; + } progress; + std::atomic<bool> canceled = false; + std::atomic<bool> finished = false; + + // Open a progress dialog. + wxProgressDialog progress_dialog( + _(L("Model fixing")), + _(L("Exporting model...")), + 100, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); + // Executing the calculation in a background thread, so that the COM context could be created with its own threading model. + // (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context). + bool success = false; + auto on_progress = [&mutex, &condition, &progress](const char *msg, unsigned prcnt) { + std::lock_guard<std::mutex> lk(mutex); + progress.message = msg; + progress.percent = prcnt; + progress.updated = true; + condition.notify_all(); + }; + auto worker_thread = boost::thread([&model_object, &print, &result, on_progress, &success, &canceled, &finished]() { + try { + on_progress(L("Exporting the source model"), 0); + boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); + path_src += ".3mf"; + Model model; + model.add_object(model_object); + if (! Slic3r::store_3mf(path_src.string().c_str(), &model, const_cast<Print*>(&print), false)) { + boost::filesystem::remove(path_src); + throw std::runtime_error(L("Export of a temporary 3mf file failed")); + } + model.clear_objects(); + model.clear_materials(); + boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); + path_dst += ".3mf"; + fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress, + [&canceled]() { if (canceled) throw RepairCanceledException(); }); + boost::filesystem::remove(path_src); + PresetBundle bundle; + on_progress(L("Loading the repaired model"), 80); + bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &bundle, &result); + boost::filesystem::remove(path_dst); + if (! loaded) + throw std::runtime_error(L("Import of the repaired 3mf file failed")); + success = true; + finished = true; + on_progress(L("Model repair finished"), 100); + } catch (RepairCanceledException &ex) { + canceled = true; + finished = true; + on_progress(L("Model repair canceled"), 100); + } catch (std::exception &ex) { + success = false; + finished = true; + on_progress(ex.what(), 100); + } + }); + while (! finished) { + condition.wait_for(lock, std::chrono::milliseconds(500), [&progress]{ return progress.updated; }); + if (! progress_dialog.Update(progress.percent, _(progress.message))) + canceled = true; + progress.updated = false; + } + + if (canceled) { + // Nothing to show. + } else if (success) { + wxMessageDialog dlg(nullptr, _(L("Model repaired successfully")), _(L("Model Repair by the Netfabb service")), wxICON_INFORMATION | wxOK_DEFAULT); + dlg.ShowModal(); + } else { + wxMessageDialog dlg(nullptr, _(L("Model repair failed: \n")) + _(progress.message), _(L("Model Repair by the Netfabb service")), wxICON_ERROR | wxOK_DEFAULT); + dlg.ShowModal(); + } + worker_thread.join(); +} + +} // namespace Slic3r + +#endif /* HAS_WIN10SDK */ diff --git a/xs/src/slic3r/Utils/FixModelByWin10.hpp b/xs/src/slic3r/Utils/FixModelByWin10.hpp new file mode 100644 index 000000000..c148a6970 --- /dev/null +++ b/xs/src/slic3r/Utils/FixModelByWin10.hpp @@ -0,0 +1,26 @@ +#ifndef slic3r_GUI_Utils_FixModelByWin10_hpp_ +#define slic3r_GUI_Utils_FixModelByWin10_hpp_ + +#include <string> + +namespace Slic3r { + +class Model; +class ModelObject; +class Print; + +#ifdef HAS_WIN10SDK + +extern bool is_windows10(); +extern void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result); + +#else /* HAS_WIN10SDK */ + +inline bool is_windows10() { return false; } +inline void fix_model_by_win10_sdk_gui(const ModelObject &, const Print &, Model &) {} + +#endif /* HAS_WIN10SDK */ + +} // namespace Slic3r + +#endif /* slic3r_GUI_Utils_FixModelByWin10_hpp_ */ diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp index 0826284d8..37eb59a00 100644 --- a/xs/src/slic3r/Utils/Http.cpp +++ b/xs/src/slic3r/Utils/Http.cpp @@ -3,13 +3,16 @@ #include <cstdlib> #include <functional> #include <thread> -#include <tuple> +#include <deque> +#include <boost/filesystem/fstream.hpp> #include <boost/format.hpp> #include <curl/curl.h> #include "../../libslic3r/libslic3r.h" +namespace fs = boost::filesystem; + namespace Slic3r { @@ -34,7 +37,11 @@ struct Http::priv ::curl_httppost *form; ::curl_httppost *form_end; ::curl_slist *headerlist; + // Used for reading the body std::string buffer; + // Used for storing file streams added as multipart form parts + // Using a deque here because unlike vector it doesn't ivalidate pointers on insertion + std::deque<fs::ifstream> form_files; size_t limit; bool cancel; @@ -50,6 +57,10 @@ struct Http::priv static size_t writecb(void *data, size_t size, size_t nmemb, void *userp); static int xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); static int xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow); + static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp); + + void form_add_file(const char *name, const fs::path &path, const char* filename); + std::string curl_error(CURLcode curlcode); std::string body_size_error(); void http_perform(); @@ -60,6 +71,7 @@ Http::priv::priv(const std::string &url) : form(nullptr), form_end(nullptr), headerlist(nullptr), + limit(0), cancel(false) { if (curl == nullptr) { @@ -135,6 +147,46 @@ int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double return xfercb(userp, dltotal, dlnow, ultotal, ulnow); } +size_t Http::priv::form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp) +{ + auto stream = reinterpret_cast<fs::ifstream*>(userp); + + try { + stream->read(buffer, size * nitems); + } catch (...) { + return CURL_READFUNC_ABORT; + } + + return stream->gcount(); +} + +void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename) +{ + // We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows + // and so we use CURLFORM_STREAM with boost ifstream to read the file. + + if (filename == nullptr) { + filename = path.string().c_str(); + } + + form_files.emplace_back(path, std::ios::in | std::ios::binary); + auto &stream = form_files.back(); + stream.seekg(0, std::ios::end); + size_t size = stream.tellg(); + stream.seekg(0); + + if (filename != nullptr) { + ::curl_formadd(&form, &form_end, + CURLFORM_COPYNAME, name, + CURLFORM_FILENAME, filename, + CURLFORM_CONTENTTYPE, "application/octet-stream", + CURLFORM_STREAM, static_cast<void*>(&stream), + CURLFORM_CONTENTSLENGTH, static_cast<long>(size), + CURLFORM_END + ); + } +} + std::string Http::priv::curl_error(CURLcode curlcode) { return (boost::format("%1% (%2%)") @@ -150,10 +202,10 @@ std::string Http::priv::body_size_error() void Http::priv::http_perform() { - ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb); ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(this)); + ::curl_easy_setopt(curl, CURLOPT_READFUNCTION, form_file_read_cb); ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); #if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 32 @@ -178,23 +230,32 @@ void Http::priv::http_perform() } CURLcode res = ::curl_easy_perform(curl); - long http_status = 0; - ::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status); if (res != CURLE_OK) { if (res == CURLE_ABORTED_BY_CALLBACK) { - Progress dummyprogress(0, 0, 0, 0); - bool cancel = true; - if (progressfn) { progressfn(dummyprogress, cancel); } + if (cancel) { + // The abort comes from the request being cancelled programatically + Progress dummyprogress(0, 0, 0, 0); + bool cancel = true; + if (progressfn) { progressfn(dummyprogress, cancel); } + } else { + // The abort comes from the CURLOPT_READFUNCTION callback, which means reading file failed + if (errorfn) { errorfn(std::move(buffer), "Error reading file for file upload", 0); } + } } else if (res == CURLE_WRITE_ERROR) { - if (errorfn) { errorfn(std::move(buffer), std::move(body_size_error()), http_status); } + if (errorfn) { errorfn(std::move(buffer), body_size_error(), 0); } } else { - if (errorfn) { errorfn(std::move(buffer), std::move(curl_error(res)), http_status); } + if (errorfn) { errorfn(std::move(buffer), curl_error(res), 0); } }; } else { - if (completefn) { - completefn(std::move(buffer), http_status); + long http_status = 0; + ::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status); + + if (http_status >= 400) { + if (errorfn) { errorfn(std::move(buffer), std::string(), http_status); } + } else { + if (completefn) { completefn(std::move(buffer), http_status); } } } } @@ -265,17 +326,15 @@ Http& Http::form_add(const std::string &name, const std::string &contents) return *this; } -Http& Http::form_add_file(const std::string &name, const std::string &filename) +Http& Http::form_add_file(const std::string &name, const fs::path &path) { - if (p) { - ::curl_formadd(&p->form, &p->form_end, - CURLFORM_COPYNAME, name.c_str(), - CURLFORM_FILE, filename.c_str(), - CURLFORM_CONTENTTYPE, "application/octet-stream", - CURLFORM_END - ); - } + if (p) { p->form_add_file(name.c_str(), path.c_str(), nullptr); } + return *this; +} +Http& Http::form_add_file(const std::string &name, const fs::path &path, const std::string &filename) +{ + if (p) { p->form_add_file(name.c_str(), path.c_str(), filename.c_str()); } return *this; } diff --git a/xs/src/slic3r/Utils/Http.hpp b/xs/src/slic3r/Utils/Http.hpp index 7ed8196e6..ce4e438ca 100644 --- a/xs/src/slic3r/Utils/Http.hpp +++ b/xs/src/slic3r/Utils/Http.hpp @@ -4,6 +4,7 @@ #include <memory> #include <string> #include <functional> +#include <boost/filesystem/path.hpp> namespace Slic3r { @@ -16,11 +17,11 @@ private: public: struct Progress { - size_t dltotal; - size_t dlnow; - size_t ultotal; - size_t ulnow; - + size_t dltotal; // Total bytes to download + size_t dlnow; // Bytes downloaded so far + size_t ultotal; // Total bytes to upload + size_t ulnow; // Bytes uploaded so far + Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow) : dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow) {} @@ -28,11 +29,24 @@ public: typedef std::shared_ptr<Http> Ptr; typedef std::function<void(std::string /* body */, unsigned /* http_status */)> CompleteFn; + + // A HTTP request may fail at various stages of completeness (URL parsing, DNS lookup, TCP connection, ...). + // If the HTTP request could not be made or failed before completion, the `error` arg contains a description + // of the error and `http_status` is zero. + // If the HTTP request was completed but the response HTTP code is >= 400, `error` is empty and `http_status` contains the response code. + // In either case there may or may not be a body. typedef std::function<void(std::string /* body */, std::string /* error */, unsigned /* http_status */)> ErrorFn; + + // See the Progress struct above. + // Writing true to the `cancel` reference cancels the request in progress. typedef std::function<void(Progress, bool& /* cancel */)> ProgressFn; Http(Http &&other); + // Note: strings are expected to be UTF-8-encoded + + // These are the primary constructors that create a HTTP object + // for a GET and a POST request respectively. static Http get(std::string url); static Http post(std::string url); ~Http(); @@ -41,21 +55,43 @@ public: Http& operator=(const Http &) = delete; Http& operator=(Http &&) = delete; + // Sets a maximum size of the data that can be received. + // A value of zero sets the default limit, which is is 5MB. Http& size_limit(size_t sizeLimit); + // Sets a HTTP header field. Http& header(std::string name, const std::string &value); + // Removes a header field. Http& remove_header(std::string name); + // Sets a CA certificate file for usage with HTTPS. This is only supported on some backends, + // specifically, this is supported with OpenSSL and NOT supported with Windows and OS X native certificate store. + // See also ca_file_supported(). Http& ca_file(const std::string &filename); + // Add a HTTP multipart form field Http& form_add(const std::string &name, const std::string &contents); - Http& form_add_file(const std::string &name, const std::string &filename); + // Add a HTTP multipart form file data contents, `name` is the name of the part + Http& form_add_file(const std::string &name, const boost::filesystem::path &path); + // Same as above except also override the file's filename with a custom one + Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename); + // Callback called on HTTP request complete Http& on_complete(CompleteFn fn); + // Callback called on an error occuring at any stage of the requests: Url parsing, DNS lookup, + // TCP connection, HTTP transfer, and finally also when the response indicates an error (status >= 400). + // Therefore, a response body may or may not be present. Http& on_error(ErrorFn fn); + // Callback called on data download/upload prorgess (called fairly frequently). + // See the `Progress` structure for description of the data passed. + // Writing a true-ish value into the cancel reference parameter cancels the request. Http& on_progress(ProgressFn fn); + // Starts performing the request in a background thread Ptr perform(); + // Starts performing the request on the current thread void perform_sync(); + // Cancels a request in progress void cancel(); + // Tells whether current backend supports seting up a CA file using ca_file() static bool ca_file_supported(); private: Http(const std::string &url); diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp index e63a16c38..97b4123d4 100644 --- a/xs/src/slic3r/Utils/OctoPrint.cpp +++ b/xs/src/slic3r/Utils/OctoPrint.cpp @@ -1,20 +1,65 @@ #include "OctoPrint.hpp" #include <algorithm> +#include <boost/filesystem/path.hpp> #include <boost/format.hpp> +#include <boost/log/trivial.hpp> #include <wx/frame.h> #include <wx/event.h> #include <wx/progdlg.h> +#include <wx/sizer.h> +#include <wx/stattext.h> +#include <wx/textctrl.h> +#include <wx/checkbox.h> #include "libslic3r/PrintConfig.hpp" #include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/MsgDialog.hpp" #include "Http.hpp" +namespace fs = boost::filesystem; + namespace Slic3r { +struct SendDialog : public GUI::MsgDialog +{ + wxTextCtrl *txt_filename; + wxCheckBox *box_print; + + SendDialog(const fs::path &path) : + MsgDialog(nullptr, _(L("Send G-Code to printer")), _(L("Upload to OctoPrint with the following filename:")), wxID_NONE), + txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())), + box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload")))) + { + auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed."))); + label_dir_hint->Wrap(CONTENT_WIDTH); + + content_sizer->Add(txt_filename, 0, wxEXPAND); + content_sizer->Add(label_dir_hint); + content_sizer->AddSpacer(VERT_SPACING); + content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING); + + btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL)); + + txt_filename->SetFocus(); + wxString stem(path.stem().wstring()); + txt_filename->SetSelection(0, stem.Length()); + + Fit(); + } + + fs::path filename() const { + return fs::path(txt_filename->GetValue().wx_str()); + } + + bool print() const { return box_print->GetValue(); } +}; + + + OctoPrint::OctoPrint(DynamicPrintConfig *config) : host(config->opt_string("octoprint_host")), apikey(config->opt_string("octoprint_apikey")), @@ -27,24 +72,39 @@ bool OctoPrint::test(wxString &msg) const // it is ok to refer to `msg` from within the closure bool res = true; + auto url = make_url("api/version"); + + BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Get version at: %1%") % url; - auto url = std::move(make_url("api/version")); auto http = Http::get(std::move(url)); set_auth(http); - http.on_error([&](std::string, std::string error, unsigned status) { + http.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error getting version: %1%, HTTP %2%, body: `%3%`") % error % status % body; res = false; - msg = format_error(error, status); + msg = format_error(body, error, status); + }) + .on_complete([&](std::string body, unsigned) { + BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: Got version: %1%") % body; }) .perform_sync(); return res; } -bool OctoPrint::send_gcode(const std::string &filename, bool print) const +bool OctoPrint::send_gcode(const std::string &filename) const { enum { PROGRESS_RANGE = 1000 }; const auto errortitle = _(L("Error while uploading to the OctoPrint server")); + fs::path filepath(filename); + + SendDialog send_dialog(filepath.filename()); + if (send_dialog.ShowModal() != wxID_OK) { return false; } + + const bool print = send_dialog.print(); + const auto upload_filepath = send_dialog.filename(); + const auto upload_filename = upload_filepath.filename(); + const auto upload_parent_path = upload_filepath.parent_path(); wxProgressDialog progress_dialog( _(L("OctoPrint upload")), @@ -61,15 +121,27 @@ bool OctoPrint::send_gcode(const std::string &filename, bool print) const bool res = true; - auto http = Http::post(std::move(make_url("api/files/local"))); + auto url = make_url("api/files/local"); + + BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Uploading file %1% at %2%, filename: %3%, path: %4%, print: %5%") + % filepath.string() + % url + % upload_filename.string() + % upload_parent_path.string() + % print; + + auto http = Http::post(std::move(url)); set_auth(http); http.form_add("print", print ? "true" : "false") - .form_add_file("file", filename) + .form_add("path", upload_parent_path.string()) + .form_add_file("file", filename, upload_filename.string()) .on_complete([&](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: File uploaded: HTTP %1%: %2%") % status % body; progress_dialog.Update(PROGRESS_RANGE); }) .on_error([&](std::string body, std::string error, unsigned status) { - auto errormsg = wxString::Format("%s: %s", errortitle, format_error(error, status)); + BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body; + auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status)); GUI::show_error(&progress_dialog, std::move(errormsg)); res = false; }) @@ -102,24 +174,22 @@ std::string OctoPrint::make_url(const std::string &path) const { if (host.find("http://") == 0 || host.find("https://") == 0) { if (host.back() == '/') { - return std::move((boost::format("%1%%2%") % host % path).str()); + return (boost::format("%1%%2%") % host % path).str(); } else { - return std::move((boost::format("%1%/%2%") % host % path).str()); + return (boost::format("%1%/%2%") % host % path).str(); } } else { - return std::move((boost::format("http://%1%/%2%") % host % path).str()); + return (boost::format("http://%1%/%2%") % host % path).str(); } } -wxString OctoPrint::format_error(std::string error, unsigned status) +wxString OctoPrint::format_error(const std::string &body, const std::string &error, unsigned status) { - const wxString wxerror = error; - if (status != 0) { - return wxString::Format("HTTP %u: %s", status, - (status == 401 ? _(L("Invalid API key")) : wxerror)); + auto wxbody = wxString::FromUTF8(body.data()); + return wxString::Format("HTTP %u: %s", status, wxbody); } else { - return std::move(wxerror); + return wxString::FromUTF8(error.data()); } } diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp index 744b4fcc1..1e2098ae3 100644 --- a/xs/src/slic3r/Utils/OctoPrint.hpp +++ b/xs/src/slic3r/Utils/OctoPrint.hpp @@ -17,7 +17,8 @@ public: OctoPrint(DynamicPrintConfig *config); bool test(wxString &curl_msg) const; - bool send_gcode(const std::string &filename, bool print = false) const; + // Send gcode file to octoprint, filename is expected to be in UTF-8 + bool send_gcode(const std::string &filename) const; private: std::string host; std::string apikey; @@ -25,7 +26,7 @@ private: void set_auth(Http &http) const; std::string make_url(const std::string &path) const; - static wxString format_error(std::string error, unsigned status); + static wxString format_error(const std::string &body, const std::string &error, unsigned status); }; diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index f34fc4c19..1ce814b89 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -259,7 +259,7 @@ void PresetUpdater::priv::sync_config(const std::set<VendorProfile> vendors) con } const auto recommended = recommended_it->config_version; - BOOST_LOG_TRIVIAL(debug) << boost::format("New index for vendor: %1%: current version: %2%, recommended version: %3%") + BOOST_LOG_TRIVIAL(debug) << boost::format("Got index for vendor: %1%: current version: %2%, recommended version: %3%") % vendor.name % vendor.config_version.to_string() % recommended.to_string(); @@ -352,20 +352,25 @@ Updates PresetUpdater::priv::get_config_updates() const continue; } - auto path_in_cache = cache_path / (idx.vendor() + ".ini"); - if (! fs::exists(path_in_cache)) { - BOOST_LOG_TRIVIAL(warning) << "Index indicates update, but new bundle not found in cache: " << path_in_cache.string(); - continue; + auto path_src = cache_path / (idx.vendor() + ".ini"); + if (! fs::exists(path_src)) { + auto path_in_rsrc = rsrc_path / (idx.vendor() + ".ini"); + if (! fs::exists(path_in_rsrc)) { + BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update, but bundle found in neither cache nor resources") + % idx.vendor();; + continue; + } else { + path_src = std::move(path_in_rsrc); + } } - const auto cached_vp = VendorProfile::from_ini(path_in_cache, false); - if (cached_vp.config_version == recommended->config_version) { - updates.updates.emplace_back(std::move(path_in_cache), std::move(bundle_path), *recommended); + const auto new_vp = VendorProfile::from_ini(path_src, false); + if (new_vp.config_version == recommended->config_version) { + updates.updates.emplace_back(std::move(path_src), std::move(bundle_path), *recommended); } else { - BOOST_LOG_TRIVIAL(warning) << boost::format("Vendor: %1%: Index indicates update (%2%) but cached bundle has a different version: %3%") + BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources") % idx.vendor() - % recommended->config_version.to_string() - % cached_vp.config_version.to_string(); + % recommended->config_version.to_string(); } } } @@ -532,15 +537,15 @@ bool PresetUpdater::config_update() const incompats_map.emplace(std::make_pair(std::move(vendor), std::move(restrictions))); } + p->had_config_update = true; // This needs to be done before a dialog is shown because of OnIdle() + CallAfter() in Perl + GUI::MsgDataIncompatible dlg(std::move(incompats_map)); const auto res = dlg.ShowModal(); if (res == wxID_REPLACE) { BOOST_LOG_TRIVIAL(info) << "User wants to re-configure..."; p->perform_updates(std::move(updates)); GUI::ConfigWizard wizard(nullptr, GUI::ConfigWizard::RR_DATA_INCOMPAT); - if (wizard.run(GUI::get_preset_bundle(), this)) { - p->had_config_update = true; - } else { + if (! wizard.run(GUI::get_preset_bundle(), this)) { return false; } } else { @@ -561,6 +566,8 @@ bool PresetUpdater::config_update() const updates_map.emplace(std::make_pair(std::move(vendor), std::move(ver_str))); } + p->had_config_update = true; // Ditto, see above + GUI::MsgUpdateConfig dlg(std::move(updates_map)); const auto res = dlg.ShowModal(); @@ -576,8 +583,6 @@ bool PresetUpdater::config_update() const } else { BOOST_LOG_TRIVIAL(info) << "User refused the update"; } - - p->had_config_update = true; } else { BOOST_LOG_TRIVIAL(info) << "No configuration updates available."; } diff --git a/xs/xsp/BoundingBox.xsp b/xs/xsp/BoundingBox.xsp index df8e6baea..ebeb17822 100644 --- a/xs/xsp/BoundingBox.xsp +++ b/xs/xsp/BoundingBox.xsp @@ -29,6 +29,10 @@ int x_max() %code{% RETVAL = THIS->max.x; %}; int y_min() %code{% RETVAL = THIS->min.y; %}; int y_max() %code{% RETVAL = THIS->max.y; %}; + void set_x_min(double val) %code{% THIS->min.x = val; %}; + void set_x_max(double val) %code{% THIS->max.x = val; %}; + void set_y_min(double val) %code{% THIS->min.y = val; %}; + void set_y_max(double val) %code{% THIS->max.y = val; %}; std::string serialize() %code{% char buf[2048]; sprintf(buf, "%ld,%ld;%ld,%ld", THIS->min.x, THIS->min.y, THIS->max.x, THIS->max.y); RETVAL = buf; %}; bool defined() %code{% RETVAL = THIS->defined; %}; diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 36b43271c..c1856ccf7 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -31,7 +31,7 @@ try { THIS->do_export(print, path, preview_data); } catch (std::exception& e) { - croak(e.what()); + croak("%s\n", e.what()); } %}; diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 50fffc545..af0612f19 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -4,6 +4,7 @@ #include <xsinit.h> #include "slic3r/GUI/GUI.hpp" #include "slic3r/Utils/ASCIIFolding.hpp" +#include "slic3r/Utils/FixModelByWin10.hpp" #include "slic3r/Utils/Serial.hpp" %} @@ -28,6 +29,9 @@ bool debugged() void break_to_debugger() %code{% Slic3r::GUI::break_to_debugger(); %}; +bool is_windows10() + %code{% RETVAL=Slic3r::is_windows10(); %}; + void set_wxapp(SV *ui) %code%{ Slic3r::GUI::set_wxapp((wxApp*)wxPli_sv_2_object(aTHX_ ui, "Wx::App")); %}; @@ -94,3 +98,6 @@ int get_export_option(SV *ui) void desktop_open_datadir_folder() %code%{ Slic3r::GUI::desktop_open_datadir_folder(); %}; + +void fix_model_by_win10_sdk_gui(ModelObject *model_object_src, Print *print, Model *model_dst) + %code%{ Slic3r::fix_model_by_win10_sdk_gui(*model_object_src, *print, *model_dst); %}; diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 6a907291e..48bd30cd1 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -8,7 +8,8 @@ GLShader(); ~GLShader(); - bool load(const char *fragment_shader, const char *vertex_shader); + bool load_from_text(const char *fragment_shader, const char *vertex_shader); + bool load_from_file(const char *fragment_shader, const char *vertex_shader); void release(); int get_attrib_location(const char *name) const; @@ -92,9 +93,6 @@ int count() %code{% RETVAL = THIS->volumes.size(); %}; - std::vector<double> get_current_print_zs(bool active_only) - %code{% RETVAL = THIS->get_current_print_zs(active_only); %}; - void set_range(double low, double high); void render_VBOs() const; @@ -155,11 +153,456 @@ 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_as_dirty(canvas) + SV *canvas; + CODE: + _3DScene::set_as_dirty((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +unsigned int +get_volumes_count(canvas) + SV *canvas; + CODE: + RETVAL = _3DScene::get_volumes_count((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + OUTPUT: + RETVAL + +void +reset_volumes(canvas) + SV *canvas; + CODE: + _3DScene::reset_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +void +deselect_volumes(canvas) + SV *canvas; + CODE: + _3DScene::deselect_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +void +select_volume(canvas, id) + SV *canvas; + unsigned int id; + CODE: + _3DScene::select_volume((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id); + +void +update_volumes_selection(canvas, selections) + SV *canvas; + std::vector<int> selections; + CODE: + _3DScene::update_volumes_selection((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), selections); + +bool +check_volumes_outside_state(canvas, config) + SV *canvas; + DynamicPrintConfig *config; + CODE: + RETVAL = _3DScene::check_volumes_outside_state((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), config); + OUTPUT: + RETVAL + +bool +move_volume_up(canvas, id) + SV *canvas; + unsigned int id; + CODE: + RETVAL = _3DScene::move_volume_up((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id); + OUTPUT: + RETVAL + +bool +move_volume_down(canvas, id) + SV *canvas; + unsigned int id; + CODE: + RETVAL = _3DScene::move_volume_down((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id); + OUTPUT: + RETVAL + +void +set_objects_selections(canvas, selections) + SV *canvas; + std::vector<int> selections; + CODE: + _3DScene::set_objects_selections((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), selections); + +void +set_config(canvas, config) + SV *canvas; + DynamicPrintConfig *config; + CODE: + _3DScene::set_config((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), config); + +void +set_print(canvas, print) + SV *canvas; + Print *print; + CODE: + _3DScene::set_print((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), print); + +void +set_model(canvas, model) + SV *canvas; + Model *model; + CODE: + _3DScene::set_model((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model); + +void +set_bed_shape(canvas, shape) + SV *canvas; + Pointfs shape; + CODE: + _3DScene::set_bed_shape((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), shape); + +void +set_auto_bed_shape(canvas) + SV *canvas; + CODE: + _3DScene::set_auto_bed_shape((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +Clone<BoundingBoxf3> +get_volumes_bounding_box(canvas) + SV *canvas; + CODE: + RETVAL = _3DScene::get_volumes_bounding_box((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + OUTPUT: + RETVAL + +void +set_axes_length(canvas, length) + SV *canvas; + float length; + CODE: + _3DScene::set_axes_length((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), length); + +void +set_cutting_plane(canvas, z, polygons) + SV *canvas; + float z; + ExPolygons polygons; + CODE: + _3DScene::set_cutting_plane((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), z, polygons); + +void +set_color_by(canvas, value) + SV *canvas; + std::string value; + CODE: + _3DScene::set_color_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value); + +void +set_select_by(canvas, value) + SV *canvas; + std::string value; + CODE: + _3DScene::set_select_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value); + +void +set_drag_by(canvas, value) + SV *canvas; + std::string value; + CODE: + _3DScene::set_drag_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value); + +bool +is_layers_editing_enabled(canvas) + SV *canvas; + CODE: + RETVAL = _3DScene::is_layers_editing_enabled((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + OUTPUT: + RETVAL + +bool +is_layers_editing_allowed(canvas) + SV *canvas; + CODE: + RETVAL = _3DScene::is_layers_editing_allowed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + OUTPUT: + RETVAL + +bool +is_shader_enabled(canvas) + SV *canvas; + CODE: + RETVAL = _3DScene::is_shader_enabled((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + OUTPUT: + RETVAL + +bool +is_reload_delayed(canvas) + SV *canvas; + CODE: + RETVAL = _3DScene::is_reload_delayed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + OUTPUT: + RETVAL + +void +enable_layers_editing(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_layers_editing((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +enable_warning_texture(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_warning_texture((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +enable_legend_texture(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_legend_texture((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +enable_picking(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_picking((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +enable_moving(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_moving((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +enable_gizmos(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_gizmos((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +enable_shader(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_shader((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +enable_force_zoom_to_bed(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_force_zoom_to_bed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +allow_multisample(canvas, allow) + SV *canvas; + bool allow; + CODE: + _3DScene::allow_multisample((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), allow); + +void +zoom_to_bed(canvas) + SV *canvas; + CODE: + _3DScene::zoom_to_bed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +void +zoom_to_volumes(canvas) + SV *canvas; + CODE: + _3DScene::zoom_to_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +void +select_view(canvas, direction) + SV *canvas; + std::string direction; + CODE: + _3DScene::select_view((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), direction); + +void +set_viewport_from_scene(canvas, other) + SV *canvas; + SV *other; + CODE: + _3DScene::set_viewport_from_scene((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (wxGLCanvas*)wxPli_sv_2_object(aTHX_ other, "Wx::GLCanvas")); + +void +update_volumes_colors_by_extruder(canvas) + SV *canvas; + CODE: + _3DScene::update_volumes_colors_by_extruder((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +void +render(canvas) + SV *canvas; + CODE: + _3DScene::render((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +std::vector<double> +get_current_print_zs(canvas, active_only) + SV *canvas; + bool active_only; + CODE: + RETVAL = _3DScene::get_current_print_zs((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), active_only); + OUTPUT: + RETVAL + +void +set_toolpaths_range(canvas, low, high) + SV *canvas; + double low; + double high; + CODE: + _3DScene::set_toolpaths_range((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), low, high); + +void +register_on_viewport_changed_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_viewport_changed_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_double_click_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_double_click_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_right_click_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_right_click_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_select_object_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_select_object_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_model_update_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_model_update_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_remove_object_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_remove_object_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_arrange_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_arrange_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_rotate_object_left_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_rotate_object_left_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_rotate_object_right_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_rotate_object_right_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_scale_object_uniformly_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_scale_object_uniformly_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_increase_objects_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_increase_objects_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_decrease_objects_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_decrease_objects_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_instance_moved_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_instance_moved_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_wipe_tower_moved_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_wipe_tower_moved_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_enable_action_buttons_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_enable_action_buttons_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_gizmo_scale_uniformly_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_gizmo_scale_uniformly_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); unsigned int finalize_legend_texture() @@ -218,41 +661,61 @@ reset_warning_texture() CODE: _3DScene::reset_warning_texture(); +std::vector<int> +load_model_object(canvas, model_object, obj_idx, instance_idxs) + SV *canvas; + ModelObject *model_object; + int obj_idx; + std::vector<int> instance_idxs; + CODE: + RETVAL = _3DScene::load_object((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model_object, obj_idx, instance_idxs); + OUTPUT: + RETVAL + +std::vector<int> +load_model(canvas, model, obj_idx) + SV *canvas; + Model *model; + int obj_idx; + CODE: + RETVAL = _3DScene::load_object((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model, obj_idx); + OUTPUT: + RETVAL + void -_load_print_toolpaths(print, volumes, tool_colors, use_VBOs) - Print *print; - GLVolumeCollection *volumes; - std::vector<std::string> tool_colors; - int use_VBOs; +reload_scene(canvas, force) + SV *canvas; + bool force; + CODE: + _3DScene::reload_scene((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), force); + +void +load_print_toolpaths(canvas) + SV *canvas; CODE: - _3DScene::_load_print_toolpaths(print, volumes, tool_colors, use_VBOs != 0); + _3DScene::load_print_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); void -_load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs) - PrintObject *print_object; - GLVolumeCollection *volumes; +load_print_object_toolpaths(canvas, print_object, tool_colors) + SV *canvas; + PrintObject *print_object; std::vector<std::string> tool_colors; - int use_VBOs; CODE: - _3DScene::_load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs != 0); + _3DScene::load_print_object_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), print_object, tool_colors); void -_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs) - Print *print; - GLVolumeCollection *volumes; +load_wipe_tower_toolpaths(canvas, tool_colors) + SV *canvas; std::vector<std::string> tool_colors; - int use_VBOs; CODE: - _3DScene::_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs != 0); + _3DScene::load_wipe_tower_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), tool_colors); void -load_gcode_preview(print, preview_data, volumes, str_tool_colors, use_VBOs) - Print *print; +load_gcode_preview(canvas, preview_data, str_tool_colors) + SV *canvas; GCodePreviewData *preview_data; - GLVolumeCollection *volumes; std::vector<std::string> str_tool_colors; - int use_VBOs; CODE: - _3DScene::load_gcode_preview(print, preview_data, volumes, str_tool_colors, use_VBOs != 0); + _3DScene::load_gcode_preview((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), preview_data, str_tool_colors); %} diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index ef9c5345f..b53b5e82d 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -52,6 +52,8 @@ _constant() int region_count() %code%{ RETVAL = THIS->print()->regions.size(); %}; + int region_volumes_count() + %code%{ RETVAL = THIS->region_volumes.size(); %}; Ref<Print> print(); Ref<ModelObject> model_object(); Ref<StaticPrintConfig> config() @@ -119,15 +121,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() diff --git a/xs/xsp/Utils_OctoPrint.xsp b/xs/xsp/Utils_OctoPrint.xsp index 282a3055d..28610cb01 100644 --- a/xs/xsp/Utils_OctoPrint.xsp +++ b/xs/xsp/Utils_OctoPrint.xsp @@ -9,5 +9,5 @@ OctoPrint(DynamicPrintConfig *config); ~OctoPrint(); - bool send_gcode(std::string filename, bool print = false) const; + bool send_gcode(std::string filename) const; }; |