diff --git a/doc/updating/Updatig.md b/doc/updating/Updatig.md new file mode 100644 index 0000000000..3ce1f109c4 --- /dev/null +++ b/doc/updating/Updatig.md @@ -0,0 +1,52 @@ +# Slic3r PE 1.40 configuration update + +Slic3r PE 1.40.0 comes with a major re-work of the way configuration presets work. +There are three new features: + ++ A two-tier system of presets being divided into _System_ and _User_ groups ++ Configuration snapshots ++ Configuration updating from the internet + +## System and User presets + +- _System preset_: These are the presets that come with Slic3r PE installation. They come from a vendor configuration bundle (not individual files like before). They are **read-only** – a user cannot modify them, but may instead create a derived User preset based on a System preset +- _User preset_: These are regular presets stored in files just like before. Additionally, they may be derived (inherited) from one of the System presets + +A derived User preset keeps track of wich settings are inherited from the parent System preset and which are modified by the user. When a system preset is updated (either via installation of a new Slic3r or automatically from the internet), in a User preset the settings that are modified by the user will stay that way, while the ones that are inherited reflect the updated System preset. + +This system ensures that we don't overwrite user's settings when there is an update to the built in presets. + +Slic3r GUI now displays accurately which settings are inherited and which are modified. +A setting derived from a System preset is represeted by green label and a locked lock icon: + +![a system setting](setting_sys.png) + +A settings modified in a User preset has an open lock icon: + +![a user setting](setting_user.png) + +Clickign the open lock icon restored the system setting. + +Additionaly, any setting that is modified but not yet saved onto disk is represented by orange label and a back-arrow: + +![a modified setting](setting_mod.png) + +Clicking the back-arrow restores the value that was previously saved in this Preset. + +## Configuration snapshots + +Configuration snapshots can now be taken via the _Configuration_ menu. +A snapshot contains complete configuration from the point when the snapshot was taken. +Users may move back and forth between snapshots at will using a dialog: + +![snapshots dialog](snapshots_dialog.png) + + +# Updating from the internet + +Slic3r PE 1.40.0 checks for updates of the built-in System presets and downloads them. +The first-time configuration assistant will ask you if you want to enable this feature - it is **not** mandatory. + +Updates are checked for and downloaded in the background. If there's is an update, Slic3r will prompt about it +next time it is launched, never during normal program operation. An update may be either accepted or refused. +Before any update is applied a configuration snapshot (as described above) is taken. diff --git a/doc/updating/setting_mod.png b/doc/updating/setting_mod.png new file mode 100644 index 0000000000..e4d3b7e7bd Binary files /dev/null and b/doc/updating/setting_mod.png differ diff --git a/doc/updating/setting_sys.png b/doc/updating/setting_sys.png new file mode 100644 index 0000000000..842a8bf736 Binary files /dev/null and b/doc/updating/setting_sys.png differ diff --git a/doc/updating/setting_user.png b/doc/updating/setting_user.png new file mode 100644 index 0000000000..ffec5e0f3e Binary files /dev/null and b/doc/updating/setting_user.png differ diff --git a/doc/updating/snapshots_dialog.png b/doc/updating/snapshots_dialog.png new file mode 100644 index 0000000000..d4d2895505 Binary files /dev/null and b/doc/updating/snapshots_dialog.png differ diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 5e91f56ce6..0c6c81bb57 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -161,8 +161,13 @@ sub thread_cleanup { *Slic3r::Print::SupportMaterial2::DESTROY = sub {}; *Slic3r::TriangleMesh::DESTROY = sub {}; *Slic3r::GUI::AppConfig::DESTROY = sub {}; + *Slic3r::GUI::GCodePreviewData::DESTROY = sub {}; *Slic3r::GUI::PresetBundle::DESTROY = sub {}; *Slic3r::GUI::Tab::DESTROY = sub {}; + *Slic3r::GUI::PresetHints::DESTROY = sub {}; + *Slic3r::GUI::TabIface::DESTROY = sub {}; + *Slic3r::OctoPrint::DESTROY = sub {}; + *Slic3r::PresetUpdater::DESTROY = sub {}; return undef; # this prevents a "Scalars leaked" warning } @@ -278,5 +283,6 @@ sub system_info # this package declaration prevents an ugly fatal warning to be emitted when # spawning a new thread package GLUquadricObjPtr; +package Wx::Printout; 1; diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 4ec388c147..04dc803231 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -7,9 +7,7 @@ use File::Basename qw(basename); use FindBin; use List::Util qw(first); use Slic3r::GUI::2DBed; -use Slic3r::GUI::AboutDialog; use Slic3r::GUI::BedShapeDialog; -use Slic3r::GUI::ConfigWizard; use Slic3r::GUI::Controller; use Slic3r::GUI::Controller::ManualControlDialog; use Slic3r::GUI::Controller::PrinterPanel; @@ -70,6 +68,8 @@ our $grey = Wx::Colour->new(200,200,200); our $LANGUAGE_CHANGE_EVENT = Wx::NewEventType; # 2) To inform about a change of Preferences. our $PREFERENCES_EVENT = Wx::NewEventType; +# To inform AppConfig about Slic3r version available online +our $VERSION_ONLINE_EVENT = Wx::NewEventType; sub OnInit { my ($self) = @_; @@ -86,7 +86,9 @@ sub OnInit { Slic3r::GUI::set_wxapp($self); $self->{app_config} = Slic3r::GUI::AppConfig->new; + Slic3r::GUI::set_app_config($self->{app_config}); $self->{preset_bundle} = Slic3r::GUI::PresetBundle->new; + Slic3r::GUI::set_preset_bundle($self->{preset_bundle}); # 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 @@ -95,13 +97,24 @@ sub OnInit { warn $@ . "\n"; fatal_error(undef, $@); } - my $run_wizard = ! $self->{app_config}->exists; + my $app_conf_exists = $self->{app_config}->exists; # load settings - $self->{app_config}->load if ! $run_wizard; + $self->{app_config}->load if $app_conf_exists; $self->{app_config}->set('version', $Slic3r::VERSION); $self->{app_config}->save; - Slic3r::GUI::set_app_config($self->{app_config}); + $self->{preset_updater} = Slic3r::PresetUpdater->new($VERSION_ONLINE_EVENT); + Slic3r::GUI::set_preset_updater($self->{preset_updater}); + eval { + if (! $self->{preset_updater}->config_update()) { + exit 0; + } + }; + if ($@) { + warn $@ . "\n"; + fatal_error(undef, $@); + } + Slic3r::GUI::load_language(); # Suppress the '- default -' presets. @@ -111,11 +124,9 @@ sub OnInit { warn $@ . "\n"; show_error(undef, $@); } - $run_wizard = 1 if $self->{preset_bundle}->has_defauls_only; - Slic3r::GUI::set_preset_bundle($self->{preset_bundle}); - # application frame + print STDERR "Creating main frame...\n"; Wx::Image::FindHandlerType(wxBITMAP_TYPE_PNG) || Wx::Image::AddHandler(Wx::PNGHandler->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. @@ -126,7 +137,6 @@ sub OnInit { ); $self->SetTopWindow($frame); - #EVT_IDLE($frame, sub { EVT_IDLE($self->{mainframe}, sub { while (my $cb = shift @cb) { $cb->(); @@ -134,17 +144,19 @@ sub OnInit { $self->{app_config}->save if $self->{app_config}->dirty; }); - if ($run_wizard) { - # On OSX the UI was not initialized correctly if the wizard was called - # before the UI was up and running. - $self->CallAfter(sub { - # Run the config wizard, don't offer the "reset user profile" checkbox. - $self->{mainframe}->config_wizard(1); - }); - } + # On OSX the UI was not initialized correctly if the wizard was called + # before the UI was up and running. + $self->CallAfter(sub { + if (! Slic3r::GUI::config_wizard_startup($app_conf_exists)) { + # Only notify if there was not wizard so as not to bother too much ... + $self->{preset_updater}->slic3r_update_notify(); + } + $self->{preset_updater}->sync($self->{preset_bundle}); + }); # The following event is emited by the C++ menu implementation of application language change. EVT_COMMAND($self, -1, $LANGUAGE_CHANGE_EVENT, sub{ + print STDERR "LANGUAGE_CHANGE_EVENT\n"; $self->recreate_GUI; }); @@ -153,10 +165,19 @@ sub OnInit { $self->update_ui_from_settings; }); + # The following event is emited by PresetUpdater (C++) + EVT_COMMAND($self, -1, $VERSION_ONLINE_EVENT, sub { + my ($self, $event) = @_; + my $version = $event->GetString; + $self->{app_config}->set('version_online', $version); + $self->{app_config}->save; + }); + return 1; } sub recreate_GUI{ + print STDERR "recreate_GUI\n"; my ($self) = @_; my $topwindow = $self->GetTopWindow(); $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new( @@ -180,22 +201,12 @@ sub recreate_GUI{ $self->{app_config}->save if $self->{app_config}->dirty; }); - my $run_wizard = 1 if $self->{preset_bundle}->has_defauls_only; - if ($run_wizard) { - # On OSX the UI was not initialized correctly if the wizard was called - # before the UI was up and running. - $self->CallAfter(sub { - # Run the config wizard, don't offer the "reset user profile" checkbox. - $self->{mainframe}->config_wizard(1); - }); - } -} - -sub about { - my ($self) = @_; - my $about = Slic3r::GUI::AboutDialog->new(undef); - $about->ShowModal; - $about->Destroy; + # On OSX the UI was not initialized correctly if the wizard was called + # before the UI was up and running. + $self->CallAfter(sub { + # Run the config wizard, don't offer the "reset user profile" checkbox. + Slic3r::GUI::config_wizard_startup(1); + }); } sub system_info { @@ -238,7 +249,7 @@ sub catch_error { # static method accepting a wxWindow object as first parameter sub show_error { my ($parent, $message) = @_; - Wx::MessageDialog->new($parent, $message, 'Error', wxOK | wxICON_ERROR)->ShowModal; + Slic3r::GUI::show_error_id($parent ? $parent->GetId() : 0, $message); } # static method accepting a wxWindow object as first parameter diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 80fa337c40..6b8f16fe29 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -389,7 +389,7 @@ sub mouse_event { $self->_mouse_dragging($e->Dragging); - if ($e->Entering && &Wx::wxMSW) { + if ($e->Entering && (&Wx::wxMSW || $^O eq 'linux')) { # wxMSW needs focus in order to catch mouse wheel events $self->SetFocus; $self->_drag_start_xy(undef); @@ -687,7 +687,7 @@ sub select_view { } sub get_zoom_to_bounding_box_factor { - my ($self, $bb) = @_; + my ($self, $bb) = @_; my $max_bb_size = max(@{ $bb->size }); return undef if ($max_bb_size == 0); @@ -760,6 +760,8 @@ sub get_zoom_to_bounding_box_factor { $max_y = max($max_y, $margin_factor * 2 * abs($y_on_plane)); } + return undef if (($max_x == 0) || ($max_y == 0)); + my ($cw, $ch) = $self->GetSizeWH; my $min_ratio = min($cw / $max_x, $ch / $max_y); @@ -1112,7 +1114,7 @@ sub Resize { # is only a workaround for an incorrectly set camera. # This workaround harms Z-buffer accuracy! # my $depth = 1.05 * $self->max_bounding_box->radius(); - my $depth = max(@{ $self->max_bounding_box->size }); + my $depth = 5.0 * max(@{ $self->max_bounding_box->size }); glOrtho( -$x/2, $x/2, -$y/2, $y/2, -$depth, $depth, @@ -1150,6 +1152,12 @@ sub InitGL { $self->volumes->finalize_geometry(1) if ($^O eq 'linux' && $self->UseVBOs); + if (scalar @{$self->volumes} > 0) { + $self->zoom_to_volumes; + } else { + $self->zoom_to_bed; + } + glClearColor(0, 0, 0, 1); glColor3f(1, 0, 0); glEnable(GL_DEPTH_TEST); diff --git a/lib/Slic3r/GUI/AboutDialog.pm b/lib/Slic3r/GUI/AboutDialog.pm deleted file mode 100644 index 0879ea35b5..0000000000 --- a/lib/Slic3r/GUI/AboutDialog.pm +++ /dev/null @@ -1,122 +0,0 @@ -package Slic3r::GUI::AboutDialog; -use strict; -use warnings; -use utf8; - -use Wx qw(:font :html :misc :dialog :sizer :systemsettings :frame :id); -use Wx::Event qw(EVT_HTML_LINK_CLICKED EVT_LEFT_DOWN EVT_BUTTON); -use Wx::Print; -use Wx::Html; -use base 'Wx::Dialog'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 340], wxCAPTION); - - $self->SetBackgroundColour(Wx::wxWHITE); - my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); - $self->SetSizer($hsizer); - - # logo - my $logo = Slic3r::GUI::AboutDialog::Logo->new($self, -1, wxDefaultPosition, wxDefaultSize); - $logo->SetBackgroundColour(Wx::wxWHITE); - $hsizer->Add($logo, 0, wxEXPAND | wxLEFT | wxRIGHT, 30); - - my $vsizer = Wx::BoxSizer->new(wxVERTICAL); - $hsizer->Add($vsizer, 1, wxEXPAND, 0); - - # title - my $title = Wx::StaticText->new($self, -1, $Slic3r::FORK_NAME, wxDefaultPosition, wxDefaultSize); - my $title_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - $title_font->SetWeight(wxFONTWEIGHT_BOLD); - $title_font->SetFamily(wxFONTFAMILY_ROMAN); - $title_font->SetPointSize(24); - $title->SetFont($title_font); - $vsizer->Add($title, 0, wxALIGN_LEFT | wxTOP, 30); - - # version - my $version = Wx::StaticText->new($self, -1, "Version $Slic3r::VERSION", wxDefaultPosition, wxDefaultSize); - my $version_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - $version_font->SetPointSize(&Wx::wxMSW ? 9 : 11); - $version->SetFont($version_font); - $vsizer->Add($version, 0, wxALIGN_LEFT | wxBOTTOM, 10); - - # text - my $text = - '' . - '' . - '' . - 'Copyright © 2016 Vojtech Bubnik, Prusa Research.
' . - 'Copyright © 2011-2016 Alessandro Ranellucci.
' . - 'Slic3r is licensed under the ' . - 'GNU Affero General Public License, version 3.' . - '


' . - 'Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Y. Sapir, Mike Sheldrake and numerous others. ' . - 'Manual by Gary Hodgson. Inspired by the RepRap community.
' . - 'Slic3r logo designed by Corey Daniels, Silk Icon Set designed by Mark James. ' . - '
' . - '' . - ''; - my $html = Wx::HtmlWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER); - my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - my $size = &Wx::wxMSW ? 8 : 10; - $html->SetFonts($font->GetFaceName, $font->GetFaceName, [$size, $size, $size, $size, $size, $size, $size]); - $html->SetBorders(2); - $html->SetPage($text); - $vsizer->Add($html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 20); - EVT_HTML_LINK_CLICKED($self, $html, \&link_clicked); - - my $buttons = $self->CreateStdDialogButtonSizer(wxOK); - $self->SetEscapeId(wxID_CLOSE); - EVT_BUTTON($self, wxID_CLOSE, sub { - $self->EndModal(wxID_CLOSE); - $self->Close; - }); - $vsizer->Add($buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3); - - EVT_LEFT_DOWN($self, sub { $self->Close }); - EVT_LEFT_DOWN($logo, sub { $self->Close }); - - return $self; -} - -sub link_clicked { - my ($self, $event) = @_; - - Wx::LaunchDefaultBrowser($event->GetLinkInfo->GetHref); - $event->Skip(0); -} - -package Slic3r::GUI::AboutDialog::Logo; -use Wx qw(:bitmap :dc); -use Wx::Event qw(EVT_PAINT); -use base 'Wx::Panel'; - -sub new { - my $class = shift; - my $self = $class->SUPER::new(@_); - - $self->{logo} = Wx::Bitmap->new(Slic3r::var("Slic3r_192px.png"), wxBITMAP_TYPE_PNG); - $self->SetMinSize(Wx::Size->new($self->{logo}->GetWidth, $self->{logo}->GetHeight)); - - EVT_PAINT($self, \&repaint); - - return $self; -} - -sub repaint { - my ($self, $event) = @_; - - my $dc = Wx::PaintDC->new($self); - $dc->SetBackgroundMode(wxTRANSPARENT); - - my $size = $self->GetSize; - my $logo_w = $self->{logo}->GetWidth; - my $logo_h = $self->{logo}->GetHeight; - $dc->DrawBitmap($self->{logo}, ($size->GetWidth - $logo_w) / 2, ($size->GetHeight - $logo_h) / 2, 1); - - $event->Skip; -} - -1; diff --git a/lib/Slic3r/GUI/ConfigWizard.pm b/lib/Slic3r/GUI/ConfigWizard.pm deleted file mode 100644 index a32d345ed0..0000000000 --- a/lib/Slic3r/GUI/ConfigWizard.pm +++ /dev/null @@ -1,458 +0,0 @@ -# The config wizard is executed when the Slic3r is first started. -# The wizard helps the user to specify the 3D printer properties. - -package Slic3r::GUI::ConfigWizard; -use strict; -use warnings; -use utf8; - -use Wx; -use base 'Wx::Wizard'; - -# adhere to various human interface guidelines -our $wizard = 'Wizard'; -$wizard = 'Assistant' if &Wx::wxMAC || &Wx::wxGTK; - -sub new { - my ($class, $parent, $presets, $fresh_start) = @_; - my $self = $class->SUPER::new($parent, -1, "Configuration $wizard"); - - # initialize an empty repository - $self->{config} = Slic3r::Config->new; - - my $welcome_page = Slic3r::GUI::ConfigWizard::Page::Welcome->new($self, $fresh_start); - $self->add_page($welcome_page); - $self->add_page(Slic3r::GUI::ConfigWizard::Page::Firmware->new($self)); - $self->add_page(Slic3r::GUI::ConfigWizard::Page::Bed->new($self)); - $self->add_page(Slic3r::GUI::ConfigWizard::Page::Nozzle->new($self)); - $self->add_page(Slic3r::GUI::ConfigWizard::Page::Filament->new($self)); - $self->add_page(Slic3r::GUI::ConfigWizard::Page::Temperature->new($self)); - $self->add_page(Slic3r::GUI::ConfigWizard::Page::BedTemperature->new($self)); - $self->add_page(Slic3r::GUI::ConfigWizard::Page::Finished->new($self)); - - $_->build_index for @{$self->{pages}}; - - $welcome_page->set_selection_presets([@{$presets}, 'Other']); - - return $self; -} - -sub add_page { - my ($self, $page) = @_; - - my $n = push @{$self->{pages}}, $page; - # add first page to the page area sizer - $self->GetPageAreaSizer->Add($page) if $n == 1; - # link pages - $self->{pages}[$n-2]->set_next_page($page) if $n >= 2; - $page->set_previous_page($self->{pages}[$n-2]) if $n >= 2; -} - -sub run { - my ($self) = @_; - my $result; - if (Wx::Wizard::RunWizard($self, $self->{pages}[0])) { - my $preset_name = $self->{pages}[0]->{preset_name}; - $result = { - preset_name => $preset_name, - reset_user_profile => $self->{pages}[0]->{reset_user_profile} - }; - if ($preset_name eq 'Other') { - # it would be cleaner to have these defined inside each page class, - # in some event getting called before leaving the page - # set first_layer_height + layer_height based on nozzle_diameter - my $nozzle = $self->{config}->nozzle_diameter; - $self->{config}->set('first_layer_height', $nozzle->[0]); - $self->{config}->set('layer_height', $nozzle->[0] - 0.1); - - # set first_layer_temperature to temperature + 5 - $self->{config}->set('first_layer_temperature', [$self->{config}->temperature->[0] + 5]); - - # set first_layer_bed_temperature to temperature + 5 - $self->{config}->set('first_layer_bed_temperature', - [ ($self->{config}->bed_temperature->[0] > 0) ? ($self->{config}->bed_temperature->[0] + 5) : 0 ]); - $result->{config} = $self->{config}; - } - } - $self->Destroy; - return $result; -} - -package Slic3r::GUI::ConfigWizard::Index; -use Wx qw(:bitmap :dc :font :misc :sizer :systemsettings :window); -use Wx::Event qw(EVT_ERASE_BACKGROUND EVT_PAINT); -use base 'Wx::Panel'; - -sub new { - my $class = shift; - my ($parent, $title) = @_; - my $self = $class->SUPER::new($parent); - - push @{$self->{titles}}, $title; - $self->{own_index} = 0; - - $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}->{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->SetMinSize(Wx::Size->new($self->{background}->GetWidth, $self->{background}->GetHeight)); - - EVT_PAINT($self, \&repaint); - - return $self; -} - -sub repaint { - my ($self, $event) = @_; - my $size = $self->GetClientSize; - my $gap = 5; - - my $dc = Wx::PaintDC->new($self); - $dc->SetBackgroundMode(wxTRANSPARENT); - $dc->SetFont($self->GetFont); - $dc->SetTextForeground($self->GetForegroundColour); - - my $background_h = $self->{background}->GetHeight; - my $background_w = $self->{background}->GetWidth; - $dc->DrawBitmap($self->{background}, ($size->GetWidth - $background_w) / 2, ($size->GetHeight - $background_h) / 2, 1); - - my $label_h = $self->{bullets}->{own}->GetHeight; - $label_h = $dc->GetCharHeight if $dc->GetCharHeight > $label_h; - my $label_w = $size->GetWidth; - - my $i = 0; - foreach (@{$self->{titles}}) { - my $bullet = $self->{bullets}->{own}; - $bullet = $self->{bullets}->{before} if $i < $self->{own_index}; - $bullet = $self->{bullets}->{after} if $i > $self->{own_index}; - - $dc->SetTextForeground(Wx::Colour->new(128, 128, 128)) if $i > $self->{own_index}; - $dc->DrawLabel($_, $bullet, Wx::Rect->new(0, $i * ($label_h + $gap), $label_w, $label_h)); - # Only show the first bullet if this is the only wizard page to be displayed. - last if $i == 0 && $self->{just_welcome}; - $i++; - } - - $event->Skip; -} - -sub prepend_title { - my $self = shift; - my ($title) = @_; - - unshift @{$self->{titles}}, $title; - $self->{own_index}++; - $self->Refresh; -} - -sub append_title { - my $self = shift; - my ($title) = @_; - - push @{$self->{titles}}, $title; - $self->Refresh; -} - -package Slic3r::GUI::ConfigWizard::Page; -use Wx qw(:font :misc :sizer :staticline :systemsettings); -use base 'Wx::WizardPage'; - -sub new { - my $class = shift; - my ($parent, $title, $short_title) = @_; - my $self = $class->SUPER::new($parent); - - my $sizer = Wx::FlexGridSizer->new(0, 2, 10, 10); - $sizer->AddGrowableCol(1, 1); - $sizer->AddGrowableRow(1, 1); - $sizer->AddStretchSpacer(0); - $self->SetSizer($sizer); - - # title - my $text = Wx::StaticText->new($self, -1, $title, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - my $bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - $bold_font->SetWeight(wxFONTWEIGHT_BOLD); - $bold_font->SetPointSize(14); - $text->SetFont($bold_font); - $sizer->Add($text, 0, wxALIGN_LEFT, 0); - - # index - $self->{short_title} = $short_title ? $short_title : $title; - $self->{index} = Slic3r::GUI::ConfigWizard::Index->new($self, $self->{short_title}); - $sizer->Add($self->{index}, 1, wxEXPAND | wxTOP | wxRIGHT, 10); - - # contents - $self->{width} = 430; - $self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL); - $sizer->Add($self->{vsizer}, 1); - - return $self; -} - -sub append_text { - my $self = shift; - my ($text) = @_; - - my $para = Wx::StaticText->new($self, -1, $text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - $para->Wrap($self->{width}); - $para->SetMinSize([$self->{width}, -1]); - $self->{vsizer}->Add($para, 0, wxALIGN_LEFT | wxTOP | wxBOTTOM, 10); -} - -sub append_option { - my $self = shift; - my ($full_key) = @_; - - # populate repository with the factory default - my ($opt_key, $opt_index) = split /#/, $full_key, 2; - $self->config->apply(Slic3r::Config::new_from_defaults_keys([$opt_key])); - - # draw the control - my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( - parent => $self, - title => '', - config => $self->config, - full_labels => 1, - ); - $optgroup->append_single_option_line($opt_key, $opt_index); - $self->{vsizer}->Add($optgroup->sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); -} - -sub append_panel { - my ($self, $panel) = @_; - $self->{vsizer}->Add($panel, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); -} - -sub set_previous_page { - my $self = shift; - my ($previous_page) = @_; - $self->{previous_page} = $previous_page; -} - -sub GetPrev { - my $self = shift; - return $self->{previous_page}; -} - -sub set_next_page { - my $self = shift; - my ($next_page) = @_; - $self->{next_page} = $next_page; -} - -sub GetNext { - my $self = shift; - return $self->{next_page}; -} - -sub get_short_title { - my $self = shift; - return $self->{short_title}; -} - -sub build_index { - my $self = shift; - - my $page = $self; - $self->{index}->prepend_title($page->get_short_title) while ($page = $page->GetPrev); - $page = $self; - $self->{index}->append_title($page->get_short_title) while ($page = $page->GetNext); -} - -sub config { - my ($self) = @_; - return $self->GetParent->{config}; -} - -package Slic3r::GUI::ConfigWizard::Page::Welcome; -use base 'Slic3r::GUI::ConfigWizard::Page'; -use Wx qw(:misc :sizer wxID_FORWARD); -use Wx::Event qw(EVT_ACTIVATE EVT_CHOICE EVT_CHECKBOX); - -sub new { - my ($class, $parent, $fresh_start) = @_; - my $self = $class->SUPER::new($parent, "Welcome to the Slic3r Configuration $wizard", 'Welcome'); - $self->{full_wizard_workflow} = 1; - $self->{reset_user_profile} = 0; - - # Test for the existence of the old config path. - my $message_has_legacy; - { - my $datadir = Slic3r::data_dir; - if ($datadir =~ /Slic3rPE/) { - # Check for existence of the legacy Slic3r directory. - my $datadir_legacy = substr $datadir, 0, -2; - my $dir_enc = Slic3r::encode_path($datadir_legacy); - if (-e $dir_enc && -d $dir_enc && - -e ($dir_enc . '/print') && -d ($dir_enc . '/print') && - -e ($dir_enc . '/filament') && -d ($dir_enc . '/filament') && - -e ($dir_enc . '/printer') && -d ($dir_enc . '/printer') && - -e ($dir_enc . '/slic3r.ini')) { - $message_has_legacy = "Starting with Slic3r 1.38.4, the user profile directory has been renamed to $datadir. You may consider closing Slic3r and renaming $datadir_legacy to $datadir."; - } - } - } - - $self->append_text('Hello, welcome to Slic3r Prusa Edition! This '.lc($wizard).' helps you with the initial configuration; just a few settings and you will be ready to print.'); - $self->append_text('Please select your printer vendor and printer type. If your printer is not listed, you may try your luck and select a similar one. If you select "Other", this ' . lc($wizard) . ' will let you set the basic 3D printer parameters.'); - $self->append_text($message_has_legacy) if defined $message_has_legacy; - # To import an existing configuration instead, cancel this '.lc($wizard).' and use the Open Config menu item found in the File menu.'); - $self->append_text('If you received a configuration file or a config bundle from your 3D printer vendor, cancel this '.lc($wizard).' and use the "File->Load Config" or "File->Load Config Bundle" menu.'); - - $self->{choice} = my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize, []); - $self->{vsizer}->Add($choice, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); - if (! $fresh_start) { - $self->{reset_checkbox} = Wx::CheckBox->new($self, -1, "Reset user profile, install from scratch"); - $self->{vsizer}->Add($self->{reset_checkbox}, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); - } - - EVT_CHOICE($parent, $choice, sub { - my $sel = $self->{choice}->GetStringSelection; - $self->{preset_name} = $sel; - $self->set_full_wizard_workflow(($sel eq 'Other') || ($sel eq '')); - }); - - if (! $fresh_start) { - EVT_CHECKBOX($self, $self->{reset_checkbox}, sub { - $self->{reset_user_profile} = $self->{reset_checkbox}->GetValue(); - }); - } - - EVT_ACTIVATE($parent, sub { - $self->set_full_wizard_workflow($self->{preset_name} eq 'Other'); - }); - - return $self; -} - -sub set_full_wizard_workflow { - my ($self, $full_workflow) = @_; - $self->{full_wizard_workflow} = $full_workflow; - $self->{index}->{just_welcome} = !$full_workflow; - $self->{index}->Refresh; - my $next_button = $self->GetParent->FindWindow(wxID_FORWARD); - $next_button->SetLabel($full_workflow ? "&Next >" : "&Finish"); -} - -# Set the preset names, select the first item. -sub set_selection_presets { - my ($self, $names) = @_; - $self->{choice}->Append($names); - $self->{choice}->SetSelection(0); - $self->{preset_name} = $names->[0]; -} - -sub GetNext { - my $self = shift; - return $self->{full_wizard_workflow} ? $self->{next_page} : undef; -} - -package Slic3r::GUI::ConfigWizard::Page::Firmware; -use base 'Slic3r::GUI::ConfigWizard::Page'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, 'Firmware Type'); - - $self->append_text('Choose the type of firmware used by your printer, then click Next.'); - $self->append_option('gcode_flavor'); - - return $self; -} - -package Slic3r::GUI::ConfigWizard::Page::Bed; -use base 'Slic3r::GUI::ConfigWizard::Page'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, 'Bed Size'); - - $self->append_text('Set the shape of your printer\'s bed, then click Next.'); - - $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}->on_change(sub { - $self->config->set('bed_shape', $self->{bed_shape_panel}->GetValue); - }); - $self->append_panel($self->{bed_shape_panel}); - return $self; -} - -package Slic3r::GUI::ConfigWizard::Page::Nozzle; -use base 'Slic3r::GUI::ConfigWizard::Page'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, 'Nozzle Diameter'); - - $self->append_text('Enter the diameter of your printer\'s hot end nozzle, then click Next.'); - $self->append_option('nozzle_diameter#0'); - - return $self; -} - -package Slic3r::GUI::ConfigWizard::Page::Filament; -use base 'Slic3r::GUI::ConfigWizard::Page'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, 'Filament Diameter'); - - $self->append_text('Enter the diameter of your filament, then click Next.'); - $self->append_text('Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.'); - $self->append_option('filament_diameter#0'); - - return $self; -} - -package Slic3r::GUI::ConfigWizard::Page::Temperature; -use base 'Slic3r::GUI::ConfigWizard::Page'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, 'Extrusion Temperature'); - - $self->append_text('Enter the temperature needed for extruding your filament, then click Next.'); - $self->append_text('A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.'); - $self->append_option('temperature#0'); - - return $self; -} - -package Slic3r::GUI::ConfigWizard::Page::BedTemperature; -use base 'Slic3r::GUI::ConfigWizard::Page'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, 'Bed Temperature'); - - $self->append_text('Enter the bed temperature needed for getting your filament to stick to your heated bed, then click Next.'); - $self->append_text('A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.'); - $self->append_option('bed_temperature#0'); - - return $self; -} - -package Slic3r::GUI::ConfigWizard::Page::Finished; -use base 'Slic3r::GUI::ConfigWizard::Page'; - -sub new { - my $class = shift; - my ($parent) = @_; - my $self = $class->SUPER::new($parent, 'Congratulations!', 'Finish'); - - $self->append_text("You have successfully completed the Slic3r Configuration $wizard. " . - 'Slic3r is now configured for your printer and filament.'); - $self->append_text('To close this '.lc($wizard).' and apply the newly created configuration, click Finish.'); - - return $self; -} - -1; diff --git a/lib/Slic3r/GUI/Controller.pm b/lib/Slic3r/GUI/Controller.pm index 6aa7b34cb8..f7d90c7962 100644 --- a/lib/Slic3r/GUI/Controller.pm +++ b/lib/Slic3r/GUI/Controller.pm @@ -7,7 +7,7 @@ use strict; use warnings; 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 wxBORDER_NONE); use Wx::Event qw(EVT_CLOSE EVT_LEFT_DOWN EVT_MENU); use base qw(Wx::ScrolledWindow Class::Accessor); use List::Util qw(first); @@ -34,7 +34,7 @@ sub new { # 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), - wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); $btn->SetToolTipString("Add printer…") if $btn->can('SetToolTipString'); diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index b2f51b9e1d..fbcd34a3f6 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -81,7 +81,7 @@ sub new { # declare events EVT_CLOSE($self, sub { my (undef, $event) = @_; - if ($event->CanVeto && !$self->check_unsaved_changes) { + if ($event->CanVeto && !Slic3r::GUI::check_unsaved_changes) { $event->Veto; return; } @@ -95,7 +95,7 @@ sub new { }); $self->update_ui_from_settings; - + return $self; } @@ -149,6 +149,7 @@ sub _init_tabpanel { if (defined $presets){ my $reload_dependent_tabs = $tab->get_dependent_tabs; $self->{plater}->update_presets($tab_name, $reload_dependent_tabs, $presets); + $self->{plater}->{"selected_item_$tab_name"} = $tab->get_selected_preset_item; if ($tab_name eq 'printer') { # Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. for my $tab_name_other (qw(print filament)) { @@ -237,12 +238,6 @@ sub _init_menubar { $self->repair_stl; }, undef, 'wrench.png'); $fileMenu->AppendSeparator(); - # Cmd+, is standard on OS X - what about other operating systems? - $self->_append_menu_item($fileMenu, L("Preferences…\tCtrl+,"), L('Application preferences'), sub { - # Opening the C++ preferences dialog. - Slic3r::GUI::open_preferences_dialog($self->{preferences_event}); - }, wxID_PREFERENCES); - $fileMenu->AppendSeparator(); $self->_append_menu_item($fileMenu, L("&Quit"), L('Quit Slic3r'), sub { $self->Close(0); }, wxID_EXIT); @@ -319,11 +314,6 @@ sub _init_menubar { # Help menu my $helpMenu = Wx::Menu->new; { - $self->_append_menu_item($helpMenu, L("&Configuration ").$Slic3r::GUI::ConfigWizard::wizard."…", L("Run Configuration ").$Slic3r::GUI::ConfigWizard::wizard, sub { - # Run the config wizard, offer the "reset user profile" checkbox. - $self->config_wizard(0); - }); - $helpMenu->AppendSeparator(); $self->_append_menu_item($helpMenu, L("Prusa 3D Drivers"), L('Open the Prusa3D drivers download page in your browser'), sub { Wx::LaunchDefaultBrowser('http://www.prusa3d.com/drivers/'); }); @@ -348,7 +338,7 @@ sub _init_menubar { Wx::LaunchDefaultBrowser('http://github.com/prusa3d/slic3r/issues/new'); }); $self->_append_menu_item($helpMenu, L("&About Slic3r"), L('Show about dialog'), sub { - wxTheApp->about; + Slic3r::GUI::about; }); } @@ -362,11 +352,9 @@ sub _init_menubar { $menubar->Append($self->{object_menu}, L("&Object")) if $self->{object_menu}; $menubar->Append($windowMenu, L("&Window")); $menubar->Append($self->{viewMenu}, L("&View")) if $self->{viewMenu}; - # Add an optional debug menu - # (Select application language from the list of installed languages) - Slic3r::GUI::add_debug_menu($menubar, $self->{lang_ch_event}); + # Add a configuration menu. + Slic3r::GUI::add_config_menu($menubar, $self->{preferences_event}, $self->{lang_ch_event}); $menubar->Append($helpMenu, L("&Help")); - # Add an optional debug menu. In production code, the add_debug_menu() call should do nothing. $self->SetMenuBar($menubar); } } @@ -562,7 +550,7 @@ sub export_config { sub load_config_file { my ($self, $file) = @_; if (!$file) { - return unless $self->check_unsaved_changes; + return unless Slic3r::GUI::check_unsaved_changes; my $dlg = Wx::FileDialog->new($self, L('Select configuration to load:'), $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, "config.ini", @@ -581,7 +569,7 @@ sub load_config_file { sub export_configbundle { my ($self) = @_; - return unless $self->check_unsaved_changes; + return unless Slic3r::GUI::check_unsaved_changes; # validate current configuration in case it's dirty eval { wxTheApp->{preset_bundle}->full_config->validate; }; Slic3r::GUI::catch_error($self) and return; @@ -605,7 +593,7 @@ sub export_configbundle { # but that behavior was not documented and likely buggy. sub load_configbundle { my ($self, $file, $reset_user_profile) = @_; - return unless $self->check_unsaved_changes; + return unless Slic3r::GUI::check_unsaved_changes; if (!$file) { my $dlg = Wx::FileDialog->new($self, L('Select configuration to load:'), $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, @@ -639,70 +627,6 @@ sub load_config { $self->{plater}->on_config_change($config) if $self->{plater}; } -sub config_wizard { - my ($self, $fresh_start) = @_; - # Exit wizard if there are unsaved changes and the user cancels the action. - return unless $self->check_unsaved_changes; - # Enumerate the profiles bundled with the Slic3r installation under resources/profiles. - my $directory = Slic3r::resources_dir() . "/profiles"; - my @profiles = (); - if (opendir(DIR, Slic3r::encode_path($directory))) { - while (my $file = readdir(DIR)) { - if ($file =~ /\.ini$/) { - $file =~ s/\.ini$//; - push @profiles, Slic3r::decode_path($file); - } - } - closedir(DIR); - } - # Open the wizard. - if (my $result = Slic3r::GUI::ConfigWizard->new($self, \@profiles, $fresh_start)->run) { - eval { - if ($result->{reset_user_profile}) { - wxTheApp->{preset_bundle}->reset(1); - } - if (defined $result->{config}) { - # Load and save the settings into print, filament and printer presets. - wxTheApp->{preset_bundle}->load_config('My Settings', $result->{config}); - } else { - # Wizard returned a name of a preset bundle bundled with the installation. Unpack it. - wxTheApp->{preset_bundle}->install_vendor_configbundle($directory . '/' . $result->{preset_name} . '.ini'); - # Reset the print / filament / printer selections, so that following line will select some sensible defaults. - if ($fresh_start) { - wxTheApp->{app_config}->reset_selections; - } - # Reload all presets after the vendor config bundle has been installed. - wxTheApp->{preset_bundle}->load_presets(wxTheApp->{app_config}); - } - }; - Slic3r::GUI::catch_error($self) and return; - # Load the currently selected preset into the GUI, update the preset selection box. - foreach my $tab (values %{$self->{options_tabs}}) { - $tab->load_current_preset; - } - } -} - -# 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. -sub check_unsaved_changes { - my $self = shift; - - my @dirty = (); - foreach my $tab (values %{$self->{options_tabs}}) { - push @dirty, $tab->title if $tab->current_preset_is_dirty; - } - - if (@dirty) { - my $titles = join ', ', @dirty; - my $confirm = Wx::MessageDialog->new($self, L("You have unsaved changes ").($titles).L(". Discard changes and continue anyway?"), - L('Unsaved Presets'), wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); - return $confirm->ShowModal == wxID_YES; - } - - return 1; -} - sub select_tab { my ($self, $tab) = @_; $self->{tabpanel}->SetSelection($tab); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 7959741890..0c93444c88 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -486,7 +486,7 @@ sub new { my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; - $right_sizer->Add($frequently_changed_parameters_sizer, 0, wxEXPAND | wxTOP, 10) if defined $frequently_changed_parameters_sizer; + $right_sizer->Add($frequently_changed_parameters_sizer, 0, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); $right_sizer->Add($self->{list}, 1, wxEXPAND, 5); $right_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); @@ -514,6 +514,13 @@ sub new { $self->SetSizer($sizer); } + # Last correct selected item for each preset + { + $self->{selected_item_print} = 0; + $self->{selected_item_filament} = 0; + $self->{selected_item_printer} = 0; + } + $self->update_ui_from_settings(); return $self; @@ -538,9 +545,21 @@ sub _on_select_preset { # Only update the platter UI for the 2nd and other filaments. wxTheApp->{preset_bundle}->update_platter_filament_ui($idx, $choice); } else { + my $selected_item = $choice->GetSelection(); + return if ($selected_item == $self->{"selected_item_$group"}); + + my $selected_string = $choice->GetString($selected_item); + if ($selected_string eq "------- System presets -------" || + $selected_string eq "------- User presets -------"){ + $choice->SetSelection($self->{"selected_item_$group"}); + return; + } + # call GetSelection() in scalar context as it's context-aware - $self->{on_select_preset}->($group, $choice->GetStringSelection) - if $self->{on_select_preset}; +# $self->{on_select_preset}->($group, $choice->GetStringSelection) + $self->{on_select_preset}->($group, $selected_string) + if $self->{on_select_preset}; + $self->{"selected_item_$group"} = $selected_item; } # Synchronize config.ini with the current selections. wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config}); @@ -1786,22 +1805,24 @@ sub on_config_change { sub list_item_deselected { my ($self, $event) = @_; return if $PreventListEvents; + $self->{_lecursor} = Wx::BusyCursor->new(); if ($self->{list}->GetFirstSelected == -1) { $self->select_object(undef); $self->{canvas}->Refresh; - #FIXME VBOs are being refreshed just to change a selection color? - $self->{canvas3D}->reload_scene if $self->{canvas3D}; + $self->{canvas3D}->deselect_volumes if $self->{canvas3D}; } + undef $self->{_lecursor}; } sub list_item_selected { my ($self, $event) = @_; return if $PreventListEvents; + $self->{_lecursor} = Wx::BusyCursor->new(); my $obj_idx = $event->GetIndex; $self->select_object($obj_idx); $self->{canvas}->Refresh; - #FIXME VBOs are being refreshed just to change a selection color? - $self->{canvas3D}->reload_scene if $self->{canvas3D}; + $self->{canvas3D}->update_volumes_selection if $self->{canvas3D}; + undef $self->{_lecursor}; } sub list_item_activated { @@ -1935,7 +1956,8 @@ sub selection_changed { my ($self) = @_; my ($obj_idx, $object) = $self->selected_object; my $have_sel = defined $obj_idx; - + + $self->Freeze; if ($self->{htoolbar}) { # On OSX or Linux $self->{htoolbar}->EnableTool($_, $have_sel) @@ -1986,12 +2008,20 @@ sub selection_changed { # prepagate the event to the frame (a custom Wx event would be cleaner) $self->GetFrame->on_plater_selection_changed($have_sel); + $self->Thaw; } sub select_object { my ($self, $obj_idx) = @_; + + # remove current selection + foreach my $o (0..$#{$self->{objects}}) { + $PreventListEvents = 1; + $self->{objects}->[$o]->selected(0); + $self->{list}->Select($o, 0); + $PreventListEvents = 0; + } - $_->selected(0) for @{ $self->{objects} }; if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); # We use this flag to avoid circular event handling diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm index 54b9167735..2a518b1319 100644 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ b/lib/Slic3r/GUI/Plater/3D.pm @@ -31,7 +31,9 @@ sub new { $self->{on_select_object} = sub {}; $self->{on_instances_moved} = sub {}; $self->{on_wipe_tower_moved} = sub {}; - + + $self->{objects_volumes_idxs} = (); + $self->on_select(sub { my ($volume_idx) = @_; $self->{on_select_object}->(($volume_idx == -1) ? undef : $self->volumes->[$volume_idx]->object_idx) @@ -181,6 +183,17 @@ sub set_on_enable_action_buttons { $self->on_enable_action_buttons($cb); } +sub update_volumes_selection { + my ($self) = @_; + + foreach my $obj_idx (0..$#{$self->{model}->objects}) { + if ($self->{objects}[$obj_idx]->selected) { + my @volume_idxs = @{$self->{objects_volumes_idxs}[$obj_idx]}; + $self->select_volume($_) for @volume_idxs; + } + } +} + sub reload_scene { my ($self, $force) = @_; @@ -194,12 +207,14 @@ sub reload_scene { $self->{reload_delayed} = 0; + $self->{objects_volumes_idxs} = (); foreach my $obj_idx (0..$#{$self->{model}->objects}) { my @volume_idxs = $self->load_object($self->{model}, $self->{print}, $obj_idx); - if ($self->{objects}[$obj_idx]->selected) { - $self->select_volume($_) for @volume_idxs; - } + push(@{$self->{objects_volumes_idxs}}, \@volume_idxs); } + + $self->update_volumes_selection; + if (defined $self->{config}->nozzle_diameter) { # Should the wipe tower be visualized? my $extruders_count = scalar @{ $self->{config}->nozzle_diameter }; diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm index 7b5752cd2b..4d55e313a6 100644 --- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm @@ -116,7 +116,6 @@ sub new { $canvas->set_auto_bed_shape; $canvas->SetSize([500,500]); $canvas->SetMinSize($canvas->GetSize); - $canvas->zoom_to_volumes; } $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL); @@ -227,12 +226,14 @@ sub _update { push @objects, $self->{model_object}; } + my $z_cut = $z + $self->{model_object}->bounding_box->z_min; + # get section contour my @expolygons = (); foreach my $volume (@{$self->{model_object}->volumes}) { next if !$volume->mesh; next if $volume->modifier; - my $expp = $volume->mesh->slice([ $z + $volume->mesh->bounding_box->z_min ])->[0]; + my $expp = $volume->mesh->slice([ $z_cut ])->[0]; push @expolygons, @$expp; } foreach my $expolygon (@expolygons) { diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index f7e38ed873..28e3bf92b5 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -163,7 +163,6 @@ sub new { $canvas->set_auto_bed_shape; $canvas->SetSize([500,700]); $canvas->update_volumes_colors_by_extruder($self->GetParent->GetParent->GetParent->{config}); - $canvas->zoom_to_volumes; } $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL); diff --git a/resources/icons/Slic3r_192px_grayscale.png b/resources/icons/Slic3r_192px_grayscale.png new file mode 100644 index 0000000000..910f941870 Binary files /dev/null and b/resources/icons/Slic3r_192px_grayscale.png differ diff --git a/resources/icons/action_undo.png b/resources/icons/action_undo.png index 877f159868..06bb98b22a 100644 Binary files a/resources/icons/action_undo.png and b/resources/icons/action_undo.png differ diff --git a/resources/icons/action_undo_grey.png b/resources/icons/action_undo_grey.png new file mode 100644 index 0000000000..eaa1593e34 Binary files /dev/null and b/resources/icons/action_undo_grey.png differ diff --git a/resources/icons/printers/BarBaz_M1.png b/resources/icons/printers/BarBaz_M1.png new file mode 100644 index 0000000000..5924cc88b9 Binary files /dev/null and b/resources/icons/printers/BarBaz_M1.png differ diff --git a/resources/icons/printers/BarBaz_M2.png b/resources/icons/printers/BarBaz_M2.png new file mode 100644 index 0000000000..5924cc88b9 Binary files /dev/null and b/resources/icons/printers/BarBaz_M2.png differ diff --git a/resources/icons/printers/BarBaz_M3.png b/resources/icons/printers/BarBaz_M3.png new file mode 100644 index 0000000000..5924cc88b9 Binary files /dev/null and b/resources/icons/printers/BarBaz_M3.png differ diff --git a/resources/icons/printers/Foobar_M1.png b/resources/icons/printers/Foobar_M1.png new file mode 100644 index 0000000000..61a76a63d1 Binary files /dev/null and b/resources/icons/printers/Foobar_M1.png differ diff --git a/resources/icons/printers/Foobar_M2.png b/resources/icons/printers/Foobar_M2.png new file mode 100644 index 0000000000..61a76a63d1 Binary files /dev/null and b/resources/icons/printers/Foobar_M2.png differ diff --git a/resources/icons/printers/Foobar_M3.png b/resources/icons/printers/Foobar_M3.png new file mode 100644 index 0000000000..61a76a63d1 Binary files /dev/null and b/resources/icons/printers/Foobar_M3.png differ diff --git a/resources/icons/printers/PrusaResearch_MK2S.png b/resources/icons/printers/PrusaResearch_MK2S.png new file mode 100644 index 0000000000..925447cf20 Binary files /dev/null and b/resources/icons/printers/PrusaResearch_MK2S.png differ diff --git a/resources/icons/printers/PrusaResearch_MK2SMM.png b/resources/icons/printers/PrusaResearch_MK2SMM.png new file mode 100644 index 0000000000..d6ff161259 Binary files /dev/null and b/resources/icons/printers/PrusaResearch_MK2SMM.png differ diff --git a/resources/icons/printers/PrusaResearch_MK3.png b/resources/icons/printers/PrusaResearch_MK3.png new file mode 100644 index 0000000000..5279ba01e0 Binary files /dev/null and b/resources/icons/printers/PrusaResearch_MK3.png differ diff --git a/resources/icons/question_mark_01.png b/resources/icons/question_mark_01.png new file mode 100644 index 0000000000..25814a61d4 Binary files /dev/null and b/resources/icons/question_mark_01.png differ diff --git a/resources/icons/sys_unlock_grey.png b/resources/icons/sys_unlock_grey.png new file mode 100644 index 0000000000..0dedf4deef Binary files /dev/null and b/resources/icons/sys_unlock_grey.png differ diff --git a/resources/profiles/BarBaz.ini b/resources/profiles/BarBaz.ini new file mode 100644 index 0000000000..ed2686cdc4 --- /dev/null +++ b/resources/profiles/BarBaz.ini @@ -0,0 +1,985 @@ +# Print profiles for the BarBaz Research printers. + +[vendor] +# Vendor name will be shown by the Config Wizard. +name = Bar Baz +# Configuration version of this file. Config file will only be installed, if the config_version differs. +# This means, the server may force the Slic3r configuration to be downgraded. +config_version = 0.1.0 +# Where to get the updates from? +config_update_url = https://example.com + +# The printer models will be shown by the Configuration Wizard in this order, +# also the first model installed & the first nozzle installed will be activated after install. +#TODO: One day we may differentiate variants of the nozzles / hot ends, +#for example by the melt zone size, or whether the nozzle is hardened. +[printer_model:M1] +name = Bar Baz Model 1 +variants = 0.4; 0.25; 0.6 + +[printer_model:M2] +name = Bar Baz Model 2 +variants = 0.4; 0.25; 0.6 + +[printer_model:M3] +# Printer model name will be shown by the installation wizard. +name = Bar Baz Model 3 +variants = 0.4; 0.6 + +# All presets starting with asterisk, for example *common*, are intermediate and they will +# not make it into the user interface. + +# Common print preset, mostly derived from MK2 single material with a 0.4mm nozzle. +# All other print presets will derive from the *common* print preset. +[print:*common*] +avoid_crossing_perimeters = 0 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeters_first = 0 +external_perimeter_extrusion_width = 0.45 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +interface_shells = 0 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +overhangs = 0 +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +perimeters = 2 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirts = 1 +skirt_distance = 2 +skirt_height = 3 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_extruder = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_enforce_layers = 0 +support_material_contact_distance = 0.15 +support_material_interface_contact_loops = 0 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 40 +travel_speed = 180 +wipe_tower = 0 +wipe_tower_per_color_wipe = 20 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +# Print parameters common to a 0.25mm diameter nozzle. +[print:*0.25nozzle*] +external_perimeter_extrusion_width = 0.25 +extrusion_width = 0.25 +first_layer_extrusion_width = 0.25 +infill_extrusion_width = 0.25 +perimeter_extrusion_width = 0.25 +solid_infill_extrusion_width = 0.25 +top_infill_extrusion_width = 0.25 +support_material_extrusion_width = 0.18 +support_material_interface_layers = 0 +support_material_interface_spacing = 0.15 +support_material_spacing = 1 +support_material_xy_spacing = 150% + +# Print parameters common to a 0.6mm diameter nozzle. +[print:*0.6nozzle*] +external_perimeter_extrusion_width = 0.61 +extrusion_width = 0.67 +first_layer_extrusion_width = 0.65 +infill_extrusion_width = 0.7 +perimeter_extrusion_width = 0.65 +solid_infill_extrusion_width = 0.65 +top_infill_extrusion_width = 0.6 + +[print:*soluble_support*] +overhangs = 1 +skirts = 0 +support_material = 1 +support_material_contact_distance = 0 +support_material_extruder = 4 +support_material_extrusion_width = 0.45 +support_material_interface_extruder = 4 +support_material_interface_spacing = 0.1 +support_material_synchronize_layers = 1 +support_material_threshold = 80 +support_material_with_sheath = 1 +wipe_tower = 1 + +[print:*0.05mm*] +inherits = *common* +bottom_solid_layers = 10 +bridge_acceleration = 300 +bridge_flow_ratio = 0.7 +default_acceleration = 500 +external_perimeter_speed = 20 +fill_density = 20% +first_layer_acceleration = 500 +gap_fill_speed = 20 +infill_acceleration = 800 +infill_speed = 30 +max_print_speed = 80 +small_perimeter_speed = 15 +solid_infill_speed = 30 +support_material_extrusion_width = 0.3 +support_material_spacing = 1.5 +layer_height = 0.05 +perimeter_acceleration = 300 +perimeter_speed = 30 +perimeters = 3 +support_material_speed = 30 +top_solid_infill_speed = 20 +top_solid_layers = 15 + +[print:0.05mm ULTRADETAIL] +inherits = *0.05mm* +infill_extrusion_width = 0.5 + +[print:0.05mm ULTRADETAIL MK3] +inherits = *0.05mm* +fill_pattern = grid +top_infill_extrusion_width = 0.4 + +[print:0.05mm ULTRADETAIL 0.25 nozzle] +inherits = *0.05mm* +external_perimeter_extrusion_width = 0 +extrusion_width = 0.28 +fill_density = 20% +first_layer_extrusion_width = 0.3 +infill_extrusion_width = 0 +infill_speed = 20 +max_print_speed = 100 +perimeter_extrusion_width = 0 +perimeter_speed = 20 +small_perimeter_speed = 10 +solid_infill_extrusion_width = 0 +solid_infill_speed = 20 +support_material_speed = 20 +top_infill_extrusion_width = 0 + +[print:0.05mm ULTRADETAIL 0.25 nozzle MK3] +inherits = *0.05mm*; *0.25nozzle* +fill_pattern = grid +top_infill_extrusion_width = 0.4 + +[print:*0.10mm*] +inherits = *common* +bottom_solid_layers = 7 +bridge_flow_ratio = 0.7 +layer_height = 0.1 +perimeter_acceleration = 800 +top_solid_layers = 9 + +[print:0.10mm DETAIL] +inherits = *0.10mm* +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +perimeter_speed = 50 +solid_infill_speed = 50 + +[print:0.10mm DETAIL MK3] +inherits = *0.10mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 170 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 + +[print:0.10mm DETAIL 0.25 nozzle] +inherits = *0.10mm* +bridge_acceleration = 600 +external_perimeter_speed = 20 +infill_acceleration = 1600 +infill_speed = 40 +perimeter_acceleration = 600 +perimeter_speed = 25 +small_perimeter_speed = 10 +solid_infill_speed = 40 +top_solid_infill_speed = 30 + +[print:0.10mm DETAIL 0.25 nozzle MK3] +inherits = *0.10mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 170 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 + +[print:0.10mm DETAIL 0.6 nozzle MK3] +inherits = *0.10mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 170 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 + +[print:*0.15mm*] +inherits = *common* +bottom_solid_layers = 5 +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +layer_height = 0.15 +perimeter_acceleration = 800 +perimeter_speed = 50 +solid_infill_speed = 50 +top_infill_extrusion_width = 0.4 +top_solid_layers = 7 + +[print:0.15mm 100mms Linear Advance] +inherits = *0.15mm* +bridge_flow_ratio = 0.95 +external_perimeter_speed = 50 +infill_speed = 100 +max_print_speed = 150 +perimeter_speed = 60 +small_perimeter_speed = 30 +solid_infill_speed = 100 +support_material_speed = 60 +top_solid_infill_speed = 70 + +[print:0.15mm OPTIMAL] +inherits = *0.15mm* +top_infill_extrusion_width = 0.45 + +[print:0.15mm OPTIMAL 0.25 nozzle] +inherits = *0.15mm*; *0.25nozzle* +bridge_acceleration = 600 +bridge_flow_ratio = 0.7 +external_perimeter_speed = 20 +infill_acceleration = 1600 +infill_speed = 40 +perimeter_acceleration = 600 +perimeter_speed = 25 +small_perimeter_speed = 10 +solid_infill_speed = 40 +support_material_extrusion_width = 0.2 +top_solid_infill_speed = 30 + +[print:0.15mm OPTIMAL 0.6 nozzle] +inherits = *0.15mm*; *0.6nozzle* + +[print:0.15mm OPTIMAL MK3] +inherits = *0.15mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:0.15mm OPTIMAL SOLUBLE FULL] +inherits = *0.15mm*; *soluble_support* +external_perimeter_speed = 25 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +perimeter_speed = 40 +solid_infill_speed = 40 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 +wipe_tower = 1 + +[print:0.15mm OPTIMAL SOLUBLE INTERFACE] +inherits = 0.15mm OPTIMAL SOLUBLE FULL +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 +support_material_xy_spacing = 80% + +[print:0.15mm OPTIMAL 0.25 nozzle MK3] +inherits = *0.15mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 +[print:*0.20mm*] +inherits = *common* +bottom_solid_layers = 4 +bridge_flow_ratio = 0.95 +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +layer_height = 0.2 +perimeter_acceleration = 800 +perimeter_speed = 50 +solid_infill_speed = 50 +top_infill_extrusion_width = 0.4 +top_solid_layers = 5 + +[print:0.15mm OPTIMAL 0.6 nozzle MK3] +inherits = *0.15mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:0.20mm 100mms Linear Advance] +inherits = *0.20mm* +external_perimeter_speed = 50 +infill_speed = 100 +max_print_speed = 150 +perimeter_speed = 60 +small_perimeter_speed = 30 +solid_infill_speed = 100 +support_material_speed = 60 +top_solid_infill_speed = 70 + +[print:0.20mm FAST MK3] +inherits = *0.20mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:0.20mm NORMAL] +inherits = *0.20mm* + +[print:0.20mm NORMAL 0.6 nozzle] +inherits = *0.20mm*; *0.6nozzle* + +[print:0.20mm NORMAL SOLUBLE FULL] +inherits = *0.20mm*; *soluble_support* +external_perimeter_speed = 30 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +perimeter_speed = 40 +solid_infill_speed = 40 +top_solid_infill_speed = 30 + +[print:0.20mm NORMAL SOLUBLE INTERFACE] +inherits = 0.20mm NORMAL SOLUBLE FULL +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 +support_material_xy_spacing = 80% + +[print:0.20mm FAST 0.6 nozzle MK3] +inherits = *0.20mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:*0.35mm*] +inherits = *common* +bottom_solid_layers = 3 +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 40 +first_layer_extrusion_width = 0.75 +infill_acceleration = 2000 +infill_speed = 60 +layer_height = 0.35 +perimeter_acceleration = 800 +perimeter_extrusion_width = 0.65 +perimeter_speed = 50 +solid_infill_extrusion_width = 0.65 +solid_infill_speed = 60 +top_solid_infill_speed = 50 +top_solid_layers = 4 + +[print:0.35mm FAST] +inherits = *0.35mm* +bridge_flow_ratio = 0.95 +first_layer_extrusion_width = 0.42 +perimeter_extrusion_width = 0.43 +solid_infill_extrusion_width = 0.7 +top_infill_extrusion_width = 0.43 + +[print:0.35mm FAST 0.6 nozzle] +inherits = *0.35mm*; *0.6nozzle* + +[print:0.35mm FAST sol full 0.6 nozzle] +inherits = *0.35mm*; *0.6nozzle*; *soluble_support* +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 30 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +perimeter_speed = 40 +support_material_extrusion_width = 0.55 +support_material_interface_layers = 3 +support_material_xy_spacing = 120% +top_infill_extrusion_width = 0.57 + +[print:0.35mm FAST sol int 0.6 nozzle] +inherits = 0.35mm FAST sol full 0.6 nozzle +support_material_extruder = 0 +support_material_interface_layers = 2 +support_material_with_sheath = 0 +support_material_xy_spacing = 150% + +[filament:*common*] +cooling = 1 +compatible_printers = +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +min_print_speed = 5 +slowdown_below_layer_time = 20 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" + +[filament:*PLA*] +inherits = *common* +bed_temperature = 60 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #FF3232 +filament_max_volumetric_speed = 15 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 210 + +[filament:*PET*] +inherits = *common* +bed_temperature = 90 +bridge_fan_speed = 50 +disable_fan_first_layers = 3 +fan_always_on = 1 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_max_volumetric_speed = 8 +filament_type = PET +first_layer_bed_temperature = 85 +first_layer_temperature = 230 +max_fan_speed = 50 +min_fan_speed = 30 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" +temperature = 240 + +[filament:*ABS*] +inherits = *common* +bed_temperature = 110 +bridge_fan_speed = 30 +cooling = 0 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #3A80CA +filament_max_volumetric_speed = 11 +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 30 +min_fan_speed = 20 +temperature = 255 + +[filament:*FLEX*] +inherits = *common* +bridge_fan_speed = 100 +cooling = 0 +disable_fan_first_layers = 1 +extrusion_multiplier = 1.2 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #00CA0A +filament_max_volumetric_speed = 1.5 +filament_type = FLEX +first_layer_bed_temperature = 50 +first_layer_temperature = 240 +max_fan_speed = 90 +min_fan_speed = 70 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 240 + +[filament:ColorFabb Brass Bronze] +inherits = *PLA* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 10 + +[filament:ColorFabb HT] +inherits = *PET* +bed_temperature = 110 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 10 +first_layer_bed_temperature = 105 +first_layer_temperature = 270 +max_fan_speed = 20 +min_fan_speed = 10 +min_print_speed = 5 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" +temperature = 270 + +[filament:ColorFabb PLA-PHA] +inherits = *PLA* + +[filament:ColorFabb Woodfil] +inherits = *PLA* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 10 +first_layer_temperature = 200 +min_print_speed = 5 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 200 + +[filament:ColorFabb XT] +inherits = *PET* +filament_type = PLA +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 270 + +[filament:ColorFabb XT-CF20] +inherits = *PET* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 1 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" +temperature = 260 + +[filament:ColorFabb nGen] +inherits = *PET* +bridge_fan_speed = 40 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_type = NGEN +first_layer_temperature = 240 +max_fan_speed = 35 +min_fan_speed = 20 + +[filament:ColorFabb nGen flex] +inherits = *FLEX* +bed_temperature = 85 +bridge_fan_speed = 40 +cooling = 1 +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +fan_below_layer_time = 10 +filament_max_volumetric_speed = 5 +first_layer_bed_temperature = 85 +first_layer_temperature = 260 +max_fan_speed = 35 +min_fan_speed = 20 +temperature = 260 + +[filament:E3D Edge] +inherits = *PET* +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" + +[filament:E3D PC-ABS] +inherits = *ABS* +first_layer_temperature = 270 +temperature = 270 + +[filament:Fillamentum ABS] +inherits = *ABS* +first_layer_temperature = 240 +temperature = 240 + +[filament:Fillamentum ASA] +inherits = *ABS* +fan_always_on = 1 +first_layer_temperature = 265 +temperature = 265 + +[filament:Fillamentum CPE HG100 HM100] +inherits = *PET* +filament_notes = "CPE HG100 , CPE HM100" +first_layer_bed_temperature = 90 +first_layer_temperature = 275 +max_fan_speed = 50 +min_fan_speed = 50 +temperature = 275 + +[filament:Fillamentum Timberfil] +inherits = *PLA* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 10 +first_layer_temperature = 190 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 190 + +[filament:Generic ABS] +inherits = *ABS* +filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladeč ABS" + +[filament:Generic PET] +inherits = *PET* +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" + +[filament:Generic PLA] +inherits = *PLA* +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" + +[filament:Polymaker PC-Max] +inherits = *ABS* +bed_temperature = 115 +filament_colour = #3A80CA +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +temperature = 270 + +[filament:Primavalue PVA] +inherits = *PLA* +cooling = 0 +fan_always_on = 0 +filament_colour = #FFFFD7 +filament_max_volumetric_speed = 10 +filament_notes = "List of materials tested with standart PVA print settings for MK2:\n\nPrimaSelect PVA+\nICE FILAMENTS PVA 'NAUGHTY NATURAL'\nVerbatim BVOH" +filament_soluble = 1 +filament_type = PVA +first_layer_temperature = 195 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 195 + +[filament:BarBaz ABS] +inherits = *ABS* +filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladeč ABS" + +[filament:BarBaz HIPS] +inherits = *ABS* +bridge_fan_speed = 50 +cooling = 1 +extrusion_multiplier = 0.9 +fan_always_on = 1 +fan_below_layer_time = 10 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = HIPS +first_layer_temperature = 220 +max_fan_speed = 20 +min_fan_speed = 20 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 220 + +[filament:BarBaz PET] +inherits = *PET* +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" + +[filament:BarBaz PLA] +inherits = *PLA* +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" + +[filament:SemiFlex or Flexfill 98A] +inherits = *FLEX* + +[filament:Taulman Bridge] +inherits = *common* +bed_temperature = 90 +bridge_fan_speed = 40 +cooling = 0 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 10 +filament_soluble = 0 +filament_type = PET +first_layer_bed_temperature = 60 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 +min_print_speed = 5 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 250 + +[filament:Taulman T-Glase] +inherits = *PET* +bridge_fan_speed = 40 +cooling = 0 +fan_always_on = 0 +first_layer_bed_temperature = 90 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" + +[filament:Verbatim BVOH] +inherits = *common* +bed_temperature = 60 +bridge_fan_speed = 100 +cooling = 0 +disable_fan_first_layers = 1 +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #FFFFD7 +filament_max_volumetric_speed = 10 +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +filament_soluble = 1 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +min_print_speed = 15 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 210 + +[filament:Verbatim PP] +inherits = *common* +bed_temperature = 100 +bridge_fan_speed = 100 +cooling = 1 +disable_fan_first_layers = 2 +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 5 +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nEUMAKERS PLA" +filament_type = PLA +first_layer_bed_temperature = 100 +first_layer_temperature = 220 +max_fan_speed = 100 +min_fan_speed = 100 +min_print_speed = 15 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 220 + +[printer:*common*] +bed_shape = 0x0,250x0,250x210,0x210 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +between_objects_gcode = +deretract_speed = 0 +end_gcode = G4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +extruder_colour = #FFFF00 +extruder_offset = 0x0 +gcode_flavor = marlin +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.25 +min_layer_height = 0.07 +nozzle_diameter = 0.4 +octoprint_apikey = +octoprint_host = +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_BarBaz3D\nPRINTER_MODEL_MK2\n +printer_settings_id = +retract_before_travel = 1 +retract_before_wipe = 0% +retract_layer_change = 1 +retract_length = 0.8 +retract_length_toolchange = 4 +retract_lift = 0.6 +retract_lift_above = 0 +retract_lift_below = 199 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 35 +serial_port = +serial_speed = 250000 +single_extruder_multi_material = 0 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 1 +wipe = 1 +z_offset = 0 +printer_model = M2 +printer_variant = 0.4 +default_print_profile = 0.15mm OPTIMAL +default_filament_profile = BarBaz PLA + +[printer:*multimaterial*] +inherits = *common* +deretract_speed = 50 +retract_before_travel = 3 +retract_before_wipe = 60% +retract_layer_change = 0 +retract_length = 4 +retract_lift = 0.6 +retract_lift_above = 0 +retract_lift_below = 199 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 80 +single_extruder_multi_material = 1 +printer_model = M3 + +[printer:*mm-single*] +inherits = *multimaterial* +end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_BarBaz3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 + +[printer:*mm-multi*] +inherits = *multimaterial* +end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes.\nG1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\n{endif}\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors +extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 +nozzle_diameter = 0.4,0.4,0.4,0.4 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_BarBaz3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 +variable_layer_height = 0 + +[printer:BarBaz i3 MK2] +inherits = *common* + +[printer:BarBaz i3 MK2 0.25 nozzle] +inherits = *common* +max_layer_height = 0.1 +min_layer_height = 0.05 +nozzle_diameter = 0.25 +retract_length = 1 +retract_speed = 50 +variable_layer_height = 0 +printer_variant = 0.25 +default_print_profile = 0.10mm DETAIL 0.25 nozzle + +[printer:BarBaz i3 MK2 0.6 nozzle] +inherits = *common* +max_layer_height = 0.35 +min_layer_height = 0.1 +nozzle_diameter = 0.6 +printer_variant = 0.6 + +[printer:BarBaz i3 MK2 MM Single Mode] +inherits = *mm-single* + +[printer:BarBaz i3 MK2 MM Single Mode 0.6 nozzle] +inherits = *mm-single* +nozzle_diameter = 0.6 +printer_variant = 0.6 + +[printer:BarBaz i3 MK2 MultiMaterial] +inherits = *mm-multi* +nozzle_diameter = 0.4,0.4,0.4,0.4 + +[printer:BarBaz i3 MK2 MultiMaterial 0.6 nozzle] +inherits = *mm-multi* +nozzle_diameter = 0.6,0.6,0.6,0.6 +printer_variant = 0.6 + +[printer:BarBaz i3 MK3] +inherits = *common* +end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_BarBaz3D\nPRINTER_MODEL_MK3\n +retract_lift_below = 209 +start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +printer_model = M1 +default_print_profile = 0.15mm OPTIMAL MK3 + +[printer:BarBaz i3 MK3 0.25 nozzle] +inherits = *common* +nozzle_diameter = 0.25 +printer_variant = 0.25 +end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_BarBaz3D\nPRINTER_MODEL_MK3\n +retract_lift_below = 209 +start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +printer_model = M1 +default_print_profile = 0.10mm DETAIL MK3 + +[printer:BarBaz i3 MK3 0.6 nozzle] +inherits = *common* +nozzle_diameter = 0.6 +printer_variant = 0.6 +end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_BarBaz3D\nPRINTER_MODEL_MK3\n +retract_lift_below = 209 +start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +printer_model = M1 +default_print_profile = 0.15mm OPTIMAL MK3 diff --git a/resources/profiles/Foobar.ini b/resources/profiles/Foobar.ini new file mode 100644 index 0000000000..6f31401ea7 --- /dev/null +++ b/resources/profiles/Foobar.ini @@ -0,0 +1,985 @@ +# Print profiles for the Foobar Research printers. + +[vendor] +# Vendor name will be shown by the Config Wizard. +name = Foo Bar +# Configuration version of this file. Config file will only be installed, if the config_version differs. +# This means, the server may force the Slic3r configuration to be downgraded. +config_version = 0.1.0 +# Where to get the updates from? +config_update_url = https://example.com + +# The printer models will be shown by the Configuration Wizard in this order, +# also the first model installed & the first nozzle installed will be activated after install. +#TODO: One day we may differentiate variants of the nozzles / hot ends, +#for example by the melt zone size, or whether the nozzle is hardened. +[printer_model:M1] +name = Foo Bar Model 1 +variants = 0.4; 0.25; 0.6 + +[printer_model:M2] +name = Foo Bar Model 2 +variants = 0.4; 0.25; 0.6 + +[printer_model:M3] +# Printer model name will be shown by the installation wizard. +name = Foo Bar Model 3 +variants = 0.4; 0.6 + +# All presets starting with asterisk, for example *common*, are intermediate and they will +# not make it into the user interface. + +# Common print preset, mostly derived from MK2 single material with a 0.4mm nozzle. +# All other print presets will derive from the *common* print preset. +[print:*common*] +avoid_crossing_perimeters = 0 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeters_first = 0 +external_perimeter_extrusion_width = 0.45 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +interface_shells = 0 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +overhangs = 0 +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +perimeters = 2 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirts = 1 +skirt_distance = 2 +skirt_height = 3 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_extruder = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_enforce_layers = 0 +support_material_contact_distance = 0.15 +support_material_interface_contact_loops = 0 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 40 +travel_speed = 180 +wipe_tower = 0 +wipe_tower_per_color_wipe = 20 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +# Print parameters common to a 0.25mm diameter nozzle. +[print:*0.25nozzle*] +external_perimeter_extrusion_width = 0.25 +extrusion_width = 0.25 +first_layer_extrusion_width = 0.25 +infill_extrusion_width = 0.25 +perimeter_extrusion_width = 0.25 +solid_infill_extrusion_width = 0.25 +top_infill_extrusion_width = 0.25 +support_material_extrusion_width = 0.18 +support_material_interface_layers = 0 +support_material_interface_spacing = 0.15 +support_material_spacing = 1 +support_material_xy_spacing = 150% + +# Print parameters common to a 0.6mm diameter nozzle. +[print:*0.6nozzle*] +external_perimeter_extrusion_width = 0.61 +extrusion_width = 0.67 +first_layer_extrusion_width = 0.65 +infill_extrusion_width = 0.7 +perimeter_extrusion_width = 0.65 +solid_infill_extrusion_width = 0.65 +top_infill_extrusion_width = 0.6 + +[print:*soluble_support*] +overhangs = 1 +skirts = 0 +support_material = 1 +support_material_contact_distance = 0 +support_material_extruder = 4 +support_material_extrusion_width = 0.45 +support_material_interface_extruder = 4 +support_material_interface_spacing = 0.1 +support_material_synchronize_layers = 1 +support_material_threshold = 80 +support_material_with_sheath = 1 +wipe_tower = 1 + +[print:*0.05mm*] +inherits = *common* +bottom_solid_layers = 10 +bridge_acceleration = 300 +bridge_flow_ratio = 0.7 +default_acceleration = 500 +external_perimeter_speed = 20 +fill_density = 20% +first_layer_acceleration = 500 +gap_fill_speed = 20 +infill_acceleration = 800 +infill_speed = 30 +max_print_speed = 80 +small_perimeter_speed = 15 +solid_infill_speed = 30 +support_material_extrusion_width = 0.3 +support_material_spacing = 1.5 +layer_height = 0.05 +perimeter_acceleration = 300 +perimeter_speed = 30 +perimeters = 3 +support_material_speed = 30 +top_solid_infill_speed = 20 +top_solid_layers = 15 + +[print:0.05mm ULTRADETAIL] +inherits = *0.05mm* +infill_extrusion_width = 0.5 + +[print:0.05mm ULTRADETAIL MK3] +inherits = *0.05mm* +fill_pattern = grid +top_infill_extrusion_width = 0.4 + +[print:0.05mm ULTRADETAIL 0.25 nozzle] +inherits = *0.05mm* +external_perimeter_extrusion_width = 0 +extrusion_width = 0.28 +fill_density = 20% +first_layer_extrusion_width = 0.3 +infill_extrusion_width = 0 +infill_speed = 20 +max_print_speed = 100 +perimeter_extrusion_width = 0 +perimeter_speed = 20 +small_perimeter_speed = 10 +solid_infill_extrusion_width = 0 +solid_infill_speed = 20 +support_material_speed = 20 +top_infill_extrusion_width = 0 + +[print:0.05mm ULTRADETAIL 0.25 nozzle MK3] +inherits = *0.05mm*; *0.25nozzle* +fill_pattern = grid +top_infill_extrusion_width = 0.4 + +[print:*0.10mm*] +inherits = *common* +bottom_solid_layers = 7 +bridge_flow_ratio = 0.7 +layer_height = 0.1 +perimeter_acceleration = 800 +top_solid_layers = 9 + +[print:0.10mm DETAIL] +inherits = *0.10mm* +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +perimeter_speed = 50 +solid_infill_speed = 50 + +[print:0.10mm DETAIL MK3] +inherits = *0.10mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 170 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 + +[print:0.10mm DETAIL 0.25 nozzle] +inherits = *0.10mm* +bridge_acceleration = 600 +external_perimeter_speed = 20 +infill_acceleration = 1600 +infill_speed = 40 +perimeter_acceleration = 600 +perimeter_speed = 25 +small_perimeter_speed = 10 +solid_infill_speed = 40 +top_solid_infill_speed = 30 + +[print:0.10mm DETAIL 0.25 nozzle MK3] +inherits = *0.10mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 170 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 + +[print:0.10mm DETAIL 0.6 nozzle MK3] +inherits = *0.10mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 170 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 + +[print:*0.15mm*] +inherits = *common* +bottom_solid_layers = 5 +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +layer_height = 0.15 +perimeter_acceleration = 800 +perimeter_speed = 50 +solid_infill_speed = 50 +top_infill_extrusion_width = 0.4 +top_solid_layers = 7 + +[print:0.15mm 100mms Linear Advance] +inherits = *0.15mm* +bridge_flow_ratio = 0.95 +external_perimeter_speed = 50 +infill_speed = 100 +max_print_speed = 150 +perimeter_speed = 60 +small_perimeter_speed = 30 +solid_infill_speed = 100 +support_material_speed = 60 +top_solid_infill_speed = 70 + +[print:0.15mm OPTIMAL] +inherits = *0.15mm* +top_infill_extrusion_width = 0.45 + +[print:0.15mm OPTIMAL 0.25 nozzle] +inherits = *0.15mm*; *0.25nozzle* +bridge_acceleration = 600 +bridge_flow_ratio = 0.7 +external_perimeter_speed = 20 +infill_acceleration = 1600 +infill_speed = 40 +perimeter_acceleration = 600 +perimeter_speed = 25 +small_perimeter_speed = 10 +solid_infill_speed = 40 +support_material_extrusion_width = 0.2 +top_solid_infill_speed = 30 + +[print:0.15mm OPTIMAL 0.6 nozzle] +inherits = *0.15mm*; *0.6nozzle* + +[print:0.15mm OPTIMAL MK3] +inherits = *0.15mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:0.15mm OPTIMAL SOLUBLE FULL] +inherits = *0.15mm*; *soluble_support* +external_perimeter_speed = 25 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +perimeter_speed = 40 +solid_infill_speed = 40 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 +wipe_tower = 1 + +[print:0.15mm OPTIMAL SOLUBLE INTERFACE] +inherits = 0.15mm OPTIMAL SOLUBLE FULL +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 +support_material_xy_spacing = 80% + +[print:0.15mm OPTIMAL 0.25 nozzle MK3] +inherits = *0.15mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 +[print:*0.20mm*] +inherits = *common* +bottom_solid_layers = 4 +bridge_flow_ratio = 0.95 +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +layer_height = 0.2 +perimeter_acceleration = 800 +perimeter_speed = 50 +solid_infill_speed = 50 +top_infill_extrusion_width = 0.4 +top_solid_layers = 5 + +[print:0.15mm OPTIMAL 0.6 nozzle MK3] +inherits = *0.15mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:0.20mm 100mms Linear Advance] +inherits = *0.20mm* +external_perimeter_speed = 50 +infill_speed = 100 +max_print_speed = 150 +perimeter_speed = 60 +small_perimeter_speed = 30 +solid_infill_speed = 100 +support_material_speed = 60 +top_solid_infill_speed = 70 + +[print:0.20mm FAST MK3] +inherits = *0.20mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:0.20mm NORMAL] +inherits = *0.20mm* + +[print:0.20mm NORMAL 0.6 nozzle] +inherits = *0.20mm*; *0.6nozzle* + +[print:0.20mm NORMAL SOLUBLE FULL] +inherits = *0.20mm*; *soluble_support* +external_perimeter_speed = 30 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +perimeter_speed = 40 +solid_infill_speed = 40 +top_solid_infill_speed = 30 + +[print:0.20mm NORMAL SOLUBLE INTERFACE] +inherits = 0.20mm NORMAL SOLUBLE FULL +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 +support_material_xy_spacing = 80% + +[print:0.20mm FAST 0.6 nozzle MK3] +inherits = *0.20mm* +bridge_speed = 30 +external_perimeter_speed = 35 +fill_pattern = grid +infill_acceleration = 1500 +infill_speed = 170 +max_print_speed = 170 +perimeter_speed = 45 +solid_infill_speed = 170 +top_solid_infill_speed = 50 + +[print:*0.35mm*] +inherits = *common* +bottom_solid_layers = 3 +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 40 +first_layer_extrusion_width = 0.75 +infill_acceleration = 2000 +infill_speed = 60 +layer_height = 0.35 +perimeter_acceleration = 800 +perimeter_extrusion_width = 0.65 +perimeter_speed = 50 +solid_infill_extrusion_width = 0.65 +solid_infill_speed = 60 +top_solid_infill_speed = 50 +top_solid_layers = 4 + +[print:0.35mm FAST] +inherits = *0.35mm* +bridge_flow_ratio = 0.95 +first_layer_extrusion_width = 0.42 +perimeter_extrusion_width = 0.43 +solid_infill_extrusion_width = 0.7 +top_infill_extrusion_width = 0.43 + +[print:0.35mm FAST 0.6 nozzle] +inherits = *0.35mm*; *0.6nozzle* + +[print:0.35mm FAST sol full 0.6 nozzle] +inherits = *0.35mm*; *0.6nozzle*; *soluble_support* +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 30 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +perimeter_speed = 40 +support_material_extrusion_width = 0.55 +support_material_interface_layers = 3 +support_material_xy_spacing = 120% +top_infill_extrusion_width = 0.57 + +[print:0.35mm FAST sol int 0.6 nozzle] +inherits = 0.35mm FAST sol full 0.6 nozzle +support_material_extruder = 0 +support_material_interface_layers = 2 +support_material_with_sheath = 0 +support_material_xy_spacing = 150% + +[filament:*common*] +cooling = 1 +compatible_printers = +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +min_print_speed = 5 +slowdown_below_layer_time = 20 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" + +[filament:*PLA*] +inherits = *common* +bed_temperature = 60 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #FF3232 +filament_max_volumetric_speed = 15 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +temperature = 210 + +[filament:*PET*] +inherits = *common* +bed_temperature = 90 +bridge_fan_speed = 50 +disable_fan_first_layers = 3 +fan_always_on = 1 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_max_volumetric_speed = 8 +filament_type = PET +first_layer_bed_temperature = 85 +first_layer_temperature = 230 +max_fan_speed = 50 +min_fan_speed = 30 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" +temperature = 240 + +[filament:*ABS*] +inherits = *common* +bed_temperature = 110 +bridge_fan_speed = 30 +cooling = 0 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #3A80CA +filament_max_volumetric_speed = 11 +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 30 +min_fan_speed = 20 +temperature = 255 + +[filament:*FLEX*] +inherits = *common* +bridge_fan_speed = 100 +cooling = 0 +disable_fan_first_layers = 1 +extrusion_multiplier = 1.2 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #00CA0A +filament_max_volumetric_speed = 1.5 +filament_type = FLEX +first_layer_bed_temperature = 50 +first_layer_temperature = 240 +max_fan_speed = 90 +min_fan_speed = 70 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 240 + +[filament:ColorFabb Brass Bronze] +inherits = *PLA* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 10 + +[filament:ColorFabb HT] +inherits = *PET* +bed_temperature = 110 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 10 +first_layer_bed_temperature = 105 +first_layer_temperature = 270 +max_fan_speed = 20 +min_fan_speed = 10 +min_print_speed = 5 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" +temperature = 270 + +[filament:ColorFabb PLA-PHA] +inherits = *PLA* + +[filament:ColorFabb Woodfil] +inherits = *PLA* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 10 +first_layer_temperature = 200 +min_print_speed = 5 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 200 + +[filament:ColorFabb XT] +inherits = *PET* +filament_type = PLA +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 270 + +[filament:ColorFabb XT-CF20] +inherits = *PET* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 1 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" +temperature = 260 + +[filament:ColorFabb nGen] +inherits = *PET* +bridge_fan_speed = 40 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_type = NGEN +first_layer_temperature = 240 +max_fan_speed = 35 +min_fan_speed = 20 + +[filament:ColorFabb nGen flex] +inherits = *FLEX* +bed_temperature = 85 +bridge_fan_speed = 40 +cooling = 1 +disable_fan_first_layers = 3 +extrusion_multiplier = 1 +fan_below_layer_time = 10 +filament_max_volumetric_speed = 5 +first_layer_bed_temperature = 85 +first_layer_temperature = 260 +max_fan_speed = 35 +min_fan_speed = 20 +temperature = 260 + +[filament:E3D Edge] +inherits = *PET* +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" + +[filament:E3D PC-ABS] +inherits = *ABS* +first_layer_temperature = 270 +temperature = 270 + +[filament:Fillamentum ABS] +inherits = *ABS* +first_layer_temperature = 240 +temperature = 240 + +[filament:Fillamentum ASA] +inherits = *ABS* +fan_always_on = 1 +first_layer_temperature = 265 +temperature = 265 + +[filament:Fillamentum CPE HG100 HM100] +inherits = *PET* +filament_notes = "CPE HG100 , CPE HM100" +first_layer_bed_temperature = 90 +first_layer_temperature = 275 +max_fan_speed = 50 +min_fan_speed = 50 +temperature = 275 + +[filament:Fillamentum Timberfil] +inherits = *PLA* +extrusion_multiplier = 1.2 +filament_colour = #804040 +filament_max_volumetric_speed = 10 +first_layer_temperature = 190 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 190 + +[filament:Generic ABS] +inherits = *ABS* +filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladeč ABS" + +[filament:Generic PET] +inherits = *PET* +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" + +[filament:Generic PLA] +inherits = *PLA* +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" + +[filament:Polymaker PC-Max] +inherits = *ABS* +bed_temperature = 115 +filament_colour = #3A80CA +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +temperature = 270 + +[filament:Primavalue PVA] +inherits = *PLA* +cooling = 0 +fan_always_on = 0 +filament_colour = #FFFFD7 +filament_max_volumetric_speed = 10 +filament_notes = "List of materials tested with standart PVA print settings for MK2:\n\nPrimaSelect PVA+\nICE FILAMENTS PVA 'NAUGHTY NATURAL'\nVerbatim BVOH" +filament_soluble = 1 +filament_type = PVA +first_layer_temperature = 195 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 195 + +[filament:Foobar ABS] +inherits = *ABS* +filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladeč ABS" + +[filament:Foobar HIPS] +inherits = *ABS* +bridge_fan_speed = 50 +cooling = 1 +extrusion_multiplier = 0.9 +fan_always_on = 1 +fan_below_layer_time = 10 +filament_colour = #FFFFD7 +filament_soluble = 1 +filament_type = HIPS +first_layer_temperature = 220 +max_fan_speed = 20 +min_fan_speed = 20 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 220 + +[filament:Foobar PET] +inherits = *PET* +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" + +[filament:Foobar PLA] +inherits = *PLA* +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" + +[filament:SemiFlex or Flexfill 98A] +inherits = *FLEX* + +[filament:Taulman Bridge] +inherits = *common* +bed_temperature = 90 +bridge_fan_speed = 40 +cooling = 0 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 10 +filament_soluble = 0 +filament_type = PET +first_layer_bed_temperature = 60 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 +min_print_speed = 5 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 250 + +[filament:Taulman T-Glase] +inherits = *PET* +bridge_fan_speed = 40 +cooling = 0 +fan_always_on = 0 +first_layer_bed_temperature = 90 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" + +[filament:Verbatim BVOH] +inherits = *common* +bed_temperature = 60 +bridge_fan_speed = 100 +cooling = 0 +disable_fan_first_layers = 1 +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #FFFFD7 +filament_max_volumetric_speed = 10 +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +filament_soluble = 1 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 100 +min_print_speed = 15 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 210 + +[filament:Verbatim PP] +inherits = *common* +bed_temperature = 100 +bridge_fan_speed = 100 +cooling = 1 +disable_fan_first_layers = 2 +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #DEE0E6 +filament_max_volumetric_speed = 5 +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nEUMAKERS PLA" +filament_type = PLA +first_layer_bed_temperature = 100 +first_layer_temperature = 220 +max_fan_speed = 100 +min_fan_speed = 100 +min_print_speed = 15 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 220 + +[printer:*common*] +bed_shape = 0x0,250x0,250x210,0x210 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +between_objects_gcode = +deretract_speed = 0 +end_gcode = G4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +extruder_colour = #FFFF00 +extruder_offset = 0x0 +gcode_flavor = marlin +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.25 +min_layer_height = 0.07 +nozzle_diameter = 0.4 +octoprint_apikey = +octoprint_host = +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Foobar3D\nPRINTER_MODEL_MK2\n +printer_settings_id = +retract_before_travel = 1 +retract_before_wipe = 0% +retract_layer_change = 1 +retract_length = 0.8 +retract_length_toolchange = 4 +retract_lift = 0.6 +retract_lift_above = 0 +retract_lift_below = 199 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 35 +serial_port = +serial_speed = 250000 +single_extruder_multi_material = 0 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 1 +wipe = 1 +z_offset = 0 +printer_model = M2 +printer_variant = 0.4 +default_print_profile = 0.15mm OPTIMAL +default_filament_profile = Foobar PLA + +[printer:*multimaterial*] +inherits = *common* +deretract_speed = 50 +retract_before_travel = 3 +retract_before_wipe = 60% +retract_layer_change = 0 +retract_length = 4 +retract_lift = 0.6 +retract_lift_above = 0 +retract_lift_below = 199 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 80 +single_extruder_multi_material = 1 +printer_model = M3 + +[printer:*mm-single*] +inherits = *multimaterial* +end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Foobar3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 + +[printer:*mm-multi*] +inherits = *multimaterial* +end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes.\nG1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\n{endif}\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors +extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 +nozzle_diameter = 0.4,0.4,0.4,0.4 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Foobar3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 +variable_layer_height = 0 + +[printer:Foobar i3 MK2] +inherits = *common* + +[printer:Foobar i3 MK2 0.25 nozzle] +inherits = *common* +max_layer_height = 0.1 +min_layer_height = 0.05 +nozzle_diameter = 0.25 +retract_length = 1 +retract_speed = 50 +variable_layer_height = 0 +printer_variant = 0.25 +default_print_profile = 0.10mm DETAIL 0.25 nozzle + +[printer:Foobar i3 MK2 0.6 nozzle] +inherits = *common* +max_layer_height = 0.35 +min_layer_height = 0.1 +nozzle_diameter = 0.6 +printer_variant = 0.6 + +[printer:Foobar i3 MK2 MM Single Mode] +inherits = *mm-single* + +[printer:Foobar i3 MK2 MM Single Mode 0.6 nozzle] +inherits = *mm-single* +nozzle_diameter = 0.6 +printer_variant = 0.6 + +[printer:Foobar i3 MK2 MultiMaterial] +inherits = *mm-multi* +nozzle_diameter = 0.4,0.4,0.4,0.4 + +[printer:Foobar i3 MK2 MultiMaterial 0.6 nozzle] +inherits = *mm-multi* +nozzle_diameter = 0.6,0.6,0.6,0.6 +printer_variant = 0.6 + +[printer:Foobar i3 MK3] +inherits = *common* +end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Foobar3D\nPRINTER_MODEL_MK3\n +retract_lift_below = 209 +start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +printer_model = M1 +default_print_profile = 0.15mm OPTIMAL MK3 + +[printer:Foobar i3 MK3 0.25 nozzle] +inherits = *common* +nozzle_diameter = 0.25 +printer_variant = 0.25 +end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Foobar3D\nPRINTER_MODEL_MK3\n +retract_lift_below = 209 +start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +printer_model = M1 +default_print_profile = 0.10mm DETAIL MK3 + +[printer:Foobar i3 MK3 0.6 nozzle] +inherits = *common* +nozzle_diameter = 0.6 +printer_variant = 0.6 +end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Foobar3D\nPRINTER_MODEL_MK3\n +retract_lift_below = 209 +start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +printer_model = M1 +default_print_profile = 0.15mm OPTIMAL MK3 diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx new file mode 100644 index 0000000000..f07d8b2806 --- /dev/null +++ b/resources/profiles/PrusaResearch.idx @@ -0,0 +1 @@ +0.1.0 Initial diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index de23bf0ad3..d82921fd8d 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,23 +5,25 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.1 +config_version = 0.1.0 # Where to get the updates from? -config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch.ini +config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ # The printer models will be shown by the Configuration Wizard in this order, # also the first model installed & the first nozzle installed will be activated after install. #TODO: One day we may differentiate variants of the nozzles / hot ends, #for example by the melt zone size, or whether the nozzle is hardened. +# Printer model name will be shown by the installation wizard. [printer_model:MK3] +name = Original Prusa i3 MK3 variants = 0.4; 0.25; 0.6 [printer_model:MK2S] +name = Original Prusa i3 MK2S variants = 0.4; 0.25; 0.6 [printer_model:MK2SMM] -# Printer model name will be shown by the installation wizard. -name = MK2S Multi Material +name = Original Prusa i3 MK2SMM variants = 0.4; 0.6 # All presets starting with asterisk, for example *common*, are intermediate and they will @@ -66,6 +68,7 @@ infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% interface_shells = 0 +max_print_height = 200 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 @@ -117,7 +120,7 @@ thin_walls = 0 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 40 travel_speed = 180 -wipe_tower = 0 +wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 @@ -936,6 +939,8 @@ inherits = *multimaterial* end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 +default_print_profile = 0.15mm OPTIMAL +default_filament_profile = Prusa PLA [printer:*mm-multi*] inherits = *multimaterial* @@ -945,10 +950,11 @@ nozzle_diameter = 0.4,0.4,0.4,0.4 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 variable_layer_height = 0 +default_print_profile = 0.15mm OPTIMAL +default_filament_profile = Prusa PLA [printer:Original Prusa i3 MK2] inherits = *common* -default_print_profile = 0.15mm OPTIMAL [printer:Original Prusa i3 MK2 0.25 nozzle] inherits = *common* @@ -986,12 +992,14 @@ nozzle_diameter = 0.4,0.4,0.4,0.4 inherits = *mm-multi* nozzle_diameter = 0.6,0.6,0.6,0.6 printer_variant = 0.6 +default_print_profile = 0.20mm NORMAL 0.6 nozzle [printer:Original Prusa i3 MK3] inherits = *common* end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 +max_print_height = 210 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = MK3 default_print_profile = 0.15mm OPTIMAL MK3 @@ -1002,9 +1010,10 @@ nozzle_diameter = 0.25 end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 +max_print_height = 210 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = MK3 -default_print_profile = 0.10mm DETAIL MK3 +default_print_profile = 0.10mm DETAIL 0.25 nozzle MK3 [printer:Original Prusa i3 MK3 0.6 nozzle] inherits = *common* @@ -1012,11 +1021,7 @@ nozzle_diameter = 0.6 end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 +max_print_height = 210 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = MK3 -default_print_profile = 0.15mm OPTIMAL MK3 - -[presets] -print = 0.15mm OPTIMAL MK3 -printer = Original Prusa i3 MK3 -filament = Prusa PLA +default_print_profile = 0.15mm OPTIMAL 0.6 nozzle MK3 diff --git a/t/cooling.t b/t/cooling.t index ee4f6abea5..2f444cf9d1 100644 --- a/t/cooling.t +++ b/t/cooling.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 15; +plan tests => 14; BEGIN { use FindBin; @@ -79,6 +79,7 @@ $config->set('disable_fan_first_layers', [ 0 ]); "G1 X50 F2500\n" . "G1 F3000;_EXTRUDE_SET_SPEED\n" . "G1 X100 E1\n" . + ";_EXTRUDE_END\n" . "G1 E4 F400", # Print time of $gcode. my $print_time = 50 / (2500 / 60) + 100 / (3000 / 60) + 4 / (400 / 60); @@ -203,8 +204,8 @@ $config->set('disable_fan_first_layers', [ 0 ]); ok $all_below, 'slowdown_below_layer_time is honored'; # check that all layers have at least one unaltered external perimeter speed - my $external = all { $_ > 0 } values %layer_external; - ok $external, 'slowdown_below_layer_time does not alter external perimeters'; +# my $external = all { $_ > 0 } values %layer_external; +# ok $external, 'slowdown_below_layer_time does not alter external perimeters'; } __END__ diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index b8d786937a..4f97c0acfe 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -170,10 +170,14 @@ add_library(libslic3r STATIC ) add_library(libslic3r_gui STATIC + ${LIBDIR}/slic3r/GUI/AboutDialog.cpp + ${LIBDIR}/slic3r/GUI/AboutDialog.hpp ${LIBDIR}/slic3r/GUI/AppConfig.cpp ${LIBDIR}/slic3r/GUI/AppConfig.hpp ${LIBDIR}/slic3r/GUI/BitmapCache.cpp ${LIBDIR}/slic3r/GUI/BitmapCache.hpp + ${LIBDIR}/slic3r/GUI/ConfigSnapshotDialog.cpp + ${LIBDIR}/slic3r/GUI/ConfigSnapshotDialog.hpp ${LIBDIR}/slic3r/GUI/3DScene.cpp ${LIBDIR}/slic3r/GUI/3DScene.hpp ${LIBDIR}/slic3r/GUI/GLShader.cpp @@ -208,18 +212,30 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/RammingChart.hpp ${LIBDIR}/slic3r/GUI/BonjourDialog.cpp ${LIBDIR}/slic3r/GUI/BonjourDialog.hpp + ${LIBDIR}/slic3r/GUI/ButtonsDescription.cpp + ${LIBDIR}/slic3r/GUI/ButtonsDescription.hpp ${LIBDIR}/slic3r/Config/Snapshot.cpp ${LIBDIR}/slic3r/Config/Snapshot.hpp ${LIBDIR}/slic3r/Config/Version.cpp ${LIBDIR}/slic3r/Config/Version.hpp ${LIBDIR}/slic3r/Utils/ASCIIFolding.cpp ${LIBDIR}/slic3r/Utils/ASCIIFolding.hpp + ${LIBDIR}/slic3r/GUI/ConfigWizard.cpp + ${LIBDIR}/slic3r/GUI/ConfigWizard.hpp + ${LIBDIR}/slic3r/GUI/MsgDialog.cpp + ${LIBDIR}/slic3r/GUI/MsgDialog.hpp + ${LIBDIR}/slic3r/GUI/UpdateDialogs.cpp + ${LIBDIR}/slic3r/GUI/UpdateDialogs.hpp ${LIBDIR}/slic3r/Utils/Http.cpp ${LIBDIR}/slic3r/Utils/Http.hpp ${LIBDIR}/slic3r/Utils/OctoPrint.cpp ${LIBDIR}/slic3r/Utils/OctoPrint.hpp ${LIBDIR}/slic3r/Utils/Bonjour.cpp ${LIBDIR}/slic3r/Utils/Bonjour.hpp + ${LIBDIR}/slic3r/Utils/PresetUpdater.cpp + ${LIBDIR}/slic3r/Utils/PresetUpdater.hpp + ${LIBDIR}/slic3r/Utils/Time.cpp + ${LIBDIR}/slic3r/Utils/Time.hpp ) add_library(admesh STATIC @@ -365,6 +381,7 @@ set(XS_XSP_FILES ${XSP_DIR}/SurfaceCollection.xsp ${XSP_DIR}/TriangleMesh.xsp ${XSP_DIR}/Utils_OctoPrint.xsp + ${XSP_DIR}/Utils_PresetUpdater.xsp ${XSP_DIR}/XS.xsp ) foreach (file ${XS_XSP_FILES}) @@ -410,7 +427,7 @@ if(APPLE) # Ignore undefined symbols of the perl interpreter, they will be found in the caller image. target_link_libraries(XS "-undefined dynamic_lookup") endif() -target_link_libraries(XS libslic3r libslic3r_gui admesh miniz clipper nowide polypartition poly2tri) +target_link_libraries(XS libslic3r libslic3r_gui admesh miniz clipper nowide polypartition poly2tri semver) if(SLIC3R_PROFILE) target_link_libraries(XS Shiny) endif() @@ -537,13 +554,13 @@ if (SLIC3R_PRUSACONTROL) set(wxWidgets_UseAlienWx 1) if (wxWidgets_UseAlienWx) set(AlienWx_DEBUG 1) - find_package(AlienWx REQUIRED COMPONENTS base core adv) + find_package(AlienWx REQUIRED COMPONENTS base core adv html) include_directories(${AlienWx_INCLUDE_DIRS}) #add_compile_options(${AlienWx_CXX_FLAGS}) add_definitions(${AlienWx_DEFINITIONS}) set(wxWidgets_LIBRARIES ${AlienWx_LIBRARIES}) else () - find_package(wxWidgets REQUIRED COMPONENTS base core adv) + find_package(wxWidgets REQUIRED COMPONENTS base core adv html) include(${wxWidgets_USE_FILE}) endif () add_definitions(-DSLIC3R_GUI -DSLIC3R_PRUS) @@ -623,6 +640,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E copy "$" "${PERL_LOCAL_LIB_DIR}/auto/Slic3r/XS/" COMMAND ${CMAKE_COMMAND} -E make_directory "${PERL_LOCAL_LIB_DIR}/Slic3r/" COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/xs/lib/Slic3r/XS.pm" "${PERL_LOCAL_LIB_DIR}/Slic3r/" + COMMENT "Installing XS.pm and XS.{so,dll,bundle} into the local-lib directory ..." ) if(APPLE) add_custom_command( diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 47a584343e..06eb041dfb 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -12,7 +12,7 @@ our $VERSION = '0.01'; BEGIN { if ($^O eq 'MSWin32') { eval "use Wx"; -# eval "use Wx::Html"; + eval "use Wx::Html"; eval "use Wx::Print"; # because of some Wx bug, thread creation fails if we don't have this (looks like Wx::Printout is hard-coded in some thread cleanup code) } } diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index a4eaf3072a..8c1349e085 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -206,6 +206,44 @@ t_config_option_keys ConfigBase::diff(const ConfigBase &other) const return diff; } +template +void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys& vec, const ConfigBase &other, const ConfigBase *this_c) +{ + const T* opt_init = static_cast(other.option(opt_key)); + const T* opt_cur = static_cast(this_c->option(opt_key)); + int opt_init_max_id = opt_init->values.size() - 1; + for (int i = 0; i < opt_cur->values.size(); i++) + { + int init_id = i <= opt_init_max_id ? i : 0; + if (opt_cur->values[i] != opt_init->values[init_id]) + vec.emplace_back(opt_key + "#" + std::to_string(i)); + } +} + +t_config_option_keys ConfigBase::deep_diff(const ConfigBase &other) const +{ + t_config_option_keys diff; + for (const t_config_option_key &opt_key : this->keys()) { + const ConfigOption *this_opt = this->option(opt_key); + const ConfigOption *other_opt = other.option(opt_key); + if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) + { + if (opt_key == "bed_shape"){ diff.emplace_back(opt_key); continue; } + switch (other_opt->type()) + { + case coInts: add_correct_opts_to_diff(opt_key, diff, other, this); break; + case coBools: add_correct_opts_to_diff(opt_key, diff, other, this); break; + case coFloats: add_correct_opts_to_diff(opt_key, diff, other, this); break; + case coStrings: add_correct_opts_to_diff(opt_key, diff, other, this); break; + case coPercents:add_correct_opts_to_diff(opt_key, diff, other, this); break; + case coPoints: add_correct_opts_to_diff(opt_key, diff, other, this); break; + default: diff.emplace_back(opt_key); break; + } + } + } + return diff; +} + t_config_option_keys ConfigBase::equal(const ConfigBase &other) const { t_config_option_keys equal; diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 6eb307c5ce..14050c6cc5 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -659,6 +659,7 @@ public: ConfigOptionPoints() : ConfigOptionVector() {} explicit ConfigOptionPoints(size_t n, const Pointf &value) : ConfigOptionVector(n, value) {} explicit ConfigOptionPoints(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} + explicit ConfigOptionPoints(const std::vector &values) : ConfigOptionVector(values) {} static ConfigOptionType static_type() { return coPoints; } ConfigOptionType type() const override { return static_type(); } @@ -1046,6 +1047,9 @@ public: void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false); bool equals(const ConfigBase &other) const { return this->diff(other).empty(); } t_config_option_keys diff(const ConfigBase &other) const; + // Use deep_diff to correct return of changed options, + // considering individual options for each extruder + t_config_option_keys deep_diff(const ConfigBase &other) const; t_config_option_keys equal(const ConfigBase &other) const; std::string serialize(const t_config_option_key &opt_key) const; // Set a configuration value from a string, it will call an overridable handle_legacy() diff --git a/xs/src/libslic3r/FileParserError.hpp b/xs/src/libslic3r/FileParserError.hpp index 82a6b328e8..3f560fa4f5 100644 --- a/xs/src/libslic3r/FileParserError.hpp +++ b/xs/src/libslic3r/FileParserError.hpp @@ -4,6 +4,7 @@ #include "libslic3r.h" #include +#include #include namespace Slic3r { @@ -15,6 +16,9 @@ public: file_parser_error(const std::string &msg, const std::string &file, unsigned long line = 0) : std::runtime_error(format_what(msg, file, line)), m_message(msg), m_filename(file), m_line(line) {} + file_parser_error(const std::string &msg, const boost::filesystem::path &file, unsigned long line = 0) : + std::runtime_error(format_what(msg, file.string(), line)), + m_message(msg), m_filename(file.string()), m_line(line) {} // gcc 3.4.2 complains about lack of throw specifier on compiler // generated dtor ~file_parser_error() throw() {} diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp index 7b5bf7e8a6..0467962c3d 100644 --- a/xs/src/libslic3r/Format/3mf.cpp +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -16,6 +16,12 @@ #include #include +// VERSION NUMBERS +// 0 : .3mf, files saved by older slic3r or other applications. No version definition in them. +// 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files. +const unsigned int VERSION_3MF = 1; +const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file + const std::string MODEL_FOLDER = "3D/"; const std::string MODEL_EXTENSION = ".model"; const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA @@ -23,6 +29,7 @@ const std::string CONTENT_TYPES_FILE = "[Content_Types].xml"; const std::string RELATIONSHIPS_FILE = "_rels/.rels"; const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; +const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; const char* MODEL_TAG = "model"; const char* RESOURCES_TAG = "resources"; @@ -36,9 +43,9 @@ const char* COMPONENTS_TAG = "components"; const char* COMPONENT_TAG = "component"; const char* BUILD_TAG = "build"; const char* ITEM_TAG = "item"; +const char* METADATA_TAG = "metadata"; const char* CONFIG_TAG = "config"; -const char* METADATA_TAG = "metadata"; const char* VOLUME_TAG = "volume"; const char* UNIT_ATTR = "unit"; @@ -315,6 +322,10 @@ namespace Slic3r { typedef std::vector InstancesList; typedef std::map IdToMetadataMap; typedef std::map IdToGeometryMap; + typedef std::map> IdToLayerHeightsProfileMap; + + // Version of the 3mf file + unsigned int m_version; XML_Parser m_xml_parser; Model* m_model; @@ -326,6 +337,9 @@ namespace Slic3r { IdToGeometryMap m_geometries; CurrentConfig m_curr_config; IdToMetadataMap m_objects_metadata; + IdToLayerHeightsProfileMap m_layer_heights_profiles; + std::string m_curr_metadata_name; + std::string m_curr_characters; public: _3MF_Importer(); @@ -339,12 +353,14 @@ namespace Slic3r { bool _load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - bool _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename); + void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename); bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); // handlers to parse the .model file void _handle_start_model_xml_element(const char* name, const char** attributes); void _handle_end_model_xml_element(const char* name); + void _handle_model_xml_characters(const XML_Char* s, int len); // handlers to parse the MODEL_CONFIG_FILE file void _handle_start_config_xml_element(const char* name, const char** attributes); @@ -386,6 +402,9 @@ namespace Slic3r { bool _handle_start_item(const char** attributes, unsigned int num_attributes); bool _handle_end_item(); + bool _handle_start_metadata(const char** attributes, unsigned int num_attributes); + bool _handle_end_metadata(); + bool _create_object_instance(int object_id, const Matrix4x4& matrix, unsigned int recur_counter); void _apply_transform(ModelInstance& instance, const Matrix4x4& matrix); @@ -407,6 +426,7 @@ namespace Slic3r { // callbacks to parse the .model file static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes); static void XMLCALL _handle_end_model_xml_element(void* userData, const char* name); + static void XMLCALL _handle_model_xml_characters(void* userData, const XML_Char* s, int len); // callbacks to parse the MODEL_CONFIG_FILE file static void XMLCALL _handle_start_config_xml_element(void* userData, const char* name, const char** attributes); @@ -414,9 +434,12 @@ namespace Slic3r { }; _3MF_Importer::_3MF_Importer() - : m_xml_parser(nullptr) + : m_version(0) + , m_xml_parser(nullptr) , m_model(nullptr) , m_unit_factor(1.0f) + , m_curr_metadata_name("") + , m_curr_characters("") { } @@ -427,6 +450,7 @@ namespace Slic3r { bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle) { + m_version = 0; m_model = &model; m_unit_factor = 1.0f; m_curr_object.reset(); @@ -437,6 +461,9 @@ namespace Slic3r { m_curr_config.object_id = -1; m_curr_config.volume_id = -1; m_objects_metadata.clear(); + m_layer_heights_profiles.clear(); + m_curr_metadata_name.clear(); + m_curr_characters.clear(); clear_errors(); return _load_model_from_file(filename, model, bundle); @@ -472,6 +499,8 @@ namespace Slic3r { mz_uint num_entries = mz_zip_reader_get_num_files(&archive); mz_zip_archive_file_stat stat; + + // we first loop the entries to read from the archive the .model file only, in order to extract the version from it for (mz_uint i = 0; i < num_entries; ++i) { if (mz_zip_reader_file_stat(&archive, i, &stat)) @@ -489,15 +518,26 @@ namespace Slic3r { return false; } } + } + } + + // we then loop again the entries to read other files stored in the archive + for (mz_uint i = 0; i < num_entries; ++i) + { + if (mz_zip_reader_file_stat(&archive, i, &stat)) + { + std::string name(stat.m_filename); + std::replace(name.begin(), name.end(), '\\', '/'); + + if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) + { + // extract slic3r lazer heights profile file + _extract_layer_heights_profile_config_from_archive(archive, stat); + } else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { // extract slic3r print config file - if (!_extract_print_config_from_archive(archive, stat, bundle, filename)) - { - mz_zip_reader_end(&archive); - add_error("Archive does not contain a valid print config"); - return false; - } + _extract_print_config_from_archive(archive, stat, bundle, filename); } else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) { @@ -526,6 +566,13 @@ namespace Slic3r { return false; } + IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.first); + if (obj_layer_heights_profile != m_layer_heights_profiles.end()) + { + object.second->layer_height_profile = obj_layer_heights_profile->second; + object.second->layer_height_profile_valid = true; + } + IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first); if (obj_metadata != m_objects_metadata.end()) { @@ -583,6 +630,7 @@ namespace Slic3r { XML_SetUserData(m_xml_parser, (void*)this); XML_SetElementHandler(m_xml_parser, _3MF_Importer::_handle_start_model_xml_element, _3MF_Importer::_handle_end_model_xml_element); + XML_SetCharacterDataHandler(m_xml_parser, _3MF_Importer::_handle_model_xml_characters); void* parser_buffer = XML_GetBuffer(m_xml_parser, (int)stat.m_uncomp_size); if (parser_buffer == nullptr) @@ -609,23 +657,90 @@ namespace Slic3r { return true; } - bool _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename) + void _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename) { if (stat.m_uncomp_size > 0) { - std::vector buffer((size_t)stat.m_uncomp_size + 1, 0); + std::string buffer((size_t)stat.m_uncomp_size, 0); mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); if (res == 0) { add_error("Error while reading config data to buffer"); - return false; + return; } - - buffer.back() = '\0'; bundle.load_config_string(buffer.data(), archive_filename.c_str()); } + } - return true; + void _3MF_Importer::_extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + if (stat.m_uncomp_size > 0) + { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) + { + add_error("Error while reading layer heights profile data to buffer"); + return; + } + + if (buffer.back() == '\n') + buffer.pop_back(); + + std::vector objects; + boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); + + for (const std::string& object : objects) + { + std::vector object_data; + boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); + if (object_data.size() != 2) + { + add_error("Error while reading object data"); + continue; + } + + std::vector object_data_id; + boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); + if (object_data_id.size() != 2) + { + add_error("Error while reading object id"); + continue; + } + + int object_id = std::atoi(object_data_id[1].c_str()); + if (object_id == 0) + { + add_error("Found invalid object id"); + continue; + } + + IdToLayerHeightsProfileMap::iterator object_item = m_layer_heights_profiles.find(object_id); + if (object_item != m_layer_heights_profiles.end()) + { + add_error("Found duplicated layer heights profile"); + continue; + } + + std::vector object_data_profile; + boost::split(object_data_profile, object_data[1], boost::is_any_of(";"), boost::token_compress_off); + if ((object_data_profile.size() <= 4) || (object_data_profile.size() % 2 != 0)) + { + add_error("Found invalid layer heights profile"); + continue; + } + + std::vector profile; + profile.reserve(object_data_profile.size()); + + for (const std::string& value : object_data_profile) + { + profile.push_back((coordf_t)std::atof(value.c_str())); + } + + m_layer_heights_profiles.insert(IdToLayerHeightsProfileMap::value_type(object_id, profile)); + } + } } bool _3MF_Importer::_extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model) @@ -705,6 +820,8 @@ namespace Slic3r { res = _handle_start_build(attributes, num_attributes); else if (::strcmp(ITEM_TAG, name) == 0) res = _handle_start_item(attributes, num_attributes); + else if (::strcmp(METADATA_TAG, name) == 0) + res = _handle_start_metadata(attributes, num_attributes); if (!res) _stop_xml_parser(); @@ -741,11 +858,18 @@ namespace Slic3r { res = _handle_end_build(); else if (::strcmp(ITEM_TAG, name) == 0) res = _handle_end_item(); + else if (::strcmp(METADATA_TAG, name) == 0) + res = _handle_end_metadata(); if (!res) _stop_xml_parser(); } + void _3MF_Importer::_handle_model_xml_characters(const XML_Char* s, int len) + { + m_curr_characters.append(s, len); + } + void _3MF_Importer::_handle_start_config_xml_element(const char* name, const char** attributes) { if (m_xml_parser == nullptr) @@ -1052,6 +1176,25 @@ namespace Slic3r { return true; } + bool _3MF_Importer::_handle_start_metadata(const char** attributes, unsigned int num_attributes) + { + m_curr_characters.clear(); + + std::string name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR); + if (!name.empty()) + m_curr_metadata_name = name; + + return true; + } + + bool _3MF_Importer::_handle_end_metadata() + { + if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION) + m_version = (unsigned int)atoi(m_curr_characters.c_str()); + + return true; + } + bool _3MF_Importer::_create_object_instance(int object_id, const Matrix4x4& matrix, unsigned int recur_counter) { static const unsigned int MAX_RECURSIONS = 10; @@ -1358,6 +1501,13 @@ namespace Slic3r { importer->_handle_end_model_xml_element(name); } + void XMLCALL _3MF_Importer::_handle_model_xml_characters(void* userData, const XML_Char* s, int len) + { + _3MF_Importer* importer = (_3MF_Importer*)userData; + if (importer != nullptr) + importer->_handle_model_xml_characters(s, len); + } + void XMLCALL _3MF_Importer::_handle_start_config_xml_element(void* userData, const char* name, const char** attributes) { _3MF_Importer* importer = (_3MF_Importer*)userData; @@ -1429,6 +1579,7 @@ namespace Slic3r { bool _add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets); bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); + bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print); bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model); }; @@ -1477,6 +1628,14 @@ namespace Slic3r { return false; } + // adds layer height profile file + if (!_add_layer_height_profile_file_to_archive(archive, model)) + { + mz_zip_writer_end(&archive); + boost::filesystem::remove(filename); + return false; + } + // adds slic3r print config file if (export_print_config) { @@ -1552,7 +1711,8 @@ namespace Slic3r { { std::stringstream stream; stream << "\n"; - stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\">\n"; + stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n"; + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "\n"; stream << " <" << RESOURCES_TAG << ">\n"; BuildItemsList build_items; @@ -1736,6 +1896,44 @@ namespace Slic3r { return true; } + bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model) + { + std::string out = ""; + char buffer[1024]; + + unsigned int count = 0; + for (const ModelObject* object : model.objects) + { + ++count; + std::vector layer_height_profile = object->layer_height_profile_valid ? object->layer_height_profile : std::vector(); + if ((layer_height_profile.size() >= 4) && ((layer_height_profile.size() % 2) == 0)) + { + sprintf(buffer, "object_id=%d|", count); + out += buffer; + + // Store the layer height profile as a single semicolon separated list. + for (size_t i = 0; i < layer_height_profile.size(); ++i) + { + sprintf(buffer, (i == 0) ? "%f" : ";%f", layer_height_profile[i]); + out += buffer; + } + + out += "\n"; + } + } + + if (!out.empty()) + { + if (!mz_zip_writer_add_mem(&archive, LAYER_HEIGHTS_PROFILE_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) + { + add_error("Unable to add layer heights profile file to archive"); + return false; + } + } + + return true; + } + bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print) { char buffer[1024]; @@ -1744,10 +1942,13 @@ namespace Slic3r { GCode::append_full_config(print, out); - if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) + if (!out.empty()) { - add_error("Unable to add print config file to archive"); - return false; + if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) + { + add_error("Unable to add print config file to archive"); + return false; + } } return true; @@ -1832,10 +2033,7 @@ namespace Slic3r { _3MF_Importer importer; bool res = importer.load_model_from_file(path, *model, *bundle); - - if (!res) - importer.log_errors(); - + importer.log_errors(); return res; } diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp index 98683cd8aa..b446f456b2 100644 --- a/xs/src/libslic3r/Format/AMF.cpp +++ b/xs/src/libslic3r/Format/AMF.cpp @@ -24,6 +24,12 @@ #include +// VERSION NUMBERS +// 0 : .amf, .amf.xml and .zip.amf files saved by older slic3r. No version definition in them. +// 1 : Introduction of amf versioning. No other change in data saved into amf files. +const unsigned int VERSION_AMF = 1; +const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version"; + const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config"; namespace Slic3r @@ -32,6 +38,7 @@ namespace Slic3r struct AMFParserContext { AMFParserContext(XML_Parser parser, const std::string& archive_filename, PresetBundle* preset_bundle, Model *model) : + m_version(0), m_parser(parser), m_model(*model), m_object(nullptr), @@ -137,6 +144,8 @@ struct AMFParserContext std::vector instances; }; + // Version of the amf file + unsigned int m_version; // Current Expat XML parser instance. XML_Parser m_parser; // Model to receive objects extracted from an AMF file. @@ -360,9 +369,9 @@ void AMFParserContext::endElement(const char * /* name */) case NODE_TYPE_VERTEX: assert(m_object); // Parse the vertex data - m_object_vertices.emplace_back(atof(m_value[0].c_str())); - m_object_vertices.emplace_back(atof(m_value[1].c_str())); - m_object_vertices.emplace_back(atof(m_value[2].c_str())); + m_object_vertices.emplace_back((float)atof(m_value[0].c_str())); + m_object_vertices.emplace_back((float)atof(m_value[1].c_str())); + m_object_vertices.emplace_back((float)atof(m_value[2].c_str())); m_value[0].clear(); m_value[1].clear(); m_value[2].clear(); @@ -462,6 +471,10 @@ void AMFParserContext::endElement(const char * /* name */) if (m_volume && m_value[0] == "name") m_volume->name = std::move(m_value[1]); } + else if (strncmp(m_value[0].c_str(), SLIC3RPE_AMF_VERSION, strlen(SLIC3RPE_AMF_VERSION)) == 0) { + m_version = (unsigned int)atoi(m_value[1].c_str()); + } + m_value[0].clear(); m_value[1].clear(); break; @@ -543,46 +556,8 @@ bool load_amf_file(const char *path, PresetBundle* bundle, Model *model) return result; } -// Load an AMF archive into a provided model. -bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) +bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, const char* path, PresetBundle* bundle, Model* model, unsigned int& version) { - if ((path == nullptr) || (model == nullptr)) - return false; - - mz_zip_archive archive; - mz_zip_zero_struct(&archive); - - mz_bool res = mz_zip_reader_init_file(&archive, path, 0); - if (res == 0) - { - printf("Unable to init zip reader\n"); - return false; - } - - mz_uint num_entries = mz_zip_reader_get_num_files(&archive); - if (num_entries != 1) - { - printf("Found invalid number of entries\n"); - mz_zip_reader_end(&archive); - return false; - } - - mz_zip_archive_file_stat stat; - res = mz_zip_reader_file_stat(&archive, 0, &stat); - if (res == 0) - { - printf("Unable to extract entry statistics\n"); - mz_zip_reader_end(&archive); - return false; - } - - if (!boost::iends_with(stat.m_filename, ".amf")) - { - printf("Found invalid internal filename\n"); - mz_zip_reader_end(&archive); - return false; - } - if (stat.m_uncomp_size == 0) { printf("Found invalid size\n"); @@ -610,7 +585,7 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) return false; } - res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t)stat.m_uncomp_size, 0); if (res == 0) { printf("Error while reading model data to buffer\n"); @@ -627,6 +602,62 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) ctx.endDocument(); + version = ctx.m_version; + + return true; +} + +// Load an AMF archive into a provided model. +bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) +{ + if ((path == nullptr) || (model == nullptr)) + return false; + + unsigned int version = 0; + + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + + mz_bool res = mz_zip_reader_init_file(&archive, path, 0); + if (res == 0) + { + printf("Unable to init zip reader\n"); + return false; + } + + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + + mz_zip_archive_file_stat stat; + // we first loop the entries to read from the archive the .amf file only, in order to extract the version from it + for (mz_uint i = 0; i < num_entries; ++i) + { + if (mz_zip_reader_file_stat(&archive, i, &stat)) + { + if (boost::iends_with(stat.m_filename, ".amf")) + { + if (!extract_model_from_archive(archive, stat, path, bundle, model, version)) + { + mz_zip_reader_end(&archive); + printf("Archive does not contain a valid model"); + return false; + } + + break; + } + } + } + +#if 0 // forward compatibility + // we then loop again the entries to read other files stored in the archive + for (mz_uint i = 0; i < num_entries; ++i) + { + if (mz_zip_reader_file_stat(&archive, i, &stat)) + { + // add code to extract the file + } + } +#endif // forward compatibility + mz_zip_reader_end(&archive); return true; } @@ -664,6 +695,7 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c stream << "\n"; stream << "\n"; stream << "Slic3r " << SLIC3R_VERSION << "\n"; + stream << "" << VERSION_AMF << "\n"; if (export_print_config) { diff --git a/xs/src/libslic3r/GCode/Analyzer.cpp b/xs/src/libslic3r/GCode/Analyzer.cpp index 799bd6661e..b7ecee5a4c 100644 --- a/xs/src/libslic3r/GCode/Analyzer.cpp +++ b/xs/src/libslic3r/GCode/Analyzer.cpp @@ -718,10 +718,10 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ Helper::store_polyline(polyline, data, z, preview_data); // updates preview ranges data - preview_data.ranges.height.set_from(height_range); - preview_data.ranges.width.set_from(width_range); - preview_data.ranges.feedrate.set_from(feedrate_range); - preview_data.ranges.volumetric_rate.set_from(volumetric_rate_range); + preview_data.ranges.height.update_from(height_range); + preview_data.ranges.width.update_from(width_range); + preview_data.ranges.feedrate.update_from(feedrate_range); + preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range); } void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) @@ -790,9 +790,9 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) Helper::store_polyline(polyline, type, direction, feedrate, extruder_id, preview_data); // updates preview ranges data - preview_data.ranges.height.set_from(height_range); - preview_data.ranges.width.set_from(width_range); - preview_data.ranges.feedrate.set_from(feedrate_range); + preview_data.ranges.height.update_from(height_range); + preview_data.ranges.width.update_from(width_range); + preview_data.ranges.feedrate.update_from(feedrate_range); } void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data) diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp index cd2baeffbd..b786f9bce5 100644 --- a/xs/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp @@ -30,359 +30,579 @@ void CoolingBuffer::reset() m_current_pos[4] = float(m_gcodegen.config().travel_speed.value); } -#define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) +struct CoolingLine +{ + enum Type { + TYPE_SET_TOOL = 1 << 0, + TYPE_EXTRUDE_END = 1 << 1, + TYPE_BRIDGE_FAN_START = 1 << 2, + TYPE_BRIDGE_FAN_END = 1 << 3, + TYPE_G0 = 1 << 4, + TYPE_G1 = 1 << 5, + TYPE_ADJUSTABLE = 1 << 6, + TYPE_EXTERNAL_PERIMETER = 1 << 7, + // The line sets a feedrate. + TYPE_HAS_F = 1 << 8, + TYPE_WIPE = 1 << 9, + TYPE_G4 = 1 << 10, + TYPE_G92 = 1 << 11, + }; + + CoolingLine(unsigned int type, size_t line_start, size_t line_end) : + type(type), line_start(line_start), line_end(line_end), + length(0.f), feedrate(0.f), time(0.f), time_max(0.f), slowdown(false) {} + + bool adjustable(bool slowdown_external_perimeters) const { + return (this->type & TYPE_ADJUSTABLE) && + (! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) && + this->time < this->time_max; + } + + bool adjustable() const { + return (this->type & TYPE_ADJUSTABLE) && this->time < this->time_max; + } + + size_t type; + // Start of this line at the G-code snippet. + size_t line_start; + // End of this line at the G-code snippet. + size_t line_end; + // XY Euclidian length of this segment. + float length; + // Current feedrate, possibly adjusted. + float feedrate; + // Current duration of this segment. + float time; + // Maximum duration of this segment. + float time_max; + // If marked with the "slowdown" flag, the line has been slowed down. + bool slowdown; +}; + +// Calculate the required per extruder time stretches. +struct PerExtruderAdjustments +{ + // Calculate the total elapsed time per this extruder, adjusted for the slowdown. + float elapsed_time_total() { + float time_total = 0.f; + for (const CoolingLine &line : lines) + time_total += line.time; + return time_total; + } + // Calculate the total elapsed time when slowing down + // to the minimum extrusion feed rate defined for the current material. + float maximum_time_after_slowdown(bool slowdown_external_perimeters) { + float time_total = 0.f; + for (const CoolingLine &line : lines) + if (line.adjustable(slowdown_external_perimeters)) { + if (line.time_max == FLT_MAX) + return FLT_MAX; + else + time_total += line.time_max; + } else + time_total += line.time; + return time_total; + } + // Calculate the adjustable part of the total time. + float adjustable_time(bool slowdown_external_perimeters) { + float time_total = 0.f; + for (const CoolingLine &line : lines) + if (line.adjustable(slowdown_external_perimeters)) + time_total += line.time; + return time_total; + } + // Calculate the non-adjustable part of the total time. + float non_adjustable_time(bool slowdown_external_perimeters) { + float time_total = 0.f; + for (const CoolingLine &line : lines) + if (! line.adjustable(slowdown_external_perimeters)) + time_total += line.time; + return time_total; + } + // Slow down the adjustable extrusions to the minimum feedrate allowed for the current extruder material. + // Used by both proportional and non-proportional slow down. + float slowdown_to_minimum_feedrate(bool slowdown_external_perimeters) { + float time_total = 0.f; + for (CoolingLine &line : lines) { + if (line.adjustable(slowdown_external_perimeters)) { + assert(line.time_max >= 0.f && line.time_max < FLT_MAX); + line.slowdown = true; + line.time = line.time_max; + line.feedrate = line.length / line.time; + } + time_total += line.time; + } + return time_total; + } + // Slow down each adjustable G-code line proportionally by a factor. + // Used by the proportional slow down. + float slow_down_proportional(float factor, bool slowdown_external_perimeters) { + assert(factor >= 1.f); + float time_total = 0.f; + for (CoolingLine &line : lines) { + if (line.adjustable(slowdown_external_perimeters)) { + line.slowdown = true; + line.time = std::min(line.time_max, line.time * factor); + line.feedrate = line.length / line.time; + } + time_total += line.time; + } + return time_total; + } + + // Sort the lines, adjustable first, higher feedrate first. + // Used by non-proportional slow down. + void sort_lines_by_decreasing_feedrate() { + std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) { + bool adj1 = l1.adjustable(); + bool adj2 = l2.adjustable(); + return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1; + }); + for (n_lines_adjustable = 0; + n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable(); + ++ n_lines_adjustable); + time_non_adjustable = 0.f; + for (size_t i = n_lines_adjustable; i < lines.size(); ++ i) + time_non_adjustable += lines[i].time; + } + + // Calculate the maximum time stretch when slowing down to min_feedrate. + // Slowdown to min_feedrate shall be allowed for this extruder's material. + // Used by non-proportional slow down. + float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) { + float time_stretch = 0.f; + assert(this->min_print_speed < min_feedrate + EPSILON); + for (size_t i = 0; i < n_lines_adjustable; ++ i) { + const CoolingLine &line = lines[i]; + if (line.feedrate > min_feedrate) + time_stretch += line.time * (line.feedrate / min_feedrate - 1.f); + } + return time_stretch; + } + + // Slow down all adjustable lines down to min_feedrate. + // Slowdown to min_feedrate shall be allowed for this extruder's material. + // Used by non-proportional slow down. + void slow_down_to_feedrate(float min_feedrate) { + assert(this->min_print_speed < min_feedrate + EPSILON); + for (size_t i = 0; i < n_lines_adjustable; ++ i) { + CoolingLine &line = lines[i]; + if (line.feedrate > min_feedrate) { + line.time *= std::max(1.f, line.feedrate / min_feedrate); + line.feedrate = min_feedrate; + line.slowdown = true; + } + } + } + + // Extruder, for which the G-code will be adjusted. + unsigned int extruder_id = 0; + // Is the cooling slow down logic enabled for this extruder's material? + bool cooling_slow_down_enabled = false; + // Slow down the print down to min_print_speed if the total layer time is below slowdown_below_layer_time. + float slowdown_below_layer_time = 0.f; + // Minimum print speed allowed for this extruder. + float min_print_speed = 0.f; + + // Parsed lines. + std::vector lines; + // The following two values are set by sort_lines_by_decreasing_feedrate(): + // Number of adjustable lines, at the start of lines. + size_t n_lines_adjustable = 0; + // Non-adjustable time of lines starting with n_lines_adjustable. + float time_non_adjustable = 0; + // Current total time for this extruder. + float time_total = 0; + // Maximum time for this extruder, when the maximum slow down is applied. + float time_maximum = 0; + + // Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable. + size_t idx_line_begin = 0; + size_t idx_line_end = 0; +}; std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id) +{ + std::vector per_extruder_adjustments = this->parse_layer_gcode(gcode, m_current_pos); + float layer_time_stretched = this->calculate_layer_slowdown(per_extruder_adjustments); + return this->apply_layer_cooldown(gcode, layer_id, layer_time_stretched, per_extruder_adjustments); +} + +// Parse the layer G-code for the moves, which could be adjusted. +// Return the list of parsed lines, bucketed by an extruder. +std::vector CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector ¤t_pos) const { const FullPrintConfig &config = m_gcodegen.config(); const std::vector &extruders = m_gcodegen.writer().extruders(); - const size_t num_extruders = extruders.size(); - - // Calculate the required per extruder time stretches. - struct Adjustment { - Adjustment(unsigned int extruder_id = 0) : extruder_id(extruder_id) {} - // Calculate the total elapsed time per this extruder, adjusted for the slowdown. - float elapsed_time_total() { - float time_total = 0.f; - for (const Line &line : lines) - time_total += line.time; - return time_total; - } - // Calculate the maximum time when slowing down. - float maximum_time(bool slowdown_external_perimeters) { - float time_total = 0.f; - for (const Line &line : lines) - if (line.adjustable(slowdown_external_perimeters)) { - if (line.time_max == FLT_MAX) - return FLT_MAX; - else - time_total += line.time_max; - } else - time_total += line.time; - return time_total; - } - // Calculate the non-adjustable part of the total time. - float non_adjustable_time(bool slowdown_external_perimeters) { - float time_total = 0.f; - for (const Line &line : lines) - if (! line.adjustable(slowdown_external_perimeters)) - time_total += line.time; - return time_total; - } - float slow_down_maximum(bool slowdown_external_perimeters) { - float time_total = 0.f; - for (Line &line : lines) { - if (line.adjustable(slowdown_external_perimeters)) { - assert(line.time_max >= 0.f && line.time_max < FLT_MAX); - line.slowdown = true; - line.time = line.time_max; - } - time_total += line.time; - } - return time_total; - } - float slow_down_proportional(float factor, bool slowdown_external_perimeters) { - assert(factor >= 1.f); - float time_total = 0.f; - for (Line &line : lines) { - if (line.adjustable(slowdown_external_perimeters)) { - line.slowdown = true; - line.time = std::min(line.time_max, line.time * factor); - } - time_total += line.time; - } - return time_total; - } - - bool operator<(const Adjustment &rhs) const { return this->extruder_id < rhs.extruder_id; } - - struct Line - { - enum Type { - TYPE_SET_TOOL = 1 << 0, - TYPE_EXTRUDE_END = 1 << 1, - TYPE_BRIDGE_FAN_START = 1 << 2, - TYPE_BRIDGE_FAN_END = 1 << 3, - TYPE_G0 = 1 << 4, - TYPE_G1 = 1 << 5, - TYPE_ADJUSTABLE = 1 << 6, - TYPE_EXTERNAL_PERIMETER = 1 << 7, - // The line sets a feedrate. - TYPE_HAS_F = 1 << 8, - TYPE_WIPE = 1 << 9, - TYPE_G4 = 1 << 10, - TYPE_G92 = 1 << 11, - }; - - Line(unsigned int type, size_t line_start, size_t line_end) : - type(type), line_start(line_start), line_end(line_end), - length(0.f), time(0.f), time_max(0.f), slowdown(false) {} - - bool adjustable(bool slowdown_external_perimeters) const { - return (this->type & TYPE_ADJUSTABLE) && - (! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) && - this->time < this->time_max; - } - - size_t type; - // Start of this line at the G-code snippet. - size_t line_start; - // End of this line at the G-code snippet. - size_t line_end; - // XY Euclidian length of this segment. - float length; - // Current duration of this segment. - float time; - // Maximum duration of this segment. - float time_max; - // If marked with the "slowdown" flag, the line has been slowed down. - bool slowdown; - }; - - // Extruder, for which the G-code will be adjusted. - unsigned int extruder_id; - // Parsed lines. - std::vector lines; - }; - std::vector adjustments(num_extruders, Adjustment()); - for (size_t i = 0; i < num_extruders; ++ i) - adjustments[i].extruder_id = extruders[i].id(); - const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); - // Parse the layer G-code for the moves, which could be adjusted. - { - float min_print_speed = float(EXTRUDER_CONFIG(min_print_speed)); - auto adjustment = std::lower_bound(adjustments.begin(), adjustments.end(), Adjustment(m_current_extruder)); - unsigned int initial_extruder = m_current_extruder; - const char *line_start = gcode.c_str(); - const char *line_end = line_start; - const char extrusion_axis = config.get_extrusion_axis()[0]; - // Index of an existing Adjustment::Line of the current adjustment, which holds the feedrate setting command - // for a sequence of extrusion moves. - size_t active_speed_modifier = size_t(-1); - for (; *line_start != 0; line_start = line_end) { - while (*line_end != '\n' && *line_end != 0) - ++ line_end; - // sline will not contain the trailing '\n'. - std::string sline(line_start, line_end); - // Adjustment::Line will contain the trailing '\n'. - if (*line_end == '\n') - ++ line_end; - Adjustment::Line line(0, line_start - gcode.c_str(), line_end - gcode.c_str()); - if (boost::starts_with(sline, "G0 ")) - line.type = Adjustment::Line::TYPE_G0; - else if (boost::starts_with(sline, "G1 ")) - line.type = Adjustment::Line::TYPE_G1; - else if (boost::starts_with(sline, "G92 ")) - line.type = Adjustment::Line::TYPE_G92; - if (line.type) { - // G0, G1 or G92 - // Parse the G-code line. - std::vector new_pos(m_current_pos); - const char *c = sline.data() + 3; - for (;;) { - // Skip whitespaces. - for (; *c == ' ' || *c == '\t'; ++ c); - if (*c == 0 || *c == ';') - break; - // Parse the axis. - size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : - (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1); - if (axis != size_t(-1)) { - new_pos[axis] = float(atof(++c)); - if (axis == 4) { - // Convert mm/min to mm/sec. - new_pos[4] /= 60.f; - if ((line.type & Adjustment::Line::TYPE_G92) == 0) - // This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls. - line.type |= Adjustment::Line::TYPE_HAS_F; - } - } - // Skip this word. - for (; *c != ' ' && *c != '\t' && *c != 0; ++ c); - } - bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); - bool wipe = boost::contains(sline, ";_WIPE"); - if (external_perimeter) - line.type |= Adjustment::Line::TYPE_EXTERNAL_PERIMETER; - if (wipe) - line.type |= Adjustment::Line::TYPE_WIPE; - if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) { - line.type |= Adjustment::Line::TYPE_ADJUSTABLE; - active_speed_modifier = adjustment->lines.size(); - } - if ((line.type & Adjustment::Line::TYPE_G92) == 0) { - // G0 or G1. Calculate the duration. - if (config.use_relative_e_distances.value) - // Reset extruder accumulator. - m_current_pos[3] = 0.f; - float dif[4]; - for (size_t i = 0; i < 4; ++ i) - dif[i] = new_pos[i] - m_current_pos[i]; - float dxy2 = dif[0] * dif[0] + dif[1] * dif[1]; - float dxyz2 = dxy2 + dif[2] * dif[2]; - if (dxyz2 > 0.f) { - // Movement in xyz, calculate time from the xyz Euclidian distance. - line.length = sqrt(dxyz2); - } else if (std::abs(dif[3]) > 0.f) { - // Movement in the extruder axis. - line.length = std::abs(dif[3]); - } - if (line.length > 0) - line.time = line.length / new_pos[4]; // current F - line.time_max = line.time; - if ((line.type & Adjustment::Line::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1)) - line.time_max = (min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / min_print_speed); - if (active_speed_modifier < adjustment->lines.size() && (line.type & Adjustment::Line::TYPE_G1)) { - // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry. - assert((line.type & Adjustment::Line::TYPE_HAS_F) == 0); - Adjustment::Line &sm = adjustment->lines[active_speed_modifier]; - sm.length += line.length; - sm.time += line.time; - if (sm.time_max != FLT_MAX) { - if (line.time_max == FLT_MAX) - sm.time_max = FLT_MAX; - else - sm.time_max += line.time_max; - } - // Don't store this line. - line.type = 0; - } - } - m_current_pos = std::move(new_pos); - } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { - line.type = Adjustment::Line::TYPE_EXTRUDE_END; - active_speed_modifier = size_t(-1); - } else if (boost::starts_with(sline, toolchange_prefix)) { - // Switch the tool. - line.type = Adjustment::Line::TYPE_SET_TOOL; - unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size()); - if (new_extruder != m_current_extruder) { - m_current_extruder = new_extruder; - min_print_speed = float(EXTRUDER_CONFIG(min_print_speed)); - adjustment = std::lower_bound(adjustments.begin(), adjustments.end(), Adjustment(m_current_extruder)); - } - } else if (boost::starts_with(sline, ";_BRIDGE_FAN_START")) { - line.type = Adjustment::Line::TYPE_BRIDGE_FAN_START; - } else if (boost::starts_with(sline, ";_BRIDGE_FAN_END")) { - line.type = Adjustment::Line::TYPE_BRIDGE_FAN_END; - } else if (boost::starts_with(sline, "G4 ")) { - // Parse the wait time. - line.type = Adjustment::Line::TYPE_G4; - size_t pos_S = sline.find('S', 3); - size_t pos_P = sline.find('P', 3); - line.time = line.time_max = float( - (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) : - (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.); - } - if (line.type != 0) - adjustment->lines.emplace_back(std::move(line)); - } - m_current_extruder = initial_extruder; + unsigned int num_extruders = 0; + for (const Extruder &ex : extruders) + num_extruders = std::max(ex.id() + 1, num_extruders); + + std::vector per_extruder_adjustments(extruders.size()); + std::vector map_extruder_to_per_extruder_adjustment(num_extruders, 0); + for (size_t i = 0; i < extruders.size(); ++ i) { + PerExtruderAdjustments &adj = per_extruder_adjustments[i]; + unsigned int extruder_id = extruders[i].id(); + adj.extruder_id = extruder_id; + adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id); + adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id); + adj.min_print_speed = config.min_print_speed.get_at(extruder_id); + map_extruder_to_per_extruder_adjustment[extruder_id] = i; } - // Sort the extruders by the increasing slowdown_below_layer_time. - std::vector by_slowdown_layer_time; - by_slowdown_layer_time.reserve(num_extruders); + const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); + unsigned int current_extruder = m_current_extruder; + PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; + const char *line_start = gcode.c_str(); + const char *line_end = line_start; + const char extrusion_axis = config.get_extrusion_axis()[0]; + // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command + // for a sequence of extrusion moves. + size_t active_speed_modifier = size_t(-1); + + for (; *line_start != 0; line_start = line_end) + { + while (*line_end != '\n' && *line_end != 0) + ++ line_end; + // sline will not contain the trailing '\n'. + std::string sline(line_start, line_end); + // CoolingLine will contain the trailing '\n'. + if (*line_end == '\n') + ++ line_end; + CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str()); + if (boost::starts_with(sline, "G0 ")) + line.type = CoolingLine::TYPE_G0; + else if (boost::starts_with(sline, "G1 ")) + line.type = CoolingLine::TYPE_G1; + else if (boost::starts_with(sline, "G92 ")) + line.type = CoolingLine::TYPE_G92; + if (line.type) { + // G0, G1 or G92 + // Parse the G-code line. + std::vector new_pos(current_pos); + const char *c = sline.data() + 3; + for (;;) { + // Skip whitespaces. + for (; *c == ' ' || *c == '\t'; ++ c); + if (*c == 0 || *c == ';') + break; + // Parse the axis. + size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : + (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1); + if (axis != size_t(-1)) { + new_pos[axis] = float(atof(++c)); + if (axis == 4) { + // Convert mm/min to mm/sec. + new_pos[4] /= 60.f; + if ((line.type & CoolingLine::TYPE_G92) == 0) + // This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls. + line.type |= CoolingLine::TYPE_HAS_F; + } + } + // Skip this word. + for (; *c != ' ' && *c != '\t' && *c != 0; ++ c); + } + bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); + bool wipe = boost::contains(sline, ";_WIPE"); + if (external_perimeter) + line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER; + if (wipe) + line.type |= CoolingLine::TYPE_WIPE; + if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) { + line.type |= CoolingLine::TYPE_ADJUSTABLE; + active_speed_modifier = adjustment->lines.size(); + } + if ((line.type & CoolingLine::TYPE_G92) == 0) { + // G0 or G1. Calculate the duration. + if (config.use_relative_e_distances.value) + // Reset extruder accumulator. + current_pos[3] = 0.f; + float dif[4]; + for (size_t i = 0; i < 4; ++ i) + dif[i] = new_pos[i] - current_pos[i]; + float dxy2 = dif[0] * dif[0] + dif[1] * dif[1]; + float dxyz2 = dxy2 + dif[2] * dif[2]; + if (dxyz2 > 0.f) { + // Movement in xyz, calculate time from the xyz Euclidian distance. + line.length = sqrt(dxyz2); + } else if (std::abs(dif[3]) > 0.f) { + // Movement in the extruder axis. + line.length = std::abs(dif[3]); + } + line.feedrate = new_pos[4]; + assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f); + if (line.length > 0) + line.time = line.length / line.feedrate; + line.time_max = line.time; + if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1)) + line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed); + if (active_speed_modifier < adjustment->lines.size() && (line.type & CoolingLine::TYPE_G1)) { + // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry. + assert((line.type & CoolingLine::TYPE_HAS_F) == 0); + CoolingLine &sm = adjustment->lines[active_speed_modifier]; + assert(sm.feedrate > 0.f); + sm.length += line.length; + sm.time += line.time; + if (sm.time_max != FLT_MAX) { + if (line.time_max == FLT_MAX) + sm.time_max = FLT_MAX; + else + sm.time_max += line.time_max; + } + // Don't store this line. + line.type = 0; + } + } + current_pos = std::move(new_pos); + } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { + line.type = CoolingLine::TYPE_EXTRUDE_END; + active_speed_modifier = size_t(-1); + } else if (boost::starts_with(sline, toolchange_prefix)) { + // Switch the tool. + line.type = CoolingLine::TYPE_SET_TOOL; + unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size()); + if (new_extruder != current_extruder) { + current_extruder = new_extruder; + adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; + } + } else if (boost::starts_with(sline, ";_BRIDGE_FAN_START")) { + line.type = CoolingLine::TYPE_BRIDGE_FAN_START; + } else if (boost::starts_with(sline, ";_BRIDGE_FAN_END")) { + line.type = CoolingLine::TYPE_BRIDGE_FAN_END; + } else if (boost::starts_with(sline, "G4 ")) { + // Parse the wait time. + line.type = CoolingLine::TYPE_G4; + size_t pos_S = sline.find('S', 3); + size_t pos_P = sline.find('P', 3); + line.time = line.time_max = float( + (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) : + (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.); + } + if (line.type != 0) + adjustment->lines.emplace_back(std::move(line)); + } + + return per_extruder_adjustments; +} + +// Slow down an extruder range proportionally down to slowdown_below_layer_time. +// Return the total time for the complete layer. +static inline float extruder_range_slow_down_proportional( + std::vector::iterator it_begin, + std::vector::iterator it_end, + // Elapsed time for the extruders already processed. + float elapsed_time_total0, + // Initial total elapsed time before slow down. + float elapsed_time_before_slowdown, + // Target time for the complete layer (all extruders applied). + float slowdown_below_layer_time) +{ + // Total layer time after the slow down has been applied. + float total_after_slowdown = elapsed_time_before_slowdown; + // Now decide, whether the external perimeters shall be slowed down as well. + float max_time_nep = elapsed_time_total0; + for (auto it = it_begin; it != it_end; ++ it) + max_time_nep += (*it)->maximum_time_after_slowdown(false); + if (max_time_nep > slowdown_below_layer_time) { + // It is sufficient to slow down the non-external perimeter moves to reach the target layer time. + // Slow down the non-external perimeters proportionally. + float non_adjustable_time = elapsed_time_total0; + for (auto it = it_begin; it != it_end; ++ it) + non_adjustable_time += (*it)->non_adjustable_time(false); + // The following step is a linear programming task due to the minimum movement speeds of the print moves. + // Run maximum 5 iterations until a good enough approximation is reached. + for (size_t iter = 0; iter < 5; ++ iter) { + float factor = (slowdown_below_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time); + assert(factor > 1.f); + total_after_slowdown = elapsed_time_total0; + for (auto it = it_begin; it != it_end; ++ it) + total_after_slowdown += (*it)->slow_down_proportional(factor, false); + if (total_after_slowdown > 0.95f * slowdown_below_layer_time) + break; + } + } else { + // Slow down everything. First slow down the non-external perimeters to maximum. + for (auto it = it_begin; it != it_end; ++ it) + (*it)->slowdown_to_minimum_feedrate(false); + // Slow down the external perimeters proportionally. + float non_adjustable_time = elapsed_time_total0; + for (auto it = it_begin; it != it_end; ++ it) + non_adjustable_time += (*it)->non_adjustable_time(true); + for (size_t iter = 0; iter < 5; ++ iter) { + float factor = (slowdown_below_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time); + assert(factor > 1.f); + total_after_slowdown = elapsed_time_total0; + for (auto it = it_begin; it != it_end; ++ it) + total_after_slowdown += (*it)->slow_down_proportional(factor, true); + if (total_after_slowdown > 0.95f * slowdown_below_layer_time) + break; + } + } + return total_after_slowdown; +} + +// Slow down an extruder range to slowdown_below_layer_time. +// Return the total time for the complete layer. +static inline void extruder_range_slow_down_non_proportional( + std::vector::iterator it_begin, + std::vector::iterator it_end, + float time_stretch) +{ + // Slow down. Try to equalize the feedrates. + std::vector by_min_print_speed(it_begin, it_end); + // Find the next highest adjustable feedrate among the extruders. + float feedrate = 0; + for (PerExtruderAdjustments *adj : by_min_print_speed) { + adj->idx_line_begin = 0; + adj->idx_line_end = 0; + assert(adj->idx_line_begin < adj->n_lines_adjustable); + if (adj->lines[adj->idx_line_begin].feedrate > feedrate) + feedrate = adj->lines[adj->idx_line_begin].feedrate; + } + assert(feedrate > 0.f); + // Sort by min_print_speed, maximum speed first. + std::sort(by_min_print_speed.begin(), by_min_print_speed.end(), + [](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->min_print_speed > p2->min_print_speed; }); + // Slow down, fast moves first. + for (;;) { + // For each extruder, find the span of lines with a feedrate close to feedrate. + for (PerExtruderAdjustments *adj : by_min_print_speed) { + for (adj->idx_line_end = adj->idx_line_begin; + adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON; + ++ adj->idx_line_end) ; + } + // Find the next highest adjustable feedrate among the extruders. + float feedrate_next = 0.f; + for (PerExtruderAdjustments *adj : by_min_print_speed) + if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next) + feedrate_next = adj->lines[adj->idx_line_end].feedrate; + // Slow down, limited by max(feedrate_next, min_print_speed). + for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) { + // Slow down at most by time_stretch. + if ((*adj)->min_print_speed == 0.f) { + // All the adjustable speeds are now lowered to the same speed, + // and the minimum speed is set to zero. + float time_adjustable = 0.f; + for (auto it = adj; it != by_min_print_speed.end(); ++ it) + time_adjustable += (*it)->adjustable_time(true); + float rate = (time_adjustable + time_stretch) / time_adjustable; + for (auto it = adj; it != by_min_print_speed.end(); ++ it) + (*it)->slow_down_proportional(rate, true); + return; + } else { + float feedrate_limit = std::max(feedrate_next, (*adj)->min_print_speed); + bool done = false; + float time_stretch_max = 0.f; + for (auto it = adj; it != by_min_print_speed.end(); ++ it) + time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit); + if (time_stretch_max >= time_stretch) { + feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max; + done = true; + } else + time_stretch -= time_stretch_max; + for (auto it = adj; it != by_min_print_speed.end(); ++ it) + (*it)->slow_down_to_feedrate(feedrate_limit); + if (done) + return; + } + // Skip the other extruders with nearly the same min_print_speed, as they have been processed already. + auto next = adj; + for (++ next; next != by_min_print_speed.end() && (*next)->min_print_speed > (*adj)->min_print_speed - EPSILON; ++ next); + adj = next; + } + if (feedrate_next == 0.f) + // There are no other extrusions available for slow down. + break; + for (PerExtruderAdjustments *adj : by_min_print_speed) { + adj->idx_line_begin = adj->idx_line_end; + feedrate = feedrate_next; + } + } +} + +// Calculate slow down for all the extruders. +float CoolingBuffer::calculate_layer_slowdown(std::vector &per_extruder_adjustments) +{ + // Sort the extruders by an increasing slowdown_below_layer_time. + // The layers with a lower slowdown_below_layer_time are slowed down + // together with all the other layers with slowdown_below_layer_time above. + std::vector by_slowdown_time; + by_slowdown_time.reserve(per_extruder_adjustments.size()); // Only insert entries, which are adjustable (have cooling enabled and non-zero stretchable time). // Collect total print time of non-adjustable extruders. - float elapsed_time_total_non_adjustable = 0.f; - for (size_t i = 0; i < num_extruders; ++ i) { - if (config.cooling.get_at(extruders[i].id())) - by_slowdown_layer_time.emplace_back(i); - else - elapsed_time_total_non_adjustable += adjustments[i].elapsed_time_total(); + float elapsed_time_total0 = 0.f; + for (PerExtruderAdjustments &adj : per_extruder_adjustments) { + // Curren total time for this extruder. + adj.time_total = adj.elapsed_time_total(); + // Maximum time for this extruder, when all extrusion moves are slowed down to min_extrusion_speed. + adj.time_maximum = adj.maximum_time_after_slowdown(true); + if (adj.cooling_slow_down_enabled) { + by_slowdown_time.emplace_back(&adj); + if (! m_cooling_logic_proportional) + // sorts the lines, also sets adj.time_non_adjustable + adj.sort_lines_by_decreasing_feedrate(); + } else + elapsed_time_total0 += adj.elapsed_time_total(); } - std::sort(by_slowdown_layer_time.begin(), by_slowdown_layer_time.end(), - [&config, &extruders](const size_t idx1, const size_t idx2){ - return config.slowdown_below_layer_time.get_at(extruders[idx1].id()) < - config.slowdown_below_layer_time.get_at(extruders[idx2].id()); - }); + std::sort(by_slowdown_time.begin(), by_slowdown_time.end(), + [](const PerExtruderAdjustments *adj1, const PerExtruderAdjustments *adj2) + { return adj1->slowdown_below_layer_time < adj2->slowdown_below_layer_time; }); - // Elapsed time after adjustment. - float elapsed_time_total = 0.f; - { - // Elapsed time for the already adjusted extruders. - float elapsed_time_total0 = elapsed_time_total_non_adjustable; - for (size_t i_by_slowdown_layer_time = 0; i_by_slowdown_layer_time < by_slowdown_layer_time.size(); ++ i_by_slowdown_layer_time) { - // Idx in adjustments. - size_t idx = by_slowdown_layer_time[i_by_slowdown_layer_time]; - // Macro to sum or adjust all sections starting with i_by_slowdown_layer_time. - #define FORALL_UNPROCESSED(ACCUMULATOR, ACTION) \ - ACCUMULATOR = elapsed_time_total0;\ - for (size_t j = i_by_slowdown_layer_time; j < by_slowdown_layer_time.size(); ++ j) \ - ACCUMULATOR += adjustments[by_slowdown_layer_time[j]].ACTION - // Calculate the current adjusted elapsed_time_total over the non-finalized extruders. - float total; - FORALL_UNPROCESSED(total, elapsed_time_total()); - float slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(adjustments[idx].extruder_id)) * 1.001f; - if (total > slowdown_below_layer_time) { - // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything. + for (auto cur_begin = by_slowdown_time.begin(); cur_begin != by_slowdown_time.end(); ++ cur_begin) { + PerExtruderAdjustments &adj = *(*cur_begin); + // Calculate the current adjusted elapsed_time_total over the non-finalized extruders. + float total = elapsed_time_total0; + for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it) + total += (*it)->time_total; + float slowdown_below_layer_time = adj.slowdown_below_layer_time * 1.001f; + if (total > slowdown_below_layer_time) { + // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything. + } else { + // Adjust this and all the following (higher config.slowdown_below_layer_time) extruders. + // Sum maximum slow down time as if everything was slowed down including the external perimeters. + float max_time = elapsed_time_total0; + for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it) + max_time += (*it)->time_maximum; + if (max_time > slowdown_below_layer_time) { + if (m_cooling_logic_proportional) + extruder_range_slow_down_proportional(cur_begin, by_slowdown_time.end(), elapsed_time_total0, total, slowdown_below_layer_time); + else + extruder_range_slow_down_non_proportional(cur_begin, by_slowdown_time.end(), slowdown_below_layer_time - total); } else { - // Adjust this and all the following (higher config.slowdown_below_layer_time) extruders. - // Sum maximum slow down time as if everything was slowed down including the external perimeters. - float max_time; - FORALL_UNPROCESSED(max_time, maximum_time(true)); - if (max_time > slowdown_below_layer_time) { - // By slowing every possible movement, the layer time could be reached. Now decide - // whether the external perimeters shall be slowed down as well. - float max_time_nep; - FORALL_UNPROCESSED(max_time_nep, maximum_time(false)); - if (max_time_nep > slowdown_below_layer_time) { - // It is sufficient to slow down the non-external perimeter moves to reach the target layer time. - // Slow down the non-external perimeters proportionally. - float non_adjustable_time; - FORALL_UNPROCESSED(non_adjustable_time, non_adjustable_time(false)); - // The following step is a linear programming task due to the minimum movement speeds of the print moves. - // Run maximum 5 iterations until a good enough approximation is reached. - for (size_t iter = 0; iter < 5; ++ iter) { - float factor = (slowdown_below_layer_time - non_adjustable_time) / (total - non_adjustable_time); - assert(factor > 1.f); - FORALL_UNPROCESSED(total, slow_down_proportional(factor, false)); - if (total > 0.95f * slowdown_below_layer_time) - break; - } - } else { - // Slow down everything. First slow down the non-external perimeters to maximum. - FORALL_UNPROCESSED(total, slow_down_maximum(false)); - // Slow down the external perimeters proportionally. - float non_adjustable_time; - FORALL_UNPROCESSED(non_adjustable_time, non_adjustable_time(true)); - for (size_t iter = 0; iter < 5; ++ iter) { - float factor = (slowdown_below_layer_time - non_adjustable_time) / (total - non_adjustable_time); - assert(factor > 1.f); - FORALL_UNPROCESSED(total, slow_down_proportional(factor, true)); - if (total > 0.95f * slowdown_below_layer_time) - break; - } - } - } else { - // Slow down to maximum possible. - FORALL_UNPROCESSED(total, slow_down_maximum(true)); - } + // Slow down to maximum possible. + for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it) + (*it)->slowdown_to_minimum_feedrate(true); } - #undef FORALL_UNPROCESSED - // Sum the final elapsed time for all extruders up to i_by_slowdown_layer_time. - if (i_by_slowdown_layer_time + 1 == by_slowdown_layer_time.size()) - // Optimization for single extruder prints. - elapsed_time_total0 = total; - else - elapsed_time_total0 += adjustments[idx].elapsed_time_total(); } - elapsed_time_total = elapsed_time_total0; + elapsed_time_total0 += adj.elapsed_time_total(); } - // Transform the G-code. - // First sort the adjustment lines by their position in the source G-code. - std::vector lines; + return elapsed_time_total0; +} + +// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed. +// Returns the adjusted G-code. +std::string CoolingBuffer::apply_layer_cooldown( + // Source G-code for the current layer. + const std::string &gcode, + // ID of the current layer, used to disable fan for the first n layers. + size_t layer_id, + // Total time of this layer after slow down, used to control the fan. + float layer_time, + // Per extruder list of G-code lines and their cool down attributes. + std::vector &per_extruder_adjustments) +{ + // First sort the adjustment lines by of multiple extruders by their position in the source G-code. + std::vector lines; { size_t n_lines = 0; - for (const Adjustment &adj : adjustments) + for (const PerExtruderAdjustments &adj : per_extruder_adjustments) n_lines += adj.lines.size(); lines.reserve(n_lines); - for (const Adjustment &adj : adjustments) - for (const Adjustment::Line &line : adj.lines) + for (const PerExtruderAdjustments &adj : per_extruder_adjustments) + for (const CoolingLine &line : adj.lines) lines.emplace_back(&line); - std::sort(lines.begin(), lines.end(), [](const Adjustment::Line *ln1, const Adjustment::Line *ln2) { return ln1->line_start < ln2->line_start; } ); + std::sort(lines.begin(), lines.end(), [](const CoolingLine *ln1, const CoolingLine *ln2) { return ln1->line_start < ln2->line_start; } ); } // Second generate the adjusted G-code. std::string new_gcode; @@ -390,8 +610,9 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ int fan_speed = -1; bool bridge_fan_control = false; int bridge_fan_speed = 0; - auto change_extruder_set_fan = [ this, layer_id, elapsed_time_total, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() { + auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() { const FullPrintConfig &config = m_gcodegen.config(); +#define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; if (layer_id >= EXTRUDER_CONFIG(disable_fan_first_layers)) { @@ -399,17 +620,18 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ float slowdown_below_layer_time = float(EXTRUDER_CONFIG(slowdown_below_layer_time)); float fan_below_layer_time = float(EXTRUDER_CONFIG(fan_below_layer_time)); if (EXTRUDER_CONFIG(cooling)) { - if (elapsed_time_total < slowdown_below_layer_time) { + if (layer_time < slowdown_below_layer_time) { // Layer time very short. Enable the fan to a full throttle. fan_speed_new = max_fan_speed; - } else if (elapsed_time_total < fan_below_layer_time) { + } else if (layer_time < fan_below_layer_time) { // Layer time quite short. Enable the fan proportionally according to the current layer time. - assert(elapsed_time_total >= slowdown_below_layer_time); - double t = (elapsed_time_total - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time); + assert(layer_time >= slowdown_below_layer_time); + double t = (layer_time - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time); fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5); } } bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed); +#undef EXTRUDER_CONFIG bridge_fan_control = bridge_fan_speed > fan_speed_new; } else { bridge_fan_control = false; @@ -421,49 +643,50 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ new_gcode += m_gcodegen.writer().set_fan(fan_speed); } }; - change_extruder_set_fan(); - const char *pos = gcode.c_str(); - int current_feedrate = 0; - for (const Adjustment::Line *line : lines) { + const char *pos = gcode.c_str(); + int current_feedrate = 0; + const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); + change_extruder_set_fan(); + for (const CoolingLine *line : lines) { const char *line_start = gcode.c_str() + line->line_start; const char *line_end = gcode.c_str() + line->line_end; if (line_start > pos) new_gcode.append(pos, line_start - pos); - if (line->type & Adjustment::Line::TYPE_SET_TOOL) { + if (line->type & CoolingLine::TYPE_SET_TOOL) { unsigned int new_extruder = (unsigned int)atoi(line_start + toolchange_prefix.size()); if (new_extruder != m_current_extruder) { m_current_extruder = new_extruder; change_extruder_set_fan(); } new_gcode.append(line_start, line_end - line_start); - } else if (line->type & Adjustment::Line::TYPE_BRIDGE_FAN_START) { + } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) { if (bridge_fan_control) new_gcode += m_gcodegen.writer().set_fan(bridge_fan_speed, true); - } else if (line->type & Adjustment::Line::TYPE_BRIDGE_FAN_END) { + } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_END) { if (bridge_fan_control) new_gcode += m_gcodegen.writer().set_fan(fan_speed, true); - } else if (line->type & Adjustment::Line::TYPE_EXTRUDE_END) { + } else if (line->type & CoolingLine::TYPE_EXTRUDE_END) { // Just remove this comment. - } else if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE | Adjustment::Line::TYPE_HAS_F)) { + } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) { // Find the start of a comment, or roll to the end of line. - const char *end = line_start; - for (; end < line_end && *end != ';'; ++ end); - // Find the 'F' word. + const char *end = line_start; + for (; end < line_end && *end != ';'; ++ end); + // Find the 'F' word. const char *fpos = strstr(line_start + 2, " F") + 2; int new_feedrate = current_feedrate; bool modify = false; assert(fpos != nullptr); if (line->slowdown) { modify = true; - new_feedrate = int(floor(60. * (line->length / line->time) + 0.5)); + new_feedrate = int(floor(60. * line->feedrate + 0.5)); } else { new_feedrate = atoi(fpos); if (new_feedrate != current_feedrate) { // Append the line without the comment. new_gcode.append(line_start, end - line_start); current_feedrate = new_feedrate; - } else if ((line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE)) || line->length == 0.) { + } else if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) { // Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment. end = line_end; } else { @@ -488,7 +711,7 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ new_gcode.append(line_start, f - line_start + 1); } // Skip the non-whitespaces of the F parameter up the comment or end of line. - for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++fpos); + for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++fpos); // Append the rest of the line without the comment. if (fpos < end) new_gcode.append(fpos, end - fpos); @@ -497,22 +720,22 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ } // Process the rest of the line. if (end < line_end) { - if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE)) { - // Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE" - std::string comment(end, line_end); - boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", ""); - if (line->type & Adjustment::Line::TYPE_EXTERNAL_PERIMETER) + if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) { + // Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE" + std::string comment(end, line_end); + boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", ""); + if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER) boost::replace_all(comment, ";_EXTERNAL_PERIMETER", ""); - if (line->type & Adjustment::Line::TYPE_WIPE) + if (line->type & CoolingLine::TYPE_WIPE) boost::replace_all(comment, ";_WIPE", ""); - new_gcode += comment; - } else { - // Just attach the rest of the source line. - new_gcode.append(end, line_end - end); - } + new_gcode += comment; + } else { + // Just attach the rest of the source line. + new_gcode.append(end, line_end - end); + } } } else { - new_gcode.append(line_start, line_end - line_start); + new_gcode.append(line_start, line_end - line_start); } pos = line_end; } diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.hpp b/xs/src/libslic3r/GCode/CoolingBuffer.hpp index f85c470b30..bf4b082e25 100644 --- a/xs/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/xs/src/libslic3r/GCode/CoolingBuffer.hpp @@ -9,13 +9,17 @@ namespace Slic3r { class GCode; class Layer; +class PerExtruderAdjustments; -/* -A standalone G-code filter, to control cooling of the print. -The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited -and the print is modified to stretch over a minimum layer time. -*/ - +// A standalone G-code filter, to control cooling of the print. +// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited +// and the print is modified to stretch over a minimum layer time. +// +// The simple it sounds, the actual implementation is significantly more complex. +// Namely, for a multi-extruder print, each material may require a different cooling logic. +// For example, some materials may not like to print too slowly, while with some materials +// we may slow down significantly. +// class CoolingBuffer { public: CoolingBuffer(GCode &gcodegen); @@ -25,7 +29,12 @@ public: GCode* gcodegen() { return &m_gcodegen; } private: - CoolingBuffer& operator=(const CoolingBuffer&); + CoolingBuffer& operator=(const CoolingBuffer&) = delete; + std::vector parse_layer_gcode(const std::string &gcode, std::vector ¤t_pos) const; + float calculate_layer_slowdown(std::vector &per_extruder_adjustments); + // Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed. + // Returns the adjusted G-code. + std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector &per_extruder_adjustments); GCode& m_gcodegen; std::string m_gcode; @@ -34,6 +43,9 @@ private: std::vector m_axis; std::vector m_current_pos; unsigned int m_current_extruder; + + // Old logic: proportional. + bool m_cooling_logic_proportional = false; }; } diff --git a/xs/src/libslic3r/GCode/PreviewData.cpp b/xs/src/libslic3r/GCode/PreviewData.cpp index 69fd3524da..d431708c1b 100644 --- a/xs/src/libslic3r/GCode/PreviewData.cpp +++ b/xs/src/libslic3r/GCode/PreviewData.cpp @@ -99,17 +99,31 @@ void GCodePreviewData::Range::set_from(const Range& other) float GCodePreviewData::Range::step_size() const { - return (max - min) / (float)Colors_Count; + return (max - min) / (float)(Colors_Count - 1); } -const GCodePreviewData::Color& GCodePreviewData::Range::get_color_at_max() const +GCodePreviewData::Color GCodePreviewData::Range::get_color_at(float value) const { - return colors[Colors_Count - 1]; -} + if (empty()) + return Color::Dummy; -const GCodePreviewData::Color& GCodePreviewData::Range::get_color_at(float value) const -{ - return empty() ? get_color_at_max() : colors[clamp((unsigned int)0, Colors_Count - 1, (unsigned int)((value - min) / step_size()))]; + float global_t = (value - min) / step_size(); + + unsigned int low = (unsigned int)global_t; + unsigned int high = clamp((unsigned int)0, Colors_Count - 1, low + 1); + + Color color_low = colors[low]; + Color color_high = colors[high]; + + float local_t = global_t - (float)low; + + // interpolate in RGB space + Color ret; + for (unsigned int i = 0; i < 4; ++i) + { + ret.rgba[i] = lerp(color_low.rgba[i], color_high.rgba[i], local_t); + } + return ret; } GCodePreviewData::LegendItem::LegendItem(const std::string& text, const GCodePreviewData::Color& color) @@ -266,22 +280,22 @@ const GCodePreviewData::Color& GCodePreviewData::get_extrusion_role_color(Extrus return extrusion.role_colors[role]; } -const GCodePreviewData::Color& GCodePreviewData::get_height_color(float height) const +GCodePreviewData::Color GCodePreviewData::get_height_color(float height) const { return ranges.height.get_color_at(height); } -const GCodePreviewData::Color& GCodePreviewData::get_width_color(float width) const +GCodePreviewData::Color GCodePreviewData::get_width_color(float width) const { return ranges.width.get_color_at(width); } -const GCodePreviewData::Color& GCodePreviewData::get_feedrate_color(float feedrate) const +GCodePreviewData::Color GCodePreviewData::get_feedrate_color(float feedrate) const { return ranges.feedrate.get_color_at(feedrate); } -const GCodePreviewData::Color& GCodePreviewData::get_volumetric_rate_color(float rate) const +GCodePreviewData::Color GCodePreviewData::get_volumetric_rate_color(float rate) const { return ranges.volumetric_rate.get_color_at(rate); } @@ -370,10 +384,10 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: list.reserve(Range::Colors_Count); float step = range.step_size(); - for (unsigned int i = 0; i < Range::Colors_Count; ++i) + for (int i = Range::Colors_Count - 1; i >= 0; --i) { char buf[1024]; - sprintf(buf, "%.*f/%.*f", decimals, scale_factor * (range.min + (float)i * step), decimals, scale_factor * (range.min + (float)(i + 1) * step)); + sprintf(buf, "%.*f", decimals, scale_factor * (range.min + (float)i * step)); list.emplace_back(buf, range.colors[i]); } } @@ -408,7 +422,7 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: } case Extrusion::Feedrate: { - Helper::FillListFromRange(items, ranges.feedrate, 0, 1.0f); + Helper::FillListFromRange(items, ranges.feedrate, 1, 1.0f); break; } case Extrusion::VolumetricRate: diff --git a/xs/src/libslic3r/GCode/PreviewData.hpp b/xs/src/libslic3r/GCode/PreviewData.hpp index e9c5f75154..a7d77e0b93 100644 --- a/xs/src/libslic3r/GCode/PreviewData.hpp +++ b/xs/src/libslic3r/GCode/PreviewData.hpp @@ -41,8 +41,7 @@ public: void set_from(const Range& other); float step_size() const; - const Color& get_color_at(float value) const; - const Color& get_color_at_max() const; + Color get_color_at(float value) const; }; struct Ranges @@ -189,10 +188,10 @@ public: bool empty() const; const Color& get_extrusion_role_color(ExtrusionRole role) const; - const Color& get_height_color(float height) const; - const Color& get_width_color(float width) const; - const Color& get_feedrate_color(float feedrate) const; - const Color& get_volumetric_rate_color(float rate) const; + Color get_height_color(float height) const; + Color get_width_color(float width) const; + Color get_feedrate_color(float feedrate) const; + Color get_volumetric_rate_color(float rate) const; void set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha); void set_extrusion_paths_colors(const std::vector& colors); diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 585977ba7f..5f50af8ac3 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -598,10 +598,10 @@ std::string Print::validate() const return "The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."; SlicingParameters slicing_params0 = this->objects.front()->slicing_parameters(); - const PrintObject* most_layered_object = this->objects.front(); // object with highest layer_height_profile.size() encountered so far + const PrintObject* tallest_object = this->objects.front(); // let's find the tallest object for (const auto* object : objects) - if (object->layer_height_profile.size() > most_layered_object->layer_height_profile.size()) - most_layered_object = object; + if (*(object->layer_height_profile.end()-2) > *(tallest_object->layer_height_profile.end()-2) ) + tallest_object = object; for (PrintObject *object : this->objects) { SlicingParameters slicing_params = object->slicing_parameters(); @@ -618,17 +618,26 @@ std::string Print::validate() const object->update_layer_height_profile(); object->layer_height_profile_valid = was_layer_height_profile_valid; - if ( this->config.variable_layer_height ) { - int i = 0; - while ( i < object->layer_height_profile.size() ) { - if (std::abs(most_layered_object->layer_height_profile[i] - object->layer_height_profile[i]) > EPSILON) - return "The Wipe tower is only supported if all objects have the same layer height profile"; - ++i; - if (i == object->layer_height_profile.size()-2) // this element contains the objects max z, if the other object is taller, - // it does not have to match - we will step over it - if (most_layered_object->layer_height_profile[i] > object->layer_height_profile[i]) - ++i; + if ( this->config.variable_layer_height ) { // comparing layer height profiles + bool failed = false; + if (tallest_object->layer_height_profile.size() >= object->layer_height_profile.size() ) { + int i = 0; + while ( i < object->layer_height_profile.size() && i < tallest_object->layer_height_profile.size()) { + if (std::abs(tallest_object->layer_height_profile[i] - object->layer_height_profile[i])) { + failed = true; + break; + } + ++i; + if (i == object->layer_height_profile.size()-2) // this element contains this objects max z + if (tallest_object->layer_height_profile[i] > object->layer_height_profile[i]) // the difference does not matter in this case + ++i; + } } + else + failed = true; + + if (failed) + return "The Wipe tower is only supported if all objects have the same layer height profile"; } /*for (size_t i = 5; i < object->layer_height_profile.size(); i += 2) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 02995baf38..f70a42182e 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1679,7 +1679,7 @@ PrintConfigDef::PrintConfigDef() "temperature control commands in the output."); def->cli = "temperature=i@"; def->full_label = L("Temperature"); - def->max = 0; + def->min = 0; def->max = max_temp; def->default_value = new ConfigOptionInts { 200 }; diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index 5727d6c89f..0066aa5e69 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -3,6 +3,8 @@ #include +#include "libslic3r.h" + namespace Slic3r { extern void set_logging_level(unsigned int level); @@ -60,6 +62,9 @@ extern std::string timestamp_str(); // 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(); } +// getpid platform wrapper +extern unsigned get_current_pid(); + // Compute the next highest power of 2 of 32-bit v // http://graphics.stanford.edu/~seander/bithacks.html template diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 0f192c37c6..4aef4d5c16 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -14,7 +14,7 @@ #include #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" -#define SLIC3R_VERSION "1.39.0" +#define SLIC3R_VERSION "1.40.0" #define SLIC3R_BUILD "UNKNOWN" typedef int32_t coord_t; diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 733757e25a..f2415ac072 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -1,6 +1,12 @@ #include #include +#ifdef WIN32 +#include +#else +#include +#endif + #include #include #include @@ -271,4 +277,13 @@ std::string timestamp_str() return buf; } +unsigned get_current_pid() +{ +#ifdef WIN32 + return GetCurrentProcessId(); +#else + return ::getpid(); +#endif +} + }; // namespace Slic3r diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index d7c9a590a0..205eec2188 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -62,8 +62,8 @@ REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection"); REGISTER_CLASS(Preset, "GUI::Preset"); REGISTER_CLASS(PresetCollection, "GUI::PresetCollection"); REGISTER_CLASS(PresetBundle, "GUI::PresetBundle"); -REGISTER_CLASS(PresetHints, "GUI::PresetHints"); REGISTER_CLASS(TabIface, "GUI::Tab"); +REGISTER_CLASS(PresetUpdater, "PresetUpdater"); REGISTER_CLASS(OctoPrint, "OctoPrint"); SV* ConfigBase__as_hash(ConfigBase* THIS) diff --git a/xs/src/semver/semver.c b/xs/src/semver/semver.c index 29bc1868d3..527738644d 100644 --- a/xs/src/semver/semver.c +++ b/xs/src/semver/semver.c @@ -175,6 +175,9 @@ semver_parse_version (const char *str, semver_t *ver) { slice = (char *) str; index = 0; + // non mandatory + ver->patch = 0; + while (slice != NULL && index++ < 4) { next = strchr(slice, DELIMITER[0]); if (next == NULL) @@ -200,7 +203,8 @@ semver_parse_version (const char *str, semver_t *ver) { slice = next + 1; } - return 0; + // Major and minor versions are mandatory, patch version is not mandatory. + return (index == 2 || index == 3) ? 0 : -1; } static int @@ -615,3 +619,22 @@ semver_numeric (semver_t *x) { return num; } + +char *semver_strdup(const char *src) { + if (src == NULL) return NULL; + size_t len = strlen(src) + 1; + char *res = malloc(len); + return res != NULL ? (char *) memcpy(res, src, len) : NULL; +} + +semver_t +semver_copy(const semver_t *ver) { + semver_t res = *ver; + if (ver->metadata != NULL) { + res.metadata = strdup(ver->metadata); + } + if (ver->prerelease != NULL) { + res.prerelease = strdup(ver->prerelease); + } + return res; +} diff --git a/xs/src/semver/semver.h b/xs/src/semver/semver.h index 1b48670ca3..01a15fc43e 100644 --- a/xs/src/semver/semver.h +++ b/xs/src/semver/semver.h @@ -98,6 +98,12 @@ semver_is_valid (const char *s); int semver_clean (char *s); +char * +semver_strdup(const char *src); + +semver_t +semver_copy(const semver_t *ver); + #ifdef __cplusplus } #endif diff --git a/xs/src/slic3r/Config/Snapshot.cpp b/xs/src/slic3r/Config/Snapshot.cpp index 559e4c63cb..704fbcfa1b 100644 --- a/xs/src/slic3r/Config/Snapshot.cpp +++ b/xs/src/slic3r/Config/Snapshot.cpp @@ -1,11 +1,13 @@ #include "Snapshot.hpp" #include "../GUI/AppConfig.hpp" +#include "../GUI/PresetBundle.hpp" #include "../Utils/Time.hpp" #include #include #include +#include #include #include #include @@ -56,6 +58,7 @@ void Snapshot::load_ini(const std::string &path) // Parse snapshot.ini std::string group_name_vendor = "Vendor:"; std::string key_filament = "filament"; + std::string key_prefix_model = "model_"; for (auto §ion : tree) { if (section.first == "snapshot") { // Parse the common section. @@ -79,6 +82,8 @@ void Snapshot::load_ini(const std::string &path) this->reason = SNAPSHOT_UPGRADE; else if (rsn == "downgrade") this->reason = SNAPSHOT_DOWNGRADE; + else if (rsn == "before_rollback") + this->reason = SNAPSHOT_BEFORE_ROLLBACK; else if (rsn == "user") this->reason = SNAPSHOT_USER; else @@ -106,24 +111,49 @@ void Snapshot::load_ini(const std::string &path) VendorConfig vc; vc.name = section.first.substr(group_name_vendor.size()); for (auto &kvp : section.second) { - if (boost::starts_with(kvp.first, "model_")) { - //model:MK2S = 0.4;xxx - //model:MK3 = 0.4;xxx - } else if (kvp.first == "version" || kvp.first == "min_slic3r_version" || kvp.first == "max_slic3r_version") { + if (kvp.first == "version" || kvp.first == "min_slic3r_version" || kvp.first == "max_slic3r_version") { // Version of the vendor specific config bundle bundled with this snapshot. auto semver = Semver::parse(kvp.second.data()); if (! semver) throw_on_parse_error("invalid " + kvp.first + " format for " + section.first); if (kvp.first == "version") - vc.version = *semver; + vc.version.config_version = *semver; else if (kvp.first == "min_slic3r_version") - vc.min_slic3r_version = *semver; + vc.version.min_slic3r_version = *semver; else - vc.max_slic3r_version = *semver; - } + vc.version.max_slic3r_version = *semver; + } else if (boost::starts_with(kvp.first, key_prefix_model) && kvp.first.size() > key_prefix_model.size()) { + // Parse the printer variants installed for the current model. + auto &set_variants = vc.models_variants_installed[kvp.first.substr(key_prefix_model.size())]; + std::vector variants; + if (unescape_strings_cstyle(kvp.second.data(), variants)) + for (auto &variant : variants) + set_variants.insert(std::move(variant)); + } } + this->vendor_configs.emplace_back(std::move(vc)); } } + // Sort the vendors lexicographically. + std::sort(this->vendor_configs.begin(), this->vendor_configs.begin(), + [](const VendorConfig &cfg1, const VendorConfig &cfg2) { return cfg1.name < cfg2.name; }); +} + +static std::string reason_string(const Snapshot::Reason reason) +{ + switch (reason) { + case Snapshot::SNAPSHOT_UPGRADE: + return "upgrade"; + case Snapshot::SNAPSHOT_DOWNGRADE: + return "downgrade"; + case Snapshot::SNAPSHOT_BEFORE_ROLLBACK: + return "before_rollback"; + case Snapshot::SNAPSHOT_USER: + return "user"; + case Snapshot::SNAPSHOT_UNKNOWN: + default: + return "unknown"; + } } void Snapshot::save_ini(const std::string &path) @@ -138,7 +168,7 @@ void Snapshot::save_ini(const std::string &path) c << "time_captured = " << Slic3r::Utils::format_time_ISO8601Z(this->time_captured) << std::endl; c << "slic3r_version_captured = " << this->slic3r_version_captured.to_string() << std::endl; c << "comment = " << this->comment << std::endl; - c << "reason = " << this->reason << std::endl; + c << "reason = " << reason_string(this->reason) << std::endl; // Export the active presets at the time of the snapshot. c << std::endl << "[presets]" << std::endl; @@ -151,9 +181,17 @@ void Snapshot::save_ini(const std::string &path) // Export the vendor configs. for (const VendorConfig &vc : this->vendor_configs) { c << std::endl << "[Vendor:" << vc.name << "]" << std::endl; - c << "version = " << vc.version.to_string() << std::endl; - c << "min_slic3r_version = " << vc.min_slic3r_version.to_string() << std::endl; - c << "max_slic3r_version = " << vc.max_slic3r_version.to_string() << std::endl; + c << "version = " << vc.version.config_version.to_string() << std::endl; + c << "min_slic3r_version = " << vc.version.min_slic3r_version.to_string() << std::endl; + c << "max_slic3r_version = " << vc.version.max_slic3r_version.to_string() << std::endl; + // Export installed printer models and their variants. + for (const auto &model : vc.models_variants_installed) { + if (model.second.size() == 0) + continue; + const std::vector variants(model.second.begin(), model.second.end()); + const auto escaped = escape_strings_cstyle(variants); + c << "model_" << model.first << " = " << escaped << std::endl; + } } c.close(); } @@ -172,6 +210,82 @@ void Snapshot::export_selections(AppConfig &config) const config.set("presets", "printer", printer); } +void Snapshot::export_vendor_configs(AppConfig &config) const +{ + std::map>> vendors; + for (const VendorConfig &vc : vendor_configs) + vendors[vc.name] = vc.models_variants_installed; + config.set_vendors(std::move(vendors)); +} + +// Perform a deep compare of the active print / filament / printer / vendor directories. +// Return true if the content of the current print / filament / printer / vendor directories +// matches the state stored in this snapshot. +bool Snapshot::equal_to_active(const AppConfig &app_config) const +{ + // 1) Check, whether this snapshot contains the same set of active vendors, printer models and variants + // as app_config. + { + std::set matched; + for (const VendorConfig &vc : this->vendor_configs) { + auto it_vendor_models_variants = app_config.vendors().find(vc.name); + if (it_vendor_models_variants == app_config.vendors().end() || + it_vendor_models_variants->second != vc.models_variants_installed) + // There are more vendors enabled in the snapshot than currently installed. + return false; + matched.insert(vc.name); + } + for (const std::pair>> &v : app_config.vendors()) + if (matched.find(v.first) == matched.end() && ! v.second.empty()) + // There are more vendors currently installed than enabled in the snapshot. + return false; + } + + // 2) Check, whether this snapshot references the same set of ini files as the current state. + boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); + boost::filesystem::path snapshot_dir = boost::filesystem::path(Slic3r::data_dir()) / SLIC3R_SNAPSHOTS_DIR / this->id; + for (const char *subdir : { "print", "filament", "printer", "vendor" }) { + boost::filesystem::path path1 = data_dir / subdir; + boost::filesystem::path path2 = snapshot_dir / subdir; + std::vector files1, files2; + for (auto &dir_entry : boost::filesystem::directory_iterator(path1)) + if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) + files1.emplace_back(dir_entry.path().filename().string()); + for (auto &dir_entry : boost::filesystem::directory_iterator(path2)) + if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) + files2.emplace_back(dir_entry.path().filename().string()); + std::sort(files1.begin(), files1.end()); + std::sort(files2.begin(), files2.end()); + if (files1 != files2) + return false; + for (const std::string &filename : files1) { + FILE *f1 = boost::nowide::fopen((path1 / filename).string().c_str(), "rb"); + FILE *f2 = boost::nowide::fopen((path2 / filename).string().c_str(), "rb"); + bool same = true; + if (f1 && f2) { + char buf1[4096]; + char buf2[4096]; + do { + size_t r1 = fread(buf1, 1, 4096, f1); + size_t r2 = fread(buf2, 1, 4096, f2); + if (r1 != r2 || memcmp(buf1, buf2, r1)) { + same = false; + break; + } + } while (! feof(f1) || ! feof(f2)); + } else + same = false; + if (f1) + fclose(f1); + if (f2) + fclose(f2); + if (! same) + return false; + } + } + return true; +} + size_t SnapshotDB::load_db() { boost::filesystem::path snapshots_dir = SnapshotDB::create_db_dir(); @@ -199,12 +313,29 @@ size_t SnapshotDB::load_db() } m_snapshots.emplace_back(std::move(snapshot)); } - + // Sort the snapshots by their date/time. + std::sort(m_snapshots.begin(), m_snapshots.end(), [](const Snapshot &s1, const Snapshot &s2) { return s1.time_captured < s2.time_captured; }); if (! errors_cummulative.empty()) throw std::runtime_error(errors_cummulative); return m_snapshots.size(); } +void SnapshotDB::update_slic3r_versions(std::vector &index_db) +{ + for (Snapshot &snapshot : m_snapshots) { + for (Snapshot::VendorConfig &vendor_config : snapshot.vendor_configs) { + auto it = std::find_if(index_db.begin(), index_db.end(), [&vendor_config](const Index &idx) { return idx.vendor() == vendor_config.name; }); + if (it != index_db.end()) { + Index::const_iterator it_version = it->find(vendor_config.version.config_version); + if (it_version != it->end()) { + vendor_config.version.min_slic3r_version = it_version->min_slic3r_version; + vendor_config.version.max_slic3r_version = it_version->max_slic3r_version; + } + } + } + } +} + static void copy_config_dir_single_level(const boost::filesystem::path &path_src, const boost::filesystem::path &path_dst) { if (! boost::filesystem::is_directory(path_dst) && @@ -225,7 +356,7 @@ static void delete_existing_ini_files(const boost::filesystem::path &path) boost::filesystem::remove(dir_entry.path()); } -const Snapshot& SnapshotDB::make_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment) +const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment) { boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir(); @@ -235,7 +366,7 @@ const Snapshot& SnapshotDB::make_snapshot(const AppConfig &app_config, Snapshot: // Snapshot header. snapshot.time_captured = Slic3r::Utils::get_current_time_utc(); snapshot.id = Slic3r::Utils::format_time_ISO8601Z(snapshot.time_captured); - snapshot.slic3r_version_captured = *Semver::parse(SLIC3R_VERSION); + snapshot.slic3r_version_captured = *Semver::parse(SLIC3R_VERSION); // XXX: have Semver Slic3r version snapshot.comment = comment; snapshot.reason = reason; // Active presets at the time of the snapshot. @@ -250,22 +381,54 @@ const Snapshot& SnapshotDB::make_snapshot(const AppConfig &app_config, Snapshot: snapshot.filaments.emplace_back(app_config.get("presets", name)); } // Vendor specific config bundles and installed printers. + for (const std::pair>> &vendor : app_config.vendors()) { + Snapshot::VendorConfig cfg; + cfg.name = vendor.first; + cfg.models_variants_installed = vendor.second; + for (auto it = cfg.models_variants_installed.begin(); it != cfg.models_variants_installed.end();) + if (it->second.empty()) + cfg.models_variants_installed.erase(it ++); + else + ++ it; + // Read the active config bundle, parse the config version. + PresetBundle bundle; + bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); + for (const VendorProfile &vp : bundle.vendors) + if (vp.id == cfg.name) + cfg.version.config_version = vp.config_version; + // Fill-in the min/max slic3r version from the config index, if possible. + try { + // Load the config index for the vendor. + Index index; + index.load(data_dir / "vendor" / (cfg.name + ".idx")); + auto it = index.find(cfg.version.config_version); + if (it != index.end()) { + cfg.version.min_slic3r_version = it->min_slic3r_version; + cfg.version.max_slic3r_version = it->max_slic3r_version; + } + } catch (const std::runtime_error &err) { + } + snapshot.vendor_configs.emplace_back(std::move(cfg)); + } + + boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id; + boost::filesystem::create_directory(snapshot_dir); // Backup the presets. - boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id; for (const char *subdir : { "print", "filament", "printer", "vendor" }) copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir); snapshot.save_ini((snapshot_dir / "snapshot.ini").string()); + assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured); m_snapshots.emplace_back(std::move(snapshot)); return m_snapshots.back(); } -void SnapshotDB::restore_snapshot(const std::string &id, AppConfig &app_config) +const Snapshot& SnapshotDB::restore_snapshot(const std::string &id, AppConfig &app_config) { for (const Snapshot &snapshot : m_snapshots) if (snapshot.id == id) { this->restore_snapshot(snapshot, app_config); - return; + return snapshot; } throw std::runtime_error(std::string("Snapshot with id " + id + " was not found.")); } @@ -275,18 +438,59 @@ void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_confi boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir(); boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id; - // Remove existing ini files and restore the ini files from the snapshot. for (const char *subdir : { "print", "filament", "printer", "vendor" }) { delete_existing_ini_files(data_dir / subdir); copy_config_dir_single_level(snapshot_dir / subdir, data_dir / subdir); } - - // Update app_config from the snapshot. + // Update AppConfig with the selections of the print / filament / printer profiles + // and about the installed printer types and variants. snapshot.export_selections(app_config); + snapshot.export_vendor_configs(app_config); +} - // Store information about the snapshot. +bool SnapshotDB::is_on_snapshot(AppConfig &app_config) const +{ + // Is the "on_snapshot" configuration value set? + std::string on_snapshot = app_config.get("on_snapshot"); + if (on_snapshot.empty()) + // No, we are not on a snapshot. + return false; + // Is the "on_snapshot" equal to the current configuration state? + auto it_snapshot = this->snapshot(on_snapshot); + if (it_snapshot != this->end() && it_snapshot->equal_to_active(app_config)) + // Yes, we are on the snapshot. + return true; + // No, we are no more on a snapshot. Reset the state. + app_config.set("on_snapshot", ""); + return false; +} +SnapshotDB::const_iterator SnapshotDB::snapshot_with_vendor_preset(const std::string &vendor_name, const Semver &config_version) +{ + auto it_found = m_snapshots.end(); + Snapshot::VendorConfig key; + key.name = vendor_name; + for (auto it = m_snapshots.begin(); it != m_snapshots.end(); ++ it) { + const Snapshot &snapshot = *it; + auto it_vendor_config = std::lower_bound(snapshot.vendor_configs.begin(), snapshot.vendor_configs.end(), + key, [](const Snapshot::VendorConfig &cfg1, const Snapshot::VendorConfig &cfg2) { return cfg1.name < cfg2.name; }); + if (it_vendor_config != snapshot.vendor_configs.end() && it_vendor_config->name == vendor_name && + config_version == it_vendor_config->version.config_version) { + // Vendor config found with the correct version. + // Save it, but continue searching, as we want the newest snapshot. + it_found = it; + } + } + return it_found; +} + +SnapshotDB::const_iterator SnapshotDB::snapshot(const std::string &id) const +{ + for (const_iterator it = m_snapshots.begin(); it != m_snapshots.end(); ++ it) + if (it->id == id) + return it; + return m_snapshots.end(); } boost::filesystem::path SnapshotDB::create_db_dir() @@ -303,6 +507,26 @@ boost::filesystem::path SnapshotDB::create_db_dir() return snapshots_dir; } +SnapshotDB& SnapshotDB::singleton() +{ + static SnapshotDB instance; + static bool loaded = false; + if (! loaded) { + try { + loaded = true; + // Load the snapshot database. + instance.load_db(); + // Load the vendor specific configuration indices. + std::vector index_db = Index::load_db(); + // Update the min / max slic3r versions compatible with the configurations stored inside the snapshots + // based on the min / max slic3r versions defined by the vendor specific config indices. + instance.update_slic3r_versions(index_db); + } catch (std::exception &ex) { + } + } + return instance; +} + } // namespace Config } // namespace GUI } // namespace Slic3r diff --git a/xs/src/slic3r/Config/Snapshot.hpp b/xs/src/slic3r/Config/Snapshot.hpp index 358797bf76..a916dfe92a 100644 --- a/xs/src/slic3r/Config/Snapshot.hpp +++ b/xs/src/slic3r/Config/Snapshot.hpp @@ -1,11 +1,14 @@ #ifndef slic3r_GUI_Snapshot_ #define slic3r_GUI_Snapshot_ +#include +#include #include #include #include +#include "Version.hpp" #include "../Utils/Semver.hpp" namespace Slic3r { @@ -15,6 +18,8 @@ class AppConfig; namespace GUI { namespace Config { +class Index; + // A snapshot contains: // Slic3r.ini // vendor/ @@ -28,6 +33,7 @@ public: SNAPSHOT_UNKNOWN, SNAPSHOT_UPGRADE, SNAPSHOT_DOWNGRADE, + SNAPSHOT_BEFORE_ROLLBACK, SNAPSHOT_USER, }; @@ -39,6 +45,12 @@ public: // Export the print / filament / printer selections to be activated into the AppConfig. void export_selections(AppConfig &config) const; + void export_vendor_configs(AppConfig &config) const; + + // Perform a deep compare of the active print / filament / printer / vendor directories. + // Return true if the content of the current print / filament / printer / vendor directories + // matches the state stored in this snapshot. + bool equal_to_active(const AppConfig &app_config) const; // ID of a snapshot should equal to the name of the snapshot directory. // The ID contains the date/time, reason and comment to be human readable. @@ -50,6 +62,8 @@ public: std::string comment; Reason reason; + std::string format_reason() const; + // Active presets at the time of the snapshot. std::string print; std::vector filaments; @@ -61,41 +75,50 @@ public: struct VendorConfig { // Name of the vendor contained in this snapshot. std::string name; - // Version of the vendor config contained in this snapshot. - Semver version = Semver::invalid(); - // Minimum Slic3r version compatible with this vendor configuration. - Semver min_slic3r_version = Semver::zero(); - // Maximum Slic3r version compatible with this vendor configuration, or empty. - Semver max_slic3r_version = Semver::inf(); + // Version of the vendor config contained in this snapshot, along with compatibility data. + Version version; + // Which printer models of this vendor were installed, and which variants of the models? + std::map> models_variants_installed; }; - // List of vendor configs contained in this snapshot. + // List of vendor configs contained in this snapshot, sorted lexicographically. std::vector vendor_configs; }; class SnapshotDB { public: + // Initialize the SnapshotDB singleton instance. Load the database if it has not been loaded yet. + static SnapshotDB& singleton(); + typedef std::vector::const_iterator const_iterator; // Load the snapshot database from the snapshots directory. // If the snapshot directory or its parent does not exist yet, it will be created. // Returns a number of snapshots loaded. size_t load_db(); + void update_slic3r_versions(std::vector &index_db); // Create a snapshot directory, copy the vendor config bundles, user print/filament/printer profiles, // create an index. - const Snapshot& make_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment); - void restore_snapshot(const std::string &id, AppConfig &app_config); + const Snapshot& take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment = ""); + const Snapshot& restore_snapshot(const std::string &id, AppConfig &app_config); void restore_snapshot(const Snapshot &snapshot, AppConfig &app_config); + // Test whether the AppConfig's on_snapshot variable points to an existing snapshot, and the existing snapshot + // matches the current state. If it does not match the current state, the AppConfig's "on_snapshot" ID is reset. + bool is_on_snapshot(AppConfig &app_config) const; + // Finds the newest snapshot, which contains a config bundle for vendor_name with config_version. + const_iterator snapshot_with_vendor_preset(const std::string &vendor_name, const Semver &config_version); const_iterator begin() const { return m_snapshots.begin(); } const_iterator end() const { return m_snapshots.end(); } + const_iterator snapshot(const std::string &id) const; const std::vector& snapshots() const { return m_snapshots; } private: // Create the snapshots directory if it does not exist yet. static boost::filesystem::path create_db_dir(); + // Snapshots are sorted by their date/time, oldest first. std::vector m_snapshots; }; diff --git a/xs/src/slic3r/Config/Version.cpp b/xs/src/slic3r/Config/Version.cpp index 1102f31494..a85322ecaf 100644 --- a/xs/src/slic3r/Config/Version.cpp +++ b/xs/src/slic3r/Config/Version.cpp @@ -6,18 +6,140 @@ #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/Config.hpp" +#include "../../libslic3r/FileParserError.hpp" +#include "../../libslic3r/Utils.hpp" namespace Slic3r { namespace GUI { namespace Config { -static boost::optional s_current_slic3r_semver = Semver::parse(SLIC3R_VERSION); +static const Semver s_current_slic3r_semver(SLIC3R_VERSION); + +// Optimized lexicographic compare of two pre-release versions, ignoring the numeric suffix. +static int compare_prerelease(const char *p1, const char *p2) +{ + for (;;) { + char c1 = *p1 ++; + char c2 = *p2 ++; + bool a1 = std::isalpha(c1) && c1 != 0; + bool a2 = std::isalpha(c2) && c2 != 0; + if (a1) { + if (a2) { + if (c1 != c2) + return (c1 < c2) ? -1 : 1; + } else + return 1; + } else { + if (a2) + return -1; + else + return 0; + } + } + // This shall never happen. + return 0; +} + +bool Version::is_slic3r_supported(const Semver &slic3r_version) const +{ + if (! slic3r_version.in_range(min_slic3r_version, max_slic3r_version)) + return false; + // Now verify, whether the configuration pre-release status is compatible with the Slic3r's pre-release status. + // Alpha Slic3r will happily load any configuration, while beta Slic3r will ignore alpha configurations etc. + const char *prerelease_slic3r = slic3r_version.prerelease(); + const char *prerelease_config = this->config_version.prerelease(); + if (prerelease_config == nullptr) + // Released config is always supported. + return true; + else if (prerelease_slic3r == nullptr) + // Released slic3r only supports released configs. + return false; + // Compare the pre-release status of Slic3r against the config. + // If the prerelease status of slic3r is lexicographically lower or equal + // to the prerelease status of the config, accept it. + return compare_prerelease(prerelease_slic3r, prerelease_config) != 1; +} bool Version::is_current_slic3r_supported() const { - return this->is_slic3r_supported(*s_current_slic3r_semver); + return this->is_slic3r_supported(s_current_slic3r_semver); } +#if 0 +//TODO: This test should be moved to a unit test, once we have C++ unit tests in place. +static int version_test() +{ + Version v; + v.config_version = *Semver::parse("1.1.2"); + v.min_slic3r_version = *Semver::parse("1.38.0"); + v.max_slic3r_version = Semver::inf(); + assert(v.is_slic3r_supported(*Semver::parse("1.38.0"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha"))); + // Test the prerelease status. + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0"))); + v.config_version = *Semver::parse("1.1.2-alpha"); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + v.config_version = *Semver::parse("1.1.2-alpha1"); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + v.config_version = *Semver::parse("1.1.2-beta"); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + v.config_version = *Semver::parse("1.1.2-rc"); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + v.config_version = *Semver::parse("1.1.2-rc2"); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc"))); + assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + // Test the upper boundary. + v.config_version = *Semver::parse("1.1.2"); + v.max_slic3r_version = *Semver::parse("1.39.3-beta1"); + assert(v.is_slic3r_supported(*Semver::parse("1.38.0"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha1"))); + assert(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha"))); + return 0; +} +static int version_test_run = version_test(); +#endif + inline char* left_trim(char *c) { for (; *c == ' ' || *c == '\t'; ++ c); @@ -38,12 +160,13 @@ inline std::string unquote_value(char *value, char *end, const std::string &path if (value == end) { // Empty string is a valid string. } else if (*value == '"') { - if (++ value < -- end || *end != '"') + if (++ value > -- end || *end != '"') throw file_parser_error("String not enquoted correctly", path, idx_line); *end = 0; if (! unescape_string_cstyle(value, svalue)) throw file_parser_error("Invalid escape sequence inside a quoted string", path, idx_line); - } + } else + svalue.assign(value, end); return svalue; } @@ -53,20 +176,22 @@ inline std::string unquote_version_comment(char *value, char *end, const std::st if (value == end) { // Empty string is a valid string. } else if (*value == '"') { - if (++ value < -- end || *end != '"') + if (++ value > -- end || *end != '"') throw file_parser_error("Version comment not enquoted correctly", path, idx_line); *end = 0; if (! unescape_string_cstyle(value, svalue)) throw file_parser_error("Invalid escape sequence inside a quoted version comment", path, idx_line); - } + } else + svalue.assign(value, end); return svalue; } -size_t Index::load(const std::string &path) +size_t Index::load(const boost::filesystem::path &path) { m_configs.clear(); + m_vendor = path.stem().string(); - boost::nowide::ifstream ifs(path); + boost::nowide::ifstream ifs(path.string()); std::string line; size_t idx_line = 0; Version ver; @@ -74,63 +199,121 @@ size_t Index::load(const std::string &path) ++ idx_line; // Skip the initial white spaces. char *key = left_trim(const_cast(line.data())); + if (*key == '#') + // Skip a comment line. + continue; // Right trim the line. char *end = right_trim(key); + if (key == end) + // Skip an empty line. + continue; // Keyword may only contain alphanumeric characters. Semantic version may in addition contain "+.-". char *key_end = key; - bool maybe_semver = false; - for (;; ++ key) { - if (strchr("+.-", *key) != nullptr) - maybe_semver = true; - else if (! std::isalnum(*key)) - break; + bool maybe_semver = true; + for (; *key_end != 0; ++ key_end) { + if (std::isalnum(*key_end) || strchr("+.-", *key_end) != nullptr) { + // It may be a semver. + } else if (*key_end == '_') { + // Cannot be a semver, but it may be a key. + maybe_semver = false; + } else + // End of semver or keyword. + break; } - if (*key != 0 && *key != ' ' && *key != '\t' && *key != '=') + if (*key_end != 0 && *key_end != ' ' && *key_end != '\t' && *key_end != '=') throw file_parser_error("Invalid keyword or semantic version", path, idx_line); - *key_end = 0; + char *value = left_trim(key_end); + bool key_value_pair = *value == '='; + if (key_value_pair) + value = left_trim(value + 1); + *key_end = 0; boost::optional semver; if (maybe_semver) semver = Semver::parse(key); - char *value = left_trim(key_end); - if (*value == '=') { + if (key_value_pair) { if (semver) - throw file_parser_error("Key cannot be a semantic version", path, idx_line); + throw file_parser_error("Key cannot be a semantic version", path, idx_line);\ // Verify validity of the key / value pair. - std::string svalue = unquote_value(left_trim(++ value), end, path, idx_line); - if (key == "min_sic3r_version" || key == "max_slic3r_version") { + std::string svalue = unquote_value(value, end, path.string(), idx_line); + if (strcmp(key, "min_slic3r_version") == 0 || strcmp(key, "max_slic3r_version") == 0) { if (! svalue.empty()) - semver = Semver::parse(key); + semver = Semver::parse(svalue); if (! semver) throw file_parser_error(std::string(key) + " must referece a valid semantic version", path, idx_line); - if (key == "min_sic3r_version") + if (strcmp(key, "min_slic3r_version") == 0) ver.min_slic3r_version = *semver; else ver.max_slic3r_version = *semver; } else { // Ignore unknown keys, as there may come new keys in the future. } + continue; } if (! semver) throw file_parser_error("Invalid semantic version", path, idx_line); ver.config_version = *semver; - ver.comment = (end <= key_end) ? "" : unquote_version_comment(value, end, path, idx_line); + ver.comment = (end <= key_end) ? "" : unquote_version_comment(value, end, path.string(), idx_line); m_configs.emplace_back(ver); } + // Sort the configs by their version. + std::sort(m_configs.begin(), m_configs.end(), [](const Version &v1, const Version &v2) { return v1.config_version < v2.config_version; }); return m_configs.size(); } +Semver Index::version() const +{ + Semver ver = Semver::zero(); + for (const Version &cv : m_configs) + if (cv.config_version >= ver) + ver = cv.config_version; + return ver; +} + +Index::const_iterator Index::find(const Semver &ver) const +{ + Version key; + key.config_version = ver; + auto it = std::lower_bound(m_configs.begin(), m_configs.end(), key, + [](const Version &v1, const Version &v2) { return v1.config_version < v2.config_version; }); + return (it == m_configs.end() || it->config_version == ver) ? it : m_configs.end(); +} + Index::const_iterator Index::recommended() const { int idx = -1; - const_iterator highest = m_configs.end(); + const_iterator highest = this->end(); for (const_iterator it = this->begin(); it != this->end(); ++ it) if (it->is_current_slic3r_supported() && - (highest == this->end() || highest->max_slic3r_version < it->max_slic3r_version)) + (highest == this->end() || highest->config_version < it->config_version)) highest = it; return highest; } +std::vector Index::load_db() +{ + boost::filesystem::path cache_dir = boost::filesystem::path(Slic3r::data_dir()) / "cache"; + + std::vector index_db; + std::string errors_cummulative; + for (auto &dir_entry : boost::filesystem::directory_iterator(cache_dir)) + if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".idx")) { + Index idx; + try { + idx.load(dir_entry.path()); + } catch (const std::runtime_error &err) { + errors_cummulative += err.what(); + errors_cummulative += "\n"; + continue; + } + index_db.emplace_back(std::move(idx)); + } + + if (! errors_cummulative.empty()) + throw std::runtime_error(errors_cummulative); + return index_db; +} + } // namespace Config } // namespace GUI } // namespace Slic3r diff --git a/xs/src/slic3r/Config/Version.hpp b/xs/src/slic3r/Config/Version.hpp index 7af1d4b5b7..acb0ae4600 100644 --- a/xs/src/slic3r/Config/Version.hpp +++ b/xs/src/slic3r/Config/Version.hpp @@ -4,6 +4,8 @@ #include #include +#include + #include "../../libslic3r/FileParserError.hpp" #include "../Utils/Semver.hpp" @@ -25,7 +27,7 @@ struct Version // Single comment line. std::string comment; - bool is_slic3r_supported(const Semver &slicer_version) const { return slicer_version.in_range(min_slic3r_version, max_slic3r_version); } + bool is_slic3r_supported(const Semver &slicer_version) const; bool is_current_slic3r_supported() const; }; @@ -54,17 +56,28 @@ public: typedef std::vector::const_iterator const_iterator; // Read a config index file in the simple format described in the Index class comment. // Throws Slic3r::file_parser_error and the standard std file access exceptions. - size_t load(const std::string &path); + size_t load(const boost::filesystem::path &path); + + const std::string& vendor() const { return m_vendor; } + // Returns version of the index as the highest version of all the configs. + // If there is no config, Semver::zero() is returned. + Semver version() const; const_iterator begin() const { return m_configs.begin(); } const_iterator end() const { return m_configs.end(); } + const_iterator find(const Semver &ver) const; const std::vector& configs() const { return m_configs; } // Finds a recommended config to be installed for the current Slic3r version. // Returns configs().end() if such version does not exist in the index. This shall never happen // if the index is valid. const_iterator recommended() const; + // Load all vendor specific indices. + // Throws Slic3r::file_parser_error and the standard std file access exceptions. + static std::vector load_db(); + private: + std::string m_vendor; std::vector m_configs; }; diff --git a/xs/src/slic3r/GUI/2DBed.cpp b/xs/src/slic3r/GUI/2DBed.cpp index c5d68400da..6d788cf340 100644 --- a/xs/src/slic3r/GUI/2DBed.cpp +++ b/xs/src/slic3r/GUI/2DBed.cpp @@ -1,4 +1,4 @@ -#include "2DBed.hpp"; +#include "2DBed.hpp" #include #include "BoundingBox.hpp" @@ -66,7 +66,7 @@ void Bed_2D::repaint() shift.y - (cbb.max.y - GetSize().GetHeight())); // draw bed fill - dc.SetBrush(*new wxBrush(*new wxColour(255, 255, 255), wxSOLID)); + dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxSOLID)); wxPointList pt_list; for (auto pt: m_bed_shape) { @@ -87,7 +87,7 @@ void Bed_2D::repaint() } polylines = intersection_pl(polylines, bed_polygon); - dc.SetPen(*new wxPen(*new wxColour(230, 230, 230), 1, wxSOLID)); + dc.SetPen(wxPen(wxColour(230, 230, 230), 1, wxSOLID)); for (auto pl : polylines) { for (size_t i = 0; i < pl.points.size()-1; i++){ @@ -98,8 +98,8 @@ void Bed_2D::repaint() } // draw bed contour - dc.SetPen(*new wxPen(*new wxColour(0, 0, 0), 1, wxSOLID)); - dc.SetBrush(*new wxBrush(*new wxColour(0, 0, 0), wxTRANSPARENT)); + dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxSOLID)); + dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxTRANSPARENT)); dc.DrawPolygon(&pt_list, 0, 0); auto origin_px = to_pixels(Pointf(0, 0)); @@ -108,7 +108,7 @@ void Bed_2D::repaint() auto axes_len = 50; auto arrow_len = 6; auto arrow_angle = Geometry::deg2rad(45.0); - dc.SetPen(*new wxPen(*new wxColour(255, 0, 0), 2, wxSOLID)); // red + dc.SetPen(wxPen(wxColour(255, 0, 0), 2, wxSOLID)); // red auto x_end = Pointf(origin_px.x + axes_len, origin_px.y); dc.DrawLine(wxPoint(origin_px.x, origin_px.y), wxPoint(x_end.x, x_end.y)); for (auto angle : { -arrow_angle, arrow_angle }){ @@ -118,7 +118,7 @@ void Bed_2D::repaint() dc.DrawLine(wxPoint(x_end.x, x_end.y), wxPoint(end.x, end.y)); } - dc.SetPen(*new wxPen(*new wxColour(0, 255, 0), 2, wxSOLID)); // green + dc.SetPen(wxPen(wxColour(0, 255, 0), 2, wxSOLID)); // green auto y_end = Pointf(origin_px.x, origin_px.y - axes_len); dc.DrawLine(wxPoint(origin_px.x, origin_px.y), wxPoint(y_end.x, y_end.y)); for (auto angle : { -arrow_angle, arrow_angle }) { @@ -129,19 +129,23 @@ void Bed_2D::repaint() } // draw origin - dc.SetPen(*new wxPen(*new wxColour(0, 0, 0), 1, wxSOLID)); - dc.SetBrush(*new wxBrush(*new wxColour(0, 0, 0), wxSOLID)); + dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxSOLID)); + dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxSOLID)); dc.DrawCircle(origin_px.x, origin_px.y, 3); - dc.SetTextForeground(*new wxColour(0, 0, 0)); - dc.SetFont(*new wxFont(10, wxDEFAULT, wxNORMAL, wxNORMAL)); - dc.DrawText("(0,0)", origin_px.x + 1, origin_px.y + 2); + static const auto origin_label = wxString("(0,0)"); + dc.SetTextForeground(wxColour(0, 0, 0)); + dc.SetFont(wxFont(10, wxDEFAULT, wxNORMAL, wxNORMAL)); + auto extent = dc.GetTextExtent(origin_label); + const auto origin_label_x = origin_px.x <= cw / 2 ? origin_px.x + 1 : origin_px.x - 1 - extent.GetWidth(); + const auto origin_label_y = origin_px.y <= ch / 2 ? origin_px.y + 1 : origin_px.y - 1 - extent.GetHeight(); + dc.DrawText(origin_label, origin_label_x, origin_label_y); // draw current position if (m_pos!= Pointf(0, 0)) { auto pos_px = to_pixels(m_pos); - dc.SetPen(*new wxPen(*new wxColour(200, 0, 0), 2, wxSOLID)); - dc.SetBrush(*new wxBrush(*new wxColour(200, 0, 0), wxTRANSPARENT)); + dc.SetPen(wxPen(wxColour(200, 0, 0), 2, wxSOLID)); + dc.SetBrush(wxBrush(wxColour(200, 0, 0), wxTRANSPARENT)); dc.DrawCircle(pos_px.x, pos_px.y, 5); dc.DrawLine(pos_px.x - 15, pos_px.y, pos_px.x + 15, pos_px.y); diff --git a/xs/src/slic3r/GUI/2DBed.hpp b/xs/src/slic3r/GUI/2DBed.hpp index a170b708ad..4b14986a2b 100644 --- a/xs/src/slic3r/GUI/2DBed.hpp +++ b/xs/src/slic3r/GUI/2DBed.hpp @@ -1,3 +1,6 @@ +#ifndef slic3r_2DBed_hpp_ +#define slic3r_2DBed_hpp_ + #include #include "Config.hpp" @@ -45,3 +48,5 @@ public: } // GUI } // Slic3r + +#endif /* slic3r_2DBed_hpp_ */ diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index b9ac1966e3..0738bd46a9 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -762,7 +762,10 @@ std::vector GLVolumeCollection::get_current_print_zs() const // Collect layer top positions of all volumes. std::vector print_zs; for (GLVolume *vol : this->volumes) - append(print_zs, vol->print_zs); + { + if (vol->is_active) + append(print_zs, vol->print_zs); + } std::sort(print_zs.begin(), print_zs.end()); // Replace intervals of layers with similar top positions with their average value. @@ -1768,6 +1771,11 @@ void _3DScene::load_gcode_preview(const Print* print, const GCodePreviewData* pr { _generate_legend_texture(*preview_data, tool_colors); _load_shells(*print, *volumes, use_VBOs); + + // removes empty volumes + volumes->volumes.erase(std::remove_if(volumes->volumes.begin(), volumes->volumes.end(), + [](const GLVolume *volume) { return volume->print_zs.empty(); }), + volumes->volumes.end()); } } diff --git a/xs/src/slic3r/GUI/AboutDialog.cpp b/xs/src/slic3r/GUI/AboutDialog.cpp new file mode 100644 index 0000000000..3c359fd66a --- /dev/null +++ b/xs/src/slic3r/GUI/AboutDialog.cpp @@ -0,0 +1,126 @@ +#include "AboutDialog.hpp" + +#include "../../libslic3r/Utils.hpp" + +namespace Slic3r { +namespace GUI { + +AboutDialogLogo::AboutDialogLogo(wxWindow* parent) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) +{ + this->SetBackgroundColour(*wxWHITE); + this->logo = wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG); + this->SetMinSize(this->logo.GetSize()); + + this->Bind(wxEVT_PAINT, &AboutDialogLogo::onRepaint, this); +} + +void AboutDialogLogo::onRepaint(wxEvent &event) +{ + wxPaintDC dc(this); + dc.SetBackgroundMode(wxTRANSPARENT); + + wxSize size = this->GetSize(); + int logo_w = this->logo.GetWidth(); + int logo_h = this->logo.GetHeight(); + dc.DrawBitmap(this->logo, (size.GetWidth() - logo_w)/2, (size.GetHeight() - logo_h)/2, true); + + event.Skip(); +} + +AboutDialog::AboutDialog() + : wxDialog(NULL, wxID_ANY, _(L("About Slic3r")), wxDefaultPosition, wxSize(600, 340), wxCAPTION) +{ + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)/**wxWHITE*/); + wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL); + this->SetSizer(hsizer); + + // logo +// AboutDialogLogo* logo = new AboutDialogLogo(this); + wxBitmap logo_bmp = wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG); + auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(logo_bmp)); + hsizer->Add(logo, 0, wxEXPAND | wxLEFT | wxRIGHT, 30); + + wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); + hsizer->Add(vsizer, 1, wxEXPAND, 0); + + // title + { + wxStaticText* title = new wxStaticText(this, wxID_ANY, "Slic3r Prusa Edition", wxDefaultPosition, wxDefaultSize); + wxFont title_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + title_font.SetWeight(wxFONTWEIGHT_BOLD); + title_font.SetFamily(wxFONTFAMILY_ROMAN); + title_font.SetPointSize(24); + title->SetFont(title_font); + vsizer->Add(title, 0, wxALIGN_LEFT | wxTOP, 30); + } + + // version + { + auto version_string = _(L("Version ")) + std::string(SLIC3R_VERSION); + wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize); + wxFont version_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + #ifdef __WXMSW__ + version_font.SetPointSize(9); + #else + version_font.SetPointSize(11); + #endif + version->SetFont(version_font); + vsizer->Add(version, 0, wxALIGN_LEFT | wxBOTTOM, 10); + } + + // text + wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER); + { + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + #ifdef __WXMSW__ + int size[] = {8,8,8,8,8,8,8}; + #else + int size[] = {11,11,11,11,11,11,11}; + #endif + html->SetFonts(font.GetFaceName(), font.GetFaceName(), size); + html->SetHTMLBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + html->SetBorders(2); + const char* text = + "" + "" + "" + "Copyright © 2016-2018 Prusa Research.
" + "Copyright © 2011-2017 Alessandro Ranellucci.
" + "Slic3r is licensed under the " + "GNU Affero General Public License, version 3." + "


" + "Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others. " + "Manual by Gary Hodgson. Inspired by the RepRap community.
" + "Slic3r logo designed by Corey Daniels, Silk Icon Set designed by Mark James. " + "
" + "" + ""; + html->SetPage(text); + vsizer->Add(html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 20); + html->Bind(wxEVT_HTML_LINK_CLICKED, &AboutDialog::onLinkClicked, this); + } + + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE); + this->SetEscapeId(wxID_CLOSE); + this->Bind(wxEVT_BUTTON, &AboutDialog::onCloseDialog, this, wxID_CLOSE); + vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3); + + this->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this); + logo->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this); +} + +void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event) +{ + wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref()); + event.Skip(false); +} + +void AboutDialog::onCloseDialog(wxEvent &) +{ + this->EndModal(wxID_CLOSE); + this->Close(); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/AboutDialog.hpp b/xs/src/slic3r/GUI/AboutDialog.hpp new file mode 100644 index 0000000000..01f7564c50 --- /dev/null +++ b/xs/src/slic3r/GUI/AboutDialog.hpp @@ -0,0 +1,36 @@ +#ifndef slic3r_GUI_AboutDialog_hpp_ +#define slic3r_GUI_AboutDialog_hpp_ + +#include "GUI.hpp" + +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +class AboutDialogLogo : public wxPanel +{ +public: + AboutDialogLogo(wxWindow* parent); + +private: + wxBitmap logo; + void onRepaint(wxEvent &event); +}; + +class AboutDialog : public wxDialog +{ +public: + AboutDialog(); + +private: + void onLinkClicked(wxHtmlLinkEvent &event); + void onCloseDialog(wxEvent &); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index e32b645b47..70f4ba110d 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -1,5 +1,3 @@ -#include - #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/Utils.hpp" #include "AppConfig.hpp" @@ -9,15 +7,22 @@ #include #include #include +#include +#include #include #include #include #include #include +#include namespace Slic3r { +static const std::string VENDOR_PREFIX = "vendor:"; +static const std::string MODEL_PREFIX = "model:"; +static const std::string VERSION_CHECK_URL = "https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/Slic3rPE.version"; + void AppConfig::reset() { m_storage.clear(); @@ -42,9 +47,12 @@ void AppConfig::set_defaults() 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"); + if (get("preset_update").empty()) + set("preset_update", "1"); + // Use OpenGL 1.1 even if OpenGL 2.0 is available. This is mainly to support some buggy Intel HD Graphics drivers. // https://github.com/prusa3d/Slic3r/issues/233 if (get("use_legacy_opengl").empty()) @@ -67,6 +75,19 @@ void AppConfig::load() 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 if (boost::starts_with(section.first, VENDOR_PREFIX)) { + // This is a vendor section listing enabled model / variants + const auto vendor_name = section.first.substr(VENDOR_PREFIX.size()); + auto &vendor = m_vendors[vendor_name]; + for (const auto &kvp : section.second) { + if (! boost::starts_with(kvp.first, MODEL_PREFIX)) { continue; } + const auto model_name = kvp.first.substr(MODEL_PREFIX.size()); + std::vector variants; + if (! unescape_strings_cstyle(kvp.second.data(), variants)) { continue; } + for (const auto &variant : variants) { + vendor[model_name].insert(variant); + } + } } else { // This must be a section name. Read the entries of a section. std::map &storage = m_storage[section.first]; @@ -75,6 +96,16 @@ void AppConfig::load() } } + // Figure out if datadir has legacy presets + auto ini_ver = Semver::parse(get("version")); + m_legacy_datadir = false; + if (ini_ver) { + // Make 1.40.0 alphas compare well + ini_ver->set_metadata(boost::none); + ini_ver->set_prerelease(boost::none); + m_legacy_datadir = ini_ver < Semver(1, 40, 0); + } + // Override missing or keys with their defaults. this->set_defaults(); m_dirty = false; @@ -96,10 +127,57 @@ void AppConfig::save() for (const std::pair &kvp : category.second) c << kvp.first << " = " << kvp.second << std::endl; } + // Write vendor sections + for (const auto &vendor : m_vendors) { + size_t size_sum = 0; + for (const auto &model : vendor.second) { size_sum += model.second.size(); } + if (size_sum == 0) { continue; } + + c << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl; + + for (const auto &model : vendor.second) { + if (model.second.size() == 0) { continue; } + const std::vector variants(model.second.begin(), model.second.end()); + const auto escaped = escape_strings_cstyle(variants); + c << MODEL_PREFIX << model.first << " = " << escaped << std::endl; + } + } c.close(); m_dirty = false; } +bool AppConfig::get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const +{ + const auto it_v = m_vendors.find(vendor); + if (it_v == m_vendors.end()) { return false; } + const auto it_m = it_v->second.find(model); + return it_m == it_v->second.end() ? false : it_m->second.find(variant) != it_m->second.end(); +} + +void AppConfig::set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable) +{ + if (enable) { + if (get_variant(vendor, model, variant)) { return; } + m_vendors[vendor][model].insert(variant); + } else { + auto it_v = m_vendors.find(vendor); + if (it_v == m_vendors.end()) { return; } + auto it_m = it_v->second.find(model); + if (it_m == it_v->second.end()) { return; } + auto it_var = it_m->second.find(variant); + if (it_var == it_m->second.end()) { return; } + it_m->second.erase(it_var); + } + // If we got here, there was an update + m_dirty = true; +} + +void AppConfig::set_vendors(const AppConfig &from) +{ + m_vendors = from.m_vendors; + m_dirty = true; +} + std::string AppConfig::get_last_dir() const { const auto it = m_storage.find("recent"); @@ -161,6 +239,12 @@ std::string AppConfig::config_path() return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string(); } +std::string AppConfig::version_check_url() const +{ + auto from_settings = get("version_check_url"); + return from_settings.empty() ? VERSION_CHECK_URL : from_settings; +} + bool AppConfig::exists() { return boost::filesystem::exists(AppConfig::config_path()); diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp index 9b1d5a7128..16469f0e95 100644 --- a/xs/src/slic3r/GUI/AppConfig.hpp +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -1,15 +1,19 @@ #ifndef slic3r_AppConfig_hpp_ #define slic3r_AppConfig_hpp_ +#include #include #include +#include "libslic3r/Config.hpp" +#include "slic3r/Utils/Semver.hpp" + namespace Slic3r { class AppConfig { public: - AppConfig() : m_dirty(false) { this->reset(); } + AppConfig() : m_dirty(false), m_legacy_datadir(false) { this->reset(); } // Clear and reset to defaults. void reset(); @@ -65,6 +69,14 @@ public: void clear_section(const std::string §ion) { m_storage[section].clear(); } + typedef std::map>> VendorMap; + bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const; + void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable); + void set_vendors(const AppConfig &from); + void set_vendors(const VendorMap &vendors) { m_vendors = vendors; m_dirty = true; } + void set_vendors(VendorMap &&vendors) { m_vendors = std::move(vendors); m_dirty = true; } + const VendorMap& vendors() const { return m_vendors; } + // 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); @@ -81,14 +93,25 @@ public: // Get the default config path from Slic3r::data_dir(). static std::string config_path(); + // Returns true if the user's data directory comes from before Slic3r 1.40.0 (no updating) + bool legacy_datadir() const { return m_legacy_datadir; } + + // Get the Slic3r version check url. + // This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file. + std::string version_check_url() const; + // Does the config file exist? static bool exists(); private: // Map of section, name -> value std::map> m_storage; + // Map of enabled vendors / models / variants + VendorMap m_vendors; // Has any value been modified since the config.ini has been last saved or loaded? bool m_dirty; + // Whether the existing version is before system profiles & configuration updating + bool m_legacy_datadir; }; }; // namespace Slic3r diff --git a/xs/src/slic3r/GUI/BedShapeDialog.hpp b/xs/src/slic3r/GUI/BedShapeDialog.hpp index f4614c3426..5ff4880637 100644 --- a/xs/src/slic3r/GUI/BedShapeDialog.hpp +++ b/xs/src/slic3r/GUI/BedShapeDialog.hpp @@ -1,3 +1,5 @@ +#ifndef slic3r_BedShapeDialog_hpp_ +#define slic3r_BedShapeDialog_hpp_ // The bed shape dialog. // The dialog opens from Print Settins tab->Bed Shape : Set... @@ -49,3 +51,6 @@ public: } // GUI } // Slic3r + + +#endif /* slic3r_BedShapeDialog_hpp_ */ diff --git a/xs/src/slic3r/GUI/ButtonsDescription.cpp b/xs/src/slic3r/GUI/ButtonsDescription.cpp new file mode 100644 index 0000000000..7ea16d9420 --- /dev/null +++ b/xs/src/slic3r/GUI/ButtonsDescription.cpp @@ -0,0 +1,84 @@ +#include "ButtonsDescription.hpp" +#include +#include +#include +#include + +#include "GUI.hpp" + +namespace Slic3r { +namespace GUI { + +ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* icon_descriptions) : + wxDialog(parent, wxID_ANY, "Buttons And Text Colors Description", wxDefaultPosition, wxDefaultSize), + m_icon_descriptions(icon_descriptions) +{ + auto grid_sizer = new wxFlexGridSizer(3, 20, 20); + + auto main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->Add(grid_sizer, 0, wxEXPAND | wxALL, 20); + + // Icon description + for (auto pair : *m_icon_descriptions) + { + auto icon = new wxStaticBitmap(this, wxID_ANY, *pair.first); + grid_sizer->Add(icon, -1, wxALIGN_CENTRE_VERTICAL); + + std::istringstream f(pair.second); + std::string s; + getline(f, s, ';'); + auto description = new wxStaticText(this, wxID_ANY, _(s)); + grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL); + getline(f, s, ';'); + description = new wxStaticText(this, wxID_ANY, _(s)); + grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND); + } + + // Text color description + auto sys_label = new wxStaticText(this, wxID_ANY, _(L("Value is the same as the system value"))); + sys_label->SetForegroundColour(get_label_clr_sys()); + auto sys_colour = new wxColourPickerCtrl(this, wxID_ANY, get_label_clr_sys()); + sys_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([sys_colour, sys_label](wxCommandEvent e) + { + sys_label->SetForegroundColour(sys_colour->GetColour()); + sys_label->Refresh(); + })); + size_t t= 0; + while (t < 3){ + grid_sizer->Add(new wxStaticText(this, wxID_ANY, ""), -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND); + ++t; + } + grid_sizer->Add(0, -1, wxALIGN_CENTRE_VERTICAL); + grid_sizer->Add(sys_colour, -1, wxALIGN_CENTRE_VERTICAL); + grid_sizer->Add(sys_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND); + + auto mod_label = new wxStaticText(this, wxID_ANY, _(L("Value was changed and is not equal to the system value or the last saved preset"))); + mod_label->SetForegroundColour(get_label_clr_modified()); + auto mod_colour = new wxColourPickerCtrl(this, wxID_ANY, get_label_clr_modified()); + mod_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([mod_colour, mod_label](wxCommandEvent e) + { + mod_label->SetForegroundColour(mod_colour->GetColour()); + mod_label->Refresh(); + })); + grid_sizer->Add(0, -1, wxALIGN_CENTRE_VERTICAL); + grid_sizer->Add(mod_colour, -1, wxALIGN_CENTRE_VERTICAL); + grid_sizer->Add(mod_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND); + + + auto buttons = CreateStdDialogButtonSizer(wxOK|wxCANCEL); + main_sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); + + wxButton* btn = static_cast(FindWindowById(wxID_OK, this)); + btn->Bind(wxEVT_BUTTON, [sys_colour, mod_colour, this](wxCommandEvent&) { + set_label_clr_sys(sys_colour->GetColour()); + set_label_clr_modified(mod_colour->GetColour()); + EndModal(wxID_OK); + }); + + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); +} + +} // GUI +} // Slic3r + diff --git a/xs/src/slic3r/GUI/ButtonsDescription.hpp b/xs/src/slic3r/GUI/ButtonsDescription.hpp new file mode 100644 index 0000000000..4858eaaea3 --- /dev/null +++ b/xs/src/slic3r/GUI/ButtonsDescription.hpp @@ -0,0 +1,27 @@ +#ifndef slic3r_ButtonsDescription_hpp +#define slic3r_ButtonsDescription_hpp + +#include +#include + +namespace Slic3r { +namespace GUI { + +using t_icon_descriptions = std::vector>; + +class ButtonsDescription : public wxDialog +{ + t_icon_descriptions* m_icon_descriptions; +public: + ButtonsDescription(wxWindow* parent, t_icon_descriptions* icon_descriptions); + ~ButtonsDescription(){} + + +}; + +} // GUI +} // Slic3r + + +#endif + diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp new file mode 100644 index 0000000000..c0affcab7c --- /dev/null +++ b/xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -0,0 +1,139 @@ +#include "ConfigSnapshotDialog.hpp" + +#include "../Config/Snapshot.hpp" +#include "../Utils/Time.hpp" + +#include "../../libslic3r/Utils.hpp" + +namespace Slic3r { +namespace GUI { + +static std::string format_reason(const Config::Snapshot::Reason reason) +{ + switch (reason) { + case Config::Snapshot::SNAPSHOT_UPGRADE: + return std::string(_(L("Upgrade"))); + case Config::Snapshot::SNAPSHOT_DOWNGRADE: + return std::string(_(L("Downgrade"))); + case Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK: + return std::string(_(L("Before roll back"))); + case Config::Snapshot::SNAPSHOT_USER: + return std::string(_(L("User"))); + case Config::Snapshot::SNAPSHOT_UNKNOWN: + default: + return std::string(_(L("Unknown"))); + } +} + +static std::string generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active) +{ + // Start by declaring a row with an alternating background color. + std::string text = ""; + text += ""; + // Format the row header. + text += std::string("") + (snapshot_active ? _(L("Active: ")) : "") + + Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason); + if (! snapshot.comment.empty()) + text += " (" + snapshot.comment + ")"; + text += "
"; + // End of row header. + text += _(L("slic3r version")) + ": " + snapshot.slic3r_version_captured.to_string() + "
"; + text += _(L("print")) + ": " + snapshot.print + "
"; + text += _(L("filaments")) + ": " + snapshot.filaments.front() + "
"; + text += _(L("printer")) + ": " + snapshot.printer + "
"; + + bool compatible = true; + for (const Config::Snapshot::VendorConfig &vc : snapshot.vendor_configs) { + text += _(L("vendor")) + ": " + vc.name + ", ver: " + vc.version.config_version.to_string() + ", min slic3r ver: " + vc.version.min_slic3r_version.to_string(); + if (vc.version.max_slic3r_version != Semver::inf()) + text += ", max slic3r ver: " + vc.version.max_slic3r_version.to_string(); + text += "
"; + for (const std::pair> &model : vc.models_variants_installed) { + text += _(L("model")) + ": " + model.first + ", " + _(L("variants")) + ": "; + for (const std::string &variant : model.second) { + if (&variant != &*model.second.begin()) + text += ", "; + text += variant; + } + text += "
"; + } + if (! vc.version.is_current_slic3r_supported()) { compatible = false; } + } + + if (! compatible) { + text += "

" + _(L("Incompatible with this Slic3r")) + "

"; + } + else if (! snapshot_active) + text += "

" + _(L("Activate")) + "

"; + text += ""; + text += ""; + return text; +} + +static std::string generate_html_page(const Config::SnapshotDB &snapshot_db, const std::string &on_snapshot) +{ + std::string text = + "" + "" + ""; + text += ""; + for (size_t i_row = 0; i_row < snapshot_db.snapshots().size(); ++ i_row) { + const Config::Snapshot &snapshot = snapshot_db.snapshots()[snapshot_db.snapshots().size() - i_row - 1]; + text += generate_html_row(snapshot, i_row & 1, snapshot.id == on_snapshot); + } + text += + "
" + "
" + "" + ""; + return text; +} + +ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const std::string &on_snapshot) + : wxDialog(NULL, wxID_ANY, _(L("Configuration Snapshots")), wxDefaultPosition, wxSize(600, 500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX) +{ + this->SetBackgroundColour(*wxWHITE); + + wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); + this->SetSizer(vsizer); + + // text + wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO); + { + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + #ifdef __WXMSW__ + int size[] = {8,8,8,8,11,11,11}; + #else + int size[] = {11,11,11,11,14,14,14}; + #endif + html->SetFonts(font.GetFaceName(), font.GetFaceName(), size); + html->SetBorders(2); + std::string text = generate_html_page(snapshot_db, on_snapshot); + html->SetPage(text.c_str()); + vsizer->Add(html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 0); + html->Bind(wxEVT_HTML_LINK_CLICKED, &ConfigSnapshotDialog::onLinkClicked, this); + } + + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE); + this->SetEscapeId(wxID_CLOSE); + this->Bind(wxEVT_BUTTON, &ConfigSnapshotDialog::onCloseDialog, this, wxID_CLOSE); + vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3); +} + +void ConfigSnapshotDialog::onLinkClicked(wxHtmlLinkEvent &event) +{ + m_snapshot_to_activate = event.GetLinkInfo().GetHref(); + this->EndModal(wxID_CLOSE); + this->Close(); +} + +void ConfigSnapshotDialog::onCloseDialog(wxEvent &) +{ + this->EndModal(wxID_CLOSE); + this->Close(); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp b/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp new file mode 100644 index 0000000000..943601e731 --- /dev/null +++ b/xs/src/slic3r/GUI/ConfigSnapshotDialog.hpp @@ -0,0 +1,34 @@ +#ifndef slic3r_GUI_ConfigSnapshotDialog_hpp_ +#define slic3r_GUI_ConfigSnapshotDialog_hpp_ + +#include "GUI.hpp" + +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +namespace Config { + class SnapshotDB; +} + +class ConfigSnapshotDialog : public wxDialog +{ +public: + ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const std::string &id); + const std::string& snapshot_to_activate() const { return m_snapshot_to_activate; } + +private: + void onLinkClicked(wxHtmlLinkEvent &event); + void onCloseDialog(wxEvent &); + + // If set, it contains a snapshot ID to be restored after the dialog closes. + std::string m_snapshot_to_activate; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif /* slic3r_GUI_ConfigSnapshotDialog_hpp_ */ diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp new file mode 100644 index 0000000000..1217f86831 --- /dev/null +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -0,0 +1,860 @@ +#include "ConfigWizard_private.hpp" + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/Utils.hpp" +#include "PresetBundle.hpp" +#include "GUI.hpp" +#include "slic3r/Utils/PresetUpdater.hpp" + + +namespace Slic3r { +namespace GUI { + + +// Printer model picker GUI control + +struct PrinterPickerEvent : public wxEvent +{ + std::string vendor_id; + std::string model_id; + std::string variant_name; + bool enable; + + PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable) : + wxEvent(winid, eventType), + vendor_id(std::move(vendor_id)), + model_id(std::move(model_id)), + variant_name(std::move(variant_name)), + enable(enable) + {} + + virtual wxEvent *Clone() const + { + return new PrinterPickerEvent(*this); + } +}; + +wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent); + +PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors) : + wxPanel(parent), + vendor_id(vendor.id), + variants_checked(0) +{ + const auto &models = vendor.models; + + auto *sizer = new wxBoxSizer(wxVERTICAL); + + auto *printer_grid = new wxFlexGridSizer(models.size(), 0, 20); + printer_grid->SetFlexibleDirection(wxVERTICAL); + sizer->Add(printer_grid); + + auto namefont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + namefont.SetWeight(wxFONTWEIGHT_BOLD); + + for (const auto &model : models) { + auto *panel = new wxPanel(this); + auto *col_sizer = new wxBoxSizer(wxVERTICAL); + panel->SetSizer(col_sizer); + + auto *title = new wxStaticText(panel, wxID_ANY, model.name, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + title->SetFont(namefont); + col_sizer->Add(title, 0, wxBOTTOM, 3); + + auto bitmap_file = wxString::Format("printers/%s_%s.png", vendor.id, model.id); + wxBitmap bitmap(GUI::from_u8(Slic3r::var(bitmap_file.ToStdString())), wxBITMAP_TYPE_PNG); + auto *bitmap_widget = new wxStaticBitmap(panel, wxID_ANY, bitmap); + col_sizer->Add(bitmap_widget, 0, wxBOTTOM, 3); + + col_sizer->AddSpacer(20); + + const auto model_id = model.id; + + for (const auto &variant : model.variants) { + const auto label = wxString::Format("%s %s %s", variant.name, _(L("mm")), _(L("nozzle"))); + auto *cbox = new Checkbox(panel, label, model_id, variant.name); + const size_t idx = cboxes.size(); + cboxes.push_back(cbox); + bool enabled = appconfig_vendors.get_variant("PrusaResearch", model_id, variant.name); + variants_checked += enabled; + cbox->SetValue(enabled); + col_sizer->Add(cbox, 0, wxBOTTOM, 3); + cbox->Bind(wxEVT_CHECKBOX, [this, idx](wxCommandEvent &event) { + if (idx >= this->cboxes.size()) { return; } + this->on_checkbox(this->cboxes[idx], event.IsChecked()); + }); + } + + printer_grid->Add(panel); + } + + auto *all_none_sizer = new wxBoxSizer(wxHORIZONTAL); + auto *sel_all = new wxButton(this, wxID_ANY, _(L("Select all"))); + auto *sel_none = new wxButton(this, wxID_ANY, _(L("Select none"))); + sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true); }); + sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); }); + all_none_sizer->AddStretchSpacer(); + all_none_sizer->Add(sel_all); + all_none_sizer->Add(sel_none); + sizer->AddStretchSpacer(); + sizer->Add(all_none_sizer, 0, wxEXPAND); + + SetSizer(sizer); +} + +void PrinterPicker::select_all(bool select) +{ + for (const auto &cb : cboxes) { + if (cb->GetValue() != select) { + cb->SetValue(select); + on_checkbox(cb, select); + } + } +} + +void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked) +{ + variants_checked += checked ? 1 : -1; + PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked); + AddPendingEvent(evt); +} + + +// Wizard page base + +ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname) : + wxPanel(parent), + parent(parent), + shortname(std::move(shortname)), + p_prev(nullptr), + p_next(nullptr) +{ + auto *sizer = new wxBoxSizer(wxVERTICAL); + + auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + auto font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + font.SetWeight(wxFONTWEIGHT_BOLD); + font.SetPointSize(14); + text->SetFont(font); + sizer->Add(text, 0, wxALIGN_LEFT, 0); + sizer->AddSpacer(10); + + content = new wxBoxSizer(wxVERTICAL); + sizer->Add(content, 1); + + SetSizer(sizer); + + this->Hide(); + + Bind(wxEVT_SIZE, [this](wxSizeEvent &event) { + this->Layout(); + event.Skip(); + }); +} + +ConfigWizardPage::~ConfigWizardPage() {} + +ConfigWizardPage* ConfigWizardPage::chain(ConfigWizardPage *page) +{ + if (p_next != nullptr) { p_next->p_prev = nullptr; } + p_next = page; + if (page != nullptr) { + if (page->p_prev != nullptr) { page->p_prev->p_next = nullptr; } + page->p_prev = this; + } + + return page; +} + +void ConfigWizardPage::append_text(wxString text) +{ + auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + widget->Wrap(CONTENT_WIDTH); + widget->SetMinSize(wxSize(CONTENT_WIDTH, -1)); + append(widget); +} + +void ConfigWizardPage::append_spacer(int space) +{ + content->AddSpacer(space); +} + +bool ConfigWizardPage::Show(bool show) +{ + if (extra_buttons() != nullptr) { extra_buttons()->Show(show); } + return wxPanel::Show(show); +} + +void ConfigWizardPage::enable_next(bool enable) { parent->p->enable_next(enable); } + + +// Wizard pages + +PageWelcome::PageWelcome(ConfigWizard *parent) : + ConfigWizardPage(parent, wxString::Format(_(L("Welcome to the Slic3r %s")), ConfigWizard::name()), _(L("Welcome"))), + printer_picker(nullptr), + others_buttons(new wxPanel(parent)), + cbox_reset(nullptr) +{ + if (wizard_p()->run_reason == ConfigWizard::RR_DATA_EMPTY) { + wxString::Format(_(L("Run %s")), ConfigWizard::name()); + append_text(wxString::Format( + _(L("Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")), + ConfigWizard::name()) + ); + } else { + cbox_reset = new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles - install from scratch (a snapshot will be taken beforehand)"))); + append(cbox_reset); + } + + const auto &vendors = wizard_p()->vendors; + const auto vendor_prusa = vendors.find("PrusaResearch"); + + if (vendor_prusa != vendors.cend()) { + AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors; + + printer_picker = new PrinterPicker(this, vendor_prusa->second, appconfig_vendors); + printer_picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) { + appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); + this->on_variant_checked(); + }); + + append(printer_picker); + } + + const size_t num_other_vendors = vendors.size() - (vendor_prusa != vendors.cend()); + auto *sizer = new wxBoxSizer(wxHORIZONTAL); + auto *other_vendors = new wxButton(others_buttons, wxID_ANY, _(L("Other vendors"))); + other_vendors->Enable(num_other_vendors > 0); + auto *custom_setup = new wxButton(others_buttons, wxID_ANY, _(L("Custom setup"))); + + sizer->Add(other_vendors); + sizer->AddSpacer(BTN_SPACING); + sizer->Add(custom_setup); + + other_vendors->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_other_vendors(); }); + custom_setup->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_custom_setup(); }); + + others_buttons->SetSizer(sizer); +} + +void PageWelcome::on_page_set() +{ + chain(wizard_p()->page_update); + on_variant_checked(); +} + +void PageWelcome::on_variant_checked() +{ + enable_next(printer_picker != nullptr ? printer_picker->variants_checked > 0 : false); +} + +PageUpdate::PageUpdate(ConfigWizard *parent) : + ConfigWizardPage(parent, _(L("Automatic updates")), _(L("Updates"))), + version_check(true), + preset_update(true) +{ + const AppConfig *app_config = GUI::get_app_config(); + auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _(L("Check for application updates"))); + box_slic3r->SetValue(app_config->get("version_check") == "1"); + append(box_slic3r); + append_text(_(L("If enabled, Slic3r checks for new versions of Slic3r PE online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done."))); + + append_spacer(VERTICAL_SPACING); + + auto *box_presets = new wxCheckBox(this, wxID_ANY, _(L("Update built-in Presets automatically"))); + box_presets->SetValue(app_config->get("preset_update") == "1"); + append(box_presets); + append_text(_(L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup."))); + const auto text_bold = _(L("Updates are never applied without user's consent and never overwrite user's customized settings.")); + auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold); + label_bold->SetFont(boldfont); + label_bold->Wrap(CONTENT_WIDTH); + append(label_bold); + append_text(_(L("Additionally a backup snapshot of the whole configuration is created before an update is applied."))); + + box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); }); + box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); }); +} + +PageVendors::PageVendors(ConfigWizard *parent) : + ConfigWizardPage(parent, _(L("Other Vendors")), _(L("Other Vendors"))) +{ + append_text(_(L("Pick another vendor supported by Slic3r PE:"))); + + auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors; + wxArrayString choices_vendors; + + for (const auto vendor_pair : wizard_p()->vendors) { + const auto &vendor = vendor_pair.second; + if (vendor.id == "PrusaResearch") { continue; } + + auto *picker = new PrinterPicker(this, vendor, appconfig_vendors); + picker->Hide(); + pickers.push_back(picker); + choices_vendors.Add(vendor.name); + + picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) { + appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); + this->on_variant_checked(); + }); + } + + auto *vendor_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices_vendors); + if (choices_vendors.GetCount() > 0) { + vendor_picker->SetSelection(0); + on_vendor_pick(0); + } + + vendor_picker->Bind(wxEVT_CHOICE, [this](wxCommandEvent &evt) { + this->on_vendor_pick(evt.GetInt()); + }); + + append(vendor_picker); + for (PrinterPicker *picker : pickers) { this->append(picker); } +} + +void PageVendors::on_page_set() +{ + on_variant_checked(); +} + +void PageVendors::on_vendor_pick(size_t i) +{ + for (PrinterPicker *picker : pickers) { picker->Hide(); } + if (i < pickers.size()) { + pickers[i]->Show(); + wizard_p()->layout_fit(); + } +} + +void PageVendors::on_variant_checked() +{ + size_t variants_checked = 0; + for (const PrinterPicker *picker : pickers) { variants_checked += picker->variants_checked; } + enable_next(variants_checked > 0); +} + +PageFirmware::PageFirmware(ConfigWizard *parent) : + ConfigWizardPage(parent, _(L("Firmware Type")), _(L("Firmware"))), + gcode_opt(print_config_def.options["gcode_flavor"]), + gcode_picker(nullptr) +{ + append_text(_(L("Choose the type of firmware used by your printer."))); + append_text(gcode_opt.tooltip); + + wxArrayString choices; + choices.Alloc(gcode_opt.enum_labels.size()); + for (const auto &label : gcode_opt.enum_labels) { + choices.Add(label); + } + + gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices); + const auto &enum_values = gcode_opt.enum_values; + auto needle = enum_values.cend(); + if (gcode_opt.default_value != nullptr) { + needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize()); + } + if (needle != enum_values.cend()) { + gcode_picker->SetSelection(needle - enum_values.cbegin()); + } else { + gcode_picker->SetSelection(0); + } + + append(gcode_picker); +} + +void PageFirmware::apply_custom_config(DynamicPrintConfig &config) +{ + ConfigOptionEnum opt; + + auto sel = gcode_picker->GetSelection(); + if (sel != wxNOT_FOUND && opt.deserialize(gcode_picker->GetString(sel).ToStdString())) { + config.set_key_value("gcode_flavor", &opt); + } +} + +PageBedShape::PageBedShape(ConfigWizard *parent) : + ConfigWizardPage(parent, _(L("Bed Shape and Size")), _(L("Bed Shape"))), + shape_panel(new BedShapePanel(this)) +{ + append_text(_(L("Set the shape of your printer's bed."))); + + shape_panel->build_panel(wizard_p()->custom_config->option("bed_shape")); + append(shape_panel); +} + +void PageBedShape::apply_custom_config(DynamicPrintConfig &config) +{ + const auto points(shape_panel->GetValue()); + auto *opt = new ConfigOptionPoints(points); + config.set_key_value("bed_shape", opt); +} + +PageDiameters::PageDiameters(ConfigWizard *parent) : + ConfigWizardPage(parent, _(L("Filament and Nozzle Diameters")), _(L("Print Diameters"))), + spin_nozzle(new wxSpinCtrlDouble(this, wxID_ANY)), + spin_filam(new wxSpinCtrlDouble(this, wxID_ANY)) +{ + spin_nozzle->SetDigits(2); + spin_nozzle->SetIncrement(0.1); + const auto &def_nozzle = print_config_def.options["nozzle_diameter"]; + auto *default_nozzle = dynamic_cast(def_nozzle.default_value); + spin_nozzle->SetValue(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); + + spin_filam->SetDigits(2); + spin_filam->SetIncrement(0.25); + const auto &def_filam = print_config_def.options["filament_diameter"]; + auto *default_filam = dynamic_cast(def_filam.default_value); + spin_filam->SetValue(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); + + append_text(_(L("Enter the diameter of your printer's hot end nozzle."))); + + auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5); + auto *text_nozzle = new wxStaticText(this, wxID_ANY, _(L("Nozzle Diameter:"))); + auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _(L("mm"))); + sizer_nozzle->AddGrowableCol(0, 1); + sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL); + sizer_nozzle->Add(spin_nozzle); + sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_nozzle); + + append_spacer(VERTICAL_SPACING); + + append_text(_(L("Enter the diameter of your filament."))); + append_text(_(L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average."))); + + auto *sizer_filam = new wxFlexGridSizer(3, 5, 5); + auto *text_filam = new wxStaticText(this, wxID_ANY, _(L("Filament Diameter:"))); + auto *unit_filam = new wxStaticText(this, wxID_ANY, _(L("mm"))); + sizer_filam->AddGrowableCol(0, 1); + sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL); + sizer_filam->Add(spin_filam); + sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_filam); +} + +void PageDiameters::apply_custom_config(DynamicPrintConfig &config) +{ + auto *opt_nozzle = new ConfigOptionFloats(1, spin_nozzle->GetValue()); + config.set_key_value("nozzle_diameter", opt_nozzle); + auto *opt_filam = new ConfigOptionFloats(1, spin_filam->GetValue()); + config.set_key_value("filament_diameter", opt_filam); +} + +PageTemperatures::PageTemperatures(ConfigWizard *parent) : + ConfigWizardPage(parent, _(L("Extruder and Bed Temperatures")), _(L("Temperatures"))), + spin_extr(new wxSpinCtrlDouble(this, wxID_ANY)), + spin_bed(new wxSpinCtrlDouble(this, wxID_ANY)) +{ + spin_extr->SetIncrement(5.0); + const auto &def_extr = print_config_def.options["temperature"]; + spin_extr->SetRange(def_extr.min, def_extr.max); + auto *default_extr = dynamic_cast(def_extr.default_value); + spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200); + + spin_bed->SetIncrement(5.0); + const auto &def_bed = print_config_def.options["bed_temperature"]; + spin_bed->SetRange(def_bed.min, def_bed.max); + auto *default_bed = dynamic_cast(def_bed.default_value); + spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0); + + append_text(_(L("Enter the temperature needed for extruding your filament."))); + append_text(_(L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS."))); + + auto *sizer_extr = new wxFlexGridSizer(3, 5, 5); + auto *text_extr = new wxStaticText(this, wxID_ANY, _(L("Extrusion Temperature:"))); + auto *unit_extr = new wxStaticText(this, wxID_ANY, _(L("°C"))); + sizer_extr->AddGrowableCol(0, 1); + sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL); + sizer_extr->Add(spin_extr); + sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_extr); + + append_spacer(VERTICAL_SPACING); + + append_text(_(L("Enter the bed temperature needed for getting your filament to stick to your heated bed."))); + append_text(_(L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed."))); + + auto *sizer_bed = new wxFlexGridSizer(3, 5, 5); + auto *text_bed = new wxStaticText(this, wxID_ANY, _(L("Bed Temperature:"))); + auto *unit_bed = new wxStaticText(this, wxID_ANY, _(L("°C"))); + sizer_bed->AddGrowableCol(0, 1); + sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL); + sizer_bed->Add(spin_bed); + sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_bed); +} + +void PageTemperatures::apply_custom_config(DynamicPrintConfig &config) +{ + auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue()); + config.set_key_value("temperature", opt_extr); + auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue()); + config.set_key_value("first_layer_temperature", opt_extr1st); + auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue()); + config.set_key_value("bed_temperature", opt_bed); + auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue()); + config.set_key_value("first_layer_bed_temperature", opt_bed1st); +} + + +// Index + +ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) : + wxPanel(parent), + bg(GUI::from_u8(Slic3r::var("Slic3r_192px_transparent.png")), wxBITMAP_TYPE_PNG), + bullet_black(GUI::from_u8(Slic3r::var("bullet_black.png")), wxBITMAP_TYPE_PNG), + bullet_blue(GUI::from_u8(Slic3r::var("bullet_blue.png")), wxBITMAP_TYPE_PNG), + bullet_white(GUI::from_u8(Slic3r::var("bullet_white.png")), wxBITMAP_TYPE_PNG) +{ + SetMinSize(bg.GetSize()); + + wxClientDC dc(this); + text_height = dc.GetCharHeight(); + + // Add logo bitmap. + // This could be done in on_paint() along with the index labels, but I've found it tricky + // to get the bitmap rendered well on all platforms with transparent background. + // In some cases it didn't work at all. And so wxStaticBitmap is used here instead, + // because it has all the platform quirks figured out. + auto *sizer = new wxBoxSizer(wxVERTICAL); + auto *logo = new wxStaticBitmap(this, wxID_ANY, bg); + sizer->AddStretchSpacer(); + sizer->Add(logo); + SetSizer(sizer); + + Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this); +} + +void ConfigWizardIndex::load_items(ConfigWizardPage *firstpage) +{ + items.clear(); + item_active = items.cend(); + + for (auto *page = firstpage; page != nullptr; page = page->page_next()) { + items.emplace_back(page->shortname); + } + + Refresh(); +} + +void ConfigWizardIndex::set_active(ConfigWizardPage *page) +{ + item_active = std::find(items.cbegin(), items.cend(), page->shortname); + Refresh(); +} + +void ConfigWizardIndex::on_paint(wxPaintEvent & evt) +{ + enum { + MARGIN = 10, + SPACING = 5, + }; + + const auto size = GetClientSize(); + if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; } + + wxPaintDC dc(this); + + const auto bullet_w = bullet_black.GetSize().GetWidth(); + const auto bullet_h = bullet_black.GetSize().GetHeight(); + const int yoff_icon = bullet_h < text_height ? (text_height - bullet_h) / 2 : 0; + const int yoff_text = bullet_h > text_height ? (bullet_h - text_height) / 2 : 0; + const int yinc = std::max(bullet_h, text_height) + SPACING; + + unsigned y = 0; + for (auto it = items.cbegin(); it != items.cend(); ++it) { + if (it < item_active) { dc.DrawBitmap(bullet_black, MARGIN, y + yoff_icon, false); } + if (it == item_active) { dc.DrawBitmap(bullet_blue, MARGIN, y + yoff_icon, false); } + if (it > item_active) { dc.DrawBitmap(bullet_white, MARGIN, y + yoff_icon, false); } + dc.DrawText(*it, MARGIN + bullet_w + SPACING, y + yoff_text); + y += yinc; + } +} + + + +// priv + +static const std::unordered_map> legacy_preset_map {{ + { "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") }, + { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2S", "0.4") }, + { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, + { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2S", "0.4") }, + { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, + { "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") }, + { "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, + { "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") }, +}}; + +void ConfigWizard::priv::load_vendors() +{ + const auto vendor_dir = fs::path(Slic3r::data_dir()) / "vendor"; + const auto rsrc_vendor_dir = fs::path(resources_dir()) / "profiles"; + + // Load vendors from the "vendors" directory in datadir + for (fs::directory_iterator it(vendor_dir); it != fs::directory_iterator(); ++it) { + if (it->path().extension() == ".ini") { + auto vp = VendorProfile::from_ini(it->path()); + vendors[vp.id] = std::move(vp); + } + } + + // Additionally load up vendors from the application resources directory, but only those not seen in the datadir + for (fs::directory_iterator it(rsrc_vendor_dir); it != fs::directory_iterator(); ++it) { + if (it->path().extension() == ".ini") { + const auto id = it->path().stem().string(); + if (vendors.find(id) == vendors.end()) { + auto vp = VendorProfile::from_ini(it->path()); + vendors_rsrc[vp.id] = it->path().filename().string(); + vendors[vp.id] = std::move(vp); + } + } + } + + // Load up the set of vendors / models / variants the user has had enabled up till now + const AppConfig *app_config = GUI::get_app_config(); + if (! app_config->legacy_datadir()) { + appconfig_vendors.set_vendors(*app_config); + } else { + // In case of legacy datadir, try to guess the preference based on the printer preset files that are present + const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer"; + for (fs::directory_iterator it(printer_dir); it != fs::directory_iterator(); ++it) { + auto needle = legacy_preset_map.find(it->path().filename().string()); + if (needle == legacy_preset_map.end()) { continue; } + + const auto &model = needle->second.first; + const auto &variant = needle->second.second; + appconfig_vendors.set_variant("PrusaResearch", model, variant, true); + } + } +} + +void ConfigWizard::priv::index_refresh() +{ + index->load_items(page_welcome); +} + +void ConfigWizard::priv::add_page(ConfigWizardPage *page) +{ + topsizer->Add(page, 0, wxEXPAND); + + auto *extra_buttons = page->extra_buttons(); + if (extra_buttons != nullptr) { + btnsizer->Prepend(extra_buttons, 0); + } +} + +void ConfigWizard::priv::set_page(ConfigWizardPage *page) +{ + if (page == nullptr) { return; } + if (page_current != nullptr) { page_current->Hide(); } + page_current = page; + enable_next(true); + + page->on_page_set(); + index->load_items(page_welcome); + index->set_active(page); + page->Show(); + + btn_prev->Enable(page->page_prev() != nullptr); + btn_next->Show(page->page_next() != nullptr); + btn_finish->Show(page->page_next() == nullptr); + + layout_fit(); +} + +void ConfigWizard::priv::layout_fit() +{ + q->Layout(); + q->Fit(); +} + +void ConfigWizard::priv::enable_next(bool enable) +{ + btn_next->Enable(enable); + btn_finish->Enable(enable); +} + +void ConfigWizard::priv::on_other_vendors() +{ + page_welcome + ->chain(page_vendors) + ->chain(page_update); + set_page(page_vendors); +} + +void ConfigWizard::priv::on_custom_setup() +{ + page_welcome->chain(page_firmware); + page_temps->chain(page_update); + set_page(page_firmware); +} + +void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater) +{ + const bool is_custom_setup = page_welcome->page_next() == page_firmware; + + if (! is_custom_setup) { + const auto enabled_vendors = appconfig_vendors.vendors(); + + // Install bundles from resources if needed: + std::vector install_bundles; + for (const auto &vendor_rsrc : vendors_rsrc) { + const auto vendor = enabled_vendors.find(vendor_rsrc.first); + if (vendor == enabled_vendors.end()) { continue; } + + size_t size_sum = 0; + for (const auto &model : vendor->second) { size_sum += model.second.size(); } + if (size_sum == 0) { continue; } + + // This vendor needs to be installed + install_bundles.emplace_back(vendor_rsrc.second); + } + + // Decide whether to create snapshot based on run_reason and the reset profile checkbox + bool snapshot = true; + switch (run_reason) { + case ConfigWizard::RR_DATA_EMPTY: snapshot = false; break; + case ConfigWizard::RR_DATA_LEGACY: snapshot = true; break; + case ConfigWizard::RR_DATA_INCOMPAT: snapshot = false; break; // In this case snapshot is done by PresetUpdater with the appropriate reason + case ConfigWizard::RR_USER: snapshot = page_welcome->reset_user_profile(); break; + } + if (install_bundles.size() > 0) { + // Install bundles from resources. + updater->install_bundles_rsrc(std::move(install_bundles), snapshot); + } else { + BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources"; + } + + if (page_welcome->reset_user_profile()) { + BOOST_LOG_TRIVIAL(info) << "Resetting user profiles..."; + preset_bundle->reset(true); + } + + app_config->set_vendors(appconfig_vendors); + app_config->set("version_check", page_update->version_check ? "1" : "0"); + app_config->set("preset_update", page_update->preset_update ? "1" : "0"); + app_config->reset_selections(); + // ^ TODO: replace with appropriate printer selection + preset_bundle->load_presets(*app_config); + } else { + for (ConfigWizardPage *page = page_firmware; page != nullptr; page = page->page_next()) { + page->apply_custom_config(*custom_config); + } + preset_bundle->load_config("My Settings", *custom_config); + } + // Update the selections from the compatibilty. + preset_bundle->export_selections(*app_config); +} + +// Public + +ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) : + wxDialog(parent, wxID_ANY, name(), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), + p(new priv(this)) +{ + p->run_reason = reason; + + p->load_vendors(); + p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ + "gcode_flavor", "bed_shape", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", + })); + + p->index = new ConfigWizardIndex(this); + + auto *vsizer = new wxBoxSizer(wxVERTICAL); + p->topsizer = new wxBoxSizer(wxHORIZONTAL); + auto *hline = new wxStaticLine(this); + p->btnsizer = new wxBoxSizer(wxHORIZONTAL); + + p->topsizer->Add(p->index, 0, wxEXPAND); + p->topsizer->AddSpacer(INDEX_MARGIN); + + p->btn_prev = new wxButton(this, wxID_BACKWARD); + p->btn_next = new wxButton(this, wxID_FORWARD); + p->btn_finish = new wxButton(this, wxID_APPLY, _(L("&Finish"))); + p->btn_cancel = new wxButton(this, wxID_CANCEL); + p->btnsizer->AddStretchSpacer(); + p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING); + p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING); + p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING); + p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING); + + p->add_page(p->page_welcome = new PageWelcome(this)); + p->add_page(p->page_update = new PageUpdate(this)); + p->add_page(p->page_vendors = new PageVendors(this)); + p->add_page(p->page_firmware = new PageFirmware(this)); + p->add_page(p->page_bed = new PageBedShape(this)); + p->add_page(p->page_diams = new PageDiameters(this)); + p->add_page(p->page_temps = new PageTemperatures(this)); + p->index_refresh(); + + p->page_welcome->chain(p->page_update); + p->page_firmware + ->chain(p->page_bed) + ->chain(p->page_diams) + ->chain(p->page_temps); + + vsizer->Add(p->topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN); + vsizer->Add(hline, 0, wxEXPAND); + vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN); + + p->set_page(p->page_welcome); + SetSizerAndFit(vsizer); + SetMinSize(GetSize()); + + p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); }); + p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_next(); }); + p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->EndModal(wxID_OK); }); +} + +ConfigWizard::~ConfigWizard() {} + +bool ConfigWizard::run(PresetBundle *preset_bundle, const PresetUpdater *updater) +{ + BOOST_LOG_TRIVIAL(info) << "Running ConfigWizard, reason: " << p->run_reason; + if (ShowModal() == wxID_OK) { + p->apply_config(GUI::get_app_config(), preset_bundle, updater); + BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied"; + return true; + } else { + BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled"; + return false; + } +} + + +const wxString& ConfigWizard::name() +{ + // A different naming convention is used for the Wizard on Windows vs. OSX & GTK. +#if WIN32 + static const wxString config_wizard_name = _(L("Configuration Wizard")); +#else + static const wxString config_wizard_name = _(L("Configuration Assistant")); +#endif + return config_wizard_name; +} + +} +} diff --git a/xs/src/slic3r/GUI/ConfigWizard.hpp b/xs/src/slic3r/GUI/ConfigWizard.hpp new file mode 100644 index 0000000000..73fce7cd21 --- /dev/null +++ b/xs/src/slic3r/GUI/ConfigWizard.hpp @@ -0,0 +1,50 @@ +#ifndef slic3r_ConfigWizard_hpp_ +#define slic3r_ConfigWizard_hpp_ + +#include + +#include + +namespace Slic3r { + +class PresetBundle; +class PresetUpdater; + +namespace GUI { + + +class ConfigWizard: public wxDialog +{ +public: + // Why is the Wizard run + enum RunReason { + RR_DATA_EMPTY, // No or empty datadir + RR_DATA_LEGACY, // Pre-updating datadir + RR_DATA_INCOMPAT, // Incompatible datadir - Slic3r downgrade situation + RR_USER, // User requested the Wizard from the menus + }; + + ConfigWizard(wxWindow *parent, RunReason run_reason); + ConfigWizard(ConfigWizard &&) = delete; + ConfigWizard(const ConfigWizard &) = delete; + ConfigWizard &operator=(ConfigWizard &&) = delete; + ConfigWizard &operator=(const ConfigWizard &) = delete; + ~ConfigWizard(); + + // Run the Wizard. Return whether it was completed. + bool run(PresetBundle *preset_bundle, const PresetUpdater *updater); + + static const wxString& name(); +private: + struct priv; + std::unique_ptr p; + + friend class ConfigWizardPage; +}; + + + +} +} + +#endif diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp new file mode 100644 index 0000000000..474394bc34 --- /dev/null +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -0,0 +1,238 @@ +#ifndef slic3r_ConfigWizard_private_hpp_ +#define slic3r_ConfigWizard_private_hpp_ + +#include "ConfigWizard.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "libslic3r/PrintConfig.hpp" +#include "slic3r/Utils/PresetUpdater.hpp" +#include "AppConfig.hpp" +#include "Preset.hpp" +#include "BedShapeDialog.hpp" + +namespace fs = boost::filesystem; + +namespace Slic3r { +namespace GUI { + +enum { + CONTENT_WIDTH = 500, + + DIALOG_MARGIN = 15, + INDEX_MARGIN = 40, + BTN_SPACING = 10, + INDENT_SPACING = 30, + VERTICAL_SPACING = 10, +}; + +struct PrinterPicker: wxPanel +{ + struct Checkbox : wxCheckBox + { + Checkbox(wxWindow *parent, const wxString &label, const std::string &model, const std::string &variant) : + wxCheckBox(parent, wxID_ANY, label), + model(model), + variant(variant) + {} + + std::string model; + std::string variant; + }; + + const std::string vendor_id; + std::vector cboxes; + unsigned variants_checked; + + PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors); + + void select_all(bool select); + void on_checkbox(const Checkbox *cbox, bool checked); +}; + +struct ConfigWizardPage: wxPanel +{ + ConfigWizard *parent; + const wxString shortname; + wxBoxSizer *content; + + ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname); + + virtual ~ConfigWizardPage(); + + ConfigWizardPage* page_prev() const { return p_prev; } + ConfigWizardPage* page_next() const { return p_next; } + ConfigWizardPage* chain(ConfigWizardPage *page); + + template + void append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10) + { + content->Add(thing, proportion, flag, border); + } + + void append_text(wxString text); + void append_spacer(int space); + + ConfigWizard::priv *wizard_p() const { return parent->p.get(); } + + virtual bool Show(bool show = true); + virtual bool Hide() { return Show(false); } + virtual wxPanel* extra_buttons() { return nullptr; } + virtual void on_page_set() {} + virtual void apply_custom_config(DynamicPrintConfig &config) {} + + void enable_next(bool enable); +private: + ConfigWizardPage *p_prev; + ConfigWizardPage *p_next; +}; + +struct PageWelcome: ConfigWizardPage +{ + PrinterPicker *printer_picker; + wxPanel *others_buttons; + wxCheckBox *cbox_reset; + + PageWelcome(ConfigWizard *parent); + + virtual wxPanel* extra_buttons() { return others_buttons; } + virtual void on_page_set(); + + bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; } + void on_variant_checked(); +}; + +struct PageUpdate: ConfigWizardPage +{ + bool version_check; + bool preset_update; + + PageUpdate(ConfigWizard *parent); +}; + +struct PageVendors: ConfigWizardPage +{ + std::vector pickers; + + PageVendors(ConfigWizard *parent); + + virtual void on_page_set(); + + void on_vendor_pick(size_t i); + void on_variant_checked(); +}; + +struct PageFirmware: ConfigWizardPage +{ + const ConfigOptionDef &gcode_opt; + wxChoice *gcode_picker; + + PageFirmware(ConfigWizard *parent); + virtual void apply_custom_config(DynamicPrintConfig &config); +}; + +struct PageBedShape: ConfigWizardPage +{ + BedShapePanel *shape_panel; + + PageBedShape(ConfigWizard *parent); + virtual void apply_custom_config(DynamicPrintConfig &config); +}; + +struct PageDiameters: ConfigWizardPage +{ + wxSpinCtrlDouble *spin_nozzle; + wxSpinCtrlDouble *spin_filam; + + PageDiameters(ConfigWizard *parent); + virtual void apply_custom_config(DynamicPrintConfig &config); +}; + +struct PageTemperatures: ConfigWizardPage +{ + wxSpinCtrlDouble *spin_extr; + wxSpinCtrlDouble *spin_bed; + + PageTemperatures(ConfigWizard *parent); + virtual void apply_custom_config(DynamicPrintConfig &config); +}; + + +class ConfigWizardIndex: public wxPanel +{ +public: + ConfigWizardIndex(wxWindow *parent); + + void load_items(ConfigWizardPage *firstpage); + void set_active(ConfigWizardPage *page); +private: + const wxBitmap bg; + const wxBitmap bullet_black; + const wxBitmap bullet_blue; + const wxBitmap bullet_white; + int text_height; + + std::vector items; + std::vector::const_iterator item_active; + + void on_paint(wxPaintEvent &evt); +}; + +struct ConfigWizard::priv +{ + ConfigWizard *q; + ConfigWizard::RunReason run_reason; + AppConfig appconfig_vendors; + std::unordered_map vendors; + std::unordered_map vendors_rsrc; + std::unique_ptr custom_config; + + wxBoxSizer *topsizer = nullptr; + wxBoxSizer *btnsizer = nullptr; + ConfigWizardPage *page_current = nullptr; + ConfigWizardIndex *index = nullptr; + wxButton *btn_prev = nullptr; + wxButton *btn_next = nullptr; + wxButton *btn_finish = nullptr; + wxButton *btn_cancel = nullptr; + + PageWelcome *page_welcome = nullptr; + PageUpdate *page_update = nullptr; + PageVendors *page_vendors = nullptr; + PageFirmware *page_firmware = nullptr; + PageBedShape *page_bed = nullptr; + PageDiameters *page_diams = nullptr; + PageTemperatures *page_temps = nullptr; + + priv(ConfigWizard *q) : q(q) {} + + void load_vendors(); + void add_page(ConfigWizardPage *page); + void index_refresh(); + void set_page(ConfigWizardPage *page); + void layout_fit(); + void go_prev() { if (page_current != nullptr) { set_page(page_current->page_prev()); } } + void go_next() { if (page_current != nullptr) { set_page(page_current->page_next()); } } + void enable_next(bool enable); + + void on_other_vendors(); + void on_custom_setup(); + + void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater); +}; + + + +} +} + +#endif diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index c68b2304c0..5cae9172ef 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -20,21 +20,22 @@ namespace Slic3r { namespace GUI { void Field::PostInitialize(){ auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - m_Undo_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); - m_Undo_to_sys_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + m_Undo_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + m_Undo_to_sys_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); if (wxMSW) { m_Undo_btn->SetBackgroundColour(color); m_Undo_to_sys_btn->SetBackgroundColour(color); } - m_Undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); })); m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); })); - BUILD(); - } + //set default bitmap + wxBitmap bmp; + bmp.LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG); + set_undo_bitmap(&bmp); + set_undo_to_sys_bitmap(&bmp); - void Field::set_nonsys_btn_icon(const std::string& icon){ - m_Undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(icon)), wxBITMAP_TYPE_PNG)); + BUILD(); } void Field::on_kill_focus(wxEvent& event) { @@ -81,12 +82,13 @@ namespace Slic3r { namespace GUI { return std::regex_match(string, regex_pattern); } - boost::any Field::get_value_by_opt_type(wxString& str) +// boost::any Field::get_value_by_opt_type(wxString& str) + void Field::get_value_by_opt_type(wxString& str) { - boost::any ret_val; +// boost::any m_value; switch (m_opt.type){ case coInt: - ret_val = wxAtoi(str); + m_value = wxAtoi(str); break; case coPercent: case coPercents: @@ -96,18 +98,18 @@ namespace Slic3r { namespace GUI { str.RemoveLast(); double val; str.ToCDouble(&val); - ret_val = val; + m_value = val; break; } case coString: case coStrings: case coFloatOrPercent: - ret_val = str.ToStdString(); + m_value = str.ToStdString(); break; default: break; } - return ret_val; +// return m_value; } void TextCtrl::BUILD() { @@ -183,12 +185,12 @@ namespace Slic3r { namespace GUI { window = dynamic_cast(temp); } - boost::any TextCtrl::get_value() + boost::any& TextCtrl::get_value() { wxString ret_str = static_cast(window)->GetValue(); - boost::any ret_val = get_value_by_opt_type(ret_str); + /*boost::any ret_val*/get_value_by_opt_type(ret_str); - return ret_val; + return m_value;//ret_val; } void TextCtrl::enable() { dynamic_cast(window)->Enable(); dynamic_cast(window)->SetEditable(true); } @@ -216,15 +218,15 @@ void CheckBox::BUILD() { window = dynamic_cast(temp); } -boost::any CheckBox::get_value() +boost::any& CheckBox::get_value() { - boost::any ret_val; +// boost::any m_value; bool value = dynamic_cast(window)->GetValue(); if (m_opt.type == coBool) - ret_val = static_cast(value); + m_value = static_cast(value); else - ret_val = static_cast(value); - return ret_val; + m_value = static_cast(value); + return m_value; } int undef_spin_val = -9999; //! Probably, It's not necessary @@ -424,7 +426,33 @@ void Choice::set_value(const boost::any& value, bool change_event) break; } case coEnum:{ - dynamic_cast(window)->SetSelection(boost::any_cast(value)); + int val = boost::any_cast(value); + if (m_opt_id.compare("external_fill_pattern") == 0) + { + if (!m_opt.enum_values.empty()){ + std::string key; + t_config_enum_values map_names = ConfigOptionEnum::get_enum_values(); + for (auto it : map_names) { + if (val == it.second) { + key = it.first; + break; + } + } + + size_t idx = 0; + for (auto el : m_opt.enum_values) + { + if (el.compare(key) == 0) + break; + ++idx; + } + + val = idx == m_opt.enum_values.size() ? 0 : idx; + } + else + val = 0; + } + dynamic_cast(window)->SetSelection(val); break; } default: @@ -454,16 +482,16 @@ void Choice::set_values(const std::vector& values) m_disable_change_event = false; } -boost::any Choice::get_value() +boost::any& Choice::get_value() { - boost::any ret_val; +// boost::any m_value; wxString ret_str = static_cast(window)->GetValue(); if (m_opt_id == "support") - return ret_str; + return m_value = boost::any(ret_str);//ret_str; if (m_opt.type != coEnum) - ret_val = get_value_by_opt_type(ret_str); + /*m_value = */get_value_by_opt_type(ret_str); else { int ret_enum = static_cast(window)->GetSelection(); @@ -474,22 +502,22 @@ boost::any Choice::get_value() t_config_enum_values map_names = ConfigOptionEnum::get_enum_values(); int value = map_names.at(key); - ret_val = static_cast(value); + m_value = static_cast(value); } else - ret_val = static_cast(0); + m_value = static_cast(0); } if (m_opt_id.compare("fill_pattern") == 0) - ret_val = static_cast(ret_enum); + m_value = static_cast(ret_enum); else if (m_opt_id.compare("gcode_flavor") == 0) - ret_val = static_cast(ret_enum); + m_value = static_cast(ret_enum); else if (m_opt_id.compare("support_material_pattern") == 0) - ret_val = static_cast(ret_enum); + m_value = static_cast(ret_enum); else if (m_opt_id.compare("seam_position") == 0) - ret_val = static_cast(ret_enum); + m_value = static_cast(ret_enum); } - return ret_val; + return m_value; } void ColourPicker::BUILD() @@ -509,14 +537,14 @@ void ColourPicker::BUILD() temp->SetToolTip(get_tooltip_text(clr)); } -boost::any ColourPicker::get_value(){ - boost::any ret_val; +boost::any& ColourPicker::get_value(){ +// boost::any m_value; auto colour = static_cast(window)->GetColour(); auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue()); - ret_val = clr_str.ToStdString(); + m_value = clr_str.ToStdString(); - return ret_val; + return m_value; } void PointCtrl::BUILD() @@ -580,7 +608,7 @@ void PointCtrl::set_value(const boost::any& value, bool change_event) set_value(pt, change_event); } -boost::any PointCtrl::get_value() +boost::any& PointCtrl::get_value() { Pointf ret_point; double val; @@ -588,7 +616,7 @@ boost::any PointCtrl::get_value() ret_point.x = val; y_textctrl->GetValue().ToDouble(&val); ret_point.y = val; - return ret_point; + return m_value = ret_point; } } // GUI diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index cdc7c0d812..729f73d160 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -36,6 +36,24 @@ using t_back_to_init = std::function; wxString double_to_string(double const value); +class MyButton : public wxButton +{ +public: + MyButton() {} + MyButton(wxWindow* parent, wxWindowID id, const wxString& label = wxEmptyString, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, long style = 0, + const wxValidator& validator = wxDefaultValidator, + const wxString& name = wxTextCtrlNameStr) + { + this->Create(parent, id, label, pos, size, style, validator, name); + } + + // overridden from wxWindow base class + virtual bool + AcceptsFocusFromKeyboard() const { return false; } +}; + class Field { protected: // factory function to defer and enforce creation of derived type. @@ -85,22 +103,18 @@ public: /// Gets a boost::any representing this control. /// subclasses should overload with a specific version - virtual boost::any get_value() = 0; + virtual boost::any& get_value() = 0; virtual void enable() = 0; virtual void disable() = 0; - wxStaticText* m_Label = nullptr; - wxButton* m_Undo_btn = nullptr; - wxButton* m_Undo_to_sys_btn = nullptr; - - /// Fires the enable or disable function, based on the input. + /// Fires the enable or disable function, based on the input. inline void toggle(bool en) { en ? enable() : disable(); } virtual wxString get_tooltip_text(const wxString& default_string); // set icon to "UndoToSystemValue" button according to an inheritance of preset - void set_nonsys_btn_icon(const std::string& icon); +// void set_nonsys_btn_icon(const wxBitmap& icon); Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id) {}; Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id) {}; @@ -110,7 +124,8 @@ public: virtual wxWindow* getWindow() { return nullptr; } bool is_matched(const std::string& string, const std::string& pattern); - boost::any get_value_by_opt_type(wxString& str); +// boost::any get_value_by_opt_type(wxString& str); + void get_value_by_opt_type(wxString& str); /// Factory method for generating new derived classes. template @@ -120,6 +135,78 @@ public: p->PostInitialize(); return std::move(p); //!p; } + + bool set_undo_bitmap(const wxBitmap *bmp) { + if (m_undo_bitmap != bmp) { + m_undo_bitmap = bmp; + m_Undo_btn->SetBitmap(*bmp); + return true; + } + return false; + } + + bool set_undo_to_sys_bitmap(const wxBitmap *bmp) { + if (m_undo_to_sys_bitmap != bmp) { + m_undo_to_sys_bitmap = bmp; + m_Undo_to_sys_btn->SetBitmap(*bmp); + return true; + } + return false; + } + + bool set_label_colour(const wxColour *clr) { + if (m_Label == nullptr) return false; + if (m_label_color != clr) { + m_label_color = clr; + m_Label->SetForegroundColour(*clr); + m_Label->Refresh(true); + } + return false; + } + + bool set_label_colour_force(const wxColour *clr) { + if (m_Label == nullptr) return false; + m_Label->SetForegroundColour(*clr); + m_Label->Refresh(true); + return false; + } + + bool set_undo_tooltip(const wxString *tip) { + if (m_undo_tooltip != tip) { + m_undo_tooltip = tip; + m_Undo_btn->SetToolTip(*tip); + return true; + } + return false; + } + + bool set_undo_to_sys_tooltip(const wxString *tip) { + if (m_undo_to_sys_tooltip != tip) { + m_undo_to_sys_tooltip = tip; + m_Undo_to_sys_btn->SetToolTip(*tip); + return true; + } + return false; + } + +protected: + MyButton* m_Undo_btn = nullptr; + // Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. + const wxBitmap* m_undo_bitmap = nullptr; + const wxString* m_undo_tooltip = nullptr; + MyButton* m_Undo_to_sys_btn = nullptr; + // Bitmap and Tooltip text for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. + const wxBitmap* m_undo_to_sys_bitmap = nullptr; + const wxString* m_undo_to_sys_tooltip = nullptr; + + wxStaticText* m_Label = nullptr; + // Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one. + const wxColour* m_label_color = nullptr; + + // current value + boost::any m_value; + + friend class OptionsGroup; }; /// Convenience function, accepts a const reference to t_field and checks to see whether @@ -153,7 +240,7 @@ public: m_disable_change_event = false; } - boost::any get_value() override; + boost::any& get_value() override; virtual void enable(); virtual void disable(); @@ -180,7 +267,7 @@ public: dynamic_cast(window)->SetValue(boost::any_cast(value)); m_disable_change_event = false; } - boost::any get_value() override; + boost::any& get_value() override; void enable() override { dynamic_cast(window)->Enable(); } void disable() override { dynamic_cast(window)->Disable(); } @@ -210,8 +297,9 @@ public: dynamic_cast(window)->SetValue(tmp_value); m_disable_change_event = false; } - boost::any get_value() override { - return boost::any(tmp_value); + boost::any& get_value() override { +// return boost::any(tmp_value); + return m_value = tmp_value; } void enable() override { dynamic_cast(window)->Enable(); } @@ -233,7 +321,7 @@ public: void set_value(const std::string& value, bool change_event = false); void set_value(const boost::any& value, bool change_event = false); void set_values(const std::vector &values); - boost::any get_value() override; + boost::any& get_value() override; void enable() override { dynamic_cast(window)->Enable(); }; void disable() override{ dynamic_cast(window)->Disable(); }; @@ -261,7 +349,7 @@ public: m_disable_change_event = false; } - boost::any get_value() override; + boost::any& get_value() override; void enable() override { dynamic_cast(window)->Enable(); }; void disable() override{ dynamic_cast(window)->Disable(); }; @@ -283,7 +371,7 @@ public: void set_value(const Pointf& value, bool change_event = false); void set_value(const boost::any& value, bool change_event = false); - boost::any get_value() override; + boost::any& get_value() override; void enable() override { x_textctrl->Enable(); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index cc135931aa..8d8818126d 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -39,16 +39,25 @@ #include #include #include +#include #include #include "wxExtensions.hpp" #include "Tab.hpp" #include "TabIface.hpp" +#include "AboutDialog.hpp" #include "AppConfig.hpp" +#include "ConfigSnapshotDialog.hpp" #include "Utils.hpp" +#include "MsgDialog.hpp" +#include "ConfigWizard.hpp" #include "Preferences.hpp" #include "PresetBundle.hpp" +#include "UpdateDialogs.hpp" + +#include "../Utils/PresetUpdater.hpp" +#include "../Config/Snapshot.hpp" namespace Slic3r { namespace GUI { @@ -177,8 +186,10 @@ wxFrame *g_wxMainFrame = nullptr; wxNotebook *g_wxTabPanel = nullptr; AppConfig *g_AppConfig = nullptr; PresetBundle *g_PresetBundle= nullptr; +PresetUpdater *g_PresetUpdater = nullptr; wxColour g_color_label_modified; wxColour g_color_label_sys; +wxColour g_color_label_default; std::vector g_tabs_list; @@ -198,6 +209,22 @@ static void init_label_colours() g_color_label_modified = wxColour(253, 111, 40); g_color_label_sys = wxColour(115, 220, 103); } + g_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); +} + +void update_label_colours_from_appconfig() +{ + if (g_AppConfig->has("label_clr_sys")){ + auto str = g_AppConfig->get("label_clr_sys"); + if (str != "") + g_color_label_sys = wxColour(str); + } + + if (g_AppConfig->has("label_clr_modified")){ + auto str = g_AppConfig->get("label_clr_modified"); + if (str != "") + g_color_label_modified = wxColour(str); + } } void set_wxapp(wxApp *app) @@ -226,6 +253,11 @@ void set_preset_bundle(PresetBundle *preset_bundle) g_PresetBundle = preset_bundle; } +void set_preset_updater(PresetUpdater *updater) +{ + g_PresetUpdater = updater; +} + std::vector& get_tabs_list() { return g_tabs_list; @@ -349,26 +381,143 @@ void get_installed_languages(wxArrayString & names, } } -void add_debug_menu(wxMenuBar *menu, int event_language_change) +enum ConfigMenuIDs { + ConfigMenuWizard, + ConfigMenuSnapshots, + ConfigMenuTakeSnapshot, + ConfigMenuUpdate, + ConfigMenuPreferences, + ConfigMenuLanguage, + ConfigMenuCnt, +}; + +void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_language_change) { -//#if 0 auto local_menu = new wxMenu(); - local_menu->Append(wxWindow::NewControlId(1), _(L("Change Application Language"))); - local_menu->Bind(wxEVT_MENU, [event_language_change](wxEvent&){ - wxArrayString names; - wxArrayLong identifiers; - get_installed_languages(names, identifiers); - if (select_language(names, identifiers)){ - save_language(); - show_info(g_wxTabPanel, _(L("Application will be restarted")), _(L("Attention!"))); - if (event_language_change > 0) { - wxCommandEvent event(event_language_change); - g_wxApp->ProcessEvent(event); + wxWindowID config_id_base = wxWindow::NewControlId((int)ConfigMenuCnt); + + const auto config_wizard_tooltip = wxString::Format(_(L("Run %s")), ConfigWizard::name()); + // Cmd+, is standard on OS X - what about other operating systems? + local_menu->Append(config_id_base + ConfigMenuWizard, ConfigWizard::name() + "\u2026", config_wizard_tooltip); + local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots\u2026")), _(L("Inspect / activate configuration snapshots"))); + local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _(L("Take Configuration Snapshot")), _(L("Capture a configuration snapshot"))); + local_menu->Append(config_id_base + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates"))); + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("Preferences\u2026\tCtrl+,")), _(L("Application preferences"))); + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language"))); + local_menu->Bind(wxEVT_MENU, [config_id_base, event_language_change, event_preferences_changed](wxEvent &event){ + switch (event.GetId() - config_id_base) { + case ConfigMenuWizard: + config_wizard(ConfigWizard::RR_USER); + break; + case ConfigMenuTakeSnapshot: + // Take a configuration snapshot. + if (check_unsaved_changes()) { + wxTextEntryDialog dlg(nullptr, _(L("Taking configuration snapshot")), _(L("Snapshot name"))); + if (dlg.ShowModal() == wxID_OK) + g_AppConfig->set("on_snapshot", + Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot( + *g_AppConfig, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()).id); } + break; + case ConfigMenuSnapshots: + if (check_unsaved_changes()) { + std::string on_snapshot; + if (Config::SnapshotDB::singleton().is_on_snapshot(*g_AppConfig)) + on_snapshot = g_AppConfig->get("on_snapshot"); + ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot); + dlg.ShowModal(); + if (! dlg.snapshot_to_activate().empty()) { + if (! Config::SnapshotDB::singleton().is_on_snapshot(*g_AppConfig)) + Config::SnapshotDB::singleton().take_snapshot(*g_AppConfig, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK); + g_AppConfig->set("on_snapshot", + Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *g_AppConfig).id); + g_PresetBundle->load_presets(*g_AppConfig); + // Load the currently selected preset into the GUI, update the preset selection box. + for (Tab *tab : g_tabs_list) + tab->load_current_preset(); + } + } + break; + case ConfigMenuPreferences: + { + PreferencesDialog dlg(g_wxMainFrame, event_preferences_changed); + dlg.ShowModal(); + break; + } + case ConfigMenuLanguage: + { + wxArrayString names; + wxArrayLong identifiers; + get_installed_languages(names, identifiers); + if (select_language(names, identifiers)) { + save_language(); + show_info(g_wxTabPanel, _(L("Application will be restarted")), _(L("Attention!"))); + if (event_language_change > 0) { + wxCommandEvent event(event_language_change); + g_wxApp->ProcessEvent(event); + } + } + break; + } } }); - menu->Append(local_menu, _(L("&Localization"))); -//#endif + menu->Append(local_menu, _(L("&Configuration"))); +} + +// 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. +bool check_unsaved_changes() +{ + std::string dirty; + for (Tab *tab : g_tabs_list) + if (tab->current_preset_is_dirty()) + if (dirty.empty()) + dirty = tab->name(); + else + dirty += std::string(", ") + tab->name(); + if (dirty.empty()) + // No changes, the application may close or reload presets. + return true; + // Ask the user. + auto dialog = new wxMessageDialog(g_wxMainFrame, + _(L("You have unsaved changes ")) + dirty + _(L(". Discard changes and continue anyway?")), + _(L("Unsaved Presets")), + wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); + return dialog->ShowModal() == wxID_YES; +} + +bool config_wizard_startup(bool app_config_exists) +{ + if (! app_config_exists || g_PresetBundle->has_defauls_only()) { + config_wizard(ConfigWizard::RR_DATA_EMPTY); + return true; + } else if (g_AppConfig->legacy_datadir()) { + // Looks like user has legacy pre-vendorbundle data directory, + // explain what this is and run the wizard + + MsgDataLegacy dlg; + dlg.ShowModal(); + + config_wizard(ConfigWizard::RR_DATA_LEGACY); + return true; + } + return false; +} + +void config_wizard(int reason) +{ + // Exit wizard if there are unsaved changes and the user cancels the action. + if (! check_unsaved_changes()) + return; + + ConfigWizard wizard(nullptr, static_cast(reason)); + wizard.run(g_PresetBundle, g_PresetUpdater); + + // Load the currently selected preset into the GUI, update the preset selection box. + for (Tab *tab : g_tabs_list) + tab->load_current_preset(); } void open_preferences_dialog(int event_preferences) @@ -379,6 +528,7 @@ void open_preferences_dialog(int event_preferences) void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed) { + update_label_colours_from_appconfig(); add_created_tab(new TabPrint (g_wxTabPanel, no_controller)); add_created_tab(new TabFilament (g_wxTabPanel, no_controller)); add_created_tab(new TabPrinter (g_wxTabPanel, no_controller)); @@ -519,35 +669,65 @@ void add_created_tab(Tab* panel) g_wxTabPanel->AddPage(panel, panel->title()); } -void show_error(wxWindow* parent, const wxString& message){ - auto msg_wingow = new wxMessageDialog(parent, message, _(L("Error")), wxOK | wxICON_ERROR); - msg_wingow->ShowModal(); +void show_error(wxWindow* parent, const wxString& message) { + ErrorDialog msg(parent, message); + msg.ShowModal(); +} + +void show_error_id(int id, const std::string& message) { + auto *parent = id != 0 ? wxWindow::FindWindowById(id) : nullptr; + show_error(parent, message); } void show_info(wxWindow* parent, const wxString& message, const wxString& title){ - auto msg_wingow = new wxMessageDialog(parent, message, title.empty() ? _(L("Notice")) : title, wxOK | wxICON_INFORMATION); - msg_wingow->ShowModal(); + wxMessageDialog msg_wingow(parent, message, title.empty() ? _(L("Notice")) : title, wxOK | wxICON_INFORMATION); + msg_wingow.ShowModal(); } void warning_catcher(wxWindow* parent, const wxString& message){ if (message == _(L("GLUquadricObjPtr | Attempt to free unreferenced scalar")) ) return; - auto msg = new wxMessageDialog(parent, message, _(L("Warning")), wxOK | wxICON_WARNING); - msg->ShowModal(); + wxMessageDialog msg(parent, message, _(L("Warning")), wxOK | wxICON_WARNING); + msg.ShowModal(); } wxApp* get_app(){ return g_wxApp; } -const wxColour& get_modified_label_clr() { +PresetBundle* get_preset_bundle() +{ + return g_PresetBundle; +} + +const wxColour& get_label_clr_modified() { return g_color_label_modified; } -const wxColour& get_sys_label_clr() { +const wxColour& get_label_clr_sys() { return g_color_label_sys; } +void set_label_clr_modified(const wxColour& clr) { + g_color_label_modified = clr; + auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue()); + std::string str = clr_str.ToStdString(); + g_AppConfig->set("label_clr_modified", str); + g_AppConfig->save(); +} + +void set_label_clr_sys(const wxColour& clr) { + g_color_label_sys = clr; + auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue()); + std::string str = clr_str.ToStdString(); + g_AppConfig->set("label_clr_sys", str); + g_AppConfig->save(); +} + +const wxColour& get_label_clr_default() { + return g_color_label_default; +} + unsigned get_colour_approx_luma(const wxColour &colour) { double r = colour.Red(); @@ -634,8 +814,8 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl { DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; m_optgroup = std::make_shared(parent, "", config); - const wxArrayInt& ar = preset_sizer->GetColWidths(); - m_optgroup->label_width = ar.IsEmpty() ? 100 : ar.front(); +// const wxArrayInt& ar = preset_sizer->GetColWidths(); +// m_optgroup->label_width = ar.IsEmpty() ? 100 : ar.front(); // doesn't work m_optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){ TabPrint* tab_print = nullptr; for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { @@ -689,10 +869,9 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl tab_print->update_dirty(); }; - const int width = 250; Option option = m_optgroup->get_option("fill_density"); option.opt.sidetext = ""; - option.opt.width = width; + option.opt.full_width = true; m_optgroup->append_single_option_line(option); ConfigOptionDef def; @@ -711,7 +890,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl "Everywhere"; def.default_value = new ConfigOptionStrings { selection }; option = Option(def, "support"); - option.opt.width = width; + option.opt.full_width = true; m_optgroup->append_single_option_line(option); m_brim_width = config->opt_float("brim_width"); @@ -750,7 +929,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl - sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxBottom, 1); + sizer->Add(m_optgroup->sizer, 1, wxEXPAND | wxBOTTOM, 2); } ConfigOptionsGroup* get_optgroup() @@ -769,6 +948,7 @@ wxWindow* export_option_creator(wxWindow* parent) wxPanel* panel = new wxPanel(parent, -1); wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); wxCheckBox* cbox = new wxCheckBox(panel, wxID_HIGHEST + 1, L("Export print config")); + cbox->SetValue(true); sizer->AddSpacer(5); sizer->Add(cbox, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5); panel->SetSizer(sizer); @@ -810,6 +990,11 @@ int get_export_option(wxFileDialog* dlg) } +void about() +{ + AboutDialog dlg; + dlg.ShowModal(); + dlg.Destroy(); +} -} // namespace GUI -} // namespace Slic3r +} } diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 0b681f48f1..3752b5a7a4 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -26,6 +26,7 @@ namespace Slic3r { class PresetBundle; class PresetCollection; class AppConfig; +class PresetUpdater; class DynamicPrintConfig; class TabIface; @@ -77,18 +78,35 @@ void set_main_frame(wxFrame *main_frame); void set_tab_panel(wxNotebook *tab_panel); void set_app_config(AppConfig *app_config); void set_preset_bundle(PresetBundle *preset_bundle); +void set_preset_updater(PresetUpdater *updater); AppConfig* get_app_config(); wxApp* get_app(); +PresetBundle* get_preset_bundle(); -const wxColour& get_modified_label_clr(); -const wxColour& get_sys_label_clr(); +const wxColour& get_label_clr_modified(); +const wxColour& get_label_clr_sys(); +const wxColour& get_label_clr_default(); unsigned get_colour_approx_luma(const wxColour &colour); +void set_label_clr_modified(const wxColour& clr); +void set_label_clr_sys(const wxColour& clr); -void add_debug_menu(wxMenuBar *menu, int event_language_change); +extern void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_language_change); + +// 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. +extern bool check_unsaved_changes(); + +// Checks if configuration wizard needs to run, calls config_wizard if so. +// Returns whether the Wizard ran. +extern bool config_wizard_startup(bool app_config_exists); + +// Opens the configuration wizard, returns true if wizard is finished & accepted. +// The run_reason argument is actually ConfigWizard::RunReason, but int is used here because of Perl. +extern void config_wizard(int run_reason); // Create "Preferences" dialog after selecting menu "Preferences" in Perl part -void open_preferences_dialog(int event_preferences); +extern void open_preferences_dialog(int event_preferences); // Create a new preset tab (print, filament and printer), void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed); @@ -100,6 +118,7 @@ void add_created_tab(Tab* panel); void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0); void show_error(wxWindow* parent, const wxString& message); +void show_error_id(int id, const std::string& message); // For Perl void show_info(wxWindow* parent, const wxString& message, const wxString& title); void warning_catcher(wxWindow* parent, const wxString& message); @@ -138,7 +157,11 @@ wxButton* get_wiping_dialog_button(); void add_export_option(wxFileDialog* dlg, const std::string& format); int get_export_option(wxFileDialog* dlg); -} -} + +// Display an About dialog +void about(); + +} // namespace GUI +} // namespace Slic3r #endif diff --git a/xs/src/slic3r/GUI/MsgDialog.cpp b/xs/src/slic3r/GUI/MsgDialog.cpp new file mode 100644 index 0000000000..6fba47a37f --- /dev/null +++ b/xs/src/slic3r/GUI/MsgDialog.cpp @@ -0,0 +1,87 @@ +#include "MsgDialog.hpp" + +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" +#include "GUI.hpp" +#include "ConfigWizard.hpp" + +namespace Slic3r { +namespace GUI { + + +MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id) : + MsgDialog(parent, title, headline, wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG), button_id) +{} + +MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id) : + wxDialog(parent, wxID_ANY, title), + boldfont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)), + content_sizer(new wxBoxSizer(wxVERTICAL)), + btn_sizer(new wxBoxSizer(wxHORIZONTAL)) +{ + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + auto *topsizer = new wxBoxSizer(wxHORIZONTAL); + auto *rightsizer = new wxBoxSizer(wxVERTICAL); + + auto *headtext = new wxStaticText(this, wxID_ANY, headline); + headtext->SetFont(boldfont); + headtext->Wrap(CONTENT_WIDTH); + rightsizer->Add(headtext); + rightsizer->AddSpacer(VERT_SPACING); + + rightsizer->Add(content_sizer, 1, wxEXPAND); + + if (button_id != wxID_NONE) { + auto *button = new wxButton(this, button_id); + button->SetFocus(); + btn_sizer->Add(button); + } + + rightsizer->Add(btn_sizer, 0, wxALIGN_CENTRE_HORIZONTAL); + + auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(bitmap)); + + topsizer->Add(logo, 0, wxALL, BORDER); + topsizer->Add(rightsizer, 1, wxALL | wxEXPAND, BORDER); + + SetSizerAndFit(topsizer); +} + +MsgDialog::~MsgDialog() {} + + +// ErrorDialog + +ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) : + MsgDialog(parent, _(L("Slic3r error")), _(L("Slic3r has encountered an error")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG)) +{ + auto *panel = new wxScrolledWindow(this); + auto *p_sizer = new wxBoxSizer(wxVERTICAL); + panel->SetSizer(p_sizer); + + auto *text = new wxStaticText(panel, wxID_ANY, msg); + text->Wrap(CONTENT_WIDTH); + p_sizer->Add(text, 1, wxEXPAND); + + panel->SetMinSize(wxSize(CONTENT_WIDTH, CONTENT_HEIGHT)); + panel->SetScrollRate(0, 5); + + content_sizer->Add(panel, 1, wxEXPAND); + + Fit(); +} + +ErrorDialog::~ErrorDialog() {} + + + +} +} diff --git a/xs/src/slic3r/GUI/MsgDialog.hpp b/xs/src/slic3r/GUI/MsgDialog.hpp new file mode 100644 index 0000000000..a011270238 --- /dev/null +++ b/xs/src/slic3r/GUI/MsgDialog.hpp @@ -0,0 +1,65 @@ +#ifndef slic3r_MsgDialog_hpp_ +#define slic3r_MsgDialog_hpp_ + +#include +#include + +#include +#include +#include + +#include "slic3r/Utils/Semver.hpp" + +class wxBoxSizer; +class wxCheckBox; + +namespace Slic3r { + +namespace GUI { + + +// A message / query dialog with a bitmap on the left and any content on the right +// with buttons underneath. +struct MsgDialog : wxDialog +{ + MsgDialog(MsgDialog &&) = delete; + MsgDialog(const MsgDialog &) = delete; + MsgDialog &operator=(MsgDialog &&) = delete; + MsgDialog &operator=(const MsgDialog &) = delete; + virtual ~MsgDialog(); + +protected: + enum { + CONTENT_WIDTH = 500, + CONTENT_HEIGHT = 300, + BORDER = 30, + VERT_SPACING = 15, + HORIZ_SPACING = 5, + }; + + // button_id is an id of a button that can be added by default, use wxID_NONE to disable + MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id = wxID_OK); + MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id = wxID_OK); + + wxFont boldfont; + wxBoxSizer *content_sizer; + wxBoxSizer *btn_sizer; +}; + + +// Generic error dialog, used for displaying exceptions +struct ErrorDialog : MsgDialog +{ + ErrorDialog(wxWindow *parent, const wxString &msg); + ErrorDialog(ErrorDialog &&) = delete; + ErrorDialog(const ErrorDialog &) = delete; + ErrorDialog &operator=(ErrorDialog &&) = delete; + ErrorDialog &operator=(const ErrorDialog &) = delete; + virtual ~ErrorDialog(); +}; + + +} +} + +#endif diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 168ffcdc93..09a2fe50f6 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -22,13 +22,13 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co // is the normal type. if (opt.gui_type.compare("select") == 0) { } else if (opt.gui_type.compare("select_open") == 0) { - m_fields.emplace(id, STDMOVE(Choice::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(Choice::Create(parent(), opt, id))); } else if (opt.gui_type.compare("color") == 0) { - m_fields.emplace(id, STDMOVE(ColourPicker::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(ColourPicker::Create(parent(), opt, id))); } else if (opt.gui_type.compare("f_enum_open") == 0 || opt.gui_type.compare("i_enum_open") == 0 || opt.gui_type.compare("i_enum_closed") == 0) { - m_fields.emplace(id, STDMOVE(Choice::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(Choice::Create(parent(), opt, id))); } else if (opt.gui_type.compare("slider") == 0) { } else if (opt.gui_type.compare("i_spin") == 0) { // Spinctrl } else { @@ -40,21 +40,21 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co case coPercents: case coString: case coStrings: - m_fields.emplace(id, STDMOVE(TextCtrl::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(TextCtrl::Create(parent(), opt, id))); break; case coBool: case coBools: - m_fields.emplace(id, STDMOVE(CheckBox::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(CheckBox::Create(parent(), opt, id))); break; case coInt: case coInts: - m_fields.emplace(id, STDMOVE(SpinCtrl::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(SpinCtrl::Create(parent(), opt, id))); break; case coEnum: - m_fields.emplace(id, STDMOVE(Choice::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(Choice::Create(parent(), opt, id))); break; case coPoints: - m_fields.emplace(id, STDMOVE(PointCtrl::Create(m_parent, opt, id))); + m_fields.emplace(id, STDMOVE(PointCtrl::Create(parent(), opt, id))); break; case coNone: break; default: @@ -90,8 +90,8 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co field->m_Undo_btn->Hide(); field->m_Undo_to_sys_btn->Hide(); } - if (nonsys_btn_icon != nullptr) - field->set_nonsys_btn_icon(nonsys_btn_icon()); +// if (nonsys_btn_icon != nullptr) +// field->set_nonsys_btn_icon(*nonsys_btn_icon); // assign function objects for callbacks, etc. return field; @@ -137,11 +137,11 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // Build a label if we have it wxStaticText* label=nullptr; if (label_width != 0) { - label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ":"), - wxDefaultPosition, wxSize(label_width, -1)); + label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ": "), + wxDefaultPosition, staticbox ? wxSize(label_width, -1) : wxDefaultSize); label->SetFont(label_font); label->Wrap(label_width); // avoid a Linux/GTK bug - grid_sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL,0); + grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT) | wxALIGN_CENTER_VERTICAL, 0); if (line.label_tooltip.compare("") != 0) label->SetToolTip(line.label_tooltip); } @@ -149,7 +149,8 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // If there's a widget, build it and add the result to the sizer. if (line.widget != nullptr) { auto wgt = line.widget(parent()); - grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, wxOSX ? 0 : 5); + // If widget doesn't have label, don't use border + grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, (wxOSX || line.label.IsEmpty()) ? 0 : 5); if (colored_Label != nullptr) *colored_Label = label; return; } @@ -165,7 +166,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL); if (is_window_field(field)) - sizer->Add(field->getWindow(), 0, (option.opt.full_width ? wxEXPAND : 0) | + sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, (option.opt.full_width ? wxEXPAND : 0) | wxBOTTOM | wxTOP | wxALIGN_CENTER_VERTICAL, wxOSX ? 0 : 2); if (is_sizer_field(field)) sizer->Add(field->getSizer(), 0, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0); @@ -467,10 +468,10 @@ Field* ConfigOptionsGroup::get_fieldc(const t_config_option_key& opt_key, int op return opt_id.empty() ? nullptr : get_field(opt_id); } -void ogStaticText::SetText(const wxString& value) +void ogStaticText::SetText(const wxString& value, bool wrap/* = true*/) { SetLabel(value); - Wrap(400); + if (wrap) Wrap(400); GetParent()->Layout(); } diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index dd6d48f46c..01d9184fd1 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -1,3 +1,6 @@ +#ifndef slic3r_OptionsGroup_hpp_ +#define slic3r_OptionsGroup_hpp_ + #include #include #include @@ -86,12 +89,18 @@ public: wxFont sidetext_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; wxFont label_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; - std::function nonsys_btn_icon{ nullptr }; +// std::function nonsys_btn_icon{ nullptr }; /// Returns a copy of the pointer of the parent wxWindow. /// Accessor function is because users are not allowed to change the parent /// but defining it as const means a lot of const_casts to deal with wx functions. - inline wxWindow* parent() const { return m_parent; } + inline wxWindow* parent() const { +#ifdef __WXGTK__ + return m_panel; +#else + return m_parent; +#endif /* __WXGTK__ */ + } void append_line(const Line& line, wxStaticText** colored_Label = nullptr); Line create_single_option_line(const Option& option) const; @@ -127,8 +136,15 @@ public: m_grid_sizer = new wxFlexGridSizer(0, num_columns, 0,0); static_cast(m_grid_sizer)->SetFlexibleDirection(wxHORIZONTAL); static_cast(m_grid_sizer)->AddGrowableCol(label_width != 0); - - sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX ? 0: 5); +#ifdef __WXGTK__ + m_panel = new wxPanel( _parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panel->SetSizer(m_grid_sizer); + m_panel->Layout(); + sizer->Fit(m_panel); + sizer->Add(m_panel, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5); +#else + sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5); +#endif /* __WXGTK__ */ } protected: @@ -144,6 +160,13 @@ protected: // "true" if option is created in preset tabs bool m_is_tab_opt{ false }; + // This panel is needed for correct showing of the ToolTips for Button, StaticText and CheckBox + // Tooltips on GTK doesn't work inside wxStaticBoxSizer unless you insert a panel + // inside it before you insert the other controls. +#ifdef __WXGTK__ + wxPanel* m_panel {nullptr}; +#endif /* __WXGTK__ */ + /// Generate a wxSizer or wxWindow from a configuration option /// Precondition: opt resolves to a known ConfigOption /// Postcondition: fields contains a wx gui object. @@ -200,7 +223,9 @@ public: ogStaticText(wxWindow* parent, const char *text) : wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize){} ~ogStaticText(){} - void SetText(const wxString& value); + void SetText(const wxString& value, bool wrap = true); }; }} + +#endif /* slic3r_OptionsGroup_hpp_ */ diff --git a/xs/src/slic3r/GUI/Preferences.cpp b/xs/src/slic3r/GUI/Preferences.cpp index 0009b17ecd..4c60577d56 100644 --- a/xs/src/slic3r/GUI/Preferences.cpp +++ b/xs/src/slic3r/GUI/Preferences.cpp @@ -9,11 +9,12 @@ void PreferencesDialog::build() { auto app_config = get_app_config(); m_optgroup = std::make_shared(this, _(L("General"))); - m_optgroup->label_width = 200; + m_optgroup->label_width = 400; m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; + // TODO // $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( // opt_id = > 'version_check', // type = > 'bool', @@ -48,6 +49,22 @@ void PreferencesDialog::build() option = Option (def,"background_processing"); m_optgroup->append_single_option_line(option); + // Please keep in sync with ConfigWizard + def.label = L("Check for application updates"); + def.type = coBool; + def.tooltip = L("If enabled, Slic3r checks for new versions of Slic3r PE online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done."); + def.default_value = new ConfigOptionBool(app_config->get("version_check") == "1"); + option = Option (def, "version_check"); + m_optgroup->append_single_option_line(option); + + // Please keep in sync with ConfigWizard + def.label = L("Update built-in Presets automatically"); + def.type = coBool; + def.tooltip = L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup."); + def.default_value = new ConfigOptionBool(app_config->get("preset_update") == "1"); + option = Option (def, "preset_update"); + m_optgroup->append_single_option_line(option); + def.label = L("Disable USB/serial connection"); def.type = coBool; def.tooltip = L("Disable communication with the printer over a serial / USB cable. " diff --git a/xs/src/slic3r/GUI/Preferences.hpp b/xs/src/slic3r/GUI/Preferences.hpp index c30e94d225..f94a1db69e 100644 --- a/xs/src/slic3r/GUI/Preferences.hpp +++ b/xs/src/slic3r/GUI/Preferences.hpp @@ -1,3 +1,6 @@ +#ifndef slic3r_Preferences_hpp_ +#define slic3r_Preferences_hpp_ + #include "GUI.hpp" #include @@ -25,3 +28,5 @@ public: } // GUI } // Slic3r + +#endif /* slic3r_Preferences_hpp_ */ diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 34b4c53264..bc59ac2f92 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -2,9 +2,14 @@ #include #include "Preset.hpp" +#include "AppConfig.hpp" +#include "BitmapCache.hpp" #include +#include +#include #include +#include #include #include @@ -13,6 +18,7 @@ #include #include #include +#include #include #include @@ -23,14 +29,16 @@ #include "../../libslic3r/Utils.hpp" #include "../../libslic3r/PlaceholderParser.hpp" +using boost::property_tree::ptree; + namespace Slic3r { -ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree) +ConfigFileType guess_config_file_type(const 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) { + for (const ptree::value_type &v : tree) { if (v.second.empty()) { if (v.first == "background_processing" || v.first == "last_output_path" || @@ -58,6 +66,80 @@ ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree) (bundle > config) ? CONFIG_FILE_TYPE_CONFIG_BUNDLE : CONFIG_FILE_TYPE_CONFIG; } + +VendorProfile VendorProfile::from_ini(const boost::filesystem::path &path, bool load_all) +{ + ptree tree; + boost::filesystem::ifstream ifs(path); + boost::property_tree::read_ini(ifs, tree); + return VendorProfile::from_ini(tree, path, load_all); +} + +VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem::path &path, bool load_all) +{ + static const std::string printer_model_key = "printer_model:"; + const std::string id = path.stem().string(); + + if (! boost::filesystem::exists(path)) { + throw std::runtime_error((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str()); + } + + VendorProfile res(id); + + auto get_or_throw = [&](const ptree &tree, const std::string &key) -> ptree::const_assoc_iterator + { + auto res = tree.find(key); + if (res == tree.not_found()) { + throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str()); + } + return res; + }; + + const auto &vendor_section = get_or_throw(tree, "vendor")->second; + res.name = get_or_throw(vendor_section, "name")->second.data(); + + auto config_version_str = get_or_throw(vendor_section, "config_version")->second.data(); + auto config_version = Semver::parse(config_version_str); + if (! config_version) { + throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str()); + } else { + res.config_version = std::move(*config_version); + } + + auto config_update_url = vendor_section.find("config_update_url"); + if (config_update_url != vendor_section.not_found()) { + res.config_update_url = config_update_url->second.data(); + } + + if (! load_all) { + return res; + } + + for (auto §ion : tree) { + if (boost::starts_with(section.first, printer_model_key)) { + VendorProfile::PrinterModel model; + model.id = section.first.substr(printer_model_key.size()); + model.name = section.second.get("name", model.id); + section.second.get("variants", ""); + const auto variants_field = section.second.get("variants", ""); + std::vector variants; + if (Slic3r::unescape_strings_cstyle(variants_field, variants)) { + for (const std::string &variant_name : variants) { + if (model.variant(variant_name) == nullptr) + model.variants.emplace_back(VendorProfile::PrinterVariant(variant_name)); + } + } else { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed variants field: `%2%`") % id % variants_field; + } + if (! model.id.empty() && ! model.variants.empty()) + res.models.push_back(std::move(model)); + } + } + + return res; +} + + // 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() @@ -175,6 +257,15 @@ bool Preset::update_compatible_with_printer(const Preset &active_printer, const return this->is_compatible = is_compatible_with_printer(active_printer, extra_config); } +void Preset::set_visible_from_appconfig(const AppConfig &app_config) +{ + if (vendor == nullptr) { return; } + const std::string &model = config.opt_string("printer_model"); + const std::string &variant = config.opt_string("printer_variant"); + if (model.empty() || variant.empty()) { return; } + is_visible = app_config.get_variant(vendor->id, model, variant); +} + const std::vector& Preset::print_options() { static std::vector s_opts { @@ -252,7 +343,8 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vectorfind_preset(name, false)) { - errors_cummulative += "The user preset \"" + name + "\" cannot be loaded. A system preset of the same name has already been loaded."; + // This happens when there's is a preset (most likely legacy one) with the same name as a system preset + // that's already been loaded from a bundle. + BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name; continue; } try { @@ -453,18 +549,6 @@ size_t PresetCollection::first_visible_idx() const 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) { @@ -473,7 +557,7 @@ void PresetCollection::set_default_suppressed(bool default_suppressed) } } -void PresetCollection::update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible) +size_t PresetCollection::update_compatible_with_printer_internal(const Preset &active_printer, bool unselect_if_incompatible) { DynamicPrintConfig config; config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name)); @@ -484,14 +568,12 @@ void PresetCollection::update_compatible_with_printer(const Preset &active_print 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, &config) && - selected && select_other_if_incompatible) + selected && unselect_if_incompatible) m_idx_selected = (size_t)-1; if (selected) preset_selected.is_compatible = preset_edited.is_compatible; } - if (m_idx_selected == (size_t)-1) - // Find some other compatible preset, or the "-- default --" preset. - this->select_preset(first_compatible_idx()); + return m_idx_selected; } // Save the preset under a new name. If the name is different from the old one, @@ -511,17 +593,41 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) // Otherwise fill in the list from scratch. ui->Freeze(); ui->Clear(); - std::map nonsys_presets; + + const Preset &selected_preset = this->get_selected_preset(); + // 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.is_compatible && m_bitmap_incompatible != nullptr; + + std::map nonsys_presets; wxString selected = ""; - for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) { + if (!this->m_presets.front().is_visible) + ui->Append("------- System presets -------", wxNullBitmap); + 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); + std::string bitmap_key = ""; + // 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 ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(16, 16) : *m_bitmap_incompatible); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(4, 16)); + bmps.emplace_back(*m_bitmap_main_frame); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(6, 16)); + bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(16, 16)); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } if (preset.is_default || preset.is_system){ ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), @@ -531,20 +637,18 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) } else { - nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), preset.is_compatible); + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); if (i == m_idx_selected) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); } if (preset.is_default) - ui->Append("------------------------------------", wxNullBitmap); + ui->Append("------- System presets -------", wxNullBitmap); } if (!nonsys_presets.empty()) { - ui->Append("------------------------------------", wxNullBitmap); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - const wxBitmap *bmp = it->second ? m_bitmap_compatible : m_bitmap_incompatible; - ui->Append(it->first, - (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); + ui->Append("------- User presets -------", wxNullBitmap); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + ui->Append(it->first, *it->second); if (it->first == selected) ui->SetSelection(ui->GetCount() - 1); } @@ -552,51 +656,63 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) ui->Thaw(); } -void PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible) +size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible) { if (ui == nullptr) - return; + return 0; ui->Freeze(); ui->Clear(); - std::map nonsys_presets; + size_t selected_preset_item = 0; + + std::map nonsys_presets; wxString selected = ""; - for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) { + if (!this->m_presets.front().is_visible) + ui->Append("------- System presets -------", wxNullBitmap); + 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 || (! 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); + std::string bitmap_key = "tab"; + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + const wxBitmap* tmp_bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible; + bmps.emplace_back((tmp_bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *tmp_bmp); + // Paint a lock at the system presets. + bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(16, 16)); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } if (preset.is_default || preset.is_system){ 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); + selected_preset_item = ui->GetCount() - 1; } else { - nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), preset.is_compatible); + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); if (i == m_idx_selected) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); } if (preset.is_default) - ui->Append("------------------------------------", wxNullBitmap); + ui->Append("------- System presets -------", wxNullBitmap); } if (!nonsys_presets.empty()) { - ui->Append("------------------------------------", wxNullBitmap); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - const wxBitmap *bmp = it->second ? m_bitmap_compatible : m_bitmap_incompatible; - ui->Append(it->first, - (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); + ui->Append("------- User presets -------", wxNullBitmap); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + ui->Append(it->first, *it->second); if (it->first == selected) - ui->SetSelection(ui->GetCount() - 1); + selected_preset_item = ui->GetCount() - 1; } } + ui->SetSelection(selected_preset_item); ui->Thaw(); + return selected_preset_item; } // Update a dirty floag of the current preset, update the labels of the UI component accordingly. @@ -629,11 +745,13 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) return was_dirty != is_dirty; } -std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference) +std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type /*= false*/) { std::vector changed; - if (edited != nullptr && reference != nullptr) { - changed = reference->config.diff(edited->config); + if (edited != nullptr && reference != nullptr) { + changed = is_printer_type ? + reference->config.deep_diff(edited->config) : + reference->config.diff(edited->config); // The "compatible_printers" option key is handled differently from the others: // It is not mandatory. If the key is missing, it means it is compatible with any printer. // If the key exists and it is empty, it means it is compatible with no printer. @@ -677,8 +795,8 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b // 1) Try to find the preset by its name. auto it = this->find_preset_internal(name); size_t idx = 0; - if (it != m_presets.end() && it->name == name) - // Preset found by its name. + if (it != m_presets.end() && it->name == name && it->is_visible) + // Preset found by its name and it is visible. idx = it - m_presets.begin(); else { // Find the first visible preset. @@ -690,6 +808,11 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b // If the first visible preset was not found, return the 0th element, which is the default preset. } + // Temporary decision + if (name_w_suffix == "------- System presets -------" || + name_w_suffix == "------- User presets -------") + return true; + // 2) Select the new preset. if (m_idx_selected != idx || force) { this->select_preset(idx); @@ -699,6 +822,46 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b return false; } +bool PresetCollection::select_preset_by_name_strict(const std::string &name) +{ + // 1) Try to find the preset by its name. + auto it = this->find_preset_internal(name); + size_t idx = (size_t)-1; + if (it != m_presets.end() && it->name == name && it->is_visible) + // Preset found by its name. + idx = it - m_presets.begin(); + // 2) Select the new preset. + if (idx != (size_t)-1) { + this->select_preset(idx); + return true; + } + m_idx_selected = idx; + return false; +} + +// Merge one vendor's presets with the other vendor's presets, report duplicates. +std::vector PresetCollection::merge_presets(PresetCollection &&other, const std::set &new_vendors) +{ + std::vector duplicates; + for (Preset &preset : other.m_presets) { + if (preset.is_default || preset.is_external) + continue; + Preset key(m_type, preset.name); + auto it = std::lower_bound(m_presets.begin() + 1, m_presets.end(), key); + if (it == m_presets.end() || it->name != preset.name) { + if (preset.vendor != nullptr) { + // Re-assign a pointer to the vendor structure in the new PresetBundle. + auto it = new_vendors.find(*preset.vendor); + assert(it != new_vendors.end()); + preset.vendor = &(*it); + } + this->m_presets.emplace(it, std::move(preset)); + } else + duplicates.emplace_back(std::move(preset.name)); + } + return duplicates; +} + std::string PresetCollection::name() const { switch (this->type()) { diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index c038160f4e..ab01a48ffd 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -3,8 +3,12 @@ #include +#include +#include + #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/PrintConfig.hpp" +#include "slic3r/Utils/Semver.hpp" class wxBitmap; class wxChoice; @@ -13,6 +17,13 @@ class wxItemContainer; namespace Slic3r { +class AppConfig; +class PresetBundle; + +namespace GUI { + class BitmapCache; +} + enum ConfigFileType { CONFIG_FILE_TYPE_UNKNOWN, @@ -28,21 +39,19 @@ class VendorProfile public: std::string name; std::string id; - std::string config_version; + Semver config_version; std::string config_update_url; struct PrinterVariant { PrinterVariant() {} PrinterVariant(const std::string &name) : name(name) {} std::string name; - bool enabled = true; }; struct PrinterModel { PrinterModel() {} - PrinterModel(const std::string &name) : name(name) {} + std::string id; std::string name; - bool enabled = true; std::vector variants; PrinterVariant* variant(const std::string &name) { for (auto &v : this->variants) @@ -51,11 +60,14 @@ public: return nullptr; } const PrinterVariant* variant(const std::string &name) const { return const_cast(this)->variant(name); } - - bool operator< (const PrinterModel &rhs) const { return this->name < rhs.name; } - bool operator==(const PrinterModel &rhs) const { return this->name == rhs.name; } }; - std::set models; + std::vector models; + + VendorProfile() {} + VendorProfile(std::string id) : id(std::move(id)) {} + + static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true); + static VendorProfile from_ini(const boost::property_tree::ptree &tree, const boost::filesystem::path &path, bool load_all=true); size_t num_variants() const { size_t n = 0; for (auto &model : models) n += model.variants.size(); return n; } @@ -86,7 +98,8 @@ public: bool is_external = false; // System preset is read-only. bool is_system = false; - // Preset is visible, if it is compatible with the active Printer. + // Preset is visible, if it is associated with a printer model / variant that is enabled in the AppConfig + // or if it has no printer model / variant association. // Also the "default" preset is only visible, if it is the only preset in the list. bool is_visible = true; // Has this preset been modified? @@ -132,6 +145,9 @@ public: // Mark this preset as compatible if it is compatible with active_printer. bool update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config); + // Set is_visible according to application config + void set_visible_from_appconfig(const AppConfig &app_config); + // 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); } @@ -163,6 +179,13 @@ public: PresetCollection(Preset::Type type, const std::vector &keys); ~PresetCollection(); + typedef std::deque::iterator Iterator; + typedef std::deque::const_iterator ConstIterator; + Iterator begin() { return m_presets.begin() + 1; } + ConstIterator begin() const { return m_presets.begin() + 1; } + Iterator end() { return m_presets.end(); } + ConstIterator end() const { return m_presets.end(); } + void reset(bool delete_files); Preset::Type type() const { return m_type; } @@ -234,37 +257,67 @@ public: { return const_cast(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 compatible preset. Certainly at least the '- default -' preset shall be compatible. + // If one of the prefered_alternates is compatible, select it. + template + size_t first_compatible_idx(PreferedCondition prefered_condition) const + { + size_t i = m_default_suppressed ? 1 : 0; + size_t n = this->m_presets.size(); + size_t i_compatible = n; + for (; i < n; ++ i) + if (m_presets[i].is_compatible) { + if (prefered_condition(m_presets[i].name)) + return i; + if (i_compatible == n) + // Store the first compatible profile into i_compatible. + i_compatible = i; + } + return (i_compatible == n) ? 0 : i_compatible; + } + // Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible. + size_t first_compatible_idx() const { return this->first_compatible_idx([](const std::string&){return true;}); } + // 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()); } + template + Preset& first_compatible(PreferedCondition prefered_condition) { return this->preset(this->first_compatible_idx(prefered_condition)); } 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(); } // For Print / Filament presets, disable those, which are not compatible with the printer. - void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible); + template + void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible, PreferedCondition prefered_condition) + { + if (this->update_compatible_with_printer_internal(active_printer, select_other_if_incompatible) == (size_t)-1) + // Find some other compatible preset, or the "-- default --" preset. + this->select_preset(this->first_compatible_idx(prefered_condition)); + } + void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible) + { this->update_compatible_with_printer(active_printer, select_other_if_incompatible, [](const std::string&){return true;}); } size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); } // Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ. bool current_is_dirty() const { 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 current_dirty_options() const - { return dirty_options(&this->get_edited_preset(), &this->get_selected_preset()); } + std::vector current_dirty_options(const bool is_printer_type = false) const + { return dirty_options(&this->get_edited_preset(), &this->get_selected_preset(), is_printer_type); } // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ. - std::vector current_different_from_parent_options() const - { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent()); } + std::vector current_different_from_parent_options(const bool is_printer_type = false) const + { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), is_printer_type); } // Compare the content of get_selected_preset() with get_selected_preset_parent() configs, return the list of keys where they equal. std::vector system_equal_options() const; // Update the choice UI from the list of presets. // 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); + size_t 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. @@ -282,6 +335,14 @@ public: // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string path_from_name(const std::string &new_name) const; +protected: + // Select a preset, if it exists. If it does not exist, select an invalid (-1) index. + // This is a temporary state, which shall be fixed immediately by the following step. + bool select_preset_by_name_strict(const std::string &name); + + // Merge one vendor's presets with the other vendor's presets, report duplicates. + std::vector merge_presets(PresetCollection &&other, const std::set &new_vendors); + private: PresetCollection(); PresetCollection(const PresetCollection &other); @@ -299,7 +360,9 @@ private: std::deque::const_iterator find_preset_internal(const std::string &name) const { return const_cast(this)->find_preset_internal(name); } - static std::vector dirty_options(const Preset *edited, const Preset *reference); + size_t update_compatible_with_printer_internal(const Preset &active_printer, bool unselect_if_incompatible); + + static std::vector dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type = false); // Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER. Preset::Type m_type; @@ -324,6 +387,12 @@ private: wxBitmap *m_bitmap_main_frame; // Path to the directory to store the config files into. std::string m_dir_path; + + // Caching color bitmaps for the filament combo box. + GUI::BitmapCache *m_bitmap_cache = nullptr; + + // to access select_preset_by_name_strict() + friend class PresetBundle; }; } // namespace Slic3r diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 1c3a6dcac8..9123e144b4 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -4,6 +4,7 @@ #include "PresetBundle.hpp" #include "BitmapCache.hpp" +#include #include #include #include @@ -111,6 +112,7 @@ void PresetBundle::setup_directories() std::initializer_list paths = { data_dir, data_dir / "vendor", + data_dir / "cache", #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR // Store the print/filament/printer presets into a "presets" directory. data_dir / "presets", @@ -176,6 +178,7 @@ std::string PresetBundle::load_system_presets() // Here the vendor specific read only Config Bundles are stored. boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred(); std::string errors_cummulative; + bool first = true; 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(); @@ -183,7 +186,25 @@ std::string PresetBundle::load_system_presets() name.erase(name.size() - 4); try { // Load the config bundle, flatten it. - this->load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM); + if (first) { + // Reset this PresetBundle and load the first vendor config. + this->load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM); + first = false; + } else { + // Load the other vendor configs, merge them with this PresetBundle. + // Report duplicate profiles. + PresetBundle other; + other.load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM); + std::vector duplicates = this->merge_presets(std::move(other)); + if (! duplicates.empty()) { + errors_cummulative += "Vendor configuration file " + name + " contains the following presets with names used by other vendors: "; + for (size_t i = 0; i < duplicates.size(); ++ i) { + if (i > 0) + errors_cummulative += ", "; + errors_cummulative += duplicates[i]; + } + } + } } catch (const std::runtime_error &err) { errors_cummulative += err.what(); errors_cummulative += "\n"; @@ -192,6 +213,18 @@ std::string PresetBundle::load_system_presets() return errors_cummulative; } +// Merge one vendor's presets with the other vendor's presets, report duplicates. +std::vector PresetBundle::merge_presets(PresetBundle &&other) +{ + this->vendors.insert(other.vendors.begin(), other.vendors.end()); + std::vector duplicate_prints = this->prints .merge_presets(std::move(other.prints), this->vendors); + std::vector duplicate_filaments = this->filaments.merge_presets(std::move(other.filaments), this->vendors); + std::vector duplicate_printers = this->printers .merge_presets(std::move(other.printers), this->vendors); + append(duplicate_prints, std::move(duplicate_filaments)); + append(duplicate_prints, std::move(duplicate_printers)); + return duplicate_prints; +} + static inline std::string remove_ini_suffix(const std::string &name) { std::string out = name; @@ -205,26 +238,44 @@ static inline std::string remove_ini_suffix(const std::string &name) // If the "vendor" section is missing, enable all models and variants of the particular vendor. void PresetBundle::load_installed_printers(const AppConfig &config) { - // m_storage + for (auto &preset : printers) { + preset.set_visible_from_appconfig(config); + } } // 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); + // Update visibility of presets based on application vendor / model / variant configuration. + this->load_installed_printers(config); + + // Parse the initial print / filament / printer profile names. + std::string initial_print_profile_name = remove_ini_suffix(config.get("presets", "print")); + std::vector initial_filament_profile_names; + std::string initial_printer_profile_name = remove_ini_suffix(config.get("presets", "printer")); + auto *nozzle_diameter = dynamic_cast(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); + initial_filament_profile_names.emplace_back(remove_ini_suffix(config.get("presets", "filament"))); + this->set_filament_preset(0, initial_filament_profile_names.back()); 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))); + initial_filament_profile_names.emplace_back(remove_ini_suffix(config.get("presets", name))); + this->set_filament_preset(i, initial_filament_profile_names.back()); } + + // Activate print / filament / printer profiles from the config. + // If the printer profile enumerated by the config are not visible, select an alternate preset. + // Do not select alternate profiles for the print / filament profiles as those presets + // will be selected by the following call of this->update_compatible_with_printer(true). + prints.select_preset_by_name_strict(initial_print_profile_name); + filaments.select_preset_by_name_strict(initial_filament_profile_names.front()); + printers.select_preset_by_name(initial_printer_profile_name, true); + // Update visibility of presets based on their compatibility with the active printer. // Always try to select a compatible print and filament preset to the current printer preset, // as the application may have been closed with an active "external" preset, which does not @@ -675,48 +726,6 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree) flatten_configbundle_hierarchy(tree, "printer"); } -static void load_vendor_profile(const boost::property_tree::ptree &tree, VendorProfile &vendor_profile) -{ - const std::string printer_model_key = "printer_model:"; - for (auto §ion : tree) - if (section.first == "vendor") { - // Load the names of the active presets. - for (auto &kvp : section.second) { - if (kvp.first == "name") - vendor_profile.name = kvp.second.data(); - else if (kvp.first == "id") - vendor_profile.id = kvp.second.data(); - else if (kvp.first == "config_version") - vendor_profile.config_version = kvp.second.data(); - else if (kvp.first == "config_update_url") - vendor_profile.config_update_url = kvp.second.data(); - } - } else if (boost::starts_with(section.first, printer_model_key)) { - VendorProfile::PrinterModel model; - model.name = section.first.substr(printer_model_key.size()); - section.second.get("variants", ""); - std::vector variants; - if (Slic3r::unescape_strings_cstyle(section.second.get("variants", ""), variants)) { - for (const std::string &variant_name : variants) { - if (model.variant(variant_name) == nullptr) - model.variants.emplace_back(VendorProfile::PrinterVariant(variant_name)); - } - } else { - // Log error? - } - if (! model.name.empty() && ! model.variants.empty()) - vendor_profile.models.insert(model); - } -} - -// Load a config bundle file, into presets and store the loaded presets into separate files -// of the local configuration directory. -void PresetBundle::install_vendor_configbundle(const std::string &src_path0) -{ - boost::filesystem::path src_path(src_path0); - boost::filesystem::copy_file(src_path, (boost::filesystem::path(data_dir()) / "vendor" / src_path.filename()).make_preferred(), boost::filesystem::copy_option::overwrite_if_exists); -} - // 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, unsigned int flags) @@ -730,19 +739,21 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla pt::ptree tree; boost::nowide::ifstream ifs(path); pt::read_ini(ifs, tree); - // Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed. - flatten_configbundle_hierarchy(tree); const VendorProfile *vendor_profile = nullptr; - if (flags & LOAD_CFGBNDLE_SYSTEM) { - VendorProfile vp; - load_vendor_profile(tree, vp); - if (vp.name.empty()) - throw std::runtime_error(std::string("Vendor Config Bundle is not valid: Missing vendor name key.")); + if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) { + auto vp = VendorProfile::from_ini(tree, path); if (vp.num_variants() == 0) return 0; vendor_profile = &(*this->vendors.insert(vp).first); } + + if (flags & LOAD_CFGBUNDLE_VENDOR_ONLY) { + return 0; + } + + // 1.5) Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed. + flatten_configbundle_hierarchy(tree); // 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files. std::vector loaded_prints; @@ -814,7 +825,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla section.first << "\" defines no printer variant, it will be ignored."; continue; } - auto it_model = vendor_profile->models.find(VendorProfile::PrinterModel(printer_model)); + auto it_model = std::find_if(vendor_profile->models.cbegin(), vendor_profile->models.cend(), + [&](const VendorProfile::PrinterModel &m) { return m.id == printer_model; } + ); if (it_model == vendor_profile->models.end()) { BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << section.first << "\" defines invalid printer model \"" << printer_model << "\", it will be ignored."; @@ -916,14 +929,35 @@ void PresetBundle::update_multi_material_filament_presets() void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible) { - this->prints.update_compatible_with_printer(this->printers.get_edited_preset(), select_other_if_incompatible); - this->filaments.update_compatible_with_printer(this->printers.get_edited_preset(), select_other_if_incompatible); + const Preset &printer_preset = this->printers.get_edited_preset(); + const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile"); + const std::vector &prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values; + prefered_print_profile.empty() ? + this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : + this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible, + [&prefered_print_profile](const std::string& profile_name){ return profile_name == prefered_print_profile; }); + prefered_filament_profiles.empty() ? + this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : + this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible, + [&prefered_filament_profiles](const std::string& profile_name) + { return std::find(prefered_filament_profiles.begin(), prefered_filament_profiles.end(), profile_name) != prefered_filament_profiles.end(); }); 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; + this->filament_presets.front() = this->filaments.get_edited_preset().name; + for (size_t idx = 1; idx < this->filament_presets.size(); ++ idx) { + std::string &filament_name = this->filament_presets[idx]; + Preset *preset = this->filaments.find_preset(filament_name, false); + if (preset == nullptr || ! preset->is_compatible) { + // Pick a compatible profile. If there are prefered_filament_profiles, use them. + if (prefered_filament_profiles.empty()) + filament_name = this->filaments.first_compatible().name; + else { + const std::string &preferred = (idx < prefered_filament_profiles.size()) ? + prefered_filament_profiles[idx] : prefered_filament_profiles.front(); + filament_name = this->filaments.first_compatible( + [&preferred](const std::string& profile_name){ return profile_name == preferred; }).name; + } + } } } } @@ -977,6 +1011,10 @@ void PresetBundle::export_configbundle(const std::string &path) //, const Dynami // an optional "(modified)" suffix will be removed from the filament name. void PresetBundle::set_filament_preset(size_t idx, const std::string &name) { + if (name == "------- System presets -------" || + name == "------- User presets -------") + return; + if (idx >= filament_presets.size()) filament_presets.resize(idx + 1, filaments.default_preset().name); filament_presets[idx] = Preset::remove_suffix_modified(name); @@ -1025,9 +1063,11 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma // 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); - std::map nonsys_presets; + std::map nonsys_presets; wxString selected_str = ""; - for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++ i) { + if (!this->filaments().front().is_visible) + ui->Append("------- System presets -------", wxNullBitmap); + 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)) @@ -1059,14 +1099,11 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma bmps.emplace_back(m_bitmapCache->mksolid(8, 16, rgb)); } // Paint a lock at the system presets. - bmps.emplace_back(m_bitmapCache->mkclear(4, 16)); - bmps.emplace_back((preset.is_system || preset.is_default) ? - (preset.is_dirty ? *m_bitmapLockOpen : *m_bitmapLock) : m_bitmapCache->mkclear(16, 16)); + bmps.emplace_back(m_bitmapCache->mkclear(2, 16)); + bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmapLock : m_bitmapCache->mkclear(16, 16)); +// (preset.is_dirty ? *m_bitmapLockOpen : *m_bitmapLock) : m_bitmapCache->mkclear(16, 16)); bitmap = m_bitmapCache->insert(bitmap_key, bmps); } -// 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); if (preset.is_default || preset.is_system){ ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), @@ -1077,19 +1114,19 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma else { nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), - (bitmap == 0) ? wxNullBitmap : *bitmap); + (bitmap == 0) ? &wxNullBitmap : bitmap); if (selected) selected_str = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); } if (preset.is_default) - ui->Append("------------------------------------", wxNullBitmap); + ui->Append("------- System presets -------", wxNullBitmap); } if (!nonsys_presets.empty()) { - ui->Append("------------------------------------", wxNullBitmap); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - ui->Append(it->first, it->second); + ui->Append("------- User presets -------", wxNullBitmap); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + ui->Append(it->first, *it->second); if (it->first == selected_str) ui->SetSelection(ui->GetCount() - 1); } diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp index c0307c977c..9f2afbead2 100644 --- a/xs/src/slic3r/GUI/PresetBundle.hpp +++ b/xs/src/slic3r/GUI/PresetBundle.hpp @@ -5,6 +5,7 @@ #include "Preset.hpp" #include +#include namespace Slic3r { @@ -86,13 +87,11 @@ public: LOAD_CFGBNDLE_RESET_USER_PROFILE = 2, // Load a system config bundle. LOAD_CFGBNDLE_SYSTEM = 4, + LOAD_CFGBUNDLE_VENDOR_ONLY = 8, }; // Load the config bundle, store it to the user profile directory by default. size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE); - // Install the Vendor specific config bundle into user's directory. - void install_vendor_configbundle(const std::string &src_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); @@ -117,10 +116,12 @@ public: // preset if the current print or filament preset is not compatible. void update_compatible_with_printer(bool select_other_if_incompatible); - static bool parse_color(const std::string &scolor, unsigned char *rgb_out); + static bool parse_color(const std::string &scolor, unsigned char *rgb_out); private: std::string load_system_presets(); + // Merge one vendor's presets with the other vendor's presets, report duplicates. + std::vector merge_presets(PresetBundle &&other); // Set the "enabled" flag for printer vendors, printer models and printer variants // based on the user configuration. diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 3e4d8ce609..96419f6c85 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -8,6 +8,7 @@ #include "slic3r/Utils/OctoPrint.hpp" #include "BonjourDialog.hpp" #include "WipeTowerDialog.hpp" +#include "ButtonsDescription.hpp" #include #include @@ -23,6 +24,9 @@ #include #include "wxExtensions.hpp" +#include + +#include namespace Slic3r { namespace GUI { @@ -88,9 +92,9 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) if (wxMSW) m_btn_delete_preset->SetBackgroundColour(color); m_show_incompatible_presets = false; - m_bmp_show_incompatible_presets = new wxBitmap(from_u8(Slic3r::var("flag-red-icon.png")), wxBITMAP_TYPE_PNG); - m_bmp_hide_incompatible_presets = new wxBitmap(from_u8(Slic3r::var("flag-green-icon.png")), wxBITMAP_TYPE_PNG); - m_btn_hide_incompatible_presets = new wxBitmapButton(panel, wxID_ANY, *m_bmp_hide_incompatible_presets, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); + m_bmp_show_incompatible_presets.LoadFile(from_u8(Slic3r::var("flag-red-icon.png")), wxBITMAP_TYPE_PNG); + m_bmp_hide_incompatible_presets.LoadFile(from_u8(Slic3r::var("flag-green-icon.png")), wxBITMAP_TYPE_PNG); + m_btn_hide_incompatible_presets = new wxBitmapButton(panel, wxID_ANY, m_bmp_hide_incompatible_presets, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); if (wxMSW) m_btn_hide_incompatible_presets->SetBackgroundColour(color); m_btn_save_preset->SetToolTip(_(L("Save current ")) + m_title); @@ -99,14 +103,52 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_undo_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); m_undo_to_sys_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + m_question_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); if (wxMSW) { m_undo_btn->SetBackgroundColour(color); m_undo_to_sys_btn->SetBackgroundColour(color); + m_question_btn->SetBackgroundColour(color); } - m_undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); - m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); })); - m_undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); - m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); })); + + m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information \n" + "or click this button."))); + + // Determine the theme color of OS (dark or light) + auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + m_bmp_value_lock .LoadFile(from_u8(var("sys_lock.png")), wxBITMAP_TYPE_PNG); + m_bmp_value_unlock .LoadFile(from_u8(var(luma >= 128 ? "sys_unlock.png" : "sys_unlock_grey.png")), wxBITMAP_TYPE_PNG); + m_bmp_non_system = &m_bmp_white_bullet; + // Bitmaps to be shown on the "Undo user changes" button next to each input field. + m_bmp_value_revert .LoadFile(from_u8(var(luma >= 128 ? "action_undo.png" : "action_undo_grey.png")), wxBITMAP_TYPE_PNG); + m_bmp_white_bullet .LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG); + m_bmp_question .LoadFile(from_u8(var("question_mark_01.png")), wxBITMAP_TYPE_PNG); + + fill_icon_descriptions(); + set_tooltips_text(); + + m_undo_btn->SetBitmap(m_bmp_white_bullet); + m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_roll_back_value(); })); + m_undo_to_sys_btn->SetBitmap(m_bmp_white_bullet); + m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_roll_back_value(true); })); + m_question_btn->SetBitmap(m_bmp_question); + m_question_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) + { + auto dlg = new ButtonsDescription(this, &m_icon_descriptions); + if (dlg->ShowModal() == wxID_OK){ + // Colors for ui "decoration" + for (Tab *tab : get_tabs_list()){ + tab->m_sys_label_clr = get_label_clr_sys(); + tab->m_modified_label_clr = get_label_clr_modified(); + tab->update_labels_colour(); + } + } + })); + + // Colors for ui "decoration" + m_sys_label_clr = get_label_clr_sys(); + m_modified_label_clr = get_label_clr_modified(); + m_default_text_clr = get_label_clr_default(); m_hsizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_hsizer, 0, wxBOTTOM, 3); @@ -120,7 +162,8 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_hsizer->AddSpacer(64); m_hsizer->Add(m_undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->Add(m_undo_btn, 0, wxALIGN_CENTER_VERTICAL); -// m_hsizer->AddSpacer(64); + m_hsizer->AddSpacer(32); + m_hsizer->Add(m_question_btn, 0, wxALIGN_CENTER_VERTICAL); // m_hsizer->Add(m_cc_presets_choice, 1, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); //Horizontal sizer to hold the tree and the selected page. @@ -183,8 +226,16 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) //! select_preset(m_presets_choice->GetStringSelection().ToStdString()); //! we doing next: int selected_item = m_presets_choice->GetSelection(); + if (m_selected_preset_item == selected_item && !m_presets->current_is_dirty()) + return; if (selected_item >= 0){ std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); + if (selected_string == "------- System presets -------" || + selected_string == "------- User presets -------"){ + m_presets_choice->SetSelection(m_selected_preset_item); + return; + } + m_selected_preset_item = selected_item; select_preset(selected_string); } })); @@ -204,8 +255,9 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) void Tab::load_initial_data() { m_config = &m_presets->get_edited_preset().config; - m_nonsys_btn_icon = m_presets->get_selected_preset_parent() == nullptr ? - "bullet_white.png" : "sys_unlock.png"; + m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; + m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; + m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; } PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages/* = false*/) @@ -234,140 +286,128 @@ PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bo return page; } -template -void add_correct_opts_to_dirty_options(const std::string &opt_key, std::vector *vec, TabPrinter *tab) +void Tab::update_labels_colour() { - auto opt_init = static_cast(tab->m_presets->get_selected_preset().config.option(opt_key)); - auto opt_cur = static_cast(tab->m_config->option(opt_key)); - int opt_init_max_id = opt_init->values.size()-1; - for (int i = 0; i < opt_cur->values.size(); i++) + Freeze(); + //update options "decoration" + for (const auto opt : m_options_list) { - int init_id = i <= opt_init_max_id ? i : 0; - if (opt_cur->values[i] != opt_init->values[init_id]) - vec->emplace_back(opt_key + "#" + std::to_string(i)); - } -} + const wxColour *color = &m_sys_label_clr; -template -void add_correct_opts_to_sys_options(const std::string &opt_key, std::vector *vec, TabPrinter *tab) -{ - const Preset* sys_preset = tab->m_presets->get_selected_preset_parent(); - if (sys_preset == nullptr) - return; - T *opt_cur = static_cast(tab->m_config->option(opt_key)); - const T *opt_sys = static_cast(sys_preset->config.option(opt_key)); - int opt_max_id = opt_sys->values.size()-1; - for (int i = 0; i < opt_cur->values.size(); i++) - { - int init_id = i <= opt_max_id ? i : 0; - if (opt_cur->values[i] == opt_sys->values[init_id]) - vec->emplace_back(opt_key + "#" + std::to_string(i)); + // value isn't equal to system value + if ((opt.second & osSystemValue) == 0){ + // value is equal to last saved + if ((opt.second & osInitValue) != 0) + color = &m_default_text_clr; + // value is modified + else + color = &m_modified_label_clr; + } + if (opt.first == "bed_shape" || opt.first == "compatible_printers") { + if (m_colored_Label != nullptr) { + m_colored_Label->SetForegroundColour(*color); + m_colored_Label->Refresh(true); + } + continue; + } + + Field* field = get_field(opt.first); + if (field == nullptr) continue; + field->set_label_colour_force(color); + } + Thaw(); + + auto cur_item = m_treectrl->GetFirstVisibleItem(); + while (cur_item){ + auto title = m_treectrl->GetItemText(cur_item); + for (auto page : m_pages) + { + if (page->title() != title) + continue; + + const wxColor *clr = !page->m_is_nonsys_values ? &m_sys_label_clr : + page->m_is_modified_values ? &m_modified_label_clr : + &m_default_text_clr; + + m_treectrl->SetItemTextColour(cur_item, *clr); + break; + } + cur_item = m_treectrl->GetNextVisible(cur_item); } } // Update UI according to changes void Tab::update_changed_ui() { - auto dirty_options = m_presets->current_dirty_options(); + if (m_postpone_update_ui) + return; - if (name() == "printer"){ - // Update dirty_options in case changes of Extruder's options + const bool is_printer_type = (name() == "printer"); + auto dirty_options = m_presets->current_dirty_options(is_printer_type); + auto nonsys_options = m_presets->current_different_from_parent_options(is_printer_type); + if (is_printer_type){ TabPrinter* tab = static_cast(this); - m_dirty_options.resize(0); - for (auto opt_key : dirty_options) - { - if (opt_key == "bed_shape"){ m_dirty_options.emplace_back(opt_key); continue; } - switch (m_config->option(opt_key)->type()) - { - case coInts: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; - case coBools: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; - case coFloats: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; - case coStrings: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; - case coPercents:add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; - case coPoints: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; - default: m_dirty_options.emplace_back(opt_key); break; - } - } if (tab->m_initial_extruders_count != tab->m_extruders_count) - m_dirty_options.emplace_back("extruders_count"); - - m_sys_options.resize(0); - const auto sys_preset = m_presets->get_selected_preset_parent(); - if (sys_preset){ - for (auto opt_key : m_config->keys()) - { - if (opt_key == "bed_shape"){ - if (*tab->m_config->option(opt_key) == *sys_preset->config.option(opt_key)) - m_sys_options.emplace_back(opt_key); - continue; - } - switch (m_config->option(opt_key)->type()) - { - case coInts: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; - case coBools: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; - case coFloats: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; - case coStrings: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; - case coPercents:add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; - case coPoints: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; - default:{ - const ConfigOption *opt_cur = tab->m_config->option(opt_key); - const ConfigOption *opt_sys = sys_preset->config.option(opt_key); - if (opt_cur != nullptr && opt_sys != nullptr && *opt_cur == *opt_sys) - m_sys_options.emplace_back(opt_key); - break; - } - } - } - - if (tab->m_sys_extruders_count == tab->m_extruders_count) - m_sys_options.emplace_back("extruders_count"); - } - } - else{ - m_sys_options = m_presets->system_equal_options(); - m_dirty_options = dirty_options; + dirty_options.emplace_back("extruders_count"); + if (tab->m_sys_extruders_count != tab->m_extruders_count) + nonsys_options.emplace_back("extruders_count"); } + for (auto& it : m_options_list) + it.second = m_opt_status_value; + + for (auto opt_key : dirty_options) m_options_list[opt_key] &= ~osInitValue; + for (auto opt_key : nonsys_options) m_options_list[opt_key] &= ~osSystemValue; + Freeze(); //update options "decoration" - for (const auto opt_key : m_full_options_list) + for (const auto opt : m_options_list) { bool is_nonsys_value = false; bool is_modified_value = true; - std::string sys_icon = "sys_lock.png"; - std::string icon = "action_undo.png"; - wxColour color = get_sys_label_clr(); - if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) { + const wxBitmap *sys_icon = &m_bmp_value_lock; + const wxBitmap *icon = &m_bmp_value_revert; + + const wxColour *color = &m_sys_label_clr; + + const wxString *sys_tt = &m_tt_value_lock; + const wxString *tt = &m_tt_value_revert; + + // value isn't equal to system value + if ((opt.second & osSystemValue) == 0){ is_nonsys_value = true; - sys_icon = m_nonsys_btn_icon; - if(find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) - color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + sys_icon = m_bmp_non_system; + sys_tt = m_tt_non_system; + // value is equal to last saved + if ((opt.second & osInitValue) != 0) + color = &m_default_text_clr; + // value is modified else - color = get_modified_label_clr(); + color = &m_modified_label_clr; } - if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) + if ((opt.second & osInitValue) != 0) { is_modified_value = false; - icon = "bullet_white.png"; + icon = &m_bmp_white_bullet; + tt = &m_tt_white_bullet; } - if (opt_key == "bed_shape" || opt_key == "compatible_printers") { + if (opt.first == "bed_shape" || opt.first == "compatible_printers") { if (m_colored_Label != nullptr) { - m_colored_Label->SetForegroundColour(color); + m_colored_Label->SetForegroundColour(*color); m_colored_Label->Refresh(true); } continue; } - Field* field = get_field(opt_key); + Field* field = get_field(opt.first); if (field == nullptr) continue; field->m_is_nonsys_value = is_nonsys_value; field->m_is_modified_value = is_modified_value; - field->m_Undo_btn->SetBitmap(wxBitmap(from_u8(var(icon)), wxBITMAP_TYPE_PNG)); - field->m_Undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(sys_icon)), wxBITMAP_TYPE_PNG)); - if (field->m_Label != nullptr){ - field->m_Label->SetForegroundColour(color); - field->m_Label->Refresh(true); - } + field->set_undo_bitmap(icon); + field->set_undo_to_sys_bitmap(sys_icon); + field->set_undo_tooltip(tt); + field->set_undo_to_sys_tooltip(sys_tt); + field->set_label_colour(color); } Thaw(); @@ -376,67 +416,53 @@ void Tab::update_changed_ui() }); } +void Tab::init_options_list() +{ + if (!m_options_list.empty()) + m_options_list.clear(); + + for (const auto opt_key : m_config->keys()) + m_options_list.emplace(opt_key, m_opt_status_value); +} + template -void add_correct_opts_to_full_options_list(const std::string &opt_key, std::vector *vec, TabPrinter *tab) +void add_correct_opts_to_options_list(const std::string &opt_key, std::map& map, TabPrinter *tab, const int& value) { T *opt_cur = static_cast(tab->m_config->option(opt_key)); for (int i = 0; i < opt_cur->values.size(); i++) - vec->emplace_back(opt_key + "#" + std::to_string(i)); + map.emplace(opt_key + "#" + std::to_string(i), value); } -void Tab::update_full_options_list() +void TabPrinter::init_options_list() { - if (!m_full_options_list.empty()) - m_full_options_list.resize(0); + if (!m_options_list.empty()) + m_options_list.clear(); - if (m_name != "printer"){ - m_full_options_list = m_config->keys(); - return; - } - - TabPrinter* tab = static_cast(this); for (const auto opt_key : m_config->keys()) { if (opt_key == "bed_shape"){ - m_full_options_list.emplace_back(opt_key); + m_options_list.emplace(opt_key, m_opt_status_value); continue; } switch (m_config->option(opt_key)->type()) { - case coInts: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; - case coBools: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; - case coFloats: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; - case coStrings: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; - case coPercents:add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; - case coPoints: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; - default: m_full_options_list.emplace_back(opt_key); break; + case coInts: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coBools: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coFloats: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coStrings: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coPercents:add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coPoints: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + default: m_options_list.emplace(opt_key, m_opt_status_value); break; } } - m_full_options_list.emplace_back("extruders_count"); -} - -void Tab::update_sys_ui_after_sel_preset() -{ - for (const auto opt_key : m_full_options_list){ - Field* field = get_field(opt_key); - if (field != nullptr){ - field->m_Undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(m_nonsys_btn_icon)), wxBITMAP_TYPE_PNG)); - field->m_is_nonsys_value = true; - if (field->m_Label != nullptr){ - field->m_Label->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); - field->m_Label->Refresh(true); - } - } - } - m_sys_options.resize(0); + m_options_list.emplace("extruders_count", m_opt_status_value); } void Tab::get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page) { - if (sys_page && find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) - sys_page = false; - if (!modified_page && find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) - modified_page = true; + auto opt = m_options_list.find(opt_key); + if (sys_page) sys_page = (opt->second & osSystemValue) != 0; + if (!modified_page) modified_page = (opt->second & osInitValue) == 0; } void Tab::update_changed_tree_ui() @@ -457,7 +483,7 @@ void Tab::update_changed_tree_ui() get_sys_and_mod_flags(opt_key, sys_page, modified_page); } } - if (title == _("Dependencies")){ + if (title == _("Dependencies") && name() != "printer"){ get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); } for (auto group : page->m_optgroups) @@ -469,12 +495,13 @@ void Tab::update_changed_tree_ui() get_sys_and_mod_flags(opt_key, sys_page, modified_page); } } - if (sys_page) - m_treectrl->SetItemTextColour(cur_item, get_sys_label_clr()); - else if (modified_page) - m_treectrl->SetItemTextColour(cur_item, get_modified_label_clr()); - else - m_treectrl->SetItemTextColour(cur_item, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); + + const wxColor *clr = sys_page ? &m_sys_label_clr : + modified_page ? &m_modified_label_clr : + &m_default_text_clr; + + if (page->set_item_colour(clr)) + m_treectrl->SetItemTextColour(cur_item, *clr); page->m_is_nonsys_values = !sys_page; page->m_is_modified_values = modified_page; @@ -493,80 +520,62 @@ void Tab::update_changed_tree_ui() void Tab::update_undo_buttons() { - const std::string& undo_icon = !m_is_modified_values ? "bullet_white.png" : "action_undo.png"; - const std::string& undo_to_sys_icon = m_is_nonsys_values ? m_nonsys_btn_icon : "sys_lock.png"; + m_undo_btn->SetBitmap(m_is_modified_values ? m_bmp_value_revert : m_bmp_white_bullet); + m_undo_to_sys_btn->SetBitmap(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); - m_undo_btn->SetBitmap(wxBitmap(from_u8(var(undo_icon)), wxBITMAP_TYPE_PNG)); - m_undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(undo_to_sys_icon)), wxBITMAP_TYPE_PNG)); + m_undo_btn->SetToolTip(m_is_modified_values ? m_ttg_value_revert : m_ttg_white_bullet); + m_undo_to_sys_btn->SetToolTip(m_is_nonsys_values ? *m_ttg_non_system : m_ttg_value_lock); } -void Tab::on_back_to_initial_value() +void Tab::on_roll_back_value(const bool to_sys /*= true*/) { - if (!m_is_modified_values) return; + int os; + if (to_sys) { + if (!m_is_nonsys_values) return; + os = osSystemValue; + } + else { + if (!m_is_modified_values) return; + os = osInitValue; + } + + m_postpone_update_ui = true; auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); for (auto page : m_pages) if (page->title() == selection) { for (auto group : page->m_optgroups){ if (group->title == _("Capabilities")){ - if (find(m_dirty_options.begin(), m_dirty_options.end(), "extruders_count") != m_dirty_options.end()) - group->back_to_initial_value("extruders_count"); + if ((m_options_list["extruders_count"] & os) == 0) + to_sys ? group->back_to_sys_value("extruders_count") : group->back_to_initial_value("extruders_count"); } if (group->title == _("Size and coordinates")){ - if (find(m_dirty_options.begin(), m_dirty_options.end(), "bed_shape") != m_dirty_options.end()) - group->back_to_initial_value("bed_shape"); - } - if (group->title == _("Profile dependencies")){ - if (find(m_dirty_options.begin(), m_dirty_options.end(), "compatible_printers") != m_dirty_options.end()) - group->back_to_initial_value("compatible_printers"); + if ((m_options_list["bed_shape"] & os) == 0){ + to_sys ? group->back_to_sys_value("bed_shape") : group->back_to_initial_value("bed_shape"); + load_key_value("bed_shape", true/*some value*/, true); + } - bool is_empty = m_config->option("compatible_printers")->values.empty(); - m_compatible_printers_checkbox->SetValue(is_empty); - is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); + } + if (group->title == _("Profile dependencies") && name() != "printer"){ + if ((m_options_list["compatible_printers"] & os) == 0){ + to_sys ? group->back_to_sys_value("compatible_printers") : group->back_to_initial_value("compatible_printers"); + load_key_value("compatible_printers", true/*some value*/, true); + + bool is_empty = m_config->option("compatible_printers")->values.empty(); + m_compatible_printers_checkbox->SetValue(is_empty); + is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); + } } for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { const std::string& opt_key = it->first; - if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) - group->back_to_initial_value(opt_key); + if ((m_options_list[opt_key] & os) == 0) + to_sys ? group->back_to_sys_value(opt_key) : group->back_to_initial_value(opt_key); } } break; } - update_changed_ui(); -} -void Tab::on_back_to_sys_value() -{ - if (!m_is_nonsys_values) return; - - auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); - for (auto page : m_pages) - if (page->title() == selection) { - for (auto group : page->m_optgroups) { - if (group->title == _("Capabilities")){ - if (find(m_sys_options.begin(), m_sys_options.end(), "extruders_count") == m_sys_options.end()) - group->back_to_sys_value("extruders_count"); - } - if (group->title == _("Size and coordinates")){ - if (find(m_sys_options.begin(), m_sys_options.end(), "bed_shape") == m_sys_options.end()) - group->back_to_sys_value("bed_shape"); - } - if (group->title == _("Profile dependencies")){ - if (find(m_sys_options.begin(), m_sys_options.end(), "compatible_printers") == m_sys_options.end()) - group->back_to_sys_value("compatible_printers"); - - bool is_empty = m_config->option("compatible_printers")->values.empty(); - m_compatible_printers_checkbox->SetValue(is_empty); - is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); - } - for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { - const std::string& opt_key = it->first; - if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) - group->back_to_sys_value(opt_key); - } - } - break; - } + m_postpone_update_ui = false; update_changed_ui(); } @@ -581,7 +590,7 @@ void Tab::update_dirty(){ void Tab::update_tab_ui() { - m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets); + m_selected_preset_item = m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets); // update_tab_presets(m_cc_presets_choice, m_show_incompatible_presets); // update_presetsctrl(m_presetctrl, m_show_incompatible_presets); } @@ -636,12 +645,15 @@ bool Tab::set_value(const t_config_option_key& opt_key, const boost::any& value) // To be called by custom widgets, load a value into a config, // update the preset selection boxes (the dirty flags) -void Tab::load_key_value(const std::string& opt_key, const boost::any& value) +// If value is saved before calling this function, put saved_value = true, +// and value can be some random value because in this case it will not been used +void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value /*= false*/) { - change_opt_value(*m_config, opt_key, value); + if (!saved_value) change_opt_value(*m_config, opt_key, value); // Mark the print & filament enabled if they are compatible with the currently selected preset. if (opt_key.compare("compatible_printers") == 0) { - m_preset_bundle->update_compatible_with_printer(0); + // Don't select another profile if this profile happens to become incompatible. + m_preset_bundle->update_compatible_with_printer(false); } m_presets->update_dirty_ui(m_presets_choice); on_presets_changed(); @@ -716,13 +728,52 @@ void Tab::on_presets_changed() event.SetString(m_name); g_wxMainFrame->ProcessWindowEvent(event); } + update_preset_description_line(); +} +void Tab::update_preset_description_line() +{ const Preset* parent = m_presets->get_selected_preset_parent(); - const wxString description_line = parent == nullptr ? - _(L("It's default preset")) : parent == &m_presets->get_selected_preset() ? - _(L("It's system preset")) : - _(L("Current preset is inherited from")) + ":\n" + parent->name; - m_parent_preset_description_line->SetText(description_line); + const Preset& preset = m_presets->get_edited_preset(); + + wxString description_line = preset.is_default ? + _(L("It's a default preset.")) : preset.is_system ? + _(L("It's a system preset.")) : + _(L("Current preset is inherited from ")) + (parent == nullptr ? + "default preset." : + ":\n\t" + parent->name); + + if (preset.is_default || preset.is_system) + description_line += "\n\t" + _(L("It can't be deleted or modified. ")) + + "\n\t" + _(L("Any modifications should be saved as a new preset inherited from this one. ")) + + "\n\t" + _(L("To do that please specify a new name for the preset.")); + + if (parent && parent->vendor) + { + description_line += "\n\n" + _(L("Additional information:")) + "\n"; + description_line += "\t" + _(L("vendor")) + ": " + (name()=="printer" ? "\n\t\t" : "") + parent->vendor->name + + ", ver: " + parent->vendor->config_version.to_string(); + if (name() == "printer"){ + const std::string &printer_model = preset.config.opt_string("printer_model"); + const std::string &default_print_profile = preset.config.opt_string("default_print_profile"); + const std::vector &default_filament_profiles = preset.config.option("default_filament_profile")->values; + if (!printer_model.empty()) + description_line += "\n\n\t" + _(L("printer model")) + ": \n\t\t" + printer_model; + if (!default_print_profile.empty()) + description_line += "\n\n\t" + _(L("default print profile")) + ": \n\t\t" + default_print_profile; + if (!default_filament_profiles.empty()) + { + description_line += "\n\n\t" + _(L("default filament profile")) + ": \n\t\t"; + for (auto& profile : default_filament_profiles){ + if (&profile != &*default_filament_profiles.begin()) + description_line += ", "; + description_line += profile; + } + } + } + } + + m_parent_preset_description_line->SetText(description_line, false); } void Tab::update_frequently_changed_parameters() @@ -1011,29 +1062,6 @@ void TabPrint::update() on_value_change("fill_density", fill_density); } - auto first_layer_height = m_config->option("first_layer_height")->value; - auto layer_height = m_config->opt_float("layer_height"); - if (m_config->opt_bool("wipe_tower") && - (first_layer_height != 0.2 || layer_height < 0.15 || layer_height > 0.35)) { - wxString msg_text = _(L("The Wipe Tower currently supports only:\n" - "- first layer height 0.2mm\n" - "- layer height from 0.15mm to 0.35mm\n" - "\nShall I adjust those settings in order to enable the Wipe Tower?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { - const auto &val = *m_config->option("first_layer_height"); - auto percent = val.percent; - new_conf.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.2, percent)); - - if (m_config->opt_float("layer_height") < 0.15) new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.15)); - if (m_config->opt_float("layer_height") > 0.35) new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.35)); - } - else - new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false)); - load_config(new_conf); - } - if (m_config->opt_bool("wipe_tower") && m_config->opt_bool("support_material") && m_config->opt_float("support_material_contact_distance") > 0. && (m_config->opt_int("support_material_extruder") != 0 || m_config->opt_int("support_material_interface_extruder") != 0)) { @@ -1283,7 +1311,7 @@ void TabFilament::build() }; optgroup->append_line(line); - optgroup = page->new_optgroup(_(L("Toolchange behaviour"))); + optgroup = page->new_optgroup(_(L("Toolchange parameters with single extruder MM printers"))); optgroup->append_single_option_line("filament_loading_speed"); optgroup->append_single_option_line("filament_unloading_speed"); optgroup->append_single_option_line("filament_toolchange_delay"); @@ -1353,6 +1381,7 @@ void TabFilament::reload_config(){ void TabFilament::update() { + Freeze(); wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); m_cooling_description_line->SetText(text); text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); @@ -1366,6 +1395,7 @@ void TabFilament::update() for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) get_field(el)->toggle(fan_always_on); + Thaw(); } void TabFilament::OnActivate() @@ -1381,7 +1411,7 @@ wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticTex (*StaticText)->SetFont(font); auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(*StaticText); + sizer->Add(*StaticText, 1, wxEXPAND|wxALL, 0); return sizer; } @@ -1395,6 +1425,9 @@ void TabPrinter::build() m_presets = &m_preset_bundle->printers; load_initial_data(); + // to avoid redundant memory allocation / deallocation during extruders count changing + m_pages.reserve(30); + auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); m_initial_extruders_count = m_extruders_count = nozzle_diameter->values.size(); const Preset* parent_preset = m_presets->get_selected_preset_parent(); @@ -1672,12 +1705,34 @@ void TabPrinter::extruders_count_changed(size_t extruders_count){ } void TabPrinter::build_extruder_pages(){ - for (auto extruder_idx = m_extruder_pages.size(); extruder_idx < m_extruders_count; ++extruder_idx){ + size_t n_before_extruders = 2; // Count of pages before Extruder pages + size_t n_after_single_extruder_MM = 2; // Count of pages after single_extruder_multi_material page + + if (m_extruders_count_old == m_extruders_count || m_extruders_count <= 2) + { + // if we have a single extruder MM setup, add a page with configuration options: + for (int i = 0; i < m_pages.size(); ++i) // first make sure it's not there already + if (m_pages[i]->title().find(_(L("Single extruder MM setup"))) != std::string::npos) { + m_pages.erase(m_pages.begin() + i); + break; + } + if (m_extruders_count > 1 && m_config->opt_bool("single_extruder_multi_material")) { + // create a page, but pretend it's an extruder page, so we can add it to m_pages ourselves + auto page = add_options_page(_(L("Single extruder MM setup")), "printer_empty.png", true); + auto optgroup = page->new_optgroup(_(L("Single extruder multimaterial parameters"))); + optgroup->append_single_option_line("cooling_tube_retraction"); + optgroup->append_single_option_line("cooling_tube_length"); + optgroup->append_single_option_line("parking_pos_retraction"); + m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page); + } + } + + for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx){ //# build page char buf[MIN_BUF_LENGTH_FOR_L]; sprintf(buf, _CHB(L("Extruder %d")), extruder_idx + 1); auto page = add_options_page(from_u8(buf), "funnel.png", true); - m_extruder_pages.push_back(page); + m_pages.insert(m_pages.begin() + n_before_extruders + extruder_idx, page); auto optgroup = page->new_optgroup(_(L("Size"))); optgroup->append_single_option_line("nozzle_diameter", extruder_idx); @@ -1715,36 +1770,11 @@ void TabPrinter::build_extruder_pages(){ } // # remove extra pages - if (m_extruders_count <= m_extruder_pages.size()) { - m_extruder_pages.resize(m_extruders_count); - } + if (m_extruders_count < m_extruders_count_old) + m_pages.erase( m_pages.begin() + n_before_extruders + m_extruders_count, + m_pages.begin() + n_before_extruders + m_extruders_count_old); - // # rebuild page list - PageShp page_note = m_pages.back(); - m_pages.pop_back(); - while (m_pages.back()->title().find(_(L("Extruder"))) != std::string::npos) - m_pages.pop_back(); - for (auto page_extruder : m_extruder_pages) - m_pages.push_back(page_extruder); - m_pages.push_back(page_note); - - { - // if we have a single extruder MM setup, add a page with configuration options: - for (int i=0;ititle().find(_(L("Single extruder MM setup"))) != std::string::npos) { - m_pages.erase(m_pages.begin()+i); - break; - } - if ( m_extruder_pages.size()>1 && m_config->opt_bool("single_extruder_multi_material")) { - // create a page, but pretend it's an extruder page, so we can add it to m_pages ourselves - auto page = add_options_page(_(L("Single extruder MM setup")), "printer_empty.png",true); - auto optgroup = page->new_optgroup(_(L("Single extruder multimaterial parameters"))); - optgroup->append_single_option_line("cooling_tube_retraction"); - optgroup->append_single_option_line("cooling_tube_length"); - optgroup->append_single_option_line("parking_pos_retraction"); - m_pages.insert(m_pages.begin()+1,page); - } - } + m_extruders_count_old = m_extruders_count; rebuild_page_tree(); } @@ -1846,14 +1876,15 @@ void Tab::load_current_preset() { auto preset = m_presets->get_edited_preset(); - preset.is_default ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); + (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); update(); // For the printer profile, generate the extruder pages. on_preset_loaded(); // Reload preset pages with the new configuration values. reload_config(); - const Preset* parent = m_presets->get_selected_preset_parent(); - m_nonsys_btn_icon = parent == nullptr ? "bullet_white.png" : "sys_unlock.png"; + m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; + m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; + m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; // use CallAfter because some field triggers schedule on_change calls using CallAfter, // and we don't want them to be called after this update_dirty() as they would mark the @@ -1874,8 +1905,8 @@ void Tab::load_current_preset() static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); } - update_sys_ui_after_sel_preset(); - update_full_options_list(); + m_opt_status_value = (m_presets->get_selected_preset_parent() ? osSystemValue : 0) | osInitValue; + init_options_list(); update_changed_ui(); }); } @@ -1887,11 +1918,13 @@ void Tab::rebuild_page_tree() // get label of the currently selected item auto selected = m_treectrl->GetItemText(m_treectrl->GetSelection()); auto rootItem = m_treectrl->GetRootItem(); - m_treectrl->DeleteChildren(rootItem); + auto have_selection = 0; + m_treectrl->DeleteChildren(rootItem); for (auto p : m_pages) { auto itemId = m_treectrl->AppendItem(rootItem, p->title(), p->iconID()); + m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); if (p->title() == selected) { m_disable_tree_sel_changed_event = 1; m_treectrl->SelectItem(itemId); @@ -1899,7 +1932,7 @@ void Tab::rebuild_page_tree() have_selection = 1; } } - + if (!have_selection) { // this is triggered on first load, so we don't disable the sel change event m_treectrl->SelectItem(m_treectrl->GetFirstVisibleItem());//! (treectrl->GetFirstChild(rootItem)); @@ -2015,6 +2048,8 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr void Tab::OnTreeSelChange(wxTreeEvent& event) { if (m_disable_tree_sel_changed_event) return; + wxWindowUpdateLocker noUpdates(this); + Page* page = nullptr; auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); for (auto p : m_pages) @@ -2141,7 +2176,7 @@ void Tab::toggle_show_hide_incompatible() void Tab::update_show_hide_incompatible_button() { m_btn_hide_incompatible_presets->SetBitmap(m_show_incompatible_presets ? - *m_bmp_show_incompatible_presets : *m_bmp_hide_incompatible_presets); + m_bmp_show_incompatible_presets : m_bmp_hide_incompatible_presets); m_btn_hide_incompatible_presets->SetToolTip(m_show_incompatible_presets ? "Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." : "Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer."); @@ -2200,9 +2235,9 @@ wxSizer* Tab::compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox presets.Add(preset.name); } - auto dlg = new wxMultiChoiceDialog(parent, - _(L("Select the printers this profile is compatible with.")), - _(L("Compatible printers")), presets); + wxMultiChoiceDialog dlg(parent, + _(L("Select the printers this profile is compatible with.")), + _(L("Compatible printers")), presets); // # Collect and set indices of printers marked as compatible. wxArrayInt selections; auto *compatible_printers = dynamic_cast(m_config->option("compatible_printers")); @@ -2214,12 +2249,12 @@ wxSizer* Tab::compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox selections.Add(idx); break; } - dlg->SetSelections(selections); + dlg.SetSelections(selections); std::vector value; // Show the dialog. - if (dlg->ShowModal() == wxID_OK) { + if (dlg.ShowModal() == wxID_OK) { selections.Clear(); - selections = dlg->GetSelections(); + selections = dlg.GetSelections(); for (auto idx : selections) value.push_back(presets[idx].ToStdString()); if (value.empty()) { @@ -2401,6 +2436,75 @@ void Tab::update_tab_presets(wxComboCtrl* ui, bool show_incompatible) ui->Thaw(); } +void Tab::fill_icon_descriptions() +{ + m_icon_descriptions.push_back(t_icon_description(&m_bmp_value_lock, L("LOCKED LOCK;" + "indicates that the settings are the same as the system values for the current option group"))); + + m_icon_descriptions.push_back(t_icon_description(&m_bmp_value_unlock, L("UNLOCKED LOCK;" + "indicates that some settings were changed and are not equal to the system values for " + "the current option group.\n" + "Click the UNLOCKED LOCK icon to reset all settings for current option group to " + "the system values."))); + + m_icon_descriptions.push_back(t_icon_description(&m_bmp_white_bullet, L("WHITE BULLET;" + "for the left button: \tindicates a non-system preset,\n" + "for the right button: \tindicates that the settings hasn't been modified."))); + + m_icon_descriptions.push_back(t_icon_description(&m_bmp_value_revert, L("BACK ARROW;" + "indicates that the settings were changed and are not equal to the last saved preset for " + "the current option group.\n" + "Click the BACK ARROW icon to reset all settings for the current option group to " + "the last saved preset."))); +} + +void Tab::set_tooltips_text() +{ +// m_undo_to_sys_btn->SetToolTip(_(L( "LOCKED LOCK icon indicates that the settings are the same as the system values " +// "for the current option group.\n" +// "UNLOCKED LOCK icon indicates that some settings were changed and are not equal " +// "to the system values for the current option group.\n" +// "WHITE BULLET icon indicates a non system preset.\n\n" +// "Click the UNLOCKED LOCK icon to reset all settings for current option group to " +// "the system values."))); +// +// m_undo_btn->SetToolTip(_(L( "WHITE BULLET icon indicates that the settings are the same as in the last saved" +// "preset for the current option group.\n" +// "BACK ARROW icon indicates that the settings were changed and are not equal to " +// "the last saved preset for the current option group.\n\n" +// "Click the BACK ARROW icon to reset all settings for the current option group to " +// "the last saved preset."))); + + // --- Tooltip text for reset buttons (for whole options group) + // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + m_ttg_value_lock = _(L("LOCKED LOCK icon indicates that the settings are the same as the system values " + "for the current option group")); + m_ttg_value_unlock = _(L("UNLOCKED LOCK icon indicates that some settings were changed and are not equal " + "to the system values for the current option group.\n" + "Click to reset all settings for current option group to the system values.")); + m_ttg_white_bullet_ns = _(L("WHITE BULLET icon indicates a non system preset.")); + m_ttg_non_system = &m_ttg_white_bullet_ns; + // Text to be shown on the "Undo user changes" button next to each input field. + m_ttg_white_bullet = _(L("WHITE BULLET icon indicates that the settings are the same as in the last saved " + "preset for the current option group.")); + m_ttg_value_revert = _(L("BACK ARROW icon indicates that the settings were changed and are not equal to " + "the last saved preset for the current option group.\n" + "Click to reset all settings for the current option group to the last saved preset.")); + + // --- Tooltip text for reset buttons (for each option in group) + // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + m_tt_value_lock = _(L("LOCKED LOCK icon indicates that the value is the same as the system value.")); + m_tt_value_unlock = _(L("UNLOCKED LOCK icon indicates that the value was changed and is not equal " + "to the system value.\n" + "Click to reset current value to the system value.")); + // m_tt_white_bullet_ns= _(L("WHITE BULLET icon indicates a non system preset.")); + m_tt_non_system = &m_ttg_white_bullet_ns; + // Text to be shown on the "Undo user changes" button next to each input field. + m_tt_white_bullet = _(L("WHITE BULLET icon indicates that the value is the same as in the last saved preset.")); + m_tt_value_revert = _(L("BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\n" + "Click to reset current value to the last saved preset.")); +} + void Page::reload_config() { for (auto group : m_optgroups) @@ -2459,10 +2563,6 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la return static_cast(GetParent())->m_presets->get_selected_preset_parent() != nullptr; }; - optgroup->nonsys_btn_icon = [this](){ - return static_cast(GetParent())->m_nonsys_btn_icon; - }; - vsizer()->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); m_optgroups.push_back(optgroup); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index f9ee4f3639..62030bce34 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -1,3 +1,6 @@ +#ifndef slic3r_Tab_hpp_ +#define slic3r_Tab_hpp_ + // The "Expert" tab at the right of the main tabbed window. // // This file implements following packages: @@ -34,6 +37,9 @@ namespace Slic3r { namespace GUI { +typedef std::pair t_icon_description; +typedef std::vector> t_icon_descriptions; + // Single Tab page containing a{ vsizer } of{ optgroups } // package Slic3r::GUI::Tab::Page; using ConfigOptionsGroupShp = std::shared_ptr; @@ -51,6 +57,7 @@ public: { Create(m_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_vsizer = new wxBoxSizer(wxVERTICAL); + m_item_color = &get_label_clr_default(); SetSizer(m_vsizer); } ~Page(){} @@ -71,6 +78,22 @@ public: Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const; bool set_value(const t_config_option_key& opt_key, const boost::any& value); ConfigOptionsGroupShp new_optgroup(const wxString& title, int noncommon_label_width = -1); + + bool set_item_colour(const wxColour *clr) { + if (m_item_color != clr) { + m_item_color = clr; + return true; + } + return false; + } + + const wxColour get_item_colour() { + return *m_item_color; + } + +protected: + // Color of TreeCtrlItem. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one. + const wxColour* m_item_color; }; // Slic3r::GUI::Tab; @@ -85,8 +108,6 @@ protected: wxBitmapComboBox* m_presets_choice; wxBitmapButton* m_btn_save_preset; wxBitmapButton* m_btn_delete_preset; - wxBitmap* m_bmp_show_incompatible_presets; - wxBitmap* m_bmp_hide_incompatible_presets; wxBitmapButton* m_btn_hide_incompatible_presets; wxBoxSizer* m_hsizer; wxBoxSizer* m_left_sizer; @@ -96,10 +117,51 @@ protected: wxButton* m_compatible_printers_btn; wxButton* m_undo_btn; wxButton* m_undo_to_sys_btn; + wxButton* m_question_btn; + wxComboCtrl* m_cc_presets_choice; wxDataViewTreeCtrl* m_presetctrl; wxImageList* m_preset_icons; + // Cached bitmaps. + // A "flag" icon to be displayned next to the preset name in the Tab's combo box. + wxBitmap m_bmp_show_incompatible_presets; + wxBitmap m_bmp_hide_incompatible_presets; + // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + wxBitmap m_bmp_value_lock; + wxBitmap m_bmp_value_unlock; + wxBitmap m_bmp_white_bullet; + // The following bitmap points to either m_bmp_value_unlock or m_bmp_white_bullet, depending on whether the current preset has a parent preset. + wxBitmap *m_bmp_non_system; + // Bitmaps to be shown on the "Undo user changes" button next to each input field. + wxBitmap m_bmp_value_revert; +// wxBitmap m_bmp_value_unmodified; + wxBitmap m_bmp_question; + + // Colors for ui "decoration" + wxColour m_sys_label_clr; + wxColour m_modified_label_clr; + wxColour m_default_text_clr; + + // Tooltip text for reset buttons (for whole options group) + wxString m_ttg_value_lock; + wxString m_ttg_value_unlock; + wxString m_ttg_white_bullet_ns; + // The following text points to either m_ttg_value_unlock or m_ttg_white_bullet_ns, depending on whether the current preset has a parent preset. + wxString *m_ttg_non_system; + // Tooltip text to be shown on the "Undo user changes" button next to each input field. + wxString m_ttg_white_bullet; + wxString m_ttg_value_revert; + + // Tooltip text for reset buttons (for each option in group) + wxString m_tt_value_lock; + wxString m_tt_value_unlock; + // The following text points to either m_tt_value_unlock or m_ttg_white_bullet_ns, depending on whether the current preset has a parent preset. + wxString *m_tt_non_system; + // Tooltip text to be shown on the "Undo user changes" button next to each input field. + wxString m_tt_white_bullet; + wxString m_tt_value_revert; + int m_icon_count; std::map m_icon_index; // Map from an icon file name to its index std::vector m_pages; @@ -108,9 +170,11 @@ protected: bool m_no_controller; std::vector m_reload_dependent_tabs = {}; - std::vector m_dirty_options = {}; - std::vector m_sys_options = {}; - std::vector m_full_options_list = {}; + enum OptStatus { osSystemValue = 1, osInitValue = 2 }; + std::map m_options_list; + int m_opt_status_value; + + t_icon_descriptions m_icon_descriptions = {}; // The two following two event IDs are generated at Plater.pm by calling Wx::NewEventType. wxEventType m_event_value_change = 0; @@ -118,13 +182,15 @@ protected: bool m_is_modified_values{ false }; bool m_is_nonsys_values{ true }; + bool m_postpone_update_ui {false}; + + size_t m_selected_preset_item{ 0 }; public: PresetBundle* m_preset_bundle; bool m_show_btn_incompatible_presets = false; PresetCollection* m_presets; DynamicPrintConfig* m_config; - std::string m_nonsys_btn_icon; ogStaticText* m_parent_preset_description_line; wxStaticText* m_colored_Label = nullptr; @@ -135,7 +201,9 @@ public: Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); get_tabs_list().push_back(this); } - ~Tab() { delete_tab_from_list(this); } + ~Tab(){ + delete_tab_from_list(this); + } wxWindow* parent() const { return m_parent; } wxString title() const { return m_title; } @@ -153,7 +221,7 @@ public: wxSizer* compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox, wxButton** btn); void update_presetsctrl(wxDataViewTreeCtrl* ui, bool show_incompatible); - void load_key_value(const std::string& opt_key, const boost::any& value); + void load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value = false); void reload_compatible_printers_widget(); void OnTreeSelChange(wxTreeEvent& event); @@ -164,15 +232,13 @@ public: void toggle_show_hide_incompatible(); void update_show_hide_incompatible_button(); void update_ui_from_settings(); + void update_labels_colour(); void update_changed_ui(); - void update_full_options_list(); - void update_sys_ui_after_sel_preset(); void get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page); void update_changed_tree_ui(); void update_undo_buttons(); - void on_back_to_initial_value(); - void on_back_to_sys_value(); + void on_roll_back_value(const bool to_sys = false); PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false); @@ -180,6 +246,7 @@ public: virtual void on_preset_loaded(){} virtual void build() = 0; virtual void update() = 0; + virtual void init_options_list(); void load_initial_data(); void update_dirty(); void update_tab_ui(); @@ -189,20 +256,22 @@ public: bool set_value(const t_config_option_key& opt_key, const boost::any& value); wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText); bool current_preset_is_dirty(); + DynamicPrintConfig* get_config() { return m_config; } - PresetCollection* get_presets() - { - return m_presets; - } + PresetCollection* get_presets() { return m_presets; } std::vector get_dependent_tabs() { return m_reload_dependent_tabs; } + size_t get_selected_preset_item() { return m_selected_preset_item; } void on_value_change(const std::string& opt_key, const boost::any& value); protected: void on_presets_changed(); + void update_preset_description_line(); void update_frequently_changed_parameters(); void update_wiping_button_visibility(); void update_tab_presets(wxComboCtrl* ui, bool show_incompatible); + void fill_icon_descriptions(); + void set_tooltips_text(); }; //Slic3r::GUI::Tab::Print; @@ -248,9 +317,9 @@ public: wxButton* m_octoprint_host_test_btn; size_t m_extruders_count; + size_t m_extruders_count_old = 0; size_t m_initial_extruders_count; size_t m_sys_extruders_count; - std::vector m_extruder_pages; TabPrinter() {} TabPrinter(wxNotebook* parent, bool no_controller) : Tab(parent, _(L("Printer Settings")), "printer", no_controller) {} @@ -262,6 +331,7 @@ public: void extruders_count_changed(size_t extruders_count); void build_extruder_pages(); void on_preset_loaded() override; + void init_options_list() override; }; class SavePresetWindow :public wxDialog @@ -280,3 +350,5 @@ public: } // GUI } // Slic3r + +#endif /* slic3r_Tab_hpp_ */ diff --git a/xs/src/slic3r/GUI/TabIface.cpp b/xs/src/slic3r/GUI/TabIface.cpp index 4df0e72e53..29833322b8 100644 --- a/xs/src/slic3r/GUI/TabIface.cpp +++ b/xs/src/slic3r/GUI/TabIface.cpp @@ -11,6 +11,7 @@ void TabIface::load_config(DynamicPrintConfig* config) { m_tab->load_config(*con void TabIface::load_key_value(char* opt_key, char* value){ m_tab->load_key_value(opt_key, static_cast(value)); } bool TabIface::current_preset_is_dirty() { return m_tab->current_preset_is_dirty();} void TabIface::OnActivate() { return m_tab->OnActivate();} +size_t TabIface::get_selected_preset_item() { return m_tab->get_selected_preset_item(); } std::string TabIface::title() { return m_tab->title().ToUTF8().data(); } DynamicPrintConfig* TabIface::get_config() { return m_tab->get_config(); } PresetCollection* TabIface::get_presets() { return m_tab!=nullptr ? m_tab->get_presets() : nullptr; } diff --git a/xs/src/slic3r/GUI/TabIface.hpp b/xs/src/slic3r/GUI/TabIface.hpp index 0325c855c7..2f7f4e8e7f 100644 --- a/xs/src/slic3r/GUI/TabIface.hpp +++ b/xs/src/slic3r/GUI/TabIface.hpp @@ -1,3 +1,6 @@ +#ifndef slic3r_TabIface_hpp_ +#define slic3r_TabIface_hpp_ + #include #include @@ -27,9 +30,12 @@ public: DynamicPrintConfig* get_config(); PresetCollection* get_presets(); std::vector get_dependent_tabs(); + size_t get_selected_preset_item(); protected: GUI::Tab *m_tab; -}; +}; // namespace GUI }; // namespace Slic3r + +#endif /* slic3r_TabIface_hpp_ */ diff --git a/xs/src/slic3r/GUI/UpdateDialogs.cpp b/xs/src/slic3r/GUI/UpdateDialogs.cpp new file mode 100644 index 0000000000..70d9c851c0 --- /dev/null +++ b/xs/src/slic3r/GUI/UpdateDialogs.cpp @@ -0,0 +1,196 @@ +#include "UpdateDialogs.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" +#include "GUI.hpp" +#include "ConfigWizard.hpp" + +namespace Slic3r { +namespace GUI { + + +static const std::string CONFIG_UPDATE_WIKI_URL("https://github.com/prusa3d/Slic3r/wiki/Slic3r-PE-1.40-configuration-update"); + + +// MsgUpdateSlic3r + +MsgUpdateSlic3r::MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_online) : + MsgDialog(nullptr, _(L("Update available")), _(L("New version of Slic3r PE is available"))), + ver_current(ver_current), + ver_online(ver_online) +{ + const auto url = wxString::Format("https://github.com/prusa3d/Slic3r/releases/tag/version_%s", ver_online.to_string()); + auto *link = new wxHyperlinkCtrl(this, wxID_ANY, url, url); + + auto *text = new wxStaticText(this, wxID_ANY, _(L("To download, follow the link below."))); + const auto link_width = link->GetSize().GetWidth(); + text->Wrap(CONTENT_WIDTH > link_width ? CONTENT_WIDTH : link_width); + content_sizer->Add(text); + content_sizer->AddSpacer(VERT_SPACING); + + auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING); + versions->Add(new wxStaticText(this, wxID_ANY, _(L("Current version:")))); + versions->Add(new wxStaticText(this, wxID_ANY, ver_current.to_string())); + versions->Add(new wxStaticText(this, wxID_ANY, _(L("New version:")))); + versions->Add(new wxStaticText(this, wxID_ANY, ver_online.to_string())); + content_sizer->Add(versions); + content_sizer->AddSpacer(VERT_SPACING); + + content_sizer->Add(link); + content_sizer->AddSpacer(2*VERT_SPACING); + + cbox = new wxCheckBox(this, wxID_ANY, _(L("Don't notify about new releases any more"))); + content_sizer->Add(cbox); + content_sizer->AddSpacer(VERT_SPACING); + + Fit(); +} + +MsgUpdateSlic3r::~MsgUpdateSlic3r() {} + +bool MsgUpdateSlic3r::disable_version_check() const +{ + return cbox->GetValue(); +} + + +// MsgUpdateConfig + +MsgUpdateConfig::MsgUpdateConfig(const std::unordered_map &updates) : + MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update is available")), wxID_NONE) +{ + auto *text = new wxStaticText(this, wxID_ANY, _(L( + "Would you like to install it?\n\n" + "Note that a full configuration snapshot will be created first. It can then be restored at any time " + "should there be a problem with the new version.\n\n" + "Updated configuration bundles:" + ))); + text->Wrap(CONTENT_WIDTH); + content_sizer->Add(text); + content_sizer->AddSpacer(VERT_SPACING); + + auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING); + for (const auto &update : updates) { + auto *text_vendor = new wxStaticText(this, wxID_ANY, update.first); + text_vendor->SetFont(boldfont); + versions->Add(text_vendor); + versions->Add(new wxStaticText(this, wxID_ANY, update.second)); + } + + content_sizer->Add(versions); + content_sizer->AddSpacer(2*VERT_SPACING); + + auto *btn_cancel = new wxButton(this, wxID_CANCEL); + btn_sizer->Add(btn_cancel); + btn_sizer->AddSpacer(HORIZ_SPACING); + auto *btn_ok = new wxButton(this, wxID_OK); + btn_sizer->Add(btn_ok); + btn_ok->SetFocus(); + + Fit(); +} + +MsgUpdateConfig::~MsgUpdateConfig() {} + + +// MsgDataIncompatible + +MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map &incompats) : + MsgDialog(nullptr, _(L("Slic3r incompatibility")), _(L("Slic3r configuration is incompatible")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG), wxID_NONE) +{ + auto *text = new wxStaticText(this, wxID_ANY, _(L( + "This version of Slic3r PE is not compatible with currently installed configuration bundles.\n" + "This probably happened as a result of running an older Slic3r PE after using a newer one.\n\n" + + "You may either exit Slic3r and try again with a newer version, or you may re-run the initial configuration. " + "Doing so will create a backup snapshot of the existing configuration before installing files compatible with this Slic3r.\n" + ))); + text->Wrap(CONTENT_WIDTH); + content_sizer->Add(text); + + auto *text2 = new wxStaticText(this, wxID_ANY, wxString::Format(_(L("This Slic3r PE version: %s")), SLIC3R_VERSION)); + text2->Wrap(CONTENT_WIDTH); + content_sizer->Add(text2); + content_sizer->AddSpacer(VERT_SPACING); + + auto *text3 = new wxStaticText(this, wxID_ANY, _(L("Incompatible bundles:"))); + text3->Wrap(CONTENT_WIDTH); + content_sizer->Add(text3); + content_sizer->AddSpacer(VERT_SPACING); + + auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING); + for (const auto &incompat : incompats) { + auto *text_vendor = new wxStaticText(this, wxID_ANY, incompat.first); + text_vendor->SetFont(boldfont); + versions->Add(text_vendor); + versions->Add(new wxStaticText(this, wxID_ANY, incompat.second)); + } + + content_sizer->Add(versions); + content_sizer->AddSpacer(2*VERT_SPACING); + + auto *btn_exit = new wxButton(this, wxID_EXIT, _(L("Exit Slic3r"))); + btn_sizer->Add(btn_exit); + btn_sizer->AddSpacer(HORIZ_SPACING); + auto *btn_reconf = new wxButton(this, wxID_REPLACE, _(L("Re-configure"))); + btn_sizer->Add(btn_reconf); + btn_exit->SetFocus(); + + auto exiter = [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); }; + btn_exit->Bind(wxEVT_BUTTON, exiter); + btn_reconf->Bind(wxEVT_BUTTON, exiter); + + Fit(); +} + +MsgDataIncompatible::~MsgDataIncompatible() {} + + +// MsgDataLegacy + +MsgDataLegacy::MsgDataLegacy() : + MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update"))) +{ + auto *text = new wxStaticText(this, wxID_ANY, wxString::Format( + _(L( + "Slic3r PE now uses an updated configuration structure.\n\n" + + "So called 'System presets' have been introduced, which hold the built-in default settings for various " + "printers. These System presets cannot be modified, instead, users now may create their " + "own presets inheriting settings from one of the System presets.\n" + "An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\n" + + "Please proceed with the %s that follows to set up the new presets " + "and to choose whether to enable automatic preset updates." + )), + ConfigWizard::name() + )); + text->Wrap(CONTENT_WIDTH); + content_sizer->Add(text); + content_sizer->AddSpacer(VERT_SPACING); + + auto *text2 = new wxStaticText(this, wxID_ANY, _(L("For more information please visit our wiki page:"))); + static const wxString url("https://github.com/prusa3d/Slic3r/wiki/Slic3r-PE-1.40-configuration-update"); + // The wiki page name is intentionally not localized: + auto *link = new wxHyperlinkCtrl(this, wxID_ANY, "Slic3r PE 1.40 configuration update", CONFIG_UPDATE_WIKI_URL); + content_sizer->Add(text2); + content_sizer->Add(link); + content_sizer->AddSpacer(VERT_SPACING); + + Fit(); +} + +MsgDataLegacy::~MsgDataLegacy() {} + + +} +} diff --git a/xs/src/slic3r/GUI/UpdateDialogs.hpp b/xs/src/slic3r/GUI/UpdateDialogs.hpp new file mode 100644 index 0000000000..62548b98be --- /dev/null +++ b/xs/src/slic3r/GUI/UpdateDialogs.hpp @@ -0,0 +1,81 @@ +#ifndef slic3r_UpdateDialogs_hpp_ +#define slic3r_UpdateDialogs_hpp_ + +#include +#include + +#include "slic3r/Utils/Semver.hpp" +#include "MsgDialog.hpp" + +class wxBoxSizer; +class wxCheckBox; + +namespace Slic3r { + +namespace GUI { + + +// A confirmation dialog listing configuration updates +class MsgUpdateSlic3r : public MsgDialog +{ +public: + MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_online); + MsgUpdateSlic3r(MsgUpdateSlic3r &&) = delete; + MsgUpdateSlic3r(const MsgUpdateSlic3r &) = delete; + MsgUpdateSlic3r &operator=(MsgUpdateSlic3r &&) = delete; + MsgUpdateSlic3r &operator=(const MsgUpdateSlic3r &) = delete; + virtual ~MsgUpdateSlic3r(); + + // Tells whether the user checked the "don't bother me again" checkbox + bool disable_version_check() const; + +private: + const Semver &ver_current; + const Semver &ver_online; + wxCheckBox *cbox; +}; + + +// Confirmation dialog informing about configuration update. Lists updated bundles & their versions. +class MsgUpdateConfig : public MsgDialog +{ +public: + // updates is a map of "vendor name" -> "version (comment)" + MsgUpdateConfig(const std::unordered_map &updates); + MsgUpdateConfig(MsgUpdateConfig &&) = delete; + MsgUpdateConfig(const MsgUpdateConfig &) = delete; + MsgUpdateConfig &operator=(MsgUpdateConfig &&) = delete; + MsgUpdateConfig &operator=(const MsgUpdateConfig &) = delete; + ~MsgUpdateConfig(); +}; + +// Informs about currently installed bundles not being compatible with the running Slic3r. Asks about action. +class MsgDataIncompatible : public MsgDialog +{ +public: + // incompats is a map of "vendor name" -> "version restrictions" + MsgDataIncompatible(const std::unordered_map &incompats); + MsgDataIncompatible(MsgDataIncompatible &&) = delete; + MsgDataIncompatible(const MsgDataIncompatible &) = delete; + MsgDataIncompatible &operator=(MsgDataIncompatible &&) = delete; + MsgDataIncompatible &operator=(const MsgDataIncompatible &) = delete; + ~MsgDataIncompatible(); +}; + +// Informs about a legacy data directory - an update from Slic3r PE < 1.40 +class MsgDataLegacy : public MsgDialog +{ +public: + MsgDataLegacy(); + MsgDataLegacy(MsgDataLegacy &&) = delete; + MsgDataLegacy(const MsgDataLegacy &) = delete; + MsgDataLegacy &operator=(MsgDataLegacy &&) = delete; + MsgDataLegacy &operator=(const MsgDataLegacy &) = delete; + ~MsgDataLegacy(); +}; + + +} +} + +#endif diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp new file mode 100644 index 0000000000..3b786e0d67 --- /dev/null +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -0,0 +1,559 @@ +#include "PresetUpdater.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/PresetBundle.hpp" +#include "slic3r/GUI/UpdateDialogs.hpp" +#include "slic3r/GUI/ConfigWizard.hpp" +#include "slic3r/Utils/Http.hpp" +#include "slic3r/Config/Version.hpp" +#include "slic3r/Config/Snapshot.hpp" + +namespace fs = boost::filesystem; +using Slic3r::GUI::Config::Index; +using Slic3r::GUI::Config::Version; +using Slic3r::GUI::Config::Snapshot; +using Slic3r::GUI::Config::SnapshotDB; + + +namespace Slic3r { + + +enum { + SLIC3R_VERSION_BODY_MAX = 256, +}; + +static const char *INDEX_FILENAME = "index.idx"; +static const char *TMP_EXTENSION = ".download"; + + +struct Update +{ + fs::path source; + fs::path target; + Version version; + + Update(fs::path &&source, fs::path &&target, const Version &version) : + source(std::move(source)), + target(std::move(target)), + version(version) + {} + + std::string name() const { return source.stem().string(); } + + friend std::ostream& operator<<(std::ostream& os , const Update &self) { + os << "Update(" << self.source.string() << " -> " << self.target.string() << ')'; + return os; + } +}; + +struct Incompat +{ + fs::path bundle; + Version version; + + Incompat(fs::path &&bundle, const Version &version) : + bundle(std::move(bundle)), + version(version) + {} + + std::string name() const { return bundle.stem().string(); } + + friend std::ostream& operator<<(std::ostream& os , const Incompat &self) { + os << "Incompat(" << self.bundle.string() << ')'; + return os; + } +}; + +struct Updates +{ + std::vector incompats; + std::vector updates; +}; + + +struct PresetUpdater::priv +{ + int version_online_event; + std::vector index_db; + + bool enabled_version_check; + bool enabled_config_update; + std::string version_check_url; + bool had_config_update; + + fs::path cache_path; + fs::path rsrc_path; + fs::path vendor_path; + + bool cancel; + std::thread thread; + + priv(int version_online_event); + + void set_download_prefs(AppConfig *app_config); + bool get_file(const std::string &url, const fs::path &target_path) const; + void prune_tmps() const; + void sync_version() const; + void sync_config(const std::set vendors) const; + + void check_install_indices() const; + Updates get_config_updates() const; + void perform_updates(Updates &&updates, bool snapshot = true) const; +}; + +PresetUpdater::priv::priv(int version_online_event) : + version_online_event(version_online_event), + had_config_update(false), + cache_path(fs::path(Slic3r::data_dir()) / "cache"), + rsrc_path(fs::path(resources_dir()) / "profiles"), + vendor_path(fs::path(Slic3r::data_dir()) / "vendor"), + cancel(false) +{ + set_download_prefs(GUI::get_app_config()); + check_install_indices(); + index_db = std::move(Index::load_db()); +} + +// Pull relevant preferences from AppConfig +void PresetUpdater::priv::set_download_prefs(AppConfig *app_config) +{ + enabled_version_check = app_config->get("version_check") == "1"; + version_check_url = app_config->version_check_url(); + enabled_config_update = app_config->get("preset_update") == "1"; +} + +// Downloads a file (http get operation). Cancels if the Updater is being destroyed. +bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &target_path) const +{ + bool res = false; + fs::path tmp_path = target_path; + tmp_path += (boost::format(".%1%%2%") % get_current_pid() % TMP_EXTENSION).str(); + + BOOST_LOG_TRIVIAL(info) << boost::format("Get: `%1%`\n\t-> `%2%`\n\tvia tmp path `%3%`") + % url + % target_path.string() + % tmp_path.string(); + + Http::get(url) + .on_progress([this](Http::Progress, bool &cancel) { + if (cancel) { cancel = true; } + }) + .on_error([&](std::string body, std::string error, unsigned http_status) { + (void)body; + BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%") + % url + % http_status + % error; + }) + .on_complete([&](std::string body, unsigned http_status) { + fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc); + file.write(body.c_str(), body.size()); + file.close(); + fs::rename(tmp_path, target_path); + res = true; + }) + .perform_sync(); + + return res; +} + +// Remove leftover paritally downloaded files, if any. +void PresetUpdater::priv::prune_tmps() const +{ + for (fs::directory_iterator it(cache_path); it != fs::directory_iterator(); ++it) { + if (it->path().extension() == TMP_EXTENSION) { + BOOST_LOG_TRIVIAL(debug) << "Cache prune: " << it->path().string(); + fs::remove(it->path()); + } + } +} + +// Get Slic3rPE version available online, save in AppConfig. +void PresetUpdater::priv::sync_version() const +{ + if (! enabled_version_check) { return; } + + BOOST_LOG_TRIVIAL(info) << boost::format("Downloading Slic3rPE online version from: `%1%`") % version_check_url; + + Http::get(version_check_url) + .size_limit(SLIC3R_VERSION_BODY_MAX) + .on_progress([this](Http::Progress, bool &cancel) { + cancel = this->cancel; + }) + .on_error([&](std::string body, std::string error, unsigned http_status) { + (void)body; + BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%") + % version_check_url + % http_status + % error; + }) + .on_complete([&](std::string body, unsigned http_status) { + boost::trim(body); + BOOST_LOG_TRIVIAL(info) << boost::format("Got Slic3rPE online version: `%1%`. Sending to GUI thread...") % body; + wxCommandEvent* evt = new wxCommandEvent(version_online_event); + evt->SetString(body); + GUI::get_app()->QueueEvent(evt); + }) + .perform_sync(); +} + +// Download vendor indices. Also download new bundles if an index indicates there's a new one available. +// Both are saved in cache. +void PresetUpdater::priv::sync_config(const std::set vendors) const +{ + BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache"; + + if (!enabled_config_update) { return; } + + // Donwload vendor preset bundles + for (const auto &index : index_db) { + if (cancel) { return; } + + const auto vendor_it = vendors.find(VendorProfile(index.vendor())); + if (vendor_it == vendors.end()) { + BOOST_LOG_TRIVIAL(warning) << "No such vendor: " << index.vendor(); + continue; + } + + const VendorProfile &vendor = *vendor_it; + if (vendor.config_update_url.empty()) { + BOOST_LOG_TRIVIAL(info) << "Vendor has no config_update_url: " << vendor.name; + continue; + } + + // Download a fresh index + BOOST_LOG_TRIVIAL(info) << "Downloading index for vendor: " << vendor.name; + const auto idx_url = vendor.config_update_url + "/" + INDEX_FILENAME; + const auto idx_path = cache_path / (vendor.id + ".idx"); + if (! get_file(idx_url, idx_path)) { continue; } + if (cancel) { return; } + + // Load the fresh index up + Index new_index; + new_index.load(idx_path); + + // See if a there's a new version to download + const auto recommended_it = new_index.recommended(); + if (recommended_it == new_index.end()) { + BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % vendor.name; + continue; + } + const auto recommended = recommended_it->config_version; + + BOOST_LOG_TRIVIAL(debug) << boost::format("New index for vendor: %1%: current version: %2%, recommended version: %3%") + % vendor.name + % vendor.config_version.to_string() + % recommended.to_string(); + + if (vendor.config_version >= recommended) { continue; } + + // Download a fresh bundle + BOOST_LOG_TRIVIAL(info) << "Downloading new bundle for vendor: " << vendor.name; + const auto bundle_url = (boost::format("%1%/%2%.ini") % vendor.config_update_url % recommended.to_string()).str(); + const auto bundle_path = cache_path / (vendor.id + ".ini"); + if (! get_file(bundle_url, bundle_path)) { continue; } + if (cancel) { return; } + } +} + +// Install indicies from resources. Only installs those that are either missing or older than in resources. +void PresetUpdater::priv::check_install_indices() const +{ + BOOST_LOG_TRIVIAL(info) << "Checking if indices need to be installed from resources..."; + + for (fs::directory_iterator it(rsrc_path); it != fs::directory_iterator(); ++it) { + const auto &path = it->path(); + if (path.extension() == ".idx") { + const auto path_in_cache = cache_path / path.filename(); + + if (! fs::exists(path_in_cache)) { + BOOST_LOG_TRIVIAL(info) << "Install index from resources: " << path.filename(); + fs::copy_file(path, path_in_cache, fs::copy_option::overwrite_if_exists); + } else { + Index idx_rsrc, idx_cache; + idx_rsrc.load(path); + idx_cache.load(path_in_cache); + + if (idx_cache.version() < idx_rsrc.version()) { + BOOST_LOG_TRIVIAL(info) << "Update index from resources: " << path.filename(); + fs::copy_file(path, path_in_cache, fs::copy_option::overwrite_if_exists); + } + } + } + } +} + +// Generates a list of bundle updates that are to be performed +Updates PresetUpdater::priv::get_config_updates() const +{ + Updates updates; + + BOOST_LOG_TRIVIAL(info) << "Checking for cached configuration updates..."; + + for (const auto idx : index_db) { + auto bundle_path = vendor_path / (idx.vendor() + ".ini"); + + if (! fs::exists(bundle_path)) { + BOOST_LOG_TRIVIAL(info) << "Bundle not present for index, skipping: " << idx.vendor(); + continue; + } + + // Perform a basic load and check the version + const auto vp = VendorProfile::from_ini(bundle_path, false); + + const auto ver_current = idx.find(vp.config_version); + if (ver_current == idx.end()) { + BOOST_LOG_TRIVIAL(error) << boost::format("Preset bundle (`%1%`) version not found in index: %2%") % idx.vendor() % vp.config_version.to_string(); + continue; + } + + const auto recommended = idx.recommended(); + if (recommended == idx.end()) { + BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % idx.vendor(); + } + + BOOST_LOG_TRIVIAL(debug) << boost::format("Vendor: %1%, version installed: %2%, version cached: %3%") + % vp.name + % ver_current->config_version.to_string() + % recommended->config_version.to_string(); + + if (! ver_current->is_current_slic3r_supported()) { + BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string(); + updates.incompats.emplace_back(std::move(bundle_path), *ver_current); + } else if (recommended->config_version > ver_current->config_version) { + // Config bundle update situation + + // Check if the update is already present in a snapshot + const auto recommended_snap = SnapshotDB::singleton().snapshot_with_vendor_preset(vp.name, recommended->config_version); + if (recommended_snap != SnapshotDB::singleton().end()) { + BOOST_LOG_TRIVIAL(info) << boost::format("Bundle update %1% %2% already found in snapshot %3%, skipping...") + % vp.name + % recommended->config_version.to_string() + % recommended_snap->id; + continue; + } + + auto path_in_cache = cache_path / (idx.vendor() + ".ini"); + if (! fs::exists(path_in_cache)) { + BOOST_LOG_TRIVIAL(warning) << "Index indicates update, but new bundle not found in cache: " << path_in_cache.string(); + continue; + } + + const auto cached_vp = VendorProfile::from_ini(path_in_cache, false); + if (cached_vp.config_version == recommended->config_version) { + updates.updates.emplace_back(std::move(path_in_cache), std::move(bundle_path), *recommended); + } + } + } + + return updates; +} + +void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const +{ + if (updates.incompats.size() > 0) { + if (snapshot) { + BOOST_LOG_TRIVIAL(info) << "Taking a snapshot..."; + SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_DOWNGRADE); + } + + BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% incompatible bundles") % updates.incompats.size(); + + for (const auto &incompat : updates.incompats) { + BOOST_LOG_TRIVIAL(info) << '\t' << incompat; + fs::remove(incompat.bundle); + } + } + else if (updates.updates.size() > 0) { + if (snapshot) { + BOOST_LOG_TRIVIAL(info) << "Taking a snapshot..."; + SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_UPGRADE); + } + + BOOST_LOG_TRIVIAL(info) << boost::format("Performing %1% updates") % updates.updates.size(); + + for (const auto &update : updates.updates) { + BOOST_LOG_TRIVIAL(info) << '\t' << update; + + fs::copy_file(update.source, update.target, fs::copy_option::overwrite_if_exists); + + PresetBundle bundle; + bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); + + auto preset_remover = [](const Preset &preset) { + fs::remove(preset.file); + }; + + for (const auto &preset : bundle.prints) { preset_remover(preset); } + for (const auto &preset : bundle.filaments) { preset_remover(preset); } + for (const auto &preset : bundle.printers) { preset_remover(preset); } + } + } +} + + +PresetUpdater::PresetUpdater(int version_online_event) : + p(new priv(version_online_event)) +{} + + +// Public + +PresetUpdater::~PresetUpdater() +{ + if (p && p->thread.joinable()) { + // This will stop transfers being done by the thread, if any. + // Cancelling takes some time, but should complete soon enough. + p->cancel = true; + p->thread.join(); + } +} + +void PresetUpdater::sync(PresetBundle *preset_bundle) +{ + p->set_download_prefs(GUI::get_app_config()); + if (!p->enabled_version_check && !p->enabled_config_update) { return; } + + // Copy the whole vendors data for use in the background thread + // Unfortunatelly as of C++11, it needs to be copied again + // into the closure (but perhaps the compiler can elide this). + std::set vendors = preset_bundle->vendors; + + p->thread = std::move(std::thread([this, vendors]() { + this->p->prune_tmps(); + this->p->sync_version(); + this->p->sync_config(std::move(vendors)); + })); +} + +void PresetUpdater::slic3r_update_notify() +{ + if (! p->enabled_version_check) { return; } + + if (p->had_config_update) { + BOOST_LOG_TRIVIAL(info) << "New Slic3r version available, but there was a configuration update, notification won't be displayed"; + return; + } + + auto* app_config = GUI::get_app_config(); + const auto ver_slic3r = Semver::parse(SLIC3R_VERSION); + const auto ver_online_str = app_config->get("version_online"); + const auto ver_online = Semver::parse(ver_online_str); + const auto ver_online_seen = Semver::parse(app_config->get("version_online_seen")); + if (! ver_slic3r) { + throw std::runtime_error("Could not parse Slic3r version string: " SLIC3R_VERSION); + } + + if (ver_online) { + // Only display the notification if the version available online is newer AND if we haven't seen it before + if (*ver_online > *ver_slic3r && (! ver_online_seen || *ver_online_seen < *ver_online)) { + GUI::MsgUpdateSlic3r notification(*ver_slic3r, *ver_online); + notification.ShowModal(); + if (notification.disable_version_check()) { + app_config->set("version_check", "0"); + p->enabled_version_check = false; + } + } + app_config->set("version_online_seen", ver_online_str); + } +} + +bool PresetUpdater::config_update() const +{ + if (! p->enabled_config_update) { return true; } + + auto updates = p->get_config_updates(); + if (updates.incompats.size() > 0) { + BOOST_LOG_TRIVIAL(info) << boost::format("%1% bundles incompatible. Asking for action...") % updates.incompats.size(); + + std::unordered_map incompats_map; + for (const auto &incompat : updates.incompats) { + auto vendor = incompat.name(); + auto restrictions = wxString::Format(_(L("requires min. %s and max. %s")), + incompat.version.min_slic3r_version.to_string(), + incompat.version.max_slic3r_version.to_string() + ); + incompats_map.emplace(std::make_pair(std::move(vendor), std::move(restrictions))); + } + + GUI::MsgDataIncompatible dlg(std::move(incompats_map)); + const auto res = dlg.ShowModal(); + if (res == wxID_REPLACE) { + BOOST_LOG_TRIVIAL(info) << "User wants to re-configure..."; + p->perform_updates(std::move(updates)); + GUI::ConfigWizard wizard(nullptr, GUI::ConfigWizard::RR_DATA_INCOMPAT); + if (wizard.run(GUI::get_preset_bundle(), this)) { + p->had_config_update = true; + } else { + return false; + } + } else { + BOOST_LOG_TRIVIAL(info) << "User wants to exit Slic3r, bye..."; + return false; + } + } + else if (updates.updates.size() > 0) { + BOOST_LOG_TRIVIAL(info) << boost::format("Update of %1% bundles available. Asking for confirmation ...") % updates.updates.size(); + + std::unordered_map updates_map; + for (const auto &update : updates.updates) { + auto vendor = update.name(); + auto ver_str = update.version.config_version.to_string(); + if (! update.version.comment.empty()) { + ver_str += std::string(" (") + update.version.comment + ")"; + } + updates_map.emplace(std::make_pair(std::move(vendor), std::move(ver_str))); + } + + GUI::MsgUpdateConfig dlg(std::move(updates_map)); + + const auto res = dlg.ShowModal(); + if (res == wxID_OK) { + BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; + p->perform_updates(std::move(updates)); + } else { + BOOST_LOG_TRIVIAL(info) << "User refused the update"; + } + + p->had_config_update = true; + } else { + BOOST_LOG_TRIVIAL(info) << "No configuration updates available."; + } + + return true; +} + +void PresetUpdater::install_bundles_rsrc(std::vector bundles, bool snapshot) const +{ + Updates updates; + + BOOST_LOG_TRIVIAL(info) << boost::format("Installing %1% bundles from resources ...") % bundles.size(); + + for (const auto &bundle : bundles) { + auto path_in_rsrc = p->rsrc_path / bundle; + auto path_in_vendors = p->vendor_path / bundle; + updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version()); + } + + p->perform_updates(std::move(updates), snapshot); +} + + +} diff --git a/xs/src/slic3r/Utils/PresetUpdater.hpp b/xs/src/slic3r/Utils/PresetUpdater.hpp new file mode 100644 index 0000000000..6a53cca816 --- /dev/null +++ b/xs/src/slic3r/Utils/PresetUpdater.hpp @@ -0,0 +1,42 @@ +#ifndef slic3r_PresetUpdate_hpp_ +#define slic3r_PresetUpdate_hpp_ + +#include +#include + +namespace Slic3r { + + +class AppConfig; +class PresetBundle; + +class PresetUpdater +{ +public: + PresetUpdater(int version_online_event); + PresetUpdater(PresetUpdater &&) = delete; + PresetUpdater(const PresetUpdater &) = delete; + PresetUpdater &operator=(PresetUpdater &&) = delete; + PresetUpdater &operator=(const PresetUpdater &) = delete; + ~PresetUpdater(); + + // If either version check or config updating is enabled, get the appropriate data in the background and cache it. + void sync(PresetBundle *preset_bundle); + + // If version check is enabled, check if chaced online slic3r version is newer, notify if so. + void slic3r_update_notify(); + + // If updating is enabled, check if updates are available in cache, if so, ask about installation. + // A false return value implies Slic3r should exit due to incompatibility of configuration. + bool config_update() const; + + // "Update" a list of bundles from resources (behaves like an online update). + void install_bundles_rsrc(std::vector bundles, bool snapshot = true) const; +private: + struct priv; + std::unique_ptr p; +}; + + +} +#endif diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp index 7fc3b8033a..736f9b891a 100644 --- a/xs/src/slic3r/Utils/Semver.hpp +++ b/xs/src/slic3r/Utils/Semver.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include @@ -18,9 +20,33 @@ public: struct Minor { const int i; Minor(int i) : i(i) {} }; struct Patch { const int i; Patch(int i) : i(i) {} }; + Semver() : ver(semver_zero()) {} + + Semver(int major, int minor, int patch, + boost::optional metadata = boost::none, + boost::optional prerelease = boost::none) + : ver(semver_zero()) + { + ver.major = major; + ver.minor = minor; + ver.patch = patch; + set_metadata(metadata); + set_prerelease(prerelease); + } + + Semver(const std::string &str) : ver(semver_zero()) + { + auto parsed = parse(str); + if (! parsed) { + throw std::runtime_error(std::string("Could not parse version string: ") + str); + } + ver = parsed->ver; + parsed->ver = semver_zero(); + } + static boost::optional parse(const std::string &str) { - semver_t ver; + semver_t ver = semver_zero(); if (::semver_parse(str.c_str(), &ver) == 0) { return Semver(ver); } else { @@ -28,11 +54,7 @@ public: } } - static const Semver zero() - { - static semver_t ver = { 0, 0, 0, nullptr, nullptr }; - return Semver(ver); - } + static const Semver zero() { return Semver(semver_zero()); } static const Semver inf() { @@ -46,28 +68,40 @@ public: return Semver(ver); } - Semver(Semver &&other) { *this = std::move(other); } - Semver(const Semver &other) { *this = other; } + Semver(Semver &&other) : ver(other.ver) { other.ver = semver_zero(); } + Semver(const Semver &other) : ver(::semver_copy(&other.ver)) {} Semver &operator=(Semver &&other) { + ::semver_free(&ver); ver = other.ver; - other.ver.major = other.ver.minor = other.ver.patch = 0; - other.ver.metadata = other.ver.prerelease = nullptr; + other.ver = semver_zero(); return *this; } Semver &operator=(const Semver &other) { ::semver_free(&ver); - ver = other.ver; - if (other.ver.metadata != nullptr) { std::strcpy(ver.metadata, other.ver.metadata); } - if (other.ver.prerelease != nullptr) { std::strcpy(ver.prerelease, other.ver.prerelease); } + ver = ::semver_copy(&other.ver); return *this; } ~Semver() { ::semver_free(&ver); } + // const accessors + int maj() const { return ver.major; } + int min() const { return ver.minor; } + int patch() const { return ver.patch; } + const char* prerelease() const { return ver.prerelease; } + const char* metadata() const { return ver.metadata; } + + // Setters + void set_maj(int maj) { ver.major = maj; } + void set_min(int min) { ver.minor = min; } + void set_patch(int patch) { ver.patch = patch; } + void set_metadata(boost::optional meta) { ver.metadata = meta ? strdup(*meta) : nullptr; } + void set_prerelease(boost::optional pre) { ver.prerelease = pre ? strdup(*pre) : nullptr; } + // Comparison bool operator<(const Semver &b) const { return ::semver_compare(ver, b.ver) == -1; } bool operator<=(const Semver &b) const { return ::semver_compare(ver, b.ver) <= 0; } @@ -107,6 +141,9 @@ private: semver_t ver; Semver(semver_t ver) : ver(ver) {} + + static semver_t semver_zero() { return { 0, 0, 0, nullptr, nullptr }; } + static char * strdup(const std::string &str) { return ::semver_strdup(const_cast(str.c_str())); } }; diff --git a/xs/src/slic3r/Utils/Time.cpp b/xs/src/slic3r/Utils/Time.cpp index c4123c7bba..f38c4b4074 100644 --- a/xs/src/slic3r/Utils/Time.cpp +++ b/xs/src/slic3r/Utils/Time.cpp @@ -1,13 +1,18 @@ #include "Time.hpp" +#ifdef WIN32 + #define WIN32_LEAN_AND_MEAN + #include + #undef WIN32_LEAN_AND_MEAN +#endif /* WIN32 */ + namespace Slic3r { namespace Utils { time_t parse_time_ISO8601Z(const std::string &sdate) { - int y, M, d, h, m; - float s; - if (sscanf(sdate.c_str(), "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s) != 6) + int y, M, d, h, m, s; + if (sscanf(sdate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &y, &M, &d, &h, &m, &s) != 6) return (time_t)-1; struct tm tms; tms.tm_year = y - 1900; // Year since 1900 @@ -15,7 +20,7 @@ time_t parse_time_ISO8601Z(const std::string &sdate) tms.tm_mday = d; // 1-31 tms.tm_hour = h; // 0-23 tms.tm_min = m; // 0-59 - tms.tm_sec = (int)s; // 0-61 (0-60 in C++11) + tms.tm_sec = s; // 0-61 (0-60 in C++11) return mktime(&tms); } @@ -23,21 +28,34 @@ std::string format_time_ISO8601Z(time_t time) { struct tm tms; #ifdef WIN32 - gmtime_s(time, &tms); + gmtime_s(&tms, &time); #else - gmtime_r(&tms, time); + gmtime_r(&time, &tms); #endif char buf[128]; - sprintf(buf, "%d-%d-%dT%d:%d:%fZ", - tms.tm_year + 1900 - tms.tm_mon + 1 - tms.tm_mday - tms.tm_hour - tms.tm_min + sprintf(buf, "%04d%02d%02dT%02d%02d%02dZ", + tms.tm_year + 1900, + tms.tm_mon + 1, + tms.tm_mday, + tms.tm_hour, + tms.tm_min, tms.tm_sec); return buf; } +std::string format_local_date_time(time_t time) +{ + struct tm tms; +#ifdef WIN32 + localtime_s(&tms, &time); +#else + localtime_r(&time, &tms); +#endif + char buf[80]; + strftime(buf, 80, "%x %X", &tms); + return buf; +} + time_t get_current_time_utc() { #ifdef WIN32 @@ -53,11 +71,10 @@ time_t get_current_time_utc() tm.tm_isdst = -1; return mktime(&tm); #else - return gmtime(); + const time_t current_local = time(nullptr); + return mktime(gmtime(¤t_local)); #endif } }; // namespace Utils }; // namespace Slic3r - -#endif /* slic3r_Utils_Time_hpp_ */ diff --git a/xs/src/slic3r/Utils/Time.hpp b/xs/src/slic3r/Utils/Time.hpp index 6b2fbf8930..7b670bd3ee 100644 --- a/xs/src/slic3r/Utils/Time.hpp +++ b/xs/src/slic3r/Utils/Time.hpp @@ -13,8 +13,11 @@ namespace Utils { extern time_t parse_time_ISO8601Z(const std::string &s); extern std::string format_time_ISO8601Z(time_t time); +// Format the date and time from an UTC time according to the active locales and a local time zone. +extern std::string format_local_date_time(time_t time); + // There is no gmtime() on windows. -time_t get_current_time_utc(); +extern time_t get_current_time_utc(); }; // namespace Utils }; // namespace Slic3r diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 406d718286..aa95bd647d 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -9,6 +9,9 @@ %package{Slic3r::GUI}; +void about() + %code{% Slic3r::GUI::about(); %}; + void disable_screensaver() %code{% Slic3r::GUI::disable_screensaver(); %}; @@ -32,13 +35,16 @@ void set_main_frame(SV *ui) void set_tab_panel(SV *ui) %code%{ Slic3r::GUI::set_tab_panel((wxNotebook*)wxPli_sv_2_object(aTHX_ ui, "Wx::Notebook")); %}; - -void add_debug_menu(SV *ui, int event_language_change) - %code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_language_change); %}; + +void add_config_menu(SV *ui, int event_preferences_changed, int event_language_change) + %code%{ Slic3r::GUI::add_config_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_preferences_changed, event_language_change); %}; void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed) %code%{ Slic3r::GUI::create_preset_tabs(no_controller, event_value_change, event_presets_changed); %}; +void show_error_id(int id, std::string msg) + %code%{ Slic3r::GUI::show_error_id(id, msg); %}; + TabIface* get_preset_tab(char *name) %code%{ RETVAL=Slic3r::GUI::get_preset_tab_iface(name); %}; @@ -54,12 +60,27 @@ int combochecklist_get_flags(SV *ui) void set_app_config(AppConfig *app_config) %code%{ Slic3r::GUI::set_app_config(app_config); %}; +bool check_unsaved_changes() + %code%{ RETVAL=Slic3r::GUI::check_unsaved_changes(); %}; + +bool config_wizard_startup(int app_config_exists) + %code%{ + try { + RETVAL=Slic3r::GUI::config_wizard_startup(app_config_exists != 0); + } catch (std::exception& e) { + croak("%s\n", e.what()); + } + %}; + void open_preferences_dialog(int preferences_event) %code%{ Slic3r::GUI::open_preferences_dialog(preferences_event); %}; void set_preset_bundle(PresetBundle *preset_bundle) %code%{ Slic3r::GUI::set_preset_bundle(preset_bundle); %}; +void set_preset_updater(PresetUpdater* updater) + %code%{ Slic3r::GUI::set_preset_updater(updater); %}; + void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_sizer) %code%{ Slic3r::GUI::add_frequently_changed_parameters((wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp index 00b469309e..2c63db10c4 100644 --- a/xs/xsp/GUI_Preset.xsp +++ b/xs/xsp/GUI_Preset.xsp @@ -44,9 +44,6 @@ Ref 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 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); %}; @@ -55,30 +52,6 @@ %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* @@ -144,14 +117,6 @@ PresetCollection::arrayref() croak("Loading of a config bundle %s failed:\n%s\n", path, e.what()); } %}; - void install_vendor_configbundle(const char *path) - %code%{ - try { - THIS->install_vendor_configbundle(path); - } catch (std::exception& e) { - croak("Installing a vendor config bundle %s failed:\n%s\n", path, e.what()); - } - %}; void export_configbundle(char *path) %code%{ try { @@ -175,9 +140,6 @@ PresetCollection::arrayref() std::vector 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 full_config() %code%{ RETVAL = THIS->full_config(); %}; @@ -185,15 +147,3 @@ PresetCollection::arrayref() %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); %}; - static std::string recommended_thin_wall_thickness(PresetBundle *preset) - %code%{ RETVAL = PresetHints::recommended_thin_wall_thickness(*preset); %}; -}; diff --git a/xs/xsp/GUI_Tab.xsp b/xs/xsp/GUI_Tab.xsp index 9cacac74f0..bcbdc0d9f8 100644 --- a/xs/xsp/GUI_Tab.xsp +++ b/xs/xsp/GUI_Tab.xsp @@ -16,6 +16,7 @@ bool current_preset_is_dirty(); void load_key_value(char* opt_key, char* value); void OnActivate(); + size_t get_selected_preset_item(); std::string title(); Ref get_config(); Ref get_presets(); diff --git a/xs/xsp/Utils_PresetUpdater.xsp b/xs/xsp/Utils_PresetUpdater.xsp new file mode 100644 index 0000000000..dc874acabe --- /dev/null +++ b/xs/xsp/Utils_PresetUpdater.xsp @@ -0,0 +1,13 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "slic3r/Utils/PresetUpdater.hpp" +%} + +%name{Slic3r::PresetUpdater} class PresetUpdater { + PresetUpdater(int version_online_event); + void sync(PresetBundle* preset_bundle); + void slic3r_update_notify(); + bool config_update(); +}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 87a8d8d866..cc902acae4 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -231,11 +231,12 @@ PresetCollection* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PresetBundle* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T -PresetHints* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T TabIface* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T +PresetUpdater* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T + OctoPrint* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 797f3c19f1..57eae15985 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -208,6 +208,8 @@ %typemap{Ref}{simple}; %typemap{PresetBundle*}; %typemap{Ref}{simple}; +%typemap{PresetUpdater*}; +%typemap{Ref}{simple}; %typemap{PresetHints*}; %typemap{Ref}{simple}; %typemap{TabIface*};