diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 8bc57f53d..8acb9ba27 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -19,7 +19,8 @@ package Slic3r::GUI::3DScene::Base; use strict; use warnings; -use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS); +use Wx qw(:timer); +use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS EVT_TIMER); # must load OpenGL *before* Wx::GLCanvas use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants); use base qw(Wx::GLCanvas Class::Accessor); @@ -143,6 +144,10 @@ sub new { $self->{layer_preview_z_texture_width} = 512; $self->{layer_preview_z_texture_height} = 512; $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; @@ -157,42 +162,24 @@ sub new { $self->Resize( $self->GetSizeWH ); $self->Refresh; }); - EVT_MOUSEWHEEL($self, sub { - my ($self, $e) = @_; - - # 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; - $self->_zoom($self->_zoom / (1-$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->_dirty(1); - $self->Refresh; - }); + EVT_MOUSEWHEEL($self, \&mouse_wheel_event); EVT_MOUSE_EVENTS($self, \&mouse_event); + $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; + return if $self->{layer_height_edit_last_object_id} == -1; + $self->_variable_layer_thickness_action(undef, 1); + }); + return $self; } sub Destroy { my ($self) = @_; + $self->{layer_height_edit_timer}->Stop; $self->DestroyGL; return $self->SUPER::Destroy; } @@ -207,43 +194,74 @@ sub _first_selected_object_id { 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 { + my ($self) = @_; + my ($cw, $ch) = $self->GetSizeWH; + my $bar_width = 70; + return ($cw - $bar_width, 0, $cw, $ch); +} + +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; + 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 { + my ($self, $object_idx, $mouse_evt) = @_; + my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect; + return unscale($self->{print}->get_object($object_idx)->size->z) * ($bar_bottom - $mouse_evt->GetY - 1.) / ($bar_bottom - $bar_top); +} + +sub _variable_layer_thickness_action { + my ($self, $mouse_event, $do_modification) = @_; + # A volume is selected. Test, whether hovering over a layer thickness bar. + if (defined($mouse_event)) { + $self->{layer_height_edit_last_z} = $self->_variable_layer_thickness_bar_mouse_cursor_z($self->{layer_height_edit_last_object_id}, $mouse_event); + $self->{layer_height_edit_last_action} = $mouse_event->ShiftDown ? ($mouse_event->RightIsDown ? 3 : 2) : ($mouse_event->RightIsDown ? 0 : 1); + } + if ($self->{layer_height_edit_last_object_id} != -1) { + $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->{print}->get_object($self->{layer_height_edit_last_object_id})->generate_layer_height_texture( + $self->volumes->[$self->{layer_height_edit_last_object_id}]->layer_height_texture_data->ptr, + $self->{layer_preview_z_texture_height}, + $self->{layer_preview_z_texture_width}); + $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 : -1; + if ($e->Entering && &Wx::wxMSW) { # wxMSW needs focus in order to catch mouse wheel events $self->SetFocus; } elsif ($e->LeftDClick) { - $self->on_double_click->() - if $self->on_double_click; + 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 ($self->layer_editing_enabled && $self->{print}) { - my $object_idx_selected = $self->_first_selected_object_id; - if ($object_idx_selected != -1) { - # A volume is selected. Test, whether hovering over a layer thickness bar. - my ($cw, $ch) = $self->GetSizeWH; - my $bar_width = 70; - if ($e->GetX >= $cw - $bar_width) { - # Start editing the layer height. - $self->_layer_height_edited(1); - my $z = unscale($self->{print}->get_object($object_idx_selected)->size->z) * ($ch - $e->GetY - 1.) / ($ch - 1); -# print "Modifying height profile at $z\n"; -# $self->{print}->get_object($object_idx_selected)->adjust_layer_height_profile($z, $e->RightDown ? - 0.05 : 0.05, 2., 0); - $self->{print}->get_object($object_idx_selected)->generate_layer_height_texture( - $self->volumes->[$object_idx_selected]->layer_height_texture_data->ptr, - $self->{layer_preview_z_texture_height}, - $self->{layer_preview_z_texture_width}); - $self->Refresh; - } - } - } - - if (! $self->_layer_height_edited) { + 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, 1); + } 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. @@ -304,21 +322,8 @@ sub mouse_event { $self->_dragged(1); $self->Refresh; } elsif ($e->Dragging) { - if ($self->_layer_height_edited) { - my $object_idx_selected = $self->_first_selected_object_id; - if ($object_idx_selected != -1) { - # A volume is selected. Test, whether hovering over a layer thickness bar. - my ($cw, $ch) = $self->GetSizeWH; - my $z = unscale($self->{print}->get_object($object_idx_selected)->size->z) * ($ch - $e->GetY - 1.) / ($ch - 1); -# print "Modifying height profile at $z\n"; - my $strength = 0.005; - $self->{print}->get_object($object_idx_selected)->adjust_layer_height_profile($z, $e->RightIsDown ? - $strength : $strength, 2., $e->ShiftDown ? 1 : 0); - $self->{print}->get_object($object_idx_selected)->generate_layer_height_texture( - $self->volumes->[$object_idx_selected]->layer_height_texture_data->ptr, - $self->{layer_preview_z_texture_height}, - $self->{layer_preview_z_texture_width}); - $self->Refresh; - } + if ($self->_layer_height_edited && $object_idx_selected != -1) { + $self->_variable_layer_thickness_action($e, 0); } elsif ($e->LeftIsDown) { # if dragging over blank area with left button, rotate if (defined $self->_drag_start_pos) { @@ -375,6 +380,7 @@ sub mouse_event { $self->_drag_start_xy(undef); $self->_dragged(undef); $self->_layer_height_edited(undef); + $self->{layer_height_edit_timer}->Stop; } 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 @@ -385,6 +391,49 @@ sub mouse_event { } } +sub mouse_wheel_event { + my ($self, $e) = @_; + + if ($self->layer_editing_enabled && $self->{print}) { + my $object_idx_selected = $self->_first_selected_object_id; + 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; + $self->_zoom($self->_zoom / (1-$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->_dirty(1); + $self->Refresh; +} + # Reset selection. sub reset_objects { my ($self) = @_; @@ -1084,14 +1133,17 @@ sub draw_volumes { my $z_to_texture_row_id = $self->{shader}->Map('z_to_texture_row'); my $z_texture_row_to_normalized_id = $self->{shader}->Map('z_texture_row_to_normalized'); my $z_cursor_id = $self->{shader}->Map('z_cursor'); + my $z_cursor_band_width_id = $self->{shader}->Map('z_cursor_band_width'); die if ! defined($z_to_texture_row_id); die if ! defined($z_texture_row_to_normalized_id); die if ! defined($z_cursor_id); + die if ! defined($z_cursor_band_width_id); my $ncells = $volume->{layer_height_texture_cells}; my $z_max = $volume->{bounding_box}->z_max; glUniform1fARB($z_to_texture_row_id, ($ncells - 1) / ($self->{layer_preview_z_texture_width} * $z_max)); glUniform1fARB($z_texture_row_to_normalized_id, 1. / $self->{layer_preview_z_texture_height}); glUniform1fARB($z_cursor_id, $z_max * $z_cursor_relative); + glUniform1fARB($z_cursor_band_width_id, $self->{layer_height_edit_band_width}); glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id}); # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LEVEL, 0); # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); @@ -1421,7 +1473,7 @@ void main() float z_texture_col = object_z_row - z_texture_row; // float z_blend = 0.5 + 0.5 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) / 3.))); // float z_blend = 0.5 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor)))) + 0.5; - float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor)))) + 0.25; + float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25; // Scale z_texture_row to normalized coordinates. // Sample the Z texture. gl_FragColor = diff --git a/xs/src/libslic3r/Slicing.cpp b/xs/src/libslic3r/Slicing.cpp index 5bd6bfc27..8c3902ba3 100644 --- a/xs/src/libslic3r/Slicing.cpp +++ b/xs/src/libslic3r/Slicing.cpp @@ -290,7 +290,7 @@ void adjust_layer_height_profile( coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, - int action) + LayerHeightEditActionType action) { // Constrain the profile variability by the 1st layer height. std::pair z_span_variable = @@ -320,8 +320,10 @@ void adjust_layer_height_profile( // 2) Is it possible to apply the delta? switch (action) { - case 0: - default: + case LAYER_HEIGHT_EDIT_ACTION_DECREASE: + layer_thickness_delta = - layer_thickness_delta; + // fallthrough + case LAYER_HEIGHT_EDIT_ACTION_INCREASE: if (layer_thickness_delta > 0) { if (current_layer_height >= slicing_params.max_layer_height - EPSILON) return; @@ -332,12 +334,16 @@ void adjust_layer_height_profile( layer_thickness_delta = std::max(layer_thickness_delta, slicing_params.min_layer_height - current_layer_height); } break; - case 1: + case LAYER_HEIGHT_EDIT_ACTION_REDUCE: + case LAYER_HEIGHT_EDIT_ACTION_SMOOTH: layer_thickness_delta = std::abs(layer_thickness_delta); layer_thickness_delta = std::min(layer_thickness_delta, std::abs(slicing_params.layer_height - current_layer_height)); if (layer_thickness_delta < EPSILON) return; break; + default: + assert(false); + break; } // 3) Densify the profile inside z +- band_width/2, remove duplicate Zs from the height profile inside the band. @@ -354,6 +360,7 @@ void adjust_layer_height_profile( assert(i >= 0 && i + 1 < layer_height_profile.size()); profile_new.insert(profile_new.end(), layer_height_profile.begin(), layer_height_profile.begin() + i + 2); coordf_t zz = lo; + size_t i_resampled_start = profile_new.size(); while (zz < hi) { size_t next = i + 2; coordf_t z1 = layer_height_profile[i]; @@ -368,11 +375,11 @@ void adjust_layer_height_profile( coordf_t weight = std::abs(zz - z) < 0.5 * band_width ? (0.5 + 0.5 * cos(2. * M_PI * (zz - z) / band_width)) : 0.; coordf_t height_new = height; switch (action) { - case 0: - default: + case LAYER_HEIGHT_EDIT_ACTION_INCREASE: + case LAYER_HEIGHT_EDIT_ACTION_DECREASE: height += weight * layer_thickness_delta; break; - case 1: + case LAYER_HEIGHT_EDIT_ACTION_REDUCE: { coordf_t delta = height - slicing_params.layer_height; coordf_t step = weight * layer_thickness_delta; @@ -382,6 +389,14 @@ void adjust_layer_height_profile( height += step; break; } + case LAYER_HEIGHT_EDIT_ACTION_SMOOTH: + { + // Don't modify the profile during resampling process, do it at the next step. + break; + } + default: + assert(false); + break; } // Avoid entering a too short segment. if (profile_new[profile_new.size() - 2] + EPSILON < zz) { @@ -396,15 +411,35 @@ void adjust_layer_height_profile( } i += 2; + assert(i > 0); + size_t i_resampled_end = profile_new.size(); if (i < layer_height_profile.size()) { - if (profile_new[profile_new.size() - 2] + z_step < layer_height_profile[i]) { - profile_new.push_back(profile_new[profile_new.size() - 2] + z_step); - profile_new.push_back(layer_height_profile[i + 1]); - } + assert(zz >= layer_height_profile[i - 2]); + assert(zz <= layer_height_profile[i]); +// profile_new.push_back(zz); +// profile_new.push_back(layer_height_profile[i + 1]); profile_new.insert(profile_new.end(), layer_height_profile.begin() + i, layer_height_profile.end()); } layer_height_profile = std::move(profile_new); + if (action == LAYER_HEIGHT_EDIT_ACTION_SMOOTH) { + size_t n_rounds = 6; + for (size_t i_round = 0; i_round < n_rounds; ++ i_round) { + profile_new = layer_height_profile; + for (size_t i = i_resampled_start; i < i_resampled_end; i += 2) { + coordf_t zz = profile_new[i]; + coordf_t t = std::abs(zz - z) < 0.5 * band_width ? (0.25 + 0.25 * cos(2. * M_PI * (zz - z) / band_width)) : 0.; + assert(t >= 0. && t <= 0.5000001); + if (i == 0) + layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + t * profile_new[i + 3]; + else if (i + 1 == profile_new.size()) + layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + t * profile_new[i - 1]; + else + layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + 0.5 * t * (profile_new[i - 1] + profile_new[i + 3]); + } + } + } + assert(layer_height_profile.size() > 2); assert(layer_height_profile.size() % 2 == 0); assert(layer_height_profile[0] == 0.); diff --git a/xs/src/libslic3r/Slicing.hpp b/xs/src/libslic3r/Slicing.hpp index 02ae7dd9e..5105b9518 100644 --- a/xs/src/libslic3r/Slicing.hpp +++ b/xs/src/libslic3r/Slicing.hpp @@ -103,13 +103,21 @@ extern std::vector layer_height_profile_adaptive( const t_layer_height_ranges &layer_height_ranges, const ModelVolumePtrs &volumes); + +enum LayerHeightEditActionType { + LAYER_HEIGHT_EDIT_ACTION_INCREASE = 0, + LAYER_HEIGHT_EDIT_ACTION_DECREASE = 1, + LAYER_HEIGHT_EDIT_ACTION_REDUCE = 2, + LAYER_HEIGHT_EDIT_ACTION_SMOOTH = 3 +}; + extern void adjust_layer_height_profile( const SlicingParameters &slicing_params, std::vector &layer_height_profile, coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, - int action); + LayerHeightEditActionType action); // Produce object layers as pairs of low / high layer boundaries, stored into a linear vector. // The object layers are based at z=0, ignoring the raft layers. diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 4496f303c..d2fca6105 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -125,7 +125,7 @@ _constant() %code%{ THIS->update_layer_height_profile(); adjust_layer_height_profile( - THIS->slicing_parameters(), THIS->layer_height_profile, z, layer_thickness_delta, band_width, action); + THIS->slicing_parameters(), THIS->layer_height_profile, z, layer_thickness_delta, band_width, LayerHeightEditActionType(action)); %}; int generate_layer_height_texture(void *data, int rows, int cols, bool level_of_detail_2nd_level = true)