Merge branch 'master' into sender
This commit is contained in:
commit
29d64107de
27 changed files with 372 additions and 104 deletions
6
Build.PL
6
Build.PL
|
@ -28,6 +28,8 @@ my %prereqs = qw(
|
||||||
);
|
);
|
||||||
my %recommends = qw(
|
my %recommends = qw(
|
||||||
Class::XSAccessor 0
|
Class::XSAccessor 0
|
||||||
|
LWP::UserAgent 0
|
||||||
|
Net::Bonjour 0
|
||||||
XML::SAX::ExpatXS 0
|
XML::SAX::ExpatXS 0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -109,7 +111,9 @@ EOF
|
||||||
my %modules = (%prereqs, %recommends);
|
my %modules = (%prereqs, %recommends);
|
||||||
foreach my $module (sort keys %modules) {
|
foreach my $module (sort keys %modules) {
|
||||||
my $version = $modules{$module};
|
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') {
|
if ($module eq 'XML::SAX::ExpatXS' && $^O eq 'MSWin32') {
|
||||||
my $mingw = 'C:\dev\CitrusPerl\mingw64';
|
my $mingw = 'C:\dev\CitrusPerl\mingw64';
|
||||||
$mingw = 'C:\dev\CitrusPerl\mingw32' if !-d $mingw;
|
$mingw = 'C:\dev\CitrusPerl\mingw32' if !-d $mingw;
|
||||||
|
|
|
@ -187,7 +187,7 @@ sub make_fill {
|
||||||
if ($surface->is_solid) {
|
if ($surface->is_solid) {
|
||||||
$density = 100;
|
$density = 100;
|
||||||
$filler = 'rectilinear';
|
$filler = 'rectilinear';
|
||||||
if ($surface->is_external) {
|
if ($surface->is_external && !$is_bridge) {
|
||||||
$filler = $layerm->config->external_fill_pattern;
|
$filler = $layerm->config->external_fill_pattern;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -19,12 +19,14 @@ sub fill_surface {
|
||||||
|
|
||||||
my $distance = scale($self->spacing) / $params{density};
|
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;
|
my $min = $bb->min_point;
|
||||||
$min->translate(
|
$min->translate(
|
||||||
-($bb->x_min % $distance),
|
-($bb->x_min % (2*$distance)),
|
||||||
-($bb->y_min % $distance),
|
-($bb->y_min % (2*$distance)),
|
||||||
);
|
);
|
||||||
$bb->merge_point($min);
|
$bb->merge_point($min);
|
||||||
}
|
}
|
||||||
|
@ -34,8 +36,8 @@ sub fill_surface {
|
||||||
makeGrid(
|
makeGrid(
|
||||||
scale($self->z),
|
scale($self->z),
|
||||||
$distance,
|
$distance,
|
||||||
ceil($size->x / $distance),
|
ceil($size->x / $distance) + 1,
|
||||||
ceil($size->y / $distance), #//
|
ceil($size->y / $distance) + 1, #//
|
||||||
(($self->layer_id / $surface->thickness_layers) % 2) + 1,
|
(($self->layer_id / $surface->thickness_layers) % 2) + 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -220,7 +220,7 @@ sub extrude_loop {
|
||||||
$point->rotate($angle, $first_segment->a);
|
$point->rotate($angle, $first_segment->a);
|
||||||
|
|
||||||
# generate the travel move
|
# 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;
|
return $gcode;
|
||||||
|
@ -328,6 +328,7 @@ sub _extrude_path {
|
||||||
return $gcode;
|
return $gcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# This method accepts $point in print coordinates.
|
||||||
sub travel_to {
|
sub travel_to {
|
||||||
my ($self, $point, $role, $comment) = @_;
|
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))
|
|| (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_line($travel))
|
||||||
) {
|
) {
|
||||||
# Just perform a straight travel move without any retraction.
|
# 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) {
|
} 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
|
# 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.
|
# 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 {
|
} else {
|
||||||
# If avoid_crossing_perimeters is disabled or the disable_once flag is set,
|
# If avoid_crossing_perimeters is disabled or the disable_once flag is set,
|
||||||
# perform a straight move with a retraction.
|
# 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); #))
|
$last_pos->translate(scale +$gcodegen->origin->x, scale +$gcodegen->origin->y); #))
|
||||||
my $standby_point = $last_pos->nearest_point($self->standby_points);
|
my $standby_point = $last_pos->nearest_point($self->standby_points);
|
||||||
$standby_point->translate(scale -$gcodegen->origin->x, scale -$gcodegen->origin->y); #))
|
$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) {
|
if ($gcodegen->config->standby_temperature_delta != 0) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ use File::Basename qw(basename);
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use Slic3r::GUI::AboutDialog;
|
use Slic3r::GUI::AboutDialog;
|
||||||
use Slic3r::GUI::BedShapeDialog;
|
use Slic3r::GUI::BedShapeDialog;
|
||||||
|
use Slic3r::GUI::BonjourBrowser;
|
||||||
use Slic3r::GUI::ConfigWizard;
|
use Slic3r::GUI::ConfigWizard;
|
||||||
use Slic3r::GUI::MainFrame;
|
use Slic3r::GUI::MainFrame;
|
||||||
use Slic3r::GUI::Notifier;
|
use Slic3r::GUI::Notifier;
|
||||||
|
@ -27,6 +28,7 @@ use Slic3r::GUI::SimpleTab;
|
||||||
use Slic3r::GUI::Tab;
|
use Slic3r::GUI::Tab;
|
||||||
|
|
||||||
our $have_OpenGL = eval "use Slic3r::GUI::PreviewCanvas; 1";
|
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
|
use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow
|
||||||
:filedialog);
|
:filedialog);
|
||||||
|
@ -228,7 +230,7 @@ sub have_version_check {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
|
||||||
# return an explicit 0
|
# 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 {
|
sub check_version {
|
||||||
|
|
51
lib/Slic3r/GUI/BonjourBrowser.pm
Normal file
51
lib/Slic3r/GUI/BonjourBrowser.pm
Normal 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;
|
|
@ -673,7 +673,11 @@ sub config {
|
||||||
sub check_unsaved_changes {
|
sub check_unsaved_changes {
|
||||||
my $self = shift;
|
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) {
|
if (@dirty) {
|
||||||
my $titles = join ', ', @dirty;
|
my $titles = join ', ', @dirty;
|
||||||
my $confirm = Wx::MessageDialog->new($self, "You have unsaved changes ($titles). Discard changes and continue anyway?",
|
my $confirm = Wx::MessageDialog->new($self, "You have unsaved changes ($titles). Discard changes and continue anyway?",
|
||||||
|
|
|
@ -83,7 +83,7 @@ sub append_line {
|
||||||
# if we have a single option with no sidetext just add it directly to the grid sizer
|
# if we have a single option with no sidetext just add it directly to the grid sizer
|
||||||
my @options = @{$line->get_options};
|
my @options = @{$line->get_options};
|
||||||
$self->_options->{$_->opt_id} = $_ for @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 $option = $options[0];
|
||||||
my $field = $self->_build_field($option);
|
my $field = $self->_build_field($option);
|
||||||
$grid_sizer->Add($field, 0, ($option->full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
|
$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);
|
$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 ($self, $option) = @_;
|
||||||
|
|
||||||
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||||
|
@ -125,11 +130,15 @@ sub append_single_option_line {
|
||||||
);
|
);
|
||||||
$option->label("");
|
$option->label("");
|
||||||
$line->append_option($option);
|
$line->append_option($option);
|
||||||
$self->append_line($line);
|
|
||||||
|
|
||||||
return $line;
|
return $line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub append_single_option_line {
|
||||||
|
my ($self, $option) = @_;
|
||||||
|
return $self->append_line($self->create_single_option_line($option));
|
||||||
|
}
|
||||||
|
|
||||||
sub _build_field {
|
sub _build_field {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($opt) = @_;
|
my ($opt) = @_;
|
||||||
|
@ -241,6 +250,7 @@ has 'label_tooltip' => (is => 'rw', default => sub { "" });
|
||||||
has 'sizer' => (is => 'rw');
|
has 'sizer' => (is => 'rw');
|
||||||
has 'widget' => (is => 'rw');
|
has 'widget' => (is => 'rw');
|
||||||
has '_options' => (is => 'ro', default => sub { [] });
|
has '_options' => (is => 'ro', default => sub { [] });
|
||||||
|
has '_extra_widgets' => (is => 'ro', default => sub { [] });
|
||||||
|
|
||||||
# this method accepts a Slic3r::GUI::OptionsGroup::Option object
|
# this method accepts a Slic3r::GUI::OptionsGroup::Option object
|
||||||
sub append_option {
|
sub append_option {
|
||||||
|
@ -248,11 +258,21 @@ sub append_option {
|
||||||
push @{$self->_options}, $option;
|
push @{$self->_options}, $option;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub append_widget {
|
||||||
|
my ($self, $widget) = @_;
|
||||||
|
push @{$self->_extra_widgets}, $widget;
|
||||||
|
}
|
||||||
|
|
||||||
sub get_options {
|
sub get_options {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
return [ @{$self->_options} ];
|
return [ @{$self->_options} ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub get_extra_widgets {
|
||||||
|
my ($self) = @_;
|
||||||
|
return [ @{$self->_extra_widgets} ];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
package Slic3r::GUI::OptionsGroup::Option;
|
package Slic3r::GUI::OptionsGroup::Option;
|
||||||
use Moo;
|
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 ($self, $opt_key, $opt_index) = @_;
|
||||||
|
|
||||||
my $option;
|
my $option;
|
||||||
|
@ -329,7 +349,12 @@ sub append_single_option_line {
|
||||||
} else {
|
} else {
|
||||||
$option = $self->get_option($opt_key, $opt_index);
|
$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 {
|
sub reload_config {
|
||||||
|
|
|
@ -347,6 +347,7 @@ sub BUILD {
|
||||||
$self->wxSizer($sizer);
|
$self->wxSizer($sizer);
|
||||||
|
|
||||||
my $field_size = Wx::Size->new(40, -1);
|
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->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));
|
$self->y_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[Y], wxDefaultPosition, $field_size));
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ sub new {
|
||||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||||
$self->{config} = Slic3r::Config->new_from_defaults(qw(
|
$self->{config} = Slic3r::Config->new_from_defaults(qw(
|
||||||
bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width
|
bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width
|
||||||
|
octoprint_host octoprint_apikey
|
||||||
));
|
));
|
||||||
$self->{model} = Slic3r::Model->new;
|
$self->{model} = Slic3r::Model->new;
|
||||||
$self->{print} = Slic3r::Print->new;
|
$self->{print} = Slic3r::Print->new;
|
||||||
|
@ -88,16 +89,7 @@ sub new {
|
||||||
};
|
};
|
||||||
my $on_instance_moved = sub {
|
my $on_instance_moved = sub {
|
||||||
my ($obj_idx, $instance_idx) = @_;
|
my ($obj_idx, $instance_idx) = @_;
|
||||||
|
|
||||||
$self->update;
|
$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
|
# Initialize 2D preview canvas
|
||||||
|
@ -181,9 +173,11 @@ sub new {
|
||||||
|
|
||||||
# right pane buttons
|
# right pane buttons
|
||||||
$self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", wxDefaultPosition, [-1, 30], wxBU_LEFT);
|
$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_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_gcode}->SetFont($Slic3r::GUI::small_font);
|
||||||
#$self->{btn_export_stl}->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) {
|
if ($Slic3r::GUI::have_button_icons) {
|
||||||
my %icons = qw(
|
my %icons = qw(
|
||||||
|
@ -192,6 +186,7 @@ sub new {
|
||||||
reset cross.png
|
reset cross.png
|
||||||
arrange bricks.png
|
arrange bricks.png
|
||||||
export_gcode cog_go.png
|
export_gcode cog_go.png
|
||||||
|
send_gcode arrow_up.png
|
||||||
export_stl brick_go.png
|
export_stl brick_go.png
|
||||||
|
|
||||||
increase add.png
|
increase add.png
|
||||||
|
@ -213,7 +208,11 @@ sub new {
|
||||||
$self->export_gcode;
|
$self->export_gcode;
|
||||||
Slic3r::thread_cleanup();
|
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}) {
|
if ($self->{htoolbar}) {
|
||||||
EVT_TOOL($self, TB_ADD, sub { $self->add; });
|
EVT_TOOL($self, TB_ADD, sub { $self->add; });
|
||||||
|
@ -355,6 +354,7 @@ sub new {
|
||||||
my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||||
$buttons_sizer->AddStretchSpacer(1);
|
$buttons_sizer->AddStretchSpacer(1);
|
||||||
$buttons_sizer->Add($self->{btn_export_stl}, 0, wxALIGN_RIGHT, 0);
|
$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);
|
$buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0);
|
||||||
|
|
||||||
my $right_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
my $right_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||||
|
@ -555,7 +555,6 @@ sub remove {
|
||||||
|
|
||||||
$self->select_object(undef);
|
$self->select_object(undef);
|
||||||
$self->update;
|
$self->update;
|
||||||
|
|
||||||
$self->schedule_background_process;
|
$self->schedule_background_process;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -666,10 +665,10 @@ sub rotate {
|
||||||
$model_object->update_bounding_box;
|
$model_object->update_bounding_box;
|
||||||
# update print and start background processing
|
# update print and start background processing
|
||||||
$self->{print}->add_model_object($model_object, $obj_idx);
|
$self->{print}->add_model_object($model_object, $obj_idx);
|
||||||
$self->schedule_background_process;
|
|
||||||
|
|
||||||
$self->selection_changed; # refresh info (size etc.)
|
$self->selection_changed; # refresh info (size etc.)
|
||||||
$self->update;
|
$self->update;
|
||||||
|
$self->schedule_background_process;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub flip {
|
sub flip {
|
||||||
|
@ -694,11 +693,10 @@ sub flip {
|
||||||
# update print and start background processing
|
# update print and start background processing
|
||||||
$self->stop_background_process;
|
$self->stop_background_process;
|
||||||
$self->{print}->add_model_object($model_object, $obj_idx);
|
$self->{print}->add_model_object($model_object, $obj_idx);
|
||||||
$self->schedule_background_process;
|
|
||||||
|
|
||||||
$self->selection_changed; # refresh info (size etc.)
|
$self->selection_changed; # refresh info (size etc.)
|
||||||
$self->update;
|
$self->update;
|
||||||
$self->refresh_canvases;
|
$self->schedule_background_process;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub changescale {
|
sub changescale {
|
||||||
|
@ -749,11 +747,10 @@ sub changescale {
|
||||||
# update print and start background processing
|
# update print and start background processing
|
||||||
$self->stop_background_process;
|
$self->stop_background_process;
|
||||||
$self->{print}->add_model_object($model_object, $obj_idx);
|
$self->{print}->add_model_object($model_object, $obj_idx);
|
||||||
$self->schedule_background_process;
|
|
||||||
|
|
||||||
$self->selection_changed(1); # refresh info (size, volume etc.)
|
$self->selection_changed(1); # refresh info (size, volume etc.)
|
||||||
$self->update;
|
$self->update;
|
||||||
$self->refresh_canvases;
|
$self->schedule_background_process;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub arrange {
|
sub arrange {
|
||||||
|
@ -769,17 +766,6 @@ sub arrange {
|
||||||
# when parts don't fit in print bed
|
# when parts don't fit in print bed
|
||||||
|
|
||||||
$self->update(1);
|
$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 {
|
sub split_object {
|
||||||
|
@ -788,19 +774,22 @@ sub split_object {
|
||||||
my ($obj_idx, $current_object) = $self->selected_object;
|
my ($obj_idx, $current_object) = $self->selected_object;
|
||||||
|
|
||||||
# we clone model object because split_object() adds the split volumes
|
# 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()
|
# into the same model object, thus causing duplicates when we call load_model_objects()
|
||||||
my $current_model_object = $self->{model}->clone->objects->[$obj_idx];
|
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.");
|
Slic3r::GUI::warning_catcher($self)->("The selected object can't be split because it contains more than one volume/material.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$self->stop_background_process;
|
$self->pause_background_process;
|
||||||
|
|
||||||
my @model_objects = @{$current_model_object->split_object};
|
my @model_objects = @{$current_model_object->split_object};
|
||||||
if (@model_objects == 1) {
|
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.");
|
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it contains only one part.");
|
||||||
|
$self->resume_background_process;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -944,7 +933,7 @@ sub resume_background_process {
|
||||||
}
|
}
|
||||||
|
|
||||||
sub export_gcode {
|
sub export_gcode {
|
||||||
my $self = shift;
|
my ($self, $output_file) = @_;
|
||||||
|
|
||||||
return if !@{$self->{objects}};
|
return if !@{$self->{objects}};
|
||||||
|
|
||||||
|
@ -976,14 +965,14 @@ sub export_gcode {
|
||||||
}
|
}
|
||||||
|
|
||||||
# select output file
|
# select output file
|
||||||
$self->{export_gcode_output_file} = $main::opt{output};
|
if ($output_file) {
|
||||||
{
|
$self->{export_gcode_output_file} = $self->{print}->expanded_output_filepath($output_file);
|
||||||
my $default_output_file = $self->{print}->expanded_output_filepath($self->{export_gcode_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)),
|
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);
|
basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE);
|
||||||
if ($dlg->ShowModal != wxID_OK) {
|
if ($dlg->ShowModal != wxID_OK) {
|
||||||
$dlg->Destroy;
|
$dlg->Destroy;
|
||||||
$self->{export_gcode_output_file} = undef;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($dlg->GetPath);
|
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($dlg->GetPath);
|
||||||
|
@ -1011,6 +1000,8 @@ sub export_gcode {
|
||||||
my $result = !Slic3r::GUI::catch_error($self);
|
my $result = !Slic3r::GUI::catch_error($self);
|
||||||
$self->on_export_completed($result);
|
$self->on_export_completed($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $self->{export_gcode_output_file};
|
||||||
}
|
}
|
||||||
|
|
||||||
# This gets called only if we have threads.
|
# This gets called only if we have threads.
|
||||||
|
@ -1072,14 +1063,52 @@ sub on_export_completed {
|
||||||
$self->{export_thread} = undef;
|
$self->{export_thread} = undef;
|
||||||
|
|
||||||
my $message;
|
my $message;
|
||||||
|
my $send_gcode = 0;
|
||||||
if ($result) {
|
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 {
|
} else {
|
||||||
$message = "Export failed";
|
$message = "Export failed";
|
||||||
}
|
}
|
||||||
$self->{export_gcode_output_file} = undef;
|
$self->{export_gcode_output_file} = undef;
|
||||||
$self->statusbar->SetStatusText($message);
|
$self->statusbar->SetStatusText($message);
|
||||||
wxTheApp->notify($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 {
|
sub export_stl {
|
||||||
|
@ -1159,7 +1188,6 @@ sub on_thumbnail_made {
|
||||||
my ($obj_idx) = @_;
|
my ($obj_idx) = @_;
|
||||||
|
|
||||||
$self->{objects}[$obj_idx]->transform_thumbnail($self->{model}, $obj_idx);
|
$self->{objects}[$obj_idx]->transform_thumbnail($self->{model}, $obj_idx);
|
||||||
$self->update;
|
|
||||||
$self->refresh_canvases;
|
$self->refresh_canvases;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1172,9 +1200,23 @@ sub update {
|
||||||
$self->{model}->center_instances_around_point($self->bed_centerf);
|
$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;
|
$self->refresh_canvases;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub on_model_instances_changed {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
sub on_extruders_change {
|
sub on_extruders_change {
|
||||||
my ($self, $num_extruders) = @_;
|
my ($self, $num_extruders) = @_;
|
||||||
|
|
||||||
|
@ -1208,6 +1250,13 @@ sub on_config_change {
|
||||||
$self->{canvas}->update_bed_size;
|
$self->{canvas}->update_bed_size;
|
||||||
$self->{canvas3D}->update_bed_size if $self->{canvas3D};
|
$self->{canvas3D}->update_bed_size if $self->{canvas3D};
|
||||||
$self->update;
|
$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 $have_objects = @{$self->{objects}} ? 1 : 0;
|
||||||
my $method = $have_objects ? 'Enable' : 'Disable';
|
my $method = $have_objects ? 'Enable' : 'Disable';
|
||||||
$self->{"btn_$_"}->$method
|
$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}) {
|
if ($self->{htoolbar}) {
|
||||||
$self->{htoolbar}->EnableTool($_, $have_objects)
|
$self->{htoolbar}->EnableTool($_, $have_objects)
|
||||||
|
|
|
@ -224,13 +224,7 @@ sub on_btn_load {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$self->reload_tree;
|
$self->_parts_changed;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub on_btn_delete {
|
sub on_btn_delete {
|
||||||
|
@ -250,9 +244,17 @@ sub on_btn_delete {
|
||||||
$self->{parts_changed} = 1;
|
$self->{parts_changed} = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$self->_parts_changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _parts_changed {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
$self->reload_tree;
|
$self->reload_tree;
|
||||||
if ($self->{canvas}) {
|
if ($self->{canvas}) {
|
||||||
|
$self->{canvas}->reset_objects;
|
||||||
$self->{canvas}->load_object($self->{model_object});
|
$self->{canvas}->load_object($self->{model_object});
|
||||||
|
$self->{canvas}->zoom_to_volumes;
|
||||||
$self->{canvas}->Render;
|
$self->{canvas}->Render;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use base 'Wx::Dialog';
|
||||||
|
|
||||||
sub new {
|
sub new {
|
||||||
my ($class, $parent) = @_;
|
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} = {};
|
$self->{values} = {};
|
||||||
|
|
||||||
my $optgroup;
|
my $optgroup;
|
||||||
|
@ -16,7 +16,7 @@ sub new {
|
||||||
my ($opt_id) = @_;
|
my ($opt_id) = @_;
|
||||||
$self->{values}{$opt_id} = $optgroup->get_value($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(
|
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
opt_id => 'mode',
|
opt_id => 'mode',
|
||||||
|
@ -26,6 +26,7 @@ sub new {
|
||||||
labels => ['Simple','Expert'],
|
labels => ['Simple','Expert'],
|
||||||
values => ['simple','expert'],
|
values => ['simple','expert'],
|
||||||
default => $Slic3r::GUI::Settings->{_}{mode},
|
default => $Slic3r::GUI::Settings->{_}{mode},
|
||||||
|
width => 100,
|
||||||
));
|
));
|
||||||
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
opt_id => 'version_check',
|
opt_id => 'version_check',
|
||||||
|
|
|
@ -151,9 +151,6 @@ sub mouse_event {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elsif ($e->Dragging && $e->LeftIsDown && defined($self->_drag_volume_idx)) {
|
} 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
|
# get new position at the same Z of the initial click point
|
||||||
my $mouse_ray = $self->mouse_ray($e->GetX, $e->GetY);
|
my $mouse_ray = $self->mouse_ray($e->GetX, $e->GetY);
|
||||||
my $cur_pos = $mouse_ray->intersect_plane($self->_drag_start_pos->z);
|
my $cur_pos = $mouse_ray->intersect_plane($self->_drag_start_pos->z);
|
||||||
|
@ -161,8 +158,14 @@ sub mouse_event {
|
||||||
# calculate the translation vector
|
# calculate the translation vector
|
||||||
my $vector = $self->_drag_start_pos->vector_to($cur_pos);
|
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
|
# 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->_drag_start_pos($cur_pos);
|
||||||
$self->_dragged(1);
|
$self->_dragged(1);
|
||||||
$self->Refresh;
|
$self->Refresh;
|
||||||
|
@ -346,6 +349,7 @@ sub load_object {
|
||||||
# sort volumes: non-modifiers first
|
# sort volumes: non-modifiers first
|
||||||
my @volumes = sort { ($a->modifier // 0) <=> ($b->modifier // 0) } @{$object->volumes};
|
my @volumes = sort { ($a->modifier // 0) <=> ($b->modifier // 0) } @{$object->volumes};
|
||||||
my @volumes_idx = ();
|
my @volumes_idx = ();
|
||||||
|
my $group_id = $#{$self->volumes} + 1;
|
||||||
foreach my $volume (@volumes) {
|
foreach my $volume (@volumes) {
|
||||||
my @instance_idxs = $all_instances ? (0..$#{$object->instances}) : (0);
|
my @instance_idxs = $all_instances ? (0..$#{$object->instances}) : (0);
|
||||||
foreach my $instance_idx (@instance_idxs) {
|
foreach my $instance_idx (@instance_idxs) {
|
||||||
|
@ -363,6 +367,7 @@ sub load_object {
|
||||||
my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ];
|
my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ];
|
||||||
push @$color, $volume->modifier ? 0.5 : 1;
|
push @$color, $volume->modifier ? 0.5 : 1;
|
||||||
push @{$self->volumes}, my $v = Slic3r::GUI::PreviewCanvas::Volume->new(
|
push @{$self->volumes}, my $v = Slic3r::GUI::PreviewCanvas::Volume->new(
|
||||||
|
group_id => $group_id,
|
||||||
instance_idx => $instance_idx,
|
instance_idx => $instance_idx,
|
||||||
mesh => $mesh,
|
mesh => $mesh,
|
||||||
color => $color,
|
color => $color,
|
||||||
|
@ -888,6 +893,7 @@ use Moo;
|
||||||
|
|
||||||
has 'mesh' => (is => 'ro', required => 1);
|
has 'mesh' => (is => 'ro', required => 1);
|
||||||
has 'color' => (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 'instance_idx' => (is => 'ro', default => sub { 0 });
|
||||||
has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf3->new(0,0,0) });
|
has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf3->new(0,0,0) });
|
||||||
has 'verts' => (is => 'rw');
|
has 'verts' => (is => 'rw');
|
||||||
|
|
|
@ -923,6 +923,7 @@ sub build {
|
||||||
$self->init_config_options(qw(
|
$self->init_config_options(qw(
|
||||||
bed_shape z_offset
|
bed_shape z_offset
|
||||||
gcode_flavor use_relative_e_distances
|
gcode_flavor use_relative_e_distances
|
||||||
|
octoprint_host octoprint_apikey
|
||||||
use_firmware_retraction pressure_advance vibration_limit
|
use_firmware_retraction pressure_advance vibration_limit
|
||||||
start_gcode end_gcode layer_gcode toolchange_gcode
|
start_gcode end_gcode layer_gcode toolchange_gcode
|
||||||
nozzle_diameter extruder_offset
|
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');
|
my $optgroup = $page->new_optgroup('Advanced');
|
||||||
$optgroup->append_single_option_line('use_firmware_retraction');
|
$optgroup->append_single_option_line('use_firmware_retraction');
|
||||||
|
@ -1129,6 +1169,8 @@ sub _update {
|
||||||
|
|
||||||
my $config = $self->{config};
|
my $config = $self->{config};
|
||||||
|
|
||||||
|
$self->get_field('octoprint_apikey')->toggle($config->get('octoprint_host'));
|
||||||
|
|
||||||
my $have_multiple_extruders = $self->{extruders_count} > 1;
|
my $have_multiple_extruders = $self->{extruders_count} > 1;
|
||||||
$self->get_field('toolchange_gcode')->toggle($have_multiple_extruders);
|
$self->get_field('toolchange_gcode')->toggle($have_multiple_extruders);
|
||||||
|
|
||||||
|
@ -1319,8 +1361,8 @@ sub config {
|
||||||
}
|
}
|
||||||
|
|
||||||
# apply preset values on top of defaults
|
# apply preset values on top of defaults
|
||||||
|
my $config = Slic3r::Config->new_from_defaults(@$keys);
|
||||||
my $external_config = Slic3r::Config->load($self->file);
|
my $external_config = Slic3r::Config->load($self->file);
|
||||||
my $config = Slic3r::Config->new;
|
|
||||||
$config->set($_, $external_config->get($_))
|
$config->set($_, $external_config->get($_))
|
||||||
for grep $external_config->has($_), @$keys;
|
for grep $external_config->has($_), @$keys;
|
||||||
|
|
||||||
|
|
|
@ -219,7 +219,7 @@ sub make_skirt {
|
||||||
# $skirt_height_z in this case is the highest possible skirt height for safety.
|
# $skirt_height_z in this case is the highest possible skirt height for safety.
|
||||||
my $skirt_height_z = -1;
|
my $skirt_height_z = -1;
|
||||||
foreach my $object (@{$self->objects}) {
|
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})
|
? scalar(@{$object->layers})
|
||||||
: min($self->config->skirt_height, scalar(@{$object->layers}));
|
: min($self->config->skirt_height, scalar(@{$object->layers}));
|
||||||
|
|
||||||
|
|
|
@ -147,15 +147,26 @@ sub export {
|
||||||
my $outer_skirt = convex_hull(\@skirt_points);
|
my $outer_skirt = convex_hull(\@skirt_points);
|
||||||
my @skirts = ();
|
my @skirts = ();
|
||||||
foreach my $extruder_id (@{$self->print->extruders}) {
|
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;
|
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 ]);
|
my $convex_hull = convex_hull([ map @$_, @skirts ]);
|
||||||
|
|
||||||
$gcodegen->ooze_prevention->enable(1);
|
$gcodegen->ooze_prevention->enable(1);
|
||||||
$gcodegen->ooze_prevention->standby_points(
|
$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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -442,23 +442,25 @@ sub generate_bottom_interface_layers {
|
||||||
my $z = $support_z->[$layer_id];
|
my $z = $support_z->[$layer_id];
|
||||||
next unless $z > $top_z;
|
next unless $z > $top_z;
|
||||||
|
|
||||||
# get the support material area that should be considered interface
|
if ($base->{$layer_id}) {
|
||||||
my $interface_area = intersection(
|
# get the support material area that should be considered interface
|
||||||
$base->{$layer_id},
|
my $interface_area = intersection(
|
||||||
$this,
|
$base->{$layer_id},
|
||||||
);
|
$this,
|
||||||
|
);
|
||||||
|
|
||||||
# discard too small areas
|
# discard too small areas
|
||||||
$interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ];
|
$interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ];
|
||||||
|
|
||||||
# subtract new interface area from base
|
# subtract new interface area from base
|
||||||
$base->{$layer_id} = diff(
|
$base->{$layer_id} = diff(
|
||||||
$base->{$layer_id},
|
$base->{$layer_id},
|
||||||
$interface_area,
|
$interface_area,
|
||||||
);
|
);
|
||||||
|
|
||||||
# add new interface area to interface
|
# add new interface area to interface
|
||||||
push @{$interface->{$layer_id}}, @$interface_area;
|
push @{$interface->{$layer_id}}, @$interface_area;
|
||||||
|
}
|
||||||
|
|
||||||
$interface_layers++;
|
$interface_layers++;
|
||||||
last if $interface_layers == $self->object_config->support_material_interface_layers;
|
last if $interface_layers == $self->object_config->support_material_interface_layers;
|
||||||
|
|
39
t/multi.t
39
t/multi.t
|
@ -17,23 +17,25 @@ use Slic3r::Test;
|
||||||
my $config = Slic3r::Config->new_from_defaults;
|
my $config = Slic3r::Config->new_from_defaults;
|
||||||
$config->set('raft_layers', 2);
|
$config->set('raft_layers', 2);
|
||||||
$config->set('infill_extruder', 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('ooze_prevention', 1);
|
||||||
$config->set('extruder_offset', [ [0,0], [20,0], [0,20] ]);
|
$config->set('extruder_offset', [ [0,0], [20,0], [0,20], [20,20] ]);
|
||||||
$config->set('temperature', [200, 180, 170]);
|
$config->set('temperature', [200, 180, 170, 160]);
|
||||||
$config->set('first_layer_temperature', [206, 186, 166]);
|
$config->set('first_layer_temperature', [206, 186, 166, 156]);
|
||||||
$config->set('toolchange_gcode', ';toolchange'); # test that it doesn't crash when this is supplied
|
$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 $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||||
|
|
||||||
my $tool = undef;
|
my $tool = undef;
|
||||||
my @tool_temp = (0,0,0);
|
my @tool_temp = (0,0,0,0);
|
||||||
my @toolchange_points = ();
|
my @toolchange_points = ();
|
||||||
my @extrusion_points = ();
|
my @extrusion_points = ();
|
||||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||||
my ($self, $cmd, $args, $info) = @_;
|
my ($self, $cmd, $args, $info) = @_;
|
||||||
|
|
||||||
if ($cmd =~ /^T(\d+)/) {
|
if ($cmd =~ /^T(\d+)/) {
|
||||||
|
# ignore initial toolchange
|
||||||
if (defined $tool) {
|
if (defined $tool) {
|
||||||
my $expected_temp = $self->Z == ($config->get_value('first_layer_height') + $config->z_offset)
|
my $expected_temp = $self->Z == ($config->get_value('first_layer_height') + $config->z_offset)
|
||||||
? $config->first_layer_temperature->[$tool]
|
? $config->first_layer_temperature->[$tool]
|
||||||
|
@ -41,8 +43,8 @@ use Slic3r::Test;
|
||||||
die 'standby temperature was not set before toolchange'
|
die 'standby temperature was not set before toolchange'
|
||||||
if $tool_temp[$tool] != $expected_temp + $config->standby_temperature_delta;
|
if $tool_temp[$tool] != $expected_temp + $config->standby_temperature_delta;
|
||||||
|
|
||||||
# ignore initial toolchange
|
push @toolchange_points, my $point = Slic3r::Point->new_scale($self->X, $self->Y);
|
||||||
push @toolchange_points, Slic3r::Point->new_scale($self->X, $self->Y);
|
$point->translate(map +scale($_), @{ $config->extruder_offset->[$tool] });
|
||||||
}
|
}
|
||||||
$tool = $1;
|
$tool = $1;
|
||||||
} elsif ($cmd eq 'M104' || $cmd eq 'M109') {
|
} elsif ($cmd eq 'M104' || $cmd eq 'M109') {
|
||||||
|
@ -54,11 +56,30 @@ use Slic3r::Test;
|
||||||
$tool_temp[$t] = $args->{S};
|
$tool_temp[$t] = $args->{S};
|
||||||
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
|
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
|
||||||
push @extrusion_points, my $point = Slic3r::Point->new_scale($args->{X}, $args->{Y});
|
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);
|
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
|
# offset the skirt by the maximum displacement between extruders plus a safety extra margin
|
||||||
my $delta = scale(20 * sqrt(2) + 1);
|
my $delta = scale(20 * sqrt(2) + 1);
|
||||||
|
|
BIN
var/zoom.png
Executable file
BIN
var/zoom.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 692 B |
|
@ -13,7 +13,7 @@ Model::Model(const Model &other)
|
||||||
// copy objects
|
// copy objects
|
||||||
this->objects.reserve(other.objects.size());
|
this->objects.reserve(other.objects.size());
|
||||||
for (ModelObjectPtrs::const_iterator i = other.objects.begin(); i != other.objects.end(); ++i)
|
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)
|
Model& Model::operator= (Model other)
|
||||||
|
@ -618,6 +618,7 @@ ModelObject::split(ModelObjectPtrs* new_objects)
|
||||||
new_volume->material_id(volume->material_id());
|
new_volume->material_id(volume->material_id());
|
||||||
|
|
||||||
new_objects->push_back(new_object);
|
new_objects->push_back(new_object);
|
||||||
|
delete *mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -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
|
void
|
||||||
Print::clear_regions()
|
Print::clear_regions()
|
||||||
{
|
{
|
||||||
|
|
|
@ -174,6 +174,7 @@ class Print
|
||||||
PrintObject* get_object(size_t idx);
|
PrintObject* get_object(size_t idx);
|
||||||
void delete_object(size_t idx);
|
void delete_object(size_t idx);
|
||||||
void reload_object(size_t idx);
|
void reload_object(size_t idx);
|
||||||
|
bool reload_model_instances();
|
||||||
|
|
||||||
// methods for handling regions
|
// methods for handling regions
|
||||||
PrintRegion* get_region(size_t idx);
|
PrintRegion* get_region(size_t idx);
|
||||||
|
|
|
@ -498,6 +498,16 @@ PrintConfigDef::build_def() {
|
||||||
Options["nozzle_diameter"].sidetext = "mm";
|
Options["nozzle_diameter"].sidetext = "mm";
|
||||||
Options["nozzle_diameter"].cli = "nozzle-diameter=f@";
|
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"].type = coBool;
|
||||||
Options["only_retract_when_crossing_perimeters"].label = "Only retract when crossing perimeters";
|
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).";
|
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).";
|
||||||
|
|
|
@ -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:
|
public:
|
||||||
ConfigOption* option(const t_config_option_key opt_key, bool create = false) {
|
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 = PrintObjectConfig::option(opt_key, create)) != NULL) return opt;
|
||||||
if ((opt = PrintRegionConfig::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 = PrintConfig::option(opt_key, create)) != NULL) return opt;
|
||||||
|
if ((opt = HostConfig::option(opt_key, create)) != NULL) return opt;
|
||||||
return NULL;
|
return NULL;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#define SLIC3R_VERSION "1.2.2"
|
#define SLIC3R_VERSION "1.2.4"
|
||||||
|
|
||||||
#define EPSILON 1e-4
|
#define EPSILON 1e-4
|
||||||
#define SCALING_FACTOR 0.000001
|
#define SCALING_FACTOR 0.000001
|
||||||
|
|
|
@ -42,9 +42,9 @@ template <class T>
|
||||||
class Ref {
|
class Ref {
|
||||||
T* val;
|
T* val;
|
||||||
public:
|
public:
|
||||||
Ref() {}
|
Ref() : val(NULL) {}
|
||||||
Ref(T* t) : val(t) {}
|
Ref(T* t) : val(t) {}
|
||||||
operator T*() const {return val; }
|
operator T*() const { return val; }
|
||||||
static const char* CLASS() { return ClassTraits<T>::name_ref; }
|
static const char* CLASS() { return ClassTraits<T>::name_ref; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -52,10 +52,10 @@ template <class T>
|
||||||
class Clone {
|
class Clone {
|
||||||
T* val;
|
T* val;
|
||||||
public:
|
public:
|
||||||
Clone() : val() {}
|
Clone() : val(NULL) {}
|
||||||
Clone(T* t) : val(new T(*t)) {}
|
Clone(T* t) : val(new T(*t)) {}
|
||||||
Clone(const 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; }
|
static const char* CLASS() { return ClassTraits<T>::name; }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -143,6 +143,7 @@ _constant()
|
||||||
Ref<PrintObject> get_object(int idx);
|
Ref<PrintObject> get_object(int idx);
|
||||||
void delete_object(int idx);
|
void delete_object(int idx);
|
||||||
void reload_object(int idx);
|
void reload_object(int idx);
|
||||||
|
bool reload_model_instances();
|
||||||
size_t object_count()
|
size_t object_count()
|
||||||
%code%{ RETVAL = THIS->objects.size(); %};
|
%code%{ RETVAL = THIS->objects.size(); %};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue