mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-11-02 20:51:23 -07:00 
			
		
		
		
	Removed the Controller, Layer View, System Info, ObjectCutDialog,
removed unused Perl modules.
This commit is contained in:
		
							parent
							
								
									9d9e4a0f7b
								
							
						
					
					
						commit
						36faa090fc
					
				
					 25 changed files with 70 additions and 5850 deletions
				
			
		| 
						 | 
				
			
			@ -1,372 +0,0 @@
 | 
			
		|||
# 2D preview on the platter.
 | 
			
		||||
# 3D objects are visualized by their convex hulls.
 | 
			
		||||
 | 
			
		||||
package Slic3r::GUI::Plater::2D;
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
use utf8;
 | 
			
		||||
 | 
			
		||||
use List::Util qw(min max first);
 | 
			
		||||
use Slic3r::Geometry qw(X Y scale unscale convex_hull);
 | 
			
		||||
use Slic3r::Geometry::Clipper qw(offset JT_ROUND intersection_pl);
 | 
			
		||||
use Wx qw(wxTheApp :misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL);
 | 
			
		||||
use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE);
 | 
			
		||||
use base 'Wx::Panel';
 | 
			
		||||
 | 
			
		||||
use Wx::Locale gettext => 'L';
 | 
			
		||||
 | 
			
		||||
sub new {
 | 
			
		||||
    my $class = shift;
 | 
			
		||||
    my ($parent, $size, $objects, $model, $config) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $size, wxTAB_TRAVERSAL);
 | 
			
		||||
    # This has only effect on MacOS. On Windows and Linux/GTK, the background is painted by $self->repaint().
 | 
			
		||||
    $self->SetBackgroundColour(Wx::wxWHITE);
 | 
			
		||||
 | 
			
		||||
    $self->{objects}            = $objects;
 | 
			
		||||
    $self->{model}              = $model;
 | 
			
		||||
    $self->{config}             = $config;
 | 
			
		||||
    $self->{on_select_object}   = sub {};
 | 
			
		||||
    $self->{on_double_click}    = sub {};
 | 
			
		||||
    $self->{on_right_click}     = sub {};
 | 
			
		||||
    $self->{on_instances_moved} = sub {};
 | 
			
		||||
    
 | 
			
		||||
    $self->{objects_brush}      = Wx::Brush->new(Wx::Colour->new(210,210,210), wxSOLID);
 | 
			
		||||
    $self->{selected_brush}     = Wx::Brush->new(Wx::Colour->new(255,128,128), wxSOLID);
 | 
			
		||||
    $self->{dragged_brush}      = Wx::Brush->new(Wx::Colour->new(128,128,255), wxSOLID);
 | 
			
		||||
    $self->{transparent_brush}  = Wx::Brush->new(Wx::Colour->new(0,0,0), wxTRANSPARENT);
 | 
			
		||||
    $self->{grid_pen}           = Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID);
 | 
			
		||||
    $self->{print_center_pen}   = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID);
 | 
			
		||||
    $self->{clearance_pen}      = Wx::Pen->new(Wx::Colour->new(0,0,200), 1, wxSOLID);
 | 
			
		||||
    $self->{skirt_pen}          = Wx::Pen->new(Wx::Colour->new(150,150,150), 1, wxSOLID);
 | 
			
		||||
 | 
			
		||||
    $self->{user_drawn_background} = $^O ne 'darwin';
 | 
			
		||||
    
 | 
			
		||||
    EVT_PAINT($self, \&repaint);
 | 
			
		||||
    EVT_ERASE_BACKGROUND($self, sub {}) if $self->{user_drawn_background};
 | 
			
		||||
    EVT_MOUSE_EVENTS($self, \&mouse_event);
 | 
			
		||||
    EVT_SIZE($self, sub {
 | 
			
		||||
        $self->update_bed_size;
 | 
			
		||||
        $self->Refresh;
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub on_select_object {
 | 
			
		||||
    my ($self, $cb) = @_;
 | 
			
		||||
    $self->{on_select_object} = $cb;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub on_double_click {
 | 
			
		||||
    my ($self, $cb) = @_;
 | 
			
		||||
    $self->{on_double_click} = $cb;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub on_right_click {
 | 
			
		||||
    my ($self, $cb) = @_;
 | 
			
		||||
    $self->{on_right_click} = $cb;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub on_instances_moved {
 | 
			
		||||
    my ($self, $cb) = @_;
 | 
			
		||||
    $self->{on_instances_moved} = $cb;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub repaint {
 | 
			
		||||
    my ($self, $event) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $dc = Wx::AutoBufferedPaintDC->new($self);
 | 
			
		||||
    my $size = $self->GetSize;
 | 
			
		||||
    my @size = ($size->GetWidth, $size->GetHeight);
 | 
			
		||||
 | 
			
		||||
    if ($self->{user_drawn_background}) {
 | 
			
		||||
        # On all systems the AutoBufferedPaintDC() achieves double buffering.
 | 
			
		||||
        # On MacOS the background is erased, on Windows the background is not erased 
 | 
			
		||||
        # and on Linux/GTK the background is erased to gray color.
 | 
			
		||||
        # Fill DC with the background on Windows & Linux/GTK.
 | 
			
		||||
        my $brush_background = Wx::Brush->new(Wx::wxWHITE, wxSOLID);
 | 
			
		||||
        $dc->SetPen(wxWHITE_PEN);
 | 
			
		||||
        $dc->SetBrush($brush_background);
 | 
			
		||||
        my $rect = $self->GetUpdateRegion()->GetBox();
 | 
			
		||||
        $dc->DrawRectangle($rect->GetLeft(), $rect->GetTop(), $rect->GetWidth(), $rect->GetHeight());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # draw grid
 | 
			
		||||
    $dc->SetPen($self->{grid_pen});
 | 
			
		||||
    $dc->DrawLine(map @$_, @$_) for @{$self->{grid}};
 | 
			
		||||
    
 | 
			
		||||
    # draw bed
 | 
			
		||||
    {
 | 
			
		||||
        $dc->SetPen($self->{print_center_pen});
 | 
			
		||||
        $dc->SetBrush($self->{transparent_brush});
 | 
			
		||||
        $dc->DrawPolygon($self->scaled_points_to_pixel($self->{bed_polygon}, 1), 0, 0);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # draw print center
 | 
			
		||||
    if (@{$self->{objects}} && wxTheApp->{app_config}->get("autocenter")) {
 | 
			
		||||
        my $center = $self->unscaled_point_to_pixel($self->{print_center});
 | 
			
		||||
        $dc->SetPen($self->{print_center_pen});
 | 
			
		||||
        $dc->DrawLine($center->[X], 0, $center->[X], $size[Y]);
 | 
			
		||||
        $dc->DrawLine(0, $center->[Y], $size[X], $center->[Y]);
 | 
			
		||||
        $dc->SetTextForeground(Wx::Colour->new(0,0,0));
 | 
			
		||||
        $dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL));
 | 
			
		||||
        $dc->DrawLabel("X = " . sprintf('%.0f', $self->{print_center}->[X]), Wx::Rect->new(0, 0, $center->[X]*2, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_BOTTOM);
 | 
			
		||||
        $dc->DrawRotatedText("Y = " . sprintf('%.0f', $self->{print_center}->[Y]), 0, $center->[Y]+15, 90);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # draw frame
 | 
			
		||||
    if (0) {
 | 
			
		||||
        $dc->SetPen(wxBLACK_PEN);
 | 
			
		||||
        $dc->SetBrush($self->{transparent_brush});
 | 
			
		||||
        $dc->DrawRectangle(0, 0, @size);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # draw text if plate is empty
 | 
			
		||||
    if (!@{$self->{objects}}) {
 | 
			
		||||
        $dc->SetTextForeground(Wx::Colour->new(150,50,50));
 | 
			
		||||
        $dc->SetFont(Wx::Font->new(14, wxDEFAULT, wxNORMAL, wxNORMAL));
 | 
			
		||||
        $dc->DrawLabel(
 | 
			
		||||
            join('-', +(localtime)[3,4]) eq '13-8'
 | 
			
		||||
                ? L('What do you want to print today? ™') # Sept. 13, 2006. The first part ever printed by a RepRap to make another RepRap.
 | 
			
		||||
                : L('Drag your objects here'),
 | 
			
		||||
            Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # draw thumbnails
 | 
			
		||||
    $dc->SetPen(wxBLACK_PEN);
 | 
			
		||||
    $self->clean_instance_thumbnails;
 | 
			
		||||
    for my $obj_idx (0 .. $#{$self->{objects}}) {
 | 
			
		||||
        my $object = $self->{objects}[$obj_idx];
 | 
			
		||||
        my $model_object = $self->{model}->objects->[$obj_idx];
 | 
			
		||||
        next unless defined $object->thumbnail;
 | 
			
		||||
        for my $instance_idx (0 .. $#{$model_object->instances}) {
 | 
			
		||||
            my $instance = $model_object->instances->[$instance_idx];
 | 
			
		||||
            next if !defined $object->transformed_thumbnail;
 | 
			
		||||
            
 | 
			
		||||
            my $thumbnail = $object->transformed_thumbnail->clone;      # in scaled model coordinates
 | 
			
		||||
            $thumbnail->translate(map scale($_), @{$instance->offset});
 | 
			
		||||
            
 | 
			
		||||
            $object->instance_thumbnails->[$instance_idx] = $thumbnail;
 | 
			
		||||
            
 | 
			
		||||
            if (defined $self->{drag_object} && $self->{drag_object}[0] == $obj_idx && $self->{drag_object}[1] == $instance_idx) {
 | 
			
		||||
                $dc->SetBrush($self->{dragged_brush});
 | 
			
		||||
            } elsif ($object->selected) {
 | 
			
		||||
                $dc->SetBrush($self->{selected_brush});
 | 
			
		||||
            } else {
 | 
			
		||||
                $dc->SetBrush($self->{objects_brush});
 | 
			
		||||
            }
 | 
			
		||||
            foreach my $expolygon (@$thumbnail) {
 | 
			
		||||
                foreach my $points (@{$expolygon->pp}) {
 | 
			
		||||
                    $dc->DrawPolygon($self->scaled_points_to_pixel($points, 1), 0, 0);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (0) {
 | 
			
		||||
                # draw bounding box for debugging purposes
 | 
			
		||||
                my $bb = $model_object->instance_bounding_box($instance_idx);
 | 
			
		||||
                $bb->scale($self->{scaling_factor});
 | 
			
		||||
                # no need to translate by instance offset because instance_bounding_box() does that
 | 
			
		||||
                my $points = $bb->polygon->pp;
 | 
			
		||||
                $dc->SetPen($self->{clearance_pen});
 | 
			
		||||
                $dc->SetBrush($self->{transparent_brush});
 | 
			
		||||
                $dc->DrawPolygon($self->_y($points), 0, 0);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            # if sequential printing is enabled and we have more than one object, draw clearance area
 | 
			
		||||
            if ($self->{config}->complete_objects && (map @{$_->instances}, @{$self->{model}->objects}) > 1) {
 | 
			
		||||
                my ($clearance) = @{offset([$thumbnail->convex_hull], (scale($self->{config}->extruder_clearance_radius) / 2), JT_ROUND, scale(0.1))};
 | 
			
		||||
                $dc->SetPen($self->{clearance_pen});
 | 
			
		||||
                $dc->SetBrush($self->{transparent_brush});
 | 
			
		||||
                $dc->DrawPolygon($self->scaled_points_to_pixel($clearance, 1), 0, 0);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # draw skirt
 | 
			
		||||
    if (@{$self->{objects}} && $self->{config}->skirts) {
 | 
			
		||||
        my @points = map @{$_->contour}, map @$_, map @{$_->instance_thumbnails}, @{$self->{objects}};
 | 
			
		||||
        if (@points >= 3) {
 | 
			
		||||
            my ($convex_hull) = @{offset([convex_hull(\@points)], scale max($self->{config}->brim_width + $self->{config}->skirt_distance), JT_ROUND, scale(0.1))};
 | 
			
		||||
            $dc->SetPen($self->{skirt_pen});
 | 
			
		||||
            $dc->SetBrush($self->{transparent_brush});
 | 
			
		||||
            $dc->DrawPolygon($self->scaled_points_to_pixel($convex_hull, 1), 0, 0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    $event->Skip;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub mouse_event {
 | 
			
		||||
    my ($self, $event) = @_;
 | 
			
		||||
    my $pos = $event->GetPosition;
 | 
			
		||||
    my $point = $self->point_to_model_units([ $pos->x, $pos->y ]);  #]]
 | 
			
		||||
    if ($event->ButtonDown) {
 | 
			
		||||
        $self->{on_select_object}->(undef);
 | 
			
		||||
        # traverse objects and instances in reverse order, so that if they're overlapping
 | 
			
		||||
        # we get the one that gets drawn last, thus on top (as user expects that to move)
 | 
			
		||||
        OBJECTS: for my $obj_idx (reverse 0 .. $#{$self->{objects}}) {
 | 
			
		||||
            my $object = $self->{objects}->[$obj_idx];
 | 
			
		||||
            for my $instance_idx (reverse 0 .. $#{ $object->instance_thumbnails }) {
 | 
			
		||||
                my $thumbnail = $object->instance_thumbnails->[$instance_idx];
 | 
			
		||||
                if (defined first { $_->contour->contains_point($point) } @$thumbnail) {
 | 
			
		||||
                    $self->{on_select_object}->($obj_idx);
 | 
			
		||||
                    
 | 
			
		||||
                    if ($event->LeftDown) {
 | 
			
		||||
                        # start dragging
 | 
			
		||||
                        my $instance = $self->{model}->objects->[$obj_idx]->instances->[$instance_idx];
 | 
			
		||||
                        my $instance_origin = [ map scale($_), @{$instance->offset} ];
 | 
			
		||||
                        $self->{drag_start_pos} = [   # displacement between the click and the instance origin in scaled model units
 | 
			
		||||
                            $point->x - $instance_origin->[X],
 | 
			
		||||
                            $point->y - $instance_origin->[Y],  #-
 | 
			
		||||
                        ];
 | 
			
		||||
                        $self->{drag_object} = [ $obj_idx, $instance_idx ];
 | 
			
		||||
                    } elsif ($event->RightDown) {
 | 
			
		||||
                        $self->{on_right_click}->($pos->x, $pos->y);
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    last OBJECTS;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $self->Refresh;
 | 
			
		||||
    } elsif ($event->LeftUp) {
 | 
			
		||||
        if ($self->{drag_object}) {
 | 
			
		||||
            $self->{on_instances_moved}->();
 | 
			
		||||
        }
 | 
			
		||||
        $self->{drag_start_pos} = undef;
 | 
			
		||||
        $self->{drag_object} = undef;
 | 
			
		||||
        $self->SetCursor(wxSTANDARD_CURSOR);
 | 
			
		||||
    } elsif ($event->LeftDClick) {
 | 
			
		||||
    	$self->{on_double_click}->();
 | 
			
		||||
    } elsif ($event->Dragging) {
 | 
			
		||||
        return if !$self->{drag_start_pos}; # concurrency problems
 | 
			
		||||
        my ($obj_idx, $instance_idx) = @{ $self->{drag_object} };
 | 
			
		||||
        my $model_object = $self->{model}->objects->[$obj_idx];
 | 
			
		||||
        $model_object->instances->[$instance_idx]->set_offset(
 | 
			
		||||
            Slic3r::Pointf->new(
 | 
			
		||||
                unscale($point->[X] - $self->{drag_start_pos}[X]),
 | 
			
		||||
                unscale($point->[Y] - $self->{drag_start_pos}[Y]),
 | 
			
		||||
            ));
 | 
			
		||||
        $self->Refresh;
 | 
			
		||||
    } elsif ($event->Moving) {
 | 
			
		||||
        my $cursor = wxSTANDARD_CURSOR;
 | 
			
		||||
        if (defined first { $_->contour->contains_point($point) } map @$_, map @{$_->instance_thumbnails}, @{ $self->{objects} }) {
 | 
			
		||||
            $cursor = Wx::Cursor->new(wxCURSOR_HAND);
 | 
			
		||||
        }
 | 
			
		||||
        $self->SetCursor($cursor);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub update_bed_size {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    
 | 
			
		||||
    # when the canvas is not rendered yet, its GetSize() method returns 0,0
 | 
			
		||||
    my $canvas_size = $self->GetSize;
 | 
			
		||||
    my ($canvas_w, $canvas_h) = ($canvas_size->GetWidth, $canvas_size->GetHeight);
 | 
			
		||||
    return if $canvas_w == 0;
 | 
			
		||||
    
 | 
			
		||||
    # get bed shape polygon
 | 
			
		||||
    $self->{bed_polygon} = my $polygon = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape});
 | 
			
		||||
    my $bb = $polygon->bounding_box;
 | 
			
		||||
    my $size = $bb->size;
 | 
			
		||||
    
 | 
			
		||||
    # calculate the scaling factor needed for constraining print bed area inside preview
 | 
			
		||||
    # scaling_factor is expressed in pixel / mm
 | 
			
		||||
    $self->{scaling_factor} = min($canvas_w / unscale($size->x), $canvas_h / unscale($size->y)); #)
 | 
			
		||||
    
 | 
			
		||||
    # calculate the displacement needed to center bed
 | 
			
		||||
    $self->{bed_origin} = [
 | 
			
		||||
        $canvas_w/2  - (unscale($bb->x_max + $bb->x_min)/2 * $self->{scaling_factor}),
 | 
			
		||||
        $canvas_h - ($canvas_h/2 - (unscale($bb->y_max + $bb->y_min)/2 * $self->{scaling_factor})),
 | 
			
		||||
    ];
 | 
			
		||||
    
 | 
			
		||||
    # calculate print center
 | 
			
		||||
    my $center = $bb->center;
 | 
			
		||||
    $self->{print_center} = [ unscale($center->x), unscale($center->y) ]; #))
 | 
			
		||||
    
 | 
			
		||||
    # cache bed contours and grid
 | 
			
		||||
    {
 | 
			
		||||
        my $step = scale 10;  # 1cm grid
 | 
			
		||||
        my @polylines = ();
 | 
			
		||||
        for (my $x = $bb->x_min - ($bb->x_min % $step) + $step; $x < $bb->x_max; $x += $step) {
 | 
			
		||||
            push @polylines, Slic3r::Polyline->new([$x, $bb->y_min], [$x, $bb->y_max]);
 | 
			
		||||
        }
 | 
			
		||||
        for (my $y = $bb->y_min - ($bb->y_min % $step) + $step; $y < $bb->y_max; $y += $step) {
 | 
			
		||||
            push @polylines, Slic3r::Polyline->new([$bb->x_min, $y], [$bb->x_max, $y]);
 | 
			
		||||
        }
 | 
			
		||||
        @polylines = @{intersection_pl(\@polylines, [$polygon])};
 | 
			
		||||
        $self->{grid} = [ map $self->scaled_points_to_pixel([ @$_[0,-1] ], 1), @polylines ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub clean_instance_thumbnails {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    
 | 
			
		||||
    foreach my $object (@{ $self->{objects} }) {
 | 
			
		||||
        @{ $object->instance_thumbnails } = ();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# convert a model coordinate into a pixel coordinate
 | 
			
		||||
sub unscaled_point_to_pixel {
 | 
			
		||||
    my ($self, $point) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $canvas_height = $self->GetSize->GetHeight;
 | 
			
		||||
    my $zero = $self->{bed_origin};
 | 
			
		||||
    return [
 | 
			
		||||
        $point->[X] * $self->{scaling_factor} + $zero->[X],
 | 
			
		||||
        $canvas_height - $point->[Y] * $self->{scaling_factor} + ($zero->[Y] - $canvas_height),
 | 
			
		||||
    ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub scaled_points_to_pixel {
 | 
			
		||||
    my ($self, $points, $unscale) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $result = [];
 | 
			
		||||
    foreach my $point (@$points) {
 | 
			
		||||
        $point = [ map unscale($_), @$point ] if $unscale;
 | 
			
		||||
        push @$result, $self->unscaled_point_to_pixel($point);
 | 
			
		||||
    }
 | 
			
		||||
    return $result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub point_to_model_units {
 | 
			
		||||
    my ($self, $point) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $zero = $self->{bed_origin};
 | 
			
		||||
    return Slic3r::Point->new(
 | 
			
		||||
        scale ($point->[X] - $zero->[X]) / $self->{scaling_factor},
 | 
			
		||||
        scale ($zero->[Y] - $point->[Y]) / $self->{scaling_factor},
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub reload_scene {
 | 
			
		||||
    my ($self, $force) = @_;
 | 
			
		||||
 | 
			
		||||
    if (! $self->IsShown && ! $force) {
 | 
			
		||||
        $self->{reload_delayed} = 1;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $self->{reload_delayed} = 0;
 | 
			
		||||
 | 
			
		||||
    foreach my $obj_idx (0..$#{$self->{model}->objects}) {
 | 
			
		||||
        my $plater_object = $self->{objects}[$obj_idx];
 | 
			
		||||
        next if $plater_object->thumbnail;
 | 
			
		||||
        # The thumbnail is not valid, update it with a convex hull of an object.
 | 
			
		||||
        $plater_object->thumbnail(Slic3r::ExPolygon::Collection->new);
 | 
			
		||||
        $plater_object->make_thumbnail($self->{model}, $obj_idx);
 | 
			
		||||
        $plater_object->transform_thumbnail($self->{model}, $obj_idx);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $self->Refresh;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Called by the Platter wxNotebook when this page is activated.
 | 
			
		||||
sub OnActivate {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    $self->reload_scene(1) if ($self->{reload_delayed});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,910 +0,0 @@
 | 
			
		|||
# 2D preview of the tool paths of a single layer, using a thin line.
 | 
			
		||||
# OpenGL is used to render the paths.
 | 
			
		||||
# Vojtech also added a 2D simulation of under/over extrusion in a single layer.
 | 
			
		||||
 | 
			
		||||
package Slic3r::GUI::Plater::2DToolpaths;
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
use utf8;
 | 
			
		||||
 | 
			
		||||
use Slic3r::Print::State ':steps';
 | 
			
		||||
use Wx qw(:misc :sizer :slider :statictext :keycode wxWHITE wxWANTS_CHARS);
 | 
			
		||||
use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN);
 | 
			
		||||
use base qw(Wx::Panel Class::Accessor);
 | 
			
		||||
 | 
			
		||||
__PACKAGE__->mk_accessors(qw(print enabled));
 | 
			
		||||
 | 
			
		||||
sub new {
 | 
			
		||||
    my $class = shift;
 | 
			
		||||
    my ($parent, $print) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS);
 | 
			
		||||
    $self->SetBackgroundColour(wxWHITE);
 | 
			
		||||
    
 | 
			
		||||
    # init GUI elements
 | 
			
		||||
    my $canvas = $self->{canvas} = Slic3r::GUI::Plater::2DToolpaths::Canvas->new($self, $print);
 | 
			
		||||
    my $slider = $self->{slider} = Wx::Slider->new(
 | 
			
		||||
        $self, -1,
 | 
			
		||||
        0,                              # default
 | 
			
		||||
        0,                              # min
 | 
			
		||||
        # we set max to a bogus non-zero value because the MSW implementation of wxSlider
 | 
			
		||||
        # will skip drawing the slider if max <= min:
 | 
			
		||||
        1,                              # max
 | 
			
		||||
        wxDefaultPosition,
 | 
			
		||||
        wxDefaultSize,
 | 
			
		||||
        wxVERTICAL | wxSL_INVERSE,
 | 
			
		||||
    );
 | 
			
		||||
    my $z_label = $self->{z_label} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
 | 
			
		||||
        [40,-1], wxALIGN_CENTRE_HORIZONTAL);
 | 
			
		||||
    $z_label->SetFont($Slic3r::GUI::small_font);
 | 
			
		||||
    
 | 
			
		||||
    my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
 | 
			
		||||
    $vsizer->Add($slider, 1, wxALL | wxEXPAND | wxALIGN_CENTER, 3);
 | 
			
		||||
    $vsizer->Add($z_label, 0, wxALL | wxEXPAND | wxALIGN_CENTER, 3);
 | 
			
		||||
    
 | 
			
		||||
    my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
 | 
			
		||||
    $sizer->Add($canvas, 1, wxALL | wxEXPAND, 0);
 | 
			
		||||
    $sizer->Add($vsizer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5);
 | 
			
		||||
    
 | 
			
		||||
    EVT_SLIDER($self, $slider, sub {
 | 
			
		||||
        $self->set_z($self->{layers_z}[$slider->GetValue])
 | 
			
		||||
            if $self->enabled;
 | 
			
		||||
    });
 | 
			
		||||
    EVT_KEY_DOWN($canvas, sub {
 | 
			
		||||
        my ($s, $event) = @_;
 | 
			
		||||
        if ($event->HasModifiers) {
 | 
			
		||||
            $event->Skip;
 | 
			
		||||
        } else {
 | 
			
		||||
            my $key = $event->GetKeyCode;
 | 
			
		||||
            if ($key == ord('D') || $key == WXK_LEFT) {
 | 
			
		||||
                # Keys: 'D' or WXK_LEFT
 | 
			
		||||
                $slider->SetValue($slider->GetValue - 1);
 | 
			
		||||
                $self->set_z($self->{layers_z}[$slider->GetValue]);
 | 
			
		||||
            } elsif ($key == ord('U') || $key == WXK_RIGHT) {
 | 
			
		||||
                # Keys: 'U' or WXK_RIGHT
 | 
			
		||||
                $slider->SetValue($slider->GetValue + 1);
 | 
			
		||||
                $self->set_z($self->{layers_z}[$slider->GetValue]);
 | 
			
		||||
            } elsif ($key >= ord('1') && $key <= ord('3')) {
 | 
			
		||||
                # Keys: '1' to '3'
 | 
			
		||||
                $canvas->set_simulation_mode($key - ord('1'));
 | 
			
		||||
            } else {
 | 
			
		||||
                $event->Skip;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $self->SetSizer($sizer);
 | 
			
		||||
    $self->SetMinSize($self->GetSize);
 | 
			
		||||
    $sizer->SetSizeHints($self);
 | 
			
		||||
    
 | 
			
		||||
    # init print
 | 
			
		||||
    $self->{print} = $print;
 | 
			
		||||
    $self->reload_print;
 | 
			
		||||
    
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub reload_print {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    
 | 
			
		||||
    # we require that there's at least one object and the posSlice step
 | 
			
		||||
    # is performed on all of them (this ensures that _shifted_copies was
 | 
			
		||||
    # populated and we know the number of layers)
 | 
			
		||||
    if (!$self->print->object_step_done(STEP_SLICE)) {
 | 
			
		||||
        $self->enabled(0);
 | 
			
		||||
        $self->{slider}->Hide;
 | 
			
		||||
        $self->{canvas}->Refresh;  # clears canvas
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    $self->{canvas}->bb($self->print->total_bounding_box);
 | 
			
		||||
    $self->{canvas}->_dirty(1);
 | 
			
		||||
    
 | 
			
		||||
    my %z = ();  # z => 1
 | 
			
		||||
    foreach my $object (@{$self->{print}->objects}) {
 | 
			
		||||
        foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
 | 
			
		||||
            $z{$layer->print_z} = 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    $self->enabled(1);
 | 
			
		||||
    $self->{layers_z} = [ sort { $a <=> $b } keys %z ];
 | 
			
		||||
    $self->{slider}->SetRange(0, scalar(@{$self->{layers_z}})-1);
 | 
			
		||||
    if ((my $z_idx = $self->{slider}->GetValue) <= $#{$self->{layers_z}}) {
 | 
			
		||||
        $self->set_z($self->{layers_z}[$z_idx]);
 | 
			
		||||
    } else {
 | 
			
		||||
        $self->{slider}->SetValue(0);
 | 
			
		||||
        $self->set_z($self->{layers_z}[0]) if @{$self->{layers_z}};
 | 
			
		||||
    }
 | 
			
		||||
    $self->{slider}->Show;
 | 
			
		||||
    $self->Layout;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub set_z {
 | 
			
		||||
    my ($self, $z) = @_;
 | 
			
		||||
    
 | 
			
		||||
    return if !$self->enabled;
 | 
			
		||||
    $self->{z_label}->SetLabel(sprintf '%.2f', $z);
 | 
			
		||||
    $self->{canvas}->set_z($z);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
package Slic3r::GUI::Plater::2DToolpaths::Canvas;
 | 
			
		||||
 | 
			
		||||
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
 | 
			
		||||
use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants);
 | 
			
		||||
use base qw(Wx::GLCanvas Class::Accessor);
 | 
			
		||||
use Wx::GLCanvas qw(:all);
 | 
			
		||||
use List::Util qw(min max first);
 | 
			
		||||
use Slic3r::Geometry qw(scale epsilon X Y);
 | 
			
		||||
use Slic3r::Print::State ':steps';
 | 
			
		||||
 | 
			
		||||
__PACKAGE__->mk_accessors(qw(
 | 
			
		||||
    print z layers color init
 | 
			
		||||
    bb
 | 
			
		||||
    _camera_bb
 | 
			
		||||
    _dirty
 | 
			
		||||
    _zoom
 | 
			
		||||
    _camera_target
 | 
			
		||||
    _drag_start_xy
 | 
			
		||||
    _texture_name
 | 
			
		||||
    _texture_size
 | 
			
		||||
    _extrusion_simulator
 | 
			
		||||
    _simulation_mode
 | 
			
		||||
));
 | 
			
		||||
 | 
			
		||||
sub new {
 | 
			
		||||
    my ($class, $parent, $print) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $self = (Wx::wxVERSION >= 3.000003) ?
 | 
			
		||||
        # The wxWidgets 3.0.3-beta have a bug, they crash with NULL attribute list.
 | 
			
		||||
        $class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "",
 | 
			
		||||
            [WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, 0]) :
 | 
			
		||||
        $class->SUPER::new($parent);
 | 
			
		||||
    # Immediatelly force creation of the OpenGL context to consume the static variable s_wglContextAttribs.
 | 
			
		||||
    $self->GetContext();
 | 
			
		||||
    $self->print($print);
 | 
			
		||||
    $self->_zoom(1);
 | 
			
		||||
 | 
			
		||||
    # 2D point in model space
 | 
			
		||||
    $self->_camera_target(Slic3r::Pointf->new(0,0));
 | 
			
		||||
 | 
			
		||||
    # Texture for the extrusion simulator. The texture will be allocated / reallocated on Resize.
 | 
			
		||||
    $self->_texture_name(0);
 | 
			
		||||
    $self->_texture_size(Slic3r::Point->new(0,0));
 | 
			
		||||
    $self->_extrusion_simulator(Slic3r::ExtrusionSimulator->new());
 | 
			
		||||
    $self->_simulation_mode(0);
 | 
			
		||||
 | 
			
		||||
    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->Refresh;
 | 
			
		||||
    });
 | 
			
		||||
    EVT_MOUSEWHEEL($self, sub {
 | 
			
		||||
        my ($self, $e) = @_;
 | 
			
		||||
        
 | 
			
		||||
        return if !$self->GetParent->enabled;
 | 
			
		||||
        
 | 
			
		||||
        my $old_zoom = $self->_zoom;
 | 
			
		||||
        
 | 
			
		||||
        # 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));
 | 
			
		||||
        $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
 | 
			
		||||
            # the camera target. This math is almost there but not perfect yet...
 | 
			
		||||
            my $camera_bb_size = $self->_camera_bb->size;
 | 
			
		||||
            my $size = Slic3r::Pointf->new($self->GetSizeWH);
 | 
			
		||||
            my $pos = Slic3r::Pointf->new($e->GetPositionXY);
 | 
			
		||||
            
 | 
			
		||||
            # calculate the zooming center in pixel coordinates relative to the viewport center
 | 
			
		||||
            my $vec = Slic3r::Pointf->new($pos->x - $size->x/2, $pos->y - $size->y/2);  #-
 | 
			
		||||
            
 | 
			
		||||
            # calculate where this point will end up after applying the new zoom
 | 
			
		||||
            my $vec2 = $vec->clone;
 | 
			
		||||
            $vec2->scale($old_zoom / $self->_zoom);
 | 
			
		||||
            
 | 
			
		||||
            # move the camera target by the difference of the two positions
 | 
			
		||||
            $self->_camera_target->translate(
 | 
			
		||||
                -($vec->x - $vec2->x) * $camera_bb_size->x / $size->x,
 | 
			
		||||
                 ($vec->y - $vec2->y) * $camera_bb_size->y / $size->y,  #//
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $self->_dirty(1);
 | 
			
		||||
    });
 | 
			
		||||
    EVT_MOUSE_EVENTS($self, \&mouse_event);
 | 
			
		||||
    
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub Destroy {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    
 | 
			
		||||
    # Deallocate the OpenGL resources.
 | 
			
		||||
    my $context = $self->GetContext;
 | 
			
		||||
    if ($context and $self->texture_id) {
 | 
			
		||||
        $self->SetCurrent($context);
 | 
			
		||||
        glDeleteTextures(1, ($self->texture_id));
 | 
			
		||||
        $self->SetCurrent(0);
 | 
			
		||||
        $self->texture_id(0);
 | 
			
		||||
        $self->texture_size(new Slic3r::Point(0, 0));
 | 
			
		||||
    }
 | 
			
		||||
    return $self->SUPER::Destroy;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub mouse_event {
 | 
			
		||||
    my ($self, $e) = @_;
 | 
			
		||||
    
 | 
			
		||||
    return if !$self->GetParent->enabled;
 | 
			
		||||
    
 | 
			
		||||
    my $pos = Slic3r::Pointf->new($e->GetPositionXY);
 | 
			
		||||
    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) {
 | 
			
		||||
            # if dragging, translate view
 | 
			
		||||
            
 | 
			
		||||
            if (defined $self->_drag_start_xy) {
 | 
			
		||||
                my $move = $self->_drag_start_xy->vector_to($pos);  # in pixels
 | 
			
		||||
                
 | 
			
		||||
                # get viewport and camera size in order to convert pixel to model units
 | 
			
		||||
                my ($x, $y) = $self->GetSizeWH;
 | 
			
		||||
                my $camera_bb_size = $self->_camera_bb->size;
 | 
			
		||||
                
 | 
			
		||||
                # compute translation in model units
 | 
			
		||||
                $self->_camera_target->translate(
 | 
			
		||||
                    -$move->x * $camera_bb_size->x / $x,
 | 
			
		||||
                     $move->y * $camera_bb_size->y / $y,   # /**
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                $self->_dirty(1);
 | 
			
		||||
            }
 | 
			
		||||
            $self->_drag_start_xy($pos);
 | 
			
		||||
        }
 | 
			
		||||
    } elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
 | 
			
		||||
        $self->_drag_start_xy(undef);
 | 
			
		||||
    } else {
 | 
			
		||||
        $e->Skip();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub set_z {
 | 
			
		||||
    my ($self, $z) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $print = $self->print;
 | 
			
		||||
    
 | 
			
		||||
    # can we have interlaced layers?
 | 
			
		||||
    my $interlaced = (defined first { $_->config->support_material } @{$print->objects})
 | 
			
		||||
        || (defined first { $_->config->infill_every_layers > 1 } @{$print->regions});
 | 
			
		||||
    
 | 
			
		||||
    my $max_layer_height = $print->max_allowed_layer_height;
 | 
			
		||||
    
 | 
			
		||||
    my @layers = ();
 | 
			
		||||
    foreach my $object (@{$print->objects}) {
 | 
			
		||||
        foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
 | 
			
		||||
            if ($interlaced) {
 | 
			
		||||
                push @layers, $layer
 | 
			
		||||
                    if $z > ($layer->print_z - $max_layer_height - epsilon)
 | 
			
		||||
                        && $z <= $layer->print_z + epsilon;
 | 
			
		||||
            } else {
 | 
			
		||||
                push @layers, $layer if abs($layer->print_z - $z) < epsilon;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # reverse layers so that we draw the lowermost (i.e. current) on top
 | 
			
		||||
    $self->z($z);
 | 
			
		||||
    $self->layers([ reverse @layers ]);
 | 
			
		||||
    $self->Refresh;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub set_simulation_mode
 | 
			
		||||
{
 | 
			
		||||
    my ($self, $mode) = @_;
 | 
			
		||||
    $self->_simulation_mode($mode);
 | 
			
		||||
    $self->_dirty(1);
 | 
			
		||||
    $self->Refresh;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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, 0);
 | 
			
		||||
    glClear(GL_COLOR_BUFFER_BIT);
 | 
			
		||||
    
 | 
			
		||||
    if (!$self->GetParent->enabled || !$self->layers) {
 | 
			
		||||
        $self->SwapBuffers;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    glDisable(GL_DEPTH_TEST);
 | 
			
		||||
    glMatrixMode(GL_MODELVIEW);
 | 
			
		||||
    glLoadIdentity();
 | 
			
		||||
 | 
			
		||||
    if ($self->_simulation_mode and $self->_texture_name and $self->_texture_size->x() > 0 and $self->_texture_size->y() > 0) {
 | 
			
		||||
        $self->_simulate_extrusion();
 | 
			
		||||
        my ($x, $y) = $self->GetSizeWH;
 | 
			
		||||
        glEnable(GL_TEXTURE_2D);
 | 
			
		||||
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_REPLACE);
 | 
			
		||||
        glBindTexture(GL_TEXTURE_2D, $self->_texture_name);
 | 
			
		||||
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
 | 
			
		||||
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
 | 
			
		||||
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 | 
			
		||||
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 | 
			
		||||
        glTexImage2D_c(GL_TEXTURE_2D,
 | 
			
		||||
            0,                        # level (0 normal, heighr is form mip-mapping)
 | 
			
		||||
            GL_RGBA,                  # internal format
 | 
			
		||||
            $self->_texture_size->x(), $self->_texture_size->y(),
 | 
			
		||||
            0,                        # border 
 | 
			
		||||
            GL_RGBA,                  # format RGBA color data
 | 
			
		||||
            GL_UNSIGNED_BYTE,         # unsigned byte data
 | 
			
		||||
            $self->_extrusion_simulator->image_ptr()); # ptr to texture data
 | 
			
		||||
        glMatrixMode(GL_PROJECTION);
 | 
			
		||||
        glPushMatrix();
 | 
			
		||||
        glLoadIdentity();
 | 
			
		||||
        glOrtho(0, 1, 0, 1, 0, 1);
 | 
			
		||||
        glBegin(GL_QUADS);
 | 
			
		||||
        glTexCoord2f(0, 0);
 | 
			
		||||
        glVertex2f(0, 0);
 | 
			
		||||
        glTexCoord2f($x/$self->_texture_size->x(), 0);
 | 
			
		||||
        glVertex2f(1, 0);
 | 
			
		||||
        glTexCoord2f($x/$self->_texture_size->x(), $y/$self->_texture_size->y());
 | 
			
		||||
        glVertex2f(1, 1);
 | 
			
		||||
        glTexCoord2f(0, $y/$self->_texture_size->y());
 | 
			
		||||
        glVertex2f(0, 1);
 | 
			
		||||
        glEnd();
 | 
			
		||||
        glPopMatrix();
 | 
			
		||||
        glBindTexture(GL_TEXTURE_2D, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # anti-alias
 | 
			
		||||
    if (0) {
 | 
			
		||||
        glEnable(GL_LINE_SMOOTH);
 | 
			
		||||
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 | 
			
		||||
        glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
 | 
			
		||||
        glHint(GL_POLYGON_SMOOTH_HINT, GL_DONT_CARE);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # Tesselator triangulates polygons with holes on the fly for the rendering purposes only.
 | 
			
		||||
    my $tess;
 | 
			
		||||
    if ($self->_simulation_mode() == 0 and !(&Wx::wxMSW && $OpenGL::VERSION < 0.6704)) {
 | 
			
		||||
        # We can't use the GLU tesselator on MSW with older OpenGL versions
 | 
			
		||||
        # because of an upstream bug:
 | 
			
		||||
        # http://sourceforge.net/p/pogl/bugs/16/
 | 
			
		||||
        $tess = gluNewTess();
 | 
			
		||||
        gluTessCallback($tess, GLU_TESS_BEGIN,     'DEFAULT');
 | 
			
		||||
        gluTessCallback($tess, GLU_TESS_END,       'DEFAULT');
 | 
			
		||||
        gluTessCallback($tess, GLU_TESS_VERTEX,    'DEFAULT');
 | 
			
		||||
        gluTessCallback($tess, GLU_TESS_COMBINE,   'DEFAULT');
 | 
			
		||||
        gluTessCallback($tess, GLU_TESS_ERROR,     'DEFAULT');
 | 
			
		||||
        gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    foreach my $layer (@{$self->layers}) {
 | 
			
		||||
        my $object = $layer->object;
 | 
			
		||||
        
 | 
			
		||||
        # only draw the slice for the current layer
 | 
			
		||||
        next unless abs($layer->print_z - $self->z) < epsilon;
 | 
			
		||||
        
 | 
			
		||||
        # draw slice contour
 | 
			
		||||
        glLineWidth(1);
 | 
			
		||||
        foreach my $copy (@{ $object->_shifted_copies }) {
 | 
			
		||||
            glPushMatrix();
 | 
			
		||||
            glTranslatef(@$copy, 0);
 | 
			
		||||
            
 | 
			
		||||
            foreach my $slice (@{$layer->slices}) {
 | 
			
		||||
                glColor3f(0.95, 0.95, 0.95);
 | 
			
		||||
                
 | 
			
		||||
                if ($tess) {
 | 
			
		||||
                    gluTessBeginPolygon($tess);
 | 
			
		||||
                    foreach my $polygon (@$slice) {
 | 
			
		||||
                        gluTessBeginContour($tess);
 | 
			
		||||
                        gluTessVertex_p($tess, @$_, 0) for @$polygon;
 | 
			
		||||
                        gluTessEndContour($tess);
 | 
			
		||||
                    }
 | 
			
		||||
                    gluTessEndPolygon($tess);
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                glColor3f(0.9, 0.9, 0.9);
 | 
			
		||||
                foreach my $polygon (@$slice) {
 | 
			
		||||
                    foreach my $line (@{$polygon->lines}) {
 | 
			
		||||
                        glBegin(GL_LINES);
 | 
			
		||||
                        glVertex2f(@{$line->a});
 | 
			
		||||
                        glVertex2f(@{$line->b});
 | 
			
		||||
                        glEnd();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            glPopMatrix();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    my $skirt_drawn = 0;
 | 
			
		||||
    my $brim_drawn = 0;
 | 
			
		||||
    foreach my $layer (@{$self->layers}) {
 | 
			
		||||
        my $object = $layer->object;
 | 
			
		||||
        my $print_z = $layer->print_z;
 | 
			
		||||
        
 | 
			
		||||
        # draw brim
 | 
			
		||||
        if ($self->print->step_done(STEP_BRIM) && $layer->id == 0 && !$brim_drawn) {
 | 
			
		||||
            $self->color([0, 0, 0]);
 | 
			
		||||
            $self->_draw(undef, $print_z, $_) for @{$self->print->brim};
 | 
			
		||||
            $brim_drawn = 1;
 | 
			
		||||
        }
 | 
			
		||||
        if ($self->print->step_done(STEP_SKIRT)
 | 
			
		||||
            && ($self->print->has_infinite_skirt() || $self->print->config->skirt_height > $layer->id)
 | 
			
		||||
            && !$skirt_drawn) {
 | 
			
		||||
            $self->color([0, 0, 0]);
 | 
			
		||||
            $self->_draw(undef, $print_z, $_) for @{$self->print->skirt};
 | 
			
		||||
            $skirt_drawn = 1;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        foreach my $layerm (@{$layer->regions}) {
 | 
			
		||||
            if ($object->step_done(STEP_PERIMETERS)) {
 | 
			
		||||
                $self->color([0.7, 0, 0]);
 | 
			
		||||
                $self->_draw($object, $print_z, $_) for map @$_, @{$layerm->perimeters};
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if ($object->step_done(STEP_INFILL)) {
 | 
			
		||||
                $self->color([0, 0, 0.7]);
 | 
			
		||||
                $self->_draw($object, $print_z, $_) for map @$_, @{$layerm->fills};
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($object->step_done(STEP_SUPPORTMATERIAL)) {
 | 
			
		||||
            if ($layer->isa('Slic3r::Layer::Support')) {
 | 
			
		||||
                $self->color([0, 0, 0]);
 | 
			
		||||
                $self->_draw($object, $print_z, $_) for @{$layer->support_fills};
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    gluDeleteTess($tess) if $tess;
 | 
			
		||||
    $self->SwapBuffers;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _draw {
 | 
			
		||||
    my ($self, $object, $print_z, $path) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my @paths = ($path->isa('Slic3r::ExtrusionLoop') || $path->isa('Slic3r::ExtrusionMultiPath'))
 | 
			
		||||
        ? @$path
 | 
			
		||||
        : ($path);
 | 
			
		||||
    
 | 
			
		||||
    $self->_draw_path($object, $print_z, $_) for @paths;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _draw_path {
 | 
			
		||||
    my ($self, $object, $print_z, $path) = @_;
 | 
			
		||||
    
 | 
			
		||||
    return if $print_z - $path->height > $self->z - epsilon;
 | 
			
		||||
    
 | 
			
		||||
    if (abs($print_z - $self->z) < epsilon) {
 | 
			
		||||
        glColor3f(@{$self->color});
 | 
			
		||||
    } else {
 | 
			
		||||
        glColor3f(0.8, 0.8, 0.8);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    glLineWidth(1);
 | 
			
		||||
    
 | 
			
		||||
    if (defined $object) {
 | 
			
		||||
        foreach my $copy (@{ $object->_shifted_copies }) {
 | 
			
		||||
            glPushMatrix();
 | 
			
		||||
            glTranslatef(@$copy, 0);
 | 
			
		||||
            foreach my $line (@{$path->polyline->lines}) {
 | 
			
		||||
                glBegin(GL_LINES);
 | 
			
		||||
                glVertex2f(@{$line->a});
 | 
			
		||||
                glVertex2f(@{$line->b});
 | 
			
		||||
                glEnd();
 | 
			
		||||
            }
 | 
			
		||||
            glPopMatrix();
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        foreach my $line (@{$path->polyline->lines}) {
 | 
			
		||||
            glBegin(GL_LINES);
 | 
			
		||||
            glVertex2f(@{$line->a});
 | 
			
		||||
            glVertex2f(@{$line->b});
 | 
			
		||||
            glEnd();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _simulate_extrusion {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    $self->_extrusion_simulator->reset_accumulator();
 | 
			
		||||
    foreach my $layer (@{$self->layers}) {
 | 
			
		||||
        if (abs($layer->print_z - $self->z) < epsilon) {
 | 
			
		||||
            my $object = $layer->object;
 | 
			
		||||
            my @shifts = (defined $object) ? @{$object->_shifted_copies} : (Slic3r::Point->new(0, 0));
 | 
			
		||||
            foreach my $layerm (@{$layer->regions}) {
 | 
			
		||||
                my @extrusions = ();
 | 
			
		||||
                if ($object->step_done(STEP_PERIMETERS)) {
 | 
			
		||||
                    push @extrusions, @$_ for @{$layerm->perimeters};
 | 
			
		||||
                }
 | 
			
		||||
                if ($object->step_done(STEP_INFILL)) {
 | 
			
		||||
                    push @extrusions, @$_ for @{$layerm->fills};
 | 
			
		||||
                }
 | 
			
		||||
                foreach my $extrusion_entity (@extrusions) {
 | 
			
		||||
                    my @paths = ($extrusion_entity->isa('Slic3r::ExtrusionLoop') || $extrusion_entity->isa('Slic3r::ExtrusionMultiPath'))
 | 
			
		||||
                        ? @$extrusion_entity
 | 
			
		||||
                        : ($extrusion_entity);
 | 
			
		||||
                    foreach my $path (@paths) {
 | 
			
		||||
                        print "width: ", $path->width, 
 | 
			
		||||
                              " height: ", $path->height,
 | 
			
		||||
                              " mm3_per_mm: ", $path->mm3_per_mm,
 | 
			
		||||
                              " height2: ", $path->mm3_per_mm / $path->height,
 | 
			
		||||
                              "\n";
 | 
			
		||||
                        $self->_extrusion_simulator->extrude_to_accumulator($path, $_, $self->_simulation_mode()) for @shifts;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    $self->_extrusion_simulator->evaluate_accumulator($self->_simulation_mode());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub InitGL {
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
 
 | 
			
		||||
    return if $self->init;
 | 
			
		||||
    return unless $self->GetContext;
 | 
			
		||||
 | 
			
		||||
    my $texture_id = 0;
 | 
			
		||||
    ($texture_id) = glGenTextures_p(1);
 | 
			
		||||
    $self->_texture_name($texture_id);
 | 
			
		||||
    $self->init(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub GetContext {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    return $self->{context} ||= Wx::GLContext->new($self);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
sub SetCurrent {
 | 
			
		||||
    my ($self, $context) = @_;
 | 
			
		||||
    return $self->SUPER::SetCurrent($context);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub Resize {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
 
 | 
			
		||||
    return unless $self->GetContext;
 | 
			
		||||
    return unless $self->bb;
 | 
			
		||||
    $self->_dirty(0);
 | 
			
		||||
    
 | 
			
		||||
    $self->SetCurrent($self->GetContext);
 | 
			
		||||
    my ($x, $y) = $self->GetSizeWH;
 | 
			
		||||
 | 
			
		||||
    if ($self->_texture_size->x() < $x or $self->_texture_size->y() < $y) {
 | 
			
		||||
        # Allocate a large enough OpenGL texture with power of 2 dimensions.
 | 
			
		||||
        $self->_texture_size->set_x(1) if ($self->_texture_size->x() == 0);
 | 
			
		||||
        $self->_texture_size->set_y(1) if ($self->_texture_size->y() == 0);
 | 
			
		||||
        $self->_texture_size->set_x($self->_texture_size->x() * 2) while ($self->_texture_size->x() < $x);
 | 
			
		||||
        $self->_texture_size->set_y($self->_texture_size->y() * 2) while ($self->_texture_size->y() < $y);
 | 
			
		||||
        #print "screen size ", $x, "x", $y;
 | 
			
		||||
        #print "texture size ", $self->_texture_size->x(), "x", $self->_texture_size->y();
 | 
			
		||||
        # Initialize an empty texture.
 | 
			
		||||
        glBindTexture(GL_TEXTURE_2D, $self->_texture_name);
 | 
			
		||||
        if (1) {
 | 
			
		||||
        glTexImage2D_c(GL_TEXTURE_2D,
 | 
			
		||||
            0,                        # level (0 normal, heighr is form mip-mapping)
 | 
			
		||||
            GL_RGBA,                  # internal format
 | 
			
		||||
            $self->_texture_size->x(), $self->_texture_size->y(),
 | 
			
		||||
            0,                        # border 
 | 
			
		||||
            GL_RGBA,                  # format RGBA color data
 | 
			
		||||
            GL_UNSIGNED_BYTE,         # unsigned byte data
 | 
			
		||||
            0);                       # ptr to texture data
 | 
			
		||||
        }
 | 
			
		||||
        glBindTexture(GL_TEXTURE_2D, 0);
 | 
			
		||||
        $self->_extrusion_simulator->set_image_size($self->_texture_size);
 | 
			
		||||
    }
 | 
			
		||||
    $self->_extrusion_simulator->set_viewport(Slic3r::Geometry::BoundingBox->new_from_points(
 | 
			
		||||
        [Slic3r::Point->new(0, 0), Slic3r::Point->new($x, $y)]));
 | 
			
		||||
 | 
			
		||||
    glViewport(0, 0, $x, $y);
 | 
			
		||||
    
 | 
			
		||||
    glMatrixMode(GL_PROJECTION);
 | 
			
		||||
    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;
 | 
			
		||||
    $bb->translate(@{$bb_center->negative});
 | 
			
		||||
    
 | 
			
		||||
    # scale bounding box according to zoom factor
 | 
			
		||||
    $bb->scale($self->_zoom);
 | 
			
		||||
    
 | 
			
		||||
    # reposition bounding box around original center
 | 
			
		||||
    $bb->translate(@{$bb_center});
 | 
			
		||||
    
 | 
			
		||||
    # 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);
 | 
			
		||||
#    }
 | 
			
		||||
    
 | 
			
		||||
    # save camera
 | 
			
		||||
    $self->_camera_bb($bb);
 | 
			
		||||
    
 | 
			
		||||
    my ($x1, $y1, $x2, $y2) = ($bb->x_min, $bb->y_min, $bb->x_max, $bb->y_max);
 | 
			
		||||
    if (($x2 - $x1)/($y2 - $y1) > $x/$y) {
 | 
			
		||||
        # adjust Y
 | 
			
		||||
        my $new_y = $y * ($x2 - $x1) / $x;
 | 
			
		||||
        $y1 = ($y2 + $y1)/2 - $new_y/2;
 | 
			
		||||
        $y2 = $y1 + $new_y;
 | 
			
		||||
    } else {
 | 
			
		||||
        my $new_x = $x * ($y2 - $y1) / $y;
 | 
			
		||||
        $x1 = ($x2 + $x1)/2 - $new_x/2;
 | 
			
		||||
        $x2 = $x1 + $new_x;
 | 
			
		||||
    }
 | 
			
		||||
    glOrtho($x1, $x2, $y1, $y2, 0, 1);
 | 
			
		||||
 | 
			
		||||
    # Set the adjusted bounding box at the extrusion simulator.
 | 
			
		||||
    #print "Scene bbox ", $bb->x_min, ",", $bb->y_min, " ", $bb->x_max, ",", $bb->y_max, "\n";
 | 
			
		||||
    #print "Setting simulator bbox ", $x1, ",", $y1, " ", $x2, ",", $y2, "\n";
 | 
			
		||||
    $self->_extrusion_simulator->set_bounding_box(
 | 
			
		||||
        Slic3r::Geometry::BoundingBox->new_from_points(
 | 
			
		||||
            [Slic3r::Point->new($x1, $y1), Slic3r::Point->new($x2, $y2)]));
 | 
			
		||||
 | 
			
		||||
    glMatrixMode(GL_MODELVIEW);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Thick line drawing is not used anywhere. Probably not tested?
 | 
			
		||||
sub line {
 | 
			
		||||
    my (
 | 
			
		||||
        $x1, $y1, $x2, $y2,     # coordinates of the line
 | 
			
		||||
        $w,                     # width/thickness of the line in pixel
 | 
			
		||||
        $Cr, $Cg, $Cb,          # RGB color components
 | 
			
		||||
        $Br, $Bg, $Bb,          # color of background when alphablend=false
 | 
			
		||||
                                # Br=alpha of color when alphablend=true
 | 
			
		||||
        $alphablend,            # use alpha blend or not
 | 
			
		||||
    ) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $t;
 | 
			
		||||
    my $R;
 | 
			
		||||
    my $f = $w - int($w);
 | 
			
		||||
    my $A;
 | 
			
		||||
    
 | 
			
		||||
    if ($alphablend) {
 | 
			
		||||
        $A = $Br;
 | 
			
		||||
    } else {
 | 
			
		||||
        $A = 1;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # determine parameters t,R
 | 
			
		||||
    if ($w >= 0 && $w < 1) {
 | 
			
		||||
        $t = 0.05; $R = 0.48 + 0.32 * $f;
 | 
			
		||||
        if (!$alphablend) {
 | 
			
		||||
            $Cr += 0.88 * (1-$f);
 | 
			
		||||
            $Cg += 0.88 * (1-$f);
 | 
			
		||||
            $Cb += 0.88 * (1-$f);
 | 
			
		||||
            $Cr = 1.0 if ($Cr > 1.0);
 | 
			
		||||
            $Cg = 1.0 if ($Cg > 1.0);
 | 
			
		||||
            $Cb = 1.0 if ($Cb > 1.0);
 | 
			
		||||
        } else {
 | 
			
		||||
            $A *= $f;
 | 
			
		||||
        }
 | 
			
		||||
    } elsif ($w >= 1.0 && $w < 2.0) {
 | 
			
		||||
        $t = 0.05 + $f*0.33; $R = 0.768 + 0.312*$f;
 | 
			
		||||
    } elsif ($w >= 2.0 && $w < 3.0) {
 | 
			
		||||
        $t = 0.38 + $f*0.58; $R = 1.08;
 | 
			
		||||
    } elsif ($w >= 3.0 && $w < 4.0) {
 | 
			
		||||
        $t = 0.96 + $f*0.48; $R = 1.08;
 | 
			
		||||
    } elsif ($w >= 4.0 && $w < 5.0) {
 | 
			
		||||
        $t= 1.44 + $f*0.46; $R = 1.08;
 | 
			
		||||
    } elsif ($w >= 5.0 && $w < 6.0) {
 | 
			
		||||
        $t= 1.9 + $f*0.6; $R = 1.08;
 | 
			
		||||
    } elsif ($w >= 6.0) {
 | 
			
		||||
        my $ff = $w - 6.0;
 | 
			
		||||
        $t = 2.5 + $ff*0.50; $R = 1.08;
 | 
			
		||||
    }
 | 
			
		||||
    #printf( "w=%f, f=%f, C=%.4f\n", $w, $f, $C);
 | 
			
		||||
    
 | 
			
		||||
    # determine angle of the line to horizontal
 | 
			
		||||
    my $tx = 0; my $ty = 0; # core thinkness of a line
 | 
			
		||||
    my $Rx = 0; my $Ry = 0; # fading edge of a line
 | 
			
		||||
    my $cx = 0; my $cy = 0; # cap of a line
 | 
			
		||||
    my $ALW = 0.01;
 | 
			
		||||
    my $dx = $x2 - $x1;
 | 
			
		||||
    my $dy = $y2 - $y1;
 | 
			
		||||
    if (abs($dx) < $ALW) {
 | 
			
		||||
        # vertical
 | 
			
		||||
        $tx = $t; $ty = 0;
 | 
			
		||||
        $Rx = $R; $Ry = 0;
 | 
			
		||||
        if ($w > 0.0 && $w < 1.0) {
 | 
			
		||||
            $tx *= 8;
 | 
			
		||||
        } elsif ($w == 1.0) {
 | 
			
		||||
            $tx *= 10;
 | 
			
		||||
        }
 | 
			
		||||
    } elsif (abs($dy) < $ALW) {
 | 
			
		||||
        #horizontal
 | 
			
		||||
        $tx = 0; $ty = $t;
 | 
			
		||||
        $Rx = 0; $Ry = $R;
 | 
			
		||||
        if ($w > 0.0 && $w < 1.0) {
 | 
			
		||||
            $ty *= 8;
 | 
			
		||||
        } elsif ($w == 1.0) {
 | 
			
		||||
            $ty *= 10;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        if ($w < 3) { # approximate to make things even faster
 | 
			
		||||
            my $m = $dy/$dx;
 | 
			
		||||
            # and calculate tx,ty,Rx,Ry
 | 
			
		||||
            if ($m > -0.4142 && $m <= 0.4142) {
 | 
			
		||||
                # -22.5 < $angle <= 22.5, approximate to 0 (degree)
 | 
			
		||||
                $tx = $t * 0.1; $ty = $t;
 | 
			
		||||
                $Rx = $R * 0.6; $Ry = $R;
 | 
			
		||||
            } elsif ($m > 0.4142 && $m <= 2.4142) {
 | 
			
		||||
                # 22.5 < $angle <= 67.5, approximate to 45 (degree)
 | 
			
		||||
                $tx = $t * -0.7071; $ty = $t * 0.7071;
 | 
			
		||||
                $Rx = $R * -0.7071; $Ry = $R * 0.7071;
 | 
			
		||||
            } elsif ($m > 2.4142 || $m <= -2.4142) {
 | 
			
		||||
                # 67.5 < $angle <= 112.5, approximate to 90 (degree)
 | 
			
		||||
                $tx = $t; $ty = $t*0.1;
 | 
			
		||||
                $Rx = $R; $Ry = $R*0.6;
 | 
			
		||||
            } elsif ($m > -2.4142 && $m < -0.4142) {
 | 
			
		||||
                # 112.5 < angle < 157.5, approximate to 135 (degree)
 | 
			
		||||
                $tx = $t * 0.7071; $ty = $t * 0.7071;
 | 
			
		||||
                $Rx = $R * 0.7071; $Ry = $R * 0.7071;
 | 
			
		||||
            } else {
 | 
			
		||||
                # error in determining angle
 | 
			
		||||
                printf("error in determining angle: m=%.4f\n", $m);
 | 
			
		||||
            }
 | 
			
		||||
        } else {  # calculate to exact
 | 
			
		||||
            $dx= $y1 - $y2;
 | 
			
		||||
            $dy= $x2 - $x1;
 | 
			
		||||
            my $L = sqrt($dx*$dx + $dy*$dy);
 | 
			
		||||
            $dx /= $L;
 | 
			
		||||
            $dy /= $L;
 | 
			
		||||
            $cx = -0.6*$dy; $cy=0.6*$dx;
 | 
			
		||||
            $tx = $t*$dx; $ty = $t*$dy;
 | 
			
		||||
            $Rx = $R*$dx; $Ry = $R*$dy;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # draw the line by triangle strip
 | 
			
		||||
    glBegin(GL_TRIANGLE_STRIP);
 | 
			
		||||
    if (!$alphablend) {
 | 
			
		||||
        glColor3f($Br, $Bg, $Bb);
 | 
			
		||||
    } else {
 | 
			
		||||
        glColor4f($Cr, $Cg, $Cb, 0);
 | 
			
		||||
    }
 | 
			
		||||
    glVertex2f($x1 - $tx - $Rx, $y1 - $ty - $Ry);   # fading edge
 | 
			
		||||
    glVertex2f($x2 - $tx - $Rx, $y2 - $ty - $Ry);
 | 
			
		||||
    
 | 
			
		||||
    if (!$alphablend) {
 | 
			
		||||
        glColor3f($Cr, $Cg, $Cb);
 | 
			
		||||
    } else {
 | 
			
		||||
        glColor4f($Cr, $Cg, $Cb, $A);
 | 
			
		||||
    }
 | 
			
		||||
    glVertex2f($x1 - $tx, $y1 - $ty); # core
 | 
			
		||||
    glVertex2f($x2 - $tx, $y2 - $ty);
 | 
			
		||||
    glVertex2f($x1 + $tx, $y1 + $ty);
 | 
			
		||||
    glVertex2f($x2 + $tx, $y2 + $ty);
 | 
			
		||||
    
 | 
			
		||||
    if ((abs($dx) < $ALW || abs($dy) < $ALW) && $w <= 1.0) {
 | 
			
		||||
        # printf("skipped one fading edge\n");
 | 
			
		||||
    } else {
 | 
			
		||||
        if (!$alphablend) {
 | 
			
		||||
            glColor3f($Br, $Bg, $Bb);
 | 
			
		||||
        } else {
 | 
			
		||||
            glColor4f($Cr, $Cg, $Cb, 0);
 | 
			
		||||
        }
 | 
			
		||||
        glVertex2f($x1 + $tx+ $Rx, $y1 + $ty + $Ry);    # fading edge
 | 
			
		||||
        glVertex2f($x2 + $tx+ $Rx, $y2 + $ty + $Ry);
 | 
			
		||||
    }
 | 
			
		||||
    glEnd();
 | 
			
		||||
 | 
			
		||||
    # cap
 | 
			
		||||
    if ($w < 3) {
 | 
			
		||||
        # do not draw cap
 | 
			
		||||
    } else {
 | 
			
		||||
        # draw cap
 | 
			
		||||
        glBegin(GL_TRIANGLE_STRIP);
 | 
			
		||||
        if (!$alphablend) {
 | 
			
		||||
            glColor3f($Br, $Bg, $Bb);
 | 
			
		||||
        } else {
 | 
			
		||||
            glColor4f($Cr, $Cg, $Cb, 0);
 | 
			
		||||
        }
 | 
			
		||||
        glVertex2f($x1 - $Rx + $cx, $y1 - $Ry + $cy);
 | 
			
		||||
        glVertex2f($x1 + $Rx + $cx, $y1 + $Ry + $cy);
 | 
			
		||||
        glColor3f($Cr, $Cg, $Cb);
 | 
			
		||||
        glVertex2f($x1 - $tx - $Rx, $y1 - $ty - $Ry);
 | 
			
		||||
        glVertex2f($x1 + $tx + $Rx, $y1 + $ty + $Ry);
 | 
			
		||||
        glEnd();
 | 
			
		||||
        glBegin(GL_TRIANGLE_STRIP);
 | 
			
		||||
        if (!$alphablend) {
 | 
			
		||||
            glColor3f($Br, $Bg, $Bb);
 | 
			
		||||
        } else {
 | 
			
		||||
            glColor4f($Cr, $Cg, $Cb, 0);
 | 
			
		||||
        }
 | 
			
		||||
        glVertex2f($x2 - $Rx - $cx, $y2 - $Ry - $cy);
 | 
			
		||||
        glVertex2f($x2 + $Rx - $cx, $y2 + $Ry - $cy);
 | 
			
		||||
        glColor3f($Cr, $Cg, $Cb);
 | 
			
		||||
        glVertex2f($x2 - $tx - $Rx, $y2 - $ty - $Ry);
 | 
			
		||||
        glVertex2f($x2 + $tx + $Rx, $y2 + $ty + $Ry);
 | 
			
		||||
        glEnd();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
package Slic3r::GUI::Plater::2DToolpaths::Dialog;
 | 
			
		||||
 | 
			
		||||
use Wx qw(:dialog :id :misc :sizer);
 | 
			
		||||
use Wx::Event qw(EVT_CLOSE);
 | 
			
		||||
use base 'Wx::Dialog';
 | 
			
		||||
 | 
			
		||||
sub new {
 | 
			
		||||
    my $class = shift;
 | 
			
		||||
    my ($parent, $print) = @_;
 | 
			
		||||
    my $self = $class->SUPER::new($parent, -1, "Toolpaths", wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
 | 
			
		||||
    
 | 
			
		||||
    my $sizer = Wx::BoxSizer->new(wxVERTICAL);
 | 
			
		||||
    $sizer->Add(Slic3r::GUI::Plater::2DToolpaths->new($self, $print), 1, wxEXPAND, 0);
 | 
			
		||||
    $self->SetSizer($sizer);
 | 
			
		||||
    $self->SetMinSize($self->GetSize);
 | 
			
		||||
    
 | 
			
		||||
    # needed to actually free memory
 | 
			
		||||
    EVT_CLOSE($self, sub {
 | 
			
		||||
        $self->EndModal(wxID_OK);
 | 
			
		||||
        $self->Destroy;
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,221 +0,0 @@
 | 
			
		|||
# Generate an anonymous or "lambda" 3D object. This gets used with the Add Generic option in Settings.
 | 
			
		||||
# 
 | 
			
		||||
 | 
			
		||||
package Slic3r::GUI::Plater::LambdaObjectDialog;
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
use utf8;
 | 
			
		||||
 | 
			
		||||
use Wx qw(wxTheApp :dialog :id :misc :sizer wxTAB_TRAVERSAL wxCB_READONLY wxTE_PROCESS_TAB);
 | 
			
		||||
use Wx::Event qw(EVT_CLOSE EVT_BUTTON EVT_COMBOBOX EVT_TEXT);
 | 
			
		||||
use Scalar::Util qw(looks_like_number);
 | 
			
		||||
use base 'Wx::Dialog';
 | 
			
		||||
 | 
			
		||||
sub new {
 | 
			
		||||
    my $class = shift;
 | 
			
		||||
    my ($parent, %params) = @_;
 | 
			
		||||
    my $self = $class->SUPER::new($parent, -1, "Lambda Object", wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
 | 
			
		||||
    # Note whether the window was already closed, so a pending update is not executed.
 | 
			
		||||
    $self->{already_closed} = 0;
 | 
			
		||||
    $self->{object_parameters} = { 
 | 
			
		||||
        type => "box", 
 | 
			
		||||
        dim => [1, 1, 1],
 | 
			
		||||
        cyl_r => 1,
 | 
			
		||||
        cyl_h => 1,
 | 
			
		||||
        sph_rho => 1.0,
 | 
			
		||||
        slab_h => 1.0,
 | 
			
		||||
        slab_z => 0.0,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
 | 
			
		||||
    my $button_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
 | 
			
		||||
    my $button_ok = $self->CreateStdDialogButtonSizer(wxOK);
 | 
			
		||||
    my $button_cancel = $self->CreateStdDialogButtonSizer(wxCANCEL);
 | 
			
		||||
    $button_sizer->Add($button_ok);
 | 
			
		||||
    $button_sizer->Add($button_cancel);
 | 
			
		||||
    EVT_BUTTON($self, wxID_OK, sub {
 | 
			
		||||
        # validate user input
 | 
			
		||||
        return if !$self->CanClose;
 | 
			
		||||
        
 | 
			
		||||
        $self->EndModal(wxID_OK);
 | 
			
		||||
        $self->Destroy;
 | 
			
		||||
    });
 | 
			
		||||
    EVT_BUTTON($self, wxID_CANCEL, sub {
 | 
			
		||||
        # validate user input
 | 
			
		||||
        return if !$self->CanClose;
 | 
			
		||||
        
 | 
			
		||||
        $self->EndModal(wxID_CANCEL);
 | 
			
		||||
        $self->Destroy;
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    my $optgroup_box;
 | 
			
		||||
    $optgroup_box = $self->{optgroup_box} = Slic3r::GUI::OptionsGroup->new(
 | 
			
		||||
        parent      => $self,
 | 
			
		||||
        title       => 'Add Cube...',
 | 
			
		||||
        on_change   => sub {
 | 
			
		||||
            # Do validation
 | 
			
		||||
            my ($opt_id) = @_;
 | 
			
		||||
            if ($opt_id == 0 || $opt_id == 1 || $opt_id == 2) {
 | 
			
		||||
                if (!looks_like_number($optgroup_box->get_value($opt_id))) {
 | 
			
		||||
                    return 0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $self->{object_parameters}->{dim}[$opt_id] = $optgroup_box->get_value($opt_id);
 | 
			
		||||
        },
 | 
			
		||||
        label_width => 100,
 | 
			
		||||
    );
 | 
			
		||||
    my @options = ("box", "slab", "cylinder", "sphere");
 | 
			
		||||
    $self->{type} = Wx::ComboBox->new($self, 1, "box", wxDefaultPosition, wxDefaultSize, \@options, wxCB_READONLY);
 | 
			
		||||
 | 
			
		||||
    $optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
        opt_id  =>  0,
 | 
			
		||||
        label   =>  'L',
 | 
			
		||||
        type    =>  'f',
 | 
			
		||||
        default =>  '1',
 | 
			
		||||
    ));
 | 
			
		||||
    $optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
        opt_id  =>  1,
 | 
			
		||||
        label   =>  'W',
 | 
			
		||||
        type    =>  'f',
 | 
			
		||||
        default =>  '1',
 | 
			
		||||
    ));
 | 
			
		||||
    $optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
        opt_id  =>  2,
 | 
			
		||||
        label   =>  'H',
 | 
			
		||||
        type    =>  'f',
 | 
			
		||||
        default =>  '1',
 | 
			
		||||
    ));
 | 
			
		||||
 | 
			
		||||
    my $optgroup_cylinder;
 | 
			
		||||
    $optgroup_cylinder = $self->{optgroup_cylinder} = Slic3r::GUI::OptionsGroup->new(
 | 
			
		||||
        parent      => $self,
 | 
			
		||||
        title       => 'Add Cylinder...',
 | 
			
		||||
        on_change   => sub {
 | 
			
		||||
            # Do validation
 | 
			
		||||
            my ($opt_id) = @_;
 | 
			
		||||
            if ($opt_id eq 'cyl_r' || $opt_id eq 'cyl_h') {
 | 
			
		||||
                if (!looks_like_number($optgroup_cylinder->get_value($opt_id))) {
 | 
			
		||||
                    return 0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $self->{object_parameters}->{$opt_id} = $optgroup_cylinder->get_value($opt_id);
 | 
			
		||||
        },
 | 
			
		||||
        label_width => 100,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $optgroup_cylinder->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
        opt_id  =>  "cyl_r",
 | 
			
		||||
        label   =>  'Radius',
 | 
			
		||||
        type    =>  'f',
 | 
			
		||||
        default =>  '1',
 | 
			
		||||
    ));
 | 
			
		||||
    $optgroup_cylinder->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
        opt_id  =>  "cyl_h",
 | 
			
		||||
        label   =>  'Height',
 | 
			
		||||
        type    =>  'f',
 | 
			
		||||
        default =>  '1',
 | 
			
		||||
    ));
 | 
			
		||||
 | 
			
		||||
    my $optgroup_sphere;
 | 
			
		||||
    $optgroup_sphere = $self->{optgroup_sphere} = Slic3r::GUI::OptionsGroup->new(
 | 
			
		||||
        parent      => $self,
 | 
			
		||||
        title       => 'Add Sphere...',
 | 
			
		||||
        on_change   => sub {
 | 
			
		||||
            # Do validation
 | 
			
		||||
            my ($opt_id) = @_;
 | 
			
		||||
            if ($opt_id eq 'sph_rho') {
 | 
			
		||||
                if (!looks_like_number($optgroup_sphere->get_value($opt_id))) {
 | 
			
		||||
                    return 0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $self->{object_parameters}->{$opt_id} = $optgroup_sphere->get_value($opt_id);
 | 
			
		||||
        },
 | 
			
		||||
        label_width => 100,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $optgroup_sphere->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
        opt_id  =>  "sph_rho",
 | 
			
		||||
        label   =>  'Rho',
 | 
			
		||||
        type    =>  'f',
 | 
			
		||||
        default =>  '1',
 | 
			
		||||
    ));
 | 
			
		||||
 | 
			
		||||
    my $optgroup_slab;
 | 
			
		||||
    $optgroup_slab = $self->{optgroup_slab} = Slic3r::GUI::OptionsGroup->new(
 | 
			
		||||
        parent      => $self,
 | 
			
		||||
        title       => 'Add Slab...',
 | 
			
		||||
        on_change   => sub {
 | 
			
		||||
            # Do validation
 | 
			
		||||
            my ($opt_id) = @_;
 | 
			
		||||
            if ($opt_id eq 'slab_z' || $opt_id eq 'slab_h') {
 | 
			
		||||
                if (!looks_like_number($optgroup_slab->get_value($opt_id))) {
 | 
			
		||||
                    return 0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $self->{object_parameters}->{$opt_id} = $optgroup_slab->get_value($opt_id);
 | 
			
		||||
        },
 | 
			
		||||
        label_width => 100,
 | 
			
		||||
    );
 | 
			
		||||
    $optgroup_slab->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
        opt_id  =>  "slab_h",
 | 
			
		||||
        label   =>  'H',
 | 
			
		||||
        type    =>  'f',
 | 
			
		||||
        default =>  '1',
 | 
			
		||||
    ));
 | 
			
		||||
    $optgroup_slab->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
        opt_id  =>  "slab_z",
 | 
			
		||||
        label   =>  'Initial Z',
 | 
			
		||||
        type    =>  'f',
 | 
			
		||||
        default =>  '0',
 | 
			
		||||
    ));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    EVT_COMBOBOX($self, 1, sub{ 
 | 
			
		||||
        $self->{object_parameters}->{type} = $self->{type}->GetValue();
 | 
			
		||||
        $self->_update_ui;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    $self->{sizer}->Add($self->{type}, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
 | 
			
		||||
    $self->{sizer}->Add($optgroup_box->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
 | 
			
		||||
    $self->{sizer}->Add($optgroup_cylinder->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
 | 
			
		||||
    $self->{sizer}->Add($optgroup_sphere->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
 | 
			
		||||
    $self->{sizer}->Add($optgroup_slab->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
 | 
			
		||||
    $self->{sizer}->Add($button_sizer,0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
 | 
			
		||||
    $self->_update_ui;
 | 
			
		||||
 | 
			
		||||
    $self->SetSizer($self->{sizer});
 | 
			
		||||
    $self->{sizer}->Fit($self);
 | 
			
		||||
    $self->{sizer}->SetSizeHints($self);
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
sub CanClose {
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
sub ObjectParameter {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    return $self->{object_parameters};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _update_ui {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    $self->{sizer}->Hide($self->{optgroup_cylinder}->sizer);
 | 
			
		||||
    $self->{sizer}->Hide($self->{optgroup_slab}->sizer);
 | 
			
		||||
    $self->{sizer}->Hide($self->{optgroup_box}->sizer);
 | 
			
		||||
    $self->{sizer}->Hide($self->{optgroup_sphere}->sizer);
 | 
			
		||||
    if ($self->{type}->GetValue eq "box") {
 | 
			
		||||
        $self->{sizer}->Show($self->{optgroup_box}->sizer);
 | 
			
		||||
    } elsif ($self->{type}->GetValue eq "cylinder") {
 | 
			
		||||
        $self->{sizer}->Show($self->{optgroup_cylinder}->sizer);
 | 
			
		||||
    } elsif ($self->{type}->GetValue eq "slab") {
 | 
			
		||||
        $self->{sizer}->Show($self->{optgroup_slab}->sizer);
 | 
			
		||||
    } elsif ($self->{type}->GetValue eq "sphere") {
 | 
			
		||||
        $self->{sizer}->Show($self->{optgroup_sphere}->sizer);
 | 
			
		||||
    }
 | 
			
		||||
    $self->{sizer}->Fit($self);
 | 
			
		||||
    $self->{sizer}->SetSizeHints($self);
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
1;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,284 +0,0 @@
 | 
			
		|||
# Cut an object at a Z position, keep either the top or the bottom of the object.
 | 
			
		||||
# This dialog gets opened with the "Cut..." button above the platter.
 | 
			
		||||
 | 
			
		||||
package Slic3r::GUI::Plater::ObjectCutDialog;
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
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 {
 | 
			
		||||
    my ($class, $parent, %params) = @_;
 | 
			
		||||
    my $self = $class->SUPER::new($parent, -1, $params{object}->name, wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
 | 
			
		||||
    $self->{model_object} = $params{model_object};
 | 
			
		||||
    $self->{new_model_objects} = [];
 | 
			
		||||
    # Mark whether the mesh cut is valid.
 | 
			
		||||
    # If not, it needs to be recalculated by _update() on wxTheApp->CallAfter() or on exit of the dialog.
 | 
			
		||||
    $self->{mesh_cut_valid} = 0;
 | 
			
		||||
    # Note whether the window was already closed, so a pending update is not executed.
 | 
			
		||||
    $self->{already_closed} = 0;
 | 
			
		||||
    
 | 
			
		||||
    # cut options
 | 
			
		||||
    $self->{cut_options} = {
 | 
			
		||||
        z               => 0,
 | 
			
		||||
        keep_upper      => 1,
 | 
			
		||||
        keep_lower      => 1,
 | 
			
		||||
        rotate_lower    => 1,
 | 
			
		||||
#        preview         => 1,
 | 
			
		||||
# Disabled live preview by default as it is not stable and/or the calculation takes too long for interactive usage.
 | 
			
		||||
        preview         => 0,
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    my $optgroup;
 | 
			
		||||
    $optgroup = $self->{optgroup} = Slic3r::GUI::OptionsGroup->new(
 | 
			
		||||
        parent      => $self,
 | 
			
		||||
        title       => 'Cut',
 | 
			
		||||
        on_change   => sub {
 | 
			
		||||
            my ($opt_id) = @_;
 | 
			
		||||
            # There seems to be an issue with wxWidgets 3.0.2/3.0.3, where the slider
 | 
			
		||||
            # genates tens of events for a single value change.
 | 
			
		||||
            # Only trigger the recalculation if the value changes
 | 
			
		||||
            # or a live preview was activated and the mesh cut is not valid yet.
 | 
			
		||||
            if ($self->{cut_options}{$opt_id} != $optgroup->get_value($opt_id) ||
 | 
			
		||||
                ! $self->{mesh_cut_valid} && $self->_life_preview_active()) {
 | 
			
		||||
                $self->{cut_options}{$opt_id} = $optgroup->get_value($opt_id);
 | 
			
		||||
                $self->{mesh_cut_valid} = 0;
 | 
			
		||||
                wxTheApp->CallAfter(sub {
 | 
			
		||||
                    $self->_update;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        label_width  => 120,
 | 
			
		||||
    );
 | 
			
		||||
    $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
        opt_id      => 'z',
 | 
			
		||||
        type        => 'slider',
 | 
			
		||||
        label       => 'Z',
 | 
			
		||||
        default     => $self->{cut_options}{z},
 | 
			
		||||
        min         => 0,
 | 
			
		||||
        max         => $self->{model_object}->bounding_box->size->z,
 | 
			
		||||
        full_width  => 1,
 | 
			
		||||
    ));
 | 
			
		||||
    {
 | 
			
		||||
        my $line = Slic3r::GUI::OptionsGroup::Line->new(
 | 
			
		||||
            label => 'Keep',
 | 
			
		||||
        );
 | 
			
		||||
        $line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
            opt_id  => 'keep_upper',
 | 
			
		||||
            type    => 'bool',
 | 
			
		||||
            label   => 'Upper part',
 | 
			
		||||
            default => $self->{cut_options}{keep_upper},
 | 
			
		||||
        ));
 | 
			
		||||
        $line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
            opt_id  => 'keep_lower',
 | 
			
		||||
            type    => 'bool',
 | 
			
		||||
            label   => 'Lower part',
 | 
			
		||||
            default => $self->{cut_options}{keep_lower},
 | 
			
		||||
        ));
 | 
			
		||||
        $optgroup->append_line($line);
 | 
			
		||||
    }
 | 
			
		||||
    $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
        opt_id      => 'rotate_lower',
 | 
			
		||||
        label       => 'Rotate lower part upwards',
 | 
			
		||||
        type        => 'bool',
 | 
			
		||||
        tooltip     => 'If enabled, the lower part will be rotated by 180° so that the flat cut surface lies on the print bed.',
 | 
			
		||||
        default     => $self->{cut_options}{rotate_lower},
 | 
			
		||||
    ));
 | 
			
		||||
    $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
        opt_id      => 'preview',
 | 
			
		||||
        label       => 'Show preview',
 | 
			
		||||
        type        => 'bool',
 | 
			
		||||
        tooltip     => 'If enabled, object will be cut in real time.',
 | 
			
		||||
        default     => $self->{cut_options}{preview},
 | 
			
		||||
    ));
 | 
			
		||||
    {
 | 
			
		||||
        my $cut_button_sizer = Wx::BoxSizer->new(wxVERTICAL);
 | 
			
		||||
        $self->{btn_cut} = Wx::Button->new($self, -1, "Perform cut", wxDefaultPosition, wxDefaultSize);
 | 
			
		||||
        $cut_button_sizer->Add($self->{btn_cut}, 0, wxALIGN_RIGHT | wxALL, 10);
 | 
			
		||||
        $optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new(
 | 
			
		||||
            sizer => $cut_button_sizer,
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # left pane with tree
 | 
			
		||||
    my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
 | 
			
		||||
    $left_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
 | 
			
		||||
    
 | 
			
		||||
    # right pane with preview canvas
 | 
			
		||||
    my $canvas;
 | 
			
		||||
    if ($Slic3r::GUI::have_OpenGL) {
 | 
			
		||||
        $canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
 | 
			
		||||
        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);
 | 
			
		||||
    $self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
 | 
			
		||||
    $self->{sizer}->Add($canvas, 1, wxEXPAND | wxALL, 0) if $canvas;
 | 
			
		||||
    
 | 
			
		||||
    $self->SetSizer($self->{sizer});
 | 
			
		||||
    $self->SetMinSize($self->GetSize);
 | 
			
		||||
    $self->{sizer}->SetSizeHints($self);
 | 
			
		||||
    
 | 
			
		||||
    EVT_BUTTON($self, $self->{btn_cut}, sub {
 | 
			
		||||
        # Recalculate the cut if the preview was not active.
 | 
			
		||||
        $self->_perform_cut() unless $self->{mesh_cut_valid};
 | 
			
		||||
 | 
			
		||||
        # Adjust position / orientation of the split object halves.
 | 
			
		||||
        if ($self->{new_model_objects}{lower}) {
 | 
			
		||||
            if ($self->{cut_options}{rotate_lower}) {
 | 
			
		||||
                $self->{new_model_objects}{lower}->rotate(PI, Slic3r::Pointf3->new(1,0,0));
 | 
			
		||||
                $self->{new_model_objects}{lower}->center_around_origin;  # align to Z = 0
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if ($self->{new_model_objects}{upper}) {
 | 
			
		||||
            $self->{new_model_objects}{upper}->center_around_origin;  # align to Z = 0
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        # 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();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    EVT_CLOSE($self, sub {
 | 
			
		||||
        # 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();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $self->_update;
 | 
			
		||||
    
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# scale Z down to original size since we're using the transformed mesh for 3D preview
 | 
			
		||||
# and cut dialog but ModelObject::cut() needs Z without any instance transformation
 | 
			
		||||
sub _mesh_slice_z_pos
 | 
			
		||||
{
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    return $self->{cut_options}{z} / $self->{model_object}->instances->[0]->scaling_factor;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Only perform live preview if just a single part of the object shall survive.
 | 
			
		||||
sub _life_preview_active
 | 
			
		||||
{
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    return $self->{cut_options}{preview} && ($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Slice the mesh, keep the top / bottom part.
 | 
			
		||||
sub _perform_cut 
 | 
			
		||||
{
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
 | 
			
		||||
    # Early exit. If the cut is valid, don't recalculate it.
 | 
			
		||||
    return if $self->{mesh_cut_valid};
 | 
			
		||||
 | 
			
		||||
    my $z = $self->_mesh_slice_z_pos();
 | 
			
		||||
 | 
			
		||||
    my ($new_model) = $self->{model_object}->cut($z);
 | 
			
		||||
    my ($upper_object, $lower_object) = @{$new_model->objects};
 | 
			
		||||
    $self->{new_model} = $new_model;
 | 
			
		||||
    $self->{new_model_objects} = {};
 | 
			
		||||
    if ($self->{cut_options}{keep_upper} && $upper_object->volumes_count > 0) {
 | 
			
		||||
        $self->{new_model_objects}{upper} = $upper_object;
 | 
			
		||||
    }
 | 
			
		||||
    if ($self->{cut_options}{keep_lower} && $lower_object->volumes_count > 0) {
 | 
			
		||||
        $self->{new_model_objects}{lower} = $lower_object;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $self->{mesh_cut_valid} = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _update {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
 | 
			
		||||
    # Don't update if the window was already closed.
 | 
			
		||||
    # We are not sure whether the action planned by wxTheApp->CallAfter() may be triggered after the window is closed.
 | 
			
		||||
    # Probably not, but better be safe than sorry, which is espetially true on multiple platforms.
 | 
			
		||||
    return if $self->{already_closed};
 | 
			
		||||
 | 
			
		||||
    # Only recalculate the cut, if the live cut preview is active.
 | 
			
		||||
    my $life_preview_active = $self->_life_preview_active();
 | 
			
		||||
    $self->_perform_cut() if $life_preview_active;
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        # scale Z down to original size since we're using the transformed mesh for 3D preview
 | 
			
		||||
        # and cut dialog but ModelObject::cut() needs Z without any instance transformation
 | 
			
		||||
        my $z = $self->_mesh_slice_z_pos();
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        # update canvas
 | 
			
		||||
        if ($self->{canvas}) {
 | 
			
		||||
            # get volumes to render
 | 
			
		||||
            my @objects = ();
 | 
			
		||||
            if ($life_preview_active) {
 | 
			
		||||
                push @objects, values %{$self->{new_model_objects}};
 | 
			
		||||
            } else {
 | 
			
		||||
                push @objects, $self->{model_object};
 | 
			
		||||
            }
 | 
			
		||||
        
 | 
			
		||||
            my $z_cut = $z + $self->{model_object}->bounding_box->z_min;        
 | 
			
		||||
        
 | 
			
		||||
            # get section contour
 | 
			
		||||
            my @expolygons = ();
 | 
			
		||||
            foreach my $volume (@{$self->{model_object}->volumes}) {
 | 
			
		||||
                next if !$volume->mesh;
 | 
			
		||||
                next if $volume->modifier;
 | 
			
		||||
                my $expp = $volume->mesh->slice([ $z_cut ])->[0];
 | 
			
		||||
                push @expolygons, @$expp;
 | 
			
		||||
            }
 | 
			
		||||
            foreach my $expolygon (@expolygons) {
 | 
			
		||||
                $self->{model_object}->instances->[0]->transform_polygon($_)
 | 
			
		||||
                    for @$expolygon;
 | 
			
		||||
                $expolygon->translate(map Slic3r::Geometry::scale($_), @{ $self->{model_object}->instances->[0]->offset });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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});
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # update controls
 | 
			
		||||
    {
 | 
			
		||||
        my $z = $self->{cut_options}{z};
 | 
			
		||||
        my $optgroup = $self->{optgroup};
 | 
			
		||||
        $optgroup->get_field('keep_upper')->toggle(my $have_upper = abs($z - $optgroup->get_option('z')->max) > 0.1);
 | 
			
		||||
        $optgroup->get_field('keep_lower')->toggle(my $have_lower = $z > 0.1);
 | 
			
		||||
        $optgroup->get_field('rotate_lower')->toggle($z > 0 && $self->{cut_options}{keep_lower});
 | 
			
		||||
# Disabled live preview by default as it is not stable and/or the calculation takes too long for interactive usage.
 | 
			
		||||
#        $optgroup->get_field('preview')->toggle($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower});
 | 
			
		||||
    
 | 
			
		||||
        # update cut button
 | 
			
		||||
        if (($self->{cut_options}{keep_upper} && $have_upper)
 | 
			
		||||
            || ($self->{cut_options}{keep_lower} && $have_lower)) {
 | 
			
		||||
            $self->{btn_cut}->Enable;
 | 
			
		||||
        } else {
 | 
			
		||||
            $self->{btn_cut}->Disable;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub NewModelObjects {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    return values %{ $self->{new_model_objects} };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,588 +0,0 @@
 | 
			
		|||
# Configuration of mesh modifiers and their parameters.
 | 
			
		||||
# This panel is inserted into ObjectSettingsDialog.
 | 
			
		||||
 | 
			
		||||
package Slic3r::GUI::Plater::ObjectPartsPanel;
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
use utf8;
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
use constant ICON_SOLIDMESH     => 1;
 | 
			
		||||
use constant ICON_MODIFIERMESH  => 2;
 | 
			
		||||
 | 
			
		||||
sub new {
 | 
			
		||||
    my ($class, $parent, %params) = @_;
 | 
			
		||||
    my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
 | 
			
		||||
    
 | 
			
		||||
    # C++ type Slic3r::ModelObject
 | 
			
		||||
    my $object = $self->{model_object} = $params{model_object};
 | 
			
		||||
 | 
			
		||||
    # Save state for sliders.
 | 
			
		||||
    $self->{move_options} = {
 | 
			
		||||
        x               => 0,
 | 
			
		||||
        y               => 0,
 | 
			
		||||
        z               => 0,
 | 
			
		||||
    };
 | 
			
		||||
    $self->{last_coords} = {
 | 
			
		||||
        x               => 0,
 | 
			
		||||
        y               => 0,
 | 
			
		||||
        z               => 0,
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    # create TreeCtrl
 | 
			
		||||
    my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [300, 100], 
 | 
			
		||||
        wxTR_NO_BUTTONS | wxSUNKEN_BORDER | wxTR_HAS_VARIABLE_ROW_HEIGHT
 | 
			
		||||
        | wxTR_SINGLE);
 | 
			
		||||
    {
 | 
			
		||||
        $self->{tree_icons} = Wx::ImageList->new(16, 16, 1);
 | 
			
		||||
        $tree->AssignImageList($self->{tree_icons});
 | 
			
		||||
        $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("brick.png"), wxBITMAP_TYPE_PNG));     # ICON_OBJECT
 | 
			
		||||
        $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG));   # ICON_SOLIDMESH
 | 
			
		||||
        $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("plugin.png"), wxBITMAP_TYPE_PNG));    # ICON_MODIFIERMESH
 | 
			
		||||
        
 | 
			
		||||
        my $rootId = $tree->AddRoot("Object", ICON_OBJECT);
 | 
			
		||||
        $tree->SetPlData($rootId, { type => 'object' });
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # buttons
 | 
			
		||||
    $self->{btn_load_part} = Wx::Button->new($self, -1, "Load part…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
 | 
			
		||||
    $self->{btn_load_modifier} = Wx::Button->new($self, -1, "Load modifier…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
 | 
			
		||||
    $self->{btn_load_lambda_modifier} = Wx::Button->new($self, -1, "Load generic…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
 | 
			
		||||
    $self->{btn_delete} = Wx::Button->new($self, -1, "Delete part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
 | 
			
		||||
    $self->{btn_split} = Wx::Button->new($self, -1, "Split part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
 | 
			
		||||
    $self->{btn_move_up} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT);
 | 
			
		||||
    $self->{btn_move_down} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT);
 | 
			
		||||
    $self->{btn_load_part}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG));
 | 
			
		||||
    $self->{btn_load_modifier}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG));
 | 
			
		||||
    $self->{btn_load_lambda_modifier}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG));
 | 
			
		||||
    $self->{btn_delete}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG));
 | 
			
		||||
    $self->{btn_split}->SetBitmap(Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG));
 | 
			
		||||
    $self->{btn_move_up}->SetBitmap(Wx::Bitmap->new(Slic3r::var("bullet_arrow_up.png"), wxBITMAP_TYPE_PNG));
 | 
			
		||||
    $self->{btn_move_down}->SetBitmap(Wx::Bitmap->new(Slic3r::var("bullet_arrow_down.png"), wxBITMAP_TYPE_PNG));
 | 
			
		||||
    
 | 
			
		||||
    # buttons sizer
 | 
			
		||||
    my $buttons_sizer = Wx::GridSizer->new(2, 3);
 | 
			
		||||
    $buttons_sizer->Add($self->{btn_load_part}, 0, wxEXPAND | wxBOTTOM | wxRIGHT, 5);
 | 
			
		||||
    $buttons_sizer->Add($self->{btn_load_modifier}, 0, wxEXPAND | wxBOTTOM | wxRIGHT, 5);
 | 
			
		||||
    $buttons_sizer->Add($self->{btn_load_lambda_modifier}, 0, wxEXPAND | wxBOTTOM, 5);
 | 
			
		||||
    $buttons_sizer->Add($self->{btn_delete}, 0, wxEXPAND | wxRIGHT, 5);
 | 
			
		||||
    $buttons_sizer->Add($self->{btn_split}, 0, wxEXPAND | wxRIGHT, 5);
 | 
			
		||||
    {
 | 
			
		||||
        my $up_down_sizer = Wx::GridSizer->new(1, 2);
 | 
			
		||||
        $up_down_sizer->Add($self->{btn_move_up}, 0, wxEXPAND | wxRIGHT, 5);
 | 
			
		||||
        $up_down_sizer->Add($self->{btn_move_down}, 0, wxEXPAND, 5);
 | 
			
		||||
        $buttons_sizer->Add($up_down_sizer, 0, wxEXPAND, 5);
 | 
			
		||||
    }
 | 
			
		||||
    $self->{btn_load_part}->SetFont($Slic3r::GUI::small_font);
 | 
			
		||||
    $self->{btn_load_modifier}->SetFont($Slic3r::GUI::small_font);
 | 
			
		||||
    $self->{btn_load_lambda_modifier}->SetFont($Slic3r::GUI::small_font);
 | 
			
		||||
    $self->{btn_delete}->SetFont($Slic3r::GUI::small_font);
 | 
			
		||||
    $self->{btn_split}->SetFont($Slic3r::GUI::small_font);
 | 
			
		||||
    $self->{btn_move_up}->SetFont($Slic3r::GUI::small_font);
 | 
			
		||||
    $self->{btn_move_down}->SetFont($Slic3r::GUI::small_font);
 | 
			
		||||
    
 | 
			
		||||
    # part settings panel
 | 
			
		||||
    $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; $self->_update_canvas; });
 | 
			
		||||
    my $settings_sizer = Wx::StaticBoxSizer->new($self->{staticbox} = Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL);
 | 
			
		||||
    $settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0);
 | 
			
		||||
 | 
			
		||||
    my $optgroup_movers;
 | 
			
		||||
    $optgroup_movers = $self->{optgroup_movers} = Slic3r::GUI::OptionsGroup->new(
 | 
			
		||||
        parent      => $self,
 | 
			
		||||
        title       => 'Move',
 | 
			
		||||
        on_change   => sub {
 | 
			
		||||
            my ($opt_id) = @_;
 | 
			
		||||
            # There seems to be an issue with wxWidgets 3.0.2/3.0.3, where the slider
 | 
			
		||||
            # genates tens of events for a single value change.
 | 
			
		||||
            # Only trigger the recalculation if the value changes
 | 
			
		||||
            # or a live preview was activated and the mesh cut is not valid yet.
 | 
			
		||||
            if ($self->{move_options}{$opt_id} != $optgroup_movers->get_value($opt_id)) {
 | 
			
		||||
                $self->{move_options}{$opt_id} = $optgroup_movers->get_value($opt_id);
 | 
			
		||||
                wxTheApp->CallAfter(sub {
 | 
			
		||||
                    $self->_update;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        label_width  => 20,
 | 
			
		||||
    );
 | 
			
		||||
    $optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
        opt_id      => 'x',
 | 
			
		||||
        type        => 'slider',
 | 
			
		||||
        label       => 'X',
 | 
			
		||||
        default     => 0,
 | 
			
		||||
        min         => -($self->{model_object}->bounding_box->size->x)*4,
 | 
			
		||||
        max         => $self->{model_object}->bounding_box->size->x*4,
 | 
			
		||||
        full_width  => 1,
 | 
			
		||||
    ));
 | 
			
		||||
    $optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
        opt_id      => 'y',
 | 
			
		||||
        type        => 'slider',
 | 
			
		||||
        label       => 'Y',
 | 
			
		||||
        default     => 0,
 | 
			
		||||
        min         => -($self->{model_object}->bounding_box->size->y)*4,
 | 
			
		||||
        max         => $self->{model_object}->bounding_box->size->y*4,
 | 
			
		||||
        full_width  => 1,
 | 
			
		||||
    ));
 | 
			
		||||
    $optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
 | 
			
		||||
        opt_id      => 'z',
 | 
			
		||||
        type        => 'slider',
 | 
			
		||||
        label       => 'Z',
 | 
			
		||||
        default     => 0,
 | 
			
		||||
        min         => -($self->{model_object}->bounding_box->size->z)*4,
 | 
			
		||||
        max         => $self->{model_object}->bounding_box->size->z*4,
 | 
			
		||||
        full_width  => 1,
 | 
			
		||||
    ));
 | 
			
		||||
 
 | 
			
		||||
    # left pane with tree
 | 
			
		||||
    my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
 | 
			
		||||
    $left_sizer->Add($tree, 3, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
 | 
			
		||||
    $left_sizer->Add($buttons_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
 | 
			
		||||
    $left_sizer->Add($settings_sizer, 5, wxEXPAND | wxALL, 0);
 | 
			
		||||
    $left_sizer->Add($optgroup_movers->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
 | 
			
		||||
    
 | 
			
		||||
    # right pane with preview canvas
 | 
			
		||||
    my $canvas;
 | 
			
		||||
    if ($Slic3r::GUI::have_OpenGL) {
 | 
			
		||||
        $canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
 | 
			
		||||
        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) = @_;
 | 
			
		||||
            $self->reload_tree($volume_idx);
 | 
			
		||||
        });
 | 
			
		||||
        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]);
 | 
			
		||||
        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);
 | 
			
		||||
    $self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxALL, 0);
 | 
			
		||||
    $self->{sizer}->Add($canvas, 1, wxEXPAND | wxALL, 0) if $canvas;
 | 
			
		||||
    
 | 
			
		||||
    $self->SetSizer($self->{sizer});
 | 
			
		||||
    $self->{sizer}->SetSizeHints($self);
 | 
			
		||||
    
 | 
			
		||||
    # attach events
 | 
			
		||||
    EVT_TREE_ITEM_COLLAPSING($self, $tree, sub {
 | 
			
		||||
        my ($self, $event) = @_;
 | 
			
		||||
        $event->Veto;
 | 
			
		||||
    });
 | 
			
		||||
    EVT_TREE_SEL_CHANGED($self, $tree, sub {
 | 
			
		||||
        my ($self, $event) = @_;
 | 
			
		||||
        return if $self->{disable_tree_sel_changed_event};
 | 
			
		||||
        $self->selection_changed;
 | 
			
		||||
    });
 | 
			
		||||
    EVT_TREE_KEY_DOWN($self, $tree, \&on_tree_key_down);
 | 
			
		||||
    EVT_BUTTON($self, $self->{btn_load_part}, sub { $self->on_btn_load(0) });
 | 
			
		||||
    EVT_BUTTON($self, $self->{btn_load_modifier}, sub { $self->on_btn_load(1) });
 | 
			
		||||
    EVT_BUTTON($self, $self->{btn_load_lambda_modifier}, sub { $self->on_btn_lambda(1) });
 | 
			
		||||
    EVT_BUTTON($self, $self->{btn_delete}, \&on_btn_delete);
 | 
			
		||||
    EVT_BUTTON($self, $self->{btn_split}, \&on_btn_split);
 | 
			
		||||
    EVT_BUTTON($self, $self->{btn_move_up}, \&on_btn_move_up);
 | 
			
		||||
    EVT_BUTTON($self, $self->{btn_move_down}, \&on_btn_move_down);
 | 
			
		||||
    EVT_KEY_DOWN($canvas, sub {
 | 
			
		||||
        my ($canvas, $event) = @_;
 | 
			
		||||
        if ($event->GetKeyCode == WXK_DELETE) {
 | 
			
		||||
            $canvas->GetParent->on_btn_delete;
 | 
			
		||||
        } else {
 | 
			
		||||
            $event->Skip;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    $self->reload_tree;
 | 
			
		||||
    
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub reload_tree {
 | 
			
		||||
    my ($self, $selected_volume_idx) = @_;
 | 
			
		||||
    
 | 
			
		||||
    $selected_volume_idx //= -1;
 | 
			
		||||
    my $object  = $self->{model_object};
 | 
			
		||||
    my $tree    = $self->{tree};
 | 
			
		||||
    my $rootId  = $tree->GetRootItem;
 | 
			
		||||
    
 | 
			
		||||
    # despite wxWidgets states that DeleteChildren "will not generate any events unlike Delete() method",
 | 
			
		||||
    # the MSW implementation of DeleteChildren actually calls Delete() for each item, so
 | 
			
		||||
    # EVT_TREE_SEL_CHANGED is being called, with bad effects (the event handler is called; this 
 | 
			
		||||
    # subroutine is never continued; an invisible EndModal is called on the dialog causing Plater
 | 
			
		||||
    # to continue its logic and rescheduling the background process etc. GH #2774)
 | 
			
		||||
    $self->{disable_tree_sel_changed_event} = 1;
 | 
			
		||||
    $tree->DeleteChildren($rootId);
 | 
			
		||||
    $self->{disable_tree_sel_changed_event} = 0;
 | 
			
		||||
    
 | 
			
		||||
    my $selectedId = $rootId;
 | 
			
		||||
    foreach my $volume_id (0..$#{$object->volumes}) {
 | 
			
		||||
        my $volume = $object->volumes->[$volume_id];
 | 
			
		||||
        
 | 
			
		||||
        my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH;
 | 
			
		||||
        my $itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon);
 | 
			
		||||
        if ($volume_id == $selected_volume_idx) {
 | 
			
		||||
            $selectedId = $itemId;
 | 
			
		||||
        }
 | 
			
		||||
        $tree->SetPlData($itemId, {
 | 
			
		||||
            type        => 'volume',
 | 
			
		||||
            volume_id   => $volume_id,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    $tree->ExpandAll;
 | 
			
		||||
    
 | 
			
		||||
    Slic3r::GUI->CallAfter(sub {
 | 
			
		||||
        $self->{tree}->SelectItem($selectedId);
 | 
			
		||||
        
 | 
			
		||||
        # SelectItem() should trigger EVT_TREE_SEL_CHANGED as per wxWidgets docs,
 | 
			
		||||
        # but in fact it doesn't if the given item is already selected (this happens
 | 
			
		||||
        # on first load)
 | 
			
		||||
        $self->selection_changed;
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub get_selection {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $nodeId = $self->{tree}->GetSelection;
 | 
			
		||||
    if ($nodeId->IsOk) {
 | 
			
		||||
        return $self->{tree}->GetPlData($nodeId);
 | 
			
		||||
    }
 | 
			
		||||
    return undef;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub selection_changed {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    
 | 
			
		||||
    # deselect all meshes
 | 
			
		||||
    if ($self->{canvas}) {
 | 
			
		||||
        Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas});
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # disable things as if nothing is selected
 | 
			
		||||
    $self->{'btn_' . $_}->Disable for (qw(delete move_up move_down split));
 | 
			
		||||
    $self->{settings_panel}->disable;
 | 
			
		||||
    $self->{settings_panel}->set_config(undef);
 | 
			
		||||
 | 
			
		||||
    # reset move sliders
 | 
			
		||||
    $self->{optgroup_movers}->set_value("x", 0);
 | 
			
		||||
    $self->{optgroup_movers}->set_value("y", 0);
 | 
			
		||||
    $self->{optgroup_movers}->set_value("z", 0);
 | 
			
		||||
    $self->{move_options} = {
 | 
			
		||||
        x               => 0,
 | 
			
		||||
        y               => 0,
 | 
			
		||||
        z               => 0,
 | 
			
		||||
    };
 | 
			
		||||
    $self->{last_coords} = {
 | 
			
		||||
        x               => 0,
 | 
			
		||||
        y               => 0,
 | 
			
		||||
        z               => 0,
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    if (my $itemData = $self->get_selection) {
 | 
			
		||||
        my ($config, @opt_keys);
 | 
			
		||||
        if ($itemData->{type} eq 'volume') {
 | 
			
		||||
            # select volume in 3D preview
 | 
			
		||||
            if ($self->{canvas}) {
 | 
			
		||||
                Slic3r::GUI::_3DScene::select_volume($self->{canvas}, $itemData->{volume_id});
 | 
			
		||||
            }
 | 
			
		||||
            $self->{btn_delete}->Enable;
 | 
			
		||||
            $self->{btn_split}->Enable;
 | 
			
		||||
            $self->{btn_move_up}->Enable if $itemData->{volume_id} > 0;
 | 
			
		||||
            $self->{btn_move_down}->Enable if $itemData->{volume_id} + 1 < $self->{model_object}->volumes_count;
 | 
			
		||||
            
 | 
			
		||||
            # attach volume config to settings panel
 | 
			
		||||
            my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
 | 
			
		||||
   
 | 
			
		||||
            if ($volume->modifier) {
 | 
			
		||||
                $self->{optgroup_movers}->enable;
 | 
			
		||||
            } else {
 | 
			
		||||
                $self->{optgroup_movers}->disable;
 | 
			
		||||
            }
 | 
			
		||||
            $config = $volume->config;
 | 
			
		||||
            $self->{staticbox}->SetLabel('Part Settings');
 | 
			
		||||
            
 | 
			
		||||
            # get default values
 | 
			
		||||
            @opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys};
 | 
			
		||||
        } elsif ($itemData->{type} eq 'object') {
 | 
			
		||||
            # select nothing in 3D preview
 | 
			
		||||
            
 | 
			
		||||
            # attach object config to settings panel
 | 
			
		||||
            $self->{optgroup_movers}->disable;
 | 
			
		||||
            $self->{staticbox}->SetLabel('Object Settings');
 | 
			
		||||
            @opt_keys = (map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new);
 | 
			
		||||
            $config = $self->{model_object}->config;
 | 
			
		||||
        }
 | 
			
		||||
        # get default values
 | 
			
		||||
        my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys);
 | 
			
		||||
 | 
			
		||||
       # decide which settings will be shown by default
 | 
			
		||||
        if ($itemData->{type} eq 'object') {
 | 
			
		||||
            $config->set_ifndef('wipe_into_objects', 0);
 | 
			
		||||
            $config->set_ifndef('wipe_into_infill', 0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        # append default extruder
 | 
			
		||||
        push @opt_keys, 'extruder';
 | 
			
		||||
        $default_config->set('extruder', 0);
 | 
			
		||||
        $config->set_ifndef('extruder', 0);
 | 
			
		||||
        $self->{settings_panel}->set_default_config($default_config);
 | 
			
		||||
        $self->{settings_panel}->set_config($config);
 | 
			
		||||
        $self->{settings_panel}->set_opt_keys(\@opt_keys);
 | 
			
		||||
 | 
			
		||||
        # disable minus icon to remove the settings
 | 
			
		||||
        if ($itemData->{type} eq 'object') {
 | 
			
		||||
            $self->{settings_panel}->set_fixed_options([qw(extruder), qw(wipe_into_infill), qw(wipe_into_objects)]);
 | 
			
		||||
	} else {
 | 
			
		||||
            $self->{settings_panel}->set_fixed_options([qw(extruder)]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $self->{settings_panel}->enable;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    Slic3r::GUI::_3DScene::render($self->{canvas}) if $self->{canvas};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub on_btn_load {
 | 
			
		||||
    my ($self, $is_modifier) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my @input_files = wxTheApp->open_model($self);
 | 
			
		||||
    foreach my $input_file (@input_files) {
 | 
			
		||||
        my $model = eval { Slic3r::Model->read_from_file($input_file) };
 | 
			
		||||
        if ($@) {
 | 
			
		||||
            Slic3r::GUI::show_error($self, $@);
 | 
			
		||||
            next;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        foreach my $object (@{$model->objects}) {
 | 
			
		||||
            foreach my $volume (@{$object->volumes}) {
 | 
			
		||||
                my $new_volume = $self->{model_object}->add_volume($volume);
 | 
			
		||||
                $new_volume->set_modifier($is_modifier);
 | 
			
		||||
                $new_volume->set_name(basename($input_file));
 | 
			
		||||
                
 | 
			
		||||
                # apply the same translation we applied to the object
 | 
			
		||||
                $new_volume->mesh->translate(@{$self->{model_object}->origin_translation});
 | 
			
		||||
                
 | 
			
		||||
                # set a default extruder value, since user can't add it manually
 | 
			
		||||
                $new_volume->config->set_ifndef('extruder', 0);
 | 
			
		||||
                
 | 
			
		||||
                $self->{parts_changed} = 1;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $self->{model_object}->center_around_origin if $self->{parts_changed};
 | 
			
		||||
    $self->_parts_changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub on_btn_lambda {
 | 
			
		||||
    my ($self, $is_modifier) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $dlg = Slic3r::GUI::Plater::LambdaObjectDialog->new($self);
 | 
			
		||||
    if ($dlg->ShowModal() == wxID_CANCEL) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    my $params = $dlg->ObjectParameter;
 | 
			
		||||
    my $type = "".$params->{"type"};
 | 
			
		||||
    my $name = "lambda-".$params->{"type"};
 | 
			
		||||
    my $mesh;
 | 
			
		||||
 | 
			
		||||
    if ($type eq "box") {
 | 
			
		||||
        $mesh = Slic3r::TriangleMesh::cube($params->{"dim"}[0], $params->{"dim"}[1], $params->{"dim"}[2]);
 | 
			
		||||
    } elsif ($type eq "cylinder") {
 | 
			
		||||
        $mesh = Slic3r::TriangleMesh::cylinder($params->{"cyl_r"}, $params->{"cyl_h"});
 | 
			
		||||
    } elsif ($type eq "sphere") {
 | 
			
		||||
        $mesh = Slic3r::TriangleMesh::sphere($params->{"sph_rho"});
 | 
			
		||||
    } elsif ($type eq "slab") {
 | 
			
		||||
        $mesh = Slic3r::TriangleMesh::cube($self->{model_object}->bounding_box->size->x*1.5, $self->{model_object}->bounding_box->size->y*1.5, $params->{"slab_h"});
 | 
			
		||||
        # box sets the base coordinate at 0,0, move to center of plate and move it up to initial_z
 | 
			
		||||
        $mesh->translate(-$self->{model_object}->bounding_box->size->x*1.5/2.0, -$self->{model_object}->bounding_box->size->y*1.5/2.0, $params->{"slab_z"});
 | 
			
		||||
    } else {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    $mesh->repair;
 | 
			
		||||
    my $new_volume = $self->{model_object}->add_volume(mesh => $mesh);
 | 
			
		||||
    $new_volume->set_modifier($is_modifier);
 | 
			
		||||
    $new_volume->set_name($name);
 | 
			
		||||
 | 
			
		||||
    # set a default extruder value, since user can't add it manually
 | 
			
		||||
    $new_volume->config->set_ifndef('extruder', 0);
 | 
			
		||||
 | 
			
		||||
    $self->{parts_changed} = 1;
 | 
			
		||||
    $self->_parts_changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub on_tree_key_down {
 | 
			
		||||
    my ($self, $event) = @_;
 | 
			
		||||
    my $keycode = $event->GetKeyCode;    
 | 
			
		||||
    # Wx >= 0.9911
 | 
			
		||||
    if (defined(&Wx::TreeEvent::GetKeyEvent)) { 
 | 
			
		||||
        if ($event->GetKeyEvent->GetModifiers & wxMOD_CONTROL) {
 | 
			
		||||
            if ($keycode == WXK_UP) {
 | 
			
		||||
                $event->Skip;
 | 
			
		||||
                $self->on_btn_move_up;
 | 
			
		||||
            } elsif ($keycode == WXK_DOWN) {
 | 
			
		||||
                $event->Skip;
 | 
			
		||||
                $self->on_btn_move_down;
 | 
			
		||||
            }
 | 
			
		||||
        } elsif ($keycode == WXK_DELETE) {
 | 
			
		||||
            $self->on_btn_delete;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub on_btn_move_up {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    my $itemData = $self->get_selection;
 | 
			
		||||
    if ($itemData && $itemData->{type} eq 'volume') {
 | 
			
		||||
        my $volume_id = $itemData->{volume_id};
 | 
			
		||||
        if ($self->{model_object}->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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub on_btn_move_down {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    my $itemData = $self->get_selection;
 | 
			
		||||
    if ($itemData && $itemData->{type} eq 'volume') {
 | 
			
		||||
        my $volume_id = $itemData->{volume_id};
 | 
			
		||||
        if ($self->{model_object}->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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub on_btn_delete {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
 | 
			
		||||
    my $itemData = $self->get_selection;
 | 
			
		||||
    if ($itemData && $itemData->{type} eq 'volume') {
 | 
			
		||||
        my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
 | 
			
		||||
 | 
			
		||||
        # if user is deleting the last solid part, throw error
 | 
			
		||||
        if (!$volume->modifier && scalar(grep !$_->modifier, @{$self->{model_object}->volumes}) == 1) {
 | 
			
		||||
            Slic3r::GUI::show_error($self, "You can't delete the last solid part from this object.");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $self->{model_object}->delete_volume($itemData->{volume_id});
 | 
			
		||||
        $self->{parts_changed} = 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $self->{model_object}->center_around_origin if $self->{parts_changed};
 | 
			
		||||
    $self->_parts_changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub on_btn_split {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
 | 
			
		||||
    my $itemData = $self->get_selection;
 | 
			
		||||
    if ($itemData && $itemData->{type} eq 'volume') {
 | 
			
		||||
        my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
 | 
			
		||||
        my $nozzle_dmrs = $self->GetParent->GetParent->GetParent->{config}->get('nozzle_diameter');
 | 
			
		||||
        $self->{parts_changed} = 1 if $volume->split(scalar(@$nozzle_dmrs)) > 1;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    $self->_parts_changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _parts_changed {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    
 | 
			
		||||
    $self->reload_tree;
 | 
			
		||||
    if ($self->{canvas}) {
 | 
			
		||||
        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});        
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub CanClose {
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    
 | 
			
		||||
    return 1;  # skip validation for now
 | 
			
		||||
    
 | 
			
		||||
    # validate options before allowing user to dismiss the dialog
 | 
			
		||||
    # the validate method only works on full configs so we have
 | 
			
		||||
    # to merge our settings with the default ones
 | 
			
		||||
    my $config = $self->GetParent->GetParent->GetParent->GetParent->GetParent->config->clone;
 | 
			
		||||
    eval {
 | 
			
		||||
        $config->apply($self->model_object->config);
 | 
			
		||||
        $config->validate;
 | 
			
		||||
    };
 | 
			
		||||
    return ! Slic3r::GUI::catch_error($self);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub Destroy {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    $self->{canvas}->Destroy if ($self->{canvas});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub PartsChanged {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    return $self->{parts_changed};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub PartSettingsChanged {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    return $self->{part_settings_changed};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _update_canvas {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    
 | 
			
		||||
    if ($self->{canvas}) {
 | 
			
		||||
        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') {
 | 
			
		||||
                Slic3r::GUI::_3DScene::select_volume($self->{canvas}, $itemData->{volume_id});
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
 | 
			
		||||
        Slic3r::GUI::_3DScene::render($self->{canvas});
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _update {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    my ($m_x, $m_y, $m_z) = ($self->{move_options}{x}, $self->{move_options}{y}, $self->{move_options}{z});
 | 
			
		||||
    my ($l_x, $l_y, $l_z) = ($self->{last_coords}{x}, $self->{last_coords}{y}, $self->{last_coords}{z});
 | 
			
		||||
    
 | 
			
		||||
    my $itemData = $self->get_selection;
 | 
			
		||||
    if ($itemData && $itemData->{type} eq 'volume') {
 | 
			
		||||
        my $d = Slic3r::Pointf3->new($m_x - $l_x, $m_y - $l_y, $m_z - $l_z);
 | 
			
		||||
        my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
 | 
			
		||||
        $volume->mesh->translate(@{$d});
 | 
			
		||||
        $self->{last_coords}{x} = $m_x;
 | 
			
		||||
        $self->{last_coords}{y} = $m_y;
 | 
			
		||||
        $self->{last_coords}{z} = $m_z;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $self->{parts_changed} = 1;
 | 
			
		||||
    my @objects = ();
 | 
			
		||||
    push @objects, $self->{model_object};
 | 
			
		||||
    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;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,224 +0,0 @@
 | 
			
		|||
# This dialog opens up when double clicked on an object line in the list at the right side of the platter.
 | 
			
		||||
# One may load additional STLs and additional modifier STLs,
 | 
			
		||||
# one may change the properties of the print per each modifier mesh or a Z-span.
 | 
			
		||||
 | 
			
		||||
package Slic3r::GUI::Plater::ObjectSettingsDialog;
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
use utf8;
 | 
			
		||||
 | 
			
		||||
use Wx qw(:dialog :id :misc :sizer :systemsettings :notebook wxTAB_TRAVERSAL wxTheApp);
 | 
			
		||||
use Wx::Event qw(EVT_BUTTON);
 | 
			
		||||
use base 'Wx::Dialog';
 | 
			
		||||
 | 
			
		||||
# Called with
 | 
			
		||||
# %params{object} of a Perl type Slic3r::GUI::Plater::Object
 | 
			
		||||
# %params{model_object} of a C++ type Slic3r::ModelObject
 | 
			
		||||
sub new {
 | 
			
		||||
    my ($class, $parent, %params) = @_;
 | 
			
		||||
    my $self = $class->SUPER::new($parent, -1, "Settings for " . $params{object}->name, wxDefaultPosition, [700,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
 | 
			
		||||
    $self->{$_} = $params{$_} for keys %params;
 | 
			
		||||
 | 
			
		||||
    $self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL);
 | 
			
		||||
    $self->{tabpanel}->AddPage($self->{parts} = Slic3r::GUI::Plater::ObjectPartsPanel->new($self->{tabpanel}, model_object => $params{model_object}), "Parts");
 | 
			
		||||
    $self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}), "Layers");
 | 
			
		||||
    
 | 
			
		||||
    my $buttons = $self->CreateStdDialogButtonSizer(wxOK);
 | 
			
		||||
    EVT_BUTTON($self, wxID_OK, sub {
 | 
			
		||||
        # validate user input
 | 
			
		||||
        return if !$self->{parts}->CanClose;
 | 
			
		||||
        return if !$self->{layers}->CanClose;
 | 
			
		||||
        
 | 
			
		||||
        # notify tabs
 | 
			
		||||
        $self->{layers}->Closing;
 | 
			
		||||
        
 | 
			
		||||
        # save window size
 | 
			
		||||
        wxTheApp->save_window_pos($self, "object_settings");
 | 
			
		||||
        
 | 
			
		||||
        $self->EndModal(wxID_OK);
 | 
			
		||||
        $self->{parts}->Destroy;
 | 
			
		||||
        $self->Destroy;
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    my $sizer = Wx::BoxSizer->new(wxVERTICAL);
 | 
			
		||||
    $sizer->Add($self->{tabpanel}, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
 | 
			
		||||
    $sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
 | 
			
		||||
    
 | 
			
		||||
    $self->SetSizer($sizer);
 | 
			
		||||
    $self->SetMinSize($self->GetSize);
 | 
			
		||||
    
 | 
			
		||||
    $self->Layout;
 | 
			
		||||
    
 | 
			
		||||
    wxTheApp->restore_window_pos($self, "object_settings");
 | 
			
		||||
    
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub PartsChanged {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    return $self->{parts}->PartsChanged;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub PartSettingsChanged {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    return $self->{parts}->PartSettingsChanged || $self->{layers}->LayersChanged;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
package Slic3r::GUI::Plater::ObjectDialog::BaseTab;
 | 
			
		||||
use base 'Wx::Panel';
 | 
			
		||||
 | 
			
		||||
sub model_object {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    # $self->GetParent->GetParent is of type Slic3r::GUI::Plater::ObjectSettingsDialog
 | 
			
		||||
    return $self->GetParent->GetParent->{model_object};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
package Slic3r::GUI::Plater::ObjectDialog::LayersTab;
 | 
			
		||||
use Wx qw(:dialog :id :misc :sizer :systemsettings);
 | 
			
		||||
use Wx::Grid;
 | 
			
		||||
use Wx::Event qw(EVT_GRID_CELL_CHANGED);
 | 
			
		||||
use base 'Slic3r::GUI::Plater::ObjectDialog::BaseTab';
 | 
			
		||||
 | 
			
		||||
sub new {
 | 
			
		||||
    my $class = shift;
 | 
			
		||||
    my ($parent, %params) = @_;
 | 
			
		||||
    my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize);
 | 
			
		||||
    
 | 
			
		||||
    my $sizer = Wx::BoxSizer->new(wxVERTICAL);
 | 
			
		||||
    
 | 
			
		||||
    {
 | 
			
		||||
        my $label = Wx::StaticText->new($self, -1, "You can use this section to override the default layer height for parts of this object.",
 | 
			
		||||
            wxDefaultPosition, [-1, 40]);
 | 
			
		||||
        $label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
 | 
			
		||||
        $sizer->Add($label, 0, wxEXPAND | wxALL, 10);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    my $grid = $self->{grid} = Wx::Grid->new($self, -1, wxDefaultPosition, wxDefaultSize);
 | 
			
		||||
    $sizer->Add($grid, 1, wxEXPAND | wxALL, 10);
 | 
			
		||||
    $grid->CreateGrid(0, 3);
 | 
			
		||||
    $grid->DisableDragRowSize;
 | 
			
		||||
    $grid->HideRowLabels;
 | 
			
		||||
    $grid->SetColLabelValue(0, "Min Z (mm)");
 | 
			
		||||
    $grid->SetColLabelValue(1, "Max Z (mm)");
 | 
			
		||||
    $grid->SetColLabelValue(2, "Layer height (mm)");
 | 
			
		||||
    $grid->SetColSize($_, 135) for 0..2;
 | 
			
		||||
    $grid->SetDefaultCellAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
 | 
			
		||||
    
 | 
			
		||||
    # load data
 | 
			
		||||
    foreach my $range (@{ $self->model_object->layer_height_ranges }) {
 | 
			
		||||
        $grid->AppendRows(1);
 | 
			
		||||
        my $i = $grid->GetNumberRows-1;
 | 
			
		||||
        $grid->SetCellValue($i, $_, $range->[$_]) for 0..2;
 | 
			
		||||
    }
 | 
			
		||||
    $grid->AppendRows(1); # append one empty row
 | 
			
		||||
    
 | 
			
		||||
    EVT_GRID_CELL_CHANGED($grid, sub {
 | 
			
		||||
        my ($grid, $event) = @_;
 | 
			
		||||
        
 | 
			
		||||
        # remove any non-numeric character
 | 
			
		||||
        my $value = $grid->GetCellValue($event->GetRow, $event->GetCol);
 | 
			
		||||
        $value =~ s/,/./g;
 | 
			
		||||
        $value =~ s/[^0-9.]//g;
 | 
			
		||||
        $grid->SetCellValue($event->GetRow, $event->GetCol, ($event->GetCol == 2) ? $self->_clamp_layer_height($value) : $value);
 | 
			
		||||
        
 | 
			
		||||
        # if there's no empty row, let's append one
 | 
			
		||||
        for my $i (0 .. $grid->GetNumberRows) {
 | 
			
		||||
            if ($i == $grid->GetNumberRows) {
 | 
			
		||||
                # if we're here then we found no empty row
 | 
			
		||||
                $grid->AppendRows(1);
 | 
			
		||||
                last;
 | 
			
		||||
            }
 | 
			
		||||
            if (!grep $grid->GetCellValue($i, $_), 0..2) {
 | 
			
		||||
                # exit loop if this row is empty
 | 
			
		||||
                last;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $self->{layers_changed} = 1;
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    $self->SetSizer($sizer);
 | 
			
		||||
    $sizer->SetSizeHints($self);
 | 
			
		||||
    
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _clamp_layer_height
 | 
			
		||||
{
 | 
			
		||||
    my ($self, $value) = @_;
 | 
			
		||||
    # $self->GetParent->GetParent is of type Slic3r::GUI::Plater::ObjectSettingsDialog
 | 
			
		||||
    my $config            = $self->GetParent->GetParent->{config};
 | 
			
		||||
    if ($value =~ /^[0-9,.E]+$/) {
 | 
			
		||||
        # Looks like a number. Validate the layer height.
 | 
			
		||||
        my $nozzle_dmrs       = $config->get('nozzle_diameter');
 | 
			
		||||
        my $min_layer_heights = $config->get('min_layer_height');
 | 
			
		||||
        my $max_layer_heights = $config->get('max_layer_height');
 | 
			
		||||
        my $min_layer_height  = 1000.;
 | 
			
		||||
        my $max_layer_height  = 0.;
 | 
			
		||||
        my $max_nozzle_dmr    = 0.;
 | 
			
		||||
        for (my $i = 0; $i < int(@{$nozzle_dmrs}); $i += 1) {
 | 
			
		||||
            $min_layer_height = $min_layer_heights->[$i] if ($min_layer_heights->[$i] < $min_layer_height);
 | 
			
		||||
            $max_layer_height = $max_layer_heights->[$i] if ($max_layer_heights->[$i] > $max_layer_height);
 | 
			
		||||
            $max_nozzle_dmr   = $nozzle_dmrs      ->[$i] if ($nozzle_dmrs      ->[$i] > $max_nozzle_dmr  );
 | 
			
		||||
        }
 | 
			
		||||
        $min_layer_height = 0.005 if ($min_layer_height < 0.005);
 | 
			
		||||
        $max_layer_height = $max_nozzle_dmr * 0.75 if ($max_layer_height == 0.);
 | 
			
		||||
        $max_layer_height = $max_nozzle_dmr if ($max_layer_height > $max_nozzle_dmr);
 | 
			
		||||
        return ($value < $min_layer_height) ? $min_layer_height :
 | 
			
		||||
               ($value > $max_layer_height) ? $max_layer_height : $value;
 | 
			
		||||
    } else {
 | 
			
		||||
        # If an invalid numeric value has been entered, use the default layer height.
 | 
			
		||||
        return $config->get('layer_height');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub CanClose {
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    
 | 
			
		||||
    # validate ranges before allowing user to dismiss the dialog
 | 
			
		||||
    
 | 
			
		||||
    foreach my $range ($self->_get_ranges) {
 | 
			
		||||
        my ($min, $max, $height) = @$range;
 | 
			
		||||
        if ($max <= $min) {
 | 
			
		||||
            Slic3r::GUI::show_error($self, "Invalid Z range $min-$max.");
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        if ($min < 0 || $max < 0) {
 | 
			
		||||
            Slic3r::GUI::show_error($self, "Invalid Z range $min-$max.");
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        if ($height < 0) {
 | 
			
		||||
            Slic3r::GUI::show_error($self, "Invalid layer height $height.");
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        # TODO: check for overlapping ranges
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub Closing {
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    
 | 
			
		||||
    # save ranges into the plater object
 | 
			
		||||
    $self->model_object->set_layer_height_ranges([ $self->_get_ranges ]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _get_ranges {
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    
 | 
			
		||||
    my @ranges = ();
 | 
			
		||||
    for my $i (0 .. $self->{grid}->GetNumberRows-1) {
 | 
			
		||||
        my ($min, $max, $height) = map $self->{grid}->GetCellValue($i, $_), 0..2;
 | 
			
		||||
        next if $min eq '' || $max eq '' || $height eq '';
 | 
			
		||||
        push @ranges, [ $min, $max, $height ];
 | 
			
		||||
    }
 | 
			
		||||
    return sort { $a->[0] <=> $b->[0] } @ranges;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub LayersChanged {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    return $self->{layers_changed};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,182 +0,0 @@
 | 
			
		|||
# Included in ObjectSettingsDialog -> ObjectPartsPanel.
 | 
			
		||||
# Maintains, displays, adds and removes overrides of slicing parameters for an object and its modifier mesh.
 | 
			
		||||
 | 
			
		||||
package Slic3r::GUI::Plater::OverrideSettingsPanel;
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
use utf8;
 | 
			
		||||
 | 
			
		||||
use List::Util qw(first);
 | 
			
		||||
use Wx qw(:misc :sizer :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG
 | 
			
		||||
    wxTheApp);
 | 
			
		||||
use Wx::Event qw(EVT_BUTTON EVT_LEFT_DOWN EVT_MENU);
 | 
			
		||||
use base 'Wx::ScrolledWindow';
 | 
			
		||||
 | 
			
		||||
use constant ICON_MATERIAL      => 0;
 | 
			
		||||
use constant ICON_SOLIDMESH     => 1;
 | 
			
		||||
use constant ICON_MODIFIERMESH  => 2;
 | 
			
		||||
 | 
			
		||||
my %icons = (
 | 
			
		||||
    'Advanced'              => 'wand.png',
 | 
			
		||||
    'Extruders'             => 'funnel.png',
 | 
			
		||||
    'Extrusion Width'       => 'funnel.png',
 | 
			
		||||
    'Infill'                => 'infill.png',
 | 
			
		||||
    'Layers and Perimeters' => 'layers.png',
 | 
			
		||||
    'Skirt and brim'        => 'box.png',
 | 
			
		||||
    'Speed'                 => 'time.png',
 | 
			
		||||
    'Speed > Acceleration'  => 'time.png',
 | 
			
		||||
    'Support material'      => 'building.png',
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
sub new {
 | 
			
		||||
    my ($class, $parent, %params) = @_;
 | 
			
		||||
    my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
 | 
			
		||||
    # C++ class Slic3r::DynamicPrintConfig, initially empty.
 | 
			
		||||
    $self->{default_config} = Slic3r::Config->new;
 | 
			
		||||
    $self->{config} = Slic3r::Config->new;
 | 
			
		||||
    # On change callback.
 | 
			
		||||
    $self->{on_change} = $params{on_change};
 | 
			
		||||
    $self->{fixed_options} = {};
 | 
			
		||||
    
 | 
			
		||||
    $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
 | 
			
		||||
    
 | 
			
		||||
    $self->{options_sizer} = Wx::BoxSizer->new(wxVERTICAL);
 | 
			
		||||
    $self->{sizer}->Add($self->{options_sizer}, 0, wxEXPAND | wxALL, 0);
 | 
			
		||||
    
 | 
			
		||||
    # option selector
 | 
			
		||||
    {
 | 
			
		||||
        # create the button
 | 
			
		||||
        my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG),
 | 
			
		||||
            wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE);
 | 
			
		||||
        EVT_LEFT_DOWN($btn, sub {
 | 
			
		||||
            my $menu = Wx::Menu->new;
 | 
			
		||||
            # create category submenus
 | 
			
		||||
            my %categories = ();  # category => submenu
 | 
			
		||||
            foreach my $opt_key (@{$self->{options}}) {
 | 
			
		||||
                if (my $cat = $Slic3r::Config::Options->{$opt_key}{category}) {
 | 
			
		||||
                    $categories{$cat} //= Wx::Menu->new;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            # append submenus to main menu
 | 
			
		||||
            my @categories = ('Layers and Perimeters', 'Infill', 'Support material', 'Speed', 'Extruders', 'Extrusion Width', 'Advanced');
 | 
			
		||||
            #foreach my $cat (sort keys %categories) {
 | 
			
		||||
            foreach my $cat (@categories) {
 | 
			
		||||
                wxTheApp->append_submenu($menu, $cat, "", $categories{$cat}, undef, $icons{$cat});
 | 
			
		||||
            }
 | 
			
		||||
            # append options to submenus
 | 
			
		||||
            foreach my $opt_key (@{$self->{options}}) {
 | 
			
		||||
                my $cat = $Slic3r::Config::Options->{$opt_key}{category} or next;
 | 
			
		||||
                my $cb = sub {
 | 
			
		||||
                    $self->{config}->set($opt_key, $self->{default_config}->get($opt_key));
 | 
			
		||||
                    $self->update_optgroup;
 | 
			
		||||
                    $self->{on_change}->($opt_key) if $self->{on_change};
 | 
			
		||||
                };
 | 
			
		||||
                wxTheApp->append_menu_item($categories{$cat}, $self->{option_labels}{$opt_key},
 | 
			
		||||
                    $Slic3r::Config::Options->{$opt_key}{tooltip}, $cb);
 | 
			
		||||
            }
 | 
			
		||||
            $self->PopupMenu($menu, $btn->GetPosition);
 | 
			
		||||
            $menu->Destroy;
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
 | 
			
		||||
        $h_sizer->Add($btn, 0, wxALL, 0);
 | 
			
		||||
        $self->{sizer}->Add($h_sizer, 0, wxEXPAND | wxBOTTOM, 10);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    $self->SetSizer($self->{sizer});
 | 
			
		||||
    $self->SetScrollbars(0, 1, 0, 1);
 | 
			
		||||
    
 | 
			
		||||
    $self->set_opt_keys($params{opt_keys}) if $params{opt_keys};
 | 
			
		||||
    $self->update_optgroup;
 | 
			
		||||
    
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub set_default_config {
 | 
			
		||||
    my ($self, $config) = @_;
 | 
			
		||||
    $self->{default_config} = $config;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub set_config {
 | 
			
		||||
    my ($self, $config) = @_;
 | 
			
		||||
    $self->{config} = $config;
 | 
			
		||||
    $self->update_optgroup;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub set_opt_keys {
 | 
			
		||||
    my ($self, $opt_keys) = @_;
 | 
			
		||||
    # sort options by category+label
 | 
			
		||||
    $self->{option_labels} = { map { $_ => $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label} } @$opt_keys };
 | 
			
		||||
    $self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub set_fixed_options {
 | 
			
		||||
    my ($self, $opt_keys) = @_;
 | 
			
		||||
    $self->{fixed_options} = { map {$_ => 1} @$opt_keys };
 | 
			
		||||
    $self->update_optgroup;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub update_optgroup {
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    
 | 
			
		||||
    $self->{options_sizer}->Clear(1);
 | 
			
		||||
    return if !defined $self->{config};
 | 
			
		||||
    
 | 
			
		||||
    my %categories = ();
 | 
			
		||||
    foreach my $opt_key (@{$self->{config}->get_keys}) {
 | 
			
		||||
        my $category = $Slic3r::Config::Options->{$opt_key}{category};
 | 
			
		||||
        $categories{$category} ||= [];
 | 
			
		||||
        push @{$categories{$category}}, $opt_key;
 | 
			
		||||
    }
 | 
			
		||||
    foreach my $category (sort keys %categories) {
 | 
			
		||||
        my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
 | 
			
		||||
            parent          => $self,
 | 
			
		||||
            title           => $category,
 | 
			
		||||
            config          => $self->{config},
 | 
			
		||||
            full_labels     => 1,
 | 
			
		||||
            label_font      => $Slic3r::GUI::small_font,
 | 
			
		||||
            sidetext_font   => $Slic3r::GUI::small_font,
 | 
			
		||||
            label_width     => 150,
 | 
			
		||||
            on_change       => sub { $self->{on_change}->() if $self->{on_change} },
 | 
			
		||||
            extra_column    => sub {
 | 
			
		||||
                my ($line) = @_;
 | 
			
		||||
                
 | 
			
		||||
                my $opt_key = $line->get_options->[0]->opt_id;  # we assume that we have one option per line
 | 
			
		||||
                
 | 
			
		||||
                # disallow deleting fixed options
 | 
			
		||||
                return undef if $self->{fixed_options}{$opt_key};
 | 
			
		||||
                
 | 
			
		||||
                my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG),
 | 
			
		||||
                    wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE);
 | 
			
		||||
                EVT_BUTTON($self, $btn, sub {
 | 
			
		||||
                    $self->{config}->erase($opt_key);
 | 
			
		||||
                    $self->{on_change}->() if $self->{on_change};
 | 
			
		||||
                    wxTheApp->CallAfter(sub { $self->update_optgroup });
 | 
			
		||||
                });
 | 
			
		||||
                return $btn;
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
        foreach my $opt_key (sort @{$categories{$category}}) {
 | 
			
		||||
            $optgroup->append_single_option_line($opt_key);
 | 
			
		||||
        }
 | 
			
		||||
        $self->{options_sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 0);
 | 
			
		||||
    }
 | 
			
		||||
    $self->GetParent->Layout;  # we need this for showing scrollbars
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# work around a wxMAC bug causing controls not being disabled when calling Disable() on a Window
 | 
			
		||||
sub enable {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    
 | 
			
		||||
    $self->{btn_add}->Enable;
 | 
			
		||||
    $self->Enable;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub disable {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    
 | 
			
		||||
    $self->{btn_add}->Disable;
 | 
			
		||||
    $self->Disable;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue