Merge branch 'sender-dlp'
This commit is contained in:
commit
fbc67d9078
@ -26,6 +26,7 @@ use Slic3r::GUI::Plater::ObjectSettingsDialog;
|
|||||||
use Slic3r::GUI::Plater::OverrideSettingsPanel;
|
use Slic3r::GUI::Plater::OverrideSettingsPanel;
|
||||||
use Slic3r::GUI::Preferences;
|
use Slic3r::GUI::Preferences;
|
||||||
use Slic3r::GUI::ProgressStatusBar;
|
use Slic3r::GUI::ProgressStatusBar;
|
||||||
|
use Slic3r::GUI::Projector;
|
||||||
use Slic3r::GUI::OptionsGroup;
|
use Slic3r::GUI::OptionsGroup;
|
||||||
use Slic3r::GUI::OptionsGroup::Field;
|
use Slic3r::GUI::OptionsGroup::Field;
|
||||||
use Slic3r::GUI::SimpleTab;
|
use Slic3r::GUI::SimpleTab;
|
||||||
@ -77,6 +78,8 @@ our $grey = Wx::Colour->new(200,200,200);
|
|||||||
|
|
||||||
our $VERSION_CHECK_EVENT : shared = Wx::NewEventType;
|
our $VERSION_CHECK_EVENT : shared = Wx::NewEventType;
|
||||||
|
|
||||||
|
our $DLP_projection_screen;
|
||||||
|
|
||||||
sub OnInit {
|
sub OnInit {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
|
||||||
|
@ -9,16 +9,19 @@ use Wx qw(:dialog :id :misc :sizer :choicebook :button :bitmap
|
|||||||
use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
|
use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
|
||||||
use base qw(Wx::Dialog Class::Accessor);
|
use base qw(Wx::Dialog Class::Accessor);
|
||||||
|
|
||||||
__PACKAGE__->mk_accessors(qw(sender));
|
__PACKAGE__->mk_accessors(qw(sender config2 x_homed y_homed));
|
||||||
|
|
||||||
use constant TRAVEL_SPEED => 130*60; # TODO: make customizable?
|
|
||||||
|
|
||||||
sub new {
|
sub new {
|
||||||
my ($class, $printer_panel) = @_;
|
my ($class, $parent, $config, $sender) = @_;
|
||||||
|
|
||||||
my $self = $class->SUPER::new($printer_panel, -1, "Manual Control", wxDefaultPosition,
|
my $self = $class->SUPER::new($parent, -1, "Manual Control", wxDefaultPosition,
|
||||||
[430,380], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
[500,380], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||||
$self->sender($printer_panel->sender);
|
$self->sender($sender);
|
||||||
|
|
||||||
|
$self->config2({
|
||||||
|
xy_travel_speed => 130,
|
||||||
|
z_travel_speed => 10,
|
||||||
|
});
|
||||||
|
|
||||||
my $bed_sizer = Wx::FlexGridSizer->new(2, 3, 1, 1);
|
my $bed_sizer = Wx::FlexGridSizer->new(2, 3, 1, 1);
|
||||||
$bed_sizer->AddGrowableCol(1, 1);
|
$bed_sizer->AddGrowableCol(1, 1);
|
||||||
@ -53,12 +56,17 @@ sub new {
|
|||||||
|
|
||||||
# Bed canvas
|
# Bed canvas
|
||||||
{
|
{
|
||||||
my $bed_shape = $printer_panel->config->bed_shape;
|
my $bed_shape = $config->bed_shape;
|
||||||
$self->{canvas} = my $canvas = Slic3r::GUI::2DBed->new($self, $bed_shape);
|
$self->{canvas} = my $canvas = Slic3r::GUI::2DBed->new($self, $bed_shape);
|
||||||
$canvas->interactive(1);
|
$canvas->interactive(1);
|
||||||
$canvas->on_move(sub {
|
$canvas->on_move(sub {
|
||||||
my ($pos) = @_;
|
my ($pos) = @_;
|
||||||
|
|
||||||
|
if (!($self->x_homed && $self->y_homed)) {
|
||||||
|
Slic3r::GUI::show_error($self, "Please home both X and Y before moving.");
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
|
||||||
# delete any pending commands to get a smoother movement
|
# delete any pending commands to get a smoother movement
|
||||||
$self->sender->purge_queue(1);
|
$self->sender->purge_queue(1);
|
||||||
$self->abs_xy_move($pos);
|
$self->abs_xy_move($pos);
|
||||||
@ -94,10 +102,40 @@ sub new {
|
|||||||
$bed_sizer->Add($sizer, 1, wxEXPAND, 0);
|
$bed_sizer->Add($sizer, 1, wxEXPAND, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
my $optgroup = Slic3r::GUI::OptionsGroup->new(
|
||||||
|
parent => $self,
|
||||||
|
title => 'Settings',
|
||||||
|
on_change => sub {
|
||||||
|
my ($opt_id, $value) = @_;
|
||||||
|
$self->config2->{$opt_id} = $value;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
{
|
||||||
|
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||||
|
label => 'Speed (mm/s)',
|
||||||
|
);
|
||||||
|
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
|
opt_id => 'xy_travel_speed',
|
||||||
|
type => 'f',
|
||||||
|
label => 'X/Y',
|
||||||
|
tooltip => '',
|
||||||
|
default => $self->config2->{xy_travel_speed},
|
||||||
|
));
|
||||||
|
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
|
opt_id => 'z_travel_speed',
|
||||||
|
type => 'f',
|
||||||
|
label => 'Z',
|
||||||
|
tooltip => '',
|
||||||
|
default => $self->config2->{z_travel_speed},
|
||||||
|
));
|
||||||
|
$optgroup->append_line($line);
|
||||||
|
}
|
||||||
|
|
||||||
my $main_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
my $main_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||||
$main_sizer->Add($bed_sizer, 1, wxEXPAND | wxALL, 10);
|
$main_sizer->Add($bed_sizer, 1, wxEXPAND | wxALL, 10);
|
||||||
$main_sizer->Add($self->CreateButtonSizer(wxCLOSE), 0, wxEXPAND);
|
$main_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10);
|
||||||
EVT_BUTTON($self, wxID_CLOSE, sub { $self->Close });
|
#$main_sizer->Add($self->CreateButtonSizer(wxCLOSE), 0, wxEXPAND);
|
||||||
|
#EVT_BUTTON($self, wxID_CLOSE, sub { $self->Close });
|
||||||
|
|
||||||
$self->SetSizer($main_sizer);
|
$self->SetSizer($main_sizer);
|
||||||
$self->SetMinSize($self->GetSize);
|
$self->SetMinSize($self->GetSize);
|
||||||
@ -117,15 +155,16 @@ sub abs_xy_move {
|
|||||||
my ($self, $pos) = @_;
|
my ($self, $pos) = @_;
|
||||||
|
|
||||||
$self->sender->send("G90", 1); # set absolute positioning
|
$self->sender->send("G90", 1); # set absolute positioning
|
||||||
$self->sender->send(sprintf("G1 X%.1f Y%.1f F%d", @$pos, TRAVEL_SPEED), 1);
|
$self->sender->send(sprintf("G1 X%.1f Y%.1f F%d", @$pos, $self->config2->{xy_travel_speed}*60), 1);
|
||||||
$self->{canvas}->set_pos($pos);
|
$self->{canvas}->set_pos($pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub rel_move {
|
sub rel_move {
|
||||||
my ($self, $axis, $distance) = @_;
|
my ($self, $axis, $distance) = @_;
|
||||||
|
|
||||||
|
my $speed = ($axis eq 'Z') ? $self->config2->{z_travel_speed} : $self->config2->{xy_travel_speed};
|
||||||
$self->sender->send("G91", 1); # set relative positioning
|
$self->sender->send("G91", 1); # set relative positioning
|
||||||
$self->sender->send(sprintf("G1 %s%.1f F%d", $axis, $distance, TRAVEL_SPEED), 1);
|
$self->sender->send(sprintf("G1 %s%.1f F%d", $axis, $distance, $speed*60), 1);
|
||||||
$self->sender->send("G90", 1); # set absolute positioning
|
$self->sender->send("G90", 1); # set absolute positioning
|
||||||
|
|
||||||
if (my $pos = $self->{canvas}->pos) {
|
if (my $pos = $self->{canvas}->pos) {
|
||||||
@ -143,6 +182,8 @@ sub home {
|
|||||||
|
|
||||||
$self->sender->send(sprintf("G28 %s", $axis), 1);
|
$self->sender->send(sprintf("G28 %s", $axis), 1);
|
||||||
$self->{canvas}->set_pos(undef);
|
$self->{canvas}->set_pos(undef);
|
||||||
|
$self->x_homed if $axis eq 'X';
|
||||||
|
$self->y_homed if $axis eq 'Y';
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -170,7 +170,8 @@ sub new {
|
|||||||
$btn->Hide;
|
$btn->Hide;
|
||||||
$left_sizer->Add($btn, 0, wxTOP, 15);
|
$left_sizer->Add($btn, 0, wxTOP, 15);
|
||||||
EVT_BUTTON($self, $btn, sub {
|
EVT_BUTTON($self, $btn, sub {
|
||||||
my $dlg = Slic3r::GUI::Controller::ManualControlDialog->new($self);
|
my $dlg = Slic3r::GUI::Controller::ManualControlDialog->new
|
||||||
|
($self, $self->config, $self->sender);
|
||||||
$dlg->ShowModal;
|
$dlg->ShowModal;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -226,6 +226,13 @@ sub _init_menubar {
|
|||||||
$self->_append_menu_item($self->{plater_menu}, "Export plate as AMF...", 'Export current plate as AMF', sub {
|
$self->_append_menu_item($self->{plater_menu}, "Export plate as AMF...", 'Export current plate as AMF', sub {
|
||||||
$plater->export_amf;
|
$plater->export_amf;
|
||||||
}, undef, 'brick_go.png');
|
}, undef, 'brick_go.png');
|
||||||
|
$self->_append_menu_item($self->{plater_menu}, "Open DLP Projector…\tCtrl+L", 'Open projector window for DLP printing', sub {
|
||||||
|
my $projector = Slic3r::GUI::Projector->new($self);
|
||||||
|
|
||||||
|
# this double invocation is needed for properly hiding the MainFrame
|
||||||
|
$projector->Show;
|
||||||
|
$projector->ShowModal;
|
||||||
|
}, undef, 'film.png');
|
||||||
|
|
||||||
$self->{object_menu} = $self->{plater}->object_menu;
|
$self->{object_menu} = $self->{plater}->object_menu;
|
||||||
$self->on_plater_selection_changed(0);
|
$self->on_plater_selection_changed(0);
|
||||||
|
@ -247,8 +247,8 @@ sub set_value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub _on_change {
|
sub _on_change {
|
||||||
my ($self, $opt_id) = @_;
|
my ($self, $opt_id, $value) = @_;
|
||||||
$self->on_change->($opt_id);
|
$self->on_change->($opt_id, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub _on_kill_focus {
|
sub _on_kill_focus {
|
||||||
@ -408,7 +408,7 @@ sub _get_config_value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub _on_change {
|
sub _on_change {
|
||||||
my ($self, $opt_id) = @_;
|
my ($self, $opt_id, $value) = @_;
|
||||||
|
|
||||||
if (exists $self->_opt_map->{$opt_id}) {
|
if (exists $self->_opt_map->{$opt_id}) {
|
||||||
my ($opt_key, $opt_index) = @{ $self->_opt_map->{$opt_id} };
|
my ($opt_key, $opt_index) = @{ $self->_opt_map->{$opt_id} };
|
||||||
@ -430,7 +430,7 @@ sub _on_change {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$self->SUPER::_on_change($opt_id);
|
$self->SUPER::_on_change($opt_id, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub _on_kill_focus {
|
sub _on_kill_focus {
|
||||||
|
@ -35,7 +35,7 @@ sub toggle {
|
|||||||
sub _on_change {
|
sub _on_change {
|
||||||
my ($self, $opt_id) = @_;
|
my ($self, $opt_id) = @_;
|
||||||
|
|
||||||
$self->on_change->($opt_id)
|
$self->on_change->($opt_id, $self->get_value)
|
||||||
unless $self->disable_change_event;
|
unless $self->disable_change_event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,7 +219,7 @@ sub BUILD {
|
|||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
|
||||||
my $style = 0;
|
my $style = 0;
|
||||||
$style |= wxCB_READONLY if $self->option->gui_type ne 'select_open';
|
$style |= wxCB_READONLY if defined $self->option->gui_type && $self->option->gui_type ne 'select_open';
|
||||||
my $field = Wx::ComboBox->new($self->parent, -1, "", wxDefaultPosition, $self->_default_size,
|
my $field = Wx::ComboBox->new($self->parent, -1, "", wxDefaultPosition, $self->_default_size,
|
||||||
$self->option->labels || $self->option->values || [], $style);
|
$self->option->labels || $self->option->values || [], $style);
|
||||||
$self->wxWindow($field);
|
$self->wxWindow($field);
|
||||||
|
863
lib/Slic3r/GUI/Projector.pm
Normal file
863
lib/Slic3r/GUI/Projector.pm
Normal file
@ -0,0 +1,863 @@
|
|||||||
|
package Slic3r::GUI::Projector;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use Wx qw(:dialog :id :misc :sizer :systemsettings :bitmap :button :icon wxTheApp);
|
||||||
|
use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER EVT_SPINCTRL EVT_SLIDER);
|
||||||
|
use base qw(Wx::Dialog Class::Accessor);
|
||||||
|
use utf8;
|
||||||
|
|
||||||
|
__PACKAGE__->mk_accessors(qw(config config2 screen controller));
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
my ($class, $parent) = @_;
|
||||||
|
my $self = $class->SUPER::new($parent, -1, "Projector for DLP", wxDefaultPosition, wxDefaultSize);
|
||||||
|
$self->config2({
|
||||||
|
display => 0,
|
||||||
|
show_bed => 1,
|
||||||
|
invert_y => 0,
|
||||||
|
zoom => 100,
|
||||||
|
exposure_time => 2,
|
||||||
|
bottom_exposure_time => 7,
|
||||||
|
settle_time => 1.5,
|
||||||
|
bottom_layers => 3,
|
||||||
|
z_lift => 5,
|
||||||
|
z_lift_speed => 8,
|
||||||
|
offset => [0,0],
|
||||||
|
});
|
||||||
|
|
||||||
|
my $ini = eval { Slic3r::Config->read_ini("$Slic3r::GUI::datadir/DLP.ini") };
|
||||||
|
if ($ini) {
|
||||||
|
foreach my $opt_id (keys %{$ini->{_}}) {
|
||||||
|
my $value = $ini->{_}{$opt_id};
|
||||||
|
if ($opt_id eq 'offset') {
|
||||||
|
$value = [ split /,/, $value ];
|
||||||
|
}
|
||||||
|
$self->config2->{$opt_id} = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||||
|
|
||||||
|
$self->config(Slic3r::Config->new_from_defaults(
|
||||||
|
qw(serial_port serial_speed bed_shape start_gcode end_gcode)
|
||||||
|
));
|
||||||
|
$self->config->apply(wxTheApp->{mainframe}->config);
|
||||||
|
|
||||||
|
{
|
||||||
|
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
|
||||||
|
parent => $self,
|
||||||
|
title => 'USB/Serial connection',
|
||||||
|
config => $self->config,
|
||||||
|
label_width => 200,
|
||||||
|
);
|
||||||
|
$sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||||
|
|
||||||
|
{
|
||||||
|
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||||
|
label => 'Serial port',
|
||||||
|
);
|
||||||
|
my $serial_port = $optgroup->get_option('serial_port');
|
||||||
|
$serial_port->side_widget(sub {
|
||||||
|
my ($parent) = @_;
|
||||||
|
|
||||||
|
my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", wxBITMAP_TYPE_PNG),
|
||||||
|
wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE);
|
||||||
|
$btn->SetToolTipString("Rescan serial ports")
|
||||||
|
if $btn->can('SetToolTipString');
|
||||||
|
EVT_BUTTON($self, $btn, sub {
|
||||||
|
$optgroup->get_field('serial_port')->set_values([ wxTheApp->scan_serial_ports ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $btn;
|
||||||
|
});
|
||||||
|
my $serial_test = sub {
|
||||||
|
my ($parent) = @_;
|
||||||
|
|
||||||
|
my $btn = $self->{serial_test_btn} = Wx::Button->new($parent, -1,
|
||||||
|
"Test", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
|
||||||
|
$btn->SetFont($Slic3r::GUI::small_font);
|
||||||
|
if ($Slic3r::GUI::have_button_icons) {
|
||||||
|
$btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/wrench.png", wxBITMAP_TYPE_PNG));
|
||||||
|
}
|
||||||
|
|
||||||
|
EVT_BUTTON($self, $btn, sub {
|
||||||
|
my $sender = Slic3r::GCode::Sender->new;
|
||||||
|
my $res = $sender->connect(
|
||||||
|
$self->{config}->serial_port,
|
||||||
|
$self->{config}->serial_speed,
|
||||||
|
);
|
||||||
|
if ($res && $sender->wait_connected) {
|
||||||
|
Slic3r::GUI::show_info($self, "Connection to printer works correctly.", "Success!");
|
||||||
|
} else {
|
||||||
|
Slic3r::GUI::show_error($self, "Connection failed.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return $btn;
|
||||||
|
};
|
||||||
|
$line->append_option($serial_port);
|
||||||
|
$line->append_option($optgroup->get_option('serial_speed'));
|
||||||
|
$line->append_widget($serial_test);
|
||||||
|
$optgroup->append_line($line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
|
||||||
|
parent => $self,
|
||||||
|
title => 'G-code',
|
||||||
|
config => $self->config,
|
||||||
|
label_width => 200,
|
||||||
|
);
|
||||||
|
$sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||||
|
|
||||||
|
{
|
||||||
|
my $option = $optgroup->get_option('start_gcode');
|
||||||
|
$option->height(50);
|
||||||
|
$option->full_width(1);
|
||||||
|
$optgroup->append_single_option_line($option);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
my $option = $optgroup->get_option('end_gcode');
|
||||||
|
$option->height(50);
|
||||||
|
$option->full_width(1);
|
||||||
|
$optgroup->append_single_option_line($option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $on_change = sub {
|
||||||
|
my ($opt_id, $value) = @_;
|
||||||
|
|
||||||
|
$self->config2->{$opt_id} = $value;
|
||||||
|
$self->position_screen;
|
||||||
|
|
||||||
|
my $serialized = {};
|
||||||
|
foreach my $opt_id (keys %{$self->config2}) {
|
||||||
|
my $value = $self->config2->{$opt_id};
|
||||||
|
if (ref($value) eq 'ARRAY') {
|
||||||
|
$value = join ',', @$value;
|
||||||
|
}
|
||||||
|
$serialized->{$opt_id} = $value;
|
||||||
|
}
|
||||||
|
Slic3r::Config->write_ini(
|
||||||
|
"$Slic3r::GUI::datadir/DLP.ini",
|
||||||
|
{ _ => $serialized });
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
my $optgroup = Slic3r::GUI::OptionsGroup->new(
|
||||||
|
parent => $self,
|
||||||
|
title => 'Projection',
|
||||||
|
on_change => $on_change,
|
||||||
|
label_width => 200,
|
||||||
|
);
|
||||||
|
$sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||||
|
|
||||||
|
{
|
||||||
|
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||||
|
label => 'Display',
|
||||||
|
);
|
||||||
|
|
||||||
|
my @displays = 0 .. (Wx::Display::GetCount()-1);
|
||||||
|
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
|
opt_id => 'display',
|
||||||
|
type => 'select',
|
||||||
|
label => 'Display',
|
||||||
|
tooltip => '',
|
||||||
|
labels => [@displays],
|
||||||
|
values => [@displays],
|
||||||
|
default => $self->config2->{display},
|
||||||
|
));
|
||||||
|
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
|
opt_id => 'zoom',
|
||||||
|
type => 'percent',
|
||||||
|
label => 'Zoom %',
|
||||||
|
tooltip => '',
|
||||||
|
default => $self->config2->{zoom},
|
||||||
|
min => 0.1,
|
||||||
|
max => 100,
|
||||||
|
));
|
||||||
|
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
|
opt_id => 'offset',
|
||||||
|
type => 'point',
|
||||||
|
label => 'Offset',
|
||||||
|
tooltip => '',
|
||||||
|
default => $self->config2->{offset},
|
||||||
|
));
|
||||||
|
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
|
opt_id => 'invert_y',
|
||||||
|
type => 'bool',
|
||||||
|
label => 'Invert Y',
|
||||||
|
tooltip => '',
|
||||||
|
default => $self->config2->{invert_y},
|
||||||
|
));
|
||||||
|
$optgroup->append_line($line);
|
||||||
|
}
|
||||||
|
|
||||||
|
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
|
opt_id => 'show_bed',
|
||||||
|
type => 'bool',
|
||||||
|
label => 'Show bed',
|
||||||
|
tooltip => '',
|
||||||
|
default => $self->config2->{show_bed},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
my $optgroup = Slic3r::GUI::OptionsGroup->new(
|
||||||
|
parent => $self,
|
||||||
|
title => 'Print',
|
||||||
|
on_change => $on_change,
|
||||||
|
label_width => 200,
|
||||||
|
);
|
||||||
|
$sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||||
|
|
||||||
|
{
|
||||||
|
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||||
|
label => 'Time (seconds)',
|
||||||
|
);
|
||||||
|
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
|
opt_id => 'bottom_exposure_time',
|
||||||
|
type => 'f',
|
||||||
|
label => 'Bottom exposure',
|
||||||
|
tooltip => '',
|
||||||
|
default => $self->config2->{bottom_exposure_time},
|
||||||
|
));
|
||||||
|
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
|
opt_id => 'exposure_time',
|
||||||
|
type => 'f',
|
||||||
|
label => 'Exposure',
|
||||||
|
tooltip => '',
|
||||||
|
default => $self->config2->{exposure_time},
|
||||||
|
));
|
||||||
|
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
|
opt_id => 'settle_time',
|
||||||
|
type => 'f',
|
||||||
|
label => 'Settle',
|
||||||
|
tooltip => '',
|
||||||
|
default => $self->config2->{settle_time},
|
||||||
|
));
|
||||||
|
$optgroup->append_line($line);
|
||||||
|
}
|
||||||
|
|
||||||
|
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
|
opt_id => 'bottom_layers',
|
||||||
|
type => 'i',
|
||||||
|
label => 'Bottom layers',
|
||||||
|
tooltip => '',
|
||||||
|
default => $self->config2->{bottom_layers},
|
||||||
|
));
|
||||||
|
|
||||||
|
{
|
||||||
|
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||||
|
label => 'Z Lift',
|
||||||
|
);
|
||||||
|
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
|
opt_id => 'z_lift',
|
||||||
|
type => 'f',
|
||||||
|
label => 'Distance',
|
||||||
|
sidetext => 'mm',
|
||||||
|
tooltip => '',
|
||||||
|
default => $self->config2->{z_lift},
|
||||||
|
));
|
||||||
|
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
|
opt_id => 'z_lift_speed',
|
||||||
|
type => 'f',
|
||||||
|
label => 'Speed',
|
||||||
|
sidetext => 'mm/s',
|
||||||
|
tooltip => '',
|
||||||
|
default => $self->config2->{z_lift_speed},
|
||||||
|
));
|
||||||
|
$optgroup->append_line($line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
my $sizer1 = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||||
|
$sizer->Add($sizer1, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||||
|
|
||||||
|
{
|
||||||
|
my $btn = $self->{btn_manual_control} = Wx::Button->new($self, -1, 'Manual Control', wxDefaultPosition, wxDefaultSize);
|
||||||
|
if ($Slic3r::GUI::have_button_icons) {
|
||||||
|
$btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/cog.png", wxBITMAP_TYPE_PNG));
|
||||||
|
}
|
||||||
|
$sizer1->Add($btn, 0);
|
||||||
|
EVT_BUTTON($self, $btn, sub {
|
||||||
|
my $sender = Slic3r::GCode::Sender->new;
|
||||||
|
my $res = $sender->connect(
|
||||||
|
$self->config->serial_port,
|
||||||
|
$self->config->serial_speed,
|
||||||
|
);
|
||||||
|
if (!$res || !$sender->wait_connected) {
|
||||||
|
Slic3r::GUI::show_error(undef, "Connection failed. Check serial port and speed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
my $dlg = Slic3r::GUI::Controller::ManualControlDialog->new
|
||||||
|
($self, $self->config, $sender);
|
||||||
|
$dlg->ShowModal;
|
||||||
|
$sender->disconnect;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
{
|
||||||
|
my $btn = $self->{btn_print} = Wx::Button->new($self, -1, 'Print', wxDefaultPosition, wxDefaultSize);
|
||||||
|
if ($Slic3r::GUI::have_button_icons) {
|
||||||
|
$btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/control_play.png", wxBITMAP_TYPE_PNG));
|
||||||
|
}
|
||||||
|
$sizer1->Add($btn, 0);
|
||||||
|
EVT_BUTTON($self, $btn, sub {
|
||||||
|
$self->controller->start_print;
|
||||||
|
$self->_update_buttons;
|
||||||
|
$self->_set_status('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
my $btn = $self->{btn_stop} = Wx::Button->new($self, -1, 'Stop/Black', wxDefaultPosition, wxDefaultSize);
|
||||||
|
if ($Slic3r::GUI::have_button_icons) {
|
||||||
|
$btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/control_stop.png", wxBITMAP_TYPE_PNG));
|
||||||
|
}
|
||||||
|
$sizer1->Add($btn, 0);
|
||||||
|
EVT_BUTTON($self, $btn, sub {
|
||||||
|
$self->controller->stop_print;
|
||||||
|
$self->_update_buttons;
|
||||||
|
$self->_set_status('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
{
|
||||||
|
my $text = Wx::StaticText->new($self, -1, "Layer:", wxDefaultPosition, wxDefaultSize);
|
||||||
|
$text->SetFont($Slic3r::GUI::small_font);
|
||||||
|
$sizer1->Add($text, 0, wxEXPAND | wxLEFT, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
my $spin = $self->{layers_spinctrl} = Wx::SpinCtrl->new($self, -1, 0, wxDefaultPosition, [60,-1],
|
||||||
|
0, 0, 300, 0);
|
||||||
|
$sizer1->Add($spin, 0);
|
||||||
|
EVT_SPINCTRL($self, $spin, sub {
|
||||||
|
my $value = $spin->GetValue;
|
||||||
|
$self->{layers_slider}->SetValue($value);
|
||||||
|
$self->controller->project_layer($value);
|
||||||
|
$self->_update_buttons;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
my $slider = $self->{layers_slider} = Wx::Slider->new(
|
||||||
|
$self, -1,
|
||||||
|
0, # default
|
||||||
|
0, # min
|
||||||
|
300, # max
|
||||||
|
wxDefaultPosition,
|
||||||
|
wxDefaultSize,
|
||||||
|
);
|
||||||
|
$sizer1->Add($slider, 1);
|
||||||
|
EVT_SLIDER($self, $slider, sub {
|
||||||
|
my $value = $slider->GetValue;
|
||||||
|
$self->{layers_spinctrl}->SetValue($value);
|
||||||
|
$self->controller->project_layer($value);
|
||||||
|
$self->_update_buttons;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $sizer2 = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||||
|
$sizer->Add($sizer2, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||||
|
|
||||||
|
{
|
||||||
|
$self->{status_text} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, wxDefaultSize);
|
||||||
|
$self->{status_text}->SetFont($Slic3r::GUI::small_font);
|
||||||
|
$sizer2->Add($self->{status_text}, 1 | wxEXPAND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
my $buttons = $self->CreateStdDialogButtonSizer(wxCLOSE);
|
||||||
|
EVT_BUTTON($self, wxID_CLOSE, sub {
|
||||||
|
$self->_close;
|
||||||
|
});
|
||||||
|
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->SetSizer($sizer);
|
||||||
|
$sizer->SetSizeHints($self);
|
||||||
|
|
||||||
|
# reuse existing screen if any
|
||||||
|
if ($Slic3r::GUI::DLP_projection_screen) {
|
||||||
|
$self->screen($Slic3r::GUI::DLP_projection_screen);
|
||||||
|
$self->screen->config($self->config);
|
||||||
|
$self->screen->config2($self->config2);
|
||||||
|
} else {
|
||||||
|
$self->screen(Slic3r::GUI::Projector::Screen->new($parent, $self->config, $self->config2));
|
||||||
|
$Slic3r::GUI::DLP_projection_screen = $self->screen;
|
||||||
|
}
|
||||||
|
$self->position_screen;
|
||||||
|
$self->screen->Show;
|
||||||
|
wxTheApp->{mainframe}->Hide;
|
||||||
|
|
||||||
|
# initialize controller
|
||||||
|
$self->controller(Slic3r::GUI::Projector::Controller->new(
|
||||||
|
config => $self->config,
|
||||||
|
config2 => $self->config2,
|
||||||
|
screen => $self->screen,
|
||||||
|
on_project_layer => sub {
|
||||||
|
my ($layer_num) = @_;
|
||||||
|
|
||||||
|
$self->{layers_spinctrl}->SetValue($layer_num);
|
||||||
|
$self->{layers_slider}->SetValue($layer_num);
|
||||||
|
$self->_set_status(sprintf "Printing layer %d/%d (z = %.2f)",
|
||||||
|
$layer_num, $self->controller->layer_count,
|
||||||
|
$self->controller->current_layer_height);
|
||||||
|
},
|
||||||
|
on_print_completed => sub {
|
||||||
|
$self->_update_buttons;
|
||||||
|
$self->_set_status('');
|
||||||
|
},
|
||||||
|
));
|
||||||
|
{
|
||||||
|
my $max = $self->controller->layer_count-1;
|
||||||
|
$self->{layers_spinctrl}->SetRange(0, $max);
|
||||||
|
$self->{layers_slider}->SetRange(0, $max);
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->_update_buttons;
|
||||||
|
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _update_buttons {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
my $is_printing = $self->controller->is_printing;
|
||||||
|
my $is_projecting = $self->controller->is_projecting;
|
||||||
|
$self->{btn_manual_control}->Show(!$is_printing);
|
||||||
|
$self->{btn_print}->Show(!$is_printing && !$is_projecting);
|
||||||
|
$self->{btn_stop}->Show($is_printing || $is_projecting);
|
||||||
|
$self->{layers_spinctrl}->Enable(!$is_printing);
|
||||||
|
$self->Layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _set_status {
|
||||||
|
my ($self, $status) = @_;
|
||||||
|
$self->{status_text}->SetLabel($status // '');
|
||||||
|
$self->{status_text}->Wrap($self->{status_text}->GetSize->GetWidth);
|
||||||
|
$self->{status_text}->Refresh;
|
||||||
|
$self->Layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub position_screen {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
my $display = Wx::Display->new($self->config2->{display});
|
||||||
|
my $area = $display->GetGeometry;
|
||||||
|
$self->screen->Move($area->GetPosition);
|
||||||
|
# ShowFullScreen doesn't use the right screen
|
||||||
|
#$self->screen->ShowFullScreen($self->config2->{fullscreen});
|
||||||
|
$self->screen->SetSize($area->GetSize);
|
||||||
|
$self->screen->_resize;
|
||||||
|
$self->screen->Refresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _close {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
# if projection screen is not on the same display as our dialog,
|
||||||
|
# ask the user whether they want to keep it open
|
||||||
|
my $keep_screen = 0;
|
||||||
|
my $display_area = Wx::Display->new($self->config2->{display})->GetGeometry;
|
||||||
|
if (!$display_area->Contains($self->GetScreenPosition)) {
|
||||||
|
my $res = Wx::MessageDialog->new($self, "Do you want to keep the black screen open?", 'Black screen', wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION)->ShowModal;
|
||||||
|
$keep_screen = ($res == wxID_YES);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($keep_screen) {
|
||||||
|
$self->screen->config(undef);
|
||||||
|
$self->screen->config2(undef);
|
||||||
|
$self->screen->Refresh;
|
||||||
|
} else {
|
||||||
|
$self->screen->Destroy;
|
||||||
|
$self->screen(undef);
|
||||||
|
$Slic3r::GUI::DLP_projection_screen = undef;
|
||||||
|
}
|
||||||
|
wxTheApp->{mainframe}->Show;
|
||||||
|
|
||||||
|
$self->EndModal(wxID_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
package Slic3r::GUI::Projector::Controller;
|
||||||
|
use Moo;
|
||||||
|
use Wx qw(wxTheApp :id :timer);
|
||||||
|
use Wx::Event qw(EVT_TIMER);
|
||||||
|
use Slic3r::Print::State ':steps';
|
||||||
|
|
||||||
|
has 'config' => (is => 'ro', required => 1);
|
||||||
|
has 'config2' => (is => 'ro', required => 1);
|
||||||
|
has 'screen' => (is => 'ro', required => 1);
|
||||||
|
has 'on_project_layer' => (is => 'rw');
|
||||||
|
has 'on_print_completed' => (is => 'rw');
|
||||||
|
has 'sender' => (is => 'rw');
|
||||||
|
has 'timer' => (is => 'rw');
|
||||||
|
has 'is_printing' => (is => 'rw', default => sub { 0 });
|
||||||
|
has '_print' => (is => 'rw');
|
||||||
|
has '_layers' => (is => 'rw');
|
||||||
|
has '_heights' => (is => 'rw');
|
||||||
|
has '_layer_num' => (is => 'rw');
|
||||||
|
has '_timer_cb' => (is => 'rw');
|
||||||
|
|
||||||
|
sub BUILD {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
$self->set_print(wxTheApp->{mainframe}->{plater}->{print});
|
||||||
|
|
||||||
|
# projection timer
|
||||||
|
my $timer_id = &Wx::NewId();
|
||||||
|
$self->timer(Wx::Timer->new($self->screen, $timer_id));
|
||||||
|
EVT_TIMER($self->screen, $timer_id, sub {
|
||||||
|
my $cb = $self->_timer_cb;
|
||||||
|
$self->_timer_cb(undef);
|
||||||
|
$cb->();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sub delay {
|
||||||
|
my ($self, $wait, $cb) = @_;
|
||||||
|
|
||||||
|
$self->_timer_cb($cb);
|
||||||
|
$self->timer->Start($wait * 1000, wxTIMER_ONE_SHOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_print {
|
||||||
|
my ($self, $print) = @_;
|
||||||
|
|
||||||
|
# make sure layers were sliced
|
||||||
|
{
|
||||||
|
my $progress_dialog;
|
||||||
|
foreach my $object (@{$print->objects}) {
|
||||||
|
next if $object->step_done(STEP_SLICE);
|
||||||
|
$progress_dialog //= Wx::ProgressDialog->new('Slicing…', "Processing layers…", 100, undef, 0);
|
||||||
|
$progress_dialog->Pulse;
|
||||||
|
$object->slice;
|
||||||
|
}
|
||||||
|
$progress_dialog->Destroy if $progress_dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->_print($print);
|
||||||
|
|
||||||
|
# sort layers by Z
|
||||||
|
my %layers = ();
|
||||||
|
foreach my $layer (map { @{$_->layers}, @{$_->support_layers} } @{$print->objects}) {
|
||||||
|
my $height = $layer->print_z;
|
||||||
|
$layers{$height} //= [];
|
||||||
|
push @{$layers{$height}}, $layer;
|
||||||
|
}
|
||||||
|
$self->_layers({ %layers });
|
||||||
|
$self->_heights([ sort { $a <=> $b } keys %layers ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub layer_count {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return scalar @{$self->_heights};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub current_layer_height {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return $self->_heights->[$self->_layer_num];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub start_print {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
{
|
||||||
|
$self->sender(Slic3r::GCode::Sender->new);
|
||||||
|
my $res = $self->sender->connect(
|
||||||
|
$self->config->serial_port,
|
||||||
|
$self->config->serial_speed,
|
||||||
|
);
|
||||||
|
if (!$res || !$self->sender->wait_connected) {
|
||||||
|
Slic3r::GUI::show_error(undef, "Connection failed. Check serial port and speed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Slic3r::debugf "connected to " . $self->config->serial_port . "\n";
|
||||||
|
|
||||||
|
# send custom start G-code
|
||||||
|
$self->sender->send($_, 1) for grep !/^;/, split /\n/, $self->config->start_gcode;
|
||||||
|
$self->sender->send(sprintf("G1 Z%.5f F%d",
|
||||||
|
$self->config2->{z_lift}, $self->config2->{z_lift_speed}*60), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->is_printing(1);
|
||||||
|
|
||||||
|
# TODO: block until the G1 command has been performed
|
||||||
|
# we could do this with M400 + M115 but maybe it's not portable
|
||||||
|
$self->delay(5, sub {
|
||||||
|
# start with black
|
||||||
|
Slic3r::debugf "starting black projection\n";
|
||||||
|
$self->_layer_num(-1);
|
||||||
|
$self->screen->project_layers(undef);
|
||||||
|
$self->delay($self->config2->{settle_time}, sub {
|
||||||
|
$self->project_next_layer;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sub stop_print {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
$self->is_printing(0);
|
||||||
|
$self->timer->Stop;
|
||||||
|
$self->_timer_cb(undef);
|
||||||
|
$self->screen->project_layers(undef);
|
||||||
|
|
||||||
|
# send custom end G-code
|
||||||
|
$self->sender->send($_, 1) for grep !/^;/, split /\n/, $self->config->end_gcode;
|
||||||
|
$self->sender->disconnect if $self->sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub is_projecting {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return defined $self->screen->layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub project_layer {
|
||||||
|
my ($self, $layer_num) = @_;
|
||||||
|
|
||||||
|
if (!defined $layer_num || $layer_num >= $self->layer_count) {
|
||||||
|
$self->screen->project_layers(undef);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my @layers = @{ $self->_layers->{ $self->_heights->[$layer_num] } };
|
||||||
|
$self->screen->project_layers([ @layers ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub project_next_layer {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
$self->_layer_num($self->_layer_num + 1);
|
||||||
|
Slic3r::debugf "projecting layer %d\n", $self->_layer_num;
|
||||||
|
if ($self->_layer_num >= $self->layer_count) {
|
||||||
|
$self->on_print_completed->()
|
||||||
|
if $self->is_printing && $self->on_print_completed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->on_project_layer->($self->_layer_num) if $self->on_project_layer;
|
||||||
|
|
||||||
|
if ($self->sender) {
|
||||||
|
my $z = $self->current_layer_height;
|
||||||
|
my $F = $self->config2->{z_lift_speed} * 60;
|
||||||
|
if ($self->config2->{z_lift} != 0) {
|
||||||
|
$self->sender->send(sprintf("G1 Z%.5f F%d", $z + $self->config2->{z_lift}, $F), 1);
|
||||||
|
}
|
||||||
|
$self->sender->send(sprintf("G1 Z%.5f F%d", $z, $F), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: we should block until G1 commands have been performed, see note below
|
||||||
|
# TODO: subtract this waiting time from the settle_time
|
||||||
|
$self->delay(2, sub {
|
||||||
|
$self->project_layer($self->_layer_num);
|
||||||
|
|
||||||
|
# get exposure time
|
||||||
|
my $time = $self->config2->{exposure_time};
|
||||||
|
if ($self->_layer_num < $self->config2->{bottom_layers}) {
|
||||||
|
$time = $self->config2->{bottom_exposure_time};
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->delay($time, sub {
|
||||||
|
$self->settle;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sub settle {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
Slic3r::debugf "settling\n";
|
||||||
|
$self->screen->project_layers(undef);
|
||||||
|
$self->delay($self->config2->{settle_time}, sub {
|
||||||
|
$self->project_next_layer;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sub DESTROY {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
$self->timer->Stop if $self->timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
package Slic3r::GUI::Projector::Screen;
|
||||||
|
use Wx qw(:dialog :id :misc :sizer :colour :pen :brush :font);
|
||||||
|
use Wx::Event qw(EVT_PAINT EVT_SIZE);
|
||||||
|
use base qw(Wx::Dialog Class::Accessor);
|
||||||
|
|
||||||
|
use List::Util qw(min);
|
||||||
|
use Slic3r::Geometry qw(X Y unscale scale);
|
||||||
|
use Slic3r::Geometry::Clipper qw(intersection_pl);
|
||||||
|
|
||||||
|
__PACKAGE__->mk_accessors(qw(config config2 scaling_factor bed_origin layers));
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
my ($class, $parent, $config, $config2) = @_;
|
||||||
|
my $self = $class->SUPER::new($parent, -1, "Projector", wxDefaultPosition, wxDefaultSize, 0);
|
||||||
|
|
||||||
|
$self->config($config);
|
||||||
|
$self->config2($config2);
|
||||||
|
EVT_SIZE($self, \&_resize);
|
||||||
|
EVT_PAINT($self, \&_repaint);
|
||||||
|
$self->_resize;
|
||||||
|
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _resize {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return if !$self->config;
|
||||||
|
my ($cw, $ch) = $self->GetSizeWH;
|
||||||
|
|
||||||
|
# get bed shape polygon
|
||||||
|
my $bed_polygon = Slic3r::Polygon->new_scale(@{$self->config->bed_shape});
|
||||||
|
my $bb = $bed_polygon->bounding_box;
|
||||||
|
my $size = $bb->size;
|
||||||
|
my $center = $bb->center;
|
||||||
|
|
||||||
|
# calculate the scaling factor needed for constraining print bed area inside preview
|
||||||
|
# scaling_factor is expressed in pixel / mm
|
||||||
|
$self->scaling_factor(min($cw / unscale($size->x), $ch / unscale($size->y))); #)
|
||||||
|
|
||||||
|
# apply zoom to scaling factor
|
||||||
|
if ($self->config2->{zoom} != 0) {
|
||||||
|
# TODO: make sure min and max in the option config are enforced
|
||||||
|
$self->scaling_factor($self->scaling_factor * ($self->config2->{zoom}/100));
|
||||||
|
}
|
||||||
|
|
||||||
|
# calculate the displacement needed for centering bed on screen
|
||||||
|
$self->bed_origin([
|
||||||
|
$cw/2 - (unscale($center->x) - $self->config2->{offset}->[X]) * $self->scaling_factor,
|
||||||
|
$ch/2 - (unscale($center->y) - $self->config2->{offset}->[Y]) * $self->scaling_factor, #))
|
||||||
|
]);
|
||||||
|
|
||||||
|
$self->Refresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub project_layers {
|
||||||
|
my ($self, $layers) = @_;
|
||||||
|
|
||||||
|
$self->layers($layers);
|
||||||
|
$self->Refresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _repaint {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
my $dc = Wx::PaintDC->new($self);
|
||||||
|
my ($cw, $ch) = $self->GetSizeWH;
|
||||||
|
return if $cw == 0; # when canvas is not rendered yet, size is 0,0
|
||||||
|
|
||||||
|
$dc->SetPen(Wx::Pen->new(wxBLACK, 1, wxSOLID));
|
||||||
|
$dc->SetBrush(Wx::Brush->new(wxBLACK, wxSOLID));
|
||||||
|
$dc->DrawRectangle(0, 0, $cw, $ch);
|
||||||
|
|
||||||
|
return if !$self->config;
|
||||||
|
|
||||||
|
# turn size into max visible coordinates
|
||||||
|
# TODO: or should we use ClientArea?
|
||||||
|
$cw--;
|
||||||
|
$ch--;
|
||||||
|
|
||||||
|
# draw bed
|
||||||
|
if ($self->config2->{show_bed}) {
|
||||||
|
$dc->SetPen(Wx::Pen->new(wxRED, 2, wxSOLID));
|
||||||
|
$dc->SetBrush(Wx::Brush->new(wxWHITE, wxTRANSPARENT));
|
||||||
|
|
||||||
|
# draw contour
|
||||||
|
my $bed_polygon = Slic3r::Polygon->new_scale(@{$self->config->bed_shape});
|
||||||
|
$dc->DrawPolygon($self->scaled_points_to_pixel($bed_polygon), 0, 0);
|
||||||
|
|
||||||
|
# draw grid
|
||||||
|
$dc->SetPen(Wx::Pen->new(wxRED, 1, wxSOLID));
|
||||||
|
{
|
||||||
|
my $bb = $bed_polygon->bounding_box;
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
$dc->DrawLine(map @$_, @$_)
|
||||||
|
for map $self->scaled_points_to_pixel([ @$_[0,-1] ]),
|
||||||
|
@{intersection_pl(\@polylines, [$bed_polygon])};
|
||||||
|
}
|
||||||
|
|
||||||
|
# draw axes orientation
|
||||||
|
$dc->SetPen(Wx::Pen->new(wxWHITE, 4, wxSOLID));
|
||||||
|
{
|
||||||
|
foreach my $endpoint ([10, 0], [0, 10]) {
|
||||||
|
$dc->DrawLine(
|
||||||
|
map @{$self->unscaled_point_to_pixel($_)}, [0,0], $endpoint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$dc->SetTextForeground(wxWHITE);
|
||||||
|
$dc->SetFont(Wx::Font->new(20, wxDEFAULT, wxNORMAL, wxNORMAL));
|
||||||
|
$dc->DrawText("X", @{$self->unscaled_point_to_pixel([10, -2])});
|
||||||
|
$dc->DrawText("Y", @{$self->unscaled_point_to_pixel([-2, 10])});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if !defined $self->layers;
|
||||||
|
|
||||||
|
# get layers at this height
|
||||||
|
# draw layers
|
||||||
|
$dc->SetPen(Wx::Pen->new(wxWHITE, 1, wxSOLID));
|
||||||
|
foreach my $layer (@{$self->layers}) {
|
||||||
|
my @polygons = sort { $a->contains_point($b->first_point) ? -1 : 1 } map @$_, @{ $layer->slices };
|
||||||
|
foreach my $copy (@{$layer->object->_shifted_copies}) {
|
||||||
|
foreach my $polygon (@polygons) {
|
||||||
|
$polygon = $polygon->clone;
|
||||||
|
$polygon->translate(@$copy);
|
||||||
|
|
||||||
|
if ($polygon->is_counter_clockwise) {
|
||||||
|
$dc->SetBrush(Wx::Brush->new(wxWHITE, wxSOLID));
|
||||||
|
} else {
|
||||||
|
$dc->SetBrush(Wx::Brush->new(wxBLACK, wxSOLID));
|
||||||
|
}
|
||||||
|
$dc->DrawPolygon($self->scaled_points_to_pixel($polygon->pp), 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# convert a model coordinate into a pixel coordinate
|
||||||
|
sub unscaled_point_to_pixel {
|
||||||
|
my ($self, $point) = @_;
|
||||||
|
|
||||||
|
my $zero = $self->bed_origin;
|
||||||
|
my $p = [
|
||||||
|
$point->[X] * $self->scaling_factor + $zero->[X],
|
||||||
|
$point->[Y] * $self->scaling_factor + $zero->[Y],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!$self->config2->{invert_y}) {
|
||||||
|
my $ch = $self->GetSize->GetHeight;
|
||||||
|
$p->[Y] = $ch - $p->[Y];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $p;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub scaled_points_to_pixel {
|
||||||
|
my ($self, $points) = @_;
|
||||||
|
|
||||||
|
return [
|
||||||
|
map $self->unscaled_point_to_pixel($_),
|
||||||
|
map Slic3r::Pointf->new_unscale(@$_),
|
||||||
|
@$points
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
BIN
var/film.png
Normal file
BIN
var/film.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 653 B |
Loading…
Reference in New Issue
Block a user