diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index edb5eb492..2be09e596 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -53,10 +53,15 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init origin _mouse_pos _hover_volume_idx + _drag_volume_idx _drag_start_pos _drag_start_xy _dragged + + layer_editing_enabled + _layer_height_edited + _camera_type _camera_target _camera_distance @@ -74,7 +79,7 @@ use constant SELECTED_COLOR => [0,1,0,1]; use constant HOVER_COLOR => [0.4,0.9,0,1]; # phi / theta angles to orient the camera. -use constant VIEW_ISO => [45.0,45.0]; +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]; @@ -82,7 +87,11 @@ 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 GIMBAL_LOCK_THETA_MAX => 170; +use constant MANIPULATION_IDLE => 0; +use constant MANIPULATION_DRAGGING => 1; +use constant MANIPULATION_LAYER_HEIGHT => 2; + +use constant GIMBALL_LOCK_THETA_MAX => 170; # make OpenGL::Array thread-safe { @@ -129,6 +138,11 @@ sub new { # $self->_camera_type('perspective'); $self->_camera_target(Slic3r::Pointf3->new(0,0,0)); $self->_camera_distance(0.); + + # Size of a layer height texture, used by a shader to color map the object print layers. + $self->{layer_preview_z_texture_width} = 512; + $self->{layer_preview_z_texture_height} = 512; + $self->{layer_height_edit_band_width} = 2.; $self->reset_objects; @@ -177,6 +191,16 @@ sub new { return $self; } +sub _first_selected_object_id { + my ($self) = @_; + for my $i (0..$#{$self->volumes}) { + if ($self->volumes->[$i]->selected) { + return int($self->volumes->[$i]->select_group_id / 1000000); + } + } + return -1; +} + sub mouse_event { my ($self, $e) = @_; @@ -191,40 +215,65 @@ sub mouse_event { # 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; - - # select volume in this 3D canvas - if ($self->enable_picking) { - $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->_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; } } - - $self->Refresh; } - - # propagate event through callback - $self->on_select->($volume_idx) - if $self->on_select; - - if ($volume_idx != -1) { - if ($e->LeftDown && $self->enable_moving) { - $self->_drag_volume_idx($volume_idx); - $self->_drag_start_pos($self->mouse_to_3d(@$pos)); - } elsif ($e->RightDown) { - # if right clicking on volume, propagate event through callback - $self->on_right_click->($e->GetPosition) - if $self->on_right_click; + + if (! $self->_layer_height_edited) { + # 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; + } + + # propagate event through callback + $self->on_select->($volume_idx) + if $self->on_select; + + if ($volume_idx != -1) { + if ($e->LeftDown && $self->enable_moving) { + $self->_drag_volume_idx($volume_idx); + $self->_drag_start_pos($self->mouse_to_3d(@$pos)); + } 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 && defined($self->_drag_volume_idx)) { + } 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 $mouse_ray = $self->mouse_ray($e->GetX, $e->GetY); my $cur_pos = $mouse_ray->intersect_plane($self->_drag_start_pos->z); @@ -249,14 +298,29 @@ sub mouse_event { $self->_dragged(1); $self->Refresh; } elsif ($e->Dragging) { - if ($e->LeftIsDown) { + 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; + } + } 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) { $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE); $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #- - $self->_stheta(GIMBAL_LOCK_THETA_MAX) if $self->_stheta > GIMBAL_LOCK_THETA_MAX; + $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; @@ -304,6 +368,7 @@ sub mouse_event { $self->_drag_start_pos(undef); $self->_drag_start_xy(undef); $self->_dragged(undef); + $self->_layer_height_edited(undef); } elsif ($e->Moving) { $self->_mouse_pos($pos); $self->Refresh; @@ -340,8 +405,8 @@ sub select_view { if (ref($direction)) { $dirvec = $direction; } else { - if ($direction eq 'iso') { - $dirvec = VIEW_ISO; + if ($direction eq 'default') { + $dirvec = VIEW_DEFAULT; } elsif ($direction eq 'left') { $dirvec = VIEW_LEFT; } elsif ($direction eq 'right') { @@ -356,22 +421,18 @@ sub select_view { $dirvec = VIEW_REAR; } } - - $self->_sphi($dirvec->[0]); - $self->_stheta($dirvec->[1]); - - # Avoid gimbal lock. - $self->_stheta(GIMBAL_LOCK_THETA_MAX) if $self->_stheta > GIMBAL_LOCK_THETA_MAX; - $self->_stheta(0) if $self->_stheta < 0; - - # View everything. - $self->volumes_bounding_box->defined - ? $self->zoom_to_volumes - : $self->zoom_to_bed; - - $self->on_viewport_changed->() if $self->on_viewport_changed; - $self->_dirty(1); - $self->Refresh; + 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; + # View everything. + $self->zoom_to_bounding_box($bb); + $self->on_viewport_changed->() if $self->on_viewport_changed; + $self->Refresh; + } } sub zoom_to_bounding_box { @@ -380,7 +441,7 @@ sub zoom_to_bounding_box { # calculate the zoom factor needed to adjust viewport to # bounding box - my $max_size = max(@{$bb->size}) * 1.05; + my $max_size = max(@{$bb->size}) * 2; my $min_viewport_size = min($self->GetSizeWH); $self->_zoom($min_viewport_size / $max_size); @@ -725,12 +786,19 @@ sub InitGL { $self->init(1); my $shader; -# $shader = $self->{shader} = new Slic3r::GUI::GLShader; + $shader = $self->{shader} = new Slic3r::GUI::GLShader; if ($self->{shader}) { my $info = $shader->Load($self->_fragment_shader, $self->_vertex_shader); print $info if $info; + + ($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_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + glBindTexture(GL_TEXTURE_2D, 0); } - + glClearColor(0, 0, 0, 1); glColor3f(1, 0, 0); glEnable(GL_DEPTH_TEST); @@ -808,11 +876,15 @@ sub Render { 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) { # 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. + glDisable(GL_MULTISAMPLE); glDisable(GL_LIGHTING); $self->draw_volumes(1); glFlush(); @@ -839,6 +911,7 @@ sub Render { glFlush(); glFinish(); glEnable(GL_LIGHTING); + glEnable(GL_MULTISAMPLE); } # draw fixed background @@ -938,7 +1011,7 @@ sub Render { # draw objects $self->draw_volumes; - + # draw cutting plane if (defined $self->cutting_plane_z) { my $plane_z = $self->cutting_plane_z; @@ -957,6 +1030,8 @@ sub Render { glEnable(GL_CULL_FACE); glDisable(GL_BLEND); } + + $self->draw_active_object_annotations; glFlush(); @@ -967,18 +1042,64 @@ 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) = @_; - $self->{shader}->Enable if (! $fakecolor && $self->{shader}); - glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); + # 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 ($cw, $ch) = $self->GetSizeWH; + my $bar_width = 70; + my ($bar_left, $bar_right) = ((0.5 * $cw - $bar_width)/$self->_zoom, $cw/(2*$self->_zoom)); + my ($bar_bottom, $bar_top) = (-$ch/(2*$self->_zoom), $ch/(2*$self->_zoom)); + my $mouse_pos = $self->ScreenToClientPoint(Wx::GetMousePosition()); + my $z_cursor_relative = ($mouse_pos->x < $cw - $bar_width) ? -1000. : + ($ch - $mouse_pos->y - 1.) / ($ch - 1); + foreach my $volume_idx (0..$#{$self->volumes}) { my $volume = $self->volumes->[$volume_idx]; - - if ($fakecolor) { + + my $shader_active = 0; + if ($self->layer_editing_enabled && ! $fakecolor && $volume->selected && $self->{shader} && $volume->{layer_height_texture_data} && $volume->{layer_height_texture_cells}) { + $self->{shader}->Enable; + 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'); + die if ! defined($z_to_texture_row_id); + die if ! defined($z_texture_row_to_normalized_id); + die if ! defined($z_cursor_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); + 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); + if (1) { + glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $self->{layer_preview_z_texture_width}, $self->{layer_preview_z_texture_height}, + 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $self->{layer_preview_z_texture_width} / 2, $self->{layer_preview_z_texture_height} / 2, + 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +# glPixelStorei(GL_UNPACK_ALIGNMENT, 1); +# glPixelStorei(GL_UNPACK_ROW_LENGTH, $self->{layer_preview_z_texture_width}); + glTexSubImage2D_c(GL_TEXTURE_2D, 0, 0, 0, $self->{layer_preview_z_texture_width}, $self->{layer_preview_z_texture_height}, + GL_RGBA, GL_UNSIGNED_BYTE, $volume->{layer_height_texture_data}->ptr); + glTexSubImage2D_c(GL_TEXTURE_2D, 1, 0, 0, $self->{layer_preview_z_texture_width} / 2, $self->{layer_preview_z_texture_height} / 2, + GL_RGBA, GL_UNSIGNED_BYTE, $volume->{layer_height_texture_data}->offset($self->{layer_preview_z_texture_width} * $self->{layer_preview_z_texture_height} * 4)); + } else { + glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $self->{layer_preview_z_texture_width}, $self->{layer_preview_z_texture_height}, + 0, GL_RGBA, GL_UNSIGNED_BYTE, $volume->{layer_height_texture_data}->ptr); + glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $self->{layer_preview_z_texture_width}/2, $self->{layer_preview_z_texture_height}/2, + 0, GL_RGBA, GL_UNSIGNED_BYTE, $volume->{layer_height_texture_data}->ptr + $self->{layer_preview_z_texture_width} * $self->{layer_preview_z_texture_height} * 4); + } + +# my $nlines = ceil($ncells / ($self->{layer_preview_z_texture_width} - 1)); + + $shader_active = 1; + } elsif ($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; @@ -1055,8 +1176,13 @@ sub draw_volumes { } glVertexPointer_c(3, GL_FLOAT, 0, 0); - glNormalPointer_c(GL_FLOAT, 0, 0); + glNormalPointer_c(GL_FLOAT, 0, 0); glPopMatrix(); + + if ($shader_active) { + glBindTexture(GL_TEXTURE_2D, 0); + $self->{shader}->Disable; + } } glDisableClientState(GL_NORMAL_ARRAY); glDisable(GL_BLEND); @@ -1069,8 +1195,93 @@ sub draw_volumes { glVertexPointer_c(3, GL_FLOAT, 0, 0); } glDisableClientState(GL_VERTEX_ARRAY); +} - $self->{shader}->Disable if (! $fakecolor && $self->{shader}); +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->{shader} || ! $self->layer_editing_enabled); + + my $volume; + foreach my $volume_idx (0..$#{$self->volumes}) { + my $v = $self->volumes->[$volume_idx]; + if ($v->selected && $v->{layer_height_texture_data} && $v->{layer_height_texture_cells}) { + $volume = $v; + last; + } + } + return if (! $volume); + + # 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 ($cw, $ch) = $self->GetSizeWH; + my $bar_width = 70; + my ($bar_left, $bar_right) = ((0.5 * $cw - $bar_width)/$self->_zoom, $cw/(2*$self->_zoom)); + my ($bar_bottom, $bar_top) = (-$ch/(2*$self->_zoom), $ch/(2*$self->_zoom)); + my $mouse_pos = $self->ScreenToClientPoint(Wx::GetMousePosition()); + my $z_cursor_relative = ($mouse_pos->x < $cw - $bar_width) ? -1000. : + ($ch - $mouse_pos->y - 1.) / ($ch - 1); + + $self->{shader}->Enable; + 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 $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); + glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id}); + glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $self->{layer_preview_z_texture_width}, $self->{layer_preview_z_texture_height}, + 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $self->{layer_preview_z_texture_width} / 2, $self->{layer_preview_z_texture_height} / 2, + 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexSubImage2D_c(GL_TEXTURE_2D, 0, 0, 0, $self->{layer_preview_z_texture_width}, $self->{layer_preview_z_texture_height}, + GL_RGBA, GL_UNSIGNED_BYTE, $volume->{layer_height_texture_data}->ptr); + glTexSubImage2D_c(GL_TEXTURE_2D, 1, 0, 0, $self->{layer_preview_z_texture_width} / 2, $self->{layer_preview_z_texture_height} / 2, + GL_RGBA, GL_UNSIGNED_BYTE, $volume->{layer_height_texture_data}->offset($self->{layer_preview_z_texture_width} * $self->{layer_preview_z_texture_height} * 4)); + + # 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, $volume->{bounding_box}->z_max); + glVertex3f($bar_left, $bar_top, $volume->{bounding_box}->z_max); + glEnd(); + glBindTexture(GL_TEXTURE_2D, 0); + $self->{shader}->Disable; + + # Paint the graph. + my $object_idx = int($volume->select_group_id / 1000000); + my $print_object = $self->{print}->get_object($object_idx); + my $max_z = unscale($print_object->size->z); + my $profile = $print_object->layer_height_profile; + my $layer_height = $print_object->config->get('layer_height'); + # Baseline + glColor3f(0., 0., 0.); + glBegin(GL_LINE_STRIP); + glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / 0.45, $bar_bottom); + glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / 0.45, $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) / 0.45, $bar_bottom + $z * ($bar_top - $bar_bottom) / $max_z, $z); + } + glEnd(); + # Revert the matrices. + glPopMatrix(); + glEnable(GL_DEPTH_TEST); } sub _report_opengl_state @@ -1106,57 +1317,61 @@ sub _vertex_shader { return <<'VERTEX'; #version 110 +#define LIGHT_TOP_DIR 0., 1., 0. +#define LIGHT_TOP_DIFFUSE 0.2 +#define LIGHT_TOP_SPECULAR 0.3 + +#define LIGHT_FRONT_DIR 0., 0., 1. +#define LIGHT_FRONT_DIFFUSE 0.5 +#define LIGHT_FRONT_SPECULAR 0.3 + +#define INTENSITY_AMBIENT 0.1 + +uniform float z_to_texture_row; +varying float intensity_specular; +varying float intensity_tainted; varying float object_z; void main() { - vec3 normal, lightDir, viewVector, halfVector; - vec4 diffuse, ambient, globalAmbient, specular = vec4(0.0); - float NdotL,NdotHV; - + vec3 eye, normal, lightDir, viewVector, halfVector; + float NdotL, NdotHV; + +// eye = gl_ModelViewMatrixInverse[3].xyz; + eye = vec3(0., 0., 1.); + // First transform the normal into eye space and normalize the result. normal = normalize(gl_NormalMatrix * gl_Normal); // Now normalize the light's direction. Note that according to the OpenGL specification, the light is stored in eye space. // Also since we're talking about a directional light, the position field is actually direction. - lightDir = normalize(vec3(gl_LightSource[0].position)); + lightDir = vec3(LIGHT_TOP_DIR); + halfVector = normalize(lightDir + eye); // 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. NdotL = max(dot(normal, lightDir), 0.0); - - // Compute the diffuse, ambient and globalAmbient terms. -// diffuse = NdotL * (gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse); -// ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient; - diffuse = NdotL * (gl_Color * gl_LightSource[0].diffuse); - ambient = gl_Color * gl_LightSource[0].ambient; - globalAmbient = gl_LightModel.ambient * gl_FrontMaterial.ambient; - - // compute the specular term if NdotL is larger than zero - if (NdotL > 0.0) { - NdotHV = max(dot(normal, normalize(gl_LightSource[0].halfVector.xyz)),0.0); - specular = gl_FrontMaterial.specular * gl_LightSource[0].specular * pow(NdotHV,gl_FrontMaterial.shininess); - } + + intensity_tainted = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + intensity_specular = 0.; + +// if (NdotL > 0.0) +// intensity_specular = LIGHT_TOP_SPECULAR * pow(max(dot(normal, halfVector), 0.0), gl_FrontMaterial.shininess); // Perform the same lighting calculation for the 2nd light source. - lightDir = normalize(vec3(gl_LightSource[1].position)); + lightDir = vec3(LIGHT_FRONT_DIR); + halfVector = normalize(lightDir + eye); NdotL = max(dot(normal, lightDir), 0.0); -// diffuse += NdotL * (gl_FrontMaterial.diffuse * gl_LightSource[1].diffuse); -// ambient += gl_FrontMaterial.ambient * gl_LightSource[1].ambient; - diffuse += NdotL * (gl_Color * gl_LightSource[1].diffuse); - ambient += gl_Color * gl_LightSource[1].ambient; + intensity_tainted += NdotL * LIGHT_FRONT_DIFFUSE; - // compute the specular term if NdotL is larger than zero - if (NdotL > 0.0) { - NdotHV = max(dot(normal, normalize(gl_LightSource[1].halfVector.xyz)),0.0); - specular += gl_FrontMaterial.specular * gl_LightSource[1].specular * pow(NdotHV,gl_FrontMaterial.shininess); - } - - gl_FrontColor = globalAmbient + diffuse + ambient + specular; - gl_FrontColor.a = 1.; - - gl_Position = ftransform(); + // compute the specular term if NdotL is larger than zero + if (NdotL > 0.0) + intensity_specular += LIGHT_FRONT_SPECULAR * pow(max(dot(normal, halfVector), 0.0), gl_FrontMaterial.shininess); + + // Scaled to widths of the Z texture. object_z = gl_Vertex.z / gl_Vertex.w; + + gl_Position = ftransform(); } VERTEX @@ -1165,17 +1380,39 @@ VERTEX sub _fragment_shader { 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; + +varying float intensity_specular; +varying float intensity_tainted; varying float object_z; +uniform float z_cursor; +uniform float z_cursor_band_width; void main() { - float layer_height = 0.25; - float layer_height2 = 0.5 * layer_height; - float layer_center = floor(object_z / layer_height) * layer_height + layer_height2; - float intensity = cos(M_PI * 0.7 * (layer_center - object_z) / layer_height); - gl_FragColor = gl_Color * intensity; + 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.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; + // Scale z_texture_row to normalized coordinates. + // Sample the Z texture. + gl_FragColor = + vec4(intensity_specular, intensity_specular, intensity_specular, 1.) + +// intensity_tainted * texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5)), -2.5); + (1. - z_blend) * intensity_tainted * texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5)), -200.) + + z_blend * vec4(1., 1., 0., 0.); + // and reset the transparency. gl_FragColor.a = 1.; } @@ -1189,6 +1426,8 @@ use Moo; has 'bounding_box' => (is => 'ro', required => 1); has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf3->new(0,0,0) }); has 'color' => (is => 'ro', required => 1); +# An ID containing the object ID, volume ID and instance ID. +has 'composite_id' => (is => 'rw', default => sub { -1 }); # An ID for group selection. It may be the same for all meshes of all object instances, or for just a single object instance. has 'select_group_id' => (is => 'rw', default => sub { -1 }); # An ID for group dragging. It may be the same for all meshes of all object instances, or for just a single object instance. @@ -1210,6 +1449,26 @@ has 'tverts' => (is => 'rw'); # The offsets stores tripples of (z_top, qverts_idx, tverts_idx) in a linear array. has 'offsets' => (is => 'rw'); +# RGBA texture along the Z axis of an object, to visualize layers by stripes colored by their height. +has 'layer_height_texture_data' => (is => 'rw'); +# Number of texture cells. +has 'layer_height_texture_cells' => (is => 'rw'); + +sub object_idx { + my ($self) = @_; + return $self->composite_id / 1000000; +} + +sub volume_idx { + my ($self) = @_; + return ($self->composite_id / 1000) % 1000; +} + +sub instance_idx { + my ($self) = @_; + return $self->composite_id % 1000; +} + sub transformed_bounding_box { my ($self) = @_; @@ -1235,8 +1494,6 @@ __PACKAGE__->mk_accessors(qw( color_by select_by drag_by - volumes_by_object - _objects_by_volumes )); sub new { @@ -1246,14 +1503,12 @@ sub new { $self->color_by('volume'); # object | volume $self->select_by('object'); # object | volume | instance $self->drag_by('instance'); # object | instance - $self->volumes_by_object({}); # obj_idx => [ volume_idx, volume_idx ... ] - $self->_objects_by_volumes({}); # volume_idx => [ obj_idx, instance_idx ] return $self; } sub load_object { - my ($self, $model, $obj_idx, $instance_idxs) = @_; + my ($self, $model, $print, $obj_idx, $instance_idxs) = @_; my $model_object; if ($model->isa('Slic3r::Model::Object')) { @@ -1265,6 +1520,19 @@ sub load_object { } $instance_idxs ||= [0..$#{$model_object->instances}]; + + # Object will have a single common layer height texture for all volumes. + my $layer_height_texture_data; + my $layer_height_texture_cells; + if ($print && $obj_idx < $print->object_count) { + # Generate the layer height texture. Allocate data for the 0th and 1st mipmap levels. + $layer_height_texture_data = OpenGL::Array->new($self->{layer_preview_z_texture_width}*$self->{layer_preview_z_texture_height}*5, GL_UNSIGNED_BYTE); +# $print->get_object($obj_idx)->update_layer_height_profile_from_ranges(); + $layer_height_texture_cells = $print->get_object($obj_idx)->generate_layer_height_texture( + $layer_height_texture_data->ptr, + $self->{layer_preview_z_texture_height}, + $self->{layer_preview_z_texture_width}); + } my @volumes_idx = (); foreach my $volume_idx (0..$#{$model_object->volumes}) { @@ -1287,16 +1555,18 @@ sub load_object { # not correspond to the color of the filament. my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ]; $color->[3] = $volume->modifier ? 0.5 : 1; + print "Reloading object $volume_idx, $instance_idx\n"; push @{$self->volumes}, my $v = Slic3r::GUI::3DScene::Volume->new( bounding_box => $mesh->bounding_box, color => $color, ); + $v->composite_id($obj_idx*1000000 + $volume_idx*1000 + $instance_idx); if ($self->select_by eq 'object') { $v->select_group_id($obj_idx*1000000); } elsif ($self->select_by eq 'volume') { $v->select_group_id($obj_idx*1000000 + $volume_idx*1000); } elsif ($self->select_by eq 'instance') { - $v->select_group_id($obj_idx*1000000 + $volume_idx*1000 + $instance_idx); + $v->select_group_id($v->composite_id); } if ($self->drag_by eq 'object') { $v->drag_group_id($obj_idx*1000); @@ -1304,15 +1574,18 @@ sub load_object { $v->drag_group_id($obj_idx*1000 + $instance_idx); } push @volumes_idx, my $scene_volume_idx = $#{$self->volumes}; - $self->_objects_by_volumes->{$scene_volume_idx} = [ $obj_idx, $volume_idx, $instance_idx ]; my $verts = Slic3r::GUI::_3DScene::GLVertexArray->new; $verts->load_mesh($mesh); $v->tverts($verts); + + if (! $volume->modifier) { + $v->layer_height_texture_data($layer_height_texture_data); + $v->layer_height_texture_cells($layer_height_texture_cells); + } } } - $self->volumes_by_object->{$obj_idx} = [@volumes_idx]; return @volumes_idx; } @@ -1651,19 +1924,4 @@ sub _extrusionentity_to_verts { $tverts); } -sub object_idx { - my ($self, $volume_idx) = @_; - return $self->_objects_by_volumes->{$volume_idx}[0]; -} - -sub volume_idx { - my ($self, $volume_idx) = @_; - return $self->_objects_by_volumes->{$volume_idx}[1]; -} - -sub instance_idx { - my ($self, $volume_idx) = @_; - return $self->_objects_by_volumes->{$volume_idx}[2]; -} - 1; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 54f66e221..ef9868735 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -8,10 +8,11 @@ use utf8; use File::Basename qw(basename dirname); use List::Util qw(sum first max); use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad); +use LWP::UserAgent; use threads::shared qw(shared_clone); use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc :panel :sizer :toolbar :window wxTheApp :notebook :combobox); -use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED +use Wx::Event qw(EVT_BUTTON EVT_TOGGLEBUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL EVT_CHOICE EVT_COMBOBOX EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED); use base 'Wx::Panel'; @@ -30,6 +31,7 @@ use constant TB_SCALE => &Wx::NewId; use constant TB_SPLIT => &Wx::NewId; use constant TB_CUT => &Wx::NewId; use constant TB_SETTINGS => &Wx::NewId; +use constant TB_LAYER_EDITING => &Wx::NewId; # package variables to avoid passing lexicals to threads our $THUMBNAIL_DONE_EVENT : shared = Wx::NewEventType; @@ -94,7 +96,7 @@ sub new { # Initialize 3D plater if ($Slic3r::GUI::have_OpenGL) { - $self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{config}); + $self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{print}, $self->{config}); $self->{preview_notebook}->AddPage($self->{canvas3D}, '3D'); $self->{canvas3D}->set_on_select_object($on_select_object); $self->{canvas3D}->set_on_double_click($on_double_click); @@ -154,6 +156,9 @@ sub new { $self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), ''); + + # FIXME add a button for layer editing + $self->{htoolbar}->AddCheckTool(TB_LAYER_EDITING, "Layer editing", Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), ''); } else { my %tbar_buttons = ( add => "Add…", @@ -168,12 +173,15 @@ sub new { split => "Split", cut => "Cut…", settings => "Settings…", + layer_editing => "Layer editing", ); $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) { $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); $self->{btoolbar}->Add($self->{"btn_$_"}); } + $self->{"btn_layer_editing"} = Wx::ToggleButton->new($self, -1, $tbar_buttons{'layer_editing'}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + $self->{btoolbar}->Add($self->{"btn_layer_editing"}); } $self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, wxDefaultSize, @@ -256,6 +264,7 @@ sub new { EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; }); 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 { $self->on_layer_editing_toggled($self->{htoolbar}->GetToolState(TB_LAYER_EDITING)); }); } else { EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; }); EVT_BUTTON($self, $self->{btn_remove}, sub { $self->remove() }); # explicitly pass no argument to remove @@ -269,6 +278,7 @@ sub new { EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; }); EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog }); EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog }); + EVT_TOGGLEBUTTON($self, $self->{btn_layer_editing}, sub { $self->on_layer_editing_toggled($self->{btn_layer_editing}->GetValue); }); } $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self)) @@ -464,6 +474,13 @@ sub _on_select_preset { $self->on_config_change($self->GetFrame->config); } +sub on_layer_editing_toggled { + my ($self, $new_state) = @_; + print "on_layer_editing_toggled $new_state\n"; + $self->{canvas3D}->layer_editing_enabled($new_state); + $self->{canvas3D}->update; +} + sub GetFrame { my ($self) = @_; return &Wx::GetTopLevelParent($self); @@ -1481,6 +1498,8 @@ sub on_thumbnail_made { # (i.e. when an object is added/removed/moved/rotated/scaled) sub update { my ($self, $force_autocenter) = @_; + + print "Platter - update\n"; if ($Slic3r::GUI::Settings->{_}{autocenter} || $force_autocenter) { $self->{model}->center_instances_around_point($self->bed_centerf); @@ -1679,7 +1698,7 @@ sub object_list_changed { my $have_objects = @{$self->{objects}} ? 1 : 0; my $method = $have_objects ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method - for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode); + for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode layer_editing); if ($self->{export_gcode_output_file} || $self->{send_gcode_file}) { $self->{btn_reslice}->Disable; @@ -1712,6 +1731,7 @@ sub selection_changed { if ($self->{object_info_size}) { # have we already loaded the info pane? if ($have_sel) { my $model_object = $self->{model}->objects->[$obj_idx]; + $model_object->print_info; my $model_instance = $model_object->instances->[0]; $self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size})); $self->{object_info_materials}->SetLabel($model_object->materials_count); diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm index 07818a2b5..ce56c67b4 100644 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ b/lib/Slic3r/GUI/Plater/3D.pm @@ -12,7 +12,7 @@ use base qw(Slic3r::GUI::3DScene Class::Accessor); sub new { my $class = shift; - my ($parent, $objects, $model, $config) = @_; + my ($parent, $objects, $model, $print, $config) = @_; my $self = $class->SUPER::new($parent); $self->enable_picking(1); @@ -22,6 +22,7 @@ sub new { $self->{objects} = $objects; $self->{model} = $model; + $self->{print} = $print; $self->{config} = $config; $self->{on_select_object} = sub {}; $self->{on_instances_moved} = sub {}; @@ -31,7 +32,7 @@ sub new { my $obj_idx = undef; if ($volume_idx != -1) { - $obj_idx = $self->object_idx($volume_idx); + $obj_idx = $self->volumes->[$volume_idx]->object_idx; } $self->{on_select_object}->($obj_idx) if $self->{on_select_object}; @@ -42,8 +43,8 @@ sub new { my %done = (); # prevent moving instances twice foreach my $volume_idx (@volume_idxs) { my $volume = $self->volumes->[$volume_idx]; - my $obj_idx = $self->object_idx($volume_idx); - my $instance_idx = $self->instance_idx($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; @@ -89,7 +90,7 @@ sub update { $self->update_bed_size; foreach my $obj_idx (0..$#{$self->{model}->objects}) { - my @volume_idxs = $self->load_object($self->{model}, $obj_idx); + my @volume_idxs = $self->load_object($self->{model}, $self->{print}, $obj_idx); if ($self->{objects}[$obj_idx]->selected) { $self->select_volume($_) for @volume_idxs; diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm index 9e4b33976..7889b3d66 100644 --- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm @@ -114,7 +114,7 @@ sub new { my $canvas; if ($Slic3r::GUI::have_OpenGL) { $canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self); - $canvas->load_object($self->{model_object}, undef, [0]); + $canvas->load_object($self->{model_object}, undef, undef, [0]); $canvas->set_auto_bed_shape; $canvas->SetSize([500,500]); $canvas->SetMinSize($canvas->GetSize); @@ -244,7 +244,7 @@ sub _update { } $self->{canvas}->reset_objects; - $self->{canvas}->load_object($_, undef, [0]) for @objects; + $self->{canvas}->load_object($_, undef, undef, [0]) for @objects; $self->{canvas}->SetCuttingPlane( $self->{cut_options}{z}, [@expolygons], diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index 4882233a6..51a58366c 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -22,6 +22,7 @@ sub new { my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); my $object = $self->{model_object} = $params{model_object}; + my $print_object = $self->{print_object} = $params{print_object}; # create TreeCtrl my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [300, 100], @@ -82,7 +83,7 @@ sub new { $self->reload_tree($canvas->volume_idx($volume_idx)); }); - $canvas->load_object($self->{model_object}, undef, [0]); + $canvas->load_object($self->{model_object}, undef, undef, [0]); $canvas->set_auto_bed_shape; $canvas->SetSize([500,500]); $canvas->zoom_to_volumes;