Merge branch 'master' into wipe_tower_improvements

This commit is contained in:
bubnikv 2017-11-30 10:33:52 +01:00
commit 3813402aa3
95 changed files with 5177 additions and 2981 deletions

View File

@ -10,6 +10,8 @@ my %prereqs = qw(
Devel::CheckLib 0 Devel::CheckLib 0
ExtUtils::MakeMaker 6.80 ExtUtils::MakeMaker 6.80
ExtUtils::ParseXS 3.22 ExtUtils::ParseXS 3.22
ExtUtils::XSpp 0
ExtUtils::Typemaps 0
File::Basename 0 File::Basename 0
File::Spec 0 File::Spec 0
Getopt::Long 0 Getopt::Long 0

View File

@ -23,25 +23,14 @@ sub debugf {
our $loglevel = 0; our $loglevel = 0;
# load threads before Moo as required by it # load threads before Moo as required by it
our $have_threads;
BEGIN { BEGIN {
# Test, whether the perl was compiled with ithreads support and ithreads actually work. # Test, whether the perl was compiled with ithreads support and ithreads actually work.
use Config; use Config;
$have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1";
warn "threads.pm >= 1.96 is required, please update\n" if $have_threads && $threads::VERSION < 1.96;
### temporarily disable threads if using the broken Moo version
use Moo; use Moo;
$have_threads = 0 if $Moo::VERSION == 1.003000; my $have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1";
die "Slic3r Prusa Edition requires working Perl threads.\n" if ! $have_threads;
# Disable multi threading completely by an environment value. die "threads.pm >= 1.96 is required, please update\n" if $threads::VERSION < 1.96;
# This is useful for debugging as the Perl debugger does not work die "Perl threading is broken with this Moo version: " . $Moo::VERSION . "\n" if $Moo::VERSION == 1.003000;
# in multi-threaded context at all.
# A good interactive perl debugger is the ActiveState Komodo IDE
# or the EPIC http://www.epic-ide.org/
$have_threads = 0 if (defined($ENV{'SLIC3R_SINGLETHREADED'}) && $ENV{'SLIC3R_SINGLETHREADED'} == 1);
print "Threading disabled\n" if !$have_threads;
$debug = 1 if (defined($ENV{'SLIC3R_DEBUGOUT'}) && $ENV{'SLIC3R_DEBUGOUT'} == 1); $debug = 1 if (defined($ENV{'SLIC3R_DEBUGOUT'}) && $ENV{'SLIC3R_DEBUGOUT'} == 1);
print "Debugging output enabled\n" if $debug; print "Debugging output enabled\n" if $debug;
} }
@ -50,8 +39,9 @@ warn "Running Slic3r under Perl 5.16 is neither supported nor recommended\n"
if $^V == v5.16; if $^V == v5.16;
use FindBin; use FindBin;
# Path to the images.
our $var = sub { decode_path($FindBin::Bin) . "/var/" . $_[0] }; # Let the XS module know where the GUI resources reside.
set_var_dir(decode_path($FindBin::Bin) . "/var");
use Moo 1.003001; use Moo 1.003001;
@ -163,6 +153,7 @@ sub thread_cleanup {
*Slic3r::Surface::Collection::DESTROY = sub {}; *Slic3r::Surface::Collection::DESTROY = sub {};
*Slic3r::Print::SupportMaterial2::DESTROY = sub {}; *Slic3r::Print::SupportMaterial2::DESTROY = sub {};
*Slic3r::TriangleMesh::DESTROY = sub {}; *Slic3r::TriangleMesh::DESTROY = sub {};
*Slic3r::GUI::AppConfig::DESTROY = sub {};
*Slic3r::GUI::PresetBundle::DESTROY = sub {}; *Slic3r::GUI::PresetBundle::DESTROY = sub {};
return undef; # this prevents a "Scalars leaked" warning return undef; # this prevents a "Scalars leaked" warning
} }

View File

@ -8,44 +8,21 @@ use utf8;
use List::Util qw(first max); use List::Util qw(first max);
# cemetery of old config settings
our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration
adjust_overhang_flow standby_temperature scale rotate duplicate duplicate_grid
rotate scale duplicate_grid start_perimeters_at_concave_points start_perimeters_at_non_overhang
randomize_start seal_position bed_size print_center g0 vibration_limit gcode_arcs pressure_advance);
# C++ Slic3r::PrintConfigDef exported as a Perl hash of hashes. # C++ Slic3r::PrintConfigDef exported as a Perl hash of hashes.
# The C++ counterpart is a constant singleton. # The C++ counterpart is a constant singleton.
our $Options = print_config_def(); our $Options = print_config_def();
# overwrite the hard-coded readonly value (this information is not available in XS) # Generate accessors.
$Options->{threads}{readonly} = !$Slic3r::have_threads;
# generate accessors
{ {
no strict 'refs'; no strict 'refs';
for my $opt_key (keys %$Options) { for my $opt_key (keys %$Options) {
*{$opt_key} = sub { $_[0]->get($opt_key) }; *{$opt_key} = sub {
#print "Slic3r::Config::accessor $opt_key\n";
$_[0]->get($opt_key)
};
} }
} }
# Fill in the underlying C++ Slic3r::DynamicPrintConfig with the content of the defaults
# provided by the C++ class Slic3r::FullPrintConfig.
# Used by the UI.
sub new_from_defaults {
my ($class, @opt_keys) = @_;
my $self = $class->new;
# Instantiating the C++ class Slic3r::FullPrintConfig.
my $defaults = Slic3r::Config::Full->new;
if (@opt_keys) {
$self->set($_, $defaults->get($_))
for grep $defaults->has($_), @opt_keys;
} else {
$self->apply_static($defaults);
}
return $self;
}
# From command line parameters, used by slic3r.pl # From command line parameters, used by slic3r.pl
sub new_from_cli { sub new_from_cli {
my $class = shift; my $class = shift;
@ -87,273 +64,6 @@ sub new_from_cli {
return $self; return $self;
} }
sub merge {
my $class = shift;
my $config = $class->new;
$config->apply($_) for @_;
return $config;
}
# Load a flat ini file without a category into the underlying C++ Slic3r::DynamicConfig class,
# convert legacy configuration names.
sub load {
my ($class, $file) = @_;
# Instead of using the /i modifier for case-insensitive matching, the case insensitivity is expressed
# explicitely to avoid having to bundle the UTF8 Perl library.
if ($file =~ /\.[gG][cC][oO][dD][eE]/ || $file =~ /\.[gG]/) {
my $config = $class->new;
$config->_load_from_gcode($file);
return $config;
} else {
my $config = $class->new;
$config->_load($file);
return $config;
}
}
# Deserialize a perl hash into the underlying C++ Slic3r::DynamicConfig class,
# convert legacy configuration names.
# Used to load a config bundle.
sub load_ini_hash {
my ($class, $ini_hash) = @_;
my $config = $class->new;
$config->set_deserialize($_, $ini_hash->{$_}) for keys %$ini_hash;
return $config;
}
sub clone {
my $self = shift;
my $new = (ref $self)->new;
$new->apply($self);
return $new;
}
sub get_value {
my ($self, $opt_key) = @_;
return $Options->{$opt_key}{ratio_over}
? $self->get_abs_value($opt_key)
: $self->get($opt_key);
}
# Create a hash of hashes from the underlying C++ Slic3r::DynamicPrintConfig.
# The first hash key is '_' meaning no category.
# Used to create a config bundle.
sub as_ini {
my ($self) = @_;
my $ini = { _ => {} };
foreach my $opt_key (sort @{$self->get_keys}) {
next if $Options->{$opt_key}{shortcut};
$ini->{_}{$opt_key} = $self->serialize($opt_key);
}
return $ini;
}
# this method is idempotent by design and only applies to ::DynamicConfig or ::Full
# objects because it performs cross checks
sub validate {
my $self = shift;
# -j, --threads
die "Invalid value for --threads\n"
if $self->threads < 1;
# --layer-height
die "Invalid value for --layer-height\n"
if $self->layer_height <= 0;
die "--layer-height must be a multiple of print resolution\n"
if $self->layer_height / &Slic3r::SCALING_FACTOR % 1 != 0;
# --first-layer-height
die "Invalid value for --first-layer-height\n"
if $self->first_layer_height !~ /^(?:\d*(?:\.\d+)?)%?$/;
die "Invalid value for --first-layer-height\n"
if $self->get_value('first_layer_height') <= 0;
# --filament-diameter
die "Invalid value for --filament-diameter\n"
if grep $_ < 1, @{$self->filament_diameter};
# --nozzle-diameter
die "Invalid value for --nozzle-diameter\n"
if grep $_ < 0, @{$self->nozzle_diameter};
# --perimeters
die "Invalid value for --perimeters\n"
if $self->perimeters < 0;
# --solid-layers
die "Invalid value for --solid-layers\n" if defined $self->solid_layers && $self->solid_layers < 0;
die "Invalid value for --top-solid-layers\n" if $self->top_solid_layers < 0;
die "Invalid value for --bottom-solid-layers\n" if $self->bottom_solid_layers < 0;
# --gcode-flavor
die "Invalid value for --gcode-flavor\n"
if !first { $_ eq $self->gcode_flavor } @{$Options->{gcode_flavor}{values}};
die "--use-firmware-retraction is only supported by Marlin, Smoothie, Repetier and Machinekit firmware\n"
if $self->use_firmware_retraction && $self->gcode_flavor ne 'smoothie'
&& $self->gcode_flavor ne 'reprap'
&& $self->gcode_flavor ne 'machinekit'
&& $self->gcode_flavor ne 'repetier';
die "--use-firmware-retraction is not compatible with --wipe\n"
if $self->use_firmware_retraction && first {$_} @{$self->wipe};
# --fill-pattern
die "Invalid value for --fill-pattern\n"
if !first { $_ eq $self->fill_pattern } @{$Options->{fill_pattern}{values}};
# --external-fill-pattern
die "Invalid value for --external-fill-pattern\n"
if !first { $_ eq $self->external_fill_pattern } @{$Options->{external_fill_pattern}{values}};
# --fill-density
die "The selected fill pattern is not supposed to work at 100% density\n"
if $self->fill_density == 100
&& !first { $_ eq $self->fill_pattern } @{$Options->{external_fill_pattern}{values}};
# --infill-every-layers
die "Invalid value for --infill-every-layers\n"
if $self->infill_every_layers !~ /^\d+$/ || $self->infill_every_layers < 1;
# --skirt-height
die "Invalid value for --skirt-height\n"
if $self->skirt_height < -1; # -1 means as tall as the object
# --bridge-flow-ratio
die "Invalid value for --bridge-flow-ratio\n"
if $self->bridge_flow_ratio <= 0;
# extruder clearance
die "Invalid value for --extruder-clearance-radius\n"
if $self->extruder_clearance_radius <= 0;
die "Invalid value for --extruder-clearance-height\n"
if $self->extruder_clearance_height <= 0;
# --extrusion-multiplier
die "Invalid value for --extrusion-multiplier\n"
if defined first { $_ <= 0 } @{$self->extrusion_multiplier};
# --default-acceleration
die "Invalid zero value for --default-acceleration when using other acceleration settings\n"
if ($self->perimeter_acceleration || $self->infill_acceleration || $self->bridge_acceleration || $self->first_layer_acceleration)
&& !$self->default_acceleration;
# --spiral-vase
if ($self->spiral_vase) {
# Note that we might want to have more than one perimeter on the bottom
# solid layers.
die "Can't make more than one perimeter when spiral vase mode is enabled\n"
if $self->perimeters > 1;
die "Can't make less than one perimeter when spiral vase mode is enabled\n"
if $self->perimeters < 1;
die "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0\n"
if $self->fill_density > 0;
die "Spiral vase mode is not compatible with top solid layers\n"
if $self->top_solid_layers > 0;
die "Spiral vase mode is not compatible with support material\n"
if $self->support_material || $self->support_material_enforce_layers > 0;
}
# extrusion widths
{
my $max_nozzle_diameter = max(@{ $self->nozzle_diameter });
die "Invalid extrusion width (too large)\n"
if defined first { $_ > 10 * $max_nozzle_diameter }
map $self->get_abs_value_over("${_}_extrusion_width", $max_nozzle_diameter),
qw(perimeter infill solid_infill top_infill support_material first_layer);
}
# general validation, quick and dirty
foreach my $opt_key (@{$self->get_keys}) {
my $opt = $Options->{$opt_key};
next unless defined $self->$opt_key;
next unless defined $opt->{cli} && $opt->{cli} =~ /=(.+)$/;
my $type = $1;
my @values = ();
if ($type =~ s/\@$//) {
die "Invalid value for $opt_key\n" if ref($self->$opt_key) ne 'ARRAY';
@values = @{ $self->$opt_key };
} else {
@values = ($self->$opt_key);
}
foreach my $value (@values) {
if ($type eq 'i' || $type eq 'f' || $opt->{type} eq 'percent') {
$value =~ s/%$// if $opt->{type} eq 'percent';
die "Invalid value for $opt_key\n"
if ($type eq 'i' && $value !~ /^-?\d+$/)
|| (($type eq 'f' || $opt->{type} eq 'percent') && $value !~ /^-?(?:\d+|\d*\.\d+)$/)
|| (defined $opt->{min} && $value < $opt->{min})
|| (defined $opt->{max} && $value > $opt->{max});
} elsif ($type eq 's' && $opt->{type} eq 'select') {
die "Invalid value for $opt_key\n"
unless first { $_ eq $value } @{ $opt->{values} };
}
}
}
return 1;
}
# CLASS METHODS:
# Write a "Windows" style ini file with categories enclosed in squre brackets.
# Used by config-bundle-to-config.pl and to save slic3r.ini.
sub write_ini {
my $class = shift;
my ($file, $ini) = @_;
Slic3r::open(\my $fh, '>', $file);
binmode $fh, ':utf8';
my $localtime = localtime;
printf $fh "# generated by Slic3r $Slic3r::VERSION on %s\n", "$localtime";
# make sure the _ category is the first one written
foreach my $category (sort { ($a eq '_') ? -1 : ($a cmp $b) } keys %$ini) {
printf $fh "\n[%s]\n", $category if $category ne '_';
foreach my $key (sort keys %{$ini->{$category}}) {
printf $fh "%s = %s\n", $key, $ini->{$category}{$key};
}
}
close $fh;
}
# Parse a "Windows" style ini file with categories enclosed in squre brackets.
# Returns a hash of hashes over strings.
# {category}{name}=value
# Non-categorized entries are stored under a category '_'.
# Used by config-bundle-to-config.pl and to read slic3r.ini.
sub read_ini {
my $class = shift;
my ($file) = @_;
local $/ = "\n";
Slic3r::open(\my $fh, '<', $file)
or die "Unable to open $file: $!\n";
binmode $fh, ':utf8';
my $ini = { _ => {} };
my $category = '_';
while (<$fh>) {
s/\R+$//;
next if /^\s+/;
next if /^$/;
next if /^\s*#/;
if (/^\[(.+?)\]$/) {
$category = $1;
next;
}
/^(\w+) *= *(.*)/ or die "Unreadable configuration file (invalid data at line $.)\n";
$ini->{$category}{$1} = $2;
}
close $fh;
return $ini;
}
package Slic3r::Config::Static; package Slic3r::Config::Static;
use parent 'Slic3r::Config'; use parent 'Slic3r::Config';

View File

@ -52,27 +52,12 @@ use constant FILE_WILDCARDS => {
}; };
use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf prusa)}; use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf prusa)};
# Datadir provided on the command line.
our $datadir; our $datadir;
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
our $no_controller;
our $no_plater; our $no_plater;
our $autosave;
our @cb; our @cb;
our $Settings = {
_ => {
version_check => 1,
autocenter => 1,
# Disable background processing by default as it is not stable.
background_processing => 0,
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
# By default, Prusa has the controller hidden.
no_controller => 1,
# If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available.
no_defaults => 1,
},
};
our $small_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); our $small_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$small_font->SetPointSize(11) if &Wx::wxMAC; $small_font->SetPointSize(11) if &Wx::wxMAC;
our $small_bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); our $small_bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
@ -82,10 +67,6 @@ our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$medium_font->SetPointSize(12); $medium_font->SetPointSize(12);
our $grey = Wx::Colour->new(200,200,200); our $grey = Wx::Colour->new(200,200,200);
#our $VERSION_CHECK_EVENT : shared = Wx::NewEventType;
our $DLP_projection_screen;
sub OnInit { sub OnInit {
my ($self) = @_; my ($self) = @_;
@ -93,123 +74,67 @@ sub OnInit {
$self->SetAppDisplayName('Slic3r Prusa Edition'); $self->SetAppDisplayName('Slic3r Prusa Edition');
Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION; Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION;
$self->{notifier} = Slic3r::GUI::Notifier->new; # Set the Slic3r data directory at the Slic3r XS module.
$self->{preset_bundle} = Slic3r::GUI::PresetBundle->new;
# locate or create data directory
# Unix: ~/.Slic3r # Unix: ~/.Slic3r
# Windows: "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" # Windows: "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
# Mac: "~/Library/Application Support/Slic3r" # Mac: "~/Library/Application Support/Slic3r"
$datadir ||= Wx::StandardPaths::Get->GetUserDataDir; Slic3r::set_data_dir($datadir || Wx::StandardPaths::Get->GetUserDataDir);
my $enc_datadir = Slic3r::encode_path($datadir);
Slic3r::debugf "Data directory: %s\n", $datadir;
# just checking for existence of $datadir is not enough: it may be an empty directory $self->{notifier} = Slic3r::GUI::Notifier->new;
$self->{app_config} = Slic3r::GUI::AppConfig->new;
$self->{preset_bundle} = Slic3r::GUI::PresetBundle->new;
# just checking for existence of Slic3r::data_dir is not enough: it may be an empty directory
# supplied as argument to --datadir; in that case we should still run the wizard # supplied as argument to --datadir; in that case we should still run the wizard
my $run_wizard = (-d $enc_datadir && -e "$enc_datadir/slic3r.ini") ? 0 : 1; eval { $self->{preset_bundle}->setup_directories() };
foreach my $dir ($enc_datadir, "$enc_datadir/print", "$enc_datadir/filament", "$enc_datadir/printer") { if ($@) {
next if -d $dir; warn $@ . "\n";
if (!mkdir $dir) { fatal_error(undef, $@);
my $error = "Slic3r was unable to create its data directory at $dir ($!).";
warn "$error\n";
fatal_error(undef, $error);
} }
} my $run_wizard = ! $self->{app_config}->exists;
# load settings # load settings
my $last_version; $self->{app_config}->load if ! $run_wizard;
if (-f "$enc_datadir/slic3r.ini") { $self->{app_config}->set('version', $Slic3r::VERSION);
my $ini = eval { Slic3r::Config->read_ini("$datadir/slic3r.ini") }; $self->{app_config}->save;
$Settings = $ini if $ini;
$last_version = $Settings->{_}{version}; # Suppress the '- default -' presets.
$Settings->{_}{autocenter} //= 1; $self->{preset_bundle}->set_default_suppressed($self->{app_config}->get('no_defaults') ? 1 : 0);
$Settings->{_}{background_processing} //= 1; eval {
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. $self->{preset_bundle}->load_presets(Slic3r::data_dir);
$Settings->{_}{no_controller} //= 1; };
# If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available. if ($@) {
$Settings->{_}{no_defaults} //= 1; warn $@ . "\n";
show_error(undef, $@);
} }
$Settings->{_}{version} = $Slic3r::VERSION; eval {
$self->save_settings; $self->{preset_bundle}->load_selections($self->{app_config});
};
$run_wizard = 1 if $self->{preset_bundle}->has_defauls_only;
# application frame # application frame
Wx::Image::AddHandler(Wx::PNGHandler->new); Wx::Image::AddHandler(Wx::PNGHandler->new);
$self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new( $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new(
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
no_controller => $no_controller // $Settings->{_}{no_controller}, no_controller => $self->{app_config}->get('no_controller'),
no_plater => $no_plater, no_plater => $no_plater,
); );
$self->SetTopWindow($frame); $self->SetTopWindow($frame);
if ($run_wizard) {
# load init bundle $self->{mainframe}->config_wizard;
{
my @dirs = ($FindBin::Bin);
if (&Wx::wxMAC) {
push @dirs, qw();
} elsif (&Wx::wxMSW) {
push @dirs, qw();
} }
my $init_bundle = first { -e $_ } map "$_/.init_bundle.ini", @dirs;
if ($init_bundle) {
Slic3r::debugf "Loading config bundle from %s\n", $init_bundle;
$self->{mainframe}->load_configbundle($init_bundle, 1);
$run_wizard = 0;
}
}
if (!$run_wizard && (!defined $last_version || $last_version ne $Slic3r::VERSION)) {
# user was running another Slic3r version on this computer
if (!defined $last_version || $last_version =~ /^0\./) {
show_info($self->{mainframe}, "Hello! Support material was improved since the "
. "last version of Slic3r you used. It is strongly recommended to revert "
. "your support material settings to the factory defaults and start from "
. "those. Enjoy and provide feedback!", "Support Material");
}
if (!defined $last_version || $last_version =~ /^(?:0|1\.[01])\./) {
show_info($self->{mainframe}, "Hello! In this version a new Bed Shape option was "
. "added. If the bed coordinates in the plater preview screen look wrong, go "
. "to Print Settings and click the \"Set\" button next to \"Bed Shape\".", "Bed Shape");
}
}
$self->{mainframe}->config_wizard if $run_wizard;
eval { $self->{preset_bundle}->load_presets($datadir) };
# $self->check_version
# if $self->have_version_check
# && ($Settings->{_}{version_check} // 1)
# && (!$Settings->{_}{last_version_check} || (time - $Settings->{_}{last_version_check}) >= 86400);
EVT_IDLE($frame, sub { EVT_IDLE($frame, sub {
while (my $cb = shift @cb) { while (my $cb = shift @cb) {
$cb->(); $cb->();
} }
$self->{app_config}->save if $self->{app_config}->dirty;
}); });
# EVT_COMMAND($self, -1, $VERSION_CHECK_EVENT, sub {
# my ($self, $event) = @_;
# my ($success, $response, $manual_check) = @{$event->GetData};
#
# if ($success) {
# if ($response =~ /^obsolete ?= ?([a-z0-9.-]+,)*\Q$Slic3r::VERSION\E(?:,|$)/) {
# my $res = Wx::MessageDialog->new(undef, "A new version is available. Do you want to open the Slic3r website now?",
# 'Update', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_INFORMATION | wxICON_ERROR)->ShowModal;
# Wx::LaunchDefaultBrowser('http://slic3r.org/') if $res == wxID_YES;
# } else {
# Slic3r::GUI::show_info(undef, "You're using the latest version. No updates are available.") if $manual_check;
# }
# $Settings->{_}{last_version_check} = time();
# $self->save_settings;
# } else {
# Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $manual_check;
# }
# });
return 1; return 1;
} }
sub about { sub about {
my ($self) = @_; my ($self) = @_;
my $about = Slic3r::GUI::AboutDialog->new(undef); my $about = Slic3r::GUI::AboutDialog->new(undef);
$about->ShowModal; $about->ShowModal;
$about->Destroy; $about->Destroy;
@ -217,7 +142,6 @@ sub about {
sub system_info { sub system_info {
my ($self) = @_; my ($self) = @_;
my $slic3r_info = Slic3r::slic3r_info(format => 'html'); my $slic3r_info = Slic3r::slic3r_info(format => 'html');
my $copyright_info = Slic3r::copyright_info(format => 'html'); my $copyright_info = Slic3r::copyright_info(format => 'html');
my $system_info = Slic3r::system_info(format => 'html'); my $system_info = Slic3r::system_info(format => 'html');
@ -295,11 +219,6 @@ sub notify {
$self->{notifier}->notify($message); $self->{notifier}->notify($message);
} }
sub save_settings {
my ($self) = @_;
Slic3r::Config->write_ini("$datadir/slic3r.ini", $Settings);
}
# Called after the Preferences dialog is closed and the program settings are saved. # Called after the Preferences dialog is closed and the program settings are saved.
# Update the UI based on the current preferences. # Update the UI based on the current preferences.
sub update_ui_from_settings { sub update_ui_from_settings {
@ -307,64 +226,11 @@ sub update_ui_from_settings {
$self->{mainframe}->update_ui_from_settings; $self->{mainframe}->update_ui_from_settings;
} }
sub presets {
my ($self, $section) = @_;
my %presets = ();
opendir my $dh, Slic3r::encode_path("$Slic3r::GUI::datadir/$section")
or die "Failed to read directory $Slic3r::GUI::datadir/$section (errno: $!)\n";
# Instead of using the /i modifier for case-insensitive matching, the case insensitivity is expressed
# explicitely to avoid having to bundle the UTF8 Perl library.
foreach my $file (grep /\.[iI][nN][iI]$/, readdir $dh) {
$file = Slic3r::decode_path($file);
my $name = basename($file);
$name =~ s/\.ini$//;
$presets{$name} = "$Slic3r::GUI::datadir/$section/$file";
}
closedir $dh;
return %presets;
}
#sub have_version_check {
# my ($self) = @_;
#
# # return an explicit 0
# return ($Slic3r::have_threads && $Slic3r::build && $have_LWP) || 0;
#}
#sub check_version {
# my ($self, $manual_check) = @_;
#
# Slic3r::debugf "Checking for updates...\n";
#
# @_ = ();
# threads->create(sub {
# my $ua = LWP::UserAgent->new;
# $ua->timeout(10);
# my $response = $ua->get('http://slic3r.org/updatecheck');
# Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $VERSION_CHECK_EVENT,
# threads::shared::shared_clone([ $response->is_success, $response->decoded_content, $manual_check ])));
# Slic3r::thread_cleanup();
# })->detach;
#}
sub output_path {
my ($self, $dir) = @_;
return ($Settings->{_}{last_output_path} && $Settings->{_}{remember_output_path})
? $Settings->{_}{last_output_path}
: $dir;
}
sub open_model { sub open_model {
my ($self, $window) = @_; my ($self, $window) = @_;
my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF/PRUSA):',
|| $Slic3r::GUI::Settings->{recent}{config_directory} $self->{app_config}->get_last_dir, "",
|| '';
my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF/PRUSA):', $dir, "",
MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) { if ($dialog->ShowModal != wxID_OK) {
$dialog->Destroy; $dialog->Destroy;
@ -380,31 +246,6 @@ sub CallAfter {
push @cb, $cb; push @cb, $cb;
} }
sub scan_serial_ports {
my ($self) = @_;
my @ports = ();
if ($^O eq 'MSWin32') {
# Windows
if (eval "use Win32::TieRegistry; 1") {
my $ts = Win32::TieRegistry->new("HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM",
{ Access => 'KEY_READ' });
if ($ts) {
# when no serial ports are available, the registry key doesn't exist and
# TieRegistry->new returns undef
$ts->Tie(\my %reg);
push @ports, sort values %reg;
}
}
} else {
# UNIX and OS X
push @ports, glob '/dev/{ttyUSB,ttyACM,tty.,cu.,rfcomm}*';
}
return grep !/Bluetooth|FireFly/, @ports;
}
sub append_menu_item { sub append_menu_item {
my ($self, $menu, $string, $description, $cb, $id, $icon, $kind) = @_; my ($self, $menu, $string, $description, $cb, $id, $icon, $kind) = @_;
@ -434,32 +275,31 @@ sub set_menu_item_icon {
# SetBitmap was not available on OS X before Wx 0.9927 # SetBitmap was not available on OS X before Wx 0.9927
if ($icon && $menuItem->can('SetBitmap')) { if ($icon && $menuItem->can('SetBitmap')) {
$menuItem->SetBitmap(Wx::Bitmap->new($Slic3r::var->($icon), wxBITMAP_TYPE_PNG)); $menuItem->SetBitmap(Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG));
} }
} }
sub save_window_pos { sub save_window_pos {
my ($self, $window, $name) = @_; my ($self, $window, $name) = @_;
$Settings->{_}{"${name}_pos"} = join ',', $window->GetScreenPositionXY; $self->{app_config}->set("${name}_pos", join ',', $window->GetScreenPositionXY);
$Settings->{_}{"${name}_size"} = join ',', $window->GetSizeWH; $self->{app_config}->set("${name}_size", join ',', $window->GetSizeWH);
$Settings->{_}{"${name}_maximized"} = $window->IsMaximized; $self->{app_config}->set("${name}_maximized", $window->IsMaximized);
$self->save_settings; $self->{app_config}->save;
} }
sub restore_window_pos { sub restore_window_pos {
my ($self, $window, $name) = @_; my ($self, $window, $name) = @_;
if ($self->{app_config}->has("${name}_pos")) {
if (defined $Settings->{_}{"${name}_pos"}) { my $size = [ split ',', $self->{app_config}->get("${name}_size"), 2 ];
my $size = [ split ',', $Settings->{_}{"${name}_size"}, 2 ];
$window->SetSize($size); $window->SetSize($size);
my $display = Wx::Display->new->GetClientArea(); my $display = Wx::Display->new->GetClientArea();
my $pos = [ split ',', $Settings->{_}{"${name}_pos"}, 2 ]; my $pos = [ split ',', $self->{app_config}->get("${name}_pos"), 2 ];
if (($pos->[0] + $size->[0]/2) < $display->GetRight && ($pos->[1] + $size->[1]/2) < $display->GetBottom) { if (($pos->[0] + $size->[0]/2) < $display->GetRight && ($pos->[1] + $size->[1]/2) < $display->GetBottom) {
$window->Move($pos); $window->Move($pos);
} }
$window->Maximize(1) if $Settings->{_}{"${name}_maximized"}; $window->Maximize(1) if $self->{app_config}->get("${name}_maximized");
} }
} }

View File

@ -1390,7 +1390,7 @@ sub _load_image_set_texture {
my ($self, $file_name) = @_; my ($self, $file_name) = @_;
# Load a PNG with an alpha channel. # Load a PNG with an alpha channel.
my $img = Wx::Image->new; my $img = Wx::Image->new;
$img->LoadFile($Slic3r::var->($file_name), wxBITMAP_TYPE_PNG); $img->LoadFile(Slic3r::var($file_name), wxBITMAP_TYPE_PNG);
# Get RGB & alpha raw data from wxImage, interleave them into a Perl array. # Get RGB & alpha raw data from wxImage, interleave them into a Perl array.
my @rgb = unpack 'C*', $img->GetData(); my @rgb = unpack 'C*', $img->GetData();
my @alpha = $img->HasAlpha ? unpack 'C*', $img->GetAlpha() : (255) x (int(@rgb) / 3); my @alpha = $img->HasAlpha ? unpack 'C*', $img->GetAlpha() : (255) x (int(@rgb) / 3);

View File

@ -97,7 +97,7 @@ sub new {
my $class = shift; my $class = shift;
my $self = $class->SUPER::new(@_); my $self = $class->SUPER::new(@_);
$self->{logo} = Wx::Bitmap->new($Slic3r::var->("Slic3r_192px.png"), wxBITMAP_TYPE_PNG); $self->{logo} = Wx::Bitmap->new(Slic3r::var("Slic3r_192px.png"), wxBITMAP_TYPE_PNG);
$self->SetMinSize(Wx::Size->new($self->{logo}->GetWidth, $self->{logo}->GetHeight)); $self->SetMinSize(Wx::Size->new($self->{logo}->GetWidth, $self->{logo}->GetHeight));
EVT_PAINT($self, \&repaint); EVT_PAINT($self, \&repaint);

View File

@ -89,11 +89,11 @@ sub new {
push @{$self->{titles}}, $title; push @{$self->{titles}}, $title;
$self->{own_index} = 0; $self->{own_index} = 0;
$self->{bullets}->{before} = Wx::Bitmap->new($Slic3r::var->("bullet_black.png"), wxBITMAP_TYPE_PNG); $self->{bullets}->{before} = Wx::Bitmap->new(Slic3r::var("bullet_black.png"), wxBITMAP_TYPE_PNG);
$self->{bullets}->{own} = Wx::Bitmap->new($Slic3r::var->("bullet_blue.png"), wxBITMAP_TYPE_PNG); $self->{bullets}->{own} = Wx::Bitmap->new(Slic3r::var("bullet_blue.png"), wxBITMAP_TYPE_PNG);
$self->{bullets}->{after} = Wx::Bitmap->new($Slic3r::var->("bullet_white.png"), wxBITMAP_TYPE_PNG); $self->{bullets}->{after} = Wx::Bitmap->new(Slic3r::var("bullet_white.png"), wxBITMAP_TYPE_PNG);
$self->{background} = Wx::Bitmap->new($Slic3r::var->("Slic3r_192px_transparent.png"), wxBITMAP_TYPE_PNG); $self->{background} = Wx::Bitmap->new(Slic3r::var("Slic3r_192px_transparent.png"), wxBITMAP_TYPE_PNG);
$self->SetMinSize(Wx::Size->new($self->{background}->GetWidth, $self->{background}->GetHeight)); $self->SetMinSize(Wx::Size->new($self->{background}->GetWidth, $self->{background}->GetHeight));
EVT_PAINT($self, \&repaint); EVT_PAINT($self, \&repaint);
@ -202,7 +202,7 @@ sub append_option {
# populate repository with the factory default # populate repository with the factory default
my ($opt_key, $opt_index) = split /#/, $full_key, 2; my ($opt_key, $opt_index) = split /#/, $full_key, 2;
$self->config->apply(Slic3r::Config->new_from_defaults($opt_key)); $self->config->apply(Slic3r::Config::new_from_defaults_keys([$opt_key]));
# draw the control # draw the control
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
@ -300,7 +300,7 @@ sub new {
$self->append_text('Set the shape of your printer\'s bed, then click Next.'); $self->append_text('Set the shape of your printer\'s bed, then click Next.');
$self->config->apply(Slic3r::Config->new_from_defaults('bed_shape')); $self->config->apply(Slic3r::Config::new_from_defaults_keys(['bed_shape']));
$self->{bed_shape_panel} = my $panel = Slic3r::GUI::BedShapePanel->new($self, $self->config->bed_shape); $self->{bed_shape_panel} = my $panel = Slic3r::GUI::BedShapePanel->new($self, $self->config->bed_shape);
$self->{bed_shape_panel}->on_change(sub { $self->{bed_shape_panel}->on_change(sub {
$self->config->set('bed_shape', $self->{bed_shape_panel}->GetValue); $self->config->set('bed_shape', $self->{bed_shape_panel}->GetValue);

View File

@ -10,6 +10,7 @@ use utf8;
use Wx qw(wxTheApp :frame :id :misc :sizer :bitmap :button :icon :dialog); use Wx qw(wxTheApp :frame :id :misc :sizer :bitmap :button :icon :dialog);
use Wx::Event qw(EVT_CLOSE EVT_LEFT_DOWN EVT_MENU); use Wx::Event qw(EVT_CLOSE EVT_LEFT_DOWN EVT_MENU);
use base qw(Wx::ScrolledWindow Class::Accessor); use base qw(Wx::ScrolledWindow Class::Accessor);
use List::Util qw(first);
__PACKAGE__->mk_accessors(qw(_selected_printer_preset)); __PACKAGE__->mk_accessors(qw(_selected_printer_preset));
@ -32,27 +33,25 @@ sub new {
# button for adding new printer panels # button for adding new printer panels
{ {
my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("add.png"), wxBITMAP_TYPE_PNG), my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE);
$btn->SetToolTipString("Add printer…") $btn->SetToolTipString("Add printer…")
if $btn->can('SetToolTipString'); if $btn->can('SetToolTipString');
EVT_LEFT_DOWN($btn, sub { EVT_LEFT_DOWN($btn, sub {
my $menu = Wx::Menu->new; my $menu = Wx::Menu->new;
my %presets = wxTheApp->presets('printer');
# remove printers that already exist
my @panels = $self->print_panels; my @panels = $self->print_panels;
delete $presets{$_} for map $_->printer_name, @panels; # remove printers that already exist
# update configs of currently loaded print panels
foreach my $preset_name (sort keys %presets) { foreach my $preset (@{wxTheApp->{preset_bundle}->printer}) {
my $config = Slic3r::Config->load($presets{$preset_name}); my $preset_name = $preset->name;
next if !$config->serial_port; next if ! $preset->config->serial_port ||
defined first { defined $_ && $_->printer_name eq $preset_name } @panels;
my $myconfig = $preset->config->clone_only(\@ConfigOptions);
my $id = &Wx::NewId(); my $id = &Wx::NewId();
$menu->Append($id, $preset_name); $menu->Append($id, $preset_name);
EVT_MENU($menu, $id, sub { EVT_MENU($menu, $id, sub {
$self->add_printer($preset_name, $config); $self->add_printer($preset_name, $myconfig);
}); });
} }
$self->PopupMenu($menu, $btn->GetPosition); $self->PopupMenu($menu, $btn->GetPosition);
@ -98,12 +97,8 @@ sub OnActivate {
my ($self) = @_; my ($self) = @_;
# get all available presets # get all available presets
my %presets = (); my %presets = map { $_->name => $_->config->clone_only(\@ConfigOptions) }
{ grep { $_->config->serial_port } @{wxTheApp->{preset_bundle}->printer};
my %all = wxTheApp->presets('printer');
my %configs = map { my $name = $_; $name => Slic3r::Config->load($all{$name}) } keys %all;
%presets = map { $_ => $configs{$_} } grep $configs{$_}->serial_port, keys %all;
}
# decide which ones we want to keep # decide which ones we want to keep
my %active = (); my %active = ();
@ -121,7 +116,7 @@ sub OnActivate {
} }
if (!%active) { if (!%active) {
# enable printers whose port is available # enable printers whose port is available
my %ports = map { $_ => 1 } wxTheApp->scan_serial_ports; my %ports = map { $_ => 1 } Slic3r::GUI::scan_serial_ports;
$active{$_} = 1 $active{$_} = 1
for grep exists $ports{$presets{$_}->serial_port}, keys %presets; for grep exists $ports{$presets{$_}->serial_port}, keys %presets;
} }
@ -177,26 +172,19 @@ sub print_panels {
map $_->GetWindow, $self->{sizer}->GetChildren; map $_->GetWindow, $self->{sizer}->GetChildren;
} }
# Called by # Called by Slic3r::GUI::Tab::Printer::_on_presets_changed
# Slic3r::GUI::Tab::Print::_on_presets_changed # when the presets are loaded or the user selects another preset.
# Slic3r::GUI::Tab::Filament::_on_presets_changed
# Slic3r::GUI::Tab::Printer::_on_presets_changed
# when the presets are loaded or the user select another preset.
sub update_presets { sub update_presets {
my $self = shift; my ($self, $presets) = @_;
my ($group, $presets, $default_suppressed, $selected, $is_dirty) = @_;
# update configs of currently loaded print panels # update configs of currently loaded print panels
my @presets = @$presets;
foreach my $panel ($self->print_panels) { foreach my $panel ($self->print_panels) {
foreach my $preset (@$presets) { my $preset = $presets->find_preset($panel->printer_name, 0);
if ($panel->printer_name eq $preset->name) { $panel->config($preset->config->clone_only(\@ConfigOptions))
my $config = $preset->config(\@ConfigOptions); if defined $preset;
$panel->config->apply($config);
}
}
} }
$self->_selected_printer_preset($presets->[$selected]->name); $self->_selected_printer_preset($presets->get_selected_preset->name);
} }
1; 1;

View File

@ -34,7 +34,7 @@ sub new {
my $btn = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize, my $btn = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize,
wxBU_LEFT | wxBU_EXACTFIT); wxBU_LEFT | wxBU_EXACTFIT);
$btn->SetFont($bold ? $Slic3r::GUI::small_bold_font : $Slic3r::GUI::small_font); $btn->SetFont($bold ? $Slic3r::GUI::small_bold_font : $Slic3r::GUI::small_font);
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("$icon.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("$icon.png"), wxBITMAP_TYPE_PNG));
$btn->SetBitmapPosition($pos); $btn->SetBitmapPosition($pos);
EVT_BUTTON($self, $btn, $handler); EVT_BUTTON($self, $btn, $handler);
$sizer->Add($btn, 1, wxEXPAND | wxALL, 0); $sizer->Add($btn, 1, wxEXPAND | wxALL, 0);

View File

@ -103,7 +103,7 @@ sub new {
$serial_port_sizer->Add($self->{serial_port_combobox}, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 1); $serial_port_sizer->Add($self->{serial_port_combobox}, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 1);
} }
{ {
$self->{btn_rescan_serial} = my $btn = Wx::BitmapButton->new($box, -1, Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), $self->{btn_rescan_serial} = my $btn = Wx::BitmapButton->new($box, -1, Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE); wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE);
$btn->SetToolTipString("Rescan serial ports") $btn->SetToolTipString("Rescan serial ports")
if $btn->can('SetToolTipString'); if $btn->can('SetToolTipString');
@ -127,7 +127,7 @@ sub new {
{ {
$self->{btn_disconnect} = my $btn = Wx::Button->new($box, -1, "Disconnect", wxDefaultPosition, wxDefaultSize); $self->{btn_disconnect} = my $btn = Wx::Button->new($box, -1, "Disconnect", wxDefaultPosition, wxDefaultSize);
$btn->SetFont($Slic3r::GUI::small_font); $btn->SetFont($Slic3r::GUI::small_font);
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG));
$serial_speed_sizer->Add($btn, 0, wxLEFT, 5); $serial_speed_sizer->Add($btn, 0, wxLEFT, 5);
EVT_BUTTON($self, $btn, \&disconnect); EVT_BUTTON($self, $btn, \&disconnect);
} }
@ -140,7 +140,7 @@ sub new {
my $font = $btn->GetFont; my $font = $btn->GetFont;
$font->SetPointSize($font->GetPointSize + 2); $font->SetPointSize($font->GetPointSize + 2);
$btn->SetFont($font); $btn->SetFont($font);
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("arrow_up.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("arrow_up.png"), wxBITMAP_TYPE_PNG));
$left_sizer->Add($btn, 0, wxTOP, 15); $left_sizer->Add($btn, 0, wxTOP, 15);
EVT_BUTTON($self, $btn, \&connect); EVT_BUTTON($self, $btn, \&connect);
} }
@ -160,7 +160,7 @@ sub new {
{ {
$self->{btn_manual_control} = my $btn = Wx::Button->new($box, -1, "Manual control", wxDefaultPosition, wxDefaultSize); $self->{btn_manual_control} = my $btn = Wx::Button->new($box, -1, "Manual control", wxDefaultPosition, wxDefaultSize);
$btn->SetFont($Slic3r::GUI::small_font); $btn->SetFont($Slic3r::GUI::small_font);
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG));
$btn->Hide; $btn->Hide;
$left_sizer->Add($btn, 0, wxTOP, 15); $left_sizer->Add($btn, 0, wxTOP, 15);
EVT_BUTTON($self, $btn, sub { EVT_BUTTON($self, $btn, sub {
@ -348,7 +348,7 @@ sub update_serial_ports {
my $cb = $self->{serial_port_combobox}; my $cb = $self->{serial_port_combobox};
my $current = $cb->GetValue; my $current = $cb->GetValue;
$cb->Clear; $cb->Clear;
$cb->Append($_) for wxTheApp->scan_serial_ports; $cb->Append($_) for Slic3r::GUI::scan_serial_ports;
$cb->SetValue($current); $cb->SetValue($current);
} }
@ -577,7 +577,7 @@ sub new {
$btn->SetToolTipString("Delete this job from print queue") $btn->SetToolTipString("Delete this job from print queue")
if $btn->can('SetToolTipString'); if $btn->can('SetToolTipString');
$btn->SetFont($Slic3r::GUI::small_font); $btn->SetFont($Slic3r::GUI::small_font);
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG));
if ($job->printing) { if ($job->printing) {
$btn->Hide; $btn->Hide;
} }
@ -597,8 +597,8 @@ sub new {
my $btn = $self->{btn_print} = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize, my $btn = $self->{btn_print} = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize,
$button_style); $button_style);
$btn->SetFont($Slic3r::GUI::small_bold_font); $btn->SetFont($Slic3r::GUI::small_bold_font);
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_play.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_play.png"), wxBITMAP_TYPE_PNG));
$btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_play_blue.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_play_blue.png"), wxBITMAP_TYPE_PNG));
#$btn->SetBitmapPosition(wxRIGHT); #$btn->SetBitmapPosition(wxRIGHT);
$btn->Hide; $btn->Hide;
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2); $buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
@ -616,8 +616,8 @@ sub new {
if (!$job->printing || $job->paused) { if (!$job->printing || $job->paused) {
$btn->Hide; $btn->Hide;
} }
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_pause.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_pause.png"), wxBITMAP_TYPE_PNG));
$btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_pause_blue.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_pause_blue.png"), wxBITMAP_TYPE_PNG));
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2); $buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
EVT_BUTTON($self, $btn, sub { EVT_BUTTON($self, $btn, sub {
@ -633,8 +633,8 @@ sub new {
if (!$job->printing || !$job->paused) { if (!$job->printing || !$job->paused) {
$btn->Hide; $btn->Hide;
} }
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_play.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_play.png"), wxBITMAP_TYPE_PNG));
$btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_play_blue.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_play_blue.png"), wxBITMAP_TYPE_PNG));
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2); $buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
EVT_BUTTON($self, $btn, sub { EVT_BUTTON($self, $btn, sub {
@ -650,8 +650,8 @@ sub new {
if (!$job->printing) { if (!$job->printing) {
$btn->Hide; $btn->Hide;
} }
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_stop.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_stop.png"), wxBITMAP_TYPE_PNG));
$btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_stop_blue.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_stop_blue.png"), wxBITMAP_TYPE_PNG));
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2); $buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
EVT_BUTTON($self, $btn, sub { EVT_BUTTON($self, $btn, sub {

View File

@ -6,6 +6,7 @@ use warnings;
use utf8; use utf8;
use File::Basename qw(basename dirname); use File::Basename qw(basename dirname);
use FindBin;
use List::Util qw(min); use List::Util qw(min);
use Slic3r::Geometry qw(X Y); use Slic3r::Geometry qw(X Y);
use Wx qw(:frame :bitmap :id :misc :notebook :panel :sizer :menu :dialog :filedialog use Wx qw(:frame :bitmap :id :misc :notebook :panel :sizer :menu :dialog :filedialog
@ -22,12 +23,12 @@ sub new {
my $self = $class->SUPER::new(undef, -1, $Slic3r::FORK_NAME . ' - ' . $Slic3r::VERSION, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE); my $self = $class->SUPER::new(undef, -1, $Slic3r::FORK_NAME . ' - ' . $Slic3r::VERSION, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE);
if ($^O eq 'MSWin32') { if ($^O eq 'MSWin32') {
# Load the icon either from the exe, or fron the ico file. # Load the icon either from the exe, or from the ico file.
my $iconfile = $Slic3r::var->('..\slic3r.exe'); my $iconfile = Slic3r::decode_path($FindBin::Bin) . '\slic3r.exe';
$iconfile = $Slic3r::var->("Slic3r.ico") unless -f $iconfile; $iconfile = Slic3r::var("Slic3r.ico") unless -f $iconfile;
$self->SetIcon(Wx::Icon->new($iconfile, wxBITMAP_TYPE_ICO)); $self->SetIcon(Wx::Icon->new($iconfile, wxBITMAP_TYPE_ICO));
} else { } else {
$self->SetIcon(Wx::Icon->new($Slic3r::var->("Slic3r_128px.png"), wxBITMAP_TYPE_PNG)); $self->SetIcon(Wx::Icon->new(Slic3r::var("Slic3r_128px.png"), wxBITMAP_TYPE_PNG));
} }
# store input params # store input params
@ -69,15 +70,15 @@ sub new {
# declare events # declare events
EVT_CLOSE($self, sub { EVT_CLOSE($self, sub {
my (undef, $event) = @_; my (undef, $event) = @_;
if ($event->CanVeto && !$self->check_unsaved_changes) { if ($event->CanVeto && !$self->check_unsaved_changes) {
$event->Veto; $event->Veto;
return; return;
} }
# save window size # save window size
wxTheApp->save_window_pos($self, "main_frame"); wxTheApp->save_window_pos($self, "main_frame");
# Save the slic3r.ini. Usually the ini file is saved from "on idle" callback,
# but in rare cases it may not have been called yet.
wxTheApp->{app_config}->save;
# propagate event # propagate event
$event->Skip; $event->Skip;
}); });
@ -112,7 +113,7 @@ sub _init_tabpanel {
# Callback to be executed after any of the configuration fields (Perl class Slic3r::GUI::OptionsGroup::Field) change their value. # Callback to be executed after any of the configuration fields (Perl class Slic3r::GUI::OptionsGroup::Field) change their value.
$tab->on_value_change(sub { $tab->on_value_change(sub {
my ($opt_key, $value) = @_; my ($opt_key, $value) = @_;
my $config = $tab->config; my $config = $tab->{presets}->get_current_preset->config;
if ($self->{plater}) { if ($self->{plater}) {
$self->{plater}->on_config_change($config); # propagate config change events to the plater $self->{plater}->on_config_change($config); # propagate config change events to the plater
$self->{plater}->on_extruders_change($value) if $opt_key eq 'extruders_count'; $self->{plater}->on_extruders_change($value) if $opt_key eq 'extruders_count';
@ -126,24 +127,35 @@ sub _init_tabpanel {
if ($self->{plater}) { if ($self->{plater}) {
# Update preset combo boxes (Print settings, Filament, Printer) from their respective tabs. # Update preset combo boxes (Print settings, Filament, Printer) from their respective tabs.
$self->{plater}->update_presets($tab_name, @_); $self->{plater}->update_presets($tab_name, @_);
$self->{plater}->on_config_change($tab->config); if ($tab_name eq 'printer') {
if ($self->{controller}) { # Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors.
$self->{controller}->update_presets($tab_name, @_); wxTheApp->{preset_bundle}->print->update_tab_ui(
$self->{options_tabs}{'print'}->{presets_choice},
$self->{options_tabs}{'print'}->{show_incompatible_presets});
wxTheApp->{preset_bundle}->filament->update_tab_ui(
$self->{options_tabs}{'filament'}->{presets_choice},
$self->{options_tabs}{'filament'}->{show_incompatible_presets});
# Update the controller printers.
$self->{controller}->update_presets(@_) if $self->{controller};
} }
$self->{plater}->on_config_change($tab->{presets}->get_current_preset->config);
} }
}); });
$tab->load_presets; # Load the currently selected preset into the GUI, update the preset selection box.
$tab->load_current_preset;
$panel->AddPage($tab, $tab->title); $panel->AddPage($tab, $tab->title);
} }
if ($self->{plater}) { if ($self->{plater}) {
$self->{plater}->on_select_preset(sub { $self->{plater}->on_select_preset(sub {
my ($group, $i) = @_; my ($group, $name) = @_;
$self->{options_tabs}{$group}->select_preset($i); $self->{options_tabs}{$group}->select_preset($name);
}); });
# load initial config # load initial config
$self->{plater}->on_config_change($self->config); my $full_config = wxTheApp->{preset_bundle}->full_config;
$self->{plater}->on_config_change($full_config);
# Show a correct number of filament fields.
$self->{plater}->on_extruders_change(int(@{$full_config->nozzle_diameter}));
} }
} }
@ -329,7 +341,6 @@ sub is_loaded {
# Selection of a 3D object changed on the platter. # Selection of a 3D object changed on the platter.
sub on_plater_selection_changed { sub on_plater_selection_changed {
my ($self, $have_selection) = @_; my ($self, $have_selection) = @_;
return if !defined $self->{object_menu}; return if !defined $self->{object_menu};
$self->{object_menu}->Enable($_->GetId, $have_selection) $self->{object_menu}->Enable($_->GetId, $have_selection)
for $self->{object_menu}->GetMenuItems; for $self->{object_menu}->GetMenuItems;
@ -337,20 +348,20 @@ sub on_plater_selection_changed {
# To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG". # To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".
sub quick_slice { sub quick_slice {
my $self = shift; my ($self, %params) = @_;
my %params = @_;
my $progress_dialog; my $progress_dialog;
eval { eval {
# validate configuration # validate configuration
my $config = $self->config; my $config = wxTheApp->{preset_bundle}->full_config();
$config->validate; $config->validate;
# select input file # select input file
my $input_file; my $input_file;
my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || '';
if (!$params{reslice}) { if (!$params{reslice}) {
my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF/PRUSA):', $dir, "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF/PRUSA):',
wxTheApp->{app_config}->get_last_dir, "",
&Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) { if ($dialog->ShowModal != wxID_OK) {
$dialog->Destroy; $dialog->Destroy;
return; return;
@ -372,8 +383,7 @@ sub quick_slice {
$input_file = $qs_last_input_file; $input_file = $qs_last_input_file;
} }
my $input_file_basename = basename($input_file); my $input_file_basename = basename($input_file);
$Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file); wxTheApp->{app_config}->update_skein_dir(dirname($input_file));
wxTheApp->save_settings;
my $print_center; my $print_center;
{ {
@ -395,10 +405,8 @@ sub quick_slice {
$sprint->apply_config($config); $sprint->apply_config($config);
$sprint->set_model($model); $sprint->set_model($model);
{ # Copy the names of active presets into the placeholder parser.
my $extra = $self->extra_variables; wxTheApp->{preset_bundle}->export_selections_pp($sprint->placeholder_parser);
$sprint->placeholder_parser->set($_, $extra->{$_}) for keys %$extra;
}
# select output file # select output file
my $output_file; my $output_file;
@ -408,7 +416,7 @@ sub quick_slice {
$output_file = $sprint->output_filepath; $output_file = $sprint->output_filepath;
$output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/ if $params{export_svg}; $output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/ if $params{export_svg};
my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:',
wxTheApp->output_path(dirname($output_file)), wxTheApp->{app_config}->get_last_output_dir(dirname($output_file)),
basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal != wxID_OK) { if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy; $dlg->Destroy;
@ -416,8 +424,7 @@ sub quick_slice {
} }
$output_file = $dlg->GetPath; $output_file = $dlg->GetPath;
$qs_last_output_file = $output_file unless $params{export_svg}; $qs_last_output_file = $output_file unless $params{export_svg};
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file); wxTheApp->{app_config}->update_last_output_dir(dirname($output_file));
wxTheApp->save_settings;
$dlg->Destroy; $dlg->Destroy;
} }
@ -452,9 +459,7 @@ sub quick_slice {
sub reslice_now { sub reslice_now {
my ($self) = @_; my ($self) = @_;
if ($self->{plater}) { $self->{plater}->reslice if $self->{plater};
$self->{plater}->reslice;
}
} }
sub repair_stl { sub repair_stl {
@ -462,8 +467,9 @@ sub repair_stl {
my $input_file; my $input_file;
{ {
my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; my $dialog = Wx::FileDialog->new($self, 'Select the STL file to repair:',
my $dialog = Wx::FileDialog->new($self, 'Select the STL file to repair:', $dir, "", &Slic3r::GUI::FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); wxTheApp->{app_config}->get_last_dir, "",
&Slic3r::GUI::FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) { if ($dialog->ShowModal != wxID_OK) {
$dialog->Destroy; $dialog->Destroy;
return; return;
@ -492,36 +498,25 @@ sub repair_stl {
Slic3r::GUI::show_info($self, "Your file was repaired.", "Repair"); Slic3r::GUI::show_info($self, "Your file was repaired.", "Repair");
} }
sub extra_variables {
my $self = shift;
my %extra_variables = ();
$extra_variables{"${_}_preset"} = $self->{options_tabs}{$_}->get_current_preset->name
for qw(print filament printer);
return { %extra_variables };
}
sub export_config { sub export_config {
my $self = shift; my $self = shift;
# Generate a cummulative configuration for the selected print, filaments and printer.
my $config = $self->config; my $config = wxTheApp->{preset_bundle}->full_config();
eval { # Validate the cummulative configuration.
# validate configuration eval { $config->validate; };
$config->validate;
};
Slic3r::GUI::catch_error($self) and return; Slic3r::GUI::catch_error($self) and return;
# Ask user for the file name for the config file.
my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $dlg = Wx::FileDialog->new($self, 'Save configuration as:',
my $filename = $last_config ? basename($last_config) : "config.ini"; $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir,
my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename, $last_config ? basename($last_config) : "config.ini",
&Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal == wxID_OK) { my $file = ($dlg->ShowModal == wxID_OK) ? $dlg->GetPath : undef;
my $file = $dlg->GetPath; $dlg->Destroy;
$Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); if (defined $file) {
wxTheApp->save_settings; wxTheApp->{app_config}->update_config_dir(dirname($file));
$last_config = $file; $last_config = $file;
$config->save($file); $config->save($file);
} }
$dlg->Destroy;
} }
# Load a config file containing a Print, Filament & Printer preset. # Load a config file containing a Print, Filament & Printer preset.
@ -529,222 +524,115 @@ sub load_config_file {
my ($self, $file) = @_; my ($self, $file) = @_;
if (!$file) { if (!$file) {
return unless $self->check_unsaved_changes; return unless $self->check_unsaved_changes;
my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:',
my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini", $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir,
"config.ini",
'INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g', wxFD_OPEN | wxFD_FILE_MUST_EXIST); 'INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g', wxFD_OPEN | wxFD_FILE_MUST_EXIST);
return unless $dlg->ShowModal == wxID_OK; return unless $dlg->ShowModal == wxID_OK;
$file = $dlg->GetPaths; $file = $dlg->GetPaths;
$dlg->Destroy; $dlg->Destroy;
} }
for my $tab (values %{$self->{options_tabs}}) { eval { wxTheApp->{preset_bundle}->load_config_file($file); };
# Dont proceed further if the config file cannot be loaded. # Dont proceed further if the config file cannot be loaded.
return undef if ! $tab->load_config_file($file); return if Slic3r::GUI::catch_error($self);
} $_->load_current_preset for (values %{$self->{options_tabs}});
$Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); wxTheApp->{app_config}->update_config_dir(dirname($file));
wxTheApp->save_settings;
$last_config = $file; $last_config = $file;
} }
sub export_configbundle { sub export_configbundle {
my $self = shift; my ($self) = @_;
return unless $self->check_unsaved_changes;
eval {
# validate current configuration in case it's dirty # validate current configuration in case it's dirty
$self->config->validate; eval { wxTheApp->{preset_bundle}->full_config->validate; };
};
Slic3r::GUI::catch_error($self) and return; Slic3r::GUI::catch_error($self) and return;
# Ask user for a file name.
my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:',
my $filename = "Slic3r_config_bundle.ini"; $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir,
my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', $dir, $filename, "Slic3r_config_bundle.ini",
&Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal == wxID_OK) { my $file = ($dlg->ShowModal == wxID_OK) ? $dlg->GetPath : undef;
my $file = $dlg->GetPath;
$Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
wxTheApp->save_settings;
# leave default category empty to prevent the bundle from being parsed as a normal config file
my $ini = { _ => {} };
$ini->{settings}{$_} = $Slic3r::GUI::Settings->{_}{$_} for qw(autocenter);
$ini->{presets} = $Slic3r::GUI::Settings->{presets};
foreach my $section (qw(print filament printer)) {
my %presets = wxTheApp->presets($section);
foreach my $preset_name (keys %presets) {
my $config = Slic3r::Config->load($presets{$preset_name});
$ini->{"$section:$preset_name"} = $config->as_ini->{_};
}
}
Slic3r::Config->write_ini($file, $ini);
}
$dlg->Destroy; $dlg->Destroy;
if (defined $file) {
# Export the config bundle.
wxTheApp->{app_config}->update_config_dir(dirname($file));
eval { wxTheApp->{preset_bundle}->export_configbundle($file); };
Slic3r::GUI::catch_error($self) and return;
}
} }
# Loading a config bundle with an external file name used to be used
# to auto-install a config bundle on a fresh user account,
# but that behavior was not documented and likely buggy.
sub load_configbundle { sub load_configbundle {
my ($self, $file, $skip_no_id) = @_; my ($self, $file) = @_;
return unless $self->check_unsaved_changes;
if (!$file) { if (!$file) {
my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:',
my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini", $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir,
"config.ini",
&Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
return unless $dlg->ShowModal == wxID_OK; return unless $dlg->ShowModal == wxID_OK;
$file = $dlg->GetPaths; $file = $dlg->GetPaths;
$dlg->Destroy; $dlg->Destroy;
} }
$Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); wxTheApp->{app_config}->update_config_dir(dirname($file));
wxTheApp->save_settings;
# load .ini file my $presets_imported = 0;
my $ini = Slic3r::Config->read_ini($file); eval { $presets_imported = wxTheApp->{preset_bundle}->load_configbundle($file); };
Slic3r::GUI::catch_error($self) and return;
if ($ini->{settings}) { # Load the currently selected preset into the GUI, update the preset selection box.
$Slic3r::GUI::Settings->{_}{$_} = $ini->{settings}{$_} for keys %{$ini->{settings}};
wxTheApp->save_settings;
}
if ($ini->{presets}) {
$Slic3r::GUI::Settings->{presets} = $ini->{presets};
wxTheApp->save_settings;
}
my $imported = 0;
INI_BLOCK: foreach my $ini_category (sort keys %$ini) {
next unless $ini_category =~ /^(print|filament|printer):(.+)$/;
my ($section, $preset_name) = ($1, $2);
my $config = Slic3r::Config->load_ini_hash($ini->{$ini_category});
next if $skip_no_id && !$config->get($section . "_settings_id");
{
my %current_presets = Slic3r::GUI->presets($section);
my %current_ids = map { $_ => 1 }
grep $_,
map Slic3r::Config->load($_)->get($section . "_settings_id"),
values %current_presets;
next INI_BLOCK if exists $current_ids{$config->get($section . "_settings_id")};
}
$config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $section, $preset_name);
Slic3r::debugf "Imported %s preset %s\n", $section, $preset_name;
$imported++;
}
foreach my $tab (values %{$self->{options_tabs}}) { foreach my $tab (values %{$self->{options_tabs}}) {
$tab->load_presets; $tab->load_current_preset;
} }
return if !$imported; my $message = sprintf "%d presets successfully imported.", $presets_imported;
my $message = sprintf "%d presets successfully imported.", $imported;
Slic3r::GUI::show_info($self, $message); Slic3r::GUI::show_info($self, $message);
} }
# Load a provied DynamicConfig into the Print / Filament / Printer tabs, thus modifying the active preset. # Load a provied DynamicConfig into the Print / Filament / Printer tabs, thus modifying the active preset.
# Also update the platter with the new presets. # Also update the platter with the new presets.
sub load_config { sub load_config {
my $self = shift; my ($self, $config) = @_;
my ($config) = @_; $_->load_config($config) foreach values %{$self->{options_tabs}};
$self->{plater}->on_config_change($config) if $self->{plater};
foreach my $tab (values %{$self->{options_tabs}}) {
$tab->load_config($config);
}
if ($self->{plater}) {
$self->{plater}->on_config_change($config);
}
} }
sub config_wizard { sub config_wizard {
my $self = shift; my ($self) = @_;
# Exit wizard if there are unsaved changes and the user cancels the action.
return unless $self->check_unsaved_changes; return unless $self->check_unsaved_changes;
if (my $config = Slic3r::GUI::ConfigWizard->new($self)->run) { if (my $config = Slic3r::GUI::ConfigWizard->new($self)->run) {
for my $tab (values %{$self->{options_tabs}}) { for my $tab (values %{$self->{options_tabs}}) {
$tab->select_default_preset; # Select the first visible preset, force.
$tab->select_preset(undef, 1);
} }
# Load the config over the previously selected defaults.
$self->load_config($config); $self->load_config($config);
for my $tab (values %{$self->{options_tabs}}) { for my $tab (values %{$self->{options_tabs}}) {
# Save the settings under a new name, select the name.
$tab->save_preset('My Settings'); $tab->save_preset('My Settings');
} }
} }
} }
=head2 config # This is called when closing the application, when loading a config file or when starting the config wizard
# to notify the user whether he is aware that some preset changes will be lost.
This method collects all config values from the tabs and merges them into a single config object.
=cut
sub config {
my $self = shift;
return Slic3r::Config->new_from_defaults
if !exists $self->{options_tabs}{print}
|| !exists $self->{options_tabs}{filament}
|| !exists $self->{options_tabs}{printer};
# retrieve filament presets and build a single config object for them
my $filament_config;
if (!$self->{plater} || $self->{plater}->filament_presets == 1) {
$filament_config = $self->{options_tabs}{filament}->config;
} else {
my $i = -1;
foreach my $preset_idx ($self->{plater}->filament_presets) {
$i++;
my $config;
if ($preset_idx == $self->{options_tabs}{filament}->current_preset) {
# the selected preset for this extruder is the one in the tab
# use the tab's config instead of the preset in case it is dirty
# perhaps plater shouldn't expose dirty presets at all in multi-extruder environments.
$config = $self->{options_tabs}{filament}->config;
} else {
my $preset = $self->{options_tabs}{filament}->get_preset($preset_idx);
$config = $self->{options_tabs}{filament}->get_preset_config($preset);
}
if (!$filament_config) {
$filament_config = $config->clone;
next;
}
foreach my $opt_key (@{$config->get_keys}) {
my $value = $filament_config->get($opt_key);
next unless ref $value eq 'ARRAY';
$value->[$i] = $config->get($opt_key)->[0];
$filament_config->set($opt_key, $value);
}
}
}
my $config = Slic3r::Config->merge(
Slic3r::Config->new_from_defaults,
$self->{options_tabs}{print}->config,
$self->{options_tabs}{printer}->config,
$filament_config,
);
my $extruders_count = $self->{options_tabs}{printer}{extruders_count};
$config->set("${_}_extruder", min($config->get("${_}_extruder"), $extruders_count))
for qw(perimeter infill solid_infill support_material support_material_interface);
return $config;
}
sub filament_preset_names {
my ($self) = @_;
return map $self->{options_tabs}{filament}->get_preset($_)->name,
$self->{plater}->filament_presets;
}
sub check_unsaved_changes { sub check_unsaved_changes {
my $self = shift; my $self = shift;
my @dirty = (); my @dirty = ();
foreach my $tab (values %{$self->{options_tabs}}) { foreach my $tab (values %{$self->{options_tabs}}) {
push @dirty, $tab->title if $tab->is_dirty; push @dirty, $tab->title if $tab->{presets}->current_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?",
'Unsaved Presets', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); 'Unsaved Presets', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
return ($confirm->ShowModal == wxID_YES); return $confirm->ShowModal == wxID_YES;
} }
return 1; return 1;
@ -765,21 +653,18 @@ sub select_view {
sub _append_menu_item { sub _append_menu_item {
my ($self, $menu, $string, $description, $cb, $id, $icon) = @_; my ($self, $menu, $string, $description, $cb, $id, $icon) = @_;
$id //= &Wx::NewId(); $id //= &Wx::NewId();
my $item = $menu->Append($id, $string, $description); my $item = $menu->Append($id, $string, $description);
$self->_set_menu_item_icon($item, $icon); $self->_set_menu_item_icon($item, $icon);
EVT_MENU($self, $id, $cb); EVT_MENU($self, $id, $cb);
return $item; return $item;
} }
sub _set_menu_item_icon { sub _set_menu_item_icon {
my ($self, $menuItem, $icon) = @_; my ($self, $menuItem, $icon) = @_;
# SetBitmap was not available on OS X before Wx 0.9927 # SetBitmap was not available on OS X before Wx 0.9927
if ($icon && $menuItem->can('SetBitmap')) { if ($icon && $menuItem->can('SetBitmap')) {
$menuItem->SetBitmap(Wx::Bitmap->new($Slic3r::var->($icon), wxBITMAP_TYPE_PNG)); $menuItem->SetBitmap(Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG));
} }
} }
@ -787,8 +672,11 @@ sub _set_menu_item_icon {
# Update the UI based on the current preferences. # Update the UI based on the current preferences.
sub update_ui_from_settings { sub update_ui_from_settings {
my ($self) = @_; my ($self) = @_;
$self->{menu_item_reslice_now}->Enable(! $Slic3r::GUI::Settings->{_}{background_processing}); $self->{menu_item_reslice_now}->Enable(! wxTheApp->{app_config}->get("background_processing"));
$self->{plater}->update_ui_from_settings if ($self->{plater}); $self->{plater}->update_ui_from_settings if ($self->{plater});
for my $tab_name (qw(print filament printer)) {
$self->{options_tabs}{$tab_name}->update_ui_from_settings;
}
} }
1; 1;

View File

@ -6,7 +6,7 @@ use Moo;
has 'growler' => (is => 'rw'); has 'growler' => (is => 'rw');
my $icon = $Slic3r::var->("Slic3r.png"); my $icon = Slic3r::var("Slic3r.png");
sub BUILD { sub BUILD {
my ($self) = @_; my ($self) = @_;

View File

@ -124,6 +124,10 @@ sub BUILD {
}); });
} }
sub get_value {
my ($self) = @_;
return $self->wxWindow->GetValue ? 1 : 0;
}
package Slic3r::GUI::OptionsGroup::Field::SpinCtrl; package Slic3r::GUI::OptionsGroup::Field::SpinCtrl;
use Moo; use Moo;

View File

@ -46,15 +46,14 @@ use constant PROCESS_DELAY => 0.5 * 1000; # milliseconds
my $PreventListEvents = 0; my $PreventListEvents = 0;
sub new { sub new {
my $class = shift; my ($class, $parent) = @_;
my ($parent) = @_;
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_keys([qw(
bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height
serial_port serial_speed octoprint_host octoprint_apikey serial_port serial_speed octoprint_host octoprint_apikey
nozzle_diameter single_extruder_multi_material nozzle_diameter single_extruder_multi_material
wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe extruder_colour filament_colour wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe extruder_colour filament_colour
)); )]);
# C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm
$self->{model} = Slic3r::Model->new; $self->{model} = Slic3r::Model->new;
# C++ Slic3r::Print with Perl extensions in Slic3r/Print.pm # C++ Slic3r::Print with Perl extensions in Slic3r/Print.pm
@ -64,12 +63,7 @@ sub new {
$self->{print}->set_status_cb(sub { $self->{print}->set_status_cb(sub {
my ($percent, $message) = @_; my ($percent, $message) = @_;
if ($Slic3r::have_threads) {
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROGRESS_BAR_EVENT, shared_clone([$percent, $message]))); Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROGRESS_BAR_EVENT, shared_clone([$percent, $message])));
} else {
$self->on_progress_event($percent, $message);
}
}); });
# Initialize preview notebook # Initialize preview notebook
@ -120,7 +114,7 @@ sub new {
$self->GetFrame->{options_tabs}{print}->load_config($cfg); $self->GetFrame->{options_tabs}{print}->load_config($cfg);
}); });
$self->{canvas3D}->set_on_model_update(sub { $self->{canvas3D}->set_on_model_update(sub {
if ($Slic3r::GUI::Settings->{_}{background_processing}) { if (wxTheApp->{app_config}->get("background_processing")) {
$self->schedule_background_process; $self->schedule_background_process;
} else { } else {
# Hide the print info box, it is no more valid. # Hide the print info box, it is no more valid.
@ -166,22 +160,22 @@ sub new {
if (!&Wx::wxMSW) { if (!&Wx::wxMSW) {
Wx::ToolTip::Enable(1); Wx::ToolTip::Enable(1);
$self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL); $self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL);
$self->{htoolbar}->AddTool(TB_ADD, "Add…", Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_ADD, "Add…", Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_REMOVE, "Delete", Wx::Bitmap->new($Slic3r::var->("brick_delete.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_REMOVE, "Delete", Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_RESET, "Delete All", Wx::Bitmap->new($Slic3r::var->("cross.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_RESET, "Delete All", Wx::Bitmap->new(Slic3r::var("cross.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_ARRANGE, "Arrange", Wx::Bitmap->new($Slic3r::var->("bricks.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_ARRANGE, "Arrange", Wx::Bitmap->new(Slic3r::var("bricks.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new($Slic3r::var->("add.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_FEWER, "Fewer", Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_FEWER, "Fewer", Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new($Slic3r::var->("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new(Slic3r::var("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_SCALE, "Scale…", Wx::Bitmap->new($Slic3r::var->("arrow_out.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_SCALE, "Scale…", Wx::Bitmap->new(Slic3r::var("arrow_out.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new($Slic3r::var->("shape_ungroup.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_LAYER_EDITING, 'Layer Editing', Wx::Bitmap->new($Slic3r::var->("variable_layer_height.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, 0, 'Layer Editing'); $self->{htoolbar}->AddTool(TB_LAYER_EDITING, 'Layer Editing', Wx::Bitmap->new(Slic3r::var("variable_layer_height.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, 0, 'Layer Editing');
} else { } else {
my %tbar_buttons = ( my %tbar_buttons = (
add => "Add…", add => "Add…",
@ -256,7 +250,7 @@ sub new {
settings cog.png settings cog.png
); );
for (grep $self->{"btn_$_"}, keys %icons) { for (grep $self->{"btn_$_"}, keys %icons) {
$self->{"btn_$_"}->SetBitmap(Wx::Bitmap->new($Slic3r::var->($icons{$_}), wxBITMAP_TYPE_PNG)); $self->{"btn_$_"}->SetBitmap(Wx::Bitmap->new(Slic3r::var($icons{$_}), wxBITMAP_TYPE_PNG));
} }
$self->selection_changed(0); $self->selection_changed(0);
$self->object_list_changed; $self->object_list_changed;
@ -331,7 +325,7 @@ sub new {
$self->on_process_completed($event->GetData); $self->on_process_completed($event->GetData);
}); });
if ($Slic3r::have_threads) { {
my $timer_id = Wx::NewId(); my $timer_id = Wx::NewId();
$self->{apply_config_timer} = Wx::Timer->new($self, $timer_id); $self->{apply_config_timer} = Wx::Timer->new($self, $timer_id);
EVT_TIMER($self, $timer_id, sub { EVT_TIMER($self, $timer_id, sub {
@ -366,20 +360,17 @@ sub new {
# once a printer preset with multiple extruders is activated. # once a printer preset with multiple extruders is activated.
# $self->{preset_choosers}{$group}[$idx] # $self->{preset_choosers}{$group}[$idx]
$self->{preset_choosers} = {}; $self->{preset_choosers} = {};
# Boolean indicating whether the '- default -' preset is shown by the combo box.
$self->{preset_choosers_default_suppressed} = {};
for my $group (qw(print filament printer)) { for my $group (qw(print filament printer)) {
my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT);
$text->SetFont($Slic3r::GUI::small_font); $text->SetFont($Slic3r::GUI::small_font);
my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY);
EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down(0, @_); } ); EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down(0, @_); } );
$self->{preset_choosers}{$group} = [$choice]; $self->{preset_choosers}{$group} = [$choice];
$self->{preset_choosers_default_suppressed}{$group} = 0;
# setup the listener # setup the listener
EVT_COMBOBOX($choice, $choice, sub { EVT_COMBOBOX($choice, $choice, sub {
my ($choice) = @_; my ($choice) = @_;
wxTheApp->CallAfter(sub { wxTheApp->CallAfter(sub {
$self->_on_select_preset($group, $choice); $self->_on_select_preset($group, $choice, 0);
}); });
}); });
$presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
@ -414,7 +405,7 @@ sub new {
$self->{"object_info_$field"} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $self->{"object_info_$field"} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
$self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font); $self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font);
if ($field eq 'manifold') { if ($field eq 'manifold') {
$self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self, -1, Wx::Bitmap->new($Slic3r::var->("error.png"), wxBITMAP_TYPE_PNG)); $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG));
$self->{object_info_manifold_warning_icon}->Hide; $self->{object_info_manifold_warning_icon}->Hide;
my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL); my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
@ -452,7 +443,6 @@ sub new {
$self->{"print_info_$field"}->SetFont($Slic3r::GUI::small_font); $self->{"print_info_$field"}->SetFont($Slic3r::GUI::small_font);
$grid_sizer->Add($self->{"print_info_$field"}, 0); $grid_sizer->Add($self->{"print_info_$field"}, 0);
} }
} }
my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL); my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
@ -504,30 +494,26 @@ sub on_select_preset {
$self->{on_select_preset} = $cb; $self->{on_select_preset} = $cb;
} }
# Called from the platter combo boxes selecting the active print, filament or printer.
sub _on_select_preset { sub _on_select_preset {
my $self = shift; my ($self, $group, $choice, $idx) = @_;
my ($group, $choice) = @_;
# If user changed a filament preset and the selected machine is equipped with multiple extruders, # If user changed a filament preset and the selected machine is equipped with multiple extruders,
# there are multiple filament selection combo boxes shown at the platter. In that case # there are multiple filament selection combo boxes shown at the platter. In that case
# don't propagate the filament selection changes to the tab. # don't propagate the filament selection changes to the tab.
my $default_suppressed = $self->{preset_choosers_default_suppressed}{$group}; if ($group eq 'filament') {
wxTheApp->{preset_bundle}->set_filament_preset($idx, $choice->GetStringSelection);
}
if ($group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1) { if ($group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1) {
# Indices of the filaments selected. wxTheApp->{preset_bundle}->update_platter_filament_ui($idx, $choice);
my @filament_presets = $self->filament_presets;
$Slic3r::GUI::Settings->{presets}{filament} = $choice->GetString($filament_presets[0] - $default_suppressed) . ".ini";
$Slic3r::GUI::Settings->{presets}{"filament_${_}"} = $choice->GetString($filament_presets[$_] - $default_suppressed)
for 1 .. $#filament_presets;
wxTheApp->save_settings;
$self->update_filament_colors_preview($choice);
} else { } else {
# call GetSelection() in scalar context as it's context-aware # call GetSelection() in scalar context as it's context-aware
$self->{on_select_preset}->($group, scalar($choice->GetSelection) + $default_suppressed) $self->{on_select_preset}->($group, $choice->GetStringSelection)
if $self->{on_select_preset}; if $self->{on_select_preset};
} }
# Synchronize config.ini with the current selections.
wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config});
# get new config and generate on_config_change() event for updating plater and other things # get new config and generate on_config_change() event for updating plater and other things
$self->on_config_change($self->GetFrame->config); $self->on_config_change(wxTheApp->{preset_bundle}->full_config);
} }
sub on_layer_editing_toggled { sub on_layer_editing_toggled {
@ -557,8 +543,8 @@ sub GetFrame {
sub update_ui_from_settings sub update_ui_from_settings
{ {
my ($self) = @_; my ($self) = @_;
if (defined($self->{btn_reslice}) && $self->{buttons_sizer}->IsShown($self->{btn_reslice}) != (! $Slic3r::GUI::Settings->{_}{background_processing})) { if (defined($self->{btn_reslice}) && $self->{buttons_sizer}->IsShown($self->{btn_reslice}) != (! wxTheApp->{app_config}->get("background_processing"))) {
$self->{buttons_sizer}->Show($self->{btn_reslice}, ! $Slic3r::GUI::Settings->{_}{background_processing}); $self->{buttons_sizer}->Show($self->{btn_reslice}, ! wxTheApp->{app_config}->get("background_processing"));
$self->{buttons_sizer}->Layout; $self->{buttons_sizer}->Layout;
} }
} }
@ -572,128 +558,41 @@ sub update_ui_from_settings
# For Print settings and Printer, synchronize the selection index with their tabs. # For Print settings and Printer, synchronize the selection index with their tabs.
# For Filament, synchronize the selection index for a single extruder printer only, otherwise keep the selection. # For Filament, synchronize the selection index for a single extruder printer only, otherwise keep the selection.
sub update_presets { sub update_presets {
my $self = shift; # $group: one of qw(print filament printer)
# $presets: one of qw(print filament printer) # $presets: PresetCollection
# $selected: index of the selected preset in the array. This may not correspond my ($self, $group, $presets) = @_;
# with the index of selection in the UI element, where not all items are displayed.
my ($group, $presets, $default_suppressed, $selected, $is_dirty) = @_;
my @choosers = @{$self->{preset_choosers}{$group}}; my @choosers = @{$self->{preset_choosers}{$group}};
my $choice_idx = 0;
foreach my $choice (@choosers) {
if ($group eq 'filament' && @choosers > 1) {
# if we have more than one filament chooser, keep our selection
# instead of importing the one from the tab
$selected = $choice->GetSelection + $self->{preset_choosers_default_suppressed}{$group};
$is_dirty = 0;
}
$choice->Clear;
foreach my $preset (@$presets) {
next if ($preset->default && $default_suppressed);
my $bitmap;
if ($group eq 'filament') { if ($group eq 'filament') {
$bitmap = Wx::Bitmap->new($Slic3r::var->("spool.png"), wxBITMAP_TYPE_PNG); my $choice_idx = 0;
} elsif ($group eq 'print') { if (int(@choosers) == 1) {
$bitmap = Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG); # Single filament printer, synchronize the filament presets.
} elsif ($group eq 'printer') { wxTheApp->{preset_bundle}->set_filament_preset(0, wxTheApp->{preset_bundle}->filament->get_selected_preset->name);
$bitmap = Wx::Bitmap->new($Slic3r::var->("printer_empty.png"), wxBITMAP_TYPE_PNG);
}
$choice->AppendString($preset->name, $bitmap);
}
if ($selected <= $#$presets) {
my $idx = $selected - $default_suppressed;
if ($idx >= 0) {
if ($is_dirty) {
$choice->SetString($idx, $choice->GetString($idx) . " (modified)");
}
# call SetSelection() only after SetString() otherwise the new string
# won't be picked up as the visible string
$choice->SetSelection($idx);
}
} }
foreach my $choice (@choosers) {
wxTheApp->{preset_bundle}->update_platter_filament_ui($choice_idx, $choice);
$choice_idx += 1; $choice_idx += 1;
} }
} elsif ($group eq 'print') {
$self->{preset_choosers_default_suppressed}{$group} = $default_suppressed; wxTheApp->{preset_bundle}->print->update_platter_ui($choosers[0]);
} elsif ($group eq 'printer') {
wxTheApp->CallAfter(sub { $self->update_filament_colors_preview }) if $group eq 'filament' || $group eq 'printer'; # Update the print choosers to only contain the compatible presets, update the dirty flags.
} wxTheApp->{preset_bundle}->print->update_platter_ui($self->{preset_choosers}{print}->[0]);
# Update the printer choosers, update the dirty flags.
# Update the color icon in front of each filament selection on the platter. wxTheApp->{preset_bundle}->printer->update_platter_ui($choosers[0]);
# If the extruder has a preview color assigned, apply the extruder color to the active selection. # Update the filament choosers to only contain the compatible presets, update the color preview,
# Always apply the filament color to the non-active selections. # update the dirty flags.
sub update_filament_colors_preview { my $choice_idx = 0;
my ($self, $extruder_idx) = shift; foreach my $choice (@{$self->{preset_choosers}{filament}}) {
wxTheApp->{preset_bundle}->update_platter_filament_ui($choice_idx, $choice);
my @choosers = @{$self->{preset_choosers}{filament}}; $choice_idx += 1;
if (ref $extruder_idx) {
# $extruder_idx is the chooser.
foreach my $chooser (@choosers) {
if ($extruder_idx == $chooser) {
$extruder_idx = $chooser;
last;
} }
} }
} # Synchronize config.ini with the current selections.
wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config});
my @extruder_colors = @{$self->{config}->extruder_colour};
my @extruder_list;
if (defined $extruder_idx) {
@extruder_list = ($extruder_idx);
} else {
# Collect extruder indices.
@extruder_list = (0..$#extruder_colors);
}
my $filament_tab = $self->GetFrame->{options_tabs}{filament};
my $presets = $filament_tab->{presets};
my $default_suppressed = $filament_tab->{default_suppressed};
foreach my $extruder_idx (@extruder_list) {
my $chooser = $choosers[$extruder_idx];
my $extruder_color = $self->{config}->extruder_colour->[$extruder_idx];
my $preset_idx = 0;
my $selection_idx = $chooser->GetSelection;
foreach my $preset (@$presets) {
my $bitmap;
if ($preset->default) {
next if $default_suppressed;
} else {
# Assign an extruder color to the selected item if the extruder color is defined.
my $filament_rgb = $preset->config(['filament_colour'])->filament_colour->[0];
my $extruder_rgb = ($preset_idx == $selection_idx && $extruder_color =~ m/^#[[:xdigit:]]{6}/) ? $extruder_color : $filament_rgb;
$filament_rgb =~ s/^#//;
$extruder_rgb =~ s/^#//;
my $image = Wx::Image->new(24,16);
if ($filament_rgb ne $extruder_rgb) {
my @rgb = unpack 'C*', pack 'H*', $extruder_rgb;
$image->SetRGB(Wx::Rect->new(0,0,16,16), @rgb);
@rgb = unpack 'C*', pack 'H*', $filament_rgb;
$image->SetRGB(Wx::Rect->new(16,0,8,16), @rgb);
} else {
my @rgb = unpack 'C*', pack 'H*', $filament_rgb;
$image->SetRGB(Wx::Rect->new(0,0,24,16), @rgb);
}
$bitmap = Wx::Bitmap->new($image);
}
$chooser->SetItemBitmap($preset_idx, $bitmap) if $bitmap;
$preset_idx += 1;
}
}
}
# Return a vector of indices of filaments selected by the $self->{preset_choosers}{filament} combo boxes.
sub filament_presets {
my $self = shift;
# force scalar context for GetSelection() as it's context-aware
return map scalar($_->GetSelection) + $self->{preset_choosers_default_suppressed}{filament}, @{ $self->{preset_choosers}{filament} };
} }
sub add { sub add {
my $self = shift; my ($self) = @_;
my @input_files = wxTheApp->open_model($self); my @input_files = wxTheApp->open_model($self);
$self->load_files(\@input_files); $self->load_files(\@input_files);
} }
@ -758,8 +657,7 @@ sub load_files {
} }
# Note the current directory for the file open dialog. # Note the current directory for the file open dialog.
$Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_files->[-1]); wxTheApp->{app_config}->update_skein_dir(dirname($input_files->[-1]));
wxTheApp->save_settings;
$process_dialog->Destroy; $process_dialog->Destroy;
$self->statusbar->SetStatusText("Loaded " . join(',', @loaded_files)); $self->statusbar->SetStatusText("Loaded " . join(',', @loaded_files));
@ -807,7 +705,7 @@ sub load_model_objects {
} }
# if user turned autocentering off, automatic arranging would disappoint them # if user turned autocentering off, automatic arranging would disappoint them
if (!$Slic3r::GUI::Settings->{_}{autocenter}) { if (! wxTheApp->{app_config}->get("autocenter")) {
$need_arrange = 0; $need_arrange = 0;
} }
@ -920,7 +818,7 @@ sub increase {
# only autoarrange if user has autocentering enabled # only autoarrange if user has autocentering enabled
$self->stop_background_process; $self->stop_background_process;
if ($Slic3r::GUI::Settings->{_}{autocenter}) { if (wxTheApp->{app_config}->get("autocenter")) {
$self->arrange; $self->arrange;
} else { } else {
$self->update; $self->update;
@ -1163,7 +1061,7 @@ sub arrange {
$self->pause_background_process; $self->pause_background_process;
my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape); my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape);
my $success = $self->{model}->arrange_objects($self->GetFrame->config->min_object_distance, $bb); my $success = $self->{model}->arrange_objects(wxTheApp->{preset_bundle}->full_config->min_object_distance, $bb);
# ignore arrange failures on purpose: user has visual feedback and we don't need to warn him # ignore arrange failures on purpose: user has visual feedback and we don't need to warn him
# when parts don't fit in print bed # when parts don't fit in print bed
@ -1224,7 +1122,7 @@ sub async_apply_config {
$self->pause_background_process; $self->pause_background_process;
# apply new config # apply new config
my $invalidated = $self->{print}->apply_config($self->GetFrame->config); my $invalidated = $self->{print}->apply_config(wxTheApp->{preset_bundle}->full_config);
# Just redraw the 3D canvas without reloading the scene. # Just redraw the 3D canvas without reloading the scene.
# $self->{canvas3D}->Refresh if ($invalidated && $self->{canvas3D}->layer_editing_enabled); # $self->{canvas3D}->Refresh if ($invalidated && $self->{canvas3D}->layer_editing_enabled);
@ -1233,7 +1131,7 @@ sub async_apply_config {
# Hide the slicing results if the current slicing status is no more valid. # Hide the slicing results if the current slicing status is no more valid.
$self->{"print_info_box_show"}->(0) if $invalidated; $self->{"print_info_box_show"}->(0) if $invalidated;
if ($Slic3r::GUI::Settings->{_}{background_processing}) { if (wxTheApp->{app_config}->get("background_processing")) {
if ($invalidated) { if ($invalidated) {
# kill current thread if any # kill current thread if any
$self->stop_background_process; $self->stop_background_process;
@ -1255,7 +1153,6 @@ sub async_apply_config {
sub start_background_process { sub start_background_process {
my ($self) = @_; my ($self) = @_;
return if !$Slic3r::have_threads;
return if !@{$self->{objects}}; return if !@{$self->{objects}};
return if $self->{process_thread}; return if $self->{process_thread};
@ -1266,7 +1163,7 @@ sub start_background_process {
# don't start process thread if config is not valid # don't start process thread if config is not valid
eval { eval {
# this will throw errors if config is not valid # this will throw errors if config is not valid
$self->GetFrame->config->validate; wxTheApp->{preset_bundle}->full_config->validate;
$self->{print}->validate; $self->{print}->validate;
}; };
if ($@) { if ($@) {
@ -1274,11 +1171,8 @@ sub start_background_process {
return; return;
} }
# apply extra variables # Copy the names of active presets into the placeholder parser.
{ wxTheApp->{preset_bundle}->export_selections_pp($self->{print}->placeholder_parser);
my $extra = $self->GetFrame->extra_variables;
$self->{print}->placeholder_parser->set($_, $extra->{$_}) for keys %$extra;
}
# start thread # start thread
@_ = (); @_ = ();
@ -1349,7 +1243,7 @@ sub reslice {
# explicitly cancel a previous thread and start a new one. # explicitly cancel a previous thread and start a new one.
my ($self) = @_; my ($self) = @_;
# Don't reslice if export of G-code or sending to OctoPrint is running. # Don't reslice if export of G-code or sending to OctoPrint is running.
if ($Slic3r::have_threads && ! defined($self->{export_gcode_output_file}) && ! defined($self->{send_gcode_file})) { if (! defined($self->{export_gcode_output_file}) && ! defined($self->{send_gcode_file})) {
# Stop the background processing threads, stop the async update timer. # Stop the background processing threads, stop the async update timer.
$self->stop_background_process; $self->stop_background_process;
# Rather perform one additional unnecessary update of the print object instead of skipping a pending async update. # Rather perform one additional unnecessary update of the print object instead of skipping a pending async update.
@ -1378,45 +1272,42 @@ sub export_gcode {
# (we assume that if it is running, config is valid) # (we assume that if it is running, config is valid)
eval { eval {
# this will throw errors if config is not valid # this will throw errors if config is not valid
$self->GetFrame->config->validate; wxTheApp->{preset_bundle}->full_config->validate;
$self->{print}->validate; $self->{print}->validate;
}; };
Slic3r::GUI::catch_error($self) and return; Slic3r::GUI::catch_error($self) and return;
# apply config and validate print # apply config and validate print
my $config = $self->GetFrame->config; my $config = wxTheApp->{preset_bundle}->full_config;
eval { eval {
# this will throw errors if config is not valid # this will throw errors if config is not valid
$config->validate; $config->validate;
$self->{print}->apply_config($config); $self->{print}->apply_config($config);
$self->{print}->validate; $self->{print}->validate;
}; };
if (!$Slic3r::have_threads) {
Slic3r::GUI::catch_error($self) and return; Slic3r::GUI::catch_error($self) and return;
}
# select output file # select output file
if ($output_file) { if ($output_file) {
$self->{export_gcode_output_file} = $self->{print}->output_filepath($output_file); $self->{export_gcode_output_file} = $self->{print}->output_filepath($output_file);
} else { } else {
my $default_output_file = $self->{print}->output_filepath($main::opt{output} // ''); my $default_output_file = $self->{print}->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->{app_config}->get_last_output_dir(dirname($default_output_file)),
basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal != wxID_OK) { if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy; $dlg->Destroy;
return; return;
} }
my $path = $dlg->GetPath; my $path = $dlg->GetPath;
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($path); wxTheApp->{app_config}->update_last_output_dir(dirname($path));
wxTheApp->save_settings;
$self->{export_gcode_output_file} = $path; $self->{export_gcode_output_file} = $path;
$dlg->Destroy; $dlg->Destroy;
} }
$self->statusbar->StartBusy; $self->statusbar->StartBusy;
if ($Slic3r::have_threads) {
$self->statusbar->SetCancelCallback(sub { $self->statusbar->SetCancelCallback(sub {
$self->stop_background_process; $self->stop_background_process;
$self->statusbar->SetStatusText("Export cancelled"); $self->statusbar->SetStatusText("Export cancelled");
@ -1430,14 +1321,6 @@ sub export_gcode {
# start background process, whose completion event handler # start background process, whose completion event handler
# will detect $self->{export_gcode_output_file} and proceed with export # will detect $self->{export_gcode_output_file} and proceed with export
$self->start_background_process; $self->start_background_process;
} else {
eval {
$self->{print}->process;
$self->{print}->export_gcode(output_file => $self->{export_gcode_output_file});
};
my $result = !Slic3r::GUI::catch_error($self);
$self->on_export_completed($result);
}
# this updates buttons status # this updates buttons status
$self->object_list_changed; $self->object_list_changed;
@ -1549,14 +1432,12 @@ sub on_export_completed {
sub do_print { sub do_print {
my ($self) = @_; my ($self) = @_;
my $printer_tab = $self->GetFrame->{options_tabs}{printer};
my $printer_name = $printer_tab->get_current_preset->name;
my $controller = $self->GetFrame->{controller}; my $controller = $self->GetFrame->{controller};
my $printer_panel = $controller->add_printer($printer_name, $printer_tab->config); my $printer_preset = wxTheApp->{preset_bundle}->printer->get_edited_preset;
my $printer_panel = $controller->add_printer($printer_preset->name, $printer_preset->config);
my $filament_stats = $self->{print}->filament_stats; my $filament_stats = $self->{print}->filament_stats;
my @filament_names = $self->GetFrame->filament_preset_names; my @filament_names = wxTheApp->{preset_bundle}->filament_presets;
$filament_stats = { map { $filament_names[$_] => $filament_stats->{$_} } keys %$filament_stats }; $filament_stats = { map { $filament_names[$_] => $filament_stats->{$_} } keys %$filament_stats };
$printer_panel->load_print_job($self->{print_file}, $filament_stats); $printer_panel->load_print_job($self->{print_file}, $filament_stats);
@ -1597,11 +1478,11 @@ sub send_gcode {
} }
sub export_stl { sub export_stl {
my $self = shift; my ($self) = @_;
return if !@{$self->{objects}}; return if !@{$self->{objects}};
# Ask user for a file name to write into.
my $output_file = $self->_get_export_file('STL') or return; my $output_file = $self->_get_export_file('STL') or return;
# Store a binary STL.
$self->{model}->store_stl($output_file, 1); $self->{model}->store_stl($output_file, 1);
$self->statusbar->SetStatusText("STL file exported to $output_file"); $self->statusbar->SetStatusText("STL file exported to $output_file");
} }
@ -1635,37 +1516,31 @@ sub reload_from_disk {
} }
sub export_object_stl { sub export_object_stl {
my $self = shift; my ($self) = @_;
my ($obj_idx, $object) = $self->selected_object; my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx; return if !defined $obj_idx;
my $model_object = $self->{model}->objects->[$obj_idx]; my $model_object = $self->{model}->objects->[$obj_idx];
# Ask user for a file name to write into.
my $output_file = $self->_get_export_file('STL') or return; my $output_file = $self->_get_export_file('STL') or return;
$model_object->mesh->write_binary($output_file); $model_object->mesh->write_binary($output_file);
$self->statusbar->SetStatusText("STL file exported to $output_file"); $self->statusbar->SetStatusText("STL file exported to $output_file");
} }
sub export_amf { sub export_amf {
my $self = shift; my ($self) = @_;
return if !@{$self->{objects}}; return if !@{$self->{objects}};
# Ask user for a file name to write into.
my $output_file = $self->_get_export_file('AMF') or return; my $output_file = $self->_get_export_file('AMF') or return;
$self->{model}->store_amf($output_file); $self->{model}->store_amf($output_file);
$self->statusbar->SetStatusText("AMF file exported to $output_file"); $self->statusbar->SetStatusText("AMF file exported to $output_file");
} }
# Ask user to select an output file for a given file format (STl, AMF, 3MF).
# Propose a default file name based on the 'output_filename_format' configuration value.
sub _get_export_file { sub _get_export_file {
my $self = shift; my ($self, $format) = @_;
my ($format) = @_;
my $suffix = $format eq 'STL' ? '.stl' : '.amf.xml'; my $suffix = $format eq 'STL' ? '.stl' : '.amf.xml';
my $output_file = $self->{print}->output_filepath($main::opt{output} // '');
my $output_file = $main::opt{output};
{
$output_file = $self->{print}->output_filepath($output_file);
$output_file =~ s/\.[gG][cC][oO][dD][eE]$/$suffix/; $output_file =~ s/\.[gG][cC][oO][dD][eE]$/$suffix/;
my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file),
basename($output_file), &Slic3r::GUI::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); basename($output_file), &Slic3r::GUI::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
@ -1675,7 +1550,6 @@ sub _get_export_file {
} }
$output_file = $dlg->GetPath; $output_file = $dlg->GetPath;
$dlg->Destroy; $dlg->Destroy;
}
return $output_file; return $output_file;
} }
@ -1689,7 +1563,7 @@ sub reset_thumbnail {
sub update { sub update {
my ($self, $force_autocenter) = @_; my ($self, $force_autocenter) = @_;
if ($Slic3r::GUI::Settings->{_}{autocenter} || $force_autocenter) { if (wxTheApp->{app_config}->get("autocenter") || $force_autocenter) {
$self->{model}->center_instances_around_point($self->bed_centerf); $self->{model}->center_instances_around_point($self->bed_centerf);
} }
@ -1712,11 +1586,13 @@ sub update {
} }
# When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder. # When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder.
# Also the wxTheApp->{preset_bundle}->filament_presets needs to be resized accordingly
# and some reasonable default has to be selected for the additional extruders.
sub on_extruders_change { sub on_extruders_change {
my ($self, $num_extruders) = @_; my ($self, $num_extruders) = @_;
my $choices = $self->{preset_choosers}{filament}; my $choices = $self->{preset_choosers}{filament};
while (@$choices < $num_extruders) {
while (int(@$choices) < $num_extruders) {
# copy strings from first choice # copy strings from first choice
my @presets = $choices->[0]->GetStrings; my @presets = $choices->[0]->GetStrings;
@ -1725,25 +1601,20 @@ sub on_extruders_change {
my $extruder_idx = scalar @$choices; my $extruder_idx = scalar @$choices;
EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down($extruder_idx, @_); } ); EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down($extruder_idx, @_); } );
push @$choices, $choice; push @$choices, $choice;
# copy icons from first choice # copy icons from first choice
$choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets;
# insert new choice into sizer # insert new choice into sizer
$self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0); $self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0);
$self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING);
# setup the listener # setup the listener
EVT_COMBOBOX($choice, $choice, sub { EVT_COMBOBOX($choice, $choice, sub {
my ($choice) = @_; my ($choice) = @_;
wxTheApp->CallAfter(sub { wxTheApp->CallAfter(sub {
$self->_on_select_preset('filament', $choice); $self->_on_select_preset('filament', $choice, $extruder_idx);
}); });
}); });
# initialize selection # initialize selection
my $i = first { $choice->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets; wxTheApp->{preset_bundle}->update_platter_filament_ui($extruder_idx, $choice);
$choice->SetSelection($i || 0);
} }
# remove unused choices if any # remove unused choices if any
@ -1757,8 +1628,7 @@ sub on_extruders_change {
} }
sub on_config_change { sub on_config_change {
my $self = shift; my ($self, $config) = @_;
my ($config) = @_;
my $update_scheduled; my $update_scheduled;
foreach my $opt_key (@{$self->{config}->diff($config)}) { foreach my $opt_key (@{$self->{config}->diff($config)}) {
@ -1815,7 +1685,6 @@ sub on_config_change {
sub list_item_deselected { sub list_item_deselected {
my ($self, $event) = @_; my ($self, $event) = @_;
return if $PreventListEvents; return if $PreventListEvents;
if ($self->{list}->GetFirstSelected == -1) { if ($self->{list}->GetFirstSelected == -1) {
$self->select_object(undef); $self->select_object(undef);
$self->{canvas}->Refresh; $self->{canvas}->Refresh;
@ -1827,7 +1696,6 @@ sub list_item_deselected {
sub list_item_selected { sub list_item_selected {
my ($self, $event) = @_; my ($self, $event) = @_;
return if $PreventListEvents; return if $PreventListEvents;
my $obj_idx = $event->GetIndex; my $obj_idx = $event->GetIndex;
$self->select_object($obj_idx); $self->select_object($obj_idx);
$self->{canvas}->Refresh; $self->{canvas}->Refresh;
@ -1859,19 +1727,18 @@ sub filament_color_box_lmouse_down
my $dialog = Wx::ColourDialog->new($self->GetFrame, $data); my $dialog = Wx::ColourDialog->new($self->GetFrame, $data);
if ($dialog->ShowModal == wxID_OK) { if ($dialog->ShowModal == wxID_OK) {
my $cfg = Slic3r::Config->new; my $cfg = Slic3r::Config->new;
my $colors = $self->GetFrame->config->get('extruder_colour'); my $colors = wxTheApp->{preset_bundle}->full_config->get('extruder_colour');
$colors->[$extruder_idx] = $dialog->GetColourData->GetColour->GetAsString(wxC2S_HTML_SYNTAX); $colors->[$extruder_idx] = $dialog->GetColourData->GetColour->GetAsString(wxC2S_HTML_SYNTAX);
$cfg->set('extruder_colour', $colors); $cfg->set('extruder_colour', $colors);
$self->GetFrame->{options_tabs}{printer}->load_config($cfg); $self->GetFrame->{options_tabs}{printer}->load_config($cfg);
$self->update_filament_colors_preview($extruder_idx); wxTheApp->{preset_bundle}->update_platter_filament_ui($extruder_idx, $combobox);
} }
$dialog->Destroy(); $dialog->Destroy();
} }
} }
sub object_cut_dialog { sub object_cut_dialog {
my $self = shift; my ($self, $obj_idx) = @_;
my ($obj_idx) = @_;
if (!defined $obj_idx) { if (!defined $obj_idx) {
($obj_idx, undef) = $self->selected_object; ($obj_idx, undef) = $self->selected_object;
@ -1896,23 +1763,20 @@ sub object_cut_dialog {
} }
sub object_settings_dialog { sub object_settings_dialog {
my $self = shift; my ($self, $obj_idx) = @_;
my ($obj_idx) = @_; ($obj_idx, undef) = $self->selected_object if !defined $obj_idx;
if (!defined $obj_idx) {
($obj_idx, undef) = $self->selected_object;
}
my $model_object = $self->{model}->objects->[$obj_idx]; my $model_object = $self->{model}->objects->[$obj_idx];
# validate config before opening the settings dialog because # validate config before opening the settings dialog because
# that dialog can't be closed if validation fails, but user # that dialog can't be closed if validation fails, but user
# can't fix any error which is outside that dialog # can't fix any error which is outside that dialog
return unless $self->validate_config; eval { wxTheApp->{preset_bundle}->full_config->validate; };
return if Slic3r::GUI::catch_error($_[0]);
my $dlg = Slic3r::GUI::Plater::ObjectSettingsDialog->new($self, my $dlg = Slic3r::GUI::Plater::ObjectSettingsDialog->new($self,
object => $self->{objects}[$obj_idx], object => $self->{objects}[$obj_idx],
model_object => $model_object, model_object => $model_object,
config => $self->GetFrame->config, config => wxTheApp->{preset_bundle}->full_config,
); );
$self->pause_background_process; $self->pause_background_process;
$dlg->ShowModal; $dlg->ShowModal;
@ -1963,9 +1827,9 @@ sub object_list_changed {
for grep $self->{"btn_$_"}, qw(reslice export_gcode print send_gcode); for grep $self->{"btn_$_"}, qw(reslice export_gcode print send_gcode);
} }
# Selection of an active 3D object changed.
sub selection_changed { sub selection_changed {
my $self = shift; my ($self) = @_;
my ($obj_idx, $object) = $self->selected_object; my ($obj_idx, $object) = $self->selected_object;
my $have_sel = defined $obj_idx; my $have_sel = defined $obj_idx;
@ -2026,7 +1890,6 @@ sub select_object {
$_->selected(0) for @{ $self->{objects} }; $_->selected(0) for @{ $self->{objects} };
if (defined $obj_idx) { if (defined $obj_idx) {
$self->{objects}->[$obj_idx]->selected(1); $self->{objects}->[$obj_idx]->selected(1);
# We use this flag to avoid circular event handling # We use this flag to avoid circular event handling
# Select() happens to fire a wxEVT_LIST_ITEM_SELECTED on Windows, # Select() happens to fire a wxEVT_LIST_ITEM_SELECTED on Windows,
# whose event handler calls this method again and again and again # whose event handler calls this method again and again and again
@ -2040,26 +1903,13 @@ sub select_object {
} }
sub selected_object { sub selected_object {
my $self = shift; my ($self) = @_;
my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} }; my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} };
return undef if !defined $obj_idx; return defined $obj_idx ? ($obj_idx, $self->{objects}[$obj_idx]) : undef;
return ($obj_idx, $self->{objects}[$obj_idx]),
}
sub validate_config {
my $self = shift;
eval {
$self->GetFrame->config->validate;
};
return 0 if Slic3r::GUI::catch_error($self);
return 1;
} }
sub statusbar { sub statusbar {
my $self = shift; return $_[0]->GetFrame->{statusbar};
return $self->GetFrame->{statusbar};
} }
sub object_menu { sub object_menu {
@ -2185,24 +2035,19 @@ use Wx::DND;
use base 'Wx::FileDropTarget'; use base 'Wx::FileDropTarget';
sub new { sub new {
my $class = shift; my ($class, $window) = @_;
my ($window) = @_;
my $self = $class->SUPER::new; my $self = $class->SUPER::new;
$self->{window} = $window; $self->{window} = $window;
return $self; return $self;
} }
sub OnDropFiles { sub OnDropFiles {
my $self = shift; my ($self, $x, $y, $filenames) = @_;
my ($x, $y, $filenames) = @_;
# stop scalars leaking on older perl # stop scalars leaking on older perl
# https://rt.perl.org/rt3/Public/Bug/Display.html?id=70602 # https://rt.perl.org/rt3/Public/Bug/Display.html?id=70602
@_ = (); @_ = ();
# only accept STL, OBJ and AMF files # only accept STL, OBJ and AMF files
return 0 if grep !/\.(?:[sS][tT][lL]|[oO][bB][jJ]|[aA][mM][fF](?:\.[xX][mM][lL])?|[pP][rR][uU][sS][aA])$/, @$filenames; return 0 if grep !/\.(?:[sS][tT][lL]|[oO][bB][jJ]|[aA][mM][fF](?:\.[xX][mM][lL])?|[pP][rR][uU][sS][aA])$/, @$filenames;
$self->{window}->load_files($filenames); $self->{window}->load_files($filenames);
} }
@ -2218,10 +2063,8 @@ has 'selected' => (is => 'rw', default => sub { 0 });
sub make_thumbnail { sub make_thumbnail {
my ($self, $model, $obj_idx) = @_; my ($self, $model, $obj_idx) = @_;
# make method idempotent # make method idempotent
$self->thumbnail->clear; $self->thumbnail->clear;
# raw_mesh is the non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. # raw_mesh is the non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
my $mesh = $model->objects->[$obj_idx]->raw_mesh; my $mesh = $model->objects->[$obj_idx]->raw_mesh;
#FIXME The "correct" variant could be extremely slow. #FIXME The "correct" variant could be extremely slow.
@ -2237,7 +2080,6 @@ sub make_thumbnail {
my $convex_hull = Slic3r::ExPolygon->new($mesh->convex_hull); my $convex_hull = Slic3r::ExPolygon->new($mesh->convex_hull);
$self->thumbnail->append($convex_hull); $self->thumbnail->append($convex_hull);
# } # }
return $self->thumbnail; return $self->thumbnail;
} }

View File

@ -9,7 +9,7 @@ use utf8;
use List::Util qw(min max first); use List::Util qw(min max first);
use Slic3r::Geometry qw(X Y scale unscale convex_hull); use Slic3r::Geometry qw(X Y scale unscale convex_hull);
use Slic3r::Geometry::Clipper qw(offset JT_ROUND intersection_pl); use Slic3r::Geometry::Clipper qw(offset JT_ROUND intersection_pl);
use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL); use Wx qw(wxTheApp :misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL);
use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE); use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE);
use base 'Wx::Panel'; use base 'Wx::Panel';
@ -102,7 +102,7 @@ sub repaint {
} }
# draw print center # draw print center
if (@{$self->{objects}} && $Slic3r::GUI::Settings->{_}{autocenter}) { if (@{$self->{objects}} && wxTheApp->{app_config}->get("autocenter")) {
my $center = $self->unscaled_point_to_pixel($self->{print_center}); my $center = $self->unscaled_point_to_pixel($self->{print_center});
$dc->SetPen($self->{print_center_pen}); $dc->SetPen($self->{print_center_pen});
$dc->DrawLine($center->[X], 0, $center->[X], $size[Y]); $dc->DrawLine($center->[X], 0, $center->[X], $size[Y]);
@ -197,7 +197,6 @@ sub repaint {
sub mouse_event { sub mouse_event {
my ($self, $event) = @_; my ($self, $event) = @_;
my $pos = $event->GetPosition; my $pos = $event->GetPosition;
my $point = $self->point_to_model_units([ $pos->x, $pos->y ]); #]] my $point = $self->point_to_model_units([ $pos->x, $pos->y ]); #]]
if ($event->ButtonDown) { if ($event->ButtonDown) {
@ -257,7 +256,7 @@ sub mouse_event {
} }
sub update_bed_size { sub update_bed_size {
my $self = shift; my ($self) = @_;
# when the canvas is not rendered yet, its GetSize() method returns 0,0 # when the canvas is not rendered yet, its GetSize() method returns 0,0
my $canvas_size = $self->GetSize; my $canvas_size = $self->GetSize;

View File

@ -42,9 +42,9 @@ sub new {
{ {
$self->{tree_icons} = Wx::ImageList->new(16, 16, 1); $self->{tree_icons} = Wx::ImageList->new(16, 16, 1);
$tree->AssignImageList($self->{tree_icons}); $tree->AssignImageList($self->{tree_icons});
$self->{tree_icons}->Add(Wx::Bitmap->new($Slic3r::var->("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT
$self->{tree_icons}->Add(Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH
$self->{tree_icons}->Add(Wx::Bitmap->new($Slic3r::var->("plugin.png"), wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("plugin.png"), wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH
my $rootId = $tree->AddRoot("Object", ICON_OBJECT); my $rootId = $tree->AddRoot("Object", ICON_OBJECT);
$tree->SetPlData($rootId, { type => 'object' }); $tree->SetPlData($rootId, { type => 'object' });
@ -58,13 +58,13 @@ sub new {
$self->{btn_split} = Wx::Button->new($self, -1, "Split part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $self->{btn_split} = Wx::Button->new($self, -1, "Split part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
$self->{btn_move_up} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT); $self->{btn_move_up} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT);
$self->{btn_move_down} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT); $self->{btn_move_down} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT);
$self->{btn_load_part}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG)); $self->{btn_load_part}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG));
$self->{btn_load_modifier}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG)); $self->{btn_load_modifier}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG));
$self->{btn_load_lambda_modifier}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG)); $self->{btn_load_lambda_modifier}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG));
$self->{btn_delete}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_delete.png"), wxBITMAP_TYPE_PNG)); $self->{btn_delete}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG));
$self->{btn_split}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("shape_ungroup.png"), wxBITMAP_TYPE_PNG)); $self->{btn_split}->SetBitmap(Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG));
$self->{btn_move_up}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("bullet_arrow_up.png"), wxBITMAP_TYPE_PNG)); $self->{btn_move_up}->SetBitmap(Wx::Bitmap->new(Slic3r::var("bullet_arrow_up.png"), wxBITMAP_TYPE_PNG));
$self->{btn_move_down}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("bullet_arrow_down.png"), wxBITMAP_TYPE_PNG)); $self->{btn_move_down}->SetBitmap(Wx::Bitmap->new(Slic3r::var("bullet_arrow_down.png"), wxBITMAP_TYPE_PNG));
# buttons sizer # buttons sizer
my $buttons_sizer = Wx::GridSizer->new(2, 3); my $buttons_sizer = Wx::GridSizer->new(2, 3);
@ -312,7 +312,7 @@ sub selection_changed {
$config = $self->{model_object}->config; $config = $self->{model_object}->config;
} }
# get default values # get default values
my $default_config = Slic3r::Config->new_from_defaults(@opt_keys); my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys);
# append default extruder # append default extruder
push @opt_keys, 'extruder'; push @opt_keys, 'extruder';
@ -490,12 +490,12 @@ sub CanClose {
# validate options before allowing user to dismiss the dialog # validate options before allowing user to dismiss the dialog
# the validate method only works on full configs so we have # the validate method only works on full configs so we have
# to merge our settings with the default ones # to merge our settings with the default ones
my $config = Slic3r::Config->merge($self->GetParent->GetParent->GetParent->GetParent->GetParent->config, $self->model_object->config); my $config = $self->GetParent->GetParent->GetParent->GetParent->GetParent->config->clone;
eval { eval {
$config->apply($self->model_object->config);
$config->validate; $config->validate;
}; };
return 0 if Slic3r::GUI::catch_error($self); return ! Slic3r::GUI::catch_error($self);
return 1;
} }
sub PartsChanged { sub PartsChanged {

View File

@ -46,7 +46,7 @@ sub new {
# option selector # option selector
{ {
# create the button # create the button
my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("add.png"), wxBITMAP_TYPE_PNG), my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE);
EVT_LEFT_DOWN($btn, sub { EVT_LEFT_DOWN($btn, sub {
my $menu = Wx::Menu->new; my $menu = Wx::Menu->new;
@ -146,7 +146,7 @@ sub update_optgroup {
# disallow deleting fixed options # disallow deleting fixed options
return undef if $self->{fixed_options}{$opt_key}; return undef if $self->{fixed_options}{$opt_key};
my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG), my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE);
EVT_BUTTON($self, $btn, sub { EVT_BUTTON($self, $btn, sub {
$self->{config}->erase($opt_key); $self->{config}->erase($opt_key);

View File

@ -10,6 +10,7 @@ sub new {
my $self = $class->SUPER::new($parent, -1, "Preferences", wxDefaultPosition, wxDefaultSize); my $self = $class->SUPER::new($parent, -1, "Preferences", wxDefaultPosition, wxDefaultSize);
$self->{values} = {}; $self->{values} = {};
my $app_config = wxTheApp->{app_config};
my $optgroup; my $optgroup;
$optgroup = Slic3r::GUI::OptionsGroup->new( $optgroup = Slic3r::GUI::OptionsGroup->new(
parent => $self, parent => $self,
@ -25,7 +26,7 @@ sub new {
# type => 'bool', # type => 'bool',
# label => 'Check for updates', # label => 'Check for updates',
# tooltip => 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.', # tooltip => 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.',
# default => $Slic3r::GUI::Settings->{_}{version_check} // 1, # default => $app_config->get("version_check") // 1,
# readonly => !wxTheApp->have_version_check, # readonly => !wxTheApp->have_version_check,
# )); # ));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
@ -33,36 +34,43 @@ sub new {
type => 'bool', type => 'bool',
label => 'Remember output directory', label => 'Remember output directory',
tooltip => 'If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files.', tooltip => 'If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files.',
default => $Slic3r::GUI::Settings->{_}{remember_output_path}, default => $app_config->get("remember_output_path"),
)); ));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'autocenter', opt_id => 'autocenter',
type => 'bool', type => 'bool',
label => 'Auto-center parts', label => 'Auto-center parts',
tooltip => 'If this is enabled, Slic3r will auto-center objects around the print bed center.', tooltip => 'If this is enabled, Slic3r will auto-center objects around the print bed center.',
default => $Slic3r::GUI::Settings->{_}{autocenter}, default => $app_config->get("autocenter"),
)); ));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'background_processing', opt_id => 'background_processing',
type => 'bool', type => 'bool',
label => 'Background processing', label => 'Background processing',
tooltip => 'If this is enabled, Slic3r will pre-process objects as soon as they\'re loaded in order to save time when exporting G-code.', tooltip => 'If this is enabled, Slic3r will pre-process objects as soon as they\'re loaded in order to save time when exporting G-code.',
default => $Slic3r::GUI::Settings->{_}{background_processing}, default => $app_config->get("background_processing"),
readonly => !$Slic3r::have_threads,
)); ));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'no_controller', opt_id => 'no_controller',
type => 'bool', type => 'bool',
label => 'Disable USB/serial connection', label => 'Disable USB/serial connection',
tooltip => 'Disable communication with the printer over a serial / USB cable. This simplifies the user interface in case the printer is never attached to the computer.', tooltip => 'Disable communication with the printer over a serial / USB cable. This simplifies the user interface in case the printer is never attached to the computer.',
default => $Slic3r::GUI::Settings->{_}{no_controller}, default => $app_config->get("no_controller"),
)); ));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'no_defaults', opt_id => 'no_defaults',
type => 'bool', type => 'bool',
label => 'Suppress "- default -" presets', label => 'Suppress "- default -" presets',
tooltip => 'Suppress "- default -" presets in the Print / Filament / Printer selections once there are any other valid presets available.', tooltip => 'Suppress "- default -" presets in the Print / Filament / Printer selections once there are any other valid presets available.',
default => $Slic3r::GUI::Settings->{_}{no_defaults}, default => $app_config->get("no_defaults"),
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'show_incompatible_presets',
type => 'bool',
label => 'Show incompatible print and filament presets',
tooltip => 'When checked, the print and filament presets are shown in the preset editor even ' .
'if they are marked as incompatible with the active printer',
default => $app_config->get("show_incompatible_presets"),
)); ));
my $sizer = Wx::BoxSizer->new(wxVERTICAL); my $sizer = Wx::BoxSizer->new(wxVERTICAL);
@ -79,14 +87,15 @@ sub new {
} }
sub _accept { sub _accept {
my $self = shift; my ($self) = @_;
if (defined($self->{values}{no_controller})) { if (defined($self->{values}{no_controller}) ||
defined($self->{values}{no_defaults})) {
Slic3r::GUI::warning_catcher($self)->("You need to restart Slic3r to make the changes effective."); Slic3r::GUI::warning_catcher($self)->("You need to restart Slic3r to make the changes effective.");
} }
$Slic3r::GUI::Settings->{_}{$_} = $self->{values}{$_} for keys %{$self->{values}}; my $app_config = wxTheApp->{app_config};
wxTheApp->save_settings; $app_config->set($_, $self->{values}{$_}) for keys %{$self->{values}};
$self->EndModal(wxID_OK); $self->EndModal(wxID_OK);
$self->Close; # needed on Linux $self->Close; # needed on Linux

File diff suppressed because it is too large Load Diff

View File

@ -255,13 +255,4 @@ sub make_wipe_tower {
$self->set_step_done(STEP_WIPE_TOWER); $self->set_step_done(STEP_WIPE_TOWER);
} }
# Wrapper around the C++ Slic3r::Print::validate()
# to produce a Perl exception without a hang-up on some Strawberry perls.
sub validate
{
my $self = shift;
my $err = $self->_validate;
die $err . "\n" if (defined($err) && $err ne '');
}
1; 1;

View File

@ -11,9 +11,6 @@ use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union
use Slic3r::Print::State ':steps'; use Slic3r::Print::State ':steps';
use Slic3r::Surface ':types'; use Slic3r::Surface ':types';
# If enabled, phases of prepare_infill will be written into SVG files to an "out" directory.
our $SLIC3R_DEBUG_SLICE_PROCESSING = 0;
sub layers { sub layers {
my $self = shift; my $self = shift;
return [ map $self->get_layer($_), 0..($self->layer_count - 1) ]; return [ map $self->get_layer($_), 0..($self->layer_count - 1) ];

View File

@ -92,12 +92,12 @@ if ($opt{save}) {
if (@{$cli_config->get_keys} > 0) { if (@{$cli_config->get_keys} > 0) {
$cli_config->save($opt{save}); $cli_config->save($opt{save});
} else { } else {
Slic3r::Config->new_from_defaults->save($opt{save}); Slic3r::Config::new_from_defaults->save($opt{save});
} }
} }
# apply command line config on top of default config # apply command line config on top of default config
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->apply($cli_config); $config->apply($cli_config);
# launch GUI # launch GUI
@ -105,7 +105,7 @@ my $gui;
if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") { if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") {
{ {
no warnings 'once'; no warnings 'once';
$Slic3r::GUI::datadir = $opt{datadir} // ''; $Slic3r::GUI::datadir = Slic3r::decode_path($opt{datadir} // '');
$Slic3r::GUI::no_controller = $opt{no_controller}; $Slic3r::GUI::no_controller = $opt{no_controller};
$Slic3r::GUI::no_plater = $opt{no_plater}; $Slic3r::GUI::no_plater = $opt{no_plater};
$Slic3r::GUI::autosave = $opt{autosave}; $Slic3r::GUI::autosave = $opt{autosave};
@ -236,16 +236,7 @@ if (@ARGV) { # slicing from command line
sub usage { sub usage {
my ($exit_code) = @_; my ($exit_code) = @_;
my $config = Slic3r::Config::new_from_defaults->as_hash;
my $config = Slic3r::Config->new_from_defaults->as_hash;
my $j = '';
if ($Slic3r::have_threads) {
$j = <<"EOF";
-j, --threads <num> Number of threads to use (1+, default: $config->{threads})
EOF
}
print <<"EOF"; print <<"EOF";
Slic3r $Slic3r::VERSION is a STL-to-GCODE translator for RepRap 3D printers Slic3r $Slic3r::VERSION is a STL-to-GCODE translator for RepRap 3D printers
written by Alessandro Ranellucci <aar\@cpan.org> - http://slic3r.org/ written by Alessandro Ranellucci <aar\@cpan.org> - http://slic3r.org/
@ -270,8 +261,8 @@ Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ...
them as <name>_upper.stl and <name>_lower.stl them as <name>_upper.stl and <name>_lower.stl
--split Split the shells contained in given STL file into several STL files --split Split the shells contained in given STL file into several STL files
--info Output information about the supplied file(s) and exit --info Output information about the supplied file(s) and exit
-j, --threads <num> Number of threads to use (1+, default: $config->{threads})
$j
GUI options: GUI options:
--gui Forces the GUI launch instead of command line slicing (if you --gui Forces the GUI launch instead of command line slicing (if you
supply a model file, it will be loaded into the plater) supply a model file, it will be loaded into the plater)

View File

@ -13,7 +13,7 @@ use Slic3r;
use Slic3r::Test; use Slic3r::Test;
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('avoid_crossing_perimeters', 2); $config->set('avoid_crossing_perimeters', 2);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2);
ok my $gcode = Slic3r::Test::gcode($print), "no crash with avoid_crossing_perimeters and multiple objects"; ok my $gcode = Slic3r::Test::gcode($print), "no crash with avoid_crossing_perimeters and multiple objects";

View File

@ -109,7 +109,7 @@ sub check_angle {
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('top_solid_layers', 0); # to prevent bridging on sparse infill $config->set('top_solid_layers', 0); # to prevent bridging on sparse infill
$config->set('bridge_speed', 99); $config->set('bridge_speed', 99);

View File

@ -54,7 +54,7 @@ plan tests => 8;
'infill is only present in correct number of layers'; 'infill is only present in correct number of layers';
}; };
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('layer_height', 0.2); $config->set('layer_height', 0.2);
$config->set('first_layer_height', 0.2); $config->set('first_layer_height', 0.2);
$config->set('nozzle_diameter', [0.5]); $config->set('nozzle_diameter', [0.5]);
@ -73,7 +73,7 @@ plan tests => 8;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('layer_height', 0.2); $config->set('layer_height', 0.2);
$config->set('first_layer_height', 0.2); $config->set('first_layer_height', 0.2);
$config->set('nozzle_diameter', [0.5]); $config->set('nozzle_diameter', [0.5]);
@ -98,7 +98,7 @@ plan tests => 8;
# the following needs to be adapted to the new API # the following needs to be adapted to the new API
if (0) { if (0) {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('solid_layers', 0); $config->set('solid_layers', 0);
$config->set('bottom_solid_layers', 0); $config->set('bottom_solid_layers', 0);

View File

@ -12,7 +12,7 @@ use Slic3r;
use Slic3r::Test; use Slic3r::Test;
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('perimeter_extrusion_width', '250%'); $config->set('perimeter_extrusion_width', '250%');
ok $config->validate, 'percent extrusion width is validated'; ok $config->validate, 'percent extrusion width is validated';
} }

View File

@ -42,7 +42,7 @@ my $print_time1 = 100 / (3000 / 60); # 2 sec
my $gcode2 = $gcode1 . "G1 X0 E1 F3000\n"; my $gcode2 = $gcode1 . "G1 X0 E1 F3000\n";
my $print_time2 = 2 * $print_time1; # 4 sec my $print_time2 = 2 * $print_time1; # 4 sec
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
# Default cooling settings. # Default cooling settings.
$config->set('bridge_fan_speed', [ 100 ]); $config->set('bridge_fan_speed', [ 100 ]);
$config->set('cooling', [ 1 ]); $config->set('cooling', [ 1 ]);
@ -138,7 +138,7 @@ $config->set('disable_fan_first_layers', [ 0 ]);
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('cooling', [ 1 ]); $config->set('cooling', [ 1 ]);
$config->set('bridge_fan_speed', [ 100 ]); $config->set('bridge_fan_speed', [ 100 ]);
$config->set('fan_below_layer_time', [ 0 ]); $config->set('fan_below_layer_time', [ 0 ]);
@ -172,7 +172,7 @@ $config->set('disable_fan_first_layers', [ 0 ]);
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('cooling', [ 1 ]); $config->set('cooling', [ 1 ]);
$config->set('fan_below_layer_time', [ 0 ]); $config->set('fan_below_layer_time', [ 0 ]);
$config->set('slowdown_below_layer_time', [ 10 ]); $config->set('slowdown_below_layer_time', [ 10 ]);

View File

@ -1,4 +1,4 @@
use Test::More tests => 15; use Test::More tests => 49;
use strict; use strict;
use warnings; use warnings;
@ -13,7 +13,7 @@ use Slic3r;
use Slic3r::Test; use Slic3r::Test;
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
my $test = sub { my $test = sub {
my ($conf) = @_; my ($conf) = @_;
@ -47,15 +47,30 @@ use Slic3r::Test;
{ {
my $parser = Slic3r::GCode::PlaceholderParser->new; my $parser = Slic3r::GCode::PlaceholderParser->new;
$parser->apply_config(my $config = Slic3r::Config->new_from_defaults); $parser->apply_config(my $config = Slic3r::Config::new_from_defaults);
$parser->set('foo' => '0'); $parser->set('foo' => 0);
$parser->set('bar' => 2);
is $parser->process('[temperature_[foo]]'), is $parser->process('[temperature_[foo]]'),
$config->temperature->[0], $config->temperature->[0],
"nested config options"; "nested config options (legacy syntax)";
is $parser->process('{temperature[foo]}'),
$config->temperature->[0],
"array reference";
is $parser->process("test [ temperature_ [foo] ] \n hu"),
"test " . $config->temperature->[0] . " \n hu",
"whitespaces and newlines are maintained";
is $parser->process('{2*3}'), '6', 'math: 2*3';
is $parser->process('{2*3/6}'), '1', 'math: 2*3/6';
is $parser->process('{2*3/12}'), '0', 'math: 2*3/12';
ok abs($parser->process('{2.*3/12}') - 0.5) < 1e-7, 'math: 2.*3/12';
is $parser->process('{2*(3-12)}'), '-18', 'math: 2*(3-12)';
is $parser->process('{2*foo*(3-12)}'), '0', 'math: 2*foo*(3-12)';
is $parser->process('{2*bar*(3-12)}'), '-36', 'math: 2*bar*(3-12)';
ok abs($parser->process('{2.5*bar*(3-12)}') - -45) < 1e-7, 'math: 2.5*bar*(3-12)';
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('output_filename_format', 'ts_[travel_speed]_lh_[layer_height].gcode'); $config->set('output_filename_format', 'ts_[travel_speed]_lh_[layer_height].gcode');
$config->set('start_gcode', "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n"); $config->set('start_gcode', "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n");
my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
@ -90,25 +105,44 @@ use Slic3r::Test;
ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for second extruder'; ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for second extruder';
} }
$config->set('start_gcode', qq! my @start_gcode = (qq!
;__temp0:[first_layer_temperature_0]__ ;__temp0:[first_layer_temperature_0]__
;__temp1:[first_layer_temperature_1]__ ;__temp1:[first_layer_temperature_1]__
;__temp2:[first_layer_temperature_2]__ ;__temp2:[first_layer_temperature_2]__
!, qq!
;__temp0:{first_layer_temperature[0]}__
;__temp1:{first_layer_temperature[1]}__
;__temp2:{first_layer_temperature[2]}__
!); !);
my @syntax_description = (' (legacy syntax)', ' (new syntax)');
for my $i (0, 1) {
$config->set('start_gcode', $start_gcode[$i]);
{ {
my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my $gcode = Slic3r::Test::gcode($print); my $gcode = Slic3r::Test::gcode($print);
# we use the [infill_extruder] placeholder to make sure this test doesn't # we use the [infill_extruder] placeholder to make sure this test doesn't
# catch a false positive caused by the unparsed start G-code option itself # catch a false positive caused by the unparsed start G-code option itself
# being embedded in the G-code # being embedded in the G-code
ok $gcode =~ /temp0:200/, 'temperature placeholder for first extruder correctly populated'; ok $gcode =~ /temp0:200/, 'temperature placeholder for first extruder correctly populated' . $syntax_description[$i];
ok $gcode =~ /temp1:205/, 'temperature placeholder for second extruder correctly populated'; ok $gcode =~ /temp1:205/, 'temperature placeholder for second extruder correctly populated' . $syntax_description[$i];
ok $gcode =~ /temp2:200/, 'temperature placeholder for unused extruder populated with first value'; ok $gcode =~ /temp2:200/, 'temperature placeholder for unused extruder populated with first value' . $syntax_description[$i];
}
}
$config->set('start_gcode', qq!
;substitution:{if infill_extruder==1}extruder1
{elsif infill_extruder==2}extruder2
{else}extruder3{endif}
!);
{
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my $gcode = Slic3r::Test::gcode($print);
ok $gcode =~ /substitution:extruder1/, 'if / else / endif - first block returned';
} }
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('before_layer_gcode', ';BEFORE [layer_num]'); $config->set('before_layer_gcode', ';BEFORE [layer_num]');
$config->set('layer_gcode', ';CHANGE [layer_num]'); $config->set('layer_gcode', ';CHANGE [layer_num]');
$config->set('support_material', 1); $config->set('support_material', 1);
@ -132,4 +166,66 @@ use Slic3r::Test;
'layer_num grows continously'; # i.e. no duplicates or regressions 'layer_num grows continously'; # i.e. no duplicates or regressions
} }
{
my $config = Slic3r::Config->new;
$config->set('start_gcode', qq!
;substitution:{if infill_extruder==1}if block
{elsif infill_extruder==2}elsif block 1
{elsif infill_extruder==3}elsif block 2
{elsif infill_extruder==4}elsif block 3
{else}endif block{endif}
!);
my @returned = ('', 'if block', 'elsif block 1', 'elsif block 2', 'elsif block 3', 'endif block');
for my $i (1,2,3,4,5) {
$config->set('infill_extruder', $i);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my $gcode = Slic3r::Test::gcode($print);
my $found_other = 0;
for my $j (1,2,3,4,5) {
next if $i == $j;
$found_other = 1 if $gcode =~ /substitution:$returned[$j]/;
}
ok $gcode =~ /substitution:$returned[$i]/, 'if / else / endif - ' . $returned[$i] . ' returned';
ok !$found_other, 'if / else / endif - only ' . $returned[$i] . ' returned';
}
}
{
my $config = Slic3r::Config->new;
$config->set('start_gcode',
';substitution:{if infill_extruder==1}{if perimeter_extruder==1}block11{else}block12{endif}' .
'{elsif infill_extruder==2}{if perimeter_extruder==1}block21{else}block22{endif}' .
'{else}{if perimeter_extruder==1}block31{else}block32{endif}{endif}:end');
for my $i (1,2,3) {
$config->set('infill_extruder', $i);
for my $j (1,2) {
$config->set('perimeter_extruder', $j);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my $gcode = Slic3r::Test::gcode($print);
ok $gcode =~ /substitution:block$i$j:end/, "two level if / else / endif - block$i$j returned";
}
}
}
{
my $config = Slic3r::Config->new;
$config->set('start_gcode',
';substitution:{if notes=="MK2"}MK2{elsif notes=="MK3"}MK3{else}MK1{endif}:end');
for my $printer_name ("MK2", "MK3", "MK1") {
$config->set('notes', $printer_name);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my $gcode = Slic3r::Test::gcode($print);
ok $gcode =~ /substitution:$printer_name:end/, "printer name $printer_name matched";
}
}
{
my $config = Slic3r::Config::new_from_defaults;
$config->set('complete_objects', 1);
$config->set('between_objects_gcode', '_MY_CUSTOM_GCODE_');
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 3);
my $gcode = Slic3r::Test::gcode($print);
is scalar(() = $gcode =~ /^_MY_CUSTOM_GCODE_/gm), 2, 'between_objects_gcode is applied correctly';
}
__END__ __END__

View File

@ -163,7 +163,7 @@ SKIP:
} }
for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('fill_pattern', $pattern); $config->set('fill_pattern', $pattern);
$config->set('external_fill_pattern', $pattern); $config->set('external_fill_pattern', $pattern);
$config->set('perimeters', 1); $config->set('perimeters', 1);
@ -194,7 +194,7 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('infill_only_where_needed', 1); $config->set('infill_only_where_needed', 1);
$config->set('bottom_solid_layers', 0); $config->set('bottom_solid_layers', 0);
$config->set('infill_extruder', 2); $config->set('infill_extruder', 2);
@ -240,7 +240,7 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('perimeters', 1); $config->set('perimeters', 1);
$config->set('fill_density', 0); $config->set('fill_density', 0);
@ -270,7 +270,7 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('perimeters', 3); $config->set('perimeters', 3);
$config->set('fill_density', 0); $config->set('fill_density', 0);

View File

@ -14,7 +14,7 @@ use Slic3r::Geometry qw(scale PI);
use Slic3r::Test; use Slic3r::Test;
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 1); $config->set('skirts', 1);
$config->set('brim_width', 2); $config->set('brim_width', 2);
$config->set('perimeters', 3); $config->set('perimeters', 3);
@ -41,7 +41,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('bridge_speed', 99); $config->set('bridge_speed', 99);
$config->set('bridge_flow_ratio', 1); $config->set('bridge_flow_ratio', 1);
$config->set('cooling', [ 0 ]); # to prevent speeds from being altered $config->set('cooling', [ 0 ]); # to prevent speeds from being altered

View File

@ -16,7 +16,7 @@ use Slic3r::Surface ':types';
use Slic3r::Test; use Slic3r::Test;
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('perimeter_speed', 66); $config->set('perimeter_speed', 66);
$config->set('external_perimeter_speed', 66); $config->set('external_perimeter_speed', 66);

View File

@ -21,7 +21,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('wipe', [1]); $config->set('wipe', [1]);
$config->set('retract_layer_change', [0]); $config->set('retract_layer_change', [0]);
@ -52,7 +52,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('z_offset', 5); $config->set('z_offset', 5);
$config->set('start_gcode', ''); $config->set('start_gcode', '');
@ -86,7 +86,7 @@ use Slic3r::Test;
# - Z moves are correctly generated for both objects # - Z moves are correctly generated for both objects
# - no travel moves go outside skirt # - no travel moves go outside skirt
# - temperatures are set correctly # - temperatures are set correctly
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('gcode_comments', 1); $config->set('gcode_comments', 1);
$config->set('complete_objects', 1); $config->set('complete_objects', 1);
$config->set('extrusion_axis', 'A'); $config->set('extrusion_axis', 'A');
@ -130,7 +130,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('retract_length', [1000000]); $config->set('retract_length', [1000000]);
$config->set('use_relative_e_distances', 1); $config->set('use_relative_e_distances', 1);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
@ -162,7 +162,7 @@ use Slic3r::Test;
}; };
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('gcode_flavor', 'sailfish'); $config->set('gcode_flavor', 'sailfish');
$config->set('raft_layers', 3); $config->set('raft_layers', 3);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
@ -170,21 +170,21 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('gcode_flavor', 'sailfish'); $config->set('gcode_flavor', 'sailfish');
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2);
$test->($print, 'two copies of single object'); $test->($print, 'two copies of single object');
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('gcode_flavor', 'sailfish'); $config->set('gcode_flavor', 'sailfish');
my $print = Slic3r::Test::init_print(['20mm_cube','20mm_cube'], config => $config); my $print = Slic3r::Test::init_print(['20mm_cube','20mm_cube'], config => $config);
$test->($print, 'two objects'); $test->($print, 'two objects');
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('gcode_flavor', 'sailfish'); $config->set('gcode_flavor', 'sailfish');
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale_xyz => [1,1, 1/(20/$config->layer_height) ]); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale_xyz => [1,1, 1/(20/$config->layer_height) ]);
$test->($print, 'one layer object'); $test->($print, 'one layer object');
@ -192,7 +192,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('start_gcode', 'START:[input_filename]'); $config->set('start_gcode', 'START:[input_filename]');
my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my $gcode = Slic3r::Test::gcode($print); my $gcode = Slic3r::Test::gcode($print);
@ -200,7 +200,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('spiral_vase', 1); $config->set('spiral_vase', 1);
my $print = Slic3r::Test::init_print('cube_with_hole', config => $config); my $print = Slic3r::Test::init_print('cube_with_hole', config => $config);
@ -220,7 +220,7 @@ use Slic3r::Test;
{ {
# Tests that the Repetier flavor produces M201 Xnnn Ynnn for resetting # Tests that the Repetier flavor produces M201 Xnnn Ynnn for resetting
# acceleration, also that M204 Snnn syntax is not generated. # acceleration, also that M204 Snnn syntax is not generated.
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('gcode_flavor', 'repetier'); $config->set('gcode_flavor', 'repetier');
$config->set('default_acceleration', 1337); $config->set('default_acceleration', 1337);
my $print = Slic3r::Test::init_print('cube_with_hole', config => $config); my $print = Slic3r::Test::init_print('cube_with_hole', config => $config);

View File

@ -13,7 +13,7 @@ use Slic3r;
use Slic3r::Test qw(_eq); use Slic3r::Test qw(_eq);
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
my $test = sub { my $test = sub {
my ($conf) = @_; my ($conf) = @_;

View File

@ -15,7 +15,7 @@ use Slic3r::Geometry::Clipper qw(offset);
use Slic3r::Test; 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('solid_infill_extruder', 3); $config->set('solid_infill_extruder', 3);
@ -88,7 +88,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('support_material_extruder', 3); $config->set('support_material_extruder', 3);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
@ -125,7 +125,7 @@ use Slic3r::Test;
$upper_config->set('extruder', 2); $upper_config->set('extruder', 2);
$upper_config->set('bottom_solid_layers', 1); $upper_config->set('bottom_solid_layers', 1);
$upper_config->set('top_solid_layers', 0); $upper_config->set('top_solid_layers', 0);
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('fill_density', 0); $config->set('fill_density', 0);
$config->set('solid_infill_speed', 99); $config->set('solid_infill_speed', 99);
$config->set('top_solid_infill_speed', 99); $config->set('top_solid_infill_speed', 99);
@ -171,7 +171,7 @@ use Slic3r::Test;
my $model = stacked_cubes(); my $model = stacked_cubes();
my $object = $model->objects->[0]; my $object = $model->objects->[0];
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('layer_height', 0.4); $config->set('layer_height', 0.4);
$config->set('first_layer_height', '100%'); $config->set('first_layer_height', '100%');
$config->set('skirts', 0); $config->set('skirts', 0);

View File

@ -156,7 +156,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('fill_density', 0); $config->set('fill_density', 0);
$config->set('perimeters', 3); $config->set('perimeters', 3);
@ -284,7 +284,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('perimeters', 3); $config->set('perimeters', 3);
$config->set('layer_height', 0.4); $config->set('layer_height', 0.4);
@ -314,7 +314,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('nozzle_diameter', [0.4]); $config->set('nozzle_diameter', [0.4]);
$config->set('perimeters', 2); $config->set('perimeters', 2);
$config->set('perimeter_extrusion_width', 0.4); $config->set('perimeter_extrusion_width', 0.4);
@ -372,7 +372,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('perimeters', 3); $config->set('perimeters', 3);
$config->set('layer_height', 0.4); $config->set('layer_height', 0.4);
@ -401,7 +401,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('seam_position', 'random'); $config->set('seam_position', 'random');
my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
ok Slic3r::Test::gcode($print), 'successful generation of G-code with seam_position = random'; ok Slic3r::Test::gcode($print), 'successful generation of G-code with seam_position = random';
@ -410,7 +410,7 @@ use Slic3r::Test;
{ {
my $test = sub { my $test = sub {
my ($model_name) = @_; my ($model_name) = @_;
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('seam_position', 'aligned'); $config->set('seam_position', 'aligned');
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('perimeters', 1); $config->set('perimeters', 1);

View File

@ -14,7 +14,7 @@ use Slic3r::Geometry qw(unscale X Y);
use Slic3r::Test; use Slic3r::Test;
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
my $print_center = [100,100]; my $print_center = [100,100];
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, print_center => $print_center); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, print_center => $print_center);
my @extrusion_points = (); my @extrusion_points = ();
@ -33,7 +33,7 @@ use Slic3r::Test;
{ {
# this represents the aggregate config from presets # this represents the aggregate config from presets
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
# user adds one object to the plater # user adds one object to the plater
my $print = Slic3r::Test::init_print(my $model = Slic3r::Test::model('20mm_cube'), config => $config); my $print = Slic3r::Test::init_print(my $model = Slic3r::Test::model('20mm_cube'), config => $config);

View File

@ -13,7 +13,7 @@ use Slic3r;
use Slic3r::Test qw(_eq); use Slic3r::Test qw(_eq);
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
my $duplicate = 1; my $duplicate = 1;
my $test = sub { my $test = sub {
@ -131,7 +131,7 @@ use Slic3r::Test qw(_eq);
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('start_gcode', ''); # prevent any default priming Z move from affecting our lift detection $config->set('start_gcode', ''); # prevent any default priming Z move from affecting our lift detection
$config->set('retract_length', [0]); $config->set('retract_length', [0]);
$config->set('retract_layer_change', [0]); $config->set('retract_layer_change', [0]);
@ -165,7 +165,7 @@ use Slic3r::Test qw(_eq);
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('use_firmware_retraction', 1); $config->set('use_firmware_retraction', 1);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
@ -188,7 +188,7 @@ use Slic3r::Test qw(_eq);
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('use_firmware_retraction', 1); $config->set('use_firmware_retraction', 1);
$config->set('retract_length', [0]); $config->set('retract_length', [0]);
@ -206,7 +206,7 @@ use Slic3r::Test qw(_eq);
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('start_gcode', ''); $config->set('start_gcode', '');
$config->set('retract_lift', [3, 4]); $config->set('retract_lift', [3, 4]);

View File

@ -14,7 +14,7 @@ use Slic3r::Geometry qw(epsilon);
use Slic3r::Test; use Slic3r::Test;
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('perimeters', 0); $config->set('perimeters', 0);
$config->set('solid_infill_speed', 99); $config->set('solid_infill_speed', 99);
@ -82,7 +82,7 @@ use Slic3r::Test;
# issue #1161 # issue #1161
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('layer_height', 0.3); $config->set('layer_height', 0.3);
$config->set('first_layer_height', '100%'); $config->set('first_layer_height', '100%');
$config->set('bottom_solid_layers', 0); $config->set('bottom_solid_layers', 0);
@ -106,7 +106,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
# we need to check against one perimeter because this test is calibrated # we need to check against one perimeter because this test is calibrated
# (shape, extrusion_width) so that perimeters cover the bottom surfaces of # (shape, extrusion_width) so that perimeters cover the bottom surfaces of
# their lower layer - the test checks that shells are not generated on the # their lower layer - the test checks that shells are not generated on the
@ -137,7 +137,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('perimeters', 3); $config->set('perimeters', 3);
$config->set('cooling', [ 0 ]); # prevent speed alteration $config->set('cooling', [ 0 ]); # prevent speed alteration
$config->set('first_layer_speed', '100%'); # prevent speed alteration $config->set('first_layer_speed', '100%'); # prevent speed alteration
@ -161,7 +161,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('perimeters', 1); $config->set('perimeters', 1);
$config->set('fill_density', 0); $config->set('fill_density', 0);
$config->set('top_solid_layers', 0); $config->set('top_solid_layers', 0);
@ -221,7 +221,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('spiral_vase', 1); $config->set('spiral_vase', 1);
$config->set('perimeters', 1); $config->set('perimeters', 1);
$config->set('fill_density', 0); $config->set('fill_density', 0);
@ -292,7 +292,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('perimeters', 1); $config->set('perimeters', 1);
$config->set('fill_density', 0); $config->set('fill_density', 0);
$config->set('top_solid_layers', 0); $config->set('top_solid_layers', 0);

View File

@ -14,7 +14,7 @@ use Slic3r::Geometry qw(unscale convex_hull);
use Slic3r::Test; use Slic3r::Test;
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 1); $config->set('skirts', 1);
$config->set('skirt_height', 2); $config->set('skirt_height', 2);
$config->set('perimeters', 0); $config->set('perimeters', 0);
@ -46,7 +46,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('perimeters', 0); $config->set('perimeters', 0);
$config->set('top_solid_layers', 0); # to prevent solid shells and their speeds $config->set('top_solid_layers', 0); # to prevent solid shells and their speeds
@ -72,7 +72,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 1); $config->set('skirts', 1);
$config->set('brim_width', 10); $config->set('brim_width', 10);
@ -81,7 +81,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 1); $config->set('skirts', 1);
$config->set('skirt_height', 0); $config->set('skirt_height', 0);
@ -90,7 +90,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('layer_height', 0.4); $config->set('layer_height', 0.4);
$config->set('first_layer_height', 0.4); $config->set('first_layer_height', 0.4);
$config->set('skirts', 1); $config->set('skirts', 1);
@ -135,7 +135,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('min_skirt_length', 20); $config->set('min_skirt_length', 20);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
ok Slic3r::Test::gcode($print), 'no crash when using min_skirt_length'; ok Slic3r::Test::gcode($print), 'no crash when using min_skirt_length';

View File

@ -19,7 +19,7 @@ use Slic3r::Geometry::Clipper qw(diff);
use Slic3r::Test; use Slic3r::Test;
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('support_material', 1); $config->set('support_material', 1);
my @contact_z = my @top_z = (); my @contact_z = my @top_z = ();
@ -77,7 +77,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('raft_layers', 3); $config->set('raft_layers', 3);
$config->set('brim_width', 0); $config->set('brim_width', 0);
$config->set('skirts', 0); $config->set('skirts', 0);
@ -108,7 +108,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('raft_layers', 3); $config->set('raft_layers', 3);
$config->set('support_material_pattern', 'honeycomb'); $config->set('support_material_pattern', 'honeycomb');
@ -153,7 +153,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('layer_height', 0.35); $config->set('layer_height', 0.35);
$config->set('first_layer_height', 0.3); $config->set('first_layer_height', 0.3);
@ -192,7 +192,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('brim_width', 0); $config->set('brim_width', 0);
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('support_material', 1); $config->set('support_material', 1);
@ -232,7 +232,7 @@ use Slic3r::Test;
} }
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('start_gcode', ''); $config->set('start_gcode', '');
$config->set('raft_layers', 8); $config->set('raft_layers', 8);

View File

@ -16,7 +16,7 @@ use Slic3r::Test;
# Disable this until a more robust implementation is provided. It currently # Disable this until a more robust implementation is provided. It currently
# fails on Linux 32bit because some spurious extrudates are generated. # fails on Linux 32bit because some spurious extrudates are generated.
if (0) { if (0) {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
$config->set('layer_height', 0.2); $config->set('layer_height', 0.2);
$config->set('first_layer_height', '100%'); $config->set('first_layer_height', '100%');
$config->set('extrusion_width', 0.5); $config->set('extrusion_width', 0.5);

View File

@ -1,4 +1,4 @@
use Test::More; use Test::More tests => 2;
use strict; use strict;
use warnings; use warnings;
@ -12,11 +12,6 @@ use List::Util qw(first);
use Slic3r; use Slic3r;
use Slic3r::Test; use Slic3r::Test;
if (!$Slic3r::have_threads) {
plan skip_all => "this perl is not compiled with threads";
}
plan tests => 2;
{ {
my $print = Slic3r::Test::init_print('20mm_cube'); my $print = Slic3r::Test::init_print('20mm_cube');
{ {

View File

@ -1,58 +0,0 @@
#!/usr/bin/perl
# This script extracts a full active config from a config bundle.
# (Often users reporting issues don't attach plain configs, but
# bundles...)
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use Getopt::Long qw(:config no_auto_abbrev);
use Slic3r;
use Slic3r::Test;
$|++;
my %opt = ();
{
my %options = (
'help' => sub { usage() },
'output=s' => \$opt{output},
);
GetOptions(%options) or usage(1);
$ARGV[0] or usage(1);
}
($ARGV[0] && $opt{output}) or usage(1);
{
my $bundle_ini = Slic3r::Config->read_ini($ARGV[0])
or die "Failed to read $ARGV[0]\n";
my $config_ini = { _ => {} };
foreach my $section (qw(print filament printer)) {
my $preset_name = $bundle_ini->{presets}{$section};
$preset_name =~ s/\.ini$//;
my $preset = $bundle_ini->{"$section:$preset_name"}
or die "Failed to find preset $preset_name in bundle\n";
$config_ini->{_}{$_} = $preset->{$_} for keys %$preset;
}
Slic3r::Config->write_ini($opt{output}, $config_ini);
}
sub usage {
my ($exit_code) = @_;
print <<"EOF";
Usage: config-bundle-to-config.pl --output config.ini bundle.ini
EOF
exit ($exit_code || 0);
}
__END__

View File

@ -33,7 +33,7 @@ my %opt = ();
my $model = Slic3r::Model->read_from_file($ARGV[0]); my $model = Slic3r::Model->read_from_file($ARGV[0]);
# load config # load config
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config::new_from_defaults;
if ($opt{load}) { if ($opt{load}) {
$config->apply(Slic3r::Config->load($opt{load})); $config->apply(Slic3r::Config->load($opt{load}));
} }

BIN
var/flag-green-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

BIN
var/flag-red-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

View File

@ -26,6 +26,7 @@ include_directories(${LIBDIR}/libslic3r)
if(WIN32) if(WIN32)
# BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking. # BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking.
add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB) add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB)
# -D_ITERATOR_DEBUG_LEVEL)
endif() endif()
add_definitions(-DwxUSE_UNICODE -D_UNICODE -DUNICODE) add_definitions(-DwxUSE_UNICODE -D_UNICODE -DUNICODE)
@ -162,12 +163,18 @@ add_library(libslic3r STATIC
) )
add_library(libslic3r_gui STATIC add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/GUI/AppConfig.cpp
${LIBDIR}/slic3r/GUI/AppConfig.hpp
${LIBDIR}/slic3r/GUI/3DScene.cpp ${LIBDIR}/slic3r/GUI/3DScene.cpp
${LIBDIR}/slic3r/GUI/3DScene.hpp ${LIBDIR}/slic3r/GUI/3DScene.hpp
${LIBDIR}/slic3r/GUI/GLShader.cpp ${LIBDIR}/slic3r/GUI/GLShader.cpp
${LIBDIR}/slic3r/GUI/GLShader.hpp ${LIBDIR}/slic3r/GUI/GLShader.hpp
${LIBDIR}/slic3r/GUI/Preset.cpp ${LIBDIR}/slic3r/GUI/Preset.cpp
${LIBDIR}/slic3r/GUI/Preset.hpp ${LIBDIR}/slic3r/GUI/Preset.hpp
${LIBDIR}/slic3r/GUI/PresetBundle.cpp
${LIBDIR}/slic3r/GUI/PresetBundle.hpp
${LIBDIR}/slic3r/GUI/PresetHints.cpp
${LIBDIR}/slic3r/GUI/PresetHints.hpp
${LIBDIR}/slic3r/GUI/GUI.cpp ${LIBDIR}/slic3r/GUI/GUI.cpp
${LIBDIR}/slic3r/GUI/GUI.hpp ${LIBDIR}/slic3r/GUI/GUI.hpp
) )
@ -279,6 +286,7 @@ set(XS_XSP_FILES
${XSP_DIR}/GCodeSender.xsp ${XSP_DIR}/GCodeSender.xsp
${XSP_DIR}/Geometry.xsp ${XSP_DIR}/Geometry.xsp
${XSP_DIR}/GUI.xsp ${XSP_DIR}/GUI.xsp
${XSP_DIR}/GUI_AppConfig.xsp
${XSP_DIR}/GUI_3DScene.xsp ${XSP_DIR}/GUI_3DScene.xsp
${XSP_DIR}/GUI_Preset.xsp ${XSP_DIR}/GUI_Preset.xsp
${XSP_DIR}/Layer.xsp ${XSP_DIR}/Layer.xsp

View File

@ -179,14 +179,6 @@ sub new_from_width {
); );
} }
sub new_from_spacing {
my ($class, %args) = @_;
return $class->_new_from_spacing(
@args{qw(spacing nozzle_diameter layer_height bridge)},
);
}
package Slic3r::Surface; package Slic3r::Surface;
sub new { sub new {

View File

@ -101,18 +101,16 @@ namespace nowide {
{ {
char const *key = string; char const *key = string;
char const *key_end = string; char const *key_end = string;
while(*key_end!='=' && key_end!='\0') while(*key_end != '=' && *key_end != 0)
key_end++; ++ key_end;
if(*key_end == '\0') if(*key_end == 0)
return -1; return -1;
wshort_stackstring wkey; wshort_stackstring wkey;
if(!wkey.convert(key,key_end)) if(!wkey.convert(key,key_end))
return -1; return -1;
wstackstring wvalue; wstackstring wvalue;
if(!wvalue.convert(key_end+1)) if(!wvalue.convert(key_end+1))
return -1; return -1;
if(SetEnvironmentVariableW(wkey.c_str(),wvalue.c_str())) if(SetEnvironmentVariableW(wkey.c_str(),wvalue.c_str()))
return 0; return 0;
return -1; return -1;

View File

@ -7,7 +7,8 @@ namespace Slic3r {
template <class PointClass> template <class PointClass>
BoundingBoxBase<PointClass>::BoundingBoxBase(const std::vector<PointClass> &points) BoundingBoxBase<PointClass>::BoundingBoxBase(const std::vector<PointClass> &points)
{ {
if (points.empty()) CONFESS("Empty point set supplied to BoundingBoxBase constructor"); if (points.empty())
CONFESS("Empty point set supplied to BoundingBoxBase constructor");
typename std::vector<PointClass>::const_iterator it = points.begin(); typename std::vector<PointClass>::const_iterator it = points.begin();
this->min.x = this->max.x = it->x; this->min.x = this->max.x = it->x;
this->min.y = this->max.y = it->y; this->min.y = this->max.y = it->y;
@ -26,7 +27,8 @@ template <class PointClass>
BoundingBox3Base<PointClass>::BoundingBox3Base(const std::vector<PointClass> &points) BoundingBox3Base<PointClass>::BoundingBox3Base(const std::vector<PointClass> &points)
: BoundingBoxBase<PointClass>(points) : BoundingBoxBase<PointClass>(points)
{ {
if (points.empty()) CONFESS("Empty point set supplied to BoundingBox3Base constructor"); if (points.empty())
CONFESS("Empty point set supplied to BoundingBox3Base constructor");
typename std::vector<PointClass>::const_iterator it = points.begin(); typename std::vector<PointClass>::const_iterator it = points.begin();
this->min.z = this->max.z = it->z; this->min.z = this->max.z = it->z;
for (++it; it != points.end(); ++it) { for (++it; it != points.end(); ++it) {
@ -39,9 +41,10 @@ template BoundingBox3Base<Pointf3>::BoundingBox3Base(const std::vector<Pointf3>
BoundingBox::BoundingBox(const Lines &lines) BoundingBox::BoundingBox(const Lines &lines)
{ {
Points points; Points points;
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { points.reserve(lines.size());
points.push_back(line->a); for (const Line &line : lines) {
points.push_back(line->b); points.emplace_back(line.a);
points.emplace_back(line.b);
} }
*this = BoundingBox(points); *this = BoundingBox(points);
} }
@ -190,9 +193,9 @@ BoundingBox3Base<PointClass>::size() const
} }
template Pointf3 BoundingBox3Base<Pointf3>::size() const; template Pointf3 BoundingBox3Base<Pointf3>::size() const;
template <class PointClass> double template <class PointClass> double BoundingBoxBase<PointClass>::radius() const
BoundingBoxBase<PointClass>::radius() const
{ {
assert(this->defined);
double x = this->max.x - this->min.x; double x = this->max.x - this->min.x;
double y = this->max.y - this->min.y; double y = this->max.y - this->min.y;
return 0.5 * sqrt(x*x+y*y); return 0.5 * sqrt(x*x+y*y);
@ -200,8 +203,7 @@ BoundingBoxBase<PointClass>::radius() const
template double BoundingBoxBase<Point>::radius() const; template double BoundingBoxBase<Point>::radius() const;
template double BoundingBoxBase<Pointf>::radius() const; template double BoundingBoxBase<Pointf>::radius() const;
template <class PointClass> double template <class PointClass> double BoundingBox3Base<PointClass>::radius() const
BoundingBox3Base<PointClass>::radius() const
{ {
double x = this->max.x - this->min.x; double x = this->max.x - this->min.x;
double y = this->max.y - this->min.y; double y = this->max.y - this->min.y;

View File

@ -1,6 +1,6 @@
#include "Config.hpp" #include "Config.hpp"
#include "Utils.hpp"
#include <assert.h> #include <assert.h>
#include <ctime>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <exception> // std::runtime_error #include <exception> // std::runtime_error
@ -16,7 +16,6 @@
#include <boost/nowide/cenv.hpp> #include <boost/nowide/cenv.hpp>
#include <boost/nowide/fstream.hpp> #include <boost/nowide/fstream.hpp>
#include <boost/property_tree/ini_parser.hpp> #include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <string.h> #include <string.h>
#if defined(_WIN32) && !defined(setenv) && defined(_putenv_s) #if defined(_WIN32) && !defined(setenv) && defined(_putenv_s)
@ -235,10 +234,13 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
{ {
t_config_option_key opt_key = opt_key_src; t_config_option_key opt_key = opt_key_src;
// Try to deserialize the option by its name. // Try to deserialize the option by its name.
const ConfigOptionDef* optdef = this->def()->get(opt_key); const ConfigDef *def = this->def();
if (def == nullptr)
throw NoDefinitionException();
const ConfigOptionDef *optdef = def->get(opt_key);
if (optdef == nullptr) { if (optdef == nullptr) {
// If we didn't find an option, look for any other option having this as an alias. // If we didn't find an option, look for any other option having this as an alias.
for (const auto &opt : this->def()->options) { for (const auto &opt : def->options) {
for (const t_config_option_key &opt_key2 : opt.second.aliases) { for (const t_config_option_key &opt_key2 : opt.second.aliases) {
if (opt_key2 == opt_key) { if (opt_key2 == opt_key) {
opt_key = opt_key2; opt_key = opt_key2;
@ -278,10 +280,16 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const
return static_cast<const ConfigOptionFloat*>(raw_opt)->value; return static_cast<const ConfigOptionFloat*>(raw_opt)->value;
if (raw_opt->type() == coFloatOrPercent) { if (raw_opt->type() == coFloatOrPercent) {
// Get option definition. // Get option definition.
const ConfigOptionDef *def = this->def()->get(opt_key); const ConfigDef *def = this->def();
assert(def != nullptr); if (def == nullptr)
throw NoDefinitionException();
const ConfigOptionDef *opt_def = def->get(opt_key);
assert(opt_def != nullptr);
// Compute absolute value over the absolute value of the base option. // Compute absolute value over the absolute value of the base option.
return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(this->get_abs_value(def->ratio_over)); //FIXME there are some ratio_over chains, which end with empty ratio_with.
// For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly.
return opt_def->ratio_over.empty() ? 0. :
static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over));
} }
throw std::runtime_error("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()"); throw std::runtime_error("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
} }
@ -321,11 +329,23 @@ void ConfigBase::setenv_()
void ConfigBase::load(const std::string &file) void ConfigBase::load(const std::string &file)
{ {
namespace pt = boost::property_tree; if (boost::iends_with(file, ".gcode") || boost::iends_with(file, ".g"))
pt::ptree tree; this->load_from_gcode(file);
else
this->load_from_ini(file);
}
void ConfigBase::load_from_ini(const std::string &file)
{
boost::property_tree::ptree tree;
boost::nowide::ifstream ifs(file); boost::nowide::ifstream ifs(file);
pt::read_ini(ifs, tree); boost::property_tree::read_ini(ifs, tree);
for (const pt::ptree::value_type &v : tree) { this->load(tree);
}
void ConfigBase::load(const boost::property_tree::ptree &tree)
{
for (const boost::property_tree::ptree::value_type &v : tree) {
try { try {
t_config_option_key opt_key = v.first; t_config_option_key opt_key = v.first;
this->set_deserialize(opt_key, v.second.get_value<std::string>()); this->set_deserialize(opt_key, v.second.get_value<std::string>());
@ -414,18 +434,24 @@ void ConfigBase::save(const std::string &file) const
{ {
boost::nowide::ofstream c; boost::nowide::ofstream c;
c.open(file, std::ios::out | std::ios::trunc); c.open(file, std::ios::out | std::ios::trunc);
{ c << "# " << Slic3r::header_slic3r_generated() << std::endl;
std::time_t now;
time(&now);
char buf[sizeof "0000-00-00 00:00:00"];
strftime(buf, sizeof(buf), "%F %T", gmtime(&now));
c << "# generated by Slic3r " << SLIC3R_VERSION << " on " << buf << std::endl;
}
for (const std::string &opt_key : this->keys()) for (const std::string &opt_key : this->keys())
c << opt_key << " = " << this->serialize(opt_key) << std::endl; c << opt_key << " = " << this->serialize(opt_key) << std::endl;
c.close(); c.close();
} }
bool DynamicConfig::operator==(const DynamicConfig &rhs) const
{
t_options_map::const_iterator it1 = this->options.begin();
t_options_map::const_iterator it1_end = this->options.end();
t_options_map::const_iterator it2 = rhs.options.begin();
t_options_map::const_iterator it2_end = rhs.options.end();
for (; it1 != it1_end && it2 != it2_end; ++ it1, ++ it2)
if (*it1->second != *it2->second)
return false;
return it1 == it1_end && it2 == it2_end;
}
ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create)
{ {
t_options_map::iterator it = options.find(opt_key); t_options_map::iterator it = options.find(opt_key);
@ -436,7 +462,10 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre
// Option was not found and a new option shall not be created. // Option was not found and a new option shall not be created.
return nullptr; return nullptr;
// Try to create a new ConfigOption. // Try to create a new ConfigOption.
const ConfigOptionDef *optdef = this->def()->get(opt_key); const ConfigDef *def = this->def();
if (def == nullptr)
throw NoDefinitionException();
const ConfigOptionDef *optdef = def->get(opt_key);
if (optdef == nullptr) if (optdef == nullptr)
// throw std::runtime_error(std::string("Invalid option name: ") + opt_key); // throw std::runtime_error(std::string("Invalid option name: ") + opt_key);
// Let the parent decide what to do if the opt_key is not defined by this->def(). // Let the parent decide what to do if the opt_key is not defined by this->def().

View File

@ -13,6 +13,8 @@
#include "libslic3r.h" #include "libslic3r.h"
#include "Point.hpp" #include "Point.hpp"
#include <boost/property_tree/ptree.hpp>
namespace Slic3r { namespace Slic3r {
// Name of the configuration option. // Name of the configuration option.
@ -27,41 +29,42 @@ extern bool unescape_strings_cstyle(const std::string &str, std::vector<
// Type of a configuration value. // Type of a configuration value.
enum ConfigOptionType { enum ConfigOptionType {
coNone, coVectorType = 0x4000,
coNone = 0,
// single float // single float
coFloat, coFloat = 1,
// vector of floats // vector of floats
coFloats, coFloats = coFloat + coVectorType,
// single int // single int
coInt, coInt = 2,
// vector of ints // vector of ints
coInts, coInts = coInt + coVectorType,
// single string // single string
coString, coString = 3,
// vector of strings // vector of strings
coStrings, coStrings = coString + coVectorType,
// percent value. Currently only used for infill. // percent value. Currently only used for infill.
coPercent, coPercent = 4,
// percents value. Currently used for retract before wipe only. // percents value. Currently used for retract before wipe only.
coPercents, coPercents = coPercent + coVectorType,
// a fraction or an absolute value // a fraction or an absolute value
coFloatOrPercent, coFloatOrPercent = 5,
// single 2d point. Currently not used. // single 2d point. Currently not used.
coPoint, coPoint = 6,
// vector of 2d points. Currently used for the definition of the print bed and for the extruder offsets. // vector of 2d points. Currently used for the definition of the print bed and for the extruder offsets.
coPoints, coPoints = coPoint + coVectorType,
// single boolean value // single boolean value
coBool, coBool = 7,
// vector of boolean values // vector of boolean values
coBools, coBools = coBool + coVectorType,
// a generic enum // a generic enum
coEnum, coEnum = 8,
}; };
// A generic value of a configuration option. // A generic value of a configuration option.
class ConfigOption { class ConfigOption {
public: public:
virtual ~ConfigOption() {}; virtual ~ConfigOption() {}
virtual ConfigOptionType type() const = 0; virtual ConfigOptionType type() const = 0;
virtual std::string serialize() const = 0; virtual std::string serialize() const = 0;
@ -75,8 +78,13 @@ public:
virtual void setInt(int /* val */) { throw std::runtime_error("Calling ConfigOption::setInt on a non-int ConfigOption"); } virtual void setInt(int /* val */) { throw std::runtime_error("Calling ConfigOption::setInt on a non-int ConfigOption"); }
virtual bool operator==(const ConfigOption &rhs) const = 0; virtual bool operator==(const ConfigOption &rhs) const = 0;
bool operator!=(const ConfigOption &rhs) const { return ! (*this == rhs); } bool operator!=(const ConfigOption &rhs) const { return ! (*this == rhs); }
bool is_scalar() const { return (int(this->type()) & int(coVectorType)) == 0; }
bool is_vector() const { return ! this->is_scalar(); }
}; };
typedef ConfigOption* ConfigOptionPtr;
typedef const ConfigOption* ConfigOptionConstPtr;
// Value of a single valued option (bool, int, float, string, point, enum) // Value of a single valued option (bool, int, float, string, point, enum)
template <class T> template <class T>
class ConfigOptionSingle : public ConfigOption { class ConfigOptionSingle : public ConfigOption {
@ -91,7 +99,7 @@ public:
throw std::runtime_error("ConfigOptionSingle: Assigning an incompatible type"); throw std::runtime_error("ConfigOptionSingle: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs)); assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs));
this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value; this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
}; }
bool operator==(const ConfigOption &rhs) const override bool operator==(const ConfigOption &rhs) const override
{ {
@ -110,6 +118,25 @@ class ConfigOptionVectorBase : public ConfigOption {
public: public:
// Currently used only to initialize the PlaceholderParser. // Currently used only to initialize the PlaceholderParser.
virtual std::vector<std::string> vserialize() const = 0; virtual std::vector<std::string> vserialize() const = 0;
// Set from a vector of ConfigOptions.
// If the rhs ConfigOption is scalar, then its value is used,
// otherwise for each of rhs, the first value of a vector is used.
// This function is useful to collect values for multiple extrder / filament settings.
virtual void set(const std::vector<const ConfigOption*> &rhs) = 0;
// Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions.
// This function is useful to split values from multiple extrder / filament settings into separate configurations.
virtual void set_at(const ConfigOption *rhs, size_t i, size_t j) = 0;
virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0;
// Get size of this vector.
virtual size_t size() const = 0;
// Is this vector empty?
virtual bool empty() const = 0;
protected:
// Used to verify type compatibility when assigning to / from a scalar ConfigOption.
ConfigOptionType scalar_type() const { return static_cast<ConfigOptionType>(this->type() - coVectorType); }
}; };
// Value of a vector valued option (bools, ints, floats, strings, points), template // Value of a vector valued option (bools, ints, floats, strings, points), template
@ -117,6 +144,11 @@ template <class T>
class ConfigOptionVector : public ConfigOptionVectorBase class ConfigOptionVector : public ConfigOptionVectorBase
{ {
public: public:
ConfigOptionVector() {}
explicit ConfigOptionVector(size_t n, const T &value) : values(n, value) {}
explicit ConfigOptionVector(std::initializer_list<T> il) : values(std::move(il)) {}
explicit ConfigOptionVector(const std::vector<T> &values) : values(values) {}
explicit ConfigOptionVector(std::vector<T> &&values) : values(std::move(values)) {}
std::vector<T> values; std::vector<T> values;
void set(const ConfigOption *rhs) override void set(const ConfigOption *rhs) override
@ -125,16 +157,88 @@ public:
throw std::runtime_error("ConfigOptionVector: Assigning an incompatible type"); throw std::runtime_error("ConfigOptionVector: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs)); assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs));
this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values; this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values;
}; }
// Set from a vector of ConfigOptions.
// If the rhs ConfigOption is scalar, then its value is used,
// otherwise for each of rhs, the first value of a vector is used.
// This function is useful to collect values for multiple extrder / filament settings.
void set(const std::vector<const ConfigOption*> &rhs) override
{
this->values.clear();
this->values.reserve(rhs.size());
for (const ConfigOption *opt : rhs) {
if (opt->type() == this->type()) {
auto other = static_cast<const ConfigOptionVector<T>*>(opt);
if (other->values.empty())
throw std::runtime_error("ConfigOptionVector::set(): Assigning from an empty vector");
this->values.emplace_back(other->values.front());
} else if (opt->type() == this->scalar_type())
this->values.emplace_back(static_cast<const ConfigOptionSingle<T>*>(opt)->value);
else
throw std::runtime_error("ConfigOptionVector::set():: Assigning an incompatible type");
}
}
// Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions.
// This function is useful to split values from multiple extrder / filament settings into separate configurations.
void set_at(const ConfigOption *rhs, size_t i, size_t j) override
{
// It is expected that the vector value has at least one value, which is the default, if not overwritten.
assert(! this->values.empty());
if (this->values.size() <= i) {
// Resize this vector, fill in the new vector fields with the copy of the first field.
T v = this->values.front();
this->values.resize(i + 1, v);
}
if (rhs->type() == this->type()) {
// Assign the first value of the rhs vector.
auto other = static_cast<const ConfigOptionVector<T>*>(rhs);
if (other->values.empty())
throw std::runtime_error("ConfigOptionVector::set_at(): Assigning from an empty vector");
this->values[i] = other->get_at(j);
} else if (rhs->type() == this->scalar_type())
this->values[i] = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
else
throw std::runtime_error("ConfigOptionVector::set_at(): Assigning an incompatible type");
}
T& get_at(size_t i) T& get_at(size_t i)
{ {
assert(! this->values.empty()); assert(! this->values.empty());
return (i < this->values.size()) ? this->values[i] : this->values.front(); return (i < this->values.size()) ? this->values[i] : this->values.front();
}; }
const T& get_at(size_t i) const { return const_cast<ConfigOptionVector<T>*>(this)->get_at(i); } const T& get_at(size_t i) const { return const_cast<ConfigOptionVector<T>*>(this)->get_at(i); }
// Resize this vector by duplicating the last value.
// If the current vector is empty, the default value is used instead.
void resize(size_t n, const ConfigOption *opt_default = nullptr) override
{
assert(opt_default == nullptr || opt_default->is_vector());
// assert(opt_default == nullptr || dynamic_cast<ConfigOptionVector<T>>(opt_default));
assert(! this->values.empty() || opt_default != nullptr);
if (n == 0)
this->values.clear();
else if (n < this->values.size())
this->values.erase(this->values.begin() + n, this->values.end());
else if (n > this->values.size()) {
if (this->values.empty()) {
if (opt_default == nullptr)
throw std::runtime_error("ConfigOptionVector::resize(): No default value provided.");
if (opt_default->type() != this->type())
throw std::runtime_error("ConfigOptionVector::resize(): Extending with an incompatible type.");
this->values.resize(n, static_cast<const ConfigOptionVector<T>*>(opt_default)->values.front());
} else {
// Resize by duplicating the last value.
this->values.resize(n, this->values.back());
}
}
}
size_t size() const override { return this->values.size(); }
bool empty() const override { return this->values.empty(); }
bool operator==(const ConfigOption &rhs) const override bool operator==(const ConfigOption &rhs) const override
{ {
if (rhs.type() != this->type()) if (rhs.type() != this->type())
@ -150,10 +254,11 @@ public:
class ConfigOptionFloat : public ConfigOptionSingle<double> class ConfigOptionFloat : public ConfigOptionSingle<double>
{ {
public: public:
ConfigOptionFloat() : ConfigOptionSingle<double>(0) {}; ConfigOptionFloat() : ConfigOptionSingle<double>(0) {}
explicit ConfigOptionFloat(double _value) : ConfigOptionSingle<double>(_value) {}; explicit ConfigOptionFloat(double _value) : ConfigOptionSingle<double>(_value) {}
ConfigOptionType type() const override { return coFloat; } static ConfigOptionType static_type() { return coFloat; }
ConfigOptionType type() const override { return static_type(); }
double getFloat() const override { return this->value; } double getFloat() const override { return this->value; }
ConfigOption* clone() const override { return new ConfigOptionFloat(*this); } ConfigOption* clone() const override { return new ConfigOptionFloat(*this); }
bool operator==(const ConfigOptionFloat &rhs) const { return this->value == rhs.value; } bool operator==(const ConfigOptionFloat &rhs) const { return this->value == rhs.value; }
@ -183,7 +288,12 @@ public:
class ConfigOptionFloats : public ConfigOptionVector<double> class ConfigOptionFloats : public ConfigOptionVector<double>
{ {
public: public:
ConfigOptionType type() const override { return coFloats; } ConfigOptionFloats() : ConfigOptionVector<double>() {}
explicit ConfigOptionFloats(size_t n, double value) : ConfigOptionVector<double>(n, value) {}
explicit ConfigOptionFloats(std::initializer_list<double> il) : ConfigOptionVector<double>(std::move(il)) {}
static ConfigOptionType static_type() { return coFloats; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionFloats(*this); } ConfigOption* clone() const override { return new ConfigOptionFloats(*this); }
bool operator==(const ConfigOptionFloats &rhs) const { return this->values == rhs.values; } bool operator==(const ConfigOptionFloats &rhs) const { return this->values == rhs.values; }
@ -195,7 +305,7 @@ public:
ss << *it; ss << *it;
} }
return ss.str(); return ss.str();
}; }
std::vector<std::string> vserialize() const override std::vector<std::string> vserialize() const override
{ {
@ -234,13 +344,14 @@ public:
class ConfigOptionInt : public ConfigOptionSingle<int> class ConfigOptionInt : public ConfigOptionSingle<int>
{ {
public: public:
ConfigOptionInt() : ConfigOptionSingle<int>(0) {}; ConfigOptionInt() : ConfigOptionSingle<int>(0) {}
explicit ConfigOptionInt(int value) : ConfigOptionSingle<int>(value) {}; explicit ConfigOptionInt(int value) : ConfigOptionSingle<int>(value) {}
explicit ConfigOptionInt(double _value) : ConfigOptionSingle<int>(int(floor(_value + 0.5))) {}; explicit ConfigOptionInt(double _value) : ConfigOptionSingle<int>(int(floor(_value + 0.5))) {}
ConfigOptionType type() const override { return coInt; } static ConfigOptionType static_type() { return coInt; }
int getInt() const override { return this->value; }; ConfigOptionType type() const override { return static_type(); }
void setInt(int val) { this->value = val; }; int getInt() const override { return this->value; }
void setInt(int val) { this->value = val; }
ConfigOption* clone() const override { return new ConfigOptionInt(*this); } ConfigOption* clone() const override { return new ConfigOptionInt(*this); }
bool operator==(const ConfigOptionInt &rhs) const { return this->value == rhs.value; } bool operator==(const ConfigOptionInt &rhs) const { return this->value == rhs.value; }
@ -269,7 +380,12 @@ public:
class ConfigOptionInts : public ConfigOptionVector<int> class ConfigOptionInts : public ConfigOptionVector<int>
{ {
public: public:
ConfigOptionType type() const override { return coInts; } ConfigOptionInts() : ConfigOptionVector<int>() {}
explicit ConfigOptionInts(size_t n, int value) : ConfigOptionVector<int>(n, value) {}
explicit ConfigOptionInts(std::initializer_list<int> il) : ConfigOptionVector<int>(std::move(il)) {}
static ConfigOptionType static_type() { return coInts; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionInts(*this); } ConfigOption* clone() const override { return new ConfigOptionInts(*this); }
ConfigOptionInts& operator=(const ConfigOption *opt) { this->set(opt); return *this; } ConfigOptionInts& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionInts &rhs) const { return this->values == rhs.values; } bool operator==(const ConfigOptionInts &rhs) const { return this->values == rhs.values; }
@ -314,10 +430,11 @@ public:
class ConfigOptionString : public ConfigOptionSingle<std::string> class ConfigOptionString : public ConfigOptionSingle<std::string>
{ {
public: public:
ConfigOptionString() : ConfigOptionSingle<std::string>("") {}; ConfigOptionString() : ConfigOptionSingle<std::string>("") {}
explicit ConfigOptionString(std::string _value) : ConfigOptionSingle<std::string>(_value) {}; explicit ConfigOptionString(const std::string &value) : ConfigOptionSingle<std::string>(value) {}
ConfigOptionType type() const override { return coString; } static ConfigOptionType static_type() { return coString; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionString(*this); } ConfigOption* clone() const override { return new ConfigOptionString(*this); }
ConfigOptionString& operator=(const ConfigOption *opt) { this->set(opt); return *this; } ConfigOptionString& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionString &rhs) const { return this->value == rhs.value; } bool operator==(const ConfigOptionString &rhs) const { return this->value == rhs.value; }
@ -338,7 +455,14 @@ public:
class ConfigOptionStrings : public ConfigOptionVector<std::string> class ConfigOptionStrings : public ConfigOptionVector<std::string>
{ {
public: public:
ConfigOptionType type() const override { return coStrings; } ConfigOptionStrings() : ConfigOptionVector<std::string>() {}
explicit ConfigOptionStrings(size_t n, const std::string &value) : ConfigOptionVector<std::string>(n, value) {}
explicit ConfigOptionStrings(const std::vector<std::string> &values) : ConfigOptionVector<std::string>(values) {}
explicit ConfigOptionStrings(std::vector<std::string> &&values) : ConfigOptionVector<std::string>(std::move(values)) {}
explicit ConfigOptionStrings(std::initializer_list<std::string> il) : ConfigOptionVector<std::string>(std::move(il)) {}
static ConfigOptionType static_type() { return coStrings; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionStrings(*this); } ConfigOption* clone() const override { return new ConfigOptionStrings(*this); }
ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; } ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionStrings &rhs) const { return this->values == rhs.values; } bool operator==(const ConfigOptionStrings &rhs) const { return this->values == rhs.values; }
@ -364,10 +488,11 @@ public:
class ConfigOptionPercent : public ConfigOptionFloat class ConfigOptionPercent : public ConfigOptionFloat
{ {
public: public:
ConfigOptionPercent() : ConfigOptionFloat(0) {}; ConfigOptionPercent() : ConfigOptionFloat(0) {}
explicit ConfigOptionPercent(double _value) : ConfigOptionFloat(_value) {}; explicit ConfigOptionPercent(double _value) : ConfigOptionFloat(_value) {}
ConfigOptionType type() const override { return coPercent; } static ConfigOptionType static_type() { return coPercent; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionPercent(*this); } ConfigOption* clone() const override { return new ConfigOptionPercent(*this); }
ConfigOptionPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; } ConfigOptionPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionPercent &rhs) const { return this->value == rhs.value; } bool operator==(const ConfigOptionPercent &rhs) const { return this->value == rhs.value; }
@ -395,7 +520,12 @@ public:
class ConfigOptionPercents : public ConfigOptionFloats class ConfigOptionPercents : public ConfigOptionFloats
{ {
public: public:
ConfigOptionType type() const override { return coPercents; } ConfigOptionPercents() : ConfigOptionFloats() {}
explicit ConfigOptionPercents(size_t n, double value) : ConfigOptionFloats(n, value) {}
explicit ConfigOptionPercents(std::initializer_list<double> il) : ConfigOptionFloats(std::move(il)) {}
static ConfigOptionType static_type() { return coPercents; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionPercents(*this); } ConfigOption* clone() const override { return new ConfigOptionPercents(*this); }
ConfigOptionPercents& operator=(const ConfigOption *opt) { this->set(opt); return *this; } ConfigOptionPercents& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionPercents &rhs) const { return this->values == rhs.values; } bool operator==(const ConfigOptionPercents &rhs) const { return this->values == rhs.values; }
@ -445,10 +575,11 @@ class ConfigOptionFloatOrPercent : public ConfigOptionPercent
{ {
public: public:
bool percent; bool percent;
ConfigOptionFloatOrPercent() : ConfigOptionPercent(0), percent(false) {}; ConfigOptionFloatOrPercent() : ConfigOptionPercent(0), percent(false) {}
explicit ConfigOptionFloatOrPercent(double _value, bool _percent) : ConfigOptionPercent(_value), percent(_percent) {}; explicit ConfigOptionFloatOrPercent(double _value, bool _percent) : ConfigOptionPercent(_value), percent(_percent) {}
ConfigOptionType type() const override { return coFloatOrPercent; } static ConfigOptionType static_type() { return coFloatOrPercent; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionFloatOrPercent(*this); } ConfigOption* clone() const override { return new ConfigOptionFloatOrPercent(*this); }
ConfigOptionFloatOrPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; } ConfigOptionFloatOrPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionFloatOrPercent &rhs) const bool operator==(const ConfigOptionFloatOrPercent &rhs) const
@ -485,10 +616,11 @@ public:
class ConfigOptionPoint : public ConfigOptionSingle<Pointf> class ConfigOptionPoint : public ConfigOptionSingle<Pointf>
{ {
public: public:
ConfigOptionPoint() : ConfigOptionSingle<Pointf>(Pointf(0,0)) {}; ConfigOptionPoint() : ConfigOptionSingle<Pointf>(Pointf(0,0)) {}
explicit ConfigOptionPoint(const Pointf &value) : ConfigOptionSingle<Pointf>(value) {}; explicit ConfigOptionPoint(const Pointf &value) : ConfigOptionSingle<Pointf>(value) {}
ConfigOptionType type() const override { return coPoint; } static ConfigOptionType static_type() { return coPoint; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionPoint(*this); } ConfigOption* clone() const override { return new ConfigOptionPoint(*this); }
ConfigOptionPoint& operator=(const ConfigOption *opt) { this->set(opt); return *this; } ConfigOptionPoint& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionPoint &rhs) const { return this->value == rhs.value; } bool operator==(const ConfigOptionPoint &rhs) const { return this->value == rhs.value; }
@ -517,7 +649,12 @@ public:
class ConfigOptionPoints : public ConfigOptionVector<Pointf> class ConfigOptionPoints : public ConfigOptionVector<Pointf>
{ {
public: public:
ConfigOptionType type() const override { return coPoints; } ConfigOptionPoints() : ConfigOptionVector<Pointf>() {}
explicit ConfigOptionPoints(size_t n, const Pointf &value) : ConfigOptionVector<Pointf>(n, value) {}
explicit ConfigOptionPoints(std::initializer_list<Pointf> il) : ConfigOptionVector<Pointf>(std::move(il)) {}
static ConfigOptionType static_type() { return coPoints; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionPoints(*this); } ConfigOption* clone() const override { return new ConfigOptionPoints(*this); }
ConfigOptionPoints& operator=(const ConfigOption *opt) { this->set(opt); return *this; } ConfigOptionPoints& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionPoints &rhs) const { return this->values == rhs.values; } bool operator==(const ConfigOptionPoints &rhs) const { return this->values == rhs.values; }
@ -570,11 +707,12 @@ public:
class ConfigOptionBool : public ConfigOptionSingle<bool> class ConfigOptionBool : public ConfigOptionSingle<bool>
{ {
public: public:
ConfigOptionBool() : ConfigOptionSingle<bool>(false) {}; ConfigOptionBool() : ConfigOptionSingle<bool>(false) {}
explicit ConfigOptionBool(bool _value) : ConfigOptionSingle<bool>(_value) {}; explicit ConfigOptionBool(bool _value) : ConfigOptionSingle<bool>(_value) {}
ConfigOptionType type() const override { return coBool; } static ConfigOptionType static_type() { return coBool; }
bool getBool() const override { return this->value; }; ConfigOptionType type() const override { return static_type(); }
bool getBool() const override { return this->value; }
ConfigOption* clone() const override { return new ConfigOptionBool(*this); } ConfigOption* clone() const override { return new ConfigOptionBool(*this); }
ConfigOptionBool& operator=(const ConfigOption *opt) { this->set(opt); return *this; } ConfigOptionBool& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionBool &rhs) const { return this->value == rhs.value; } bool operator==(const ConfigOptionBool &rhs) const { return this->value == rhs.value; }
@ -595,7 +733,12 @@ public:
class ConfigOptionBools : public ConfigOptionVector<unsigned char> class ConfigOptionBools : public ConfigOptionVector<unsigned char>
{ {
public: public:
ConfigOptionType type() const override { return coBools; } ConfigOptionBools() : ConfigOptionVector<unsigned char>() {}
explicit ConfigOptionBools(size_t n, bool value) : ConfigOptionVector<unsigned char>(n, (unsigned char)value) {}
explicit ConfigOptionBools(std::initializer_list<bool> il) { values.reserve(il.size()); for (bool b : il) values.emplace_back((unsigned char)b); }
static ConfigOptionType static_type() { return coBools; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionBools(*this); } ConfigOption* clone() const override { return new ConfigOptionBools(*this); }
ConfigOptionBools& operator=(const ConfigOption *opt) { this->set(opt); return *this; } ConfigOptionBools& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionBools &rhs) const { return this->values == rhs.values; } bool operator==(const ConfigOptionBools &rhs) const { return this->values == rhs.values; }
@ -652,10 +795,11 @@ class ConfigOptionEnum : public ConfigOptionSingle<T>
{ {
public: public:
// by default, use the first value (0) of the T enum type // by default, use the first value (0) of the T enum type
ConfigOptionEnum() : ConfigOptionSingle<T>(static_cast<T>(0)) {}; ConfigOptionEnum() : ConfigOptionSingle<T>(static_cast<T>(0)) {}
explicit ConfigOptionEnum(T _value) : ConfigOptionSingle<T>(_value) {}; explicit ConfigOptionEnum(T _value) : ConfigOptionSingle<T>(_value) {}
ConfigOptionType type() const override { return coEnum; } static ConfigOptionType static_type() { return coEnum; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionEnum<T>(*this); } ConfigOption* clone() const override { return new ConfigOptionEnum<T>(*this); }
ConfigOptionEnum<T>& operator=(const ConfigOption *opt) { this->set(opt); return *this; } ConfigOptionEnum<T>& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionEnum<T> &rhs) const { return this->value == rhs.value; } bool operator==(const ConfigOptionEnum<T> &rhs) const { return this->value == rhs.value; }
@ -717,7 +861,8 @@ public:
const t_config_enum_values* keys_map; const t_config_enum_values* keys_map;
ConfigOptionType type() const override { return coEnum; } static ConfigOptionType static_type() { return coEnum; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionEnumGeneric(*this); } ConfigOption* clone() const override { return new ConfigOptionEnumGeneric(*this); }
ConfigOptionEnumGeneric& operator=(const ConfigOption *opt) { this->set(opt); return *this; } ConfigOptionEnumGeneric& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
bool operator==(const ConfigOptionEnumGeneric &rhs) const { return this->value == rhs.value; } bool operator==(const ConfigOptionEnumGeneric &rhs) const { return this->value == rhs.value; }
@ -874,6 +1019,16 @@ public:
{ return const_cast<ConfigBase*>(this)->option(opt_key, false); } { return const_cast<ConfigBase*>(this)->option(opt_key, false); }
ConfigOption* option(const t_config_option_key &opt_key, bool create = false) ConfigOption* option(const t_config_option_key &opt_key, bool create = false)
{ return this->optptr(opt_key, create); } { return this->optptr(opt_key, create); }
template<typename TYPE>
TYPE* option(const t_config_option_key &opt_key, bool create = false)
{
ConfigOption *opt = this->optptr(opt_key, create);
assert(opt == nullptr || opt->type() == TYPE::static_type());
return (opt == nullptr || opt->type() != TYPE::static_type()) ? nullptr : static_cast<TYPE*>(opt);
}
template<typename TYPE>
const TYPE* option(const t_config_option_key &opt_key) const
{ return const_cast<ConfigBase*>(this)->option<TYPE>(opt_key, false); }
// Apply all keys of other ConfigBase defined by this->def() to this ConfigBase. // Apply all keys of other ConfigBase defined by this->def() to this ConfigBase.
// An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(), // An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(),
// or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set. // or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set.
@ -893,7 +1048,9 @@ public:
double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const;
void setenv_(); void setenv_();
void load(const std::string &file); void load(const std::string &file);
void load_from_ini(const std::string &file);
void load_from_gcode(const std::string &file); void load_from_gcode(const std::string &file);
void load(const boost::property_tree::ptree &tree);
void save(const std::string &file) const; void save(const std::string &file) const;
private: private:
@ -906,26 +1063,77 @@ private:
class DynamicConfig : public virtual ConfigBase class DynamicConfig : public virtual ConfigBase
{ {
public: public:
DynamicConfig() {}
DynamicConfig(const DynamicConfig& other) { *this = other; } DynamicConfig(const DynamicConfig& other) { *this = other; }
DynamicConfig(DynamicConfig&& other) : options(std::move(other.options)) { other.options.clear(); } DynamicConfig(DynamicConfig&& other) : options(std::move(other.options)) { other.options.clear(); }
virtual ~DynamicConfig() { clear(); } virtual ~DynamicConfig() { clear(); }
DynamicConfig& operator=(const DynamicConfig &other) // Copy a content of one DynamicConfig to another DynamicConfig.
// If rhs.def() is not null, then it has to be equal to this->def().
DynamicConfig& operator=(const DynamicConfig &rhs)
{ {
assert(this->def() == nullptr || this->def() == rhs.def());
this->clear(); this->clear();
for (const auto &kvp : other.options) for (const auto &kvp : rhs.options)
this->options[kvp.first] = kvp.second->clone(); this->options[kvp.first] = kvp.second->clone();
return *this; return *this;
} }
DynamicConfig& operator=(DynamicConfig &&other) // Move a content of one DynamicConfig to another DynamicConfig.
// If rhs.def() is not null, then it has to be equal to this->def().
DynamicConfig& operator=(DynamicConfig &&rhs)
{ {
assert(this->def() == nullptr || this->def() == rhs.def());
this->clear(); this->clear();
this->options = std::move(other.options); this->options = std::move(rhs.options);
other.options.clear(); rhs.options.clear();
return *this; return *this;
} }
// Add a content of one DynamicConfig to another DynamicConfig.
// If rhs.def() is not null, then it has to be equal to this->def().
DynamicConfig& operator+=(const DynamicConfig &rhs)
{
assert(this->def() == nullptr || this->def() == rhs.def());
for (const auto &kvp : rhs.options) {
auto it = this->options.find(kvp.first);
if (it == this->options.end())
this->options[kvp.first] = kvp.second->clone();
else {
assert(it->second->type() == kvp.second->type());
if (it->second->type() == kvp.second->type())
*it->second = *kvp.second;
else {
delete it->second;
it->second = kvp.second->clone();
}
}
}
return *this;
}
// Move a content of one DynamicConfig to another DynamicConfig.
// If rhs.def() is not null, then it has to be equal to this->def().
DynamicConfig& operator+=(DynamicConfig &&rhs)
{
assert(this->def() == nullptr || this->def() == rhs.def());
for (const auto &kvp : rhs.options) {
auto it = this->options.find(kvp.first);
if (it == this->options.end()) {
this->options[kvp.first] = kvp.second;
} else {
assert(it->second->type() == kvp.second->type());
delete it->second;
it->second = kvp.second;
}
}
rhs.options.clear();
return *this;
}
bool operator==(const DynamicConfig &rhs) const;
bool operator!=(const DynamicConfig &rhs) const { return ! (*this == rhs); }
void swap(DynamicConfig &other) void swap(DynamicConfig &other)
{ {
std::swap(this->options, other.options); std::swap(this->options, other.options);
@ -948,6 +1156,9 @@ public:
return true; return true;
} }
// Allow DynamicConfig to be instantiated on ints own without a definition.
// If the definition is not defined, the method requiring the definition will throw NoDefinitionException.
const ConfigDef* def() const override { return nullptr; };
template<class T> T* opt(const t_config_option_key &opt_key, bool create = false) template<class T> T* opt(const t_config_option_key &opt_key, bool create = false)
{ return dynamic_cast<T*>(this->option(opt_key, create)); } { return dynamic_cast<T*>(this->option(opt_key, create)); }
// Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. // Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name.
@ -955,23 +1166,39 @@ public:
// Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. // Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store.
t_config_option_keys keys() const override; t_config_option_keys keys() const override;
std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return dynamic_cast<ConfigOptionString*>(this->option(opt_key, create))->value; } // Set a value for an opt_key. Returns true if the value did not exist yet.
// This DynamicConfig will take ownership of opt.
// Be careful, as this method does not test the existence of opt_key in this->def().
bool set_key_value(const std::string &opt_key, ConfigOption *opt)
{
auto it = this->options.find(opt_key);
if (it == this->options.end()) {
this->options[opt_key] = opt;
return true;
} else {
delete it->second;
it->second = opt;
return false;
}
}
std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option<ConfigOptionString>(opt_key, create)->value; }
const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key); } const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key); }
std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast<ConfigOptionStrings*>(this->option(opt_key))->get_at(idx); } std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }
const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key, idx); } const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key, idx); }
double& opt_float(const t_config_option_key &opt_key) { return dynamic_cast<ConfigOptionFloat*>(this->option(opt_key))->value; } double& opt_float(const t_config_option_key &opt_key) { return this->option<ConfigOptionFloat>(opt_key)->value; }
const double opt_float(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; } const double opt_float(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; }
double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast<ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); } double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloats>(opt_key)->get_at(idx); }
const double opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); } const double opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); }
int& opt_int(const t_config_option_key &opt_key) { return dynamic_cast<ConfigOptionInt*>(this->option(opt_key))->value; } int& opt_int(const t_config_option_key &opt_key) { return this->option<ConfigOptionInt>(opt_key)->value; }
const int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; } const int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; }
int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast<ConfigOptionInts*>(this->option(opt_key))->get_at(idx); } int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionInts>(opt_key)->get_at(idx); }
const int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionInts*>(this->option(opt_key))->get_at(idx); } const int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionInts*>(this->option(opt_key))->get_at(idx); }
protected: bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; }
DynamicConfig() {} bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; }
private: private:
typedef std::map<t_config_option_key,ConfigOption*> t_options_map; typedef std::map<t_config_option_key,ConfigOption*> t_options_map;
@ -1001,6 +1228,13 @@ public:
const char* what() const noexcept override { return "Unknown config option"; } const char* what() const noexcept override { return "Unknown config option"; }
}; };
/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
class NoDefinitionException : public std::exception
{
public:
const char* what() const noexcept override { return "No config definition"; }
};
} }
#endif #endif

View File

@ -149,7 +149,6 @@ inline Polylines to_polylines(const ExPolygons &src)
return polylines; return polylines;
} }
#if SLIC3R_CPPVER >= 11
inline Polylines to_polylines(ExPolygon &&src) inline Polylines to_polylines(ExPolygon &&src)
{ {
Polylines polylines; Polylines polylines;
@ -166,6 +165,7 @@ inline Polylines to_polylines(ExPolygon &&src)
assert(idx == polylines.size()); assert(idx == polylines.size());
return polylines; return polylines;
} }
inline Polylines to_polylines(ExPolygons &&src) inline Polylines to_polylines(ExPolygons &&src)
{ {
Polylines polylines; Polylines polylines;
@ -184,7 +184,6 @@ inline Polylines to_polylines(ExPolygons &&src)
assert(idx == polylines.size()); assert(idx == polylines.size());
return polylines; return polylines;
} }
#endif
inline Polygons to_polygons(const ExPolygon &src) inline Polygons to_polygons(const ExPolygon &src)
{ {

View File

@ -149,8 +149,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
// ); // );
} }
for (Surfaces::const_iterator surface_it = surfaces.begin(); surface_it != surfaces.end(); ++ surface_it) { for (const Surface &surface : surfaces) {
const Surface &surface = *surface_it;
if (surface.surface_type == stInternalVoid) if (surface.surface_type == stInternalVoid)
continue; continue;
InfillPattern fill_pattern = layerm.region()->config.fill_pattern.value; InfillPattern fill_pattern = layerm.region()->config.fill_pattern.value;
@ -262,10 +261,10 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
// Unpacks the collection, creates multiple collections per path. // Unpacks the collection, creates multiple collections per path.
// The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection. // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection.
// Why the paths are unpacked? // Why the paths are unpacked?
for (ExtrusionEntitiesPtr::iterator thin_fill = layerm.thin_fills.entities.begin(); thin_fill != layerm.thin_fills.entities.end(); ++ thin_fill) { for (const ExtrusionEntity *thin_fill : layerm.thin_fills.entities) {
ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection()); ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection());
out.entities.push_back(&collection); out.entities.push_back(&collection);
collection.entities.push_back((*thin_fill)->clone()); collection.entities.push_back(thin_fill->clone());
} }
} }

View File

@ -176,11 +176,7 @@ void Fill3DHoneycomb::_fill_surface_single(
} }
} }
Polylines chained = PolylineCollection::chained_path_from( Polylines chained = PolylineCollection::chained_path_from(
#if SLIC3R_CPPVER >= 11
std::move(polylines), std::move(polylines),
#else
polylines,
#endif
PolylineCollection::leftmost_point(polylines), false); // reverse allowed PolylineCollection::leftmost_point(polylines), false); // reverse allowed
bool first = true; bool first = true;
for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) {
@ -199,12 +195,7 @@ void Fill3DHoneycomb::_fill_surface_single(
} }
} }
// The lines cannot be connected. // The lines cannot be connected.
#if SLIC3R_CPPVER >= 11 polylines_out.emplace_back(std::move(*it_polyline));
polylines_out.push_back(std::move(*it_polyline));
#else
polylines_out.push_back(Polyline());
std::swap(polylines_out.back(), *it_polyline);
#endif
first = false; first = false;
} }
} }

View File

@ -17,12 +17,7 @@ void FillHoneycomb::_fill_surface_single(
CacheID cache_id(params.density, this->spacing); CacheID cache_id(params.density, this->spacing);
Cache::iterator it_m = this->cache.find(cache_id); Cache::iterator it_m = this->cache.find(cache_id);
if (it_m == this->cache.end()) { if (it_m == this->cache.end()) {
#if 0
// #if SLIC3R_CPPVER > 11
it_m = this->cache.emplace_hint(it_m);
#else
it_m = this->cache.insert(it_m, std::pair<CacheID, CacheData>(cache_id, CacheData())); it_m = this->cache.insert(it_m, std::pair<CacheID, CacheData>(cache_id, CacheData()));
#endif
CacheData &m = it_m->second; CacheData &m = it_m->second;
coord_t min_spacing = scale_(this->spacing); coord_t min_spacing = scale_(this->spacing);
m.distance = min_spacing / params.density; m.distance = min_spacing / params.density;
@ -99,11 +94,7 @@ void FillHoneycomb::_fill_surface_single(
// connect paths // connect paths
if (! paths.empty()) { // prevent calling leftmost_point() on empty collections if (! paths.empty()) { // prevent calling leftmost_point() on empty collections
Polylines chained = PolylineCollection::chained_path_from( Polylines chained = PolylineCollection::chained_path_from(
#if SLIC3R_CPPVER >= 11
std::move(paths), std::move(paths),
#else
paths,
#endif
PolylineCollection::leftmost_point(paths), false); PolylineCollection::leftmost_point(paths), false);
assert(paths.empty()); assert(paths.empty());
paths.clear(); paths.clear();

View File

@ -93,11 +93,7 @@ void FillRectilinear::_fill_surface_single(
} }
} }
Polylines chained = PolylineCollection::chained_path_from( Polylines chained = PolylineCollection::chained_path_from(
#if SLIC3R_CPPVER >= 11
std::move(polylines), std::move(polylines),
#else
polylines,
#endif
PolylineCollection::leftmost_point(polylines), false); // reverse allowed PolylineCollection::leftmost_point(polylines), false); // reverse allowed
bool first = true; bool first = true;
for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) {
@ -118,12 +114,7 @@ void FillRectilinear::_fill_surface_single(
} }
} }
// The lines cannot be connected. // The lines cannot be connected.
#if SLIC3R_CPPVER >= 11 polylines_out.emplace_back(std::move(*it_polyline));
polylines_out.push_back(std::move(*it_polyline));
#else
polylines_out.push_back(Polyline());
std::swap(polylines_out.back(), *it_polyline);
#endif
first = false; first = false;
} }
} }

View File

@ -8,39 +8,6 @@ namespace Slic3r {
// This static method returns a sane extrusion width default. // This static method returns a sane extrusion width default.
static inline float auto_extrusion_width(FlowRole role, float nozzle_diameter, float height) static inline float auto_extrusion_width(FlowRole role, float nozzle_diameter, float height)
{ {
#if 0
// Here we calculate a sane default by matching the flow speed (at the nozzle) and the feed rate.
// shape: rectangle with semicircles at the ends
// This "sane" extrusion width gives the following results for a 0.4mm dmr nozzle:
// Layer Calculated Calculated width
// heigh extrusion over nozzle
// width diameter
// 0.40 0.40 1.00
// 0.35 0.43 1.09
// 0.30 0.48 1.21
// 0.25 0.56 1.39
// 0.20 0.67 1.68
// 0.15 0.87 2.17
// 0.10 1.28 3.20
// 0.05 2.52 6.31
//
float width = float(0.25 * (nozzle_diameter * nozzle_diameter) * PI / height + height * (1.0 - 0.25 * PI));
switch (role) {
case frExternalPerimeter:
case frSupportMaterial:
case frSupportMaterialInterface:
return nozzle_diameter;
case frPerimeter:
case frSolidInfill:
case frTopSolidInfill:
// do not limit width for sparse infill so that we use full native flow for it
return std::min(std::max(width, nozzle_diameter * 1.05f), nozzle_diameter * 1.7f);
case frInfill:
default:
return std::max(width, nozzle_diameter * 1.05f);
}
#else
switch (role) { switch (role) {
case frSupportMaterial: case frSupportMaterial:
case frSupportMaterialInterface: case frSupportMaterialInterface:
@ -53,7 +20,6 @@ static inline float auto_extrusion_width(FlowRole role, float nozzle_diameter, f
case frInfill: case frInfill:
return 1.125f * nozzle_diameter; return 1.125f * nozzle_diameter;
} }
#endif
} }
// This constructor builds a Flow object from an extrusion width config setting // This constructor builds a Flow object from an extrusion width config setting
@ -154,10 +120,11 @@ Flow support_material_flow(const PrintObject *object, float layer_height)
Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height) Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
{ {
const auto &width = (object->print()->config.first_layer_extrusion_width.value > 0) ? object->print()->config.first_layer_extrusion_width : object->config.support_material_extrusion_width;
return Flow::new_from_config_width( return Flow::new_from_config_width(
frSupportMaterial, frSupportMaterial,
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(object->print()->config.first_layer_extrusion_width.value > 0) ? object->print()->config.first_layer_extrusion_width : object->config.support_material_extrusion_width, (width.value > 0) ? width : object->config.extrusion_width,
float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)), float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config.first_layer_height.get_abs_value(object->config.layer_height.value)), (layer_height > 0.f) ? layer_height : float(object->config.first_layer_height.get_abs_value(object->config.layer_height.value)),
false); false);

View File

@ -52,6 +52,9 @@ public:
coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); }; coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); };
static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio); static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio);
// Create a flow from the spacing of extrusion lines.
// This method is used exclusively to calculate new flow of 100% infill, where the extrusion width was allowed to scale
// to fit a region with integer number of lines.
static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge); static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge);
}; };

View File

@ -4,6 +4,7 @@
#include "Geometry.hpp" #include "Geometry.hpp"
#include "GCode/PrintExtents.hpp" #include "GCode/PrintExtents.hpp"
#include "GCode/WipeTowerPrusaMM.hpp" #include "GCode/WipeTowerPrusaMM.hpp"
#include "Utils.hpp"
#include <algorithm> #include <algorithm>
#include <cstdlib> #include <cstdlib>
@ -11,7 +12,6 @@
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/find.hpp> #include <boost/algorithm/string/find.hpp>
#include <boost/date_time/local_time/local_time.hpp>
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <boost/nowide/iostream.hpp> #include <boost/nowide/iostream.hpp>
@ -264,10 +264,13 @@ inline void write(FILE *file, const std::string &what)
fwrite(what.data(), 1, what.size(), file); fwrite(what.data(), 1, what.size(), file);
} }
// Write a string into a file. Add a newline, if the string does not end with a newline already.
// Used to export a custom G-code section processed by the PlaceholderParser.
inline void writeln(FILE *file, const std::string &what) inline void writeln(FILE *file, const std::string &what)
{ {
if (! what.empty()) { if (! what.empty()) {
write(file, what); write(file, what);
if (what.back() != '\n')
fprintf(file, "\n"); fprintf(file, "\n");
} }
} }
@ -462,15 +465,7 @@ bool GCode::_do_export(Print &print, FILE *file)
m_enable_extrusion_role_markers = (bool)m_pressure_equalizer; m_enable_extrusion_role_markers = (bool)m_pressure_equalizer;
// Write information on the generator. // Write information on the generator.
{ fprintf(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str());
const auto now = boost::posix_time::second_clock::local_time();
const auto date = now.date();
fprintf(file, "; generated by Slic3r %s on %04d-%02d-%02d at %02d:%02d:%02d\n\n",
SLIC3R_VERSION,
// Local date in an ANSII format.
int(now.date().year()), int(now.date().month()), int(now.date().day()),
int(now.time_of_day().hours()), int(now.time_of_day().minutes()), int(now.time_of_day().seconds()));
}
// Write notes (content of the Print Settings tab -> Notes) // Write notes (content of the Print Settings tab -> Notes)
{ {
std::list<std::string> lines; std::list<std::string> lines;
@ -488,6 +483,7 @@ bool GCode::_do_export(Print &print, FILE *file)
{ {
const PrintObject *first_object = print.objects.front(); const PrintObject *first_object = print.objects.front();
const double layer_height = first_object->config.layer_height.value; const double layer_height = first_object->config.layer_height.value;
const double first_layer_height = first_object->config.first_layer_height.get_abs_value(layer_height);
for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) { for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) {
auto region = print.regions[region_id]; auto region = print.regions[region_id];
fprintf(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width); fprintf(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width);
@ -498,7 +494,7 @@ bool GCode::_do_export(Print &print, FILE *file)
if (print.has_support_material()) if (print.has_support_material())
fprintf(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width); fprintf(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width);
if (print.config.first_layer_extrusion_width.value > 0) if (print.config.first_layer_extrusion_width.value > 0)
fprintf(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, true, -1., *first_object).width); fprintf(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width);
fprintf(file, "\n"); fprintf(file, "\n");
} }
} }
@ -543,28 +539,24 @@ bool GCode::_do_export(Print &print, FILE *file)
if (! print.config.cooling.get_at(initial_extruder_id) || print.config.disable_fan_first_layers.get_at(initial_extruder_id)) if (! print.config.cooling.get_at(initial_extruder_id) || print.config.disable_fan_first_layers.get_at(initial_extruder_id))
write(file, m_writer.set_fan(0, true)); write(file, m_writer.set_fan(0, true));
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
{
// Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if
// the custom start G-code emited these.
//FIXME Should one parse the custom G-code to initialize the "current" bed temp state at m_writer?
std::string gcode = m_writer.set_bed_temperature(print.config.first_layer_bed_temperature.get_at(initial_extruder_id), true);
if (boost::ifind_first(print.config.start_gcode.value, std::string("M140")).empty() &&
boost::ifind_first(print.config.start_gcode.value, std::string("M190")).empty())
write(file, gcode);
}
// Set extruder(s) temperature before and after start G-code.
this->_print_first_layer_extruder_temperatures(file, print, initial_extruder_id, false);
// Let the start-up script prime the 1st printing tool. // Let the start-up script prime the 1st printing tool.
m_placeholder_parser.set("initial_tool", initial_extruder_id); m_placeholder_parser.set("initial_tool", initial_extruder_id);
m_placeholder_parser.set("initial_extruder", initial_extruder_id); m_placeholder_parser.set("initial_extruder", initial_extruder_id);
m_placeholder_parser.set("current_extruder", initial_extruder_id); m_placeholder_parser.set("current_extruder", initial_extruder_id);
writeln(file, m_placeholder_parser.process(print.config.start_gcode.value, initial_extruder_id)); // Useful for sequential prints.
m_placeholder_parser.set("current_object_idx", 0);
std::string start_gcode = m_placeholder_parser.process(print.config.start_gcode.value, initial_extruder_id);
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
// Set extruder(s) temperature before and after start G-code.
this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false);
// Write the custom start G-code
writeln(file, start_gcode);
// Process filament-specific gcode in extruder order. // Process filament-specific gcode in extruder order.
for (const std::string &start_gcode : print.config.start_filament_gcode.values) for (const std::string &start_gcode : print.config.start_filament_gcode.values)
writeln(file, m_placeholder_parser.process(start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front()))); writeln(file, m_placeholder_parser.process(start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front())));
this->_print_first_layer_extruder_temperatures(file, print, initial_extruder_id, true); this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true);
// Set other general things. // Set other general things.
write(file, this->preamble()); write(file, this->preamble());
@ -654,9 +646,12 @@ bool GCode::_do_export(Print &print, FILE *file)
// Ff we are printing the bottom layer of an object, and we have already finished // Ff we are printing the bottom layer of an object, and we have already finished
// another one, set first layer temperatures. This happens before the Z move // another one, set first layer temperatures. This happens before the Z move
// is triggered, so machine has more time to reach such temperatures. // is triggered, so machine has more time to reach such temperatures.
write(file, m_writer.set_bed_temperature(print.config.first_layer_bed_temperature.get_at(initial_extruder_id))); m_placeholder_parser.set("current_object_idx", int(finished_objects));
// Set first layer extruder. std::string between_objects_gcode = m_placeholder_parser.process(print.config.between_objects_gcode.value, initial_extruder_id);
this->_print_first_layer_extruder_temperatures(file, print, initial_extruder_id, false); // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature.
this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false);
this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false);
writeln(file, between_objects_gcode);
} }
// Reset the cooling buffer internal state (the current position, feed rate, accelerations). // Reset the cooling buffer internal state (the current position, feed rate, accelerations).
m_cooling_buffer->reset(); m_cooling_buffer->reset();
@ -778,15 +773,96 @@ bool GCode::_do_export(Print &print, FILE *file)
return true; return true;
} }
// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait inside the custom G-code.
// Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out.
static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, int &temp_out)
{
temp_out = -1;
if (gcode.empty())
return false;
const char *ptr = gcode.data();
bool temp_set_by_gcode = false;
while (*ptr != 0) {
// Skip whitespaces.
for (; *ptr == ' ' || *ptr == '\t'; ++ ptr);
if (*ptr == 'M') {
// Line starts with 'M'. It is a machine command.
++ ptr;
// Parse the M code value.
char *endptr = nullptr;
int mcode = int(strtol(ptr, &endptr, 10));
if (endptr != nullptr && endptr != ptr && (mcode == mcode_set_temp_dont_wait || mcode == mcode_set_temp_and_wait)) {
// M104/M109 or M140/M190 found.
ptr = endptr;
// Let the caller know that the custom G-code sets the temperature.
temp_set_by_gcode = true;
// Now try to parse the temperature value.
// While not at the end of the line:
while (strchr(";\r\n\0", *ptr) == nullptr) {
// Skip whitespaces.
for (; *ptr == ' ' || *ptr == '\t'; ++ ptr);
if (*ptr == 'S') {
// Skip whitespaces.
for (++ ptr; *ptr == ' ' || *ptr == '\t'; ++ ptr);
// Parse an int.
endptr = nullptr;
long temp_parsed = strtol(ptr, &endptr, 10);
if (endptr > ptr) {
ptr = endptr;
temp_out = temp_parsed;
}
} else {
// Skip this word.
for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr);
}
}
}
}
// Skip the rest of the line.
for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr);
// Skip the end of line indicators.
for (; *ptr == '\r' || *ptr == '\n'; ++ ptr);
}
return temp_set_by_gcode;
}
// Write 1st layer bed temperatures into the G-code.
// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
// M140 - Set Extruder Temperature
// M190 - Set Extruder Temperature and Wait
void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
{
// Initial bed temperature based on the first extruder.
int temp = print.config.first_layer_bed_temperature.get_at(first_printing_extruder_id);
// Is the bed temperature set by the provided custom G-code?
int temp_by_gcode = -1;
bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, temp_by_gcode);
if (temp_by_gcode >= 0 && temp_by_gcode < 1000)
temp = temp_by_gcode;
// Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if
// the custom start G-code emited these.
std::string set_temp_gcode = m_writer.set_bed_temperature(temp, wait);
if (! temp_by_gcode)
write(file, set_temp_gcode);
}
// Write 1st layer extruder temperatures into the G-code. // Write 1st layer extruder temperatures into the G-code.
// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
// FIXME this does not work correctly for multi-extruder, single heater configuration as it emits multiple preheat commands for the same heater.
// M104 - Set Extruder Temperature // M104 - Set Extruder Temperature
// M109 - Set Extruder Temperature and Wait // M109 - Set Extruder Temperature and Wait
void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, unsigned int first_printing_extruder_id, bool wait) void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
{ {
if (boost::ifind_first(print.config.start_gcode.value, std::string("M104")).empty() && // Is the bed temperature set by the provided custom G-code?
boost::ifind_first(print.config.start_gcode.value, std::string("M109")).empty()) { int temp_by_gcode = -1;
if (custom_gcode_sets_temperature(gcode, 104, 109, temp_by_gcode)) {
// Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code.
int temp = print.config.first_layer_temperature.get_at(first_printing_extruder_id);
if (temp_by_gcode >= 0 && temp_by_gcode < 1000)
temp = temp_by_gcode;
m_writer.set_temperature(temp_by_gcode, wait, first_printing_extruder_id);
} else {
// Custom G-code does not set the extruder temperature. Do it now.
if (print.config.single_extruder_multi_material.value) { if (print.config.single_extruder_multi_material.value) {
// Set temperature of the first printing extruder only. // Set temperature of the first printing extruder only.
int temp = print.config.first_layer_temperature.get_at(first_printing_extruder_id); int temp = print.config.first_layer_temperature.get_at(first_printing_extruder_id);
@ -894,18 +970,22 @@ void GCode::process_layer(
// Set new layer - this will change Z and force a retraction if retract_layer_change is enabled. // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled.
if (! print.config.before_layer_gcode.value.empty()) { if (! print.config.before_layer_gcode.value.empty()) {
PlaceholderParser pp(m_placeholder_parser); DynamicConfig config;
pp.set("layer_num", m_layer_index + 1); config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1));
pp.set("layer_z", print_z); config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
gcode += pp.process(print.config.before_layer_gcode.value, m_writer.extruder()->id()) + "\n"; gcode += m_placeholder_parser.process(
print.config.before_layer_gcode.value, m_writer.extruder()->id(), &config)
+ "\n";
} }
gcode += this->change_layer(print_z); // this will increase m_layer_index gcode += this->change_layer(print_z); // this will increase m_layer_index
m_layer = &layer; m_layer = &layer;
if (! print.config.layer_gcode.value.empty()) { if (! print.config.layer_gcode.value.empty()) {
PlaceholderParser pp(m_placeholder_parser); DynamicConfig config;
pp.set("layer_num", m_layer_index); config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
pp.set("layer_z", print_z); config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
gcode += pp.process(print.config.layer_gcode.value, m_writer.extruder()->id()) + "\n"; gcode += m_placeholder_parser.process(
print.config.layer_gcode.value, m_writer.extruder()->id(), &config)
+ "\n";
} }
if (! first_layer && ! m_second_layer_things_done) { if (! first_layer && ! m_second_layer_things_done) {
@ -2098,10 +2178,12 @@ std::string GCode::set_extruder(unsigned int extruder_id)
// append custom toolchange G-code // append custom toolchange G-code
if (m_writer.extruder() != nullptr && !m_config.toolchange_gcode.value.empty()) { if (m_writer.extruder() != nullptr && !m_config.toolchange_gcode.value.empty()) {
PlaceholderParser pp = m_placeholder_parser; DynamicConfig config;
pp.set("previous_extruder", m_writer.extruder()->id()); config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id()));
pp.set("next_extruder", extruder_id); config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id));
gcode += pp.process(m_config.toolchange_gcode.value, extruder_id) + '\n'; gcode += m_placeholder_parser.process(
m_config.toolchange_gcode.value, extruder_id, &config)
+ '\n';
} }
// if ooze prevention is enabled, park current extruder in the nearest // if ooze prevention is enabled, park current extruder in the nearest

View File

@ -268,7 +268,8 @@ protected:
std::pair<const PrintObject*, Point> m_last_obj_copy; std::pair<const PrintObject*, Point> m_last_obj_copy;
std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1); std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1);
void _print_first_layer_extruder_temperatures(FILE *file, Print &print, unsigned int first_printing_extruder_id, bool wait); void _print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
void _print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
// this flag triggers first layer speeds // this flag triggers first layer speeds
bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; } bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; }

View File

@ -21,8 +21,8 @@ public:
xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; } xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; }
xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; } xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; }
xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; } xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; }
bool operator==(const xy &rhs) { return x == rhs.x && y == rhs.y; } bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; }
bool operator!=(const xy &rhs) { return x != rhs.x || y != rhs.y; } bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; }
// Rotate the point around given point about given angle (in degrees) // Rotate the point around given point about given angle (in degrees)
xy rotate(const xy& origin, float angle) const { xy rotate(const xy& origin, float angle) const {

View File

@ -21,6 +21,35 @@
#endif #endif
#endif #endif
#include <boost/algorithm/string.hpp>
// Spirit v2.5 allows you to suppress automatic generation
// of predefined terminals to speed up complation. With
// BOOST_SPIRIT_NO_PREDEFINED_TERMINALS defined, you are
// responsible in creating instances of the terminals that
// you need (e.g. see qi::uint_type uint_ below).
//#define BOOST_SPIRIT_NO_PREDEFINED_TERMINALS
#define BOOST_RESULT_OF_USE_DECLTYPE
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/config/warning_disable.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/qi_lit.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <boost/spirit/include/phoenix_object.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/repository/include/qi_distinct.hpp>
#include <boost/spirit/repository/include/qi_iter_pos.hpp>
#include <boost/variant/recursive_variant.hpp>
#include <boost/phoenix/bind/bind_function.hpp>
#include <iostream>
#include <string>
namespace Slic3r { namespace Slic3r {
PlaceholderParser::PlaceholderParser() PlaceholderParser::PlaceholderParser()
@ -61,130 +90,821 @@ void PlaceholderParser::update_timestamp()
// are expected to be addressed by the extruder ID, therefore // are expected to be addressed by the extruder ID, therefore
// if a vector configuration value is addressed without an index, // if a vector configuration value is addressed without an index,
// a current extruder ID is used. // a current extruder ID is used.
void PlaceholderParser::apply_config(const DynamicPrintConfig &config) void PlaceholderParser::apply_config(const DynamicPrintConfig &rhs)
{ {
for (const t_config_option_key &opt_key : config.keys()) { const ConfigDef *def = rhs.def();
const ConfigOptionDef* def = config.def()->get(opt_key); for (const t_config_option_key &opt_key : rhs.keys()) {
if (def->multiline || opt_key == "post_process") const ConfigOptionDef *opt_def = def->get(opt_key);
if ((opt_def->multiline && boost::ends_with(opt_key, "_gcode")) || opt_key == "post_process")
continue; continue;
const ConfigOption *opt = rhs.option(opt_key);
const ConfigOption* opt = config.option(opt_key); // Store a copy of the config option.
const ConfigOptionVectorBase* optv = dynamic_cast<const ConfigOptionVectorBase*>(opt); // Convert FloatOrPercent values to floats first.
if (optv != nullptr && opt_key != "bed_shape") { //FIXME there are some ratio_over chains, which end with empty ratio_with.
// set placeholders for options with multiple values // For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly.
this->set(opt_key, optv->vserialize()); this->set(opt_key, (opt->type() == coFloatOrPercent) ?
} else if (const ConfigOptionPoint* optp = dynamic_cast<const ConfigOptionPoint*>(opt)) { new ConfigOptionFloat(rhs.get_abs_value(opt_key)) :
this->set(opt_key, optp->serialize()); opt->clone());
Pointf val = *optp;
this->set(opt_key + "_X", val.x);
this->set(opt_key + "_Y", val.y);
} else {
// set single-value placeholders
this->set(opt_key, opt->serialize());
}
} }
} }
void PlaceholderParser::apply_env_variables() void PlaceholderParser::apply_env_variables()
{ {
for (char** env = environ; *env; env++) { for (char** env = environ; *env; ++ env) {
if (strncmp(*env, "SLIC3R_", 7) == 0) { if (strncmp(*env, "SLIC3R_", 7) == 0) {
std::stringstream ss(*env); std::stringstream ss(*env);
std::string key, value; std::string key, value;
std::getline(ss, key, '='); std::getline(ss, key, '=');
ss >> value; ss >> value;
this->set(key, value); this->set(key, value);
} }
} }
} }
void PlaceholderParser::set(const std::string &key, const std::string &value) namespace spirit = boost::spirit;
namespace qi = boost::spirit::qi;
namespace px = boost::phoenix;
namespace client
{ {
m_single[key] = value; template<typename Iterator>
m_multiple.erase(key); struct OptWithPos {
OptWithPos() {}
OptWithPos(ConfigOptionConstPtr opt, boost::iterator_range<Iterator> it_range) : opt(opt), it_range(it_range) {}
ConfigOptionConstPtr opt = nullptr;
boost::iterator_range<Iterator> it_range;
};
template<typename ITERATOR>
std::ostream& operator<<(std::ostream& os, OptWithPos<ITERATOR> const& opt)
{
os << std::string(opt.it_range.begin(), opt.it_range.end());
return os;
} }
void PlaceholderParser::set(const std::string &key, int value) template<typename Iterator>
struct expr
{ {
std::ostringstream ss; expr() : type(TYPE_EMPTY) {}
ss << value; explicit expr(bool b) : type(TYPE_BOOL) { data.b = b; }
this->set(key, ss.str()); explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_BOOL), it_range(it_begin, it_end) { data.b = b; }
} explicit expr(int i) : type(TYPE_INT) { data.i = i; }
explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_INT), it_range(it_begin, it_end) { data.i = i; }
explicit expr(double d) : type(TYPE_DOUBLE) { data.d = d; }
explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_DOUBLE), it_range(it_begin, it_end) { data.d = d; }
explicit expr(const char *s) : type(TYPE_STRING) { data.s = new std::string(s); }
explicit expr(const std::string &s) : type(TYPE_STRING) { data.s = new std::string(s); }
explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) :
type(TYPE_STRING), it_range(it_begin, it_end) { data.s = new std::string(s); }
expr(const expr &rhs) : type(rhs.type), it_range(rhs.it_range)
{ if (rhs.type == TYPE_STRING) data.s = new std::string(*rhs.data.s); else data.set(rhs.data); }
explicit expr(expr &&rhs) : type(rhs.type), it_range(rhs.it_range)
{ data.set(rhs.data); rhs.type = TYPE_EMPTY; }
explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : type(rhs.type), it_range(it_begin, it_end)
{ data.set(rhs.data); rhs.type = TYPE_EMPTY; }
~expr() { this->reset(); }
void PlaceholderParser::set(const std::string &key, unsigned int value) expr &operator=(const expr &rhs)
{ {
std::ostringstream ss; this->type = rhs.type;
ss << value; this->it_range = rhs.it_range;
this->set(key, ss.str()); if (rhs.type == TYPE_STRING)
} this->data.s = new std::string(*rhs.data.s);
void PlaceholderParser::set(const std::string &key, double value)
{
std::ostringstream ss;
ss << value;
this->set(key, ss.str());
}
void PlaceholderParser::set(const std::string &key, std::vector<std::string> values)
{
m_single.erase(key);
if (values.empty())
m_multiple.erase(key);
else else
m_multiple[key] = values; this->data.set(rhs.data);
return *this;
} }
std::string PlaceholderParser::process(std::string str, unsigned int current_extruder_id) const expr &operator=(expr &&rhs)
{ {
char key[2048]; type = rhs.type;
this->it_range = rhs.it_range;
data.set(rhs.data);
rhs.type = TYPE_EMPTY;
return *this;
}
// Replace extruder independent single options, like [foo]. void reset()
for (const auto &key_value : m_single) { {
sprintf(key, "[%s]", key_value.first.c_str()); if (this->type == TYPE_STRING)
const std::string &replace = key_value.second; delete data.s;
for (size_t i = 0; (i = str.find(key, i)) != std::string::npos;) { this->type = TYPE_EMPTY;
str.replace(i, key_value.first.size() + 2, replace); }
i += replace.size();
bool& b() { return data.b; }
bool b() const { return data.b; }
void set_b(bool v) { this->reset(); this->data.b = v; this->type = TYPE_BOOL; }
int& i() { return data.i; }
int i() const { return data.i; }
void set_i(int v) { this->reset(); this->data.i = v; this->type = TYPE_INT; }
int as_i() const { return (this->type == TYPE_INT) ? this->i() : int(this->d()); }
double& d() { return data.d; }
double d() const { return data.d; }
void set_d(double v) { this->reset(); this->data.d = v; this->type = TYPE_DOUBLE; }
double as_d() const { return (this->type == TYPE_DOUBLE) ? this->d() : double(this->i()); }
std::string& s() { return *data.s; }
const std::string& s() const { return *data.s; }
void set_s(const std::string &s) { this->reset(); this->data.s = new std::string(s); this->type = TYPE_STRING; }
void set_s(std::string &&s) { this->reset(); this->data.s = new std::string(std::move(s)); this->type = TYPE_STRING; }
std::string to_string() const
{
std::string out;
switch (type) {
case TYPE_BOOL: out = boost::to_string(data.b); break;
case TYPE_INT: out = boost::to_string(data.i); break;
case TYPE_DOUBLE: out = boost::to_string(data.d); break;
case TYPE_STRING: out = *data.s; break;
default: break;
}
return out;
}
union Data {
// Raw image of the other data members.
// The C++ compiler will consider a possible aliasing of char* with any other union member,
// therefore copying the raw data is safe.
char raw[8];
bool b;
int i;
double d;
std::string *s;
// Copy the largest member variable through char*, which will alias with all other union members by default.
void set(const Data &rhs) { memcpy(this->raw, rhs.raw, sizeof(rhs.raw)); }
} data;
enum Type {
TYPE_EMPTY = 0,
TYPE_BOOL,
TYPE_INT,
TYPE_DOUBLE,
TYPE_STRING,
};
Type type;
// Range of input iterators covering this expression.
// Used for throwing parse exceptions.
boost::iterator_range<Iterator> it_range;
expr unary_minus(const Iterator start_pos) const
{
switch (this->type) {
case TYPE_INT :
return expr<Iterator>(- this->i(), start_pos, this->it_range.end());
case TYPE_DOUBLE:
return expr<Iterator>(- this->d(), start_pos, this->it_range.end());
default:
this->throw_exception("Cannot apply unary minus operator.");
}
assert(false);
// Suppress compiler warnings.
return expr();
}
expr unary_not(const Iterator start_pos) const
{
switch (this->type) {
case TYPE_BOOL :
return expr<Iterator>(! this->b(), start_pos, this->it_range.end());
default:
this->throw_exception("Cannot apply a not operator.");
}
assert(false);
// Suppress compiler warnings.
return expr();
}
expr &operator+=(const expr &rhs)
{
const char *err_msg = "Cannot multiply with non-numeric type.";
this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = this->as_d() + rhs.as_d();
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i += rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
}
expr &operator-=(const expr &rhs)
{
const char *err_msg = "Cannot multiply with non-numeric type.";
this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = this->as_d() - rhs.as_d();
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i -= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
}
expr &operator*=(const expr &rhs)
{
const char *err_msg = "Cannot multiply with non-numeric type.";
this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = this->as_d() * rhs.as_d();
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i *= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
}
expr &operator/=(const expr &rhs)
{
this->throw_if_not_numeric("Cannot divide a non-numeric type.");
rhs.throw_if_not_numeric("Cannot divide with a non-numeric type.");
if ((this->type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.))
rhs.throw_exception("Division by zero");
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = this->as_d() / rhs.as_d();
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i /= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
}
static void to_string2(expr &self, std::string &out)
{
out = self.to_string();
}
static void evaluate_boolean(expr &self, bool &out)
{
if (self.type != TYPE_BOOL)
self.throw_exception("Not a boolean expression");
out = self.b();
}
// Is lhs==rhs? Store the result into lhs.
static void compare_op(expr &lhs, expr &rhs, char op)
{
bool value = false;
if ((lhs.type == TYPE_INT || lhs.type == TYPE_DOUBLE) &&
(rhs.type == TYPE_INT || rhs.type == TYPE_DOUBLE)) {
// Both types are numeric.
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
(lhs.as_d() == rhs.as_d()) : (lhs.i() == rhs.i());
} else if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) {
// Both type are bool.
value = lhs.b() == rhs.b();
} else if (lhs.type == TYPE_STRING || rhs.type == TYPE_STRING) {
// One type is string, the other could be converted to string.
value = lhs.to_string() == rhs.to_string();
} else {
boost::throw_exception(qi::expectation_failure<Iterator>(
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("Cannot compare the types.")));
}
lhs.type = TYPE_BOOL;
lhs.data.b = (op == '=') ? value : !value;
}
static void equal(expr &lhs, expr &rhs) { compare_op(lhs, rhs, '='); }
static void not_equal(expr &lhs, expr &rhs) { compare_op(lhs, rhs, '!'); }
static void set_if(bool &cond, bool &not_yet_consumed, std::string &str_in, std::string &str_out)
{
if (cond && not_yet_consumed) {
str_out = str_in;
not_yet_consumed = false;
} }
} }
// Replace extruder dependent single options with the value for the active extruder. void throw_exception(const char *message) const
// For example, [temperature] will be replaced with the current extruder temperature. {
for (const auto &key_value : m_multiple) { boost::throw_exception(qi::expectation_failure<Iterator>(
sprintf(key, "[%s]", key_value.first.c_str()); this->it_range.begin(), this->it_range.end(), spirit::info(message)));
const std::string &replace = key_value.second[(current_extruder_id < key_value.second.size()) ? current_extruder_id : 0]; }
for (size_t i = 0; (i = str.find(key, i)) != std::string::npos;) {
str.replace(i, key_value.first.size() + 2, replace); void throw_if_not_numeric(const char *message) const
i += replace.size(); {
if (this->type != TYPE_INT && this->type != TYPE_DOUBLE)
this->throw_exception(message);
}
};
template<typename ITERATOR>
std::ostream& operator<<(std::ostream &os, const expr<ITERATOR> &expression)
{
typedef expr<ITERATOR> Expr;
os << std::string(expression.it_range.begin(), expression.it_range.end()) << " - ";
switch (expression.type) {
case Expr::TYPE_EMPTY: os << "empty"; break;
case Expr::TYPE_BOOL: os << "bool (" << expression.b() << ")"; break;
case Expr::TYPE_INT: os << "int (" << expression.i() << ")"; break;
case Expr::TYPE_DOUBLE: os << "double (" << expression.d() << ")"; break;
case Expr::TYPE_STRING: os << "string (" << expression.s() << ")"; break;
default: os << "unknown";
};
return os;
}
struct MyContext {
const PlaceholderParser *pp = nullptr;
const DynamicConfig *config_override = nullptr;
const size_t current_extruder_id = 0;
const ConfigOption* resolve_symbol(const std::string &opt_key) const
{
const ConfigOption *opt = nullptr;
if (config_override != nullptr)
opt = config_override->option(opt_key);
if (opt == nullptr)
opt = pp->option(opt_key);
return opt;
}
template <typename Iterator>
static void legacy_variable_expansion(
const MyContext *ctx,
boost::iterator_range<Iterator> &opt_key,
std::string &output)
{
std::string opt_key_str(opt_key.begin(), opt_key.end());
const ConfigOption *opt = ctx->resolve_symbol(opt_key_str);
size_t idx = ctx->current_extruder_id;
if (opt == nullptr) {
// Check whether this is a legacy vector indexing.
idx = opt_key_str.rfind('_');
if (idx != std::string::npos) {
opt = ctx->resolve_symbol(opt_key_str.substr(0, idx));
if (opt != nullptr) {
if (! opt->is_vector())
boost::throw_exception(qi::expectation_failure<Iterator>(
opt_key.begin(), opt_key.end(), spirit::info("Trying to index a scalar variable")));
char *endptr = nullptr;
idx = strtol(opt_key_str.c_str() + idx + 1, &endptr, 10);
if (endptr == nullptr || *endptr != 0)
boost::throw_exception(qi::expectation_failure<Iterator>(
opt_key.begin() + idx + 1, opt_key.end(), spirit::info("Invalid vector index")));
}
}
}
if (opt == nullptr)
boost::throw_exception(qi::expectation_failure<Iterator>(
opt_key.begin(), opt_key.end(), spirit::info("Variable does not exist")));
if (opt->is_scalar())
output = opt->serialize();
else {
const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt);
if (vec->empty())
boost::throw_exception(qi::expectation_failure<Iterator>(
opt_key.begin(), opt_key.end(), spirit::info("Indexing an empty vector variable")));
output = vec->vserialize()[(idx >= vec->size()) ? 0 : idx];
} }
} }
// Replace multiple options like [foo_0]. template <typename Iterator>
for (const auto &key_value : m_multiple) { static void legacy_variable_expansion2(
sprintf(key, "[%s_", key_value.first.c_str()); const MyContext *ctx,
const std::vector<std::string> &values = key_value.second; boost::iterator_range<Iterator> &opt_key,
for (size_t i = 0; (i = str.find(key, i)) != std::string::npos;) { boost::iterator_range<Iterator> &opt_vector_index,
size_t k = str.find(']', i + key_value.first.size() + 2); std::string &output)
if (k != std::string::npos) { {
// Parse the key index and the closing bracket. std::string opt_key_str(opt_key.begin(), opt_key.end());
++ k; const ConfigOption *opt = ctx->resolve_symbol(opt_key_str);
int idx = 0; if (opt == nullptr) {
if (sscanf(str.c_str() + i + key_value.first.size() + 2, "%d]", &idx) == 1 && idx >= 0) { // Check whether the opt_key ends with '_'.
if (idx >= int(values.size())) if (opt_key_str.back() == '_')
idx = 0; opt_key_str.resize(opt_key_str.size() - 1);
str.replace(i, k - i, values[idx]); opt = ctx->resolve_symbol(opt_key_str);
i += values[idx].size();
continue;
} }
if (! opt->is_vector())
boost::throw_exception(qi::expectation_failure<Iterator>(
opt_key.begin(), opt_key.end(), spirit::info("Trying to index a scalar variable")));
const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt);
if (vec->empty())
boost::throw_exception(qi::expectation_failure<Iterator>(
opt_key.begin(), opt_key.end(), spirit::info("Indexing an empty vector variable")));
const ConfigOption *opt_index = ctx->resolve_symbol(std::string(opt_vector_index.begin(), opt_vector_index.end()));
if (opt_index == nullptr)
boost::throw_exception(qi::expectation_failure<Iterator>(
opt_key.begin(), opt_key.end(), spirit::info("Variable does not exist")));
if (opt_index->type() != coInt)
boost::throw_exception(qi::expectation_failure<Iterator>(
opt_key.begin(), opt_key.end(), spirit::info("Indexing variable has to be integer")));
int idx = opt_index->getInt();
if (idx < 0)
boost::throw_exception(qi::expectation_failure<Iterator>(
opt_key.begin(), opt_key.end(), spirit::info("Negative vector index")));
output = vec->vserialize()[(idx >= (int)vec->size()) ? 0 : idx];
} }
// The key does not match the pattern [foo_%d]. Skip just [foo_.] with the hope that there was a missing ']',
// so an opening '[' may be found somewhere before the position k. template <typename Iterator>
i += key_value.first.size() + 3; static void resolve_variable(
const MyContext *ctx,
boost::iterator_range<Iterator> &opt_key,
OptWithPos<Iterator> &output)
{
const ConfigOption *opt = ctx->resolve_symbol(std::string(opt_key.begin(), opt_key.end()));
if (opt == nullptr)
boost::throw_exception(qi::expectation_failure<Iterator>(
opt_key.begin(), opt_key.end(), spirit::info("Not a variable name")));
output.opt = opt;
output.it_range = opt_key;
}
template <typename Iterator>
static void scalar_variable_reference(
const MyContext *ctx,
OptWithPos<Iterator> &opt,
expr<Iterator> &output)
{
if (opt.opt->is_vector())
boost::throw_exception(qi::expectation_failure<Iterator>(
opt.it_range.begin(), opt.it_range.end(), spirit::info("Referencing a scalar variable in a vector context")));
switch (opt.opt->type()) {
case coFloat: output.set_d(opt.opt->getFloat()); break;
case coInt: output.set_i(opt.opt->getInt()); break;
case coString: output.set_s(static_cast<const ConfigOptionString*>(opt.opt)->value); break;
case coPercent: output.set_d(opt.opt->getFloat()); break;
case coPoint: output.set_s(opt.opt->serialize()); break;
case coBool: output.set_b(opt.opt->getBool()); break;
case coFloatOrPercent:
boost::throw_exception(qi::expectation_failure<Iterator>(
opt.it_range.begin(), opt.it_range.end(), spirit::info("FloatOrPercent variables are not supported")));
default:
boost::throw_exception(qi::expectation_failure<Iterator>(
opt.it_range.begin(), opt.it_range.end(), spirit::info("Unknown scalar variable type")));
}
output.it_range = opt.it_range;
}
template <typename Iterator>
static void vector_variable_reference(
const MyContext *ctx,
OptWithPos<Iterator> &opt,
int &index,
Iterator it_end,
expr<Iterator> &output)
{
if (opt.opt->is_scalar())
boost::throw_exception(qi::expectation_failure<Iterator>(
opt.it_range.begin(), opt.it_range.end(), spirit::info("Referencing a vector variable in a scalar context")));
const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt.opt);
if (vec->empty())
boost::throw_exception(qi::expectation_failure<Iterator>(
opt.it_range.begin(), opt.it_range.end(), spirit::info("Indexing an empty vector variable")));
size_t idx = (index < 0) ? 0 : (index >= int(vec->size())) ? 0 : size_t(index);
switch (opt.opt->type()) {
case coFloats: output.set_d(static_cast<const ConfigOptionFloats *>(opt.opt)->values[idx]); break;
case coInts: output.set_i(static_cast<const ConfigOptionInts *>(opt.opt)->values[idx]); break;
case coStrings: output.set_s(static_cast<const ConfigOptionStrings *>(opt.opt)->values[idx]); break;
case coPercents: output.set_d(static_cast<const ConfigOptionPercents*>(opt.opt)->values[idx]); break;
case coPoints: output.set_s(static_cast<const ConfigOptionPoints *>(opt.opt)->values[idx].dump_perl()); break;
case coBools: output.set_b(static_cast<const ConfigOptionBools *>(opt.opt)->values[idx] != 0); break;
default:
boost::throw_exception(qi::expectation_failure<Iterator>(
opt.it_range.begin(), opt.it_range.end(), spirit::info("Unknown vector variable type")));
}
output.it_range = boost::iterator_range<Iterator>(opt.it_range.begin(), it_end);
}
// Verify that the expression returns an integer, which may be used
// to address a vector.
template <typename Iterator>
static void evaluate_index(expr<Iterator> &expr_index, int &output)
{
if (expr_index.type != expr<Iterator>::TYPE_INT)
expr_index.throw_exception("Non-integer index is not allowed to address a vector variable.");
output = expr_index.i();
}
};
// For debugging the boost::spirit parsers. Print out the string enclosed in it_range.
template<typename Iterator>
std::ostream& operator<<(std::ostream& os, const boost::iterator_range<Iterator> &it_range)
{
os << std::string(it_range.begin(), it_range.end());
return os;
}
// Disable parsing int numbers (without decimals) and Inf/NaN symbols by the double parser.
struct strict_real_policies_without_nan_inf : public qi::strict_real_policies<double>
{
template <typename It, typename Attr> static bool parse_nan(It&, It const&, Attr&) { return false; }
template <typename It, typename Attr> static bool parse_inf(It&, It const&, Attr&) { return false; }
};
///////////////////////////////////////////////////////////////////////////
// Our calculator grammar
///////////////////////////////////////////////////////////////////////////
// Inspired by the C grammar rules https://www.lysator.liu.se/c/ANSI-C-grammar-y.html
template <typename Iterator>
struct calculator : qi::grammar<Iterator, std::string(const MyContext*), spirit::ascii::space_type>
{
calculator() : calculator::base_type(start)
{
using namespace qi::labels;
qi::alpha_type alpha;
qi::alnum_type alnum;
qi::eps_type eps;
qi::raw_type raw;
qi::lit_type lit;
qi::lexeme_type lexeme;
qi::no_skip_type no_skip;
qi::real_parser<double, strict_real_policies_without_nan_inf> strict_double;
spirit::ascii::char_type char_;
spirit::bool_type bool_;
spirit::int_type int_;
spirit::double_type double_;
spirit::ascii::string_type string;
spirit::repository::qi::iter_pos_type iter_pos;
auto kw = spirit::repository::qi::distinct(qi::copy(alnum | '_'));
qi::_val_type _val;
qi::_1_type _1;
qi::_2_type _2;
qi::_a_type _a;
qi::_b_type _b;
qi::_r1_type _r1;
// Starting symbol of the grammer.
// The leading eps is required by the "expectation point" operator ">".
// Without it, some of the errors would not trigger the error handler.
start = eps > text_block(_r1);
start.name("start");
text_block = *(
text [_val+=_1]
// Allow back tracking after '{' in case of a text_block embedded inside a condition.
// In that case the inner-most {else} wins and the {if}/{elsif}/{else} shall be paired.
// {elsif}/{else} without an {if} will be allowed to back track from the embedded text_block.
| (lit('{') >> macro(_r1) [_val+=_1] > '}')
| (lit('[') > legacy_variable_expansion(_r1) [_val+=_1] > ']')
);
text_block.name("text_block");
// Free-form text up to a first brace, including spaces and newlines.
// The free-form text will be inserted into the processed text without a modification.
text = no_skip[raw[+(char_ - '[' - '{')]];
text.name("text");
// New style of macro expansion.
// The macro expansion may contain numeric or string expressions, ifs and cases.
macro =
(kw["if"] > if_else_output(_r1) [_val = _1])
| (kw["switch"] > switch_output(_r1) [_val = _1])
| additive_expression(_r1) [ px::bind(&expr<Iterator>::to_string2, _1, _val) ];
macro.name("macro");
// An if expression enclosed in {} (the outmost {} are already parsed by the caller).
if_else_output =
eps[_b=true] >
bool_expr_eval(_r1)[_a=_1] > '}' >
text_block(_r1)[px::bind(&expr<Iterator>::set_if, _a, _b, _1, _val)] > '{' >
*(kw["elsif"] > bool_expr_eval(_r1)[_a=_1] > '}' >
text_block(_r1)[px::bind(&expr<Iterator>::set_if, _a, _b, _1, _val)] > '{') >
-(kw["else"] > lit('}') >
text_block(_r1)[px::bind(&expr<Iterator>::set_if, _b, _b, _1, _val)] > '{') >
kw["endif"];
if_else_output.name("if_else_output");
// A switch expression enclosed in {} (the outmost {} are already parsed by the caller).
/*
switch_output =
eps[_b=true] >
omit[expr(_r1)[_a=_1]] > '}' > text_block(_r1)[px::bind(&expr<Iterator>::set_if_equal, _a, _b, _1, _val)] > '{' >
*("elsif" > omit[bool_expr_eval(_r1)[_a=_1]] > '}' > text_block(_r1)[px::bind(&expr<Iterator>::set_if, _a, _b, _1, _val)]) >>
-("else" > '}' >> text_block(_r1)[px::bind(&expr<Iterator>::set_if, _b, _b, _1, _val)]) >
"endif";
*/
// Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index].
legacy_variable_expansion =
(identifier >> &lit(']'))
[ px::bind(&MyContext::legacy_variable_expansion<Iterator>, _r1, _1, _val) ]
| (identifier > lit('[') > identifier > ']')
[ px::bind(&MyContext::legacy_variable_expansion2<Iterator>, _r1, _1, _2, _val) ]
;
legacy_variable_expansion.name("legacy_variable_expansion");
identifier =
! kw[keywords] >>
raw[lexeme[(alpha | '_') >> *(alnum | '_')]];
identifier.name("identifier");
bool_expr =
additive_expression(_r1) [_val = _1]
>> *( ("==" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::equal, _val, _1)]
| ("!=" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::not_equal, _val, _1)]
| ("<>" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::not_equal, _val, _1)]
);
bool_expr.name("bool expression");
// Evaluate a boolean expression stored as expr into a boolean value.
// Throw if the bool_expr does not produce a expr of boolean type.
bool_expr_eval = bool_expr(_r1) [ px::bind(&expr<Iterator>::evaluate_boolean, _1, _val) ];
bool_expr_eval.name("bool_expr_eval");
additive_expression =
term(_r1) [_val = _1]
>> *( (lit('+') > term(_r1) ) [_val += _1]
| (lit('-') > term(_r1) ) [_val -= _1]
);
additive_expression.name("additive_expression");
term =
factor(_r1) [_val = _1]
>> *( (lit('*') > factor(_r1) ) [_val *= _1]
| (lit('/') > factor(_r1) ) [_val /= _1]
);
term.name("term");
struct FactorActions {
static void set_start_pos(Iterator &start_pos, expr<Iterator> &out)
{ out.it_range = boost::iterator_range<Iterator>(start_pos, start_pos); }
static void int_(int &value, Iterator &end_pos, expr<Iterator> &out)
{ out = expr<Iterator>(value, out.it_range.begin(), end_pos); }
static void double_(double &value, Iterator &end_pos, expr<Iterator> &out)
{ out = expr<Iterator>(value, out.it_range.begin(), end_pos); }
static void bool_(bool &value, Iterator &end_pos, expr<Iterator> &out)
{ out = expr<Iterator>(value, out.it_range.begin(), end_pos); }
static void string_(boost::iterator_range<Iterator> &it_range, expr<Iterator> &out)
{ out = expr<Iterator>(std::string(it_range.begin() + 1, it_range.end() - 1), it_range.begin(), it_range.end()); }
static void expr_(expr<Iterator> &value, Iterator &end_pos, expr<Iterator> &out)
{ out = expr<Iterator>(std::move(value), out.it_range.begin(), end_pos); }
static void minus_(expr<Iterator> &value, expr<Iterator> &out)
{ out = value.unary_minus(out.it_range.begin()); }
static void not_(expr<Iterator> &value, expr<Iterator> &out)
{ out = value.unary_not(out.it_range.begin()); }
};
factor = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> (
scalar_variable_reference(_r1) [ _val = _1 ]
| (lit('(') > additive_expression(_r1) > ')' > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ]
| (lit('-') > factor(_r1) ) [ px::bind(&FactorActions::minus_, _1, _val) ]
| (lit('+') > factor(_r1) > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ]
| ((kw["not"] | '!') > factor(_r1) > iter_pos) [ px::bind(&FactorActions::not_, _1, _val) ]
| (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ]
| (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ]
| (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ]
| raw[lexeme['"' > *((char_ - char_('\\') - char_('"')) | ('\\' > char_)) > '"']]
[ px::bind(&FactorActions::string_, _1, _val) ]
);
factor.name("factor");
scalar_variable_reference =
variable_reference(_r1)[_a=_1] >>
(
('[' > additive_expression(_r1)[px::bind(&MyContext::evaluate_index<Iterator>, _1, _b)] > ']' >
iter_pos[px::bind(&MyContext::vector_variable_reference<Iterator>, _r1, _a, _b, _1, _val)])
| eps[px::bind(&MyContext::scalar_variable_reference<Iterator>, _r1, _a, _val)]
);
scalar_variable_reference.name("scalar variable reference");
variable_reference = identifier
[ px::bind(&MyContext::resolve_variable<Iterator>, _r1, _1, _val) ];
variable_reference.name("variable reference");
/*
qi::on_error<qi::fail>(start,
phx::ref(std::cout)
<< "Error! Expecting "
<< qi::_4
<< " here: '"
<< px::construct<std::string>(qi::_3, qi::_2)
<< "'\n"
);
*/
keywords.add
("and")
("if")
//("inf")
("else")
("elsif")
("endif")
("false")
("not")
("or")
("true");
if (0) {
debug(start);
debug(text);
debug(text_block);
debug(macro);
debug(if_else_output);
debug(switch_output);
debug(legacy_variable_expansion);
debug(identifier);
debug(bool_expr);
debug(bool_expr_eval);
debug(additive_expression);
debug(term);
debug(factor);
debug(scalar_variable_reference);
debug(variable_reference);
} }
} }
return str; // The start of the grammar.
qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> start;
// A free-form text.
qi::rule<Iterator, std::string(), spirit::ascii::space_type> text;
// A free-form text, possibly empty, possibly containing macro expansions.
qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> text_block;
// Statements enclosed in curely braces {}
qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> macro;
// Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index].
qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> legacy_variable_expansion;
// Parsed identifier name.
qi::rule<Iterator, boost::iterator_range<Iterator>(), spirit::ascii::space_type> identifier;
// Math expression consisting of +- operators over terms.
qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> additive_expression;
// Boolean expressions over expressions.
qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> bool_expr;
// Evaluate boolean expression into bool.
qi::rule<Iterator, bool(const MyContext*), spirit::ascii::space_type> bool_expr_eval;
// Math expression consisting of */ operators over factors.
qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> term;
// Number literals, functions, braced expressions, variable references, variable indexing references.
qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> factor;
// Reference of a scalar variable, or reference to a field of a vector variable.
qi::rule<Iterator, expr<Iterator>(const MyContext*), qi::locals<OptWithPos<Iterator>, int>, spirit::ascii::space_type> scalar_variable_reference;
// Rule to translate an identifier to a ConfigOption, or to fail.
qi::rule<Iterator, OptWithPos<Iterator>(const MyContext*), spirit::ascii::space_type> variable_reference;
qi::rule<Iterator, std::string(const MyContext*), qi::locals<bool, bool>, spirit::ascii::space_type> if_else_output;
qi::rule<Iterator, std::string(const MyContext*), qi::locals<expr<Iterator>, bool, std::string>, spirit::ascii::space_type> switch_output;
qi::symbols<char> keywords;
};
}
struct printer
{
typedef spirit::utf8_string string;
void element(string const& tag, string const& value, int depth) const
{
for (int i = 0; i < (depth*4); ++i) // indent to depth
std::cout << ' ';
std::cout << "tag: " << tag;
if (value != "")
std::cout << ", value: " << value;
std::cout << std::endl;
}
};
void print_info(spirit::info const& what)
{
using spirit::basic_info_walker;
printer pr;
basic_info_walker<printer> walker(pr, what.tag, 0);
boost::apply_visitor(walker, what.value);
}
std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) const
{
typedef std::string::const_iterator iterator_type;
typedef client::calculator<iterator_type> calculator;
spirit::ascii::space_type space; // Our skipper
calculator calc; // Our grammar
std::string::const_iterator iter = templ.begin();
std::string::const_iterator end = templ.end();
//std::string result;
std::string result;
bool r = false;
try {
client::MyContext context;
context.pp = this;
context.config_override = config_override;
r = phrase_parse(iter, end, calc(&context), space, result);
} catch (qi::expectation_failure<iterator_type> const& x) {
std::cout << "expected: "; print_info(x.what_);
std::cout << "got: \"" << std::string(x.first, x.last) << '"' << std::endl;
}
if (r && iter == end)
{
// std::cout << "-------------------------\n";
// std::cout << "Parsing succeeded\n";
// std::cout << "result = " << result << std::endl;
// std::cout << "-------------------------\n";
}
else
{
std::string rest(iter, end);
std::cout << "-------------------------\n";
std::cout << "Parsing failed\n";
std::cout << "stopped at: \" " << rest << "\"\n";
std::cout << "source: \n" << templ;
std::cout << "-------------------------\n";
}
return result;
} }
} }

View File

@ -12,22 +12,26 @@ namespace Slic3r {
class PlaceholderParser class PlaceholderParser
{ {
public: public:
std::map<std::string, std::string> m_single;
std::map<std::string, std::vector<std::string>> m_multiple;
PlaceholderParser(); PlaceholderParser();
void update_timestamp(); void update_timestamp();
void apply_config(const DynamicPrintConfig &config); void apply_config(const DynamicPrintConfig &config);
void apply_env_variables(); void apply_env_variables();
void set(const std::string &key, const std::string &value);
void set(const std::string &key, int value); // Add new ConfigOption values to m_config.
void set(const std::string &key, unsigned int value); void set(const std::string &key, const std::string &value) { this->set(key, new ConfigOptionString(value)); }
void set(const std::string &key, double value); void set(const std::string &key, int value) { this->set(key, new ConfigOptionInt(value)); }
void set(const std::string &key, std::vector<std::string> values); void set(const std::string &key, unsigned int value) { this->set(key, int(value)); }
std::string process(std::string str, unsigned int current_extruder_id) const; void set(const std::string &key, double value) { this->set(key, new ConfigOptionFloat(value)); }
void set(const std::string &key, const std::vector<std::string> &values) { this->set(key, new ConfigOptionStrings(values)); }
void set(const std::string &key, ConfigOption *opt) { m_config.set_key_value(key, opt); }
const ConfigOption* option(const std::string &key) const { return m_config.option(key); }
// Fill in the template.
std::string process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr) const;
private: private:
bool find_and_replace(std::string &source, std::string const &find, std::string const &replace) const; DynamicConfig m_config;
}; };
} }

View File

@ -154,7 +154,6 @@ inline Polylines to_polylines(const Polygons &polys)
return polylines; return polylines;
} }
#if SLIC3R_CPPVER >= 11
inline Polylines to_polylines(Polygons &&polys) inline Polylines to_polylines(Polygons &&polys)
{ {
Polylines polylines; Polylines polylines;
@ -168,7 +167,6 @@ inline Polylines to_polylines(Polygons &&polys)
assert(idx == polylines.size()); assert(idx == polylines.size());
return polylines; return polylines;
} }
#endif
} // Slic3r } // Slic3r

View File

@ -81,80 +81,80 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
// Cache the plenty of parameters, which influence the G-code generator only, // Cache the plenty of parameters, which influence the G-code generator only,
// or they are only notes not influencing the generated G-code. // or they are only notes not influencing the generated G-code.
static std::unordered_set<std::string> steps_ignore; static std::unordered_set<std::string> steps_ignore = {
if (steps_ignore.empty()) { "avoid_crossing_perimeters",
steps_ignore.insert("avoid_crossing_perimeters"); "bed_shape",
steps_ignore.insert("bed_shape"); "bed_temperature",
steps_ignore.insert("bed_temperature"); "before_layer_gcode",
steps_ignore.insert("before_layer_gcode"); "between_objects_gcode",
steps_ignore.insert("bridge_acceleration"); "bridge_acceleration",
steps_ignore.insert("bridge_fan_speed"); "bridge_fan_speed",
steps_ignore.insert("cooling"); "cooling",
steps_ignore.insert("default_acceleration"); "default_acceleration",
steps_ignore.insert("deretract_speed"); "deretract_speed",
steps_ignore.insert("disable_fan_first_layers"); "disable_fan_first_layers",
steps_ignore.insert("duplicate_distance"); "duplicate_distance",
steps_ignore.insert("end_gcode"); "end_gcode",
steps_ignore.insert("end_filament_gcode"); "end_filament_gcode",
steps_ignore.insert("extrusion_axis"); "extrusion_axis",
steps_ignore.insert("extruder_clearance_height"); "extruder_clearance_height",
steps_ignore.insert("extruder_clearance_radius"); "extruder_clearance_radius",
steps_ignore.insert("extruder_colour"); "extruder_colour",
steps_ignore.insert("extruder_offset"); "extruder_offset",
steps_ignore.insert("extrusion_multiplier"); "extrusion_multiplier",
steps_ignore.insert("fan_always_on"); "fan_always_on",
steps_ignore.insert("fan_below_layer_time"); "fan_below_layer_time",
steps_ignore.insert("filament_colour"); "filament_colour",
steps_ignore.insert("filament_diameter"); "filament_diameter",
steps_ignore.insert("filament_density"); "filament_density",
steps_ignore.insert("filament_notes"); "filament_notes",
steps_ignore.insert("filament_cost"); "filament_cost",
steps_ignore.insert("filament_max_volumetric_speed"); "filament_max_volumetric_speed",
steps_ignore.insert("first_layer_acceleration"); "first_layer_acceleration",
steps_ignore.insert("first_layer_bed_temperature"); "first_layer_bed_temperature",
steps_ignore.insert("first_layer_speed"); "first_layer_speed",
steps_ignore.insert("gcode_comments"); "gcode_comments",
steps_ignore.insert("gcode_flavor"); "gcode_flavor",
steps_ignore.insert("infill_acceleration"); "infill_acceleration",
steps_ignore.insert("infill_first"); "infill_first",
steps_ignore.insert("layer_gcode"); "layer_gcode",
steps_ignore.insert("min_fan_speed"); "min_fan_speed",
steps_ignore.insert("max_fan_speed"); "max_fan_speed",
steps_ignore.insert("min_print_speed"); "min_print_speed",
steps_ignore.insert("max_print_speed"); "max_print_speed",
steps_ignore.insert("max_volumetric_speed"); "max_volumetric_speed",
steps_ignore.insert("max_volumetric_extrusion_rate_slope_positive"); "max_volumetric_extrusion_rate_slope_positive",
steps_ignore.insert("max_volumetric_extrusion_rate_slope_negative"); "max_volumetric_extrusion_rate_slope_negative",
steps_ignore.insert("notes"); "notes",
steps_ignore.insert("only_retract_when_crossing_perimeters"); "only_retract_when_crossing_perimeters",
steps_ignore.insert("output_filename_format"); "output_filename_format",
steps_ignore.insert("perimeter_acceleration"); "perimeter_acceleration",
steps_ignore.insert("post_process"); "post_process",
steps_ignore.insert("printer_notes"); "printer_notes",
steps_ignore.insert("retract_before_travel"); "retract_before_travel",
steps_ignore.insert("retract_before_wipe"); "retract_before_wipe",
steps_ignore.insert("retract_layer_change"); "retract_layer_change",
steps_ignore.insert("retract_length"); "retract_length",
steps_ignore.insert("retract_length_toolchange"); "retract_length_toolchange",
steps_ignore.insert("retract_lift"); "retract_lift",
steps_ignore.insert("retract_lift_above"); "retract_lift_above",
steps_ignore.insert("retract_lift_below"); "retract_lift_below",
steps_ignore.insert("retract_restart_extra"); "retract_restart_extra",
steps_ignore.insert("retract_restart_extra_toolchange"); "retract_restart_extra_toolchange",
steps_ignore.insert("retract_speed"); "retract_speed",
steps_ignore.insert("slowdown_below_layer_time"); "slowdown_below_layer_time",
steps_ignore.insert("standby_temperature_delta"); "standby_temperature_delta",
steps_ignore.insert("start_gcode"); "start_gcode",
steps_ignore.insert("start_filament_gcode"); "start_filament_gcode",
steps_ignore.insert("toolchange_gcode"); "toolchange_gcode",
steps_ignore.insert("threads"); "threads",
steps_ignore.insert("travel_speed"); "travel_speed",
steps_ignore.insert("use_firmware_retraction"); "use_firmware_retraction",
steps_ignore.insert("use_relative_e_distances"); "use_relative_e_distances",
steps_ignore.insert("use_volumetric_e"); "use_volumetric_e",
steps_ignore.insert("variable_layer_height"); "variable_layer_height",
steps_ignore.insert("wipe"); "wipe"
} };
std::vector<PrintStep> steps; std::vector<PrintStep> steps;
std::vector<PrintObjectStep> osteps; std::vector<PrintObjectStep> osteps;
@ -707,7 +707,10 @@ double Print::skirt_first_layer_height() const
Flow Print::brim_flow() const Flow Print::brim_flow() const
{ {
ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width; ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width;
if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width; if (width.value == 0)
width = this->regions.front()->config.perimeter_extrusion_width;
if (width.value == 0)
width = this->objects.front()->config.extrusion_width;
/* We currently use a random region's perimeter extruder. /* We currently use a random region's perimeter extruder.
While this works for most cases, we should probably consider all of the perimeter While this works for most cases, we should probably consider all of the perimeter
@ -726,7 +729,10 @@ Flow Print::brim_flow() const
Flow Print::skirt_flow() const Flow Print::skirt_flow() const
{ {
ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width; ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width;
if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width; if (width.value == 0)
width = this->regions.front()->config.perimeter_extrusion_width;
if (width.value == 0)
width = this->objects.front()->config.extrusion_width;
/* We currently use a random object's support material extruder. /* We currently use a random object's support material extruder.
While this works for most cases, we should probably consider all of the support material While this works for most cases, we should probably consider all of the support material
@ -1079,13 +1085,13 @@ std::string Print::output_filepath(const std::string &path)
if (! input_file.empty()) if (! input_file.empty())
break; break;
} }
return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).string(); return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).make_preferred().string();
} }
// if we were supplied a directory, use it and append our automatically generated filename // if we were supplied a directory, use it and append our automatically generated filename
boost::filesystem::path p(path); boost::filesystem::path p(path);
if (boost::filesystem::is_directory(p)) if (boost::filesystem::is_directory(p))
return (p / this->output_filename()).string(); return (p / this->output_filename()).make_preferred().string();
// if we were supplied a file which is not a directory, use it // if we were supplied a file which is not a directory, use it
return path; return path;

File diff suppressed because it is too large Load Diff

View File

@ -142,6 +142,9 @@ public:
DynamicPrintConfig() {} DynamicPrintConfig() {}
DynamicPrintConfig(const DynamicPrintConfig &other) : DynamicConfig(other) {} DynamicPrintConfig(const DynamicPrintConfig &other) : DynamicConfig(other) {}
static DynamicPrintConfig* new_from_defaults();
static DynamicPrintConfig* new_from_defaults_keys(const std::vector<std::string> &keys);
// Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here.
const ConfigDef* def() const override { return &print_config_def; } const ConfigDef* def() const override { return &print_config_def; }
@ -449,6 +452,7 @@ class GCodeConfig : public StaticPrintConfig
STATIC_PRINT_CONFIG_CACHE(GCodeConfig) STATIC_PRINT_CONFIG_CACHE(GCodeConfig)
public: public:
ConfigOptionString before_layer_gcode; ConfigOptionString before_layer_gcode;
ConfigOptionString between_objects_gcode;
ConfigOptionFloats deretract_speed; ConfigOptionFloats deretract_speed;
ConfigOptionString end_gcode; ConfigOptionString end_gcode;
ConfigOptionStrings end_filament_gcode; ConfigOptionStrings end_filament_gcode;
@ -497,6 +501,7 @@ protected:
void initialize(StaticCacheBase &cache, const char *base_ptr) void initialize(StaticCacheBase &cache, const char *base_ptr)
{ {
OPT_PTR(before_layer_gcode); OPT_PTR(before_layer_gcode);
OPT_PTR(between_objects_gcode);
OPT_PTR(deretract_speed); OPT_PTR(deretract_speed);
OPT_PTR(end_gcode); OPT_PTR(end_gcode);
OPT_PTR(end_filament_gcode); OPT_PTR(end_filament_gcode);

View File

@ -1208,6 +1208,8 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic
// perform a safety offset to merge very close facets (TODO: find test case for this) // perform a safety offset to merge very close facets (TODO: find test case for this)
double safety_offset = scale_(0.0499); double safety_offset = scale_(0.0499);
//FIXME see https://github.com/prusa3d/Slic3r/issues/520
// double safety_offset = scale_(0.0001);
ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset); ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset);
#ifdef SLIC3R_TRIANGLEMESH_DEBUG #ifdef SLIC3R_TRIANGLEMESH_DEBUG

View File

@ -1,6 +1,8 @@
#ifndef slic3r_Utils_hpp_ #ifndef slic3r_Utils_hpp_
#define slic3r_Utils_hpp_ #define slic3r_Utils_hpp_
#include <locale>
namespace Slic3r { namespace Slic3r {
extern void set_logging_level(unsigned int level); extern void set_logging_level(unsigned int level);
@ -8,15 +10,31 @@ extern void trace(unsigned int level, const char *message);
// Set a path with GUI resource files. // Set a path with GUI resource files.
void set_var_dir(const std::string &path); void set_var_dir(const std::string &path);
// Return a path to the GUI resource files. // Return a full path to the GUI resource files.
const std::string& var_dir(); const std::string& var_dir();
// Return a resource path for a file_name. // Return a full resource path for a file_name.
std::string var(const std::string &file_name); std::string var(const std::string &file_name);
// Set a path with preset files.
void set_data_dir(const std::string &path);
// Return a full path to the GUI resource files.
const std::string& data_dir();
// Return a full path to a configuration file given its file name..
std::string config_path(const std::string &file_name);
// Return a full path to a configuration file given the section and name.
// The suffix ".ini" will be added if it is missing in the name.
std::string config_path(const std::string &section, const std::string &name);
extern std::string encode_path(const char *src); extern std::string encode_path(const char *src);
extern std::string decode_path(const char *src); extern std::string decode_path(const char *src);
extern std::string normalize_utf8_nfc(const char *src); extern std::string normalize_utf8_nfc(const char *src);
// Timestamp formatted for header_slic3r_generated().
extern std::string timestamp_str();
// Standard "generated by Slic3r version xxx timestamp xxx" header string,
// to be placed at the top of Slic3r generated files.
inline std::string header_slic3r_generated() { return std::string("generated by " SLIC3R_FORK_NAME " " SLIC3R_VERSION " " ) + timestamp_str(); }
// Compute the next highest power of 2 of 32-bit v // Compute the next highest power of 2 of 32-bit v
// http://graphics.stanford.edu/~seander/bithacks.html // http://graphics.stanford.edu/~seander/bithacks.html
template<typename T> template<typename T>

View File

@ -14,7 +14,7 @@
#include <boost/thread.hpp> #include <boost/thread.hpp>
#define SLIC3R_FORK_NAME "Slic3r Prusa Edition" #define SLIC3R_FORK_NAME "Slic3r Prusa Edition"
#define SLIC3R_VERSION "1.33.8.devel" #define SLIC3R_VERSION "1.38.2"
#define SLIC3R_BUILD "UNKNOWN" #define SLIC3R_BUILD "UNKNOWN"
typedef long coord_t; typedef long coord_t;

View File

@ -1,4 +1,5 @@
#include <locale> #include <locale>
#include <ctime>
#include <boost/log/core.hpp> #include <boost/log/core.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
@ -6,8 +7,9 @@
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/date_time/local_time/local_time.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/nowide/integration/filesystem.hpp> #include <boost/nowide/integration/filesystem.hpp>
#include <boost/nowide/convert.hpp> #include <boost/nowide/convert.hpp>
@ -87,6 +89,31 @@ std::string var(const std::string &file_name)
return file.string(); return file.string();
} }
static std::string g_data_dir;
void set_data_dir(const std::string &dir)
{
g_data_dir = dir;
}
const std::string& data_dir()
{
return g_data_dir;
}
std::string config_path(const std::string &file_name)
{
auto file = (boost::filesystem::path(g_data_dir) / file_name).make_preferred();
return file.string();
}
std::string config_path(const std::string &section, const std::string &name)
{
auto file_name = boost::algorithm::iends_with(name, ".ini") ? name : name + ".ini";
auto file = (boost::filesystem::path(g_data_dir) / section / file_name).make_preferred();
return file.string();
}
} // namespace Slic3r } // namespace Slic3r
#ifdef SLIC3R_HAS_BROKEN_CROAK #ifdef SLIC3R_HAS_BROKEN_CROAK
@ -207,4 +234,16 @@ std::string normalize_utf8_nfc(const char *src)
return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8); return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8);
} }
std::string timestamp_str()
{
const auto now = boost::posix_time::second_clock::local_time();
const auto date = now.date();
char buf[2048];
sprintf(buf, "on %04d-%02d-%02d at %02d:%02d:%02d",
// Local date in an ANSII format.
int(now.date().year()), int(now.date().month()), int(now.date().day()),
int(now.time_of_day().hours()), int(now.time_of_day().minutes()), int(now.time_of_day().seconds()));
return buf;
}
}; // namespace Slic3r }; // namespace Slic3r

View File

@ -54,12 +54,14 @@ REGISTER_CLASS(Surface, "Surface");
REGISTER_CLASS(SurfaceCollection, "Surface::Collection"); REGISTER_CLASS(SurfaceCollection, "Surface::Collection");
REGISTER_CLASS(PrintObjectSupportMaterial, "Print::SupportMaterial2"); REGISTER_CLASS(PrintObjectSupportMaterial, "Print::SupportMaterial2");
REGISTER_CLASS(TriangleMesh, "TriangleMesh"); REGISTER_CLASS(TriangleMesh, "TriangleMesh");
REGISTER_CLASS(AppConfig, "GUI::AppConfig");
REGISTER_CLASS(GLShader, "GUI::_3DScene::GLShader"); REGISTER_CLASS(GLShader, "GUI::_3DScene::GLShader");
REGISTER_CLASS(GLVolume, "GUI::_3DScene::GLVolume"); REGISTER_CLASS(GLVolume, "GUI::_3DScene::GLVolume");
REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection"); REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection");
REGISTER_CLASS(Preset, "GUI::Preset"); REGISTER_CLASS(Preset, "GUI::Preset");
REGISTER_CLASS(PresetCollection, "GUI::PresetCollection"); REGISTER_CLASS(PresetCollection, "GUI::PresetCollection");
REGISTER_CLASS(PresetBundle, "GUI::PresetBundle"); REGISTER_CLASS(PresetBundle, "GUI::PresetBundle");
REGISTER_CLASS(PresetHints, "GUI::PresetHints");
SV* ConfigBase__as_hash(ConfigBase* THIS) SV* ConfigBase__as_hash(ConfigBase* THIS)
{ {

View File

@ -0,0 +1,154 @@
#include <GL/glew.h>
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Utils.hpp"
#include "AppConfig.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utility>
#include <assert.h>
#include <boost/filesystem.hpp>
#include <boost/nowide/cenv.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
namespace Slic3r {
void AppConfig::reset()
{
m_storage.clear();
set_defaults();
};
// Override missing or keys with their defaults.
void AppConfig::set_defaults()
{
// Reset the empty fields to defaults.
if (get("autocenter").empty())
set("autocenter", "1");
// Disable background processing by default as it is not stable.
if (get("background_processing").empty())
set("background_processing", "0");
// If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
// By default, Prusa has the controller hidden.
if (get("no_controller").empty())
set("no_controller", "1");
// If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available.
if (get("no_defaults").empty())
set("no_defaults", "1");
if (get("show_incompatible_presets").empty())
set("show_incompatible_presets", "0");
// Version check is enabled by default in the config, but it is not implemented yet.
if (get("version_check").empty())
set("version_check", "1");
}
void AppConfig::load()
{
// 1) Read the complete config file into a boost::property_tree.
namespace pt = boost::property_tree;
pt::ptree tree;
boost::nowide::ifstream ifs(AppConfig::config_path());
pt::read_ini(ifs, tree);
// 2) Parse the property_tree, extract the sections and key / value pairs.
for (const auto &section : tree) {
if (section.second.empty()) {
// This may be a top level (no section) entry, or an empty section.
std::string data = section.second.data();
if (! data.empty())
// If there is a non-empty data, then it must be a top-level (without a section) config entry.
m_storage[""][section.first] = data;
} else {
// This must be a section name. Read the entries of a section.
std::map<std::string, std::string> &storage = m_storage[section.first];
for (auto &kvp : section.second)
storage[kvp.first] = kvp.second.data();
}
}
// Override missing or keys with their defaults.
this->set_defaults();
m_dirty = false;
}
void AppConfig::save()
{
boost::nowide::ofstream c;
c.open(AppConfig::config_path(), std::ios::out | std::ios::trunc);
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
// Make sure the "no" category is written first.
for (const std::pair<std::string, std::string> &kvp : m_storage[""])
c << kvp.first << " = " << kvp.second << std::endl;
// Write the other categories.
for (const auto category : m_storage) {
if (category.first.empty())
continue;
c << std::endl << "[" << category.first << "]" << std::endl;
for (const std::pair<std::string, std::string> &kvp : category.second)
c << kvp.first << " = " << kvp.second << std::endl;
}
c.close();
m_dirty = false;
}
std::string AppConfig::get_last_dir() const
{
const auto it = m_storage.find("recent");
if (it != m_storage.end()) {
{
const auto it2 = it->second.find("skein_directory");
if (it2 != it->second.end() && ! it2->second.empty())
return it2->second;
}
{
const auto it2 = it->second.find("config_directory");
if (it2 != it->second.end() && ! it2->second.empty())
return it2->second;
}
}
return std::string();
}
void AppConfig::update_config_dir(const std::string &dir)
{
this->set("recent", "config_directory", dir);
}
void AppConfig::update_skein_dir(const std::string &dir)
{
this->set("recent", "skein_directory", dir);
}
std::string AppConfig::get_last_output_dir(const std::string &alt) const
{
const auto it = m_storage.find("");
if (it != m_storage.end()) {
const auto it2 = it->second.find("last_output_path");
const auto it3 = it->second.find("remember_output_path");
if (it2 != it->second.end() && it3 != it->second.end() && ! it2->second.empty() && it3->second == "1")
return it2->second;
}
return alt;
}
void AppConfig::update_last_output_dir(const std::string &dir)
{
this->set("", "last_output_path", dir);
}
std::string AppConfig::config_path()
{
return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string();
}
bool AppConfig::exists()
{
return boost::filesystem::exists(AppConfig::config_path());
}
}; // namespace Slic3r

View File

@ -0,0 +1,91 @@
#ifndef slic3r_AppConfig_hpp_
#define slic3r_AppConfig_hpp_
#include <map>
#include <string>
namespace Slic3r {
class AppConfig
{
public:
AppConfig() : m_dirty(false) { this->reset(); }
// Clear and reset to defaults.
void reset();
// Override missing or keys with their defaults.
void set_defaults();
// Load the slic3r.ini from a user profile directory (or a datadir, if configured).
void load();
// Store the slic3r.ini into a user profile directory (or a datadir, if configured).
void save();
// Does this config need to be saved?
bool dirty() const { return m_dirty; }
// Const accessor, it will return false if a section or a key does not exist.
bool get(const std::string &section, const std::string &key, std::string &value) const
{
value.clear();
auto it = m_storage.find(section);
if (it == m_storage.end())
return false;
auto it2 = it->second.find(key);
if (it2 == it->second.end())
return false;
value = it2->second;
return true;
}
std::string get(const std::string &section, const std::string &key) const
{ std::string value; this->get(section, key, value); return value; }
std::string get(const std::string &key) const
{ std::string value; this->get("", key, value); return value; }
void set(const std::string &section, const std::string &key, const std::string &value)
{
std::string &old = m_storage[section][key];
if (old != value) {
old = value;
m_dirty = true;
}
}
void set(const std::string &key, const std::string &value)
{ this->set("", key, value); }
bool has(const std::string &section, const std::string &key) const
{
auto it = m_storage.find(section);
if (it == m_storage.end())
return false;
auto it2 = it->second.find(key);
return it2 != it->second.end() && ! it2->second.empty();
}
bool has(const std::string &key) const
{ return this->has("", key); }
void clear_section(const std::string &section)
{ m_storage[section].clear(); }
// return recent/skein_directory or recent/config_directory or empty string.
std::string get_last_dir() const;
void update_config_dir(const std::string &dir);
void update_skein_dir(const std::string &dir);
std::string get_last_output_dir(const std::string &alt) const;
void update_last_output_dir(const std::string &dir);
// Get the default config path from Slic3r::data_dir().
static std::string config_path();
// Does the config file exist?
static bool exists();
private:
// Map of section, name -> value
std::map<std::string, std::map<std::string, std::string>> m_storage;
// Has any value been modified since the config.ini has been last saved or loaded?
bool m_dirty;
};
}; // namespace Slic3r
#endif /* slic3r_AppConfig_hpp_ */

View File

@ -1,9 +1,15 @@
#include "GUI.hpp" #include "GUI.hpp"
#include <assert.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#if __APPLE__ #if __APPLE__
#import <IOKit/pwr_mgt/IOPMLib.h> #import <IOKit/pwr_mgt/IOPMLib.h>
#elif _WIN32 #elif _WIN32
#include <Windows.h> #include <Windows.h>
#include "boost/nowide/convert.hpp"
#pragma comment(lib, "user32.lib") #pragma comment(lib, "user32.lib")
#endif #endif
@ -13,8 +19,7 @@ namespace Slic3r { namespace GUI {
IOPMAssertionID assertionID; IOPMAssertionID assertionID;
#endif #endif
void void disable_screensaver()
disable_screensaver()
{ {
#if __APPLE__ #if __APPLE__
CFStringRef reasonForActivity = CFSTR("Slic3r"); CFStringRef reasonForActivity = CFSTR("Slic3r");
@ -26,8 +31,7 @@ disable_screensaver()
#endif #endif
} }
void void enable_screensaver()
enable_screensaver()
{ {
#if __APPLE__ #if __APPLE__
IOReturn success = IOPMAssertionRelease(assertionID); IOReturn success = IOPMAssertionRelease(assertionID);
@ -36,8 +40,84 @@ enable_screensaver()
#endif #endif
} }
bool std::vector<std::string> scan_serial_ports()
debugged() {
std::vector<std::string> out;
#ifdef _WIN32
// 1) Open the registry key SERIALCOM.
HKEY hKey;
LONG lRes = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_READ, &hKey);
assert(lRes == ERROR_SUCCESS);
if (lRes == ERROR_SUCCESS) {
// 2) Get number of values of SERIALCOM key.
DWORD cValues; // number of values for key
{
TCHAR achKey[255]; // buffer for subkey name
DWORD cbName; // size of name string
TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name
DWORD cchClassName = MAX_PATH; // size of class string
DWORD cSubKeys=0; // number of subkeys
DWORD cbMaxSubKey; // longest subkey size
DWORD cchMaxClass; // longest class string
DWORD cchMaxValue; // longest value name
DWORD cbMaxValueData; // longest value data
DWORD cbSecurityDescriptor; // size of security descriptor
FILETIME ftLastWriteTime; // last write time
// Get the class name and the value count.
lRes = RegQueryInfoKey(
hKey, // key handle
achClass, // buffer for class name
&cchClassName, // size of class string
NULL, // reserved
&cSubKeys, // number of subkeys
&cbMaxSubKey, // longest subkey size
&cchMaxClass, // longest class string
&cValues, // number of values for this key
&cchMaxValue, // longest value name
&cbMaxValueData, // longest value data
&cbSecurityDescriptor, // security descriptor
&ftLastWriteTime); // last write time
assert(lRes == ERROR_SUCCESS);
}
// 3) Read the SERIALCOM values.
{
DWORD dwIndex = 0;
for (int i = 0; i < cValues; ++ i, ++ dwIndex) {
wchar_t valueName[2048];
DWORD valNameLen = 2048;
DWORD dataType;
wchar_t data[2048];
DWORD dataSize = 4096;
lRes = ::RegEnumValueW(hKey, dwIndex, valueName, &valNameLen, nullptr, &dataType, (BYTE*)&data, &dataSize);
if (lRes == ERROR_SUCCESS && dataType == REG_SZ && valueName[0] != 0)
out.emplace_back(boost::nowide::narrow(data));
}
}
::RegCloseKey(hKey);
}
#else
// UNIX and OS X
std::initializer_list<const char*> prefixes { "ttyUSB" , "ttyACM", "tty.", "cu.", "rfcomm" };
for (auto &dir_entry : boost::filesystem::directory_iterator(boost::filesystem::path("/dev"))) {
std::string name = dir_entry.path().filename().string();
for (const char *prefix : prefixes) {
if (boost::starts_with(name, prefix)) {
out.emplace_back(dir_entry.path().string());
break;
}
}
}
#endif
out.erase(std::remove_if(out.begin(), out.end(),
[](const std::string &key){
return boost::starts_with(key, "Bluetooth") || boost::starts_with(key, "FireFly");
}),
out.end());
return out;
}
bool debugged()
{ {
#ifdef _WIN32 #ifdef _WIN32
return IsDebuggerPresent(); return IsDebuggerPresent();
@ -46,8 +126,7 @@ debugged()
#endif /* _WIN32 */ #endif /* _WIN32 */
} }
void void break_to_debugger()
break_to_debugger()
{ {
#ifdef _WIN32 #ifdef _WIN32
if (IsDebuggerPresent()) if (IsDebuggerPresent())

View File

@ -1,10 +1,14 @@
#ifndef slic3r_GUI_hpp_ #ifndef slic3r_GUI_hpp_
#define slic3r_GUI_hpp_ #define slic3r_GUI_hpp_
#include <string>
#include <vector>
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
void disable_screensaver(); void disable_screensaver();
void enable_screensaver(); void enable_screensaver();
std::vector<std::string> scan_serial_ports();
bool debugged(); bool debugged();
void break_to_debugger(); void break_to_debugger();

View File

@ -1,22 +1,112 @@
//#undef NDEBUG
#include <cassert>
#include "Preset.hpp" #include "Preset.hpp"
#include <fstream> #include <fstream>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/nowide/cenv.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/locale.hpp>
#include <wx/image.h> #include <wx/image.h>
#include <wx/choice.h>
#include <wx/bmpcbox.h> #include <wx/bmpcbox.h>
#include <wx/wupdlock.h>
#if 0 #include "../../libslic3r/libslic3r.h"
#define DEBUG #include "../../libslic3r/Utils.hpp"
#define _DEBUG
#undef NDEBUG
#endif
#include <assert.h>
namespace Slic3r { namespace Slic3r {
ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree)
{
size_t app_config = 0;
size_t bundle = 0;
size_t config = 0;
for (const boost::property_tree::ptree::value_type &v : tree) {
if (v.second.empty()) {
if (v.first == "background_processing" ||
v.first == "last_output_path" ||
v.first == "no_controller" ||
v.first == "no_defaults")
++ app_config;
else if (v.first == "nozzle_diameter" ||
v.first == "filament_diameter")
++ config;
} else if (boost::algorithm::starts_with(v.first, "print:") ||
boost::algorithm::starts_with(v.first, "filament:") ||
boost::algorithm::starts_with(v.first, "printer:") ||
v.first == "settings")
++ bundle;
else if (v.first == "presets") {
++ app_config;
++ bundle;
} else if (v.first == "recent") {
for (auto &kvp : v.second)
if (kvp.first == "config_directory" || kvp.first == "skein_directory")
++ app_config;
}
}
return (app_config > bundle && app_config > config) ? CONFIG_FILE_TYPE_APP_CONFIG :
(bundle > config) ? CONFIG_FILE_TYPE_CONFIG_BUNDLE : CONFIG_FILE_TYPE_CONFIG;
}
// Suffix to be added to a modified preset name in the combo box.
static std::string g_suffix_modified = " (modified)";
const std::string& Preset::suffix_modified()
{
return g_suffix_modified;
}
// Remove an optional "(modified)" suffix from a name.
// This converts a UI name to a unique preset identifier.
std::string Preset::remove_suffix_modified(const std::string &name)
{
return boost::algorithm::ends_with(name, g_suffix_modified) ?
name.substr(0, name.size() - g_suffix_modified.size()) :
name;
}
void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extruders)
{
const auto &defaults = FullPrintConfig::defaults();
for (const std::string &key : Preset::nozzle_options()) {
auto *opt = config.option(key, false);
assert(opt != nullptr);
assert(opt->is_vector());
static_cast<ConfigOptionVectorBase*>(opt)->resize(num_extruders, defaults.option(key));
}
}
// Update new extruder fields at the printer profile.
void Preset::normalize(DynamicPrintConfig &config)
{
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter"));
if (nozzle_diameter != nullptr)
// Loaded the Printer settings. Verify, that all extruder dependent values have enough values.
set_num_extruders(config, (unsigned int)nozzle_diameter->values.size());
if (config.option("filament_diameter") != nullptr) {
// This config contains single or multiple filament presets.
// Ensure that the filament preset vector options contain the correct number of values.
size_t n = (nozzle_diameter == nullptr) ? 1 : nozzle_diameter->values.size();
const auto &defaults = FullPrintConfig::defaults();
for (const std::string &key : Preset::filament_options()) {
if (key == "compatible_printers")
continue;
auto *opt = config.option(key, false);
assert(opt != nullptr);
assert(opt->is_vector());
if (opt != nullptr && opt->is_vector())
static_cast<ConfigOptionVectorBase*>(opt)->resize(n, defaults.option(key));
}
}
}
// Load a config file, return a C++ class Slic3r::DynamicPrintConfig with $keys initialized from the config file. // Load a config file, return a C++ class Slic3r::DynamicPrintConfig with $keys initialized from the config file.
// In case of a "default" config item, return the default values. // In case of a "default" config item, return the default values.
DynamicPrintConfig& Preset::load(const std::vector<std::string> &keys) DynamicPrintConfig& Preset::load(const std::vector<std::string> &keys)
@ -24,284 +114,49 @@ DynamicPrintConfig& Preset::load(const std::vector<std::string> &keys)
// Set the configuration from the defaults. // Set the configuration from the defaults.
Slic3r::FullPrintConfig defaults; Slic3r::FullPrintConfig defaults;
this->config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); this->config.apply_only(defaults, keys.empty() ? defaults.keys() : keys);
if (! this->is_default) { if (! this->is_default) {
// Load the preset file, apply preset values on top of defaults. // Load the preset file, apply preset values on top of defaults.
try { try {
if (boost::algorithm::iends_with(this->file, ".gcode") || boost::algorithm::iends_with(this->file, ".g")) this->config.load_from_ini(this->file);
this->config.load_from_gcode(this->file); Preset::normalize(this->config);
else } catch (const std::ifstream::failure &err) {
this->config.load(this->file); throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + this->file + "\n\tReason: " + err.what());
} catch (const std::ifstream::failure&) { } catch (const std::runtime_error &err) {
throw std::runtime_error(std::string("The selected preset does not exist anymore: ") + this->file); throw std::runtime_error(std::string("Failed loading the preset file: ") + this->file + "\n\tReason: " + err.what());
} catch (const std::runtime_error&) {
throw std::runtime_error(std::string("Failed loading the preset file: ") + this->file);
}
if (this->type == TYPE_PRINTER && std::find(keys.begin(), keys.end(), "nozzle_diameter") != keys.end()) {
// Loaded the Printer settings. Verify, that all extruder dependent values have enough values.
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(this->config.option("nozzle_diameter"));
size_t num_extruders = nozzle_diameter->values.size();
auto *deretract_speed = dynamic_cast<ConfigOptionFloats*>(this->config.option("deretract_speed"));
deretract_speed->values.resize(num_extruders, deretract_speed->values.empty() ?
defaults.deretract_speed.values.front() : deretract_speed->values.front());
auto *extruder_colour = dynamic_cast<ConfigOptionStrings*>(this->config.option("extruder_colour"));
extruder_colour->values.resize(num_extruders, extruder_colour->values.empty() ?
defaults.extruder_colour.values.front() : extruder_colour->values.front());
auto *retract_before_wipe = dynamic_cast<ConfigOptionPercents*>(this->config.option("retract_before_wipe"));
retract_before_wipe->values.resize(num_extruders, retract_before_wipe->values.empty() ?
defaults.retract_before_wipe.values.front() : retract_before_wipe->values.front());
} }
} }
this->loaded = true; this->loaded = true;
return this->config; return this->config;
} }
bool Preset::enable_compatible(const std::string &active_printer) void Preset::save()
{ {
auto *compatible_printers = dynamic_cast<const ConfigOptionStrings*>(this->config.optptr("compatible_printers")); this->config.save(this->file);
this->is_visible = compatible_printers && ! compatible_printers->values.empty() && }
// Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty.
std::string Preset::label() const
{
return this->name + (this->is_dirty ? g_suffix_modified : "");
}
bool Preset::is_compatible_with_printer(const std::string &active_printer) const
{
auto *compatible_printers = dynamic_cast<const ConfigOptionStrings*>(this->config.option("compatible_printers"));
return this->is_default || active_printer.empty() ||
compatible_printers == nullptr || compatible_printers->values.empty() ||
std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer) != std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer) !=
compatible_printers->values.end(); compatible_printers->values.end();
return this->is_visible;
} }
PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys) : bool Preset::update_compatible_with_printer(const std::string &active_printer)
m_type(type),
m_edited_preset(type, "", false)
{ {
// Insert just the default preset. return this->is_compatible = is_compatible_with_printer(active_printer);
m_presets.emplace_back(Preset(type, "- default -", true));
m_presets.front().load(keys);
} }
// Load all presets found in dir_path. const std::vector<std::string>& Preset::print_options()
// Throws an exception on error.
void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir)
{ {
m_presets.erase(m_presets.begin()+1, m_presets.end()); static std::vector<std::string> s_opts {
t_config_option_keys keys = this->default_preset().config.keys();
for (auto &file : boost::filesystem::directory_iterator(boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred()))
if (boost::filesystem::is_regular_file(file.status()) && boost::algorithm::iends_with(file.path().filename().string(), ".ini")) {
std::string name = file.path().filename().string();
// Remove the .ini suffix.
name.erase(name.size() - 4);
try {
Preset preset(m_type, name, false);
preset.file = file.path().string();
preset.load(keys);
m_presets.emplace_back(preset);
} catch (const boost::filesystem::filesystem_error &err) {
}
}
}
void PresetCollection::set_default_suppressed(bool default_suppressed)
{
if (m_default_suppressed != default_suppressed) {
m_default_suppressed = default_suppressed;
m_presets.front().is_visible = ! default_suppressed || m_presets.size() > 1;
}
}
void PresetCollection::enable_disable_compatible_to_printer(const std::string &active_printer)
{
size_t num_visible = 0;
for (size_t idx_preset = 1; idx_preset < m_presets.size(); ++ idx_preset)
if (m_presets[idx_preset].enable_compatible(active_printer))
++ num_visible;
if (num_visible == 0)
// Show the "-- default --" preset.
m_presets.front().is_visible = true;
}
static std::string g_suffix_modified = " (modified)";
// Update the wxChoice UI component from this list of presets.
// Hide the
void PresetCollection::update_editor_ui(wxBitmapComboBox *ui)
{
if (ui == nullptr)
return;
size_t n_visible = this->num_visible();
size_t n_choice = size_t(ui->GetCount());
std::string name_selected = dynamic_cast<wxItemContainerImmutable*>(ui)->GetStringSelection().ToUTF8().data();
if (boost::algorithm::iends_with(name_selected, g_suffix_modified))
// Remove the g_suffix_modified.
name_selected.erase(name_selected.end() - g_suffix_modified.size(), name_selected.end());
#if 0
if (std::abs(int(n_visible) - int(n_choice)) <= 1) {
// The number of items differs by at most one, update the choice.
size_t i_preset = 0;
size_t i_ui = 0;
while (i_preset < presets.size()) {
std::string name_ui = ui->GetString(i_ui).ToUTF8();
if (boost::algorithm::iends_with(name_ui, g_suffix_modified))
// Remove the g_suffix_modified.
name_ui.erase(name_ui.end() - g_suffix_modified.size(), name_ui.end());
while (this->presets[i_preset].name )
const Preset &preset = this->presets[i_preset];
if (preset)
}
} else
#endif
{
// Otherwise fill in the list from scratch.
ui->Clear();
for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) {
const Preset &preset = this->m_presets[i];
const wxBitmap *bmp = (i == 0 || preset.is_visible) ? m_bitmap_compatible : m_bitmap_incompatible;
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), (bmp == 0) ? wxNullBitmap : *bmp, (void*)i);
if (name_selected == preset.name)
ui->SetSelection(ui->GetCount() - 1);
}
}
}
void PresetCollection::update_platter_ui(wxBitmapComboBox *ui)
{
if (ui == nullptr)
return;
size_t n_visible = this->num_visible();
size_t n_choice = size_t(ui->GetCount());
if (std::abs(int(n_visible) - int(n_choice)) <= 1) {
// The number of items differs by at most one, update the choice.
} else {
// Otherwise fill in the list from scratch.
}
}
PresetBundle::PresetBundle() :
prints(Preset::TYPE_PRINT, print_options()),
filaments(Preset::TYPE_FILAMENT, filament_options()),
printers(Preset::TYPE_PRINTER, printer_options()),
m_bitmapCompatible(new wxBitmap),
m_bitmapIncompatible(new wxBitmap)
{
// Create the ID config keys, as they are not part of the Static print config classes.
this->prints.preset(0).config.opt_string("print_settings_id", true);
this->filaments.preset(0).config.opt_string("filament_settings_id", true);
this->printers.preset(0).config.opt_string("print_settings_id", true);
// Create the "compatible printers" keys, as they are not part of the Static print config classes.
this->filaments.preset(0).config.optptr("compatible_printers", true);
this->prints.preset(0).config.optptr("compatible_printers", true);
}
PresetBundle::~PresetBundle()
{
assert(m_bitmapCompatible != nullptr);
assert(m_bitmapIncompatible != nullptr);
delete m_bitmapCompatible;
m_bitmapCompatible = nullptr;
delete m_bitmapIncompatible;
m_bitmapIncompatible = nullptr;
for (std::pair<const std::string, wxBitmap*> &bitmap : m_mapColorToBitmap)
delete bitmap.second;
}
void PresetBundle::load_presets(const std::string &dir_path)
{
this->prints.load_presets(dir_path, "print");
this->prints.load_presets(dir_path, "filament");
this->prints.load_presets(dir_path, "printer");
}
bool PresetBundle::load_bitmaps(const std::string &path_bitmap_compatible, const std::string &path_bitmap_incompatible)
{
bool loaded_compatible = m_bitmapCompatible ->LoadFile(wxString::FromUTF8(path_bitmap_compatible.c_str()));
bool loaded_incompatible = m_bitmapIncompatible->LoadFile(wxString::FromUTF8(path_bitmap_incompatible.c_str()));
if (loaded_compatible) {
prints .set_bitmap_compatible(m_bitmapCompatible);
filaments.set_bitmap_compatible(m_bitmapCompatible);
printers .set_bitmap_compatible(m_bitmapCompatible);
}
if (loaded_incompatible) {
prints .set_bitmap_compatible(m_bitmapIncompatible);
filaments.set_bitmap_compatible(m_bitmapIncompatible);
printers .set_bitmap_compatible(m_bitmapIncompatible);
}
return loaded_compatible && loaded_incompatible;
}
static inline int hex_digit_to_int(const char c)
{
return
(c >= '0' && c <= '9') ? int(c - '0') :
(c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
(c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
}
static inline bool parse_color(const std::string &scolor, unsigned char *rgb_out)
{
rgb_out[0] = rgb_out[1] = rgb_out[2] = 0;
const char *c = scolor.data() + 1;
if (scolor.size() != 7 || scolor.front() != '#')
return false;
for (size_t i = 0; i < 3; ++ i) {
int digit1 = hex_digit_to_int(*c ++);
int digit2 = hex_digit_to_int(*c ++);
if (digit1 == -1 || digit2 == -1)
return false;
rgb_out[i] = (unsigned char)(digit1 * 16 + digit2);
}
return true;
}
// Update the colors preview at the platter extruder combo box.
void PresetBundle::update_platter_filament_ui_colors(wxBitmapComboBox *ui, unsigned int idx_extruder, unsigned int idx_filament)
{
unsigned char rgb[3];
std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder);
if (! parse_color(extruder_color, rgb))
// Extruder color is not defined.
extruder_color.clear();
for (unsigned int ui_id = 0; ui_id < ui->GetCount(); ++ ui_id) {
if (! ui->HasClientUntypedData())
continue;
size_t filament_preset_id = size_t(ui->GetClientData(ui_id));
const Preset &filament_preset = filaments.preset(filament_preset_id);
// Assign an extruder color to the selected item if the extruder color is defined.
std::string filament_rgb = filament_preset.config.opt_string("filament_colour", 0);
std::string extruder_rgb = (int(ui_id) == ui->GetSelection() && ! extruder_color.empty()) ? extruder_color : filament_rgb;
wxBitmap *bitmap = nullptr;
if (filament_rgb == extruder_rgb) {
auto it = m_mapColorToBitmap.find(filament_rgb);
if (it == m_mapColorToBitmap.end()) {
// Create the bitmap.
parse_color(filament_rgb, rgb);
wxImage image(24, 16);
image.SetRGB(wxRect(0, 0, 24, 16), rgb[0], rgb[1], rgb[2]);
m_mapColorToBitmap[filament_rgb] = new wxBitmap(image);
} else {
bitmap = it->second;
}
} else {
std::string bitmap_key = filament_rgb + extruder_rgb;
auto it = m_mapColorToBitmap.find(bitmap_key);
if (it == m_mapColorToBitmap.end()) {
// Create the bitmap.
wxImage image(24, 16);
parse_color(extruder_rgb, rgb);
image.SetRGB(wxRect(0, 0, 16, 16), rgb[0], rgb[1], rgb[2]);
parse_color(filament_rgb, rgb);
image.SetRGB(wxRect(16, 0, 8, 16), rgb[0], rgb[1], rgb[2]);
m_mapColorToBitmap[filament_rgb] = new wxBitmap(image);
} else {
bitmap = it->second;
}
}
ui->SetItemBitmap(ui_id, *bitmap);
}
}
const std::vector<std::string>& PresetBundle::print_options()
{
const char *opts[] = {
"layer_height", "first_layer_height", "perimeters", "spiral_vase", "top_solid_layers", "bottom_solid_layers", "layer_height", "first_layer_height", "perimeters", "spiral_vase", "top_solid_layers", "bottom_solid_layers",
"extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs", "extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs",
"seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "external_fill_pattern", "seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "external_fill_pattern",
@ -323,43 +178,370 @@ const std::vector<std::string>& PresetBundle::print_options()
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects", "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects",
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_per_color_wipe" "wipe_tower_width", "wipe_tower_per_color_wipe",
"compatible_printers"
}; };
static std::vector<std::string> s_opts;
if (s_opts.empty())
s_opts.assign(opts, opts + (sizeof(opts) / sizeof(opts[0])));
return s_opts; return s_opts;
} }
const std::vector<std::string>& PresetBundle::filament_options() const std::vector<std::string>& Preset::filament_options()
{ {
const char *opts[] = { static std::vector<std::string> s_opts {
"filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
"extrusion_multiplier", "filament_density", "filament_cost", "temperature", "first_layer_temperature", "bed_temperature", "extrusion_multiplier", "filament_density", "filament_cost", "temperature", "first_layer_temperature", "bed_temperature",
"first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed",
"disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode",
"end_filament_gcode" "end_filament_gcode",
"compatible_printers"
}; };
static std::vector<std::string> s_opts;
if (s_opts.empty())
s_opts.assign(opts, opts + (sizeof(opts) / sizeof(opts[0])));
return s_opts; return s_opts;
} }
const std::vector<std::string>& PresetBundle::printer_options() const std::vector<std::string>& Preset::printer_options()
{ {
const char *opts[] = { static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed",
"octoprint_host", "octoprint_apikey", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "octoprint_host", "octoprint_apikey", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
"nozzle_diameter", "extruder_offset", "retract_length", "retract_lift", "retract_speed", "deretract_speed", "between_objects_gcode", "printer_notes"
"retract_before_wipe", "retract_restart_extra", "retract_before_travel", "retract_layer_change", "wipe",
"retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", "printer_notes"
}; };
static std::vector<std::string> s_opts; s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end());
if (s_opts.empty()) }
s_opts.assign(opts, opts + (sizeof(opts) / sizeof(opts[0])));
return s_opts; return s_opts;
} }
const std::vector<std::string>& Preset::nozzle_options()
{
// ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings
static std::vector<std::string> s_opts {
"nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset",
"retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed",
"retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe",
"retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour"
};
return s_opts;
}
PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys) :
m_type(type),
m_edited_preset(type, "", false),
m_idx_selected(0),
m_bitmap_main_frame(new wxBitmap)
{
// Insert just the default preset.
m_presets.emplace_back(Preset(type, "- default -", true));
m_presets.front().load(keys);
m_edited_preset.config.apply(m_presets.front().config);
}
PresetCollection::~PresetCollection()
{
delete m_bitmap_main_frame;
m_bitmap_main_frame = nullptr;
}
// Load all presets found in dir_path.
// Throws an exception on error.
void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir)
{
boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred();
m_dir_path = dir.string();
m_presets.erase(m_presets.begin()+1, m_presets.end());
t_config_option_keys keys = this->default_preset().config.keys();
std::string errors_cummulative;
for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) {
std::string name = dir_entry.path().filename().string();
// Remove the .ini suffix.
name.erase(name.size() - 4);
try {
Preset preset(m_type, name, false);
preset.file = dir_entry.path().string();
preset.load(keys);
m_presets.emplace_back(preset);
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
errors_cummulative += "\n";
}
}
std::sort(m_presets.begin() + 1, m_presets.end());
this->select_preset(first_visible_idx());
if (! errors_cummulative.empty())
throw std::runtime_error(errors_cummulative);
}
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
// and select it, losing previous modifications.
Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select)
{
DynamicPrintConfig cfg(this->default_preset().config);
cfg.apply_only(config, cfg.keys(), true);
return this->load_preset(path, name, std::move(cfg));
}
Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select)
{
Preset key(m_type, name);
auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key);
if (it == m_presets.end() || it->name != name)
it = m_presets.emplace(it, Preset(m_type, name, false));
Preset &preset = *it;
preset.file = path;
preset.config = std::move(config);
preset.loaded = true;
preset.is_dirty = false;
if (select)
this->select_preset_by_name(name, true);
return preset;
}
void PresetCollection::save_current_preset(const std::string &new_name)
{
// 1) Find the preset with a new_name or create a new one,
// initialize it with the edited config.
Preset key(m_type, new_name, false);
auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key);
if (it != m_presets.end() && it->name == key.name) {
// Preset with the same name found.
Preset &preset = *it;
if (preset.is_default)
// Cannot overwrite the default preset.
return;
// Overwriting an existing preset.
preset.config = std::move(m_edited_preset.config);
} else {
// Creating a new preset.
Preset &preset = *m_presets.insert(it, m_edited_preset);
std::string file_name = new_name;
if (! boost::iends_with(file_name, ".ini"))
file_name += ".ini";
preset.name = new_name;
preset.file = (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string();
}
// 2) Activate the saved preset.
this->select_preset_by_name(new_name, true);
// 2) Store the active preset to disk.
this->get_selected_preset().save();
}
void PresetCollection::delete_current_preset()
{
const Preset &selected = this->get_selected_preset();
if (selected.is_default)
return;
if (! selected.is_external) {
// Erase the preset file.
boost::nowide::remove(selected.file.c_str());
}
// Remove the preset from the list.
m_presets.erase(m_presets.begin() + m_idx_selected);
// Find the next visible preset.
size_t new_selected_idx = m_idx_selected;
if (new_selected_idx < m_presets.size())
for (; new_selected_idx < m_presets.size() && ! m_presets[new_selected_idx].is_visible; ++ new_selected_idx) ;
if (new_selected_idx == m_presets.size())
for (--new_selected_idx; new_selected_idx > 0 && !m_presets[new_selected_idx].is_visible; --new_selected_idx);
this->select_preset(new_selected_idx);
}
bool PresetCollection::load_bitmap_default(const std::string &file_name)
{
return m_bitmap_main_frame->LoadFile(wxString::FromUTF8(Slic3r::var(file_name).c_str()), wxBITMAP_TYPE_PNG);
}
// Return a preset by its name. If the preset is active, a temporary copy is returned.
// If a preset is not found by its name, null is returned.
Preset* PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found)
{
Preset key(m_type, name, false);
auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key);
// Ensure that a temporary copy is returned if the preset found is currently selected.
return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin()) :
first_visible_if_not_found ? &this->first_visible() : nullptr;
}
// Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible.
size_t PresetCollection::first_visible_idx() const
{
size_t idx = m_default_suppressed ? 1 : 0;
for (; idx < this->m_presets.size(); ++ idx)
if (m_presets[idx].is_visible)
break;
if (idx == this->m_presets.size())
idx = 0;
return idx;
}
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
size_t PresetCollection::first_compatible_idx() const
{
size_t idx = m_default_suppressed ? 1 : 0;
for (; idx < this->m_presets.size(); ++ idx)
if (m_presets[idx].is_compatible)
break;
if (idx == this->m_presets.size())
idx = 0;
return idx;
}
void PresetCollection::set_default_suppressed(bool default_suppressed)
{
if (m_default_suppressed != default_suppressed) {
m_default_suppressed = default_suppressed;
m_presets.front().is_visible = ! default_suppressed || (m_presets.size() > 1 && m_idx_selected > 0);
}
}
void PresetCollection::update_compatible_with_printer(const std::string &active_printer, bool select_other_if_incompatible)
{
size_t num_visible = 0;
for (size_t idx_preset = 1; idx_preset < m_presets.size(); ++ idx_preset) {
bool selected = idx_preset == m_idx_selected;
Preset &preset_selected = m_presets[idx_preset];
Preset &preset_edited = selected ? m_edited_preset : preset_selected;
if (preset_edited.update_compatible_with_printer(active_printer))
// Mark compatible presets as visible.
preset_selected.is_visible = true;
else if (selected && select_other_if_incompatible) {
preset_selected.is_visible = false;
m_idx_selected = (size_t)-1;
}
if (selected)
preset_selected.is_compatible = preset_edited.is_compatible;
if (preset_selected.is_visible)
++ num_visible;
}
if (m_idx_selected == (size_t)-1)
// Find some other visible preset.
this->select_preset(first_visible_idx());
else if (num_visible == 0)
// Show the "-- default --" preset.
m_presets.front().is_visible = true;
}
// Save the preset under a new name. If the name is different from the old one,
// a new preset is stored into the list of presets.
// All presets are marked as not modified and the new preset is activated.
//void PresetCollection::save_current_preset(const std::string &new_name);
// Delete the current preset, activate the first visible preset.
//void PresetCollection::delete_current_preset();
// Update the wxChoice UI component from this list of presets.
// Hide the
void PresetCollection::update_platter_ui(wxBitmapComboBox *ui)
{
if (ui == nullptr)
return;
// Otherwise fill in the list from scratch.
ui->Freeze();
ui->Clear();
for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) {
const Preset &preset = this->m_presets[i];
if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected))
continue;
const wxBitmap *bmp = (i == 0 || preset.is_compatible) ? m_bitmap_main_frame : m_bitmap_incompatible;
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
(bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp);
if (i == m_idx_selected)
ui->SetSelection(ui->GetCount() - 1);
}
ui->Thaw();
}
void PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible)
{
if (ui == nullptr)
return;
ui->Freeze();
ui->Clear();
for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) {
const Preset &preset = this->m_presets[i];
if (! show_incompatible && ! preset.is_compatible && i != m_idx_selected)
continue;
const wxBitmap *bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible;
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
(bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp);
if (i == m_idx_selected)
ui->SetSelection(ui->GetCount() - 1);
}
ui->Thaw();
}
// Update a dirty floag of the current preset, update the labels of the UI component accordingly.
// Return true if the dirty flag changed.
bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui)
{
wxWindowUpdateLocker noUpdates(ui);
// 1) Update the dirty flag of the current preset.
bool was_dirty = this->get_selected_preset().is_dirty;
bool is_dirty = current_is_dirty();
this->get_selected_preset().is_dirty = is_dirty;
this->get_edited_preset().is_dirty = is_dirty;
// 2) Update the labels.
for (unsigned int ui_id = 0; ui_id < ui->GetCount(); ++ ui_id) {
std::string old_label = ui->GetString(ui_id).utf8_str().data();
std::string preset_name = Preset::remove_suffix_modified(old_label);
const Preset *preset = this->find_preset(preset_name, false);
assert(preset != nullptr);
std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name;
if (old_label != new_label)
ui->SetString(ui_id, wxString::FromUTF8(new_label.c_str()));
}
return was_dirty != is_dirty;
}
Preset& PresetCollection::select_preset(size_t idx)
{
for (Preset &preset : m_presets)
preset.is_dirty = false;
if (idx >= m_presets.size())
idx = first_visible_idx();
m_idx_selected = idx;
m_edited_preset = m_presets[idx];
m_presets.front().is_visible = ! m_default_suppressed || m_idx_selected == 0;
return m_presets[idx];
}
bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force)
{
std::string name = Preset::remove_suffix_modified(name_w_suffix);
// 1) Try to find the preset by its name.
Preset key(m_type, name, false);
auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key);
size_t idx = 0;
if (it != m_presets.end() && it->name == key.name)
// Preset found by its name.
idx = it - m_presets.begin();
else {
// Find the first visible preset.
for (size_t i = m_default_suppressed ? 1 : 0; i < m_presets.size(); ++ i)
if (m_presets[i].is_visible) {
idx = i;
break;
}
// If the first visible preset was not found, return the 0th element, which is the default preset.
}
// 2) Select the new preset.
if (m_idx_selected != idx || force) {
this->select_preset(idx);
return true;
}
return false;
}
std::string PresetCollection::name() const
{
switch (this->type()) {
case Preset::TYPE_PRINT: return "print";
case Preset::TYPE_FILAMENT: return "filament";
case Preset::TYPE_PRINTER: return "printer";
default: return "invalid";
}
}
} // namespace Slic3r } // namespace Slic3r

View File

@ -1,14 +1,28 @@
#ifndef slic3r_Preset_hpp_ #ifndef slic3r_Preset_hpp_
#define slic3r_Preset_hpp_ #define slic3r_Preset_hpp_
#include <deque>
#include "../../libslic3r/libslic3r.h" #include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/PrintConfig.hpp" #include "../../libslic3r/PrintConfig.hpp"
class wxBitmap; class wxBitmap;
class wxChoice;
class wxBitmapComboBox; class wxBitmapComboBox;
class wxItemContainer;
namespace Slic3r { namespace Slic3r {
enum ConfigFileType
{
CONFIG_FILE_TYPE_UNKNOWN,
CONFIG_FILE_TYPE_APP_CONFIG,
CONFIG_FILE_TYPE_CONFIG,
CONFIG_FILE_TYPE_CONFIG_BUNDLE,
};
extern ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree);
class Preset class Preset
{ {
public: public:
@ -35,6 +49,8 @@ public:
bool is_visible = true; bool is_visible = true;
// Has this preset been modified? // Has this preset been modified?
bool is_dirty = false; bool is_dirty = false;
// Is this preset compatible with the currently active printer?
bool is_compatible = true;
// Name of the preset, usually derived form the file name. // Name of the preset, usually derived form the file name.
std::string name; std::string name;
@ -53,10 +69,41 @@ public:
// Throws std::runtime_error in case the file cannot be read. // Throws std::runtime_error in case the file cannot be read.
DynamicPrintConfig& load(const std::vector<std::string> &keys); DynamicPrintConfig& load(const std::vector<std::string> &keys);
void save();
// Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty.
std::string label() const;
// Set the is_dirty flag if the provided config is different from the active one. // Set the is_dirty flag if the provided config is different from the active one.
void set_dirty(const DynamicPrintConfig &config) { this->is_dirty = ! this->config.diff(config).empty(); } void set_dirty(const DynamicPrintConfig &config) { this->is_dirty = ! this->config.diff(config).empty(); }
void set_dirty(bool dirty = true) { this->is_dirty = dirty; }
void reset_dirty() { this->is_dirty = false; } void reset_dirty() { this->is_dirty = false; }
bool enable_compatible(const std::string &active_printer);
bool is_compatible_with_printer(const std::string &active_printer) const;
// Mark this preset as compatible if it is compatible with active_printer.
bool update_compatible_with_printer(const std::string &active_printer);
// Resize the extruder specific fields, initialize them with the content of the 1st extruder.
void set_num_extruders(unsigned int n) { set_num_extruders(this->config, n); }
// Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection.
bool operator<(const Preset &other) const { return this->name < other.name; }
static const std::vector<std::string>& print_options();
static const std::vector<std::string>& filament_options();
// Printer options contain the nozzle options.
static const std::vector<std::string>& printer_options();
// Nozzle options of the printer options.
static const std::vector<std::string>& nozzle_options();
protected:
friend class PresetCollection;
friend class PresetBundle;
static void normalize(DynamicPrintConfig &config);
// Resize the extruder specific vectors ()
static void set_num_extruders(DynamicPrintConfig &config, unsigned int n);
static const std::string& suffix_modified();
static std::string remove_suffix_modified(const std::string &name);
}; };
// Collections of presets of the same type (one of the Print, Filament or Printer type). // Collections of presets of the same type (one of the Print, Filament or Printer type).
@ -65,10 +112,31 @@ class PresetCollection
public: public:
// Initialize the PresetCollection with the "- default -" preset. // Initialize the PresetCollection with the "- default -" preset.
PresetCollection(Preset::Type type, const std::vector<std::string> &keys); PresetCollection(Preset::Type type, const std::vector<std::string> &keys);
~PresetCollection();
Preset::Type type() const { return m_type; }
std::string name() const;
const std::deque<Preset>& operator()() const { return m_presets; }
// Load ini files of the particular type from the provided directory path. // Load ini files of the particular type from the provided directory path.
void load_presets(const std::string &dir_path, const std::string &subdir); void load_presets(const std::string &dir_path, const std::string &subdir);
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
// and select it, losing previous modifications.
Preset& load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select = true);
Preset& load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select = true);
// Save the preset under a new name. If the name is different from the old one,
// a new preset is stored into the list of presets.
// All presets are marked as not modified and the new preset is activated.
void save_current_preset(const std::string &new_name);
// Delete the current preset, activate the first visible preset.
void delete_current_preset();
// Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame.
bool load_bitmap_default(const std::string &file_name);
// Compatible & incompatible marks, to be placed at the wxBitmapComboBox items. // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items.
void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; } void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; }
void set_bitmap_incompatible(const wxBitmap *bmp) { m_bitmap_incompatible = bmp; } void set_bitmap_incompatible(const wxBitmap *bmp) { m_bitmap_incompatible = bmp; }
@ -82,70 +150,89 @@ public:
// Return the selected preset, without the user modifications applied. // Return the selected preset, without the user modifications applied.
Preset& get_selected_preset() { return m_presets[m_idx_selected]; } Preset& get_selected_preset() { return m_presets[m_idx_selected]; }
const Preset& get_selected_preset() const { return m_presets[m_idx_selected]; } const Preset& get_selected_preset() const { return m_presets[m_idx_selected]; }
int get_selected_idx() const { return m_idx_selected; }
// Return the selected preset including the user modifications. // Return the selected preset including the user modifications.
Preset& get_edited_preset() { return m_edited_preset; } Preset& get_edited_preset() { return m_edited_preset; }
const Preset& get_edited_preset() const { return m_edited_preset; } const Preset& get_edited_preset() const { return m_edited_preset; }
// Return a preset possibly with modifications. // Return a preset possibly with modifications.
const Preset& default_preset() const { return m_presets.front(); } const Preset& default_preset() const { return m_presets.front(); }
// Return a preset by an index. If the preset is active, a temporary copy is returned.
Preset& preset(size_t idx) { return (int(idx) == m_idx_selected) ? m_edited_preset : m_presets[idx]; } Preset& preset(size_t idx) { return (int(idx) == m_idx_selected) ? m_edited_preset : m_presets[idx]; }
const Preset& preset(size_t idx) const { return const_cast<PresetCollection*>(this)->preset(idx); } const Preset& preset(size_t idx) const { return const_cast<PresetCollection*>(this)->preset(idx); }
void discard_current_changes() { m_edited_preset = m_presets[m_idx_selected]; }
// Return a preset by its name. If the preset is active, a temporary copy is returned.
// If a preset is not found by its name, null is returned.
Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false);
const Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false) const
{ return const_cast<PresetCollection*>(this)->find_preset(name, first_visible_if_not_found); }
size_t first_visible_idx() const;
size_t first_compatible_idx() const;
// Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible.
// Return the first visible preset. Certainly at least the '- default -' preset shall be visible.
Preset& first_visible() { return this->preset(this->first_visible_idx()); }
const Preset& first_visible() const { return this->preset(this->first_visible_idx()); }
Preset& first_compatible() { return this->preset(this->first_compatible_idx()); }
const Preset& first_compatible() const { return this->preset(this->first_compatible_idx()); }
// Return number of presets including the "- default -" preset.
size_t size() const { return this->m_presets.size(); } size_t size() const { return this->m_presets.size(); }
// For Print / Filament presets, disable those, which are not compatible with the printer. // For Print / Filament presets, disable those, which are not compatible with the printer.
void enable_disable_compatible_to_printer(const std::string &active_printer); void update_compatible_with_printer(const std::string &active_printer, bool select_other_if_incompatible);
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); } size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); }
void delete_preset(const size_t idx);
// Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ.
bool current_is_dirty() { return ! this->current_dirty_options().empty(); }
// Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ.
std::vector<std::string> current_dirty_options() { return this->get_selected_preset().config.diff(this->get_edited_preset().config); }
// Update the choice UI from the list of presets. // Update the choice UI from the list of presets.
void update_editor_ui(wxBitmapComboBox *ui); // If show_incompatible, all presets are shown, otherwise only the compatible presets are shown.
// If an incompatible preset is selected, it is shown as well.
void update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible);
// Update the choice UI from the list of presets.
// Only the compatible presets are shown.
// If an incompatible preset is selected, it is shown as well.
void update_platter_ui(wxBitmapComboBox *ui); void update_platter_ui(wxBitmapComboBox *ui);
// Update a dirty floag of the current preset, update the labels of the UI component accordingly.
// Return true if the dirty flag changed.
bool update_dirty_ui(wxBitmapComboBox *ui);
// Select a profile by its name. Return true if the selection changed.
// Without force, the selection is only updated if the index changes.
// With force, the changes are reverted if the new index is the same as the old index.
bool select_preset_by_name(const std::string &name, bool force);
private: private:
PresetCollection();
PresetCollection(const PresetCollection &other);
PresetCollection& operator=(const PresetCollection &other);
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER. // Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
Preset::Type m_type; Preset::Type m_type;
// List of presets, starting with the "- default -" preset. // List of presets, starting with the "- default -" preset.
std::vector<Preset> m_presets; // Use deque to force the container to allocate an object per each entry,
// so that the addresses of the presets don't change during resizing of the container.
std::deque<Preset> m_presets;
// Initially this preset contains a copy of the selected preset. Later on, this copy may be modified by the user.
Preset m_edited_preset; Preset m_edited_preset;
// Selected preset. // Selected preset.
int m_idx_selected; int m_idx_selected;
// Is the "- default -" preset suppressed? // Is the "- default -" preset suppressed?
bool m_default_suppressed = true; bool m_default_suppressed = true;
// Compatible & incompatible marks, to be placed at the wxBitmapComboBox items. // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Platter.
// These bitmaps are not owned by PresetCollection, but by a PresetBundle.
const wxBitmap *m_bitmap_compatible = nullptr; const wxBitmap *m_bitmap_compatible = nullptr;
const wxBitmap *m_bitmap_incompatible = nullptr; const wxBitmap *m_bitmap_incompatible = nullptr;
}; // Marks placed at the wxBitmapComboBox of a MainFrame.
// These bitmaps are owned by PresetCollection.
// Bundle of Print + Filament + Printer presets. wxBitmap *m_bitmap_main_frame;
class PresetBundle // Path to the directory to store the config files into.
{ std::string m_dir_path;
public:
PresetBundle();
~PresetBundle();
bool load_bitmaps(const std::string &path_bitmap_compatible, const std::string &path_bitmap_incompatible);
// Load ini files of all types (print, filament, printer) from the provided directory path.
void load_presets(const std::string &dir_path);
PresetCollection prints;
PresetCollection filaments;
PresetCollection printers;
// Update the colors preview at the platter extruder combo box.
void update_platter_filament_ui_colors(wxBitmapComboBox *ui, unsigned int idx_extruder, unsigned int idx_filament);
static const std::vector<std::string>& print_options();
static const std::vector<std::string>& filament_options();
static const std::vector<std::string>& printer_options();
private:
// Indicator, that the preset is compatible with the selected printer.
wxBitmap *m_bitmapCompatible;
// Indicator, that the preset is NOT compatible with the selected printer.
wxBitmap *m_bitmapIncompatible;
// Caching color bitmaps for the
std::map<std::string, wxBitmap*> m_mapColorToBitmap;
}; };
} // namespace Slic3r } // namespace Slic3r

View File

@ -0,0 +1,663 @@
//#undef NDEBUGc
#include <cassert>
#include "PresetBundle.hpp"
#include <fstream>
#include <boost/filesystem.hpp>
#include <boost/algorithm/clamp.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/nowide/cenv.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/locale.hpp>
#include <wx/dcmemory.h>
#include <wx/image.h>
#include <wx/choice.h>
#include <wx/bmpcbox.h>
#include <wx/wupdlock.h>
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/PlaceholderParser.hpp"
#include "../../libslic3r/Utils.hpp"
namespace Slic3r {
PresetBundle::PresetBundle() :
prints(Preset::TYPE_PRINT, Preset::print_options()),
filaments(Preset::TYPE_FILAMENT, Preset::filament_options()),
printers(Preset::TYPE_PRINTER, Preset::printer_options()),
m_bitmapCompatible(new wxBitmap),
m_bitmapIncompatible(new wxBitmap)
{
::wxInitAllImageHandlers();
// Create the ID config keys, as they are not part of the Static print config classes.
this->prints.preset(0).config.opt_string("print_settings_id", true);
this->filaments.preset(0).config.opt_string("filament_settings_id", true);
this->printers.preset(0).config.opt_string("print_settings_id", true);
// Create the "compatible printers" keys, as they are not part of the Static print config classes.
this->filaments.preset(0).config.optptr("compatible_printers", true);
this->prints.preset(0).config.optptr("compatible_printers", true);
this->prints .load_bitmap_default("cog.png");
this->filaments.load_bitmap_default("spool.png");
this->printers .load_bitmap_default("printer_empty.png");
this->load_compatible_bitmaps();
}
PresetBundle::~PresetBundle()
{
assert(m_bitmapCompatible != nullptr);
assert(m_bitmapIncompatible != nullptr);
delete m_bitmapCompatible;
m_bitmapCompatible = nullptr;
delete m_bitmapIncompatible;
m_bitmapIncompatible = nullptr;
for (std::pair<const std::string, wxBitmap*> &bitmap : m_mapColorToBitmap)
delete bitmap.second;
}
void PresetBundle::setup_directories()
{
boost::filesystem::path dir = boost::filesystem::canonical(Slic3r::data_dir());
if (! boost::filesystem::is_directory(dir))
throw std::runtime_error(std::string("datadir does not exist: ") + Slic3r::data_dir());
std::initializer_list<const char*> names = { "print", "filament", "printer" };
for (const char *name : names) {
boost::filesystem::path subdir = (dir / name).make_preferred();
if (! boost::filesystem::is_directory(subdir) &&
! boost::filesystem::create_directory(subdir))
throw std::runtime_error(std::string("Slic3r was unable to create its data directory at ") + subdir.string());
}
}
void PresetBundle::load_presets(const std::string &dir_path)
{
std::string errors_cummulative;
try {
this->prints.load_presets(dir_path, "print");
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
}
try {
this->filaments.load_presets(dir_path, "filament");
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
}
try {
this->printers.load_presets(dir_path, "printer");
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
}
this->update_multi_material_filament_presets();
if (! errors_cummulative.empty())
throw std::runtime_error(errors_cummulative);
}
static inline std::string remove_ini_suffix(const std::string &name)
{
std::string out = name;
if (boost::iends_with(out, ".ini"))
out.erase(out.end() - 4, out.end());
return out;
}
// Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up.
void PresetBundle::load_selections(const AppConfig &config)
{
prints.select_preset_by_name(remove_ini_suffix(config.get("presets", "print")), true);
filaments.select_preset_by_name(remove_ini_suffix(config.get("presets", "filament")), true);
printers.select_preset_by_name(remove_ini_suffix(config.get("presets", "printer")), true);
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(printers.get_selected_preset().config.option("nozzle_diameter"));
size_t num_extruders = nozzle_diameter->values.size();
this->set_filament_preset(0, filaments.get_selected_preset().name);
for (unsigned int i = 1; i < (unsigned int)num_extruders; ++ i) {
char name[64];
sprintf(name, "filament_%d", i);
if (! config.has("presets", name))
break;
this->set_filament_preset(i, remove_ini_suffix(config.get("presets", name)));
}
// Update visibility of presets based on their compatibility with the active printer.
// This will switch the print or filament presets to compatible if the active presets are incompatible.
this->update_compatible_with_printer(false);
}
// Export selections (current print, current filaments, current printer) into config.ini
void PresetBundle::export_selections(AppConfig &config)
{
assert(filament_presets.size() >= 1);
assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front());
config.clear_section("presets");
config.set("presets", "print", prints.get_selected_preset().name);
config.set("presets", "filament", filament_presets.front());
for (int i = 1; i < filament_presets.size(); ++i) {
char name[64];
sprintf(name, "filament_%d", i);
config.set("presets", name, filament_presets[i]);
}
config.set("presets", "printer", printers.get_selected_preset().name);
}
void PresetBundle::export_selections(PlaceholderParser &pp)
{
assert(filament_presets.size() >= 1);
assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front());
pp.set("print_preset", prints.get_selected_preset().name);
pp.set("filament_preset", filament_presets);
pp.set("printer_preset", printers.get_selected_preset().name);
}
bool PresetBundle::load_compatible_bitmaps()
{
const std::string path_bitmap_compatible = "flag-green-icon.png";
const std::string path_bitmap_incompatible = "flag-red-icon.png";
bool loaded_compatible = m_bitmapCompatible ->LoadFile(
wxString::FromUTF8(Slic3r::var(path_bitmap_compatible).c_str()), wxBITMAP_TYPE_PNG);
bool loaded_incompatible = m_bitmapIncompatible->LoadFile(
wxString::FromUTF8(Slic3r::var(path_bitmap_incompatible).c_str()), wxBITMAP_TYPE_PNG);
if (loaded_compatible) {
prints .set_bitmap_compatible(m_bitmapCompatible);
filaments.set_bitmap_compatible(m_bitmapCompatible);
// printers .set_bitmap_compatible(m_bitmapCompatible);
}
if (loaded_incompatible) {
prints .set_bitmap_incompatible(m_bitmapIncompatible);
filaments.set_bitmap_incompatible(m_bitmapIncompatible);
// printers .set_bitmap_incompatible(m_bitmapIncompatible);
}
return loaded_compatible && loaded_incompatible;
}
DynamicPrintConfig PresetBundle::full_config() const
{
DynamicPrintConfig out;
out.apply(FullPrintConfig());
out.apply(this->prints.get_edited_preset().config);
out.apply(this->printers.get_edited_preset().config);
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(out.option("nozzle_diameter"));
size_t num_extruders = nozzle_diameter->values.size();
if (num_extruders <= 1) {
out.apply(this->filaments.get_edited_preset().config);
} else {
// Retrieve filament presets and build a single config object for them.
// First collect the filament configurations based on the user selection of this->filament_presets.
std::vector<const DynamicPrintConfig*> filament_configs;
for (const std::string &filament_preset_name : this->filament_presets)
filament_configs.emplace_back(&this->filaments.find_preset(filament_preset_name, true)->config);
while (filament_configs.size() < num_extruders)
filament_configs.emplace_back(&this->filaments.first_visible().config);
// Option values to set a ConfigOptionVector from.
std::vector<const ConfigOption*> filament_opts(num_extruders, nullptr);
// loop through options and apply them to the resulting config.
for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) {
if (key == "compatible_printers")
continue;
// Get a destination option.
ConfigOption *opt_dst = out.option(key, false);
if (opt_dst->is_scalar()) {
// Get an option, do not create if it does not exist.
const ConfigOption *opt_src = filament_configs.front()->option(key);
if (opt_src != nullptr)
opt_dst->set(opt_src);
} else {
// Setting a vector value from all filament_configs.
for (size_t i = 0; i < filament_opts.size(); ++ i)
filament_opts[i] = filament_configs[i]->option(key);
static_cast<ConfigOptionVectorBase*>(opt_dst)->set(filament_opts);
}
}
}
static const char *keys[] = { "perimeter", "infill", "solid_infill", "support_material", "support_material_interface" };
for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++ i) {
std::string key = std::string(keys[i]) + "_extruder";
auto *opt = dynamic_cast<ConfigOptionInt*>(out.option(key, false));
assert(opt != nullptr);
opt->value = boost::algorithm::clamp<int>(opt->value, 0, int(num_extruders));
}
return out;
}
// Load an external config file containing the print, filament and printer presets.
// Instead of a config file, a G-code may be loaded containing the full set of parameters.
// In the future the configuration will likely be read from an AMF file as well.
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
void PresetBundle::load_config_file(const std::string &path)
{
if (boost::iends_with(path, ".gcode") || boost::iends_with(path, ".g")) {
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config.load_from_gcode(path);
Preset::normalize(config);
load_config_file_config(path, std::move(config));
return;
}
// 1) Try to load the config file into a boost property tree.
boost::property_tree::ptree tree;
try {
boost::nowide::ifstream ifs(path);
boost::property_tree::read_ini(ifs, tree);
} catch (const std::ifstream::failure &err) {
throw std::runtime_error(std::string("The config file cannot be loaded: ") + path + "\n\tReason: " + err.what());
} catch (const std::runtime_error &err) {
throw std::runtime_error(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what());
}
// 2) Continue based on the type of the configuration file.
ConfigFileType config_file_type = guess_config_file_type(tree);
switch (config_file_type) {
case CONFIG_FILE_TYPE_UNKNOWN:
throw std::runtime_error(std::string("Unknown configuration file type: ") + path);
case CONFIG_FILE_TYPE_APP_CONFIG:
throw std::runtime_error(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
case CONFIG_FILE_TYPE_CONFIG:
{
// Initialize a config from full defaults.
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config.load(tree);
Preset::normalize(config);
load_config_file_config(path, std::move(config));
break;
}
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
load_config_file_config_bundle(path, tree);
break;
}
}
// Load a config file from a boost property_tree. This is a private method called from load_config_file.
void PresetBundle::load_config_file_config(const std::string &path, const DynamicPrintConfig &config)
{
// 1) Create a name from the file name.
// Keep the suffix (.ini, .gcode, .amf, .3mf etc) to differentiate it from the normal profiles.
std::string name = boost::filesystem::path(path).filename().string();
// 2) If the loading succeeded, split and load the config into print / filament / printer settings.
// First load the print and printer presets.
for (size_t i_group = 0; i_group < 2; ++ i_group) {
PresetCollection &presets = (i_group == 0) ? this->prints : this->printers;
presets.load_preset(path, name, config).is_external = true;
}
// 3) Now load the filaments. If there are multiple filament presets, split them and load them.
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter"));
auto *filament_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("filament_diameter"));
size_t num_extruders = std::min(nozzle_diameter->values.size(), filament_diameter->values.size());
if (num_extruders <= 1) {
this->filaments.load_preset(path, name, config).is_external = true;
this->filament_presets.clear();
this->filament_presets.emplace_back(name);
} else {
// Split the filament presets, load each of them separately.
std::vector<DynamicPrintConfig> configs(num_extruders, this->filaments.default_preset().config);
// loop through options and scatter them into configs.
for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) {
const ConfigOption *other_opt = config.option(key);
if (other_opt == nullptr)
continue;
if (other_opt->is_scalar()) {
for (size_t i = 0; i < configs.size(); ++ i)
configs[i].option(key, false)->set(other_opt);
} else {
for (size_t i = 0; i < configs.size(); ++ i)
static_cast<ConfigOptionVectorBase*>(configs[i].option(key, false))->set_at(other_opt, 0, i);
}
}
// Load the configs into this->filaments and make them active.
this->filament_presets.clear();
for (size_t i = 0; i < configs.size(); ++ i) {
char suffix[64];
if (i == 0)
suffix[0] = 0;
else
sprintf(suffix, " (%d)", i);
// Load all filament presets, but only select the first one in the preset dialog.
this->filaments.load_preset(path, name + suffix, std::move(configs[i]), i == 0).is_external = true;
this->filament_presets.emplace_back(name + suffix);
}
}
}
// Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file.
void PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree)
{
// 1) Load the config bundle into a temp data.
PresetBundle tmp_bundle;
tmp_bundle.load_configbundle(path);
std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string();
// 2) Extract active configs from the config bundle, copy them and activate them in this bundle.
auto load_one = [this, &path, &bundle_name](PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string {
Preset *preset_src = collection_src.find_preset(preset_name_src, false);
Preset *preset_dst = collection_dst.find_preset(preset_name_src, false);
assert(preset_src != nullptr);
std::string preset_name_dst;
if (preset_dst != nullptr && preset_dst->is_default) {
// No need to copy a default preset, it always exists in collection_dst.
if (activate)
collection_dst.select_preset(0);
return preset_name_src;
} else if (preset_dst != nullptr && preset_src->config == preset_dst->config) {
// Don't save as the config exists in the current bundle and its content is the same.
return preset_name_src;
} else {
// Generate a new unique name.
preset_name_dst = preset_name_src + bundle_name;
Preset *preset_dup = nullptr;
for (size_t i = 1; (preset_dup = collection_dst.find_preset(preset_name_dst, false)) != nullptr; ++ i) {
if (preset_src->config == preset_dup->config)
// The preset has been already copied into collection_dst.
return preset_name_dst;
// Try to generate another name.
char buf[64];
sprintf(buf, " (%d)", i);
preset_name_dst = preset_name_src + buf + bundle_name;
}
}
assert(! preset_name_dst.empty());
// Save preset_src->config into collection_dst under preset_name_dst.
collection_dst.load_preset(path, preset_name_dst, std::move(preset_src->config), activate).is_external = true;
return preset_name_dst;
};
load_one(this->prints, tmp_bundle.prints, tmp_bundle.prints .get_selected_preset().name, true);
load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filaments.get_selected_preset().name, true);
load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset().name, true);
this->update_multi_material_filament_presets();
for (size_t i = 1; i < std::min(tmp_bundle.filament_presets.size(), this->filament_presets.size()); ++ i)
this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false);
}
// Load a config bundle file, into presets and store the loaded presets into separate files
// of the local configuration directory.
size_t PresetBundle::load_configbundle(const std::string &path)
{
// 1) Read the complete config file into a boost::property_tree.
namespace pt = boost::property_tree;
pt::ptree tree;
boost::nowide::ifstream ifs(path);
pt::read_ini(ifs, tree);
// 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files.
std::vector<std::string> loaded_prints;
std::vector<std::string> loaded_filaments;
std::vector<std::string> loaded_printers;
std::string active_print;
std::vector<std::string> active_filaments;
std::string active_printer;
size_t presets_loaded = 0;
for (const auto &section : tree) {
PresetCollection *presets = nullptr;
std::vector<std::string> *loaded = nullptr;
std::string preset_name;
if (boost::starts_with(section.first, "print:")) {
presets = &prints;
loaded = &loaded_prints;
preset_name = section.first.substr(6);
} else if (boost::starts_with(section.first, "filament:")) {
presets = &filaments;
loaded = &loaded_filaments;
preset_name = section.first.substr(9);
} else if (boost::starts_with(section.first, "printer:")) {
presets = &printers;
loaded = &loaded_printers;
preset_name = section.first.substr(8);
} else if (section.first == "presets") {
// Load the names of the active presets.
for (auto &kvp : section.second) {
if (kvp.first == "print") {
active_print = kvp.second.data();
} else if (boost::starts_with(kvp.first, "filament")) {
int idx = 0;
if (kvp.first == "filament" || sscanf(kvp.first.c_str(), "filament_%d", &idx) == 1) {
if (int(active_filaments.size()) <= idx)
active_filaments.resize(idx + 1, std::string());
active_filaments[idx] = kvp.second.data();
}
} else if (kvp.first == "printer") {
active_printer = kvp.second.data();
}
}
} else if (section.first == "settings") {
// Load the settings.
for (auto &kvp : section.second) {
if (kvp.first == "autocenter") {
}
}
} else
// Ignore an unknown section.
continue;
if (presets != nullptr) {
// Load the print, filament or printer preset.
DynamicPrintConfig config(presets->default_preset().config);
for (auto &kvp : section.second)
config.set_deserialize(kvp.first, kvp.second.data());
Preset::normalize(config);
// Load the preset into the list of presets, save it to disk.
presets->load_preset(Slic3r::config_path(presets->name(), preset_name), preset_name, std::move(config), false).save();
++ presets_loaded;
}
}
// 3) Activate the presets.
if (! active_print.empty())
prints.select_preset_by_name(active_print, true);
if (! active_printer.empty())
printers.select_preset_by_name(active_printer, true);
// Activate the first filament preset.
if (! active_filaments.empty() && ! active_filaments.front().empty())
filaments.select_preset_by_name(active_filaments.front(), true);
this->update_multi_material_filament_presets();
for (size_t i = 0; i < std::min(this->filament_presets.size(), active_filaments.size()); ++ i)
this->filament_presets[i] = filaments.find_preset(active_filaments[i], true)->name;
return presets_loaded;
}
void PresetBundle::update_multi_material_filament_presets()
{
// Verify and select the filament presets.
auto *nozzle_diameter = static_cast<const ConfigOptionFloats*>(printers.get_edited_preset().config.option("nozzle_diameter"));
size_t num_extruders = nozzle_diameter->values.size();
// Verify validity of the current filament presets.
for (size_t i = 0; i < std::min(this->filament_presets.size(), num_extruders); ++ i)
this->filament_presets[i] = this->filaments.find_preset(this->filament_presets[i], true)->name;
// Append the rest of filament presets.
// if (this->filament_presets.size() < num_extruders)
this->filament_presets.resize(num_extruders, this->filament_presets.empty() ? this->filaments.first_visible().name : this->filament_presets.back());
}
void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible)
{
this->prints.update_compatible_with_printer(this->printers.get_selected_preset().name, select_other_if_incompatible);
this->filaments.update_compatible_with_printer(this->printers.get_selected_preset().name, select_other_if_incompatible);
if (select_other_if_incompatible) {
// Verify validity of the current filament presets.
for (std::string &filament_name : this->filament_presets) {
Preset *preset = this->filaments.find_preset(filament_name, false);
if (preset == nullptr || ! preset->is_compatible)
filament_name = this->filaments.first_compatible().name;
}
}
}
void PresetBundle::export_configbundle(const std::string &path) //, const DynamicPrintConfig &settings
{
boost::nowide::ofstream c;
c.open(path, std::ios::out | std::ios::trunc);
// Put a comment at the first line including the time stamp and Slic3r version.
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
// Export the print, filament and printer profiles.
for (size_t i_group = 0; i_group < 3; ++ i_group) {
const PresetCollection &presets = (i_group == 0) ? this->prints : (i_group == 1) ? this->filaments : this->printers;
for (const Preset &preset : presets()) {
if (preset.is_default || preset.is_external)
// Only export the common presets, not external files or the default preset.
continue;
c << std::endl << "[" << presets.name() << ":" << preset.name << "]" << std::endl;
for (const std::string &opt_key : preset.config.keys())
c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl;
}
}
// Export the names of the active presets.
c << std::endl << "[presets]" << std::endl;
c << "print = " << this->prints.get_selected_preset().name << std::endl;
c << "printer = " << this->printers.get_selected_preset().name << std::endl;
for (size_t i = 0; i < this->filament_presets.size(); ++ i) {
char suffix[64];
if (i > 0)
sprintf(suffix, "_%d", i);
else
suffix[0] = 0;
c << "filament" << suffix << " = " << this->filament_presets[i] << std::endl;
}
#if 0
// Export the following setting values from the provided setting repository.
static const char *settings_keys[] = { "autocenter" };
c << "[settings]" << std::endl;
for (size_t i = 0; i < sizeof(settings_keys) / sizeof(settings_keys[0]); ++ i)
c << settings_keys[i] << " = " << settings.serialize(settings_keys[i]) << std::endl;
#endif
c.close();
}
// Set the filament preset name. As the name could come from the UI selection box,
// an optional "(modified)" suffix will be removed from the filament name.
void PresetBundle::set_filament_preset(size_t idx, const std::string &name)
{
if (idx >= filament_presets.size())
filament_presets.resize(idx + 1, filaments.default_preset().name);
filament_presets[idx] = Preset::remove_suffix_modified(name);
}
static inline int hex_digit_to_int(const char c)
{
return
(c >= '0' && c <= '9') ? int(c - '0') :
(c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
(c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
}
static inline bool parse_color(const std::string &scolor, unsigned char *rgb_out)
{
rgb_out[0] = rgb_out[1] = rgb_out[2] = 0;
const char *c = scolor.data() + 1;
if (scolor.size() != 7 || scolor.front() != '#')
return false;
for (size_t i = 0; i < 3; ++ i) {
int digit1 = hex_digit_to_int(*c ++);
int digit2 = hex_digit_to_int(*c ++);
if (digit1 == -1 || digit2 == -1)
return false;
rgb_out[i] = (unsigned char)(digit1 * 16 + digit2);
}
return true;
}
void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui)
{
if (ui == nullptr)
return;
unsigned char rgb[3];
std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder);
if (! parse_color(extruder_color, rgb))
// Extruder color is not defined.
extruder_color.clear();
// Fill in the list from scratch.
ui->Freeze();
ui->Clear();
const Preset *selected_preset = this->filaments.find_preset(this->filament_presets[idx_extruder]);
// Show wide icons if the currently selected preset is not compatible with the current printer,
// and draw a red flag in front of the selected preset.
bool wide_icons = selected_preset != nullptr && ! selected_preset->is_compatible && m_bitmapIncompatible != nullptr;
assert(selected_preset != nullptr);
for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++ i) {
const Preset &preset = this->filaments.preset(i);
bool selected = this->filament_presets[idx_extruder] == preset.name;
if (! preset.is_visible || (! preset.is_compatible && ! selected))
continue;
// Assign an extruder color to the selected item if the extruder color is defined.
std::string filament_rgb = preset.config.opt_string("filament_colour", 0);
std::string extruder_rgb = (selected && !extruder_color.empty()) ? extruder_color : filament_rgb;
bool single_bar = filament_rgb == extruder_rgb;
std::string bitmap_key = single_bar ? filament_rgb : filament_rgb + extruder_rgb;
// If the filament preset is not compatible and there is a "red flag" icon loaded, show it left
// to the filament color image.
if (wide_icons)
bitmap_key += preset.is_compatible ? "comp" : "notcomp";
auto it = m_mapColorToBitmap.find(bitmap_key);
wxBitmap *bitmap = (it == m_mapColorToBitmap.end()) ? nullptr : it->second;
if (bitmap == nullptr) {
// Create the bitmap with color bars.
bitmap = new wxBitmap((wide_icons ? 16 : 0) + 24, 16);
#if defined(__APPLE__) || defined(_MSC_VER)
bitmap->UseAlpha();
#endif
wxMemoryDC memDC;
memDC.SelectObject(*bitmap);
memDC.SetBackground(*wxTRANSPARENT_BRUSH);
memDC.Clear();
if (wide_icons && ! preset.is_compatible)
// Paint the red flag.
memDC.DrawBitmap(*m_bitmapIncompatible, 0, 0, true);
// Paint the color bars.
parse_color(filament_rgb, rgb);
wxImage image(24, 16);
image.InitAlpha();
unsigned char* imgdata = image.GetData();
unsigned char* imgalpha = image.GetAlpha();
for (size_t i = 0; i < image.GetWidth() * image.GetHeight(); ++ i) {
*imgdata ++ = rgb[0];
*imgdata ++ = rgb[1];
*imgdata ++ = rgb[2];
*imgalpha ++ = wxALPHA_OPAQUE;
}
if (! single_bar) {
parse_color(extruder_rgb, rgb);
imgdata = image.GetData();
for (size_t r = 0; r < 16; ++ r) {
imgdata = image.GetData() + r * image.GetWidth() * 3;
for (size_t c = 0; c < 16; ++ c) {
*imgdata ++ = rgb[0];
*imgdata ++ = rgb[1];
*imgdata ++ = rgb[2];
}
}
}
memDC.DrawBitmap(wxBitmap(image), wide_icons ? 16 : 0, 0, true);
memDC.SelectObject(wxNullBitmap);
m_mapColorToBitmap[bitmap_key] = bitmap;
}
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), (bitmap == 0) ? wxNullBitmap : *bitmap);
if (selected)
ui->SetSelection(ui->GetCount() - 1);
}
ui->Thaw();
}
void PresetBundle::set_default_suppressed(bool default_suppressed)
{
prints.set_default_suppressed(default_suppressed);
filaments.set_default_suppressed(default_suppressed);
printers.set_default_suppressed(default_suppressed);
}
} // namespace Slic3r

View File

@ -0,0 +1,95 @@
#ifndef slic3r_PresetBundle_hpp_
#define slic3r_PresetBundle_hpp_
#include "AppConfig.hpp"
#include "Preset.hpp"
namespace Slic3r {
class PlaceholderParser;
// Bundle of Print + Filament + Printer presets.
class PresetBundle
{
public:
PresetBundle();
~PresetBundle();
void setup_directories();
// Load ini files of all types (print, filament, printer) from the provided directory path.
void load_presets(const std::string &dir_path);
// Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up.
void load_selections(const AppConfig &config);
// Export selections (current print, current filaments, current printer) into config.ini
void export_selections(AppConfig &config);
// Export selections (current print, current filaments, current printer) into a placeholder parser.
void export_selections(PlaceholderParser &pp);
PresetCollection prints;
PresetCollection filaments;
PresetCollection printers;
// Filament preset names for a multi-extruder or multi-material print.
// extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size()
std::vector<std::string> filament_presets;
bool has_defauls_only() const
{ return prints.size() <= 1 && filaments.size() <= 1 && printers.size() <= 1; }
DynamicPrintConfig full_config() const;
// Load an external config file containing the print, filament and printer presets.
// Instead of a config file, a G-code may be loaded containing the full set of parameters.
// In the future the configuration will likely be read from an AMF file as well.
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
void load_config_file(const std::string &path);
// Load a config bundle file, into presets and store the loaded presets into separate files
// of the local configuration directory.
// Load settings into the provided settings instance.
// Activate the presets stored in the config bundle.
// Returns the number of presets loaded successfully.
size_t load_configbundle(const std::string &path);
// Export a config bundle file containing all the presets and the names of the active presets.
void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings);
// Update a filament selection combo box on the platter for an idx_extruder.
void update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui);
// Enable / disable the "- default -" preset.
void set_default_suppressed(bool default_suppressed);
// Set the filament preset name. As the name could come from the UI selection box,
// an optional "(modified)" suffix will be removed from the filament name.
void set_filament_preset(size_t idx, const std::string &name);
// Read out the number of extruders from an active printer preset,
// update size and content of filament_presets.
void update_multi_material_filament_presets();
// Update the is_compatible flag of all print and filament presets depending on whether they are marked
// as compatible with the currently selected printer.
// Also updates the is_visible flag of each preset.
// If select_other_if_incompatible is true, then the print or filament preset is switched to some compatible
// preset if the current print or filament preset is not compatible.
void update_compatible_with_printer(bool select_other_if_incompatible);
private:
void load_config_file_config(const std::string &path, const DynamicPrintConfig &config);
void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
bool load_compatible_bitmaps();
// Indicator, that the preset is compatible with the selected printer.
wxBitmap *m_bitmapCompatible;
// Indicator, that the preset is NOT compatible with the selected printer.
wxBitmap *m_bitmapIncompatible;
// Caching color bitmaps for the
std::map<std::string, wxBitmap*> m_mapColorToBitmap;
};
} // namespace Slic3r
#endif /* slic3r_PresetBundle_hpp_ */

View File

@ -0,0 +1,229 @@
//#undef NDEBUG
#include <cassert>
#include "PresetBundle.hpp"
#include "PresetHints.hpp"
#include "Flow.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include "../../libslic3r/libslic3r.h"
namespace Slic3r {
std::string PresetHints::cooling_description(const Preset &preset)
{
std::string out;
char buf[4096];
if (preset.config.opt_bool("cooling", 0)) {
int slowdown_below_layer_time = preset.config.opt_int("slowdown_below_layer_time", 0);
int min_fan_speed = preset.config.opt_int("min_fan_speed", 0);
int max_fan_speed = preset.config.opt_int("max_fan_speed", 0);
int min_print_speed = int(preset.config.opt_float("min_print_speed", 0) + 0.5);
int fan_below_layer_time = preset.config.opt_int("fan_below_layer_time", 0);
sprintf(buf, "If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).",
slowdown_below_layer_time, max_fan_speed, slowdown_below_layer_time, min_print_speed);
out += buf;
if (fan_below_layer_time > slowdown_below_layer_time) {
sprintf(buf, "\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.",
fan_below_layer_time, max_fan_speed, min_fan_speed);
out += buf;
}
out += "\nDuring the other layers, fan ";
} else {
out = "Fan ";
}
if (preset.config.opt_bool("fan_always_on", 0)) {
int disable_fan_first_layers = preset.config.opt_int("disable_fan_first_layers", 0);
int min_fan_speed = preset.config.opt_int("min_fan_speed", 0);
sprintf(buf, "will always run at %d%% ", min_fan_speed);
out += buf;
if (disable_fan_first_layers > 1) {
sprintf(buf, "except for the first %d layers", disable_fan_first_layers);
out += buf;
}
else if (disable_fan_first_layers == 1)
out += "except for the first layer";
} else
out += "will be turned off.";
return out;
}
static const ConfigOptionFloatOrPercent& first_positive(const ConfigOptionFloatOrPercent *v1, const ConfigOptionFloatOrPercent &v2, const ConfigOptionFloatOrPercent &v3)
{
return (v1 != nullptr && v1->value > 0) ? *v1 : ((v2.value > 0) ? v2 : v3);
}
static double first_positive(double v1, double v2)
{
return (v1 > 0.) ? v1 : v2;
}
std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle &preset_bundle)
{
// Find out, to which nozzle index is the current filament profile assigned.
int idx_extruder = 0;
int num_extruders = (int)preset_bundle.filament_presets.size();
for (; idx_extruder < num_extruders; ++ idx_extruder)
if (preset_bundle.filament_presets[idx_extruder] == preset_bundle.filaments.get_selected_preset().name)
break;
if (idx_extruder == num_extruders)
// The current filament preset is not active for any extruder.
idx_extruder = -1;
const DynamicPrintConfig &print_config = preset_bundle.prints .get_edited_preset().config;
const DynamicPrintConfig &filament_config = preset_bundle.filaments.get_edited_preset().config;
const DynamicPrintConfig &printer_config = preset_bundle.printers .get_edited_preset().config;
// Current printer values.
float nozzle_diameter = (float)printer_config.opt_float("nozzle_diameter", idx_extruder);
// Print config values
double layer_height = print_config.opt_float("layer_height");
double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height);
double support_material_speed = print_config.opt_float("support_material_speed");
double support_material_interface_speed = print_config.get_abs_value("support_material_interface_speed", support_material_speed);
double bridge_speed = print_config.opt_float("bridge_speed");
double bridge_flow_ratio = print_config.opt_float("bridge_flow_ratio");
double perimeter_speed = print_config.opt_float("perimeter_speed");
double external_perimeter_speed = print_config.get_abs_value("external_perimeter_speed", perimeter_speed);
double gap_fill_speed = print_config.opt_float("gap_fill_speed");
double infill_speed = print_config.opt_float("infill_speed");
double small_perimeter_speed = print_config.get_abs_value("small_perimeter_speed", perimeter_speed);
double solid_infill_speed = print_config.get_abs_value("solid_infill_speed", infill_speed);
double top_solid_infill_speed = print_config.get_abs_value("top_solid_infill_speed", solid_infill_speed);
// Maximum print speed when auto-speed is enabled by setting any of the above speed values to zero.
double max_print_speed = print_config.opt_float("max_print_speed");
// Maximum volumetric speed allowed for the print profile.
double max_volumetric_speed = print_config.opt_float("max_volumetric_speed");
const auto &extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("extrusion_width");
const auto &external_perimeter_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("external_perimeter_extrusion_width");
const auto &first_layer_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
const auto &infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("infill_extrusion_width");
const auto &perimeter_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("perimeter_extrusion_width");
const auto &solid_infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("solid_infill_extrusion_width");
const auto &support_material_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("support_material_extrusion_width");
const auto &top_infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("top_infill_extrusion_width");
// Index of an extruder assigned to a feature. If set to 0, an active extruder will be used for a multi-material print.
// If different from idx_extruder, it will not be taken into account for this hint.
auto feature_extruder_active = [idx_extruder, num_extruders](int i) {
return i <= 0 || i > num_extruders || idx_extruder == -1 || idx_extruder == i - 1;
};
bool perimeter_extruder_active = feature_extruder_active(print_config.opt_int("perimeter_extruder"));
bool infill_extruder_active = feature_extruder_active(print_config.opt_int("infill_extruder"));
bool solid_infill_extruder_active = feature_extruder_active(print_config.opt_int("solid_infill_extruder"));
bool support_material_extruder_active = feature_extruder_active(print_config.opt_int("support_material_extruder"));
bool support_material_interface_extruder_active = feature_extruder_active(print_config.opt_int("support_material_interface_extruder"));
// Current filament values
double filament_diameter = filament_config.opt_float("filament_diameter", 0);
double filament_crossection = M_PI * 0.25 * filament_diameter * filament_diameter;
double extrusion_multiplier = filament_config.opt_float("extrusion_multiplier", 0);
// The following value will be annotated by this hint, so it does not take part in the calculation.
// double filament_max_volumetric_speed = filament_config.opt_float("filament_max_volumetric_speed", 0);
std::string out;
for (size_t idx_type = (first_layer_extrusion_width.value == 0) ? 1 : 0; idx_type < 3; ++ idx_type) {
// First test the maximum volumetric extrusion speed for non-bridging extrusions.
bool first_layer = idx_type == 0;
bool bridging = idx_type == 2;
const ConfigOptionFloatOrPercent *first_layer_extrusion_width_ptr = (first_layer && first_layer_extrusion_width.value > 0) ?
&first_layer_extrusion_width : nullptr;
const float lh = float(first_layer ? first_layer_height : layer_height);
const float bfr = bridging ? bridge_flow_ratio : 0.f;
double max_flow = 0.;
std::string max_flow_extrusion_type;
if (perimeter_extruder_active) {
double external_perimeter_rate = Flow::new_from_config_width(frExternalPerimeter,
first_positive(first_layer_extrusion_width_ptr, external_perimeter_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() *
(bridging ? bridge_speed :
first_positive(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed));
if (max_flow < external_perimeter_rate) {
max_flow = external_perimeter_rate;
max_flow_extrusion_type = "external perimeters";
}
double perimeter_rate = Flow::new_from_config_width(frPerimeter,
first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() *
(bridging ? bridge_speed :
first_positive(std::max(perimeter_speed, small_perimeter_speed), max_print_speed));
if (max_flow < perimeter_rate) {
max_flow = perimeter_rate;
max_flow_extrusion_type = "perimeters";
}
}
if (! bridging && infill_extruder_active) {
double infill_rate = Flow::new_from_config_width(frInfill,
first_positive(first_layer_extrusion_width_ptr, infill_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() * first_positive(infill_speed, max_print_speed);
if (max_flow < infill_rate) {
max_flow = infill_rate;
max_flow_extrusion_type = "infill";
}
}
if (solid_infill_extruder_active) {
double solid_infill_rate = Flow::new_from_config_width(frInfill,
first_positive(first_layer_extrusion_width_ptr, solid_infill_extrusion_width, extrusion_width),
nozzle_diameter, lh, 0).mm3_per_mm() *
(bridging ? bridge_speed : first_positive(solid_infill_speed, max_print_speed));
if (max_flow < solid_infill_rate) {
max_flow = solid_infill_rate;
max_flow_extrusion_type = "solid infill";
}
if (! bridging) {
double top_solid_infill_rate = Flow::new_from_config_width(frInfill,
first_positive(first_layer_extrusion_width_ptr, top_infill_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() * first_positive(top_solid_infill_speed, max_print_speed);
if (max_flow < top_solid_infill_rate) {
max_flow = top_solid_infill_rate;
max_flow_extrusion_type = "top solid infill";
}
}
}
if (support_material_extruder_active) {
double support_material_rate = Flow::new_from_config_width(frSupportMaterial,
first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() *
(bridging ? bridge_speed : first_positive(support_material_speed, max_print_speed));
if (max_flow < support_material_rate) {
max_flow = support_material_rate;
max_flow_extrusion_type = "support";
}
}
if (support_material_interface_extruder_active) {
double support_material_interface_rate = Flow::new_from_config_width(frSupportMaterialInterface,
first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() *
(bridging ? bridge_speed : first_positive(support_material_interface_speed, max_print_speed));
if (max_flow < support_material_interface_rate) {
max_flow = support_material_interface_rate;
max_flow_extrusion_type = "support interface";
}
}
//FIXME handle gap_fill_speed
if (! out.empty())
out += "\n";
out += (first_layer ? "First layer volumetric" : (bridging ? "Bridging volumetric" : "Volumetric"));
out += " flow rate is maximized ";
out += ((max_volumetric_speed > 0 && max_volumetric_speed < max_flow) ?
"by the print profile maximum" :
("when printing " + max_flow_extrusion_type))
+ " with a volumetric rate ";
if (max_volumetric_speed > 0 && max_volumetric_speed < max_flow)
max_flow = max_volumetric_speed;
char buf[2048];
sprintf(buf, "%3.2f mm³/s", max_flow);
out += buf;
sprintf(buf, " at filament speed %3.2f mm/s.", max_flow / filament_crossection);
out += buf;
}
return out;
}
}; // namespace Slic3r

View File

@ -0,0 +1,25 @@
#ifndef slic3r_PresetHints_hpp_
#define slic3r_PresetHints_hpp_
#include <string>
#include "PresetBundle.hpp"
namespace Slic3r {
// GUI utility functions to produce hint messages from the current profile.
class PresetHints
{
public:
// Produce a textual description of the cooling logic of a currently active filament.
static std::string cooling_description(const Preset &preset);
// Produce a textual description of the maximum flow achived for the current configuration
// (the current printer, filament and print settigns).
// This description will be useful for getting a gut feeling for the maximum volumetric
// print speed achievable with the extruder.
static std::string maximum_volumetric_flow_description(const PresetBundle &preset_bundle);
};
} // namespace Slic3r
#endif /* slic3r_PresetHints_hpp_ */

View File

@ -54,6 +54,8 @@ extern "C" {
#ifdef _MSC_VER #ifdef _MSC_VER
// Undef some of the macros set by Perl <xsinit.h>, which cause compilation errors on Win32 // Undef some of the macros set by Perl <xsinit.h>, which cause compilation errors on Win32
#undef connect #undef connect
#undef link
#undef unlink
#undef seek #undef seek
#undef send #undef send
#undef write #undef write

View File

@ -244,9 +244,8 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo
{ {
use Cwd qw(abs_path); use Cwd qw(abs_path);
use File::Basename qw(dirname); use File::Basename qw(dirname);
my $class = Slic3r::Config->new;
my $path = abs_path($0); my $path = abs_path($0);
my $config = $class->_load(dirname($path)."/inc/22_config_bad_config_options.ini"); my $config = Slic3r::Config::load(dirname($path)."/inc/22_config_bad_config_options.ini");
ok 1, 'did not crash on reading invalid items in config'; ok 1, 'did not crash on reading invalid items in config';
} }

View File

@ -8,6 +8,11 @@
%name{Slic3r::Config} class DynamicPrintConfig { %name{Slic3r::Config} class DynamicPrintConfig {
DynamicPrintConfig(); DynamicPrintConfig();
~DynamicPrintConfig(); ~DynamicPrintConfig();
static DynamicPrintConfig* new_from_defaults();
static DynamicPrintConfig* new_from_defaults_keys(std::vector<std::string> keys);
DynamicPrintConfig* clone() %code{% RETVAL = new DynamicPrintConfig(*THIS); %};
DynamicPrintConfig* clone_only(std::vector<std::string> keys)
%code{% RETVAL = new DynamicPrintConfig(); RETVAL->apply_only(*THIS, keys, true); %};
bool has(t_config_option_key opt_key); bool has(t_config_option_key opt_key);
SV* as_hash() SV* as_hash()
%code{% RETVAL = ConfigBase__as_hash(THIS); %}; %code{% RETVAL = ConfigBase__as_hash(THIS); %};
@ -15,6 +20,13 @@
%code{% RETVAL = ConfigBase__get(THIS, opt_key); %}; %code{% RETVAL = ConfigBase__get(THIS, opt_key); %};
SV* get_at(t_config_option_key opt_key, int i) SV* get_at(t_config_option_key opt_key, int i)
%code{% RETVAL = ConfigBase__get_at(THIS, opt_key, i); %}; %code{% RETVAL = ConfigBase__get_at(THIS, opt_key, i); %};
SV* get_value(t_config_option_key opt_key)
%code{%
const ConfigOptionDef *def = THIS->def()->get(opt_key);
RETVAL = (def != nullptr && ! def->ratio_over.empty()) ?
newSVnv(THIS->get_abs_value(opt_key)) :
ConfigBase__get(THIS, opt_key);
%};
bool set(t_config_option_key opt_key, SV* value) bool set(t_config_option_key opt_key, SV* value)
%code{% RETVAL = ConfigBase__set(THIS, opt_key, value); %}; %code{% RETVAL = ConfigBase__set(THIS, opt_key, value); %};
bool set_deserialize(t_config_option_key opt_key, SV* str) bool set_deserialize(t_config_option_key opt_key, SV* str)
@ -38,16 +50,24 @@
void normalize(); void normalize();
%name{setenv} void setenv_(); %name{setenv} void setenv_();
double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %}; double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %};
%name{_load} void load(std::string file); static DynamicPrintConfig* load(char *path)
%name{_load_from_gcode} void load_from_gcode(std::string input_file)
%code%{ %code%{
auto config = new DynamicPrintConfig();
try { try {
THIS->load_from_gcode(input_file); config->load(path);
RETVAL = config;
} catch (std::exception& e) { } catch (std::exception& e) {
croak("Error extracting configuration from a g-code %s:\n%s\n", input_file.c_str(), e.what()); delete config;
croak("Error extracting configuration from %s:\n%s\n", path, e.what());
} }
%}; %};
void save(std::string file); void save(std::string file);
int validate() %code%{
std::string err = THIS->validate();
if (! err.empty())
croak("Configuration is not valid: %s\n", err.c_str());
RETVAL = 1;
%};
}; };
%name{Slic3r::Config::Static} class StaticPrintConfig { %name{Slic3r::Config::Static} class StaticPrintConfig {
@ -94,8 +114,18 @@
%}; %};
%name{setenv} void setenv_(); %name{setenv} void setenv_();
double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %}; double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %};
%name{_load} void load(std::string file); static StaticPrintConfig* load(char *path)
%name{_load_from_gcode} void load_from_gcode(std::string file); %code%{
auto config = new FullPrintConfig();
try {
config->load(path);
RETVAL = static_cast<PrintObjectConfig*>(config);
} catch (std::exception& e) {
delete config;
croak("Error extracting configuration from %s:\n%s\n", path, e.what());
}
%};
void save(std::string file); void save(std::string file);
}; };

View File

@ -46,18 +46,6 @@ _new_from_width(CLASS, role, width, nozzle_diameter, height, bridge_flow_ratio)
OUTPUT: OUTPUT:
RETVAL RETVAL
Flow*
_new_from_spacing(CLASS, spacing, nozzle_diameter, height, bridge)
char* CLASS;
float spacing;
float nozzle_diameter;
float height;
bool bridge;
CODE:
RETVAL = new Flow(Flow::new_from_spacing(spacing, nozzle_diameter, height, bridge));
OUTPUT:
RETVAL
%} %}
}; };

View File

@ -14,6 +14,9 @@ void disable_screensaver()
void enable_screensaver() void enable_screensaver()
%code{% Slic3r::GUI::enable_screensaver(); %}; %code{% Slic3r::GUI::enable_screensaver(); %};
std::vector<std::string> scan_serial_ports()
%code{% RETVAL=Slic3r::GUI::scan_serial_ports(); %};
bool debugged() bool debugged()
%code{% RETVAL=Slic3r::GUI::debugged(); %}; %code{% RETVAL=Slic3r::GUI::debugged(); %};

44
xs/xsp/GUI_AppConfig.xsp Normal file
View File

@ -0,0 +1,44 @@
%module{Slic3r::XS};
%{
#include <xsinit.h>
#include "slic3r/GUI/AppConfig.hpp"
%}
%name{Slic3r::GUI::AppConfig} class AppConfig {
AppConfig();
~AppConfig();
void reset();
void set_defaults();
void load()
%code%{
try {
THIS->load();
} catch (std::exception& e) {
croak("Loading an application config file failed:\n%s\n", e.what());
}
%};
void save()
%code%{
try {
THIS->save();
} catch (std::exception& e) {
croak("Saving an application config file failed:\n%s\n", e.what());
}
%};
bool exists();
bool dirty();
std::string get(char *name);
void set(char *name, char *value);
bool has(char *section);
std::string get_last_dir();
void update_config_dir(char *dir);
void update_skein_dir(char *dir);
std::string get_last_output_dir(const char *alt = "");
void update_last_output_dir(char *dir);
};

View File

@ -3,15 +3,18 @@
%{ %{
#include <xsinit.h> #include <xsinit.h>
#include "slic3r/GUI/Preset.hpp" #include "slic3r/GUI/Preset.hpp"
#include "slic3r/GUI/PresetBundle.hpp"
#include "slic3r/GUI/PresetHints.hpp"
%} %}
%name{Slic3r::GUI::Preset} class Preset { %name{Slic3r::GUI::Preset} class Preset {
// owned by PresetCollection, no constructor/destructor // owned by PresetCollection, no constructor/destructor
bool is_default() %code%{ RETVAL = THIS->is_default; %}; bool default() %code%{ RETVAL = THIS->is_default; %};
bool is_external() %code%{ RETVAL = THIS->is_external; %}; bool external() %code%{ RETVAL = THIS->is_external; %};
bool is_visible() %code%{ RETVAL = THIS->is_visible; %}; bool visible() %code%{ RETVAL = THIS->is_visible; %};
bool is_dirty() %code%{ RETVAL = THIS->is_dirty; %}; bool dirty() %code%{ RETVAL = THIS->is_dirty; %};
bool is_compatible_with_printer(char *active_printer) const;
const char* name() %code%{ RETVAL = THIS->name.c_str(); %}; const char* name() %code%{ RETVAL = THIS->name.c_str(); %};
const char* file() %code%{ RETVAL = THIS->file.c_str(); %}; const char* file() %code%{ RETVAL = THIS->file.c_str(); %};
@ -19,6 +22,8 @@
bool loaded() %code%{ RETVAL = THIS->loaded; %}; bool loaded() %code%{ RETVAL = THIS->loaded; %};
Ref<DynamicPrintConfig> config() %code%{ RETVAL = &THIS->config; %}; Ref<DynamicPrintConfig> config() %code%{ RETVAL = &THIS->config; %};
void set_num_extruders(int num_extruders);
}; };
%name{Slic3r::GUI::PresetCollection} class PresetCollection { %name{Slic3r::GUI::PresetCollection} class PresetCollection {
@ -27,6 +32,50 @@
Ref<Preset> default_preset() %code%{ RETVAL = &THIS->default_preset(); %}; Ref<Preset> default_preset() %code%{ RETVAL = &THIS->default_preset(); %};
size_t size() const; size_t size() const;
size_t num_visible() const; size_t num_visible() const;
std::string name() const;
Ref<Preset> get_selected_preset() %code%{ RETVAL = &THIS->get_selected_preset(); %};
Ref<Preset> get_current_preset() %code%{ RETVAL = &THIS->get_edited_preset(); %};
std::string get_current_preset_name() %code%{ RETVAL = THIS->get_selected_preset().name; %};
Ref<Preset> get_edited_preset() %code%{ RETVAL = &THIS->get_edited_preset(); %};
Ref<Preset> find_preset(char *name, bool first_visible_if_not_found = false) %code%{ RETVAL = THIS->find_preset(name, first_visible_if_not_found); %};
bool current_is_dirty();
std::vector<std::string> current_dirty_options();
void update_tab_ui(SV *ui, bool show_incompatible)
%code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object( aTHX_ ui, "Wx::BitmapComboBox" );
THIS->update_tab_ui(cb, show_incompatible); %};
void update_platter_ui(SV *ui)
%code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object( aTHX_ ui, "Wx::BitmapComboBox" );
THIS->update_platter_ui(cb); %};
bool update_dirty_ui(SV *ui)
%code%{ RETVAL = THIS->update_dirty_ui((wxBitmapComboBox*)wxPli_sv_2_object(aTHX_ ui, "Wx::BitmapComboBox")); %};
void select_preset(int idx);
bool select_preset_by_name(char *name) %code%{ RETVAL = THIS->select_preset_by_name(name, true); %};
void discard_current_changes();
void save_current_preset(char *new_name)
%code%{
try {
THIS->save_current_preset(new_name);
} catch (std::exception& e) {
croak("Error saving a preset %s:\n%s\n", new_name, e.what());
}
%};
void delete_current_preset()
%code%{
try {
THIS->delete_current_preset();
} catch (std::exception& e) {
croak("Error deleting a preset file %s:\n%s\n", THIS->get_selected_preset().file.c_str(), e.what());
}
%};
%{ %{
SV* SV*
@ -34,10 +83,9 @@ PresetCollection::arrayref()
CODE: CODE:
AV* av = newAV(); AV* av = newAV();
av_fill(av, THIS->size()-1); av_fill(av, THIS->size()-1);
int i = 0; for (int i = 0; i < int(THIS->size()); ++ i) {
for (size_t i = 0; i < THIS->size(); ++ i) {
Preset &preset = THIS->preset(i); Preset &preset = THIS->preset(i);
av_store(av, i++, perl_to_SV_ref(preset)); av_store(av, i, perl_to_SV_ref(preset));
} }
RETVAL = newRV_noinc((SV*)av); RETVAL = newRV_noinc((SV*)av);
OUTPUT: OUTPUT:
@ -46,15 +94,81 @@ PresetCollection::arrayref()
%} %}
}; };
%name{Slic3r::GUI::PresetBundle} class PresetBundle { %name{Slic3r::GUI::PresetBundle} class PresetBundle {
PresetBundle(); PresetBundle();
~PresetBundle(); ~PresetBundle();
void load_bitmaps(std::string path_bitmap_compatible, std::string path_bitmap_incompatible); void setup_directories()
void load_presets(std::string dir_path); %code%{
try {
THIS->setup_directories();
} catch (std::exception& e) {
croak("Cannot create configuration directories:\n%s\n", e.what());
}
%};
void load_presets(const char *dir_path)
%code%{
try {
THIS->load_presets(dir_path);
} catch (std::exception& e) {
croak("Loading of Slic3r presets from %s failed.\n\n%s\n", dir_path, e.what());
}
%};
void load_config_file(const char *path)
%code%{
try {
THIS->load_config_file(path);
} catch (std::exception& e) {
croak("Loading a configuration file %s failed:\n%s\n", path, e.what());
}
%};
size_t load_configbundle(const char *path)
%code%{
try {
RETVAL = THIS->load_configbundle(path);
} catch (std::exception& e) {
croak("Loading of a config bundle %s failed:\n%s\n", path, e.what());
}
%};
void export_configbundle(char *path)
%code%{
try {
THIS->export_configbundle(path);
} catch (std::exception& e) {
croak("Export of a config bundle %s failed:\n%s\n", path, e.what());
}
%};
Ref<PresetCollection> prints() %code%{ RETVAL = &THIS->prints; %}; void set_default_suppressed(bool default_suppressed);
Ref<PresetCollection> filaments() %code%{ RETVAL = &THIS->filaments; %};
Ref<PresetCollection> printers() %code%{ RETVAL = &THIS->printers; %}; void load_selections (AppConfig *config) %code%{ THIS->load_selections(*config); %};
void export_selections(AppConfig *config) %code%{ THIS->export_selections(*config); %};
void export_selections_pp(PlaceholderParser *pp) %code%{ THIS->export_selections(*pp); %};
Ref<PresetCollection> print() %code%{ RETVAL = &THIS->prints; %};
Ref<PresetCollection> filament() %code%{ RETVAL = &THIS->filaments; %};
Ref<PresetCollection> printer() %code%{ RETVAL = &THIS->printers; %};
bool has_defauls_only();
std::vector<std::string> filament_presets() %code%{ RETVAL = THIS->filament_presets; %};
void set_filament_preset(int idx, const char *name);
void update_multi_material_filament_presets();
void update_compatible_with_printer(bool select_other_if_incompatible);
Clone<DynamicPrintConfig> full_config() %code%{ RETVAL = THIS->full_config(); %};
void update_platter_filament_ui(int extruder_idx, SV *ui)
%code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object(aTHX_ ui, "Wx::BitmapComboBox");
THIS->update_platter_filament_ui(extruder_idx, cb); %};
};
%name{Slic3r::GUI::PresetHints} class PresetHints {
PresetHints();
~PresetHints();
static std::string cooling_description(Preset *preset)
%code%{ RETVAL = PresetHints::cooling_description(*preset); %};
static std::string maximum_volumetric_flow_description(PresetBundle *preset)
%code%{ RETVAL = PresetHints::maximum_volumetric_flow_description(*preset); %};
}; };

View File

@ -9,15 +9,10 @@
%name{Slic3r::GCode::PlaceholderParser} class PlaceholderParser { %name{Slic3r::GCode::PlaceholderParser} class PlaceholderParser {
PlaceholderParser(); PlaceholderParser();
~PlaceholderParser(); ~PlaceholderParser();
Clone<PlaceholderParser> clone()
%code{% RETVAL = THIS; %};
void update_timestamp();
void apply_env_variables();
void apply_config(DynamicPrintConfig *config) void apply_config(DynamicPrintConfig *config)
%code%{ THIS->apply_config(*config); %}; %code%{ THIS->apply_config(*config); %};
void set(std::string key, std::string value); void set(std::string key, int value);
%name{set_multiple} void set(std::string key, std::vector<std::string> values);
std::string process(std::string str) const std::string process(std::string str) const
%code%{ RETVAL = THIS->process(str, 0); %}; %code%{ RETVAL = THIS->process(str, 0); %};
}; };

View File

@ -215,8 +215,11 @@ _constant()
bool has_infinite_skirt(); bool has_infinite_skirt();
bool has_skirt(); bool has_skirt();
std::vector<unsigned int> extruders() const; std::vector<unsigned int> extruders() const;
std::string _validate() void validate() %code%{
%code%{ RETVAL = THIS->validate(); %}; std::string err = THIS->validate();
if (! err.empty())
throw std::invalid_argument(err.c_str());
%};
Clone<BoundingBox> bounding_box(); Clone<BoundingBox> bounding_box();
Clone<BoundingBox> total_bounding_box(); Clone<BoundingBox> total_bounding_box();
double skirt_first_layer_height(); double skirt_first_layer_height();

View File

@ -67,6 +67,26 @@ var(file_name)
RETVAL = Slic3r::var(file_name); RETVAL = Slic3r::var(file_name);
OUTPUT: RETVAL OUTPUT: RETVAL
void
set_data_dir(dir)
char *dir;
CODE:
Slic3r::set_data_dir(dir);
char*
data_dir()
CODE:
RETVAL = const_cast<char*>(Slic3r::data_dir().c_str());
OUTPUT: RETVAL
std::string
config_path(section, name)
const char *section;
const char *name;
CODE:
RETVAL = Slic3r::config_path(section, name);
OUTPUT: RETVAL
std::string std::string
encode_path(src) encode_path(src)
const char *src; const char *src;

View File

@ -210,6 +210,9 @@ PrintObjectSupportMaterial* O_OBJECT_SLIC3R
Ref<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T Ref<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T
Clone<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T Clone<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T
AppConfig* O_OBJECT_SLIC3R
Ref<AppConfig> O_OBJECT_SLIC3R_T
GLShader* O_OBJECT_SLIC3R GLShader* O_OBJECT_SLIC3R
Ref<GLShader> O_OBJECT_SLIC3R_T Ref<GLShader> O_OBJECT_SLIC3R_T
GLVolume* O_OBJECT_SLIC3R GLVolume* O_OBJECT_SLIC3R
@ -223,6 +226,8 @@ PresetCollection* O_OBJECT_SLIC3R
Ref<PresetCollection> O_OBJECT_SLIC3R_T Ref<PresetCollection> O_OBJECT_SLIC3R_T
PresetBundle* O_OBJECT_SLIC3R PresetBundle* O_OBJECT_SLIC3R
Ref<PresetBundle> O_OBJECT_SLIC3R_T Ref<PresetBundle> O_OBJECT_SLIC3R_T
PresetHints* O_OBJECT_SLIC3R
Ref<PresetHints> O_OBJECT_SLIC3R_T
Axis T_UV Axis T_UV
ExtrusionLoopRole T_UV ExtrusionLoopRole T_UV

View File

@ -191,6 +191,8 @@
%typemap{ModelInstancePtrs*}; %typemap{ModelInstancePtrs*};
%typemap{Ref<ModelInstancePtrs>}{simple}; %typemap{Ref<ModelInstancePtrs>}{simple};
%typemap{Clone<ModelInstancePtrs>}{simple}; %typemap{Clone<ModelInstancePtrs>}{simple};
%typemap{AppConfig*};
%typemap{Ref<AppConfig>}{simple};
%typemap{GLShader*}; %typemap{GLShader*};
%typemap{Ref<GLShader>}{simple}; %typemap{Ref<GLShader>}{simple};
%typemap{GLVolume*}; %typemap{GLVolume*};
@ -203,6 +205,8 @@
%typemap{Ref<PresetCollection>}{simple}; %typemap{Ref<PresetCollection>}{simple};
%typemap{PresetBundle*}; %typemap{PresetBundle*};
%typemap{Ref<PresetBundle>}{simple}; %typemap{Ref<PresetBundle>}{simple};
%typemap{PresetHints*};
%typemap{Ref<PresetHints>}{simple};
%typemap{PrintRegionPtrs*}; %typemap{PrintRegionPtrs*};
%typemap{PrintObjectPtrs*}; %typemap{PrintObjectPtrs*};