Merge branch 'master' into sender

This commit is contained in:
Alessandro Ranellucci 2014-12-31 16:25:26 +01:00
commit 29d64107de
27 changed files with 372 additions and 104 deletions

View File

@ -28,6 +28,8 @@ my %prereqs = qw(
);
my %recommends = qw(
Class::XSAccessor 0
LWP::UserAgent 0
Net::Bonjour 0
XML::SAX::ExpatXS 0
);
@ -109,7 +111,9 @@ EOF
my %modules = (%prereqs, %recommends);
foreach my $module (sort keys %modules) {
my $version = $modules{$module};
my @cmd = ($cpanm, @cpanm_args, "$module~$version");
my @cmd = ($cpanm, @cpanm_args);
push @cmd, '-f', if $module eq 'OpenGL'; # temporary workaround for upstream bug in test
push @cmd, "$module~$version";
if ($module eq 'XML::SAX::ExpatXS' && $^O eq 'MSWin32') {
my $mingw = 'C:\dev\CitrusPerl\mingw64';
$mingw = 'C:\dev\CitrusPerl\mingw32' if !-d $mingw;

View File

@ -187,7 +187,7 @@ sub make_fill {
if ($surface->is_solid) {
$density = 100;
$filler = 'rectilinear';
if ($surface->is_external) {
if ($surface->is_external && !$is_bridge) {
$filler = $layerm->config->external_fill_pattern;
}
} else {

View File

@ -19,12 +19,14 @@ sub fill_surface {
my $distance = scale($self->spacing) / $params{density};
# align bounding box to a multiple of our honeycomb grid
# align bounding box to a multiple of our honeycomb grid module
# (a module is 2*$distance since one $distance half-module is
# growing while the other $distance half-module is shrinking)
{
my $min = $bb->min_point;
$min->translate(
-($bb->x_min % $distance),
-($bb->y_min % $distance),
-($bb->x_min % (2*$distance)),
-($bb->y_min % (2*$distance)),
);
$bb->merge_point($min);
}
@ -34,8 +36,8 @@ sub fill_surface {
makeGrid(
scale($self->z),
$distance,
ceil($size->x / $distance),
ceil($size->y / $distance), #//
ceil($size->x / $distance) + 1,
ceil($size->y / $distance) + 1, #//
(($self->layer_id / $surface->thickness_layers) % 2) + 1,
);

View File

@ -220,7 +220,7 @@ sub extrude_loop {
$point->rotate($angle, $first_segment->a);
# generate the travel move
$gcode .= $self->travel_to($point, $paths[-1]->role, "move inwards before travel");
$gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), "move inwards before travel");
}
return $gcode;
@ -328,6 +328,7 @@ sub _extrude_path {
return $gcode;
}
# This method accepts $point in print coordinates.
sub travel_to {
my ($self, $point, $role, $comment) = @_;
@ -355,11 +356,11 @@ sub travel_to {
|| (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_line($travel))
) {
# Just perform a straight travel move without any retraction.
$gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment);
$gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment || '');
} elsif ($self->config->avoid_crossing_perimeters && !$self->avoid_crossing_perimeters->disable_once) {
# If avoid_crossing_perimeters is enabled and the disable_once flag is not set
# we need to plan a multi-segment travel move inside the configuration space.
$gcode .= $self->avoid_crossing_perimeters->travel_to($self, $point, $comment);
$gcode .= $self->avoid_crossing_perimeters->travel_to($self, $point, $comment || '');
} else {
# If avoid_crossing_perimeters is disabled or the disable_once flag is set,
# perform a straight move with a retraction.
@ -473,7 +474,7 @@ sub pre_toolchange {
$last_pos->translate(scale +$gcodegen->origin->x, scale +$gcodegen->origin->y); #))
my $standby_point = $last_pos->nearest_point($self->standby_points);
$standby_point->translate(scale -$gcodegen->origin->x, scale -$gcodegen->origin->y); #))
$gcode .= $gcodegen->travel_to($standby_point);
$gcode .= $gcodegen->travel_to($standby_point, undef, 'move to standby position');
}
if ($gcodegen->config->standby_temperature_delta != 0) {

View File

@ -7,6 +7,7 @@ use File::Basename qw(basename);
use FindBin;
use Slic3r::GUI::AboutDialog;
use Slic3r::GUI::BedShapeDialog;
use Slic3r::GUI::BonjourBrowser;
use Slic3r::GUI::ConfigWizard;
use Slic3r::GUI::MainFrame;
use Slic3r::GUI::Notifier;
@ -27,6 +28,7 @@ use Slic3r::GUI::SimpleTab;
use Slic3r::GUI::Tab;
our $have_OpenGL = eval "use Slic3r::GUI::PreviewCanvas; 1";
our $have_LWP = eval "use LWP::UserAgent; 1";
use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow
:filedialog);
@ -228,7 +230,7 @@ sub have_version_check {
my ($self) = @_;
# return an explicit 0
return ($Slic3r::have_threads && $Slic3r::build && eval "use LWP::UserAgent; 1") || 0;
return ($Slic3r::have_threads && $Slic3r::build && $have_LWP) || 0;
}
sub check_version {

View File

@ -0,0 +1,51 @@
package Slic3r::GUI::BonjourBrowser;
use strict;
use warnings;
use utf8;
use Wx qw(:dialog :id :misc :sizer :choicebook wxTAB_TRAVERSAL);
use Wx::Event qw(EVT_CLOSE);
use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, -1, "Device Browser", wxDefaultPosition, [350,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
# look for devices
eval "use Net::Bonjour; 1";
my $res = Net::Bonjour->new('http');
$res->discover;
$self->{devices} = [ $res->entries ];
# label
my $text = Wx::StaticText->new($self, -1, "Choose an OctoPrint device in your network:", wxDefaultPosition, wxDefaultSize);
# selector
$self->{choice} = my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize,
[ map $_->name, @{$self->{devices}} ]);
my $main_sizer = Wx::BoxSizer->new(wxVERTICAL);
$main_sizer->Add($text, 1, wxEXPAND | wxALL, 10);
$main_sizer->Add($choice, 1, wxEXPAND | wxALL, 10);
$main_sizer->Add($self->CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND);
$self->SetSizer($main_sizer);
$self->SetMinSize($self->GetSize);
$main_sizer->SetSizeHints($self);
# needed to actually free memory
EVT_CLOSE($self, sub {
$self->EndModal(wxID_OK);
$self->Destroy;
});
return $self;
}
sub GetValue {
my ($self) = @_;
return $self->{devices}[ $self->{choice}->GetSelection ]->address;
}
1;

View File

@ -673,7 +673,11 @@ sub config {
sub check_unsaved_changes {
my $self = shift;
my @dirty = map $_->title, grep $_->is_dirty, values %{$self->{options_tabs}};
my @dirty = ();
foreach my $tab (values %{$self->{options_tabs}}) {
push @dirty, $tab->title if $tab->is_dirty;
}
if (@dirty) {
my $titles = join ', ', @dirty;
my $confirm = Wx::MessageDialog->new($self, "You have unsaved changes ($titles). Discard changes and continue anyway?",

View File

@ -83,7 +83,7 @@ sub append_line {
# if we have a single option with no sidetext just add it directly to the grid sizer
my @options = @{$line->get_options};
$self->_options->{$_->opt_id} = $_ for @options;
if (@options == 1 && !$options[0]->sidetext) {
if (@options == 1 && !$options[0]->sidetext && !@{$line->get_extra_widgets}) {
my $option = $options[0];
my $field = $self->_build_field($option);
$grid_sizer->Add($field, 0, ($option->full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
@ -114,9 +114,14 @@ sub append_line {
$sizer->Add($sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4);
}
}
# add extra sizers if any
foreach my $extra_widget (@{$line->get_extra_widgets}) {
$sizer->Add($extra_widget->($self->parent), 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4);
}
}
sub append_single_option_line {
sub create_single_option_line {
my ($self, $option) = @_;
my $line = Slic3r::GUI::OptionsGroup::Line->new(
@ -125,11 +130,15 @@ sub append_single_option_line {
);
$option->label("");
$line->append_option($option);
$self->append_line($line);
return $line;
}
sub append_single_option_line {
my ($self, $option) = @_;
return $self->append_line($self->create_single_option_line($option));
}
sub _build_field {
my $self = shift;
my ($opt) = @_;
@ -241,6 +250,7 @@ has 'label_tooltip' => (is => 'rw', default => sub { "" });
has 'sizer' => (is => 'rw');
has 'widget' => (is => 'rw');
has '_options' => (is => 'ro', default => sub { [] });
has '_extra_widgets' => (is => 'ro', default => sub { [] });
# this method accepts a Slic3r::GUI::OptionsGroup::Option object
sub append_option {
@ -248,11 +258,21 @@ sub append_option {
push @{$self->_options}, $option;
}
sub append_widget {
my ($self, $widget) = @_;
push @{$self->_extra_widgets}, $widget;
}
sub get_options {
my ($self) = @_;
return [ @{$self->_options} ];
}
sub get_extra_widgets {
my ($self) = @_;
return [ @{$self->_extra_widgets} ];
}
package Slic3r::GUI::OptionsGroup::Option;
use Moo;
@ -320,7 +340,7 @@ sub get_option {
);
}
sub append_single_option_line {
sub create_single_option_line {
my ($self, $opt_key, $opt_index) = @_;
my $option;
@ -329,7 +349,12 @@ sub append_single_option_line {
} else {
$option = $self->get_option($opt_key, $opt_index);
}
return $self->SUPER::append_single_option_line($option);
return $self->SUPER::create_single_option_line($option);
}
sub append_single_option_line {
my ($self, $option, $opt_index) = @_;
return $self->append_line($self->create_single_option_line($option, $opt_index));
}
sub reload_config {

View File

@ -347,6 +347,7 @@ sub BUILD {
$self->wxSizer($sizer);
my $field_size = Wx::Size->new(40, -1);
$self->x_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[X], wxDefaultPosition, $field_size));
$self->y_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[Y], wxDefaultPosition, $field_size));

View File

@ -48,6 +48,7 @@ sub new {
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
$self->{config} = Slic3r::Config->new_from_defaults(qw(
bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width
octoprint_host octoprint_apikey
));
$self->{model} = Slic3r::Model->new;
$self->{print} = Slic3r::Print->new;
@ -88,16 +89,7 @@ sub new {
};
my $on_instance_moved = sub {
my ($obj_idx, $instance_idx) = @_;
$self->update;
$self->pause_background_process;
my $invalidated = $self->{print}->objects->[$obj_idx]->reload_model_instances();
if ($invalidated) {
$self->schedule_background_process;
} else {
$self->resume_background_process;
}
};
# Initialize 2D preview canvas
@ -181,9 +173,11 @@ sub new {
# right pane buttons
$self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", wxDefaultPosition, [-1, 30], wxBU_LEFT);
$self->{btn_send_gcode} = Wx::Button->new($self, -1, "Send to printer", wxDefaultPosition, [-1, 30], wxBU_LEFT);
$self->{btn_export_stl} = Wx::Button->new($self, -1, "Export STL…", wxDefaultPosition, [-1, 30], wxBU_LEFT);
#$self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font);
#$self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font);
$self->{btn_send_gcode}->Hide;
if ($Slic3r::GUI::have_button_icons) {
my %icons = qw(
@ -192,6 +186,7 @@ sub new {
reset cross.png
arrange bricks.png
export_gcode cog_go.png
send_gcode arrow_up.png
export_stl brick_go.png
increase add.png
@ -213,7 +208,11 @@ sub new {
$self->export_gcode;
Slic3r::thread_cleanup();
});
#EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl);
EVT_BUTTON($self, $self->{btn_send_gcode}, sub {
$self->{send_gcode_file} = $self->export_gcode(Wx::StandardPaths::Get->GetTempDir());
Slic3r::thread_cleanup();
});
EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl);
if ($self->{htoolbar}) {
EVT_TOOL($self, TB_ADD, sub { $self->add; });
@ -355,6 +354,7 @@ sub new {
my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$buttons_sizer->AddStretchSpacer(1);
$buttons_sizer->Add($self->{btn_export_stl}, 0, wxALIGN_RIGHT, 0);
$buttons_sizer->Add($self->{btn_send_gcode}, 0, wxALIGN_RIGHT, 0);
$buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0);
my $right_sizer = Wx::BoxSizer->new(wxVERTICAL);
@ -555,7 +555,6 @@ sub remove {
$self->select_object(undef);
$self->update;
$self->schedule_background_process;
}
@ -666,10 +665,10 @@ sub rotate {
$model_object->update_bounding_box;
# update print and start background processing
$self->{print}->add_model_object($model_object, $obj_idx);
$self->schedule_background_process;
$self->selection_changed; # refresh info (size etc.)
$self->update;
$self->schedule_background_process;
}
sub flip {
@ -694,11 +693,10 @@ sub flip {
# update print and start background processing
$self->stop_background_process;
$self->{print}->add_model_object($model_object, $obj_idx);
$self->schedule_background_process;
$self->selection_changed; # refresh info (size etc.)
$self->update;
$self->refresh_canvases;
$self->schedule_background_process;
}
sub changescale {
@ -749,11 +747,10 @@ sub changescale {
# update print and start background processing
$self->stop_background_process;
$self->{print}->add_model_object($model_object, $obj_idx);
$self->schedule_background_process;
$self->selection_changed(1); # refresh info (size, volume etc.)
$self->update;
$self->refresh_canvases;
$self->schedule_background_process;
}
sub arrange {
@ -769,17 +766,6 @@ sub arrange {
# when parts don't fit in print bed
$self->update(1);
my $invalidated = 0;
foreach my $object (@{$self->{print}->objects}) {
$invalidated = 1 if $object->reload_model_instances;
}
if ($invalidated) {
$self->schedule_background_process;
} else {
$self->resume_background_process;
}
$self->refresh_canvases;
}
sub split_object {
@ -788,19 +774,22 @@ sub split_object {
my ($obj_idx, $current_object) = $self->selected_object;
# we clone model object because split_object() adds the split volumes
# into the same model object, thus causing duplicated when we call load_model_objects()
my $current_model_object = $self->{model}->clone->objects->[$obj_idx];
# into the same model object, thus causing duplicates when we call load_model_objects()
my $new_model = $self->{model}->clone; # store this before calling get_object()
my $current_model_object = $new_model->get_object($obj_idx);
if (@{$current_model_object->volumes} > 1) {
if ($current_model_object->volumes_count > 1) {
Slic3r::GUI::warning_catcher($self)->("The selected object can't be split because it contains more than one volume/material.");
return;
}
$self->stop_background_process;
$self->pause_background_process;
my @model_objects = @{$current_model_object->split_object};
if (@model_objects == 1) {
$self->resume_background_process;
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it contains only one part.");
$self->resume_background_process;
return;
}
@ -944,7 +933,7 @@ sub resume_background_process {
}
sub export_gcode {
my $self = shift;
my ($self, $output_file) = @_;
return if !@{$self->{objects}};
@ -976,14 +965,14 @@ sub export_gcode {
}
# select output file
$self->{export_gcode_output_file} = $main::opt{output};
{
my $default_output_file = $self->{print}->expanded_output_filepath($self->{export_gcode_output_file});
if ($output_file) {
$self->{export_gcode_output_file} = $self->{print}->expanded_output_filepath($output_file);
} else {
my $default_output_file = $self->{print}->expanded_output_filepath($main::opt{output});
my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', wxTheApp->output_path(dirname($default_output_file)),
basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE);
if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy;
$self->{export_gcode_output_file} = undef;
return;
}
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($dlg->GetPath);
@ -1011,6 +1000,8 @@ sub export_gcode {
my $result = !Slic3r::GUI::catch_error($self);
$self->on_export_completed($result);
}
return $self->{export_gcode_output_file};
}
# This gets called only if we have threads.
@ -1072,14 +1063,52 @@ sub on_export_completed {
$self->{export_thread} = undef;
my $message;
my $send_gcode = 0;
if ($result) {
$message = "G-code file exported to " . $self->{export_gcode_output_file};
if ($self->{send_gcode_file}) {
$message = "Sending G-code file to the OctoPrint server...";
$send_gcode = 1;
} else {
$message = "G-code file exported to " . $self->{export_gcode_output_file};
}
} else {
$message = "Export failed";
}
$self->{export_gcode_output_file} = undef;
$self->statusbar->SetStatusText($message);
wxTheApp->notify($message);
$self->send_gcode if $send_gcode;
$self->{send_gcode_file} = undef;
}
sub send_gcode {
my ($self) = @_;
$self->statusbar->StartBusy;
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
my $res = $ua->post(
"http://" . $self->{config}->octoprint_host . "/api/files/local",
Content_Type => 'form-data',
'X-Api-Key' => $self->{config}->octoprint_apikey,
Content => [
# OctoPrint doesn't like Windows paths
file => [ $self->{send_gcode_file}, basename($self->{send_gcode_file}) ],
],
);
$self->statusbar->StopBusy;
if ($res->is_success) {
$self->statusbar->SetStatusText("G-code file successfully uploaded to the OctoPrint server");
} else {
my $message = "Error while uploading to the OctoPrint server: " . $res->status_line;
Slic3r::GUI::show_error($self, $message);
$self->statusbar->SetStatusText($message);
}
}
sub export_stl {
@ -1159,7 +1188,6 @@ sub on_thumbnail_made {
my ($obj_idx) = @_;
$self->{objects}[$obj_idx]->transform_thumbnail($self->{model}, $obj_idx);
$self->update;
$self->refresh_canvases;
}
@ -1172,9 +1200,23 @@ sub update {
$self->{model}->center_instances_around_point($self->bed_centerf);
}
$self->pause_background_process;
my $invalidated = $self->{print}->reload_model_instances();
if ($invalidated) {
$self->schedule_background_process;
} else {
$self->resume_background_process;
}
$self->refresh_canvases;
}
sub on_model_instances_changed {
my ($self) = @_;
}
sub on_extruders_change {
my ($self, $num_extruders) = @_;
@ -1208,6 +1250,13 @@ sub on_config_change {
$self->{canvas}->update_bed_size;
$self->{canvas3D}->update_bed_size if $self->{canvas3D};
$self->update;
} elsif ($opt_key eq 'octoprint_host') {
if ($config->get('octoprint_host')) {
$self->{btn_send_gcode}->Show;
} else {
$self->{btn_send_gcode}->Hide;
}
$self->Layout;
}
}
@ -1313,7 +1362,7 @@ sub object_list_changed {
my $have_objects = @{$self->{objects}} ? 1 : 0;
my $method = $have_objects ? 'Enable' : 'Disable';
$self->{"btn_$_"}->$method
for grep $self->{"btn_$_"}, qw(reset arrange export_gcode export_stl);
for grep $self->{"btn_$_"}, qw(reset arrange export_gcode export_stl send_gcode);
if ($self->{htoolbar}) {
$self->{htoolbar}->EnableTool($_, $have_objects)

View File

@ -224,13 +224,7 @@ sub on_btn_load {
}
}
$self->reload_tree;
if ($self->{canvas}) {
$self->{canvas}->reset_objects;
$self->{canvas}->load_object($self->{model_object});
$self->{canvas}->set_bounding_box($self->{model_object}->bounding_box);
$self->{canvas}->Render;
}
$self->_parts_changed;
}
sub on_btn_delete {
@ -250,9 +244,17 @@ sub on_btn_delete {
$self->{parts_changed} = 1;
}
$self->_parts_changed;
}
sub _parts_changed {
my ($self) = @_;
$self->reload_tree;
if ($self->{canvas}) {
$self->{canvas}->reset_objects;
$self->{canvas}->load_object($self->{model_object});
$self->{canvas}->zoom_to_volumes;
$self->{canvas}->Render;
}
}

View File

@ -5,7 +5,7 @@ use base 'Wx::Dialog';
sub new {
my ($class, $parent) = @_;
my $self = $class->SUPER::new($parent, -1, "Preferences", wxDefaultPosition, [500,200]);
my $self = $class->SUPER::new($parent, -1, "Preferences", wxDefaultPosition, wxDefaultSize);
$self->{values} = {};
my $optgroup;
@ -16,7 +16,7 @@ sub new {
my ($opt_id) = @_;
$self->{values}{$opt_id} = $optgroup->get_value($opt_id);
},
label_width => 100,
label_width => 200,
);
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'mode',
@ -26,6 +26,7 @@ sub new {
labels => ['Simple','Expert'],
values => ['simple','expert'],
default => $Slic3r::GUI::Settings->{_}{mode},
width => 100,
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'version_check',

View File

@ -151,9 +151,6 @@ sub mouse_event {
}
}
} elsif ($e->Dragging && $e->LeftIsDown && defined($self->_drag_volume_idx)) {
# get volume being dragged
my $volume = $self->volumes->[$self->_drag_volume_idx];
# get new position at the same Z of the initial click point
my $mouse_ray = $self->mouse_ray($e->GetX, $e->GetY);
my $cur_pos = $mouse_ray->intersect_plane($self->_drag_start_pos->z);
@ -161,8 +158,14 @@ sub mouse_event {
# calculate the translation vector
my $vector = $self->_drag_start_pos->vector_to($cur_pos);
# get volume being dragged
my $volume = $self->volumes->[$self->_drag_volume_idx];
# get all volumes belonging to the same group but only having the same instance_idx
my @volumes = grep $_->group_id == $volume->group_id && $_->instance_idx == $volume->instance_idx, @{$self->volumes};
# apply new temporary volume origin and ignore Z
$volume->origin->translate($vector->x, $vector->y, 0); #,,
$_->origin->translate($vector->x, $vector->y, 0) for @volumes; #,,
$self->_drag_start_pos($cur_pos);
$self->_dragged(1);
$self->Refresh;
@ -346,6 +349,7 @@ sub load_object {
# sort volumes: non-modifiers first
my @volumes = sort { ($a->modifier // 0) <=> ($b->modifier // 0) } @{$object->volumes};
my @volumes_idx = ();
my $group_id = $#{$self->volumes} + 1;
foreach my $volume (@volumes) {
my @instance_idxs = $all_instances ? (0..$#{$object->instances}) : (0);
foreach my $instance_idx (@instance_idxs) {
@ -363,6 +367,7 @@ sub load_object {
my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ];
push @$color, $volume->modifier ? 0.5 : 1;
push @{$self->volumes}, my $v = Slic3r::GUI::PreviewCanvas::Volume->new(
group_id => $group_id,
instance_idx => $instance_idx,
mesh => $mesh,
color => $color,
@ -888,6 +893,7 @@ use Moo;
has 'mesh' => (is => 'ro', required => 1);
has 'color' => (is => 'ro', required => 1);
has 'group_id' => (is => 'ro', required => 1);
has 'instance_idx' => (is => 'ro', default => sub { 0 });
has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf3->new(0,0,0) });
has 'verts' => (is => 'rw');

View File

@ -923,6 +923,7 @@ sub build {
$self->init_config_options(qw(
bed_shape z_offset
gcode_flavor use_relative_e_distances
octoprint_host octoprint_apikey
use_firmware_retraction pressure_advance vibration_limit
start_gcode end_gcode layer_gcode toolchange_gcode
nozzle_diameter extruder_offset
@ -996,6 +997,45 @@ sub build {
}
});
}
{
my $optgroup = $page->new_optgroup('OctoPrint upload');
# append a button to the Host line
my $octoprint_host_widget = sub {
my ($parent) = @_;
my $btn = Wx::Button->new($parent, -1, "Browse…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
$btn->SetFont($Slic3r::GUI::small_font);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/zoom.png", wxBITMAP_TYPE_PNG));
}
if (!eval "use Net::Bonjour; 1") {
$btn->Disable;
}
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($btn);
EVT_BUTTON($self, $btn, sub {
my $dlg = Slic3r::GUI::BonjourBrowser->new($self);
if ($dlg->ShowModal == wxID_OK) {
my $value = $dlg->GetValue;
$self->{config}->set('octoprint_host', $value);
$self->update_dirty;
$self->_on_value_change('octoprint_host', $value);
$self->reload_config;
}
});
return $sizer;
};
my $host_line = $optgroup->create_single_option_line('octoprint_host');
$host_line->append_widget($octoprint_host_widget);
$optgroup->append_line($host_line);
$optgroup->append_single_option_line('octoprint_apikey');
}
{
my $optgroup = $page->new_optgroup('Advanced');
$optgroup->append_single_option_line('use_firmware_retraction');
@ -1129,6 +1169,8 @@ sub _update {
my $config = $self->{config};
$self->get_field('octoprint_apikey')->toggle($config->get('octoprint_host'));
my $have_multiple_extruders = $self->{extruders_count} > 1;
$self->get_field('toolchange_gcode')->toggle($have_multiple_extruders);
@ -1319,8 +1361,8 @@ sub config {
}
# apply preset values on top of defaults
my $config = Slic3r::Config->new_from_defaults(@$keys);
my $external_config = Slic3r::Config->load($self->file);
my $config = Slic3r::Config->new;
$config->set($_, $external_config->get($_))
for grep $external_config->has($_), @$keys;

View File

@ -219,7 +219,7 @@ sub make_skirt {
# $skirt_height_z in this case is the highest possible skirt height for safety.
my $skirt_height_z = -1;
foreach my $object (@{$self->objects}) {
my $skirt_height = ($self->config->skirt_height == -1)
my $skirt_height = ($self->config->skirt_height == -1 || $self->config->ooze_prevention)
? scalar(@{$object->layers})
: min($self->config->skirt_height, scalar(@{$object->layers}));

View File

@ -147,15 +147,26 @@ sub export {
my $outer_skirt = convex_hull(\@skirt_points);
my @skirts = ();
foreach my $extruder_id (@{$self->print->extruders}) {
my $extruder_offset = $self->config->get_at('extruder_offset', $extruder_id);
push @skirts, my $s = $outer_skirt->clone;
$s->translate(map scale($_), @{$self->config->get_at('extruder_offset', $extruder_id)});
$s->translate(-scale($extruder_offset->x), -scale($extruder_offset->y)); #)
}
my $convex_hull = convex_hull([ map @$_, @skirts ]);
$gcodegen->ooze_prevention->enable(1);
$gcodegen->ooze_prevention->standby_points(
[ map $_->clone, map @$_, map $_->subdivide(scale 10), @{offset([$convex_hull], scale 3)} ]
[ map @{$_->equally_spaced_points(scale 10)}, @{offset([$convex_hull], scale 3)} ]
);
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"ooze_prevention.svg",
polygons => [$outer_skirt],
red_polygons => \@skirts,
points => $gcodegen->ooze_prevention->standby_points,
);
}
}
}

View File

@ -442,23 +442,25 @@ sub generate_bottom_interface_layers {
my $z = $support_z->[$layer_id];
next unless $z > $top_z;
# get the support material area that should be considered interface
my $interface_area = intersection(
$base->{$layer_id},
$this,
);
if ($base->{$layer_id}) {
# get the support material area that should be considered interface
my $interface_area = intersection(
$base->{$layer_id},
$this,
);
# discard too small areas
$interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ];
# discard too small areas
$interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ];
# subtract new interface area from base
$base->{$layer_id} = diff(
$base->{$layer_id},
$interface_area,
);
# subtract new interface area from base
$base->{$layer_id} = diff(
$base->{$layer_id},
$interface_area,
);
# add new interface area to interface
push @{$interface->{$layer_id}}, @$interface_area;
# add new interface area to interface
push @{$interface->{$layer_id}}, @$interface_area;
}
$interface_layers++;
last if $interface_layers == $self->object_config->support_material_interface_layers;

View File

@ -17,23 +17,25 @@ use Slic3r::Test;
my $config = Slic3r::Config->new_from_defaults;
$config->set('raft_layers', 2);
$config->set('infill_extruder', 2);
$config->set('support_material_extruder', 3);
$config->set('solid_infill_extruder', 3);
$config->set('support_material_extruder', 4);
$config->set('ooze_prevention', 1);
$config->set('extruder_offset', [ [0,0], [20,0], [0,20] ]);
$config->set('temperature', [200, 180, 170]);
$config->set('first_layer_temperature', [206, 186, 166]);
$config->set('extruder_offset', [ [0,0], [20,0], [0,20], [20,20] ]);
$config->set('temperature', [200, 180, 170, 160]);
$config->set('first_layer_temperature', [206, 186, 166, 156]);
$config->set('toolchange_gcode', ';toolchange'); # test that it doesn't crash when this is supplied
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my $tool = undef;
my @tool_temp = (0,0,0);
my @tool_temp = (0,0,0,0);
my @toolchange_points = ();
my @extrusion_points = ();
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd =~ /^T(\d+)/) {
# ignore initial toolchange
if (defined $tool) {
my $expected_temp = $self->Z == ($config->get_value('first_layer_height') + $config->z_offset)
? $config->first_layer_temperature->[$tool]
@ -41,8 +43,8 @@ use Slic3r::Test;
die 'standby temperature was not set before toolchange'
if $tool_temp[$tool] != $expected_temp + $config->standby_temperature_delta;
# ignore initial toolchange
push @toolchange_points, Slic3r::Point->new_scale($self->X, $self->Y);
push @toolchange_points, my $point = Slic3r::Point->new_scale($self->X, $self->Y);
$point->translate(map +scale($_), @{ $config->extruder_offset->[$tool] });
}
$tool = $1;
} elsif ($cmd eq 'M104' || $cmd eq 'M109') {
@ -54,11 +56,30 @@ use Slic3r::Test;
$tool_temp[$t] = $args->{S};
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
push @extrusion_points, my $point = Slic3r::Point->new_scale($args->{X}, $args->{Y});
$point->translate(map scale($_), @{ $config->extruder_offset->[$tool] });
$point->translate(map +scale($_), @{ $config->extruder_offset->[$tool] });
}
});
my $convex_hull = convex_hull(\@extrusion_points);
ok !(defined first { $convex_hull->contains_point($_) } @toolchange_points), 'all toolchanges happen outside skirt';
my @t = ();
foreach my $point (@toolchange_points) {
foreach my $offset (@{$config->extruder_offset}) {
push @t, my $p = $point->clone;
$p->translate(map +scale($_), @$offset);
}
}
ok !(defined first { $convex_hull->contains_point($_) } @t), 'all nozzles are outside skirt at toolchange';
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"ooze_prevention.svg",
no_arrows => 1,
polygons => [$convex_hull],
points => \@toolchange_points,
red_points => \@t,
);
}
# offset the skirt by the maximum displacement between extruders plus a safety extra margin
my $delta = scale(20 * sqrt(2) + 1);

BIN
var/zoom.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

View File

@ -13,7 +13,7 @@ Model::Model(const Model &other)
// copy objects
this->objects.reserve(other.objects.size());
for (ModelObjectPtrs::const_iterator i = other.objects.begin(); i != other.objects.end(); ++i)
this->add_object(**i);
this->add_object(**i, true);
}
Model& Model::operator= (Model other)
@ -618,6 +618,7 @@ ModelObject::split(ModelObjectPtrs* new_objects)
new_volume->material_id(volume->material_id());
new_objects->push_back(new_object);
delete *mesh;
}
return;

View File

@ -116,6 +116,16 @@ Print::reload_object(size_t idx)
}
}
bool
Print::reload_model_instances()
{
bool invalidated = false;
FOREACH_OBJECT(this, object) {
if ((*object)->reload_model_instances()) invalidated = true;
}
return invalidated;
}
void
Print::clear_regions()
{

View File

@ -174,6 +174,7 @@ class Print
PrintObject* get_object(size_t idx);
void delete_object(size_t idx);
void reload_object(size_t idx);
bool reload_model_instances();
// methods for handling regions
PrintRegion* get_region(size_t idx);

View File

@ -498,6 +498,16 @@ PrintConfigDef::build_def() {
Options["nozzle_diameter"].sidetext = "mm";
Options["nozzle_diameter"].cli = "nozzle-diameter=f@";
Options["octoprint_apikey"].type = coString;
Options["octoprint_apikey"].label = "API Key";
Options["octoprint_apikey"].tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain the API Key required for authentication.";
Options["octoprint_apikey"].cli = "octoprint-apikey=s";
Options["octoprint_host"].type = coString;
Options["octoprint_host"].label = "Host or IP";
Options["octoprint_host"].tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain the hostname or IP address of the OctoPrint instance.";
Options["octoprint_host"].cli = "octoprint-host=s";
Options["only_retract_when_crossing_perimeters"].type = coBool;
Options["only_retract_when_crossing_perimeters"].label = "Only retract when crossing perimeters";
Options["only_retract_when_crossing_perimeters"].tooltip = "Disables retraction when the travel path does not exceed the upper layer's perimeters (and thus any ooze will be probably invisible).";

View File

@ -582,7 +582,27 @@ class PrintConfig : public GCodeConfig
};
};
class FullPrintConfig : public PrintObjectConfig, public PrintRegionConfig, public PrintConfig
class HostConfig : public virtual StaticPrintConfig
{
public:
ConfigOptionString octoprint_host;
ConfigOptionString octoprint_apikey;
HostConfig() : StaticPrintConfig() {
this->octoprint_host.value = "";
this->octoprint_apikey.value = "";
};
ConfigOption* option(const t_config_option_key opt_key, bool create = false) {
if (opt_key == "octoprint_host") return &this->octoprint_host;
if (opt_key == "octoprint_apikey") return &this->octoprint_apikey;
return NULL;
};
};
class FullPrintConfig
: public PrintObjectConfig, public PrintRegionConfig, public PrintConfig, public HostConfig
{
public:
ConfigOption* option(const t_config_option_key opt_key, bool create = false) {
@ -590,6 +610,7 @@ class FullPrintConfig : public PrintObjectConfig, public PrintRegionConfig, publ
if ((opt = PrintObjectConfig::option(opt_key, create)) != NULL) return opt;
if ((opt = PrintRegionConfig::option(opt_key, create)) != NULL) return opt;
if ((opt = PrintConfig::option(opt_key, create)) != NULL) return opt;
if ((opt = HostConfig::option(opt_key, create)) != NULL) return opt;
return NULL;
};
};

View File

@ -6,7 +6,7 @@
#include <iostream>
#include <sstream>
#define SLIC3R_VERSION "1.2.2"
#define SLIC3R_VERSION "1.2.4"
#define EPSILON 1e-4
#define SCALING_FACTOR 0.000001

View File

@ -42,9 +42,9 @@ template <class T>
class Ref {
T* val;
public:
Ref() {}
Ref() : val(NULL) {}
Ref(T* t) : val(t) {}
operator T*() const {return val; }
operator T*() const { return val; }
static const char* CLASS() { return ClassTraits<T>::name_ref; }
};
@ -52,10 +52,10 @@ template <class T>
class Clone {
T* val;
public:
Clone() : val() {}
Clone() : val(NULL) {}
Clone(T* t) : val(new T(*t)) {}
Clone(const T& t) : val(new T(t)) {}
operator T*() const {return val; }
operator T*() const { return val; }
static const char* CLASS() { return ClassTraits<T>::name; }
};
};

View File

@ -143,6 +143,7 @@ _constant()
Ref<PrintObject> get_object(int idx);
void delete_object(int idx);
void reload_object(int idx);
bool reload_model_instances();
size_t object_count()
%code%{ RETVAL = THIS->objects.size(); %};