Basic 2D-control to visualize layer heights

This commit is contained in:
Florens Wasserfall 2017-01-19 11:33:39 +01:00
parent 232369b2af
commit d4597fc76e
4 changed files with 357 additions and 3 deletions

View File

@ -23,8 +23,10 @@ use Slic3r::GUI::Plater::3D;
use Slic3r::GUI::Plater::3DPreview;
use Slic3r::GUI::Plater::ObjectPartsPanel;
use Slic3r::GUI::Plater::ObjectCutDialog;
use Slic3r::GUI::Plater::ObjectLayersDialog;
use Slic3r::GUI::Plater::ObjectSettingsDialog;
use Slic3r::GUI::Plater::OverrideSettingsPanel;
use Slic3r::GUI::Plater::SplineControl;
use Slic3r::GUI::Preferences;
use Slic3r::GUI::ProgressStatusBar;
use Slic3r::GUI::Projector;

View File

@ -27,6 +27,7 @@ use constant TB_45CCW => &Wx::NewId;
use constant TB_SCALE => &Wx::NewId;
use constant TB_SPLIT => &Wx::NewId;
use constant TB_CUT => &Wx::NewId;
use constant TB_LAYERS => &Wx::NewId;
use constant TB_SETTINGS => &Wx::NewId;
# package variables to avoid passing lexicals to threads
@ -148,6 +149,7 @@ sub new {
$self->{htoolbar}->AddTool(TB_SCALE, "Scale…", Wx::Bitmap->new($Slic3r::var->("arrow_out.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new($Slic3r::var->("shape_ungroup.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_LAYERS, "Layers…", Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), '');
} else {
@ -163,10 +165,11 @@ sub new {
changescale => "Scale…",
split => "Split",
cut => "Cut…",
layers => "Layers…",
settings => "Settings…",
);
$self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL);
for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) {
for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut layers settings)) {
$self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
$self->{btoolbar}->Add($self->{"btn_$_"});
}
@ -217,6 +220,7 @@ sub new {
changescale arrow_out.png
split shape_ungroup.png
cut package.png
layers cog.png
settings cog.png
);
for (grep $self->{"btn_$_"}, keys %icons) {
@ -248,6 +252,7 @@ sub new {
EVT_TOOL($self, TB_SCALE, sub { $self->changescale(undef); });
EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; });
EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog });
EVT_TOOL($self, TB_LAYERS, sub { $_[0]->object_layers_dialog });
EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog });
} else {
EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; });
@ -261,6 +266,7 @@ sub new {
EVT_BUTTON($self, $self->{btn_changescale}, sub { $self->changescale(undef); });
EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; });
EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog });
EVT_BUTTON($self, $self->{btn_layers}, sub { $_[0]->object_layers_dialog });
EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog });
}
@ -1560,6 +1566,33 @@ sub object_cut_dialog {
}
}
sub object_layers_dialog {
my $self = shift;
my ($obj_idx) = @_;
if (!defined $obj_idx) {
($obj_idx, undef) = $self->selected_object;
}
if (!$Slic3r::GUI::have_OpenGL) {
Slic3r::GUI::show_error($self, "Please install the OpenGL modules to use this feature (see build instructions).");
return;
}
my $dlg = Slic3r::GUI::Plater::ObjectLayersDialog->new($self,
object => $self->{objects}[$obj_idx],
model_object => $self->{model}->objects->[$obj_idx],
obj_idx => $obj_idx,
);
return unless $dlg->ShowModal == wxID_OK;
if (my @new_objects = $dlg->NewModelObjects) {
$self->remove($obj_idx);
$self->load_model_objects(grep defined($_), @new_objects);
$self->arrange;
}
}
sub object_settings_dialog {
my $self = shift;
my ($obj_idx) = @_;
@ -1626,11 +1659,11 @@ sub selection_changed {
my $method = $have_sel ? 'Enable' : 'Disable';
$self->{"btn_$_"}->$method
for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut settings);
for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut layers settings);
if ($self->{htoolbar}) {
$self->{htoolbar}->EnableTool($_, $have_sel)
for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS);
for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_LAYERS, TB_SETTINGS);
}
if ($self->{object_info_size}) { # have we already loaded the info pane?
@ -1812,6 +1845,9 @@ sub object_menu {
$frame->_append_menu_item($menu, "Cut…", 'Open the 3D cutting tool', sub {
$self->object_cut_dialog;
}, undef, 'package.png');
$frame->_append_menu_item($menu, "Layers…", 'Open the dynamic layer height control', sub {
$self->object_layers_dialog;
}, undef, 'cog.png');
$menu->AppendSeparator();
$frame->_append_menu_item($menu, "Settings…", 'Open the object editor dialog', sub {
$self->object_settings_dialog;

View File

@ -0,0 +1,62 @@
package Slic3r::GUI::Plater::ObjectLayersDialog;
use strict;
use warnings;
use utf8;
use Slic3r::Geometry qw(PI X scale unscale);
use Wx qw(wxTheApp :dialog :id :misc :sizer wxTAB_TRAVERSAL);
use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($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};
my $model_object = $self->{model_object} = $params{model_object};
my $obj_idx = $self->{obj_idx} = $params{obj_idx};
my $plater = $self->{plater} = $parent;
# Initialize 3D toolpaths preview
if ($Slic3r::GUI::have_OpenGL) {
$self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self, $plater->{print});
#$self->{preview3D}->canvas->on_viewport_changed(sub {
# $self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas);
#});
$self->{preview3D}->canvas->set_auto_bed_shape;
$self->{preview3D}->canvas->SetSize([500,500]);
$self->{preview3D}->canvas->SetMinSize($self->{preview3D}->canvas->GetSize);
$self->{preview3D}->load_print;
$self->{preview3D}->canvas->zoom_to_volumes;
}
$self->{splineControl} = Slic3r::GUI::Plater::SplineControl->new($self, Wx::Size->new(100, 200));
my $right_sizer = Wx::BoxSizer->new(wxVERTICAL);
$right_sizer->Add($self->{splineControl}, 1, wxEXPAND | wxALL, 0);
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
$self->{sizer}->Add($self->{preview3D}, 1, wxEXPAND | wxTOP | wxBOTTOM, 0) if $self->{preview3D};
$self->{sizer}->Add($right_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
$self->SetSizerAndFit($self->{sizer});
$self->SetSize([800, 600]);
$self->SetMinSize($self->GetSize);
# init spline control values
my $object = $self->{plater}->{print}->get_object($self->{obj_idx});
#IMPORTANT TODO: set correct min / max layer height from config!!!!
$self->{splineControl}->set_size_parameters(0.1, 0.4, unscale($object->size->z));
# get array of current Z coordinates for selected object
my @layer_heights = map $_->print_z, @{$object->layers};
$self->{splineControl}->set_interpolation_points(@layer_heights);
return $self;
}
1;

View File

@ -0,0 +1,254 @@
package Slic3r::GUI::Plater::SplineControl;
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(: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';
sub new {
my $class = shift;
my ($parent, $size) = @_;
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_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->{line_pen} = Wx::Pen->new(Wx::Colour->new(50,50,50), 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';
$self->{min_layer_height} = 0.12345678;
$self->{max_layer_height} = 0.4;
$self->{object_height} = 1.0;
$self->{interpolation_points} = ();
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 repaint {
my ($self, $event) = @_;
my $dc = Wx::AutoBufferedPaintDC->new($self);
my $size = $self->GetSize;
# Set axis orientation to leftRight, bottomUp and reset origin to the lower left corner
$dc->SetAxisOrientation(1, 1);
my @size = ($size->GetWidth, $size->GetHeight);
$dc->SetDeviceOrigin(0, $size[1]);
print "repaint\n";
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 scale (min and max indicator at the bottom)
$dc->SetTextForeground(Wx::Colour->new(0,0,0));
$dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL));
$dc->DrawLabel(sprintf('%.4g', $self->{min_layer_height}), Wx::Rect->new(0, 15, $size[0], -15), wxALIGN_LEFT | wxALIGN_TOP);
$dc->DrawLabel(sprintf('%.4g', $self->{max_layer_height}), Wx::Rect->new(0, 15, $size[0], -15), wxALIGN_RIGHT | wxALIGN_TOP);
# draw current layers as lines
my $scaling_y = $size[1]/$self->{object_height};
my $scaling_x = $size[0]/($self->{max_layer_height} - $self->{min_layer_height});
my $last_z = 0.0;
foreach my $z (@{$self->{interpolation_points}}) {
my $layer_h = $z - $last_z;
$dc->SetPen($self->{line_pen});
$dc->DrawLine(0, $z*$scaling_y, ($layer_h-$self->{min_layer_height})*$scaling_x, $z*$scaling_y);
$last_z = $z;
}
$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);
# }
#
# last OBJECTS;
# }
# }
# }
# $self->Refresh;
# } elsif ($event->LeftUp) {
# $self->{on_instances_moved}->()
# if $self->{drag_object};
# $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]),
# ));
# $model_object->update_bounding_box;
# $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 = shift;
# # 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 = $canvas_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 ];
# }
}
# Set basic parameters for this control.
# min/max_layer_height are required to define the x-range, object_height is used to scale the y-range.
# Must be called if object selection changes.
sub set_size_parameters {
my ($self, $min_layer_height, $max_layer_height, $object_height) = @_;
$self->{min_layer_height} = $min_layer_height;
$self->{max_layer_height} = $max_layer_height;
$self->{object_height} = $object_height;
#$self->repaint;
}
sub set_interpolation_points {
my ($self, @interpolation_points) = @_;
$self->{interpolation_points} = [@interpolation_points];
print "set_interpolation_points\n";
$self->Refresh;
print "refresh\n";
}
# 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},
);
}
1;