diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm deleted file mode 100644 index 31f614ba92..0000000000 --- a/lib/Slic3r/GUI.pm +++ /dev/null @@ -1,337 +0,0 @@ -package Slic3r::GUI; -use strict; -use warnings; -use utf8; - -use File::Basename qw(basename); -use FindBin; -use List::Util qw(first); -use Slic3r::GUI::MainFrame; -use Slic3r::GUI::Plater; -use Slic3r::GUI::Plater::3D; -use Slic3r::GUI::Plater::3DPreview; - -use Wx::Locale gettext => 'L'; - -our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1"; - -use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow :filedialog :font); -use Wx::Event qw(EVT_IDLE EVT_COMMAND EVT_MENU); -use base 'Wx::App'; - -use constant FILE_WILDCARDS => { - known => 'Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.zip.amf;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA', - stl => 'STL files (*.stl)|*.stl;*.STL', - obj => 'OBJ files (*.obj)|*.obj;*.OBJ', - amf => 'AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML', - threemf => '3MF files (*.3mf)|*.3mf;*.3MF', - prusa => 'Prusa Control files (*.prusa)|*.prusa;*.PRUSA', - ini => 'INI files *.ini|*.ini;*.INI', - gcode => 'G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC', -}; -use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf threemf prusa)}; - -# Datadir provided on the command line. -our $datadir; -our $no_plater; -our @cb; - -our $small_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); -$small_font->SetPointSize(11) if &Wx::wxMAC; -our $small_bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); -$small_bold_font->SetPointSize(11) if &Wx::wxMAC; -$small_bold_font->SetWeight(wxFONTWEIGHT_BOLD); -our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); -$medium_font->SetPointSize(12); -our $grey = Wx::Colour->new(200,200,200); - -# Events to be sent from a C++ menu implementation: -# 1) To inform about a change of the application language. -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) = @_; - - $self->SetAppName('Slic3rPE'); - $self->SetAppDisplayName('Slic3r Prusa Edition'); - Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION; - - # Set the Slic3r data directory at the Slic3r XS module. - # Unix: ~/.Slic3r - # Windows: "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" - # Mac: "~/Library/Application Support/Slic3r" - Slic3r::set_data_dir($datadir || Wx::StandardPaths::Get->GetUserDataDir); - 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 - eval { $self->{preset_bundle}->setup_directories() }; - if ($@) { - warn $@ . "\n"; - fatal_error(undef, $@); - } - my $app_conf_exists = $self->{app_config}->exists; - # load settings - $self->{app_config}->load if $app_conf_exists; - $self->{app_config}->set('version', $Slic3r::VERSION); - $self->{app_config}->save; - - $self->{preset_updater} = Slic3r::PresetUpdater->new($VERSION_ONLINE_EVENT); - Slic3r::GUI::set_preset_updater($self->{preset_updater}); - - Slic3r::GUI::load_language(); - - # Suppress the '- default -' presets. - $self->{preset_bundle}->set_default_suppressed($self->{app_config}->get('no_defaults') ? 1 : 0); - eval { $self->{preset_bundle}->load_presets($self->{app_config}); }; - if ($@) { - warn $@ . "\n"; - show_error(undef, $@); - } - - # 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( - no_plater => $no_plater, - lang_ch_event => $LANGUAGE_CHANGE_EVENT, - preferences_event => $PREFERENCES_EVENT, - ); - $self->SetTopWindow($frame); - - # This makes CallAfter() work - EVT_IDLE($self->{mainframe}, sub { - while (my $cb = shift @cb) { - $cb->(); - } - $self->{app_config}->save if $self->{app_config}->dirty; - }); - - # On OS X the UI tends to freeze in weird ways if modal dialogs (config wizard, update notifications, ...) - # are shown before or in the same event callback with the main frame creation. - # Therefore we schedule them for later using CallAfter. - $self->CallAfter(sub { - eval { - if (! $self->{preset_updater}->config_update()) { - $self->{mainframe}->Close; - } - }; - if ($@) { - show_error(undef, $@); - $self->{mainframe}->Close; - } - }); - - $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; - }); - - # The following event is emited by the C++ menu implementation of preferences change. - EVT_COMMAND($self, -1, $PREFERENCES_EVENT, sub{ - $self->update_ui_from_settings; - }); - - # The following event is emited by PresetUpdater (C++) to inform about - # the newer Slic3r application version avaiable online. - 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( - no_plater => $no_plater, - lang_ch_event => $LANGUAGE_CHANGE_EVENT, - preferences_event => $PREFERENCES_EVENT, - ); - - if($topwindow) - { - $self->SetTopWindow($frame); - $topwindow->Destroy; - } - - EVT_IDLE($self->{mainframe}, sub { - while (my $cb = shift @cb) { - $cb->(); - } - $self->{app_config}->save if $self->{app_config}->dirty; - }); - - # 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 { - my ($self) = @_; - my $slic3r_info = Slic3r::slic3r_info(format => 'html'); - my $copyright_info = Slic3r::copyright_info(format => 'html'); - my $system_info = Slic3r::system_info(format => 'html'); - my $opengl_info; - my $opengl_info_txt = ''; - if (defined($self->{mainframe}) && defined($self->{mainframe}->{plater}) && - defined($self->{mainframe}->{plater}->{canvas3D})) { - $opengl_info = Slic3r::GUI::_3DScene::get_gl_info(1, 1); - $opengl_info_txt = Slic3r::GUI::_3DScene::get_gl_info(0, 1); - } -# my $about = Slic3r::GUI::SystemInfo->new( -# parent => undef, -# slic3r_info => $slic3r_info, -# system_info => $system_info, -# opengl_info => $opengl_info, -# text_info => Slic3r::slic3r_info . Slic3r::system_info . $opengl_info_txt, -# ); -# $about->ShowModal; -# $about->Destroy; -} - -# static method accepting a wxWindow object as first parameter -sub catch_error { - my ($self, $cb, $message_dialog) = @_; - if (my $err = $@) { - $cb->() if $cb; - $message_dialog - ? $message_dialog->($err, 'Error', wxOK | wxICON_ERROR) - : Slic3r::GUI::show_error($self, $err); - return 1; - } - return 0; -} - -# static method accepting a wxWindow object as first parameter -sub show_error { - my ($parent, $message) = @_; - Slic3r::GUI::show_error_id($parent ? $parent->GetId() : 0, $message); -} - -# static method accepting a wxWindow object as first parameter -sub show_info { - my ($parent, $message, $title) = @_; - Wx::MessageDialog->new($parent, $message, $title || 'Notice', wxOK | wxICON_INFORMATION)->ShowModal; -} - -# static method accepting a wxWindow object as first parameter -sub fatal_error { - show_error(@_); - exit 1; -} - -# static method accepting a wxWindow object as first parameter -sub warning_catcher { - my ($self, $message_dialog) = @_; - return sub { - my $message = shift; - return if $message =~ /GLUquadricObjPtr|Attempt to free unreferenced scalar/; - my @params = ($message, 'Warning', wxOK | wxICON_WARNING); - $message_dialog - ? $message_dialog->(@params) - : Wx::MessageDialog->new($self, @params)->ShowModal; - }; -} - -sub notify { - my ($self, $message) = @_; - - my $frame = $self->GetTopWindow; - # try harder to attract user attention on OS X - $frame->RequestUserAttention(&Wx::wxMAC ? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO) - unless ($frame->IsActive); - - # There used to be notifier using a Growl application for OSX, but Growl is dead. - # The notifier also supported the Linux X D-bus notifications, but that support was broken. - #TODO use wxNotificationMessage? -} - -# Called after the Preferences dialog is closed and the program settings are saved. -# Update the UI based on the current preferences. -sub update_ui_from_settings { - my ($self) = @_; - $self->{mainframe}->update_ui_from_settings; -} - -sub open_model { - my ($self, $window) = @_; - - my $dlg_title = L('Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):'); - my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, $dlg_title, - $self->{app_config}->get_last_dir, "", - MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); - if ($dialog->ShowModal != wxID_OK) { - $dialog->Destroy; - return; - } - my @input_files = $dialog->GetPaths; - $dialog->Destroy; - return @input_files; -} - -sub CallAfter { - my ($self, $cb) = @_; - push @cb, $cb; -} - -sub append_menu_item { - my ($self, $menu, $string, $description, $cb, $id, $icon, $kind) = @_; - - $id //= &Wx::NewId(); - my $item = Wx::MenuItem->new($menu, $id, $string, $description // '', $kind // 0); - $self->set_menu_item_icon($item, $icon); - $menu->Append($item); - - EVT_MENU($self, $id, $cb); - return $item; -} - -sub append_submenu { - my ($self, $menu, $string, $description, $submenu, $id, $icon) = @_; - - $id //= &Wx::NewId(); - my $item = Wx::MenuItem->new($menu, $id, $string, $description // ''); - $self->set_menu_item_icon($item, $icon); - $item->SetSubMenu($submenu); - $menu->Append($item); - - return $item; -} - -sub set_menu_item_icon { - my ($self, $menuItem, $icon) = @_; - - # SetBitmap was not available on OS X before Wx 0.9927 - if ($icon && $menuItem->can('SetBitmap')) { - $menuItem->SetBitmap(Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG)); - } -} - -1; diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm deleted file mode 100644 index 23decaa371..0000000000 --- a/lib/Slic3r/GUI/3DScene.pm +++ /dev/null @@ -1,70 +0,0 @@ -# Implements pure perl packages -# -# Slic3r::GUI::3DScene::Base; -# Slic3r::GUI::3DScene; -# -# Slic3r::GUI::Plater::3D derives from Slic3r::GUI::3DScene, -# Slic3r::GUI::Plater::3DPreview, -# Slic3r::GUI::Plater::ObjectCutDialog and Slic3r::GUI::Plater::ObjectPartsPanel -# own $self->{canvas} of the Slic3r::GUI::3DScene type. -# -# Therefore the 3DScene supports renderng of STLs, extrusions and cutting planes, -# and camera manipulation. - -package Slic3r::GUI::3DScene::Base; -use strict; -use warnings; - -use Wx qw(wxTheApp :timer :bitmap :icon :dialog); -# must load OpenGL *before* Wx::GLCanvas -use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants); -use base qw(Wx::GLCanvas Class::Accessor); -use Wx::GLCanvas qw(:all); - -sub new { - my ($class, $parent) = @_; - - # We can only enable multi sample anti aliasing wih wxWidgets 3.0.3 and with a hacked Wx::GLCanvas, - # which exports some new WX_GL_XXX constants, namely WX_GL_SAMPLE_BUFFERS and WX_GL_SAMPLES. - my $can_multisample = - ! wxTheApp->{app_config}->get('use_legacy_opengl') && - Wx::wxVERSION >= 3.000003 && - defined Wx::GLCanvas->can('WX_GL_SAMPLE_BUFFERS') && - defined Wx::GLCanvas->can('WX_GL_SAMPLES'); - my $attrib = [WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24]; - if ($can_multisample) { - # Request a window with multi sampled anti aliasing. This is a new feature in Wx 3.0.3 (backported from 3.1.0). - # Use eval to avoid compilation, if the subs WX_GL_SAMPLE_BUFFERS and WX_GL_SAMPLES are missing. - eval 'push(@$attrib, (WX_GL_SAMPLE_BUFFERS, 1, WX_GL_SAMPLES, 4));'; - } - # wxWidgets expect the attrib list to be ended by zero. - push(@$attrib, 0); - - # we request a depth buffer explicitely because it looks like it's not created by - # default on Linux, causing transparency issues - my $self = $class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "", $attrib); - - Slic3r::GUI::_3DScene::add_canvas($self); - Slic3r::GUI::_3DScene::allow_multisample($self, $can_multisample); - - return $self; -} - -sub Destroy { - my ($self) = @_; - Slic3r::GUI::_3DScene::remove_canvas($self); - return $self->SUPER::Destroy; -} - -# The 3D canvas to display objects and tool paths. -package Slic3r::GUI::3DScene; -use base qw(Slic3r::GUI::3DScene::Base); - -sub new { - my $class = shift; - - my $self = $class->SUPER::new(@_); - return $self; -} - -1; diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm deleted file mode 100644 index c1975cd5d3..0000000000 --- a/lib/Slic3r/GUI/MainFrame.pm +++ /dev/null @@ -1,644 +0,0 @@ -# The main frame, the parent of all. - -package Slic3r::GUI::MainFrame; -use strict; -use warnings; -use utf8; - -use File::Basename qw(basename dirname); -use FindBin; -use List::Util qw(min first); -use Slic3r::Geometry qw(X Y); -use Wx qw(:frame :bitmap :id :misc :notebook :panel :sizer :menu :dialog :filedialog :dirdialog - :font :icon wxTheApp); -use Wx::Event qw(EVT_CLOSE EVT_COMMAND EVT_MENU EVT_NOTEBOOK_PAGE_CHANGED); -use base 'Wx::Frame'; - -use Wx::Locale gettext => 'L'; - -our $qs_last_input_file; -our $qs_last_output_file; -our $last_config; -our $appController; - -# Events to be sent from a C++ Tab implementation: -# 1) To inform about a change of a configuration value. -our $VALUE_CHANGE_EVENT = Wx::NewEventType; -# 2) To inform about a preset selection change or a "modified" status change. -our $PRESETS_CHANGED_EVENT = Wx::NewEventType; -# 3) To update the status bar with the progress information. -our $PROGRESS_BAR_EVENT = Wx::NewEventType; -# 4) To display an error dialog box from a thread on the UI thread. -our $ERROR_EVENT = Wx::NewEventType; -# 5) To inform about a change of object selection -our $OBJECT_SELECTION_CHANGED_EVENT = Wx::NewEventType; -# 6) To inform about a change of object settings -our $OBJECT_SETTINGS_CHANGED_EVENT = Wx::NewEventType; -# 7) To inform about a remove of object -our $OBJECT_REMOVE_EVENT = Wx::NewEventType; -# 8) To inform about a update of the scene -our $UPDATE_SCENE_EVENT = Wx::NewEventType; - -sub new { - my ($class, %params) = @_; - - my $self = $class->SUPER::new(undef, -1, $Slic3r::FORK_NAME . ' - ' . $Slic3r::VERSION, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE); - Slic3r::GUI::set_main_frame($self); - - $appController = Slic3r::AppController->new(); - - if ($^O eq 'MSWin32') { - # Load the icon either from the exe, or from the ico file. - my $iconfile = Slic3r::decode_path($FindBin::Bin) . '\slic3r.exe'; - $iconfile = Slic3r::var("Slic3r.ico") unless -f $iconfile; - $self->SetIcon(Wx::Icon->new($iconfile, wxBITMAP_TYPE_ICO)); - } else { - $self->SetIcon(Wx::Icon->new(Slic3r::var("Slic3r_128px.png"), wxBITMAP_TYPE_PNG)); - } - - # store input params - $self->{no_plater} = $params{no_plater}; - $self->{loaded} = 0; - $self->{lang_ch_event} = $params{lang_ch_event}; - $self->{preferences_event} = $params{preferences_event}; - - # initialize tabpanel and menubar - $self->_init_tabpanel; - $self->_init_menubar; - - # set default tooltip timer in msec - # SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values - # (SetAutoPop is not available on GTK.) - eval { Wx::ToolTip::SetAutoPop(32767) }; - - # initialize status bar - $self->{statusbar} = Slic3r::GUI::ProgressStatusBar->new(); - $self->{statusbar}->Embed; - $self->{statusbar}->SetStatusText(L("Version ").$Slic3r::VERSION.L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases")); - # Make the global status bar and its progress indicator available in C++ - Slic3r::GUI::set_progress_status_bar($self->{statusbar}); - $appController->set_global_progress_indicator($self->{statusbar}); - - $appController->set_model($self->{plater}->{model}); - $appController->set_print($self->{plater}->{print}); - - $self->{plater}->{appController} = $appController; - - $self->{loaded} = 1; - - # initialize layout - { - my $sizer = Wx::BoxSizer->new(wxVERTICAL); - $sizer->Add($self->{tabpanel}, 1, wxEXPAND); - $sizer->SetSizeHints($self); - $self->SetSizer($sizer); - $self->Fit; - $self->SetMinSize([760, 490]); - $self->SetSize($self->GetMinSize); - Slic3r::GUI::restore_window_size($self, "main_frame"); - $self->Show; - $self->Layout; - } - - # declare events - EVT_CLOSE($self, sub { - my (undef, $event) = @_; - if ($event->CanVeto && !Slic3r::GUI::check_unsaved_changes) { - $event->Veto; - return; - } - # save window size - Slic3r::GUI::save_window_size($self, "main_frame"); - # Save the slic3r.ini. Usually the ini file is saved from "on idle" callback, - # but in rare cases it may not have been called yet. - wxTheApp->{app_config}->save; - $self->{statusbar}->ResetCancelCallback(); - $self->{plater}->{print} = undef if($self->{plater}); - Slic3r::GUI::_3DScene::remove_all_canvases(); - Slic3r::GUI::deregister_on_request_update_callback(); - # propagate event - $event->Skip; - }); - - $self->update_ui_from_settings; - - Slic3r::GUI::update_mode(); - - return $self; -} - -sub _init_tabpanel { - my ($self) = @_; - - $self->{tabpanel} = my $panel = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); - Slic3r::GUI::set_tab_panel($panel); - - EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{tabpanel}, sub { - my $panel = $self->{tabpanel}->GetCurrentPage; - $panel->OnActivate if $panel->can('OnActivate'); - - for my $tab_name (qw(print filament printer)) { - Slic3r::GUI::get_preset_tab("$tab_name")->OnActivate if ("$tab_name" eq $panel->GetName); - } - }); - - if (!$self->{no_plater}) { - $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel, - event_object_selection_changed => $OBJECT_SELECTION_CHANGED_EVENT, - event_object_settings_changed => $OBJECT_SETTINGS_CHANGED_EVENT, - event_remove_object => $OBJECT_REMOVE_EVENT, - event_update_scene => $UPDATE_SCENE_EVENT, - ), L("Plater")); - } - - #TODO this is an example of a Slic3r XS interface call to add a new preset editor page to the main view. - # The following event is emited by the C++ Tab implementation on config value change. - EVT_COMMAND($self, -1, $VALUE_CHANGE_EVENT, sub { - my ($self, $event) = @_; - my $str = $event->GetString; - my ($opt_key, $name) = ($str =~ /(.*) (.*)/); - #print "VALUE_CHANGE_EVENT: ", $opt_key, "\n"; - my $tab = Slic3r::GUI::get_preset_tab($name); - my $config = $tab->get_config; - if ($self->{plater}) { - $self->{plater}->on_config_change($config); # propagate config change events to the plater - if ($opt_key eq 'extruders_count'){ - my $value = $event->GetInt(); - $self->{plater}->on_extruders_change($value); - } - if ($opt_key eq 'printer_technology'){ - my $value = $event->GetInt();# 0 ~ "ptFFF"; 1 ~ "ptSLA" - $self->{plater}->show_preset_comboboxes($value); - } - } - # don't save while loading for the first time - $self->config->save($Slic3r::GUI::autosave) if $Slic3r::GUI::autosave && $self->{loaded}; - }); - # The following event is emited by the C++ Tab implementation on preset selection, - # or when the preset's "modified" status changes. - EVT_COMMAND($self, -1, $PRESETS_CHANGED_EVENT, sub { - my ($self, $event) = @_; - my $tab_name = $event->GetString; - - my $tab = Slic3r::GUI::get_preset_tab($tab_name); - if ($self->{plater}) { - # Update preset combo boxes (Print settings, Filament, Material, Printer) from their respective tabs. - my $presets = $tab->get_presets; - 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 sla_material)) { - # If the printer tells us that the print or filament preset has been switched or invalidated, - # refresh the print or filament tab page. Otherwise just refresh the combo box. - my $update_action = ($reload_dependent_tabs && (first { $_ eq $tab_name_other } (@{$reload_dependent_tabs}))) - ? 'load_current_preset' : 'update_tab_ui'; - $self->{options_tabs}{$tab_name_other}->$update_action; - } - } - $self->{plater}->on_config_change($tab->get_config); - } - } - }); - - # The following event is emited by the C++ Tab implementation on object selection change. - EVT_COMMAND($self, -1, $OBJECT_SELECTION_CHANGED_EVENT, sub { - my ($self, $event) = @_; - my $obj_idx = $event->GetId; -# my $child = $event->GetInt == 1 ? 1 : undef; -# $self->{plater}->select_object($obj_idx < 0 ? undef: $obj_idx, $child); -# $self->{plater}->item_changed_selection($obj_idx); - - my $vol_idx = $event->GetInt; - $self->{plater}->select_object_from_cpp($obj_idx < 0 ? undef: $obj_idx, $vol_idx<0 ? -1 : $vol_idx); - }); - - # The following event is emited by the C++ GUI implementation on object settings change. - EVT_COMMAND($self, -1, $OBJECT_SETTINGS_CHANGED_EVENT, sub { - my ($self, $event) = @_; - - my $line = $event->GetString; - my ($obj_idx, $parts_changed, $part_settings_changed) = split('',$line); - - $self->{plater}->changed_object_settings($obj_idx, $parts_changed, $part_settings_changed); - }); - - # The following event is emited by the C++ GUI implementation on object remove. - EVT_COMMAND($self, -1, $OBJECT_REMOVE_EVENT, sub { - my ($self, $event) = @_; - $self->{plater}->remove(); - }); - - # The following event is emited by the C++ GUI implementation on extruder change for object. - EVT_COMMAND($self, -1, $UPDATE_SCENE_EVENT, sub { - my ($self, $event) = @_; - $self->{plater}->update(); - }); - - Slic3r::GUI::create_preset_tabs($VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT); - $self->{options_tabs} = {}; - for my $tab_name (qw(print filament sla_material printer)) { - $self->{options_tabs}{$tab_name} = Slic3r::GUI::get_preset_tab("$tab_name"); - } - - # Update progress bar with an event sent by the slicing core. - EVT_COMMAND($self, -1, $PROGRESS_BAR_EVENT, sub { - my ($self, $event) = @_; - if (defined $self->{progress_dialog}) { - # If a progress dialog is open, update it. - $self->{progress_dialog}->Update($event->GetInt, $event->GetString . "…"); - } else { - # Otherwise update the main window status bar. - $self->{statusbar}->SetProgress($event->GetInt); - $self->{statusbar}->SetStatusText($event->GetString . "…"); - } - }); - - EVT_COMMAND($self, -1, $ERROR_EVENT, sub { - my ($self, $event) = @_; - Slic3r::GUI::show_error($self, $event->GetString); - }); - - if ($self->{plater}) { - $self->{plater}->on_select_preset(sub { - my ($group, $name) = @_; - $self->{options_tabs}{$group}->select_preset($name); - }); - # load initial config - my $full_config = wxTheApp->{preset_bundle}->full_config; - $self->{plater}->on_config_change($full_config); - - # Show a correct number of filament fields. - if (defined $full_config->nozzle_diameter){ # nozzle_diameter is undefined when SLA printer is selected - $self->{plater}->on_extruders_change(int(@{$full_config->nozzle_diameter})); - } - - # Show correct preset comboboxes according to the printer_technology - $self->{plater}->show_preset_comboboxes(($full_config->printer_technology eq "FFF") ? 0 : 1); - } -} - -sub _init_menubar { - my ($self) = @_; - - # File menu - my $fileMenu = Wx::Menu->new; - { - wxTheApp->append_menu_item($fileMenu, L("Open STL/OBJ/AMF/3MF…\tCtrl+O"), L('Open a model'), sub { - $self->{plater}->add if $self->{plater}; - }, undef, undef); #'brick_add.png'); - $self->_append_menu_item($fileMenu, L("&Load Config…\tCtrl+L"), L('Load exported configuration file'), sub { - $self->load_config_file; - }, undef, 'plugin_add.png'); - $self->_append_menu_item($fileMenu, L("&Export Config…\tCtrl+E"), L('Export current configuration to file'), sub { - $self->export_config; - }, undef, 'plugin_go.png'); - $self->_append_menu_item($fileMenu, L("&Load Config Bundle…"), L('Load presets from a bundle'), sub { - $self->load_configbundle; - }, undef, 'lorry_add.png'); - $self->_append_menu_item($fileMenu, L("&Export Config Bundle…"), L('Export all presets to file'), sub { - $self->export_configbundle; - }, undef, 'lorry_go.png'); - $fileMenu->AppendSeparator(); - $self->_append_menu_item($fileMenu, L("Slice to PNG…"), L('Slice file to a set of PNG files'), sub { - $self->slice_to_png; - }, undef, 'shape_handles.png'); - $self->{menu_item_reslice_now} = $self->_append_menu_item( - $fileMenu, L("(&Re)Slice Now\tCtrl+S"), L('Start new slicing process'), - sub { $self->reslice_now; }, undef, 'shape_handles.png'); - $fileMenu->AppendSeparator(); - $self->_append_menu_item($fileMenu, L("Repair STL file…"), L('Automatically repair an STL file'), sub { - $self->repair_stl; - }, undef, 'wrench.png'); - $fileMenu->AppendSeparator(); - $self->_append_menu_item($fileMenu, L("&Quit"), L('Quit Slic3r'), sub { - $self->Close(0); - }, wxID_EXIT); - } - - # Plater menu - unless ($self->{no_plater}) { - my $plater = $self->{plater}; - - $self->{plater_menu} = Wx::Menu->new; - $self->_append_menu_item($self->{plater_menu}, L("Export G-code..."), L('Export current plate as G-code'), sub { - $plater->export_gcode; - }, undef, 'cog_go.png'); - $self->_append_menu_item($self->{plater_menu}, L("Export plate as STL..."), L('Export current plate as STL'), sub { - $plater->export_stl; - }, undef, 'brick_go.png'); - $self->_append_menu_item($self->{plater_menu}, L("Export plate as AMF..."), L('Export current plate as AMF'), sub { - $plater->export_amf; - }, undef, 'brick_go.png'); - $self->_append_menu_item($self->{plater_menu}, L("Export plate as 3MF..."), L('Export current plate as 3MF'), sub { - $plater->export_3mf; - }, undef, 'brick_go.png'); - - $self->{object_menu} = $self->{plater}->object_menu; - $self->on_plater_selection_changed(0); - } - - # Window menu - my $windowMenu = Wx::Menu->new; - { - my $tab_offset = 0; - if (!$self->{no_plater}) { - $self->_append_menu_item($windowMenu, L("Select &Plater Tab\tCtrl+1"), L('Show the plater'), sub { - $self->select_tab(0); - }, undef, 'application_view_tile.png'); - $tab_offset += 1; - } - if (!$self->{no_controller}) { - $self->_append_menu_item($windowMenu, L("Select &Controller Tab\tCtrl+T"), L('Show the printer controller'), sub { - $self->select_tab(1); - }, undef, 'printer_empty.png'); - $tab_offset += 1; - } - if ($tab_offset > 0) { - $windowMenu->AppendSeparator(); - } - $self->_append_menu_item($windowMenu, L("Select P&rint Settings Tab\tCtrl+2"), L('Show the print settings'), sub { - $self->select_tab($tab_offset+0); - }, undef, 'cog.png'); - $self->_append_menu_item($windowMenu, L("Select &Filament Settings Tab\tCtrl+3"), L('Show the filament settings'), sub { - $self->select_tab($tab_offset+1); - }, undef, 'spool.png'); - $self->_append_menu_item($windowMenu, L("Select Print&er Settings Tab\tCtrl+4"), L('Show the printer settings'), sub { - $self->select_tab($tab_offset+2); - }, undef, 'printer_empty.png'); - } - - # View menu - if (!$self->{no_plater}) { - $self->{viewMenu} = Wx::Menu->new; - # \xA0 is a non-breaing space. It is entered here to spoil the automatic accelerators, - # as the simple numeric accelerators spoil all numeric data entry. - # The camera control accelerators are captured by 3DScene Perl module instead. - my $accel = ($^O eq 'MSWin32') ? sub { $_[0] . "\t\xA0" . $_[1] } : sub { $_[0] }; - $self->_append_menu_item($self->{viewMenu}, $accel->(L('Iso'), '0'), L('Iso View') , sub { $self->select_view('iso' ); }); - $self->_append_menu_item($self->{viewMenu}, $accel->(L('Top'), '1'), L('Top View') , sub { $self->select_view('top' ); }); - $self->_append_menu_item($self->{viewMenu}, $accel->(L('Bottom'), '2'), L('Bottom View') , sub { $self->select_view('bottom' ); }); - $self->_append_menu_item($self->{viewMenu}, $accel->(L('Front'), '3'), L('Front View') , sub { $self->select_view('front' ); }); - $self->_append_menu_item($self->{viewMenu}, $accel->(L('Rear'), '4'), L('Rear View') , sub { $self->select_view('rear' ); }); - $self->_append_menu_item($self->{viewMenu}, $accel->(L('Left'), '5'), L('Left View') , sub { $self->select_view('left' ); }); - $self->_append_menu_item($self->{viewMenu}, $accel->(L('Right'), '6'), L('Right View') , sub { $self->select_view('right' ); }); - } - - # Help menu - my $helpMenu = Wx::Menu->new; - { - $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/'); - }); - $self->_append_menu_item($helpMenu, L("Prusa Edition Releases"), L('Open the Prusa Edition releases page in your browser'), sub { - Wx::LaunchDefaultBrowser('http://github.com/prusa3d/slic3r/releases'); - }); -# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", 'Check for new Slic3r versions', sub { -# wxTheApp->check_version(1); -# }); -# $versioncheck->Enable(wxTheApp->have_version_check); - $self->_append_menu_item($helpMenu, L("Slic3r &Website"), L('Open the Slic3r website in your browser'), sub { - Wx::LaunchDefaultBrowser('http://slic3r.org/'); - }); - $self->_append_menu_item($helpMenu, L("Slic3r &Manual"), L('Open the Slic3r manual in your browser'), sub { - Wx::LaunchDefaultBrowser('http://manual.slic3r.org/'); - }); - $helpMenu->AppendSeparator(); - $self->_append_menu_item($helpMenu, L("System Info"), L('Show system information'), sub { - wxTheApp->system_info; - }); - $self->_append_menu_item($helpMenu, L("Show &Configuration Folder"), L('Show user configuration folder (datadir)'), sub { - Slic3r::GUI::desktop_open_datadir_folder(); - }); - $self->_append_menu_item($helpMenu, L("Report an Issue"), L('Report an issue on the Slic3r Prusa Edition'), sub { - Wx::LaunchDefaultBrowser('http://github.com/prusa3d/slic3r/issues/new'); - }); - $self->_append_menu_item($helpMenu, L("&About Slic3r"), L('Show about dialog'), sub { - Slic3r::GUI::about; - }); - } - - # menubar - # assign menubar to frame after appending items, otherwise special items - # will not be handled correctly - { - my $menubar = Wx::MenuBar->new; - $menubar->Append($fileMenu, L("&File")); - $menubar->Append($self->{plater_menu}, L("&Plater")) if $self->{plater_menu}; - $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 additional menus from C++ - Slic3r::GUI::add_menus($menubar, $self->{preferences_event}, $self->{lang_ch_event}); - $menubar->Append($helpMenu, L("&Help")); - $self->SetMenuBar($menubar); - } -} - -sub is_loaded { - my ($self) = @_; - return $self->{loaded}; -} - -# Selection of a 3D object changed on the platter. -sub on_plater_selection_changed { - my ($self, $have_selection) = @_; - return if !defined $self->{object_menu}; - $self->{object_menu}->Enable($_->GetId, $have_selection) - for $self->{object_menu}->GetMenuItems; -} - -sub slice_to_png { - my $self = shift; - $self->{plater}->stop_background_process; - $self->{plater}->async_apply_config; - $appController->print_ctl()->slice_to_png(); -} - -sub reslice_now { - my ($self) = @_; - $self->{plater}->reslice if $self->{plater}; -} - -sub repair_stl { - my $self = shift; - - my $input_file; - { - my $dialog = Wx::FileDialog->new($self, L('Select the STL file to repair:'), - wxTheApp->{app_config}->get_last_dir, "", - &Slic3r::GUI::FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if ($dialog->ShowModal != wxID_OK) { - $dialog->Destroy; - return; - } - $input_file = $dialog->GetPaths; - $dialog->Destroy; - } - - my $output_file = $input_file; - { - $output_file =~ s/\.[sS][tT][lL]$/_fixed.obj/; - my $dlg = Wx::FileDialog->new($self, L("Save OBJ file (less prone to coordinate errors than STL) as:"), dirname($output_file), - basename($output_file), &Slic3r::GUI::FILE_WILDCARDS->{obj}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if ($dlg->ShowModal != wxID_OK) { - $dlg->Destroy; - return undef; - } - $output_file = $dlg->GetPath; - $dlg->Destroy; - } - - my $tmesh = Slic3r::TriangleMesh->new; - $tmesh->ReadSTLFile($input_file); - $tmesh->repair; - $tmesh->WriteOBJFile($output_file); - Slic3r::GUI::show_info($self, L("Your file was repaired."), L("Repair")); -} - -sub export_config { - my $self = shift; - # Generate a cummulative configuration for the selected print, filaments and printer. - my $config = wxTheApp->{preset_bundle}->full_config(); - # Validate the cummulative configuration. - eval { $config->validate; }; - Slic3r::GUI::catch_error($self) and return; - # Ask user for the file name for the config file. - my $dlg = Wx::FileDialog->new($self, L('Save configuration as:'), - $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, - $last_config ? basename($last_config) : "config.ini", - &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - my $file = ($dlg->ShowModal == wxID_OK) ? $dlg->GetPath : undef; - $dlg->Destroy; - if (defined $file) { - wxTheApp->{app_config}->update_config_dir(dirname($file)); - $last_config = $file; - $config->save($file); - } -} - -# Load a config file containing a Print, Filament & Printer preset. -sub load_config_file { - my ($self, $file) = @_; - if (!$file) { - 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", - 'INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g', wxFD_OPEN | wxFD_FILE_MUST_EXIST); - return unless $dlg->ShowModal == wxID_OK; - $file = $dlg->GetPaths; - $dlg->Destroy; - } - eval { wxTheApp->{preset_bundle}->load_config_file($file); }; - # Dont proceed further if the config file cannot be loaded. - return if Slic3r::GUI::catch_error($self); - $_->load_current_preset for (values %{$self->{options_tabs}}); - wxTheApp->{app_config}->update_config_dir(dirname($file)); - $last_config = $file; -} - -sub export_configbundle { - my ($self) = @_; - 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; - # Ask user for a file name. - my $dlg = Wx::FileDialog->new($self, L('Save presets bundle as:'), - $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, - "Slic3r_config_bundle.ini", - &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - my $file = ($dlg->ShowModal == wxID_OK) ? $dlg->GetPath : undef; - $dlg->Destroy; - if (defined $file) { - # Export the config bundle. - wxTheApp->{app_config}->update_config_dir(dirname($file)); - eval { wxTheApp->{preset_bundle}->export_configbundle($file); }; - Slic3r::GUI::catch_error($self) and return; - } -} - -# Loading a config bundle with an external file name used to be used -# to auto-install a config bundle on a fresh user account, -# but that behavior was not documented and likely buggy. -sub load_configbundle { - my ($self, $file, $reset_user_profile) = @_; - 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, - "config.ini", - &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); - return unless $dlg->ShowModal == wxID_OK; - $file = $dlg->GetPaths; - $dlg->Destroy; - } - - wxTheApp->{app_config}->update_config_dir(dirname($file)); - - my $presets_imported = 0; - eval { $presets_imported = wxTheApp->{preset_bundle}->load_configbundle($file); }; - 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; - } - - my $message = sprintf L("%d presets successfully imported."), $presets_imported; - Slic3r::GUI::show_info($self, $message); -} - -# Load a provied DynamicConfig into the Print / Filament / Printer tabs, thus modifying the active preset. -# Also update the platter with the new presets. -sub load_config { - my ($self, $config) = @_; - $_->load_config($config) foreach values %{$self->{options_tabs}}; - $self->{plater}->on_config_change($config) if $self->{plater}; -} - -sub select_tab { - my ($self, $tab) = @_; - $self->{tabpanel}->SetSelection($tab); -} - -# Set a camera direction, zoom to all objects. -sub select_view { - my ($self, $direction) = @_; - if (! $self->{no_plater}) { - $self->{plater}->select_view($direction); - } -} - -sub _append_menu_item { - my ($self, $menu, $string, $description, $cb, $id, $icon) = @_; - $id //= &Wx::NewId(); - my $item = $menu->Append($id, $string, $description); - $self->_set_menu_item_icon($item, $icon); - EVT_MENU($self, $id, $cb); - return $item; -} - -sub _set_menu_item_icon { - my ($self, $menuItem, $icon) = @_; - # SetBitmap was not available on OS X before Wx 0.9927 - if ($icon && $menuItem->can('SetBitmap')) { - $menuItem->SetBitmap(Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG)); - } -} - -# Called after the Preferences dialog is closed and the program settings are saved. -# Update the UI based on the current preferences. -sub update_ui_from_settings { - my ($self) = @_; - $self->{menu_item_reslice_now}->Enable(! wxTheApp->{app_config}->get("background_processing")); - $self->{plater}->update_ui_from_settings if ($self->{plater}); - for my $tab_name (qw(print filament printer)) { - $self->{options_tabs}{$tab_name}->update_ui_from_settings; - } -} - -1; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm deleted file mode 100644 index db360c811b..0000000000 --- a/lib/Slic3r/GUI/Plater.pm +++ /dev/null @@ -1,2683 +0,0 @@ -# The "Plater" tab. It contains the "3D", "2D", "Preview" and "Layers" subtabs. - -package Slic3r::GUI::Plater; -use strict; -use warnings; -use utf8; - -use File::Basename qw(basename dirname); -use List::Util qw(sum first max); -use Slic3r::Geometry qw(X Y Z scale unscale deg2rad rad2deg); -use Wx qw(:button :colour :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc - :panel :sizer :toolbar :window wxTheApp :notebook :combobox wxNullBitmap); -use Wx::Event qw(EVT_BUTTON EVT_TOGGLEBUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED - EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_LEFT_DOWN EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL - EVT_CHOICE EVT_COMBOBOX EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED); -use Slic3r::Geometry qw(PI); -use base 'Wx::Panel'; - -use constant TB_ADD => &Wx::NewId; -use constant TB_REMOVE => &Wx::NewId; -use constant TB_RESET => &Wx::NewId; -use constant TB_ARRANGE => &Wx::NewId; -use constant TB_EXPORT_GCODE => &Wx::NewId; -use constant TB_EXPORT_STL => &Wx::NewId; -use constant TB_MORE => &Wx::NewId; -use constant TB_FEWER => &Wx::NewId; -use constant TB_45CW => &Wx::NewId; -use constant TB_45CCW => &Wx::NewId; -use constant TB_SCALE => &Wx::NewId; -use constant TB_SPLIT => &Wx::NewId; -use constant TB_CUT => &Wx::NewId; -use constant TB_SETTINGS => &Wx::NewId; -use constant TB_LAYER_EDITING => &Wx::NewId; - -use Wx::Locale gettext => 'L'; - -# Emitted from the worker thread when the G-code export is finished. -our $SLICING_COMPLETED_EVENT = Wx::NewEventType; -our $PROCESS_COMPLETED_EVENT = Wx::NewEventType; - -my $PreventListEvents = 0; -our $appController; - -# XXX: VK: done, except callback handling and timer -sub new { - my ($class, $parent, %params) = @_; - my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - Slic3r::GUI::set_plater($self); - $self->{config} = Slic3r::Config::new_from_defaults_keys([qw( - bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height - serial_port serial_speed host_type print_host printhost_apikey printhost_cafile - nozzle_diameter single_extruder_multi_material wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width - wipe_tower_rotation_angle extruder_colour filament_colour max_print_height printer_model - )]); - - # store input params - $self->{event_object_selection_changed} = $params{event_object_selection_changed}; - $self->{event_object_settings_changed} = $params{event_object_settings_changed}; - $self->{event_remove_object} = $params{event_remove_object}; - $self->{event_update_scene} = $params{event_update_scene}; - - # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm - $self->{model} = Slic3r::Model->new; - # C++ Slic3r::Print with Perl extensions in Slic3r/Print.pm - $self->{print} = Slic3r::Print->new; - # List of Perl objects Slic3r::GUI::Plater::Object, representing a 2D preview of the platter. - $self->{objects} = []; - $self->{gcode_preview_data} = Slic3r::GCode::PreviewData->new; - $self->{background_slicing_process} = Slic3r::GUI::BackgroundSlicingProcess->new; - $self->{background_slicing_process}->set_print($self->{print}); - $self->{background_slicing_process}->set_gcode_preview_data($self->{gcode_preview_data}); - $self->{background_slicing_process}->set_sliced_event($SLICING_COMPLETED_EVENT); - $self->{background_slicing_process}->set_finished_event($PROCESS_COMPLETED_EVENT); - - # The C++ slicing core will post a wxCommand message to the main window. - Slic3r::GUI::set_print_callback_event($self->{print}, $Slic3r::GUI::MainFrame::PROGRESS_BAR_EVENT); - - # Initialize preview notebook - $self->{preview_notebook} = Wx::Notebook->new($self, -1, wxDefaultPosition, [-1,335], wxNB_BOTTOM); - - # Initialize handlers for canvases - my $on_select_object = sub { - my ($obj_idx, $vol_idx) = @_; - - if (($obj_idx != -1) && ($vol_idx == -1)) { - # Ignore the special objects (the wipe tower proxy and such). - $self->select_object((defined($obj_idx) && $obj_idx >= 0 && $obj_idx < 1000) ? $obj_idx : undef); - $self->item_changed_selection($obj_idx) if (defined($obj_idx)); - } - }; - my $on_right_click = sub { - my ($canvas, $click_pos_x, $click_pos_y) = @_; - - my ($obj_idx, $object) = $self->selected_object; - return if !defined $obj_idx; - - my $menu = $self->object_menu; - $canvas->PopupMenu($menu, $click_pos_x, $click_pos_y); - $menu->Destroy; - }; - my $on_instances_moved = sub { - $self->update; - }; - - # callback to enable/disable action buttons - my $enable_action_buttons = sub { - my ($enable) = @_; - Slic3r::GUI::enable_action_buttons($enable); -# $self->{btn_export_gcode}->Enable($enable); -# $self->{btn_reslice}->Enable($enable); -# $self->{btn_print}->Enable($enable); -# $self->{btn_send_gcode}->Enable($enable); - }; - - # callback to react to gizmo scale - my $on_gizmo_scale_uniformly = sub { - my ($scale) = @_; - - my ($obj_idx, $object) = $self->selected_object; - return if !defined $obj_idx; - - my $model_object = $self->{model}->objects->[$obj_idx]; - my $model_instance = $model_object->instances->[0]; - - $self->stop_background_process; - - my $variation = $scale / $model_instance->scaling_factor; - #FIXME Scale the layer height profile? - foreach my $range (@{ $model_object->layer_height_ranges }) { - $range->[0] *= $variation; - $range->[1] *= $variation; - } - $_->set_scaling_factor($scale) for @{ $model_object->instances }; - - # Set object scale on c++ side -# Slic3r::GUI::set_object_scale($obj_idx, $model_object->instances->[0]->scaling_factor * 100); - -# $object->transform_thumbnail($self->{model}, $obj_idx); - - #update print and start background processing - $self->{print}->add_model_object($model_object, $obj_idx); - - $self->selection_changed(1); # refresh info (size, volume etc.) - $self->update; - $self->schedule_background_process; - }; - - # callback to react to gizmo scale - my $on_gizmo_scale_3D = sub { - my ($scale_x, $scale_y, $scale_z) = @_; - - my ($obj_idx, $object) = $self->selected_object; - return if !defined $obj_idx; - - my $model_object = $self->{model}->objects->[$obj_idx]; - my $model_instance = $model_object->instances->[0]; - - $self->stop_background_process; - - #FIXME Scale the layer height profile? -# my $variation = $scale / $model_instance->scaling_factor; -# foreach my $range (@{ $model_object->layer_height_ranges }) { -# $range->[0] *= $variation; -# $range->[1] *= $variation; -# } - - my $scale = Slic3r::Pointf3->new($scale_x, $scale_y, $scale_z); - foreach my $inst (@{ $model_object->instances }) { - $inst->set_scaling_factors($scale); - } - Slic3r::GUI::_3DScene::update_gizmos_data($self->{canvas3D}) if ($self->{canvas3D}); - - #update print and start background processing - $self->{print}->add_model_object($model_object, $obj_idx); - - $self->selection_changed(1); # refresh info (size, volume etc.) - $self->update; - $self->schedule_background_process; - - }; - - # callback to react to gizmo rotate - my $on_gizmo_rotate = sub { - my ($angle) = @_; - $self->rotate(rad2deg($angle), Z, 'absolute'); - }; - - # callback to react to gizmo rotate - my $on_gizmo_rotate_3D = sub { - my ($angle_x, $angle_y, $angle_z) = @_; - - my ($obj_idx, $object) = $self->selected_object; - return if !defined $obj_idx; - - my $model_object = $self->{model}->objects->[$obj_idx]; - my $model_instance = $model_object->instances->[0]; - - $self->stop_background_process; - - my $rotation = Slic3r::Pointf3->new($angle_x, $angle_y, $angle_z); - foreach my $inst (@{ $model_object->instances }) { - $inst->set_rotations($rotation); - } - Slic3r::GUI::_3DScene::update_gizmos_data($self->{canvas3D}) if ($self->{canvas3D}); - - # update print and start background processing - $self->{print}->add_model_object($model_object, $obj_idx); - - $self->selection_changed; # refresh info (size etc.) - $self->update; - $self->schedule_background_process; - }; - - # callback to react to gizmo flatten - my $on_gizmo_flatten = sub { - my ($angle, $axis_x, $axis_y, $axis_z) = @_; - $self->rotate(rad2deg($angle), undef, 'absolute', $axis_x, $axis_y, $axis_z) if $angle != 0; - }; - - # callback to react to gizmo flatten - my $on_gizmo_flatten_3D = sub { - my ($angle_x, $angle_y, $angle_z) = @_; - - my ($obj_idx, $object) = $self->selected_object; - return if !defined $obj_idx; - - my $model_object = $self->{model}->objects->[$obj_idx]; - my $model_instance = $model_object->instances->[0]; - - $self->stop_background_process; - - my $rotation = Slic3r::Pointf3->new($angle_x, $angle_y, $angle_z); - foreach my $inst (@{ $model_object->instances }) { - $inst->set_rotations($rotation); - } - Slic3r::GUI::_3DScene::update_gizmos_data($self->{canvas3D}) if ($self->{canvas3D}); - - # update print and start background processing - $self->{print}->add_model_object($model_object, $obj_idx); - - $self->selection_changed; # refresh info (size etc.) - $self->update; - $self->schedule_background_process; - }; - - # callback to update object's geometry info while using gizmos - my $on_update_geometry_info = sub { - my ($size_x, $size_y, $size_z, $scale_factor) = @_; - - my ($obj_idx, $object) = $self->selected_object; - - if ((defined $obj_idx) && ($self->{object_info_size})) { # have we already loaded the info pane? - $self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", $size_x, $size_y, $size_z)); - my $model_object = $self->{model}->objects->[$obj_idx]; - if (my $stats = $model_object->mesh_stats) { - $self->{object_info_volume}->SetLabel(sprintf('%.2f', $stats->{volume} * $scale_factor**3)); - } - } - }; - - # callback to update object's geometry info while using gizmos - my $on_update_geometry_3D_info = sub { - my ($size_x, $size_y, $size_z, $scale_x, $scale_y, $scale_z) = @_; - - my ($obj_idx, $object) = $self->selected_object; - - if ((defined $obj_idx) && ($self->{object_info_size})) { # have we already loaded the info pane? - $self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", $size_x, $size_y, $size_z)); - my $model_object = $self->{model}->objects->[$obj_idx]; - if (my $stats = $model_object->mesh_stats) { - $self->{object_info_volume}->SetLabel(sprintf('%.2f', $stats->{volume} * $scale_x * $scale_y * $scale_z)); - } - } - }; - - # callbacks for toolbar - my $on_action_add = sub { - $self->add; - }; - - my $on_action_delete = sub { - $self->remove(); - }; - - my $on_action_deleteall = sub { - $self->reset; - }; - - my $on_action_arrange = sub { - $self->arrange; - }; - - my $on_action_more = sub { - $self->increase; - }; - - my $on_action_fewer = sub { - $self->decrease; - }; - - my $on_action_split = sub { - $self->split_object; - }; - - my $on_action_cut = sub { - $self->object_cut_dialog; - }; - - my $on_action_settings = sub { - $self->object_settings_dialog; - }; - - my $on_action_layersediting = sub { - my $state = Slic3r::GUI::_3DScene::is_toolbar_item_pressed($self->{canvas3D}, "layersediting"); - $self->on_layer_editing_toggled($state); - }; - - my $on_action_selectbyparts = sub { - my $curr = Slic3r::GUI::_3DScene::get_select_by($self->{canvas3D}); - if ($curr eq 'volume') { - Slic3r::GUI::_3DScene::set_select_by($self->{canvas3D}, 'object'); - my $selections = $self->collect_selections; - Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); - Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1); - } - elsif ($curr eq 'object') { - Slic3r::GUI::_3DScene::set_select_by($self->{canvas3D}, 'volume'); - my $selections = []; - Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); - Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas3D}); - Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1); - - my ($obj_idx, $object) = $self->selected_object; - if (defined $obj_idx) { - my $vol_idx = Slic3r::GUI::_3DScene::get_first_volume_id($self->{canvas3D}, $obj_idx); - #Slic3r::GUI::_3DScene::select_volume($self->{canvas3D}, $vol_idx) if ($vol_idx != -1); - my $inst_cnt = $self->{model}->objects->[$obj_idx]->instances_count; - for (0..$inst_cnt-1){ - Slic3r::GUI::_3DScene::select_volume($self->{canvas3D}, $_ + $vol_idx) if ($vol_idx != -1); - } - - my $volume_idx = Slic3r::GUI::_3DScene::get_in_object_volume_id($self->{canvas3D}, $vol_idx); - Slic3r::GUI::select_current_volume($obj_idx, $volume_idx) if ($volume_idx != -1); - } - } - }; - - # Initialize 3D plater - if ($Slic3r::GUI::have_OpenGL) { - $self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{print}, $self->{config}); - $self->{preview_notebook}->AddPage($self->{canvas3D}, L('3D')); - Slic3r::GUI::_3DScene::register_on_select_object_callback($self->{canvas3D}, $on_select_object); -# Slic3r::GUI::_3DScene::register_on_double_click_callback($self->{canvas3D}, $on_double_click); - Slic3r::GUI::_3DScene::register_on_right_click_callback($self->{canvas3D}, sub { $on_right_click->($self->{canvas3D}, @_); }); - Slic3r::GUI::_3DScene::register_on_arrange_callback($self->{canvas3D}, sub { $self->arrange }); - Slic3r::GUI::_3DScene::register_on_rotate_object_left_callback($self->{canvas3D}, sub { $self->rotate(-45, Z, 'relative') }); - Slic3r::GUI::_3DScene::register_on_rotate_object_right_callback($self->{canvas3D}, sub { $self->rotate( 45, Z, 'relative') }); - Slic3r::GUI::_3DScene::register_on_scale_object_uniformly_callback($self->{canvas3D}, sub { $self->changescale(undef) }); - Slic3r::GUI::_3DScene::register_on_increase_objects_callback($self->{canvas3D}, sub { $self->increase() }); - Slic3r::GUI::_3DScene::register_on_decrease_objects_callback($self->{canvas3D}, sub { $self->decrease() }); - Slic3r::GUI::_3DScene::register_on_remove_object_callback($self->{canvas3D}, sub { $self->remove() }); - Slic3r::GUI::_3DScene::register_on_instance_moved_callback($self->{canvas3D}, $on_instances_moved); - Slic3r::GUI::_3DScene::register_on_enable_action_buttons_callback($self->{canvas3D}, $enable_action_buttons); - Slic3r::GUI::_3DScene::register_on_gizmo_scale_uniformly_callback($self->{canvas3D}, $on_gizmo_scale_uniformly); - Slic3r::GUI::_3DScene::register_on_gizmo_scale_3D_callback($self->{canvas3D}, $on_gizmo_scale_3D); - Slic3r::GUI::_3DScene::register_on_gizmo_rotate_callback($self->{canvas3D}, $on_gizmo_rotate); - Slic3r::GUI::_3DScene::register_on_gizmo_rotate_3D_callback($self->{canvas3D}, $on_gizmo_rotate_3D); - Slic3r::GUI::_3DScene::register_on_gizmo_flatten_callback($self->{canvas3D}, $on_gizmo_flatten); - Slic3r::GUI::_3DScene::register_on_gizmo_flatten_3D_callback($self->{canvas3D}, $on_gizmo_flatten_3D); - Slic3r::GUI::_3DScene::register_on_update_geometry_info_callback($self->{canvas3D}, $on_update_geometry_info); - Slic3r::GUI::_3DScene::register_on_update_geometry_3D_info_callback($self->{canvas3D}, $on_update_geometry_3D_info); - Slic3r::GUI::_3DScene::register_action_add_callback($self->{canvas3D}, $on_action_add); - Slic3r::GUI::_3DScene::register_action_delete_callback($self->{canvas3D}, $on_action_delete); - Slic3r::GUI::_3DScene::register_action_deleteall_callback($self->{canvas3D}, $on_action_deleteall); - Slic3r::GUI::_3DScene::register_action_arrange_callback($self->{canvas3D}, $on_action_arrange); - Slic3r::GUI::_3DScene::register_action_more_callback($self->{canvas3D}, $on_action_more); - Slic3r::GUI::_3DScene::register_action_fewer_callback($self->{canvas3D}, $on_action_fewer); - Slic3r::GUI::_3DScene::register_action_split_callback($self->{canvas3D}, $on_action_split); - Slic3r::GUI::_3DScene::register_action_cut_callback($self->{canvas3D}, $on_action_cut); - Slic3r::GUI::_3DScene::register_action_settings_callback($self->{canvas3D}, $on_action_settings); - Slic3r::GUI::_3DScene::register_action_layersediting_callback($self->{canvas3D}, $on_action_layersediting); - Slic3r::GUI::_3DScene::register_action_selectbyparts_callback($self->{canvas3D}, $on_action_selectbyparts); - Slic3r::GUI::_3DScene::enable_gizmos($self->{canvas3D}, 1); - Slic3r::GUI::_3DScene::enable_toolbar($self->{canvas3D}, 1); - Slic3r::GUI::_3DScene::enable_shader($self->{canvas3D}, 1); - Slic3r::GUI::_3DScene::enable_force_zoom_to_bed($self->{canvas3D}, 1); - - Slic3r::GUI::_3DScene::register_on_wipe_tower_moved_callback($self->{canvas3D}, sub { - my ($x, $y) = @_; - my $cfg = Slic3r::Config->new; - $cfg->set('wipe_tower_x', $x); - $cfg->set('wipe_tower_y', $y); - $self->GetFrame->{options_tabs}{print}->load_config($cfg); - }); - - Slic3r::GUI::_3DScene::register_on_model_update_callback($self->{canvas3D}, sub { - if (wxTheApp->{app_config}->get("background_processing")) { - $self->schedule_background_process; - } else { - # Hide the print info box, it is no more valid. - $self->print_info_box_show(0); - } - }); - - Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{canvas3D}, - sub { - $self->{preview_iface}->set_viewport_from_scene($self->{canvas3D}); - }); -# Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{canvas3D}, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{preview3D}->canvas, $self->{canvas3D}); }); - } - - Slic3r::GUI::register_on_request_update_callback(sub { $self->schedule_background_process; }); - - # Initialize 3D toolpaths preview - if ($Slic3r::GUI::have_OpenGL) { - $self->{preview_iface} = Slic3r::GUI::create_preview_iface($self->{preview_notebook}, $self->{config}, $self->{print}, $self->{gcode_preview_data}); - $self->{preview_page_idx} = $self->{preview_notebook}->GetPageCount-1; - $self->{preview_iface}->register_on_viewport_changed_callback(sub { $self->{preview_iface}->set_viewport_into_scene($self->{canvas3D}); }); -# $self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print}, $self->{gcode_preview_data}, $self->{config}); -# Slic3r::GUI::_3DScene::enable_legend_texture($self->{preview3D}->canvas, 1); -# Slic3r::GUI::_3DScene::enable_dynamic_background($self->{preview3D}->canvas, 1); -# Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{preview3D}->canvas, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{canvas3D}, $self->{preview3D}->canvas); }); -# $self->{preview_notebook}->AddPage($self->{preview3D}, L('Preview')); - $self->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1; - } - - EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{preview_notebook}, sub { - my $preview = $self->{preview_notebook}->GetCurrentPage; - my $page_id = $self->{preview_notebook}->GetSelection; - if (($preview != $self->{canvas3D}) && ($page_id != $self->{preview_page_idx})) { -# if (($preview != $self->{preview3D}) && ($preview != $self->{canvas3D})) { - $preview->OnActivate if $preview->can('OnActivate'); - } elsif ($page_id == $self->{preview_page_idx}) { - $self->{preview_iface}->reload_print; - # sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) - $self->{preview_iface}->set_canvas_as_dirty; -# } elsif ($preview == $self->{preview3D}) { -# $self->{preview3D}->reload_print; -# # sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) -# Slic3r::GUI::_3DScene::set_as_dirty($self->{preview3D}->canvas); - } elsif ($preview == $self->{canvas3D}) { - if (Slic3r::GUI::_3DScene::is_reload_delayed($self->{canvas3D})) { - my $selections = $self->collect_selections; - Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); - Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1); - } - # sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) - Slic3r::GUI::_3DScene::set_as_dirty($self->{canvas3D}); - } - }); - -# # toolbar for object manipulation -# if (!&Wx::wxMSW) { -# Wx::ToolTip::Enable(1); -# $self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL); -# $self->{htoolbar}->AddTool(TB_ADD, L("Add…"), Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG), ''); -# $self->{htoolbar}->AddTool(TB_REMOVE, L("Delete"), Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG), ''); -# $self->{htoolbar}->AddTool(TB_RESET, L("Delete All"), Wx::Bitmap->new(Slic3r::var("cross.png"), wxBITMAP_TYPE_PNG), ''); -# $self->{htoolbar}->AddTool(TB_ARRANGE, L("Arrange"), Wx::Bitmap->new(Slic3r::var("bricks.png"), wxBITMAP_TYPE_PNG), ''); -# $self->{htoolbar}->AddSeparator; -# $self->{htoolbar}->AddTool(TB_MORE, L("More"), Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), ''); -# $self->{htoolbar}->AddTool(TB_FEWER, L("Fewer"), Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG), ''); -# $self->{htoolbar}->AddSeparator; -# $self->{htoolbar}->AddTool(TB_45CCW, L("45° ccw"), Wx::Bitmap->new(Slic3r::var("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), ''); -# $self->{htoolbar}->AddTool(TB_45CW, L("45° cw"), Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), ''); -# $self->{htoolbar}->AddTool(TB_SCALE, L("Scale…"), Wx::Bitmap->new(Slic3r::var("arrow_out.png"), wxBITMAP_TYPE_PNG), ''); -# $self->{htoolbar}->AddTool(TB_SPLIT, L("Split"), Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG), ''); -# $self->{htoolbar}->AddTool(TB_CUT, L("Cut…"), Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG), ''); -# $self->{htoolbar}->AddSeparator; -# $self->{htoolbar}->AddTool(TB_SETTINGS, L("Settings…"), Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG), ''); -# $self->{htoolbar}->AddTool(TB_LAYER_EDITING, L('Layer Editing'), Wx::Bitmap->new(Slic3r::var("variable_layer_height.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, 0, 'Layer Editing'); -# } else { -# my %tbar_buttons = ( -# add => L("Add…"), -# remove => L("Delete"), -# reset => L("Delete All"), -# arrange => L("Arrange"), -# increase => "", -# decrease => "", -# rotate45ccw => "", -# rotate45cw => "", -# changescale => L("Scale…"), -# split => L("Split"), -# cut => L("Cut…"), -# settings => L("Settings…"), -# layer_editing => L("Layer editing"), -# ); -# $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); -# for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) { -# $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); -# $self->{btoolbar}->Add($self->{"btn_$_"}); -# } -# $self->{"btn_layer_editing"} = Wx::ToggleButton->new($self, -1, $tbar_buttons{'layer_editing'}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); -# $self->{btoolbar}->Add($self->{"btn_layer_editing"}); -# } - - ### Panel for right column - $self->{right_panel} = Wx::Panel->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); -# $self->{right_panel} = Wx::ScrolledWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); -# $self->{right_panel}->SetScrollbars(0, 1, 1, 1); - - ### Scrolled Window for panel without "Export G-code" and "Slice now" buttons - my $scrolled_window_sizer = $self->{scrolled_window_sizer} = Wx::BoxSizer->new(wxVERTICAL); - $scrolled_window_sizer->SetMinSize([320, -1]); - my $scrolled_window_panel = $self->{scrolled_window_panel} = Wx::ScrolledWindow->new($self->{right_panel}, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - $scrolled_window_panel->SetSizer($scrolled_window_sizer); - $scrolled_window_panel->SetScrollbars(0, 1, 1, 1); - - # right pane buttons - $self->{btn_export_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Export G-code…"), wxDefaultPosition, [-1, 30],);# wxNO_BORDER);#, wxBU_LEFT); - $self->{btn_reslice} = Wx::Button->new($self->{right_panel}, -1, L("Slice now"), wxDefaultPosition, [-1, 30]);#, wxNO_BORDER);#, wxBU_LEFT); -# $self->{btn_print} = Wx::Button->new($self->{right_panel}, -1, L("Print…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); -# $self->{btn_send_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Send to printer"), wxDefaultPosition, [-1, 30], wxBU_LEFT); - $self->{btn_print} = Wx::Button->new($scrolled_window_panel, -1, L("Print…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); - $self->{btn_send_gcode} = Wx::Button->new($scrolled_window_panel, -1, L("Send to printer"), wxDefaultPosition, [-1, 30], wxBU_LEFT); - #$self->{btn_export_stl} = Wx::Button->new($self->{right_panel}, -1, L("Export STL…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); - #$self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font); - #$self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font); - $self->{btn_print}->Hide; - $self->{btn_send_gcode}->Hide; - -# export_gcode cog_go.png -#! reslice reslice.png - my %icons = qw( - print arrow_up.png - send_gcode arrow_up.png - export_stl brick_go.png - ); - for (grep $self->{"btn_$_"}, keys %icons) { - $self->{"btn_$_"}->SetBitmap(Wx::Bitmap->new(Slic3r::var($icons{$_}), wxBITMAP_TYPE_PNG)); - } - $self->selection_changed(0); - $self->object_list_changed; - EVT_BUTTON($self, $self->{btn_export_gcode}, sub { - $self->export_gcode; - }); - EVT_BUTTON($self, $self->{btn_print}, sub { - $self->{print_file} = $self->export_gcode(Wx::StandardPaths::Get->GetTempDir()); - }); - EVT_BUTTON($self, $self->{btn_send_gcode}, sub { - $self->{send_gcode_file} = $self->export_gcode(Wx::StandardPaths::Get->GetTempDir()); - }); - EVT_BUTTON($self, $self->{btn_reslice}, \&reslice); - EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl); - -# if ($self->{htoolbar}) { -# EVT_TOOL($self, TB_ADD, sub { $self->add; }); -# EVT_TOOL($self, TB_REMOVE, sub { $self->remove() }); # explicitly pass no argument to remove -# EVT_TOOL($self, TB_RESET, sub { $self->reset; }); -# EVT_TOOL($self, TB_ARRANGE, sub { $self->arrange; }); -# EVT_TOOL($self, TB_MORE, sub { $self->increase; }); -# EVT_TOOL($self, TB_FEWER, sub { $self->decrease; }); -# EVT_TOOL($self, TB_45CW, sub { $_[0]->rotate(-45, Z, 'relative') }); -# EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45, Z, 'relative') }); -# EVT_TOOL($self, TB_SCALE, sub { $self->changescale(undef); }); -# EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; }); -# EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog }); -# EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog }); -# EVT_TOOL($self, TB_LAYER_EDITING, sub { -# my $state = Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D}); -# $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, ! $state); -# $self->on_layer_editing_toggled(! $state); -# }); -# } else { -# EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; }); -# EVT_BUTTON($self, $self->{btn_remove}, sub { $self->remove() }); # explicitly pass no argument to remove -# EVT_BUTTON($self, $self->{btn_remove}, sub { Slic3r::GUI::remove_obj() }); # explicitly pass no argument to remove -# EVT_BUTTON($self, $self->{btn_reset}, sub { $self->reset; }); -# EVT_BUTTON($self, $self->{btn_arrange}, sub { $self->arrange; }); -# EVT_BUTTON($self, $self->{btn_increase}, sub { $self->increase; }); -# EVT_BUTTON($self, $self->{btn_decrease}, sub { $self->decrease; }); -# EVT_BUTTON($self, $self->{btn_rotate45cw}, sub { $_[0]->rotate(-45, Z, 'relative') }); -# EVT_BUTTON($self, $self->{btn_rotate45ccw}, sub { $_[0]->rotate(45, Z, 'relative') }); -# EVT_BUTTON($self, $self->{btn_changescale}, sub { $self->changescale(undef); }); -# EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; }); -# EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog }); -# EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog }); -# EVT_TOGGLEBUTTON($self, $self->{btn_layer_editing}, sub { $self->on_layer_editing_toggled($self->{btn_layer_editing}->GetValue); }); -# } - - $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self)) - for grep defined($_), - $self, $self->{canvas3D}, $self->{preview_iface}, $self->{list}; -# $self, $self->{canvas3D}, $self->{preview3D}, $self->{list}; -# $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}; - - EVT_COMMAND($self, -1, $SLICING_COMPLETED_EVENT, sub { - my ($self, $event) = @_; - $self->on_update_print_preview; - }); - - EVT_COMMAND($self, -1, $PROCESS_COMPLETED_EVENT, sub { - my ($self, $event) = @_; - $self->on_process_completed($event->GetInt ? undef : $event->GetString); - }); - -# XXX: not done - { - my $timer_id = Wx::NewId(); - $self->{apply_config_timer} = Wx::Timer->new($self, $timer_id); - EVT_TIMER($self, $timer_id, sub { - my ($self, $event) = @_; - $self->async_apply_config; - }); - } - -# $self->{canvas}->update_bed_size; - if ($self->{canvas3D}) { - Slic3r::GUI::_3DScene::set_bed_shape($self->{canvas3D}, $self->{config}->bed_shape); - Slic3r::GUI::_3DScene::zoom_to_bed($self->{canvas3D}); - } - $self->{preview_iface}->set_bed_shape($self->{config}->bed_shape) if ($self->{preview_iface}); -# if ($self->{preview3D}) { -# Slic3r::GUI::_3DScene::set_bed_shape($self->{preview3D}->canvas, $self->{config}->bed_shape); -# } - $self->update; - - { - my $presets; - { - $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(4, 2, 1, 2); - $presets->AddGrowableCol(1, 1); - $presets->SetFlexibleDirection(wxHORIZONTAL); - my %group_labels = ( - print => L('Print settings'), - filament => L('Filament'), - sla_material=> L('SLA material'), - printer => L('Printer'), - ); - # UI Combo boxes for a print, multiple filaments, SLA material and a printer. - # Initially a single filament combo box is created, but the number of combo boxes for the filament selection may increase, - # once a printer preset with multiple extruders is activated. - # $self->{preset_choosers}{$group}[$idx] - $self->{preset_choosers} = {}; - for my $group (qw(print filament sla_material printer)) { -# my $text = Wx::StaticText->new($self->{right_panel}, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); - my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); - $text->SetFont($Slic3r::GUI::small_font); -# my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); - my $choice = Wx::BitmapComboBox->new($scrolled_window_panel, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); - if ($group eq 'filament') { - EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down(0, @_); } ); - } - $self->{preset_choosers}{$group} = [$choice]; - # setup the listener - EVT_COMBOBOX($choice, $choice, sub { - my ($choice) = @_; - wxTheApp->CallAfter(sub { - $self->_on_select_preset($group, $choice, 0); - }); - }); - $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); - $presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 1); - } - $presets->Layout; - } - - my $frequently_changed_parameters_sizer = $self->{frequently_changed_parameters_sizer} = Wx::BoxSizer->new(wxVERTICAL); -#! Slic3r::GUI::add_frequently_changed_parameters($self->{right_panel}, $frequently_changed_parameters_sizer, $presets); - Slic3r::GUI::add_frequently_changed_parameters($self->{scrolled_window_panel}, $frequently_changed_parameters_sizer, $presets); - - my $object_info_sizer; - { - my $box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Info")); -# my $box = Wx::StaticBox->new($self->{right_panel}, -1, L("Info")); - $box->SetFont($Slic3r::GUI::small_bold_font); - $object_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); - $object_info_sizer->SetMinSize([300,-1]); - #!my $grid_sizer = Wx::FlexGridSizer->new(3, 4, 5, 5); - my $grid_sizer = Wx::FlexGridSizer->new(2, 4, 5, 5); - $grid_sizer->SetFlexibleDirection(wxHORIZONTAL); - $grid_sizer->AddGrowableCol(1, 1); - $grid_sizer->AddGrowableCol(3, 1); - $object_info_sizer->Add($grid_sizer, 0, wxEXPAND); - - my @info = ( - size => L("Size"), - volume => L("Volume"), - facets => L("Facets"), - materials => L("Materials"), - manifold => L("Manifold"), - ); - while (my $field = shift @info) { - my $label = shift @info; - my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); -# my $text = Wx::StaticText->new($self->{right_panel}, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - $text->SetFont($Slic3r::GUI::small_font); - #!$grid_sizer->Add($text, 0); - - $self->{"object_info_$field"} = Wx::StaticText->new($scrolled_window_panel, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); -# $self->{"object_info_$field"} = Wx::StaticText->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - $self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font); - if ($field eq 'manifold') { - $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($scrolled_window_panel, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); -# $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self->{right_panel}, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); - #$self->{object_info_manifold_warning_icon}->Hide; - $self->{"object_info_manifold_warning_icon_show"} = sub { - if ($self->{object_info_manifold_warning_icon}->IsShown() != $_[0]) { - # this fuction show/hide info_manifold_warning_icon on the c++ side now - Slic3r::GUI::set_show_manifold_warning_icon($_[0]); - #my $mode = wxTheApp->{app_config}->get("view_mode"); - #return if ($mode eq "" || $mode eq "simple"); - #$self->{object_info_manifold_warning_icon}->Show($_[0]); - #$self->Layout - } - }; - $self->{"object_info_manifold_warning_icon_show"}->(0); - - my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL); - $h_sizer->Add($text, 0); - $h_sizer->Add($self->{object_info_manifold_warning_icon}, 0, wxLEFT, 2); - $h_sizer->Add($self->{"object_info_$field"}, 0, wxLEFT, 2); - #!$grid_sizer->Add($h_sizer, 0, wxEXPAND); - $object_info_sizer->Add($h_sizer, 0, wxEXPAND|wxTOP, 4); - } else { - $grid_sizer->Add($text, 0); - $grid_sizer->Add($self->{"object_info_$field"}, 0); - } - } - } - - my $print_info_box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Sliced Info")); - $print_info_box->SetFont($Slic3r::GUI::small_bold_font); - my $print_info_sizer = $self->{print_info_sizer} = Wx::StaticBoxSizer->new($print_info_box, wxVERTICAL); -# Wx::StaticBox->new($self->{right_panel}, -1, L("Sliced Info")), wxVERTICAL); - $print_info_sizer->SetMinSize([300,-1]); - - my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL); - $self->{buttons_sizer} = $buttons_sizer; - $buttons_sizer->AddStretchSpacer(1); -# $buttons_sizer->Add($self->{btn_export_stl}, 0, wxALIGN_RIGHT, 0); -#! $buttons_sizer->Add($self->{btn_reslice}, 0, wxALIGN_RIGHT, 0); - $buttons_sizer->Add($self->{btn_print}, 0, wxALIGN_RIGHT | wxBOTTOM | wxTOP, 5); - $buttons_sizer->Add($self->{btn_send_gcode}, 0, wxALIGN_RIGHT | wxBOTTOM | wxTOP, 5); - -# $scrolled_window_sizer->Add($self->{list}, 1, wxEXPAND, 5); -# $scrolled_window_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); -# $scrolled_window_sizer->Add($print_info_sizer, 0, wxEXPAND, 0); - #$buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0); - - ### Sizer for info boxes - my $info_sizer = $self->{info_sizer} = Wx::BoxSizer->new(wxVERTICAL); - $info_sizer->SetMinSize([318, -1]); - $info_sizer->Add($object_info_sizer, 0, wxEXPAND | wxTOP, 20); - $info_sizer->Add($print_info_sizer, 0, wxEXPAND | wxTOP, 20); - - $scrolled_window_sizer->Add($presets, 0, wxEXPAND | wxLEFT, 2) if defined $presets; - $scrolled_window_sizer->Add($frequently_changed_parameters_sizer, 1, wxEXPAND | wxLEFT, 0) if defined $frequently_changed_parameters_sizer; - $scrolled_window_sizer->Add($buttons_sizer, 0, wxEXPAND, 0); - $scrolled_window_sizer->Add($info_sizer, 0, wxEXPAND | wxLEFT, 20); - # Show the box initially, let it be shown after the slicing is finished. - $self->print_info_box_show(0); - - ### Sizer for "Export G-code" & "Slice now" buttons - my $btns_sizer = Wx::BoxSizer->new(wxVERTICAL); - $btns_sizer->SetMinSize([318, -1]); - $btns_sizer->Add($self->{btn_reslice}, 0, wxEXPAND, 0); - $btns_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxTOP, 5); - - my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); - $self->{right_panel}->SetSizer($right_sizer); - $right_sizer->SetMinSize([320, -1]); -#! $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; -#! $right_sizer->Add($frequently_changed_parameters_sizer, 1, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; -#! $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM | wxTOP, 10); -#! $right_sizer->Add($info_sizer, 0, wxEXPAND | wxLEFT, 20); - # Show the box initially, let it be shown after the slicing is finished. -#! $self->print_info_box_show(0); - $right_sizer->Add($scrolled_window_panel, 1, wxEXPAND | wxTOP, 5); -# $right_sizer->Add($self->{btn_reslice}, 0, wxEXPAND | wxLEFT | wxTOP, 20); -# $right_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxLEFT | wxTOP, 20); - $right_sizer->Add($btns_sizer, 0, wxEXPAND | wxLEFT | wxTOP, 20); - - my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); - $hsizer->Add($self->{preview_notebook}, 1, wxEXPAND | wxTOP, 1); - $hsizer->Add($self->{right_panel}, 0, wxEXPAND | wxLEFT | wxRIGHT, 0);#3); - - my $sizer = Wx::BoxSizer->new(wxVERTICAL); -# $sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar}; -# $sizer->Add($self->{btoolbar}, 0, wxEXPAND, 0) if $self->{btoolbar}; - $sizer->Add($hsizer, 1, wxEXPAND, 0); - - $sizer->SetSizeHints($self); - $self->SetSizer($sizer); - - # Send sizers/buttons to C++ - Slic3r::GUI::set_objects_from_perl( $self->{scrolled_window_panel}, - $frequently_changed_parameters_sizer, - $info_sizer, - $self->{btn_export_gcode}, - # $self->{btn_export_stl}, - $self->{btn_reslice}, - $self->{btn_print}, - $self->{btn_send_gcode}, - $self->{object_info_manifold_warning_icon} ); - - Slic3r::GUI::set_model_events_from_perl( $self->{model}, - $self->{event_object_selection_changed}, - $self->{event_object_settings_changed}, - $self->{event_remove_object}, - $self->{event_update_scene}); - } - - # 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(); - $self->Layout; - - return $self; -} - -# XXX: VK: WIP -# sets the callback -sub on_select_preset { - my ($self, $cb) = @_; - $self->{on_select_preset} = $cb; -} - -# XXX: merged with on_select_preset -# Called from the platter combo boxes selecting the active print, filament or printer. -sub _on_select_preset { - my ($self, $group, $choice, $idx) = @_; - # If user changed a filament preset and the selected machine is equipped with multiple extruders, - # there are multiple filament selection combo boxes shown at the platter. In that case - # don't propagate the filament selection changes to the tab. - if ($group eq 'filament') { - wxTheApp->{preset_bundle}->set_filament_preset($idx, $choice->GetStringSelection); - } - if ($group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1) { - # 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"} && - !Slic3r::GUI::get_preset_tab($group)->current_preset_is_dirty); - - my $selected_string = $choice->GetString($selected_item); - if ($selected_string eq ("------- ".L("System presets")." -------") || - $selected_string eq ("------- ".L("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) - $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}); - # get new config and generate on_config_change() event for updating plater and other things - $self->on_config_change(wxTheApp->{preset_bundle}->full_config); -} - -# XXX: VK: done -sub on_layer_editing_toggled { - my ($self, $new_state) = @_; - Slic3r::GUI::_3DScene::enable_layers_editing($self->{canvas3D}, $new_state); - if ($new_state && ! Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D})) { - # Initialization of the OpenGL shaders failed. Disable the tool. -# if ($self->{htoolbar}) { -# $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0); -# $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, 0); -# } else { -# $self->{"btn_layer_editing"}->Disable; -# $self->{"btn_layer_editing"}->SetValue(0); -# } - Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 0); - } - $self->{canvas3D}->Refresh; - $self->{canvas3D}->Update; -} - -# XXX: VK: done (Plater::priv::main_frame) -sub GetFrame { - my ($self) = @_; - return &Wx::GetTopLevelParent($self); -} - -# XXX: not done -# Called after the Preferences dialog is closed and the program settings are saved. -# Update the UI based on the current preferences. -sub update_ui_from_settings -{ - my ($self) = @_; - if (defined($self->{btn_reslice}) && $self->{buttons_sizer}->IsShown($self->{btn_reslice}) != (! wxTheApp->{app_config}->get("background_processing"))) { - $self->{buttons_sizer}->Show($self->{btn_reslice}, ! wxTheApp->{app_config}->get("background_processing")); - $self->{buttons_sizer}->Layout; - } -} - -# XXX: VK: done -# Update preset combo boxes (Print settings, Filament, Material, Printer) from their respective tabs. -# Called by -# Slic3r::GUI::Tab::Print::_on_presets_changed -# Slic3r::GUI::Tab::Filament::_on_presets_changed -# Slic3r::GUI::Tab::Material::_on_presets_changed -# Slic3r::GUI::Tab::Printer::_on_presets_changed -# when the presets are loaded or the user selects another preset. -# For Print settings and Printer, synchronize the selection index with their tabs. -# For Filament, synchronize the selection index for a single extruder printer only, otherwise keep the selection. -sub update_presets { - # $group: one of qw(print filament sla_material printer) - # $presets: PresetCollection - my ($self, $group, $presets) = @_; - my @choosers = @{$self->{preset_choosers}{$group}}; - if ($group eq 'filament') { - my $choice_idx = 0; - if (int(@choosers) == 1) { - # Single filament printer, synchronize the filament presets. - wxTheApp->{preset_bundle}->set_filament_preset(0, wxTheApp->{preset_bundle}->filament->get_selected_preset->name); - } - foreach my $choice (@choosers) { - wxTheApp->{preset_bundle}->update_platter_filament_ui($choice_idx, $choice); - $choice_idx += 1; - } - } elsif ($group eq 'print') { - wxTheApp->{preset_bundle}->print->update_platter_ui($choosers[0]); - } elsif ($group eq 'sla_material') { - wxTheApp->{preset_bundle}->sla_material->update_platter_ui($choosers[0]); - } elsif ($group eq 'printer') { - # Update the print choosers to only contain the compatible presets, update the dirty flags. - wxTheApp->{preset_bundle}->print->update_platter_ui($self->{preset_choosers}{print}->[0]); - # Update the printer choosers, update the dirty flags. - wxTheApp->{preset_bundle}->printer->update_platter_ui($choosers[0]); - # Update the filament choosers to only contain the compatible presets, update the color preview, - # update the dirty flags. - my $choice_idx = 0; - foreach my $choice (@{$self->{preset_choosers}{filament}}) { - wxTheApp->{preset_bundle}->update_platter_filament_ui($choice_idx, $choice); - $choice_idx += 1; - } - } - # Synchronize config.ini with the current selections. - wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config}); -} - -# XXX: VK: done, in on_action_add() -sub add { - my ($self) = @_; - my @input_files = wxTheApp->open_model($self); - $self->load_files(\@input_files); -} - -# XXX: VK: done -sub load_files { - my ($self, $input_files) = @_; - - return if ! defined($input_files) || ! scalar(@$input_files); - - my $nozzle_dmrs = $self->{config}->get('nozzle_diameter'); - # One of the files is potentionally a bundle of files. Don't bundle them, but load them one by one. - # Only bundle .stls or .objs if the printer has multiple extruders. - my $one_by_one = (@$nozzle_dmrs <= 1) || (@$input_files == 1) || - defined(first { $_ =~ /.[aA][mM][fF]$/ || $_ =~ /.[aA][mM][fF].[xX][mM][lL]$/ || $_ =~ /.[zZ][iI][pP].[aA][mM][fF]$/ || $_ =~ /.3[mM][fF]$/ || $_ =~ /.[pP][rR][uI][sS][aA]$/ } @$input_files); - - my $process_dialog = Wx::ProgressDialog->new(L('Loading…'), L("Processing input file\n") . basename($input_files->[0]), 100, $self, 0); - $process_dialog->Pulse; - local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); - - # new_model to collect volumes, if all of them come from .stl or .obj and there is a chance, that they will be - # possibly merged into a single multi-part object. - my $new_model = $one_by_one ? undef : Slic3r::Model->new; - # Object indices for the UI. - my @obj_idx = (); - # Collected file names to display the final message on the status bar. - my @loaded_files = (); # XXX: used??? - # For all input files. - for (my $i = 0; $i < @$input_files; $i += 1) { - my $input_file = $input_files->[$i]; - $process_dialog->Update(100. * $i / @$input_files, L("Processing input file\n") . basename($input_file)); - - my $model; - if (($input_file =~ /.3[mM][fF]$/) || ($input_file =~ /.[zZ][iI][pP].[aA][mM][fF]$/)) - { - $model = eval { Slic3r::Model->read_from_archive($input_file, wxTheApp->{preset_bundle}, 0) }; - Slic3r::GUI::show_error($self, $@) if $@; - $_->load_current_preset for (values %{$self->GetFrame->{options_tabs}}); - wxTheApp->{app_config}->update_config_dir(dirname($input_file)); - # forces the update of the config here, or it will invalidate the imported layer heights profile if done using the timer - # and if the config contains a "layer_height" different from the current defined one - $self->async_apply_config; - } - else - { - $model = eval { Slic3r::Model->read_from_file($input_file, 0) }; - Slic3r::GUI::show_error($self, $@) if $@; - } - - next if ! defined $model; - - if ($model->looks_like_multipart_object) { - my $dialog = Wx::MessageDialog->new($self, - L("This file contains several objects positioned at multiple heights. " - . "Instead of considering them as multiple objects, should I consider\n" - . "this file as a single object having multiple parts?\n"), - L('Multi-part object detected'), wxICON_WARNING | wxYES | wxNO); - $model->convert_multipart_object(scalar(@$nozzle_dmrs)) if $dialog->ShowModal() == wxID_YES; - } - - # objects imported from 3mf require a call to center_around_origin to have gizmos working properly and this call - # need to be done after looks_like_multipart_object detection - if ($input_file =~ /[.]3[mM][fF]$/) - { - foreach my $model_object (@{$model->objects}) { - $model_object->center_around_origin; # also aligns object to Z = 0 - } - } - - if ($one_by_one) { - push @obj_idx, $self->load_model_objects(@{$model->objects}); - } else { - # This must be an .stl or .obj file, which may contain a maximum of one volume. - $new_model->add_object($_) for (@{$model->objects}); - } - } - - if ($new_model) { - my $dialog = Wx::MessageDialog->new($self, - L("Multiple objects were loaded for a multi-material printer.\n" - . "Instead of considering them as multiple objects, should I consider\n" - . "these files to represent a single object having multiple parts?\n"), - L('Multi-part object detected'), wxICON_WARNING | wxYES | wxNO); - $new_model->convert_multipart_object(scalar(@$nozzle_dmrs)) if $dialog->ShowModal() == wxID_YES; - push @obj_idx, $self->load_model_objects(@{$new_model->objects}); - } - - # Note the current directory for the file open dialog. - wxTheApp->{app_config}->update_skein_dir(dirname($input_files->[-1])); - - $process_dialog->Destroy; - $self->statusbar->SetStatusText(L("Loaded ") . join(',', @loaded_files)); - return @obj_idx; -} - -# XXX: VK: done, except a few todos -sub load_model_objects { - my ($self, @model_objects) = @_; - - my $bed_centerf = $self->bed_centerf; - my $bed_shape = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape}); - my $bed_size = $bed_shape->bounding_box->size; - - my $need_arrange = 0; - my $scaled_down = 0; - my @obj_idx = (); - foreach my $model_object (@model_objects) { - my $o = $self->{model}->add_object($model_object); - my $object_name = $model_object->name; - $object_name = basename($model_object->input_file) if ($object_name eq ''); - push @{ $self->{objects} }, Slic3r::GUI::Plater::Object->new(name => $object_name); - push @obj_idx, $#{ $self->{objects} }; - - if ($model_object->instances_count == 0) { - # if object has no defined position(s) we need to rearrange everything after loading - $need_arrange = 1; - - # add a default instance and center object around origin - $o->center_around_origin; # also aligns object to Z = 0 - $o->add_instance(offset => $bed_centerf); - } - - { - # if the object is too large (more than 5 times the bed), scale it down - my $size = $o->bounding_box->size; - my $ratio = max($size->x / unscale($bed_size->x), $size->y / unscale($bed_size->y)); - if ($ratio > 10000) { - # the size of the object is too big -> this could lead to overflow when moving to clipper coordinates, - # so scale down the mesh - $o->scale_xyz(Slic3r::Pointf3->new(1/$ratio, 1/$ratio, 1/$ratio)); - $scaled_down = 1; - } - elsif ($ratio > 5) { - $_->set_scaling_factor(1/$ratio) for @{$o->instances}; - $scaled_down = 1; - } - } - - $self->{print}->auto_assign_extruders($o); - $self->{print}->add_model_object($o); - } - - # if user turned autocentering off, automatic arranging would disappoint them - if (! wxTheApp->{app_config}->get("autocenter")) { - $need_arrange = 0; - } - - if ($scaled_down) { - Slic3r::GUI::show_info( - $self, - L('Your object appears to be too large, so it was automatically scaled down to fit your print bed.'), - L('Object too large?'), - ); - } - - foreach my $obj_idx (@obj_idx) { - my $object = $self->{objects}[$obj_idx]; - my $model_object = $self->{model}->objects->[$obj_idx]; - - # Add object to list on c++ side - Slic3r::GUI::add_object_to_list($object->name, $model_object); - - -# $self->reset_thumbnail($obj_idx); - } - $self->arrange if $need_arrange; - $self->update; - - # zoom to objects - Slic3r::GUI::_3DScene::zoom_to_volumes($self->{canvas3D}) if $self->{canvas3D}; - - $self->object_list_changed; - - $self->schedule_background_process; - - return @obj_idx; -} - -# XXX: Removed, replaced with bed_shape_bb() -sub bed_centerf { - my ($self) = @_; - - my $bed_shape = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape}); - my $bed_center = $bed_shape->bounding_box->center; - return Slic3r::Pointf->new(unscale($bed_center->x), unscale($bed_center->y)); #) -} - -# XXX: VK: done -sub remove { - my ($self, $obj_idx) = @_; - - $self->stop_background_process; - - # Prevent toolpaths preview from rendering while we modify the Print object - $self->{preview_iface}->set_enabled(0) if $self->{preview_iface}; -# $self->{preview3D}->enabled(0) if $self->{preview3D}; - - # If no object index is supplied, remove the selected one. - if (! defined $obj_idx) { - ($obj_idx, undef) = $self->selected_object; - return if ! defined $obj_idx; - } - - splice @{$self->{objects}}, $obj_idx, 1; - $self->{model}->delete_object($obj_idx); - $self->{print}->delete_object($obj_idx); - # Delete object from list on c++ side - Slic3r::GUI::delete_object_from_list(); - $self->object_list_changed; - - $self->select_object(undef); - $self->update; -} - -# XXX: VK: done -sub reset { - my ($self) = @_; - - $self->stop_background_process; - - # Prevent toolpaths preview from rendering while we modify the Print object - $self->{preview_iface}->set_enabled(0) if $self->{preview_iface}; -# $self->{preview3D}->enabled(0) if $self->{preview3D}; - - @{$self->{objects}} = (); - $self->{model}->clear_objects; - $self->{print}->clear_objects; - # Delete all objects from list on c++ side - Slic3r::GUI::delete_all_objects_from_list(); - $self->object_list_changed; - - $self->select_object(undef); - $self->update; -} - -# XXX: VK: done -sub increase { - my ($self, $copies) = @_; - $copies //= 1; - my ($obj_idx, $object) = $self->selected_object; - return if ! defined $obj_idx; - my $model_object = $self->{model}->objects->[$obj_idx]; - my $instance = $model_object->instances->[-1]; - $self->stop_background_process; - for my $i (1..$copies) { - $instance = $model_object->add_instance( - offset => Slic3r::Pointf->new(map 10+$_, @{$instance->offset}), - scaling_factor => $instance->scaling_factor, - rotation => $instance->rotation, - ); - $self->{print}->objects->[$obj_idx]->add_copy($instance->offset); - } - # Set count of object on c++ side - Slic3r::GUI::set_object_count($obj_idx, $model_object->instances_count); - - # only autoarrange if user has autocentering enabled - $self->stop_background_process; - if (wxTheApp->{app_config}->get("autocenter")) { - $self->arrange; - } else { - $self->update; - } - - $self->selection_changed; # refresh info (size, volume etc.) - $self->schedule_background_process; -} - -# XXX: VK: done -sub decrease { - my ($self, $copies_asked) = @_; - my $copies = $copies_asked // 1; - my ($obj_idx, $object) = $self->selected_object; - return if ! defined $obj_idx; - - my $model_object = $self->{model}->objects->[$obj_idx]; - if ($model_object->instances_count > $copies) { - $self->stop_background_process; - for my $i (1..$copies) { - $model_object->delete_last_instance; - $self->{print}->objects->[$obj_idx]->delete_last_copy; - } - # Set conut of object on c++ side - Slic3r::GUI::set_object_count($obj_idx, $model_object->instances_count); - } elsif (defined $copies_asked) { - # The "decrease" came from the "set number of copies" dialog. - $self->stop_background_process; - $self->remove; - } else { - # The "decrease" came from the "-" button. Don't allow the object to disappear. - return; - } - - $self->update; -} - -# XXX: VK: done -sub set_number_of_copies { - my ($self) = @_; - # get current number of copies - my ($obj_idx, $object) = $self->selected_object; - my $model_object = $self->{model}->objects->[$obj_idx]; - # prompt user - my $copies = -1; - $copies = Wx::GetNumberFromUser("", L("Enter the number of copies of the selected object:"), L("Copies"), $model_object->instances_count, 0, 1000, $self); - my $diff = $copies - $model_object->instances_count; - if ($diff == 0 || $copies == -1) { - # no variation - } elsif ($diff > 0) { - $self->increase($diff); - } elsif ($diff < 0) { - $self->decrease(-$diff); - } -} - -# XXX: VK: removed -sub _get_number_from_user { - my ($self, $title, $prompt_message, $error_message, $default, $only_positive) = @_; - for (;;) { - my $value = Wx::GetTextFromUser($prompt_message, $title, $default, $self); - # Accept both dashes and dots as a decimal separator. - $value =~ s/,/./; - # If scaling value is being entered, remove the trailing percent sign. - $value =~ s/%$// if $only_positive; - # User canceled the selection, return undef. - return if $value eq ''; - # Validate a numeric value. - return $value if ($value =~ /^-?\d*(?:\.\d*)?$/) && (! $only_positive || $value > 0); - Wx::MessageBox( - $error_message . - (($only_positive && $value <= 0) ? - ": ".$value.L("\nNon-positive value.") : - ": ".$value.L("\nNot a numeric value.")), - L("Slic3r Error"), wxOK | wxICON_EXCLAMATION, $self); - $default = $value; - } -} - -# XXX: not done -sub rotate { - my ($self, $angle, $axis, $relative_key, $axis_x, $axis_y, $axis_z) = @_; - $relative_key //= 'absolute'; # relative or absolute coordinates - $axis_x //= 0; - $axis_y //= 0; - $axis_z //= 0; - my $relative = $relative_key eq 'relative'; - - my ($obj_idx, $object) = $self->selected_object; - return if !defined $obj_idx; - - my $model_object = $self->{model}->objects->[$obj_idx]; - my $model_instance = $model_object->instances->[0]; - - if (!defined $angle) { - my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z'; - my $default = $axis == Z ? rad2deg($model_instance->rotation) : 0; - $angle = $self->_get_number_from_user(L("Enter the rotation angle:"), L("Rotate around ").$axis_name.(" axis"), L("Invalid rotation angle entered"), $default); - return if $angle eq ''; - } - - # Let's calculate vector of rotation axis (if we don't have it already) - if (defined $axis) { - if ($axis == X) { - $axis_x = 1; - } - if ($axis == Y) { - $axis_y = 1; - } - } - - $self->stop_background_process; - - if (defined $axis && $axis == Z) { - my $new_angle = deg2rad($angle); - foreach my $inst (@{ $model_object->instances }) { - my $rotation = ($relative ? $inst->rotation : 0.) + $new_angle; - while ($rotation > 2.0 * PI) { - $rotation -= 2.0 * PI; - } - while ($rotation < 0.0) { - $rotation += 2.0 * PI; - } - $inst->set_rotation($rotation); - Slic3r::GUI::_3DScene::update_gizmos_data($self->{canvas3D}) if ($self->{canvas3D}); - } -# $object->transform_thumbnail($self->{model}, $obj_idx); - } else { - if (defined $axis) { - # rotation around X and Y needs to be performed on mesh - # so we first apply any Z rotation - if ($model_instance->rotation != 0) { - $model_object->rotate($model_instance->rotation, Slic3r::Pointf3->new(0, 0, -1)); - $_->set_rotation(0) for @{ $model_object->instances }; - } - } - $model_object->rotate(deg2rad($angle), Slic3r::Pointf3->new($axis_x, $axis_y, $axis_z)); - -# # realign object to Z = 0 -# $model_object->center_around_origin; -# $self->reset_thumbnail($obj_idx); - } - - # update print and start background processing - $self->{print}->add_model_object($model_object, $obj_idx); - - $self->selection_changed; # refresh info (size etc.) - $self->update; -} - -# XXX: not done -sub mirror { - my ($self, $axis) = @_; - - my ($obj_idx, $object) = $self->selected_object; - return if !defined $obj_idx; - - my $model_object = $self->{model}->objects->[$obj_idx]; - my $model_instance = $model_object->instances->[0]; - - # apply Z rotation before mirroring - if ($model_instance->rotation != 0) { - $model_object->rotate($model_instance->rotation, Slic3r::Pointf3->new(0, 0, 1)); - $_->set_rotation(0) for @{ $model_object->instances }; - } - - $model_object->mirror($axis); - -# # realign object to Z = 0 -# $model_object->center_around_origin; -# $self->reset_thumbnail($obj_idx); - - # update print and start background processing - $self->stop_background_process; - $self->{print}->add_model_object($model_object, $obj_idx); - - $self->selection_changed; # refresh info (size etc.) - $self->update; -} - -# XXX: not done, renamed as Plater::priv::scale() -sub changescale { - my ($self, $axis, $tosize) = @_; - - my ($obj_idx, $object) = $self->selected_object; - return if !defined $obj_idx; - - my $model_object = $self->{model}->objects->[$obj_idx]; - my $model_instance = $model_object->instances->[0]; - - my $object_size = $model_object->instance_bounding_box(0)->size; - - if (defined $axis) { - my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z'; - my $scale; - if ($tosize) { - my $cursize = $object_size->[$axis]; - my $newsize = $self->_get_number_from_user( - L('Enter the new size for the selected object:'), - L("Scale along ").$axis_name, L('Invalid scaling value entered'), $cursize, 1); - return if $newsize eq ''; - $scale = $newsize / $cursize * 100; - } else { - $scale = $self->_get_number_from_user(L('Enter the scale % for the selected object:'), L("Scale along ").$axis_name, L('Invalid scaling value entered'), 100, 1); - return if $scale eq ''; - } - - # apply Z rotation before scaling - if ($model_instance->rotation != 0) { - $model_object->rotate($model_instance->rotation, Slic3r::Pointf3->new(0, 0, 1)); - $_->set_rotation(0) for @{ $model_object->instances }; - } - - my $versor = [1,1,1]; - $versor->[$axis] = $scale/100; - $model_object->scale_xyz(Slic3r::Pointf3->new(@$versor)); - #FIXME Scale the layer height profile when $axis == Z? - #FIXME Scale the layer height ranges $axis == Z? - # object was already aligned to Z = 0, so no need to realign it -# $self->reset_thumbnail($obj_idx); - } else { - my $scale; - if ($tosize) { - my $cursize = max(@$object_size); - my $newsize = $self->_get_number_from_user(L('Enter the new max size for the selected object:'), L('Scale'), L('Invalid scaling value entered'), $cursize, 1); - return if ! defined($newsize) || $newsize eq ''; - $scale = $model_instance->scaling_factor * $newsize / $cursize * 100; - } else { - # max scale factor should be above 2540 to allow importing files exported in inches - $scale = $self->_get_number_from_user(L('Enter the scale % for the selected object:'), L('Scale'), L('Invalid scaling value entered'), $model_instance->scaling_factor*100, 1); - return if ! defined($scale) || $scale eq ''; - } - - # Set object scale on c++ side -# Slic3r::GUI::set_object_scale($obj_idx, $scale); - $scale /= 100; # turn percent into factor - - my $variation = $scale / $model_instance->scaling_factor; - #FIXME Scale the layer height profile? - foreach my $range (@{ $model_object->layer_height_ranges }) { - $range->[0] *= $variation; - $range->[1] *= $variation; - } - $_->set_scaling_factor($scale) for @{ $model_object->instances }; -# $object->transform_thumbnail($self->{model}, $obj_idx); - } - - # update print and start background processing - $self->stop_background_process; - $self->{print}->add_model_object($model_object, $obj_idx); - - $self->selection_changed(1); # refresh info (size, volume etc.) - $self->update; -} - -# XXX: VK: WIP -sub arrange { - my ($self) = @_; - - $self->stop_background_process; - # my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape); - # my $success = $self->{model}->arrange_objects(wxTheApp->{preset_bundle}->full_config->min_object_distance, $bb); - - # Update is not implemented in C++ so we cannot call this for now - $self->{appController}->arrange_model; - - # ignore arrange failures on purpose: user has visual feedback and we don't need to warn him - # when parts don't fit in print bed - - # Force auto center of the aligned grid of of objects on the print bed. - $self->update(0); -} - -# XXX: not done -sub split_object { - my $self = shift; - - my ($obj_idx, $current_object) = $self->selected_object; - - # we clone model object because split_object() adds the split volumes - # into the same model object, thus causing duplicates when we call load_model_objects() - my $new_model = $self->{model}->clone; # store this before calling get_object() - my $current_model_object = $new_model->get_object($obj_idx); - - if ($current_model_object->volumes_count > 1) { - Slic3r::GUI::warning_catcher($self)->(L("The selected object can't be split because it contains more than one volume/material.")); - return; - } - - $self->stop_background_process; - - my @model_objects = @{$current_model_object->split_object}; - if (@model_objects == 1) { - Slic3r::GUI::warning_catcher($self)->(L("The selected object couldn't be split because it contains only one part.")); - $self->schedule_background_process; - } else { - $_->center_around_origin for (@model_objects); - $self->remove($obj_idx); - $current_object = $obj_idx = undef; - # load all model objects at once, otherwise the plate would be rearranged after each one - # causing original positions not to be kept - $self->load_model_objects(@model_objects); - } -} - -# XXX: not done -# Trigger $self->async_apply_config() after 500ms. -# The call is delayed to avoid restarting the background processing during typing into an edit field. -sub schedule_background_process { - my ($self) = @_; - $self->{apply_config_timer}->Start(0.5 * 1000, 1); # 1 = one shot, every half a second. -} - -# XXX: not done -# Executed asynchronously by a timer every PROCESS_DELAY (0.5 second). -# The timer is started by schedule_background_process(), -sub async_apply_config { - my ($self) = @_; - # Apply new config to the possibly running background task. - my $was_running = $self->{background_slicing_process}->running; - my $invalidated = $self->{background_slicing_process}->apply_config(wxTheApp->{preset_bundle}->full_config); - # Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile. - $self->{canvas3D}->Refresh if Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D}); - # If the apply_config caused the calculation to stop, or it was not running yet: - if ($invalidated) { - if ($was_running) { - # Hide the slicing results if the current slicing status is no more valid. - $self->print_info_box_show(0) - } - if (wxTheApp->{app_config}->get("background_processing")) { - $self->{background_slicing_process}->start; - } - if ($was_running) { - # Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared. - # Otherwise they will be just refreshed. - $self->{gcode_preview_data}->reset; - $self->{preview_iface}->reload_print if $self->{preview_iface}; -# $self->{preview3D}->reload_print if $self->{preview3D}; - # We also need to reload 3D scene because of the wipe tower preview box - if ($self->{config}->wipe_tower) { - my $selections = $self->collect_selections; - Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); - Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1) if $self->{canvas3D} - } - } - } -} - -# XXX: not done -# Background processing is started either by the "Slice now" button, by the "Export G-code button" or by async_apply_config(). -sub start_background_process { - my ($self) = @_; - return if ! @{$self->{objects}} || $self->{background_slicing_process}->running; - # Don't start process thread if config is not valid. - eval { - # this will throw errors if config is not valid - wxTheApp->{preset_bundle}->full_config->validate; - $self->{print}->validate; - }; - if ($@) { - $self->statusbar->SetStatusText($@); - return; - } - # Copy the names of active presets into the placeholder parser. - wxTheApp->{preset_bundle}->export_selections_pp($self->{print}->placeholder_parser); - # Start the background process. - $self->{background_slicing_process}->start; -} - -# XXX: not done -# Stop the background processing -sub stop_background_process { - my ($self) = @_; - $self->{background_slicing_process}->stop(); - $self->{preview_iface}->reload_print if $self->{preview_iface}; -# $self->{preview3D}->reload_print if $self->{preview3D}; -} - -# XXX: not done -# Called by the "Slice now" button, which is visible only if the background processing is disabled. -sub reslice { - # explicitly cancel a previous thread and start a new one. - my ($self) = @_; - # Don't reslice if export of G-code or sending to OctoPrint is running. - if (! defined($self->{export_gcode_output_file}) && ! defined($self->{send_gcode_file})) { - # Stop the background processing threads, stop the async update timer. - $self->stop_background_process; - # Rather perform one additional unnecessary update of the print object instead of skipping a pending async update. - $self->async_apply_config; - $self->statusbar->SetCancelCallback(sub { - $self->stop_background_process; - $self->statusbar->SetStatusText(L("Slicing cancelled")); - # this updates buttons status - $self->object_list_changed; - }); - $self->start_background_process; - } -} - -# XXX: VK: done -sub export_gcode { - my ($self, $output_file) = @_; - - return if !@{$self->{objects}}; - - if ($self->{export_gcode_output_file}) { - Wx::MessageDialog->new($self, L("Another export job is currently running."), L('Error'), wxOK | wxICON_ERROR)->ShowModal; - return; - } - - # if process is not running, validate config - # (we assume that if it is running, config is valid) - eval { - # this will throw errors if config is not valid - wxTheApp->{preset_bundle}->full_config->validate; - $self->{print}->validate; - }; - Slic3r::GUI::catch_error($self) and return; - - - # apply config and validate print - my $config = wxTheApp->{preset_bundle}->full_config; - eval { - # this will throw errors if config is not valid - $config->validate; - #FIXME it shall use the background processing! - $self->{print}->apply_config($config); - $self->{print}->validate; - }; - Slic3r::GUI::catch_error($self) and return; - - # Copy the names of active presets into the placeholder parser. - wxTheApp->{preset_bundle}->export_selections_pp($self->{print}->placeholder_parser); - # select output file - if ($output_file) { - $self->{export_gcode_output_file} = eval { $self->{print}->output_filepath($output_file) }; - Slic3r::GUI::catch_error($self) and return; - } else { - my $default_output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') }; - Slic3r::GUI::catch_error($self) and return; - # If possible, remove accents from accented latin characters. - # This function is useful for generating file names to be processed by legacy firmwares. - $default_output_file = Slic3r::GUI::fold_utf8_to_ascii($default_output_file); - my $dlg = Wx::FileDialog->new($self, L('Save G-code file as:'), - wxTheApp->{app_config}->get_last_output_dir(dirname($default_output_file)), - basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if ($dlg->ShowModal != wxID_OK) { - $dlg->Destroy; - return; - } - my $path = $dlg->GetPath; - wxTheApp->{app_config}->update_last_output_dir(dirname($path)); - $self->{export_gcode_output_file} = $path; - $dlg->Destroy; - } - - $self->statusbar->StartBusy; - - $self->statusbar->SetCancelCallback(sub { - $self->stop_background_process; - $self->statusbar->SetStatusText(L("Export cancelled")); - $self->{export_gcode_output_file} = undef; - $self->{send_gcode_file} = undef; - - # this updates buttons status - $self->object_list_changed; - }); - - $self->{background_slicing_process}->set_output_path($self->{export_gcode_output_file}); - - # start background process, whose completion event handler - # will detect $self->{export_gcode_output_file} and proceed with export - $self->start_background_process; - - # this updates buttons status - $self->object_list_changed; - - return $self->{export_gcode_output_file}; -} - -# XXX: not done -# This message should be called by the background process synchronously. -sub on_update_print_preview { - my ($self) = @_; - $self->{preview_iface}->reload_print if $self->{preview_iface}; -# $self->{preview3D}->reload_print if $self->{preview3D}; - - # in case this was MM print, wipe tower bounding box on 3D tab might need redrawing with exact depth: - my $selections = $self->collect_selections; - Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); - Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1); -} - -# XXX: not done -# This gets called also if we have no threads. -sub on_progress_event { - my ($self, $percent, $message) = @_; - - $self->statusbar->SetProgress($percent); - # TODO: three dot character is not properly translated into C++ - # $self->statusbar->SetStatusText("$message…"); - $self->statusbar->SetStatusText("$message..."); -} - -# XXX: not done -# Called when the G-code export finishes, either successfully or with an error. -# This gets called also if we don't have threads. -sub on_process_completed { - my ($self, $result) = @_; - - # Stop the background task, wait until the thread goes into the "Idle" state. - # At this point of time the thread should be either finished or canceled, - # so the following call just confirms, that the produced data were consumed. - $self->{background_slicing_process}->stop; - $self->statusbar->ResetCancelCallback(); - $self->statusbar->StopBusy; - $self->statusbar->SetStatusText(""); - - my $message; - my $send_gcode = 0; - my $do_print = 0; -# print "Process completed, message: ", $message, "\n"; - if (defined($result)) { - $message = L("Export failed"); - } else { - # G-code file exported successfully. - if ($self->{print_file}) { - $message = L("File added to print queue"); - $do_print = 1; - } elsif ($self->{send_gcode_file}) { - $message = L("Sending G-code file to the Printer Host ..."); - $send_gcode = 1; - } elsif (defined $self->{export_gcode_output_file}) { - $message = L("G-code file exported to ") . $self->{export_gcode_output_file}; - } else { - $message = L("Slicing complete"); - } - } - $self->{export_gcode_output_file} = undef; - wxTheApp->notify($message); - - $self->do_print if $do_print; - - # Send $self->{send_gcode_file} to OctoPrint. - if ($send_gcode) { - my $host = Slic3r::PrintHost::get_print_host($self->{config}); - if ($host->send_gcode($self->{send_gcode_file})) { - $message = L("Upload to host finished."); - } else { - $message = ""; - } - } - - # As of now, the BackgroundProcessing thread posts status bar update messages to a queue on the MainFrame.pm, - # but the "Processing finished" message is posted to this window. - # Delay the following status bar update, so it will be called later than what is received by MainFrame.pm. - wxTheApp->CallAfter(sub { - $self->statusbar->SetStatusText($message); - }); - - $self->{print_file} = undef; - $self->{send_gcode_file} = undef; - $self->print_info_box_show(1); - - # this updates buttons status - $self->object_list_changed; - - # refresh preview - $self->{preview_iface}->reload_print if $self->{preview_iface}; -# $self->{preview3D}->reload_print if $self->{preview3D}; -} - -# XXX: partially done in the Sidebar -# Fill in the "Sliced info" box with the result of the G-code generator. -sub print_info_box_show { - my ($self, $show) = @_; -# my $scrolled_window_panel = $self->{scrolled_window_panel}; -# my $scrolled_window_sizer = $self->{scrolled_window_sizer}; -# return if (!$show && ($scrolled_window_sizer->IsShown(2) == $show)); - my $panel = $self->{scrolled_window_panel};#$self->{right_panel}; - my $sizer = $self->{info_sizer}; -# return if (!$sizer || !$show && ($sizer->IsShown(1) == $show)); - return if (!$sizer); - - Slic3r::GUI::set_show_print_info($show); -# return if (wxTheApp->{app_config}->get("view_mode") eq "simple"); - -# if ($show) - { - my $print_info_sizer = $self->{print_info_sizer}; - $print_info_sizer->Clear(1); - my $grid_sizer = Wx::FlexGridSizer->new(2, 2, 5, 5); - $grid_sizer->SetFlexibleDirection(wxHORIZONTAL); - $grid_sizer->AddGrowableCol(1, 1); - $grid_sizer->AddGrowableCol(3, 1); - $print_info_sizer->Add($grid_sizer, 0, wxEXPAND); - my $is_wipe_tower = $self->{print}->total_wipe_tower_filament > 0; - my @info = ( - L("Used Filament (m)") - => $is_wipe_tower ? - sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_used_filament / 1000, - ($self->{print}->total_used_filament - $self->{print}->total_wipe_tower_filament) / 1000, - L("objects"), - $self->{print}->total_wipe_tower_filament / 1000, - L("wipe tower")) : - sprintf("%.2f" , $self->{print}->total_used_filament / 1000), - - L("Used Filament (mm³)") - => sprintf("%.2f" , $self->{print}->total_extruded_volume), - L("Used Filament (g)"), - => sprintf("%.2f" , $self->{print}->total_weight), - L("Cost"), - => $is_wipe_tower ? - sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_cost, - ($self->{print}->total_cost - $self->{print}->total_wipe_tower_cost), - L("objects"), - $self->{print}->total_wipe_tower_cost, - L("wipe tower")) : - sprintf("%.2f" , $self->{print}->total_cost), - L("Estimated printing time (normal mode)") - => $self->{print}->estimated_normal_print_time, - L("Estimated printing time (silent mode)") - => $self->{print}->estimated_silent_print_time - ); - # if there is a wipe tower, insert number of toolchanges info into the array: - splice (@info, 8, 0, L("Number of tool changes") => sprintf("%.d", $self->{print}->wipe_tower_number_of_toolchanges)) if ($is_wipe_tower); - - while ( my $label = shift @info) { - my $value = shift @info; - next if $value eq "N/A"; -# my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); - my $text = Wx::StaticText->new($panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); - $text->SetFont($Slic3r::GUI::small_font); - $grid_sizer->Add($text, 0); -# my $field = Wx::StaticText->new($scrolled_window_panel, -1, $value, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - my $field = Wx::StaticText->new($panel, -1, $value, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - $field->SetFont($Slic3r::GUI::small_font); - $grid_sizer->Add($field, 0); - } - } - -# $scrolled_window_sizer->Show(2, $show); -# $scrolled_window_panel->Layout; - $sizer->Show(1, $show && wxTheApp->{app_config}->get("view_mode") ne "simple"); - - $self->Layout; - $panel->Refresh; -} - -# XXX: not done - to be removed -sub do_print { - my ($self) = @_; - - my $controller = $self->GetFrame->{controller}; - my $printer_preset = wxTheApp->{preset_bundle}->printer->get_edited_preset; - my $printer_panel = $controller->add_printer($printer_preset->name, $printer_preset->config); - - my $filament_stats = $self->{print}->filament_stats; - my $filament_names = wxTheApp->{preset_bundle}->filament_presets; - $filament_stats = { map { $filament_names->[$_] => $filament_stats->{$_} } keys %$filament_stats }; - $printer_panel->load_print_job($self->{print_file}, $filament_stats); -} - -# XXX: VK: done -sub export_stl { - my ($self) = @_; - return if !@{$self->{objects}}; - # Ask user for a file name to write into. - my $output_file = $self->_get_export_file('STL') or return; - # Store a binary STL. - $self->{model}->store_stl($output_file, 1); - $self->statusbar->SetStatusText(L("STL file exported to ").$output_file); -} - -# XXX: VK: done -sub reload_from_disk { - my ($self) = @_; - - my ($obj_idx, $object) = $self->selected_object; - return if !defined $obj_idx; - - my $model_object = $self->{model}->objects->[$obj_idx]; - #FIXME convert to local file encoding - return if !$model_object->input_file - || !-e Slic3r::encode_path($model_object->input_file); - - my @new_obj_idx = $self->load_files([$model_object->input_file]); - return if !@new_obj_idx; - - foreach my $new_obj_idx (@new_obj_idx) { - my $o = $self->{model}->objects->[$new_obj_idx]; - $o->clear_instances; - $o->add_instance($_) for @{$model_object->instances}; - #$o->invalidate_bounding_box; - - if ($o->volumes_count == $model_object->volumes_count) { - for my $i (0..($o->volumes_count-1)) { - $o->get_volume($i)->config->apply($model_object->get_volume($i)->config); - } - } - #FIXME restore volumes and their configs, layer_height_ranges, layer_height_profile, layer_height_profile_valid, - } - - $self->remove($obj_idx); -} - -# XXX: VK: integrated into Plater::export_stl() -sub export_object_stl { - my ($self) = @_; - my ($obj_idx, $object) = $self->selected_object; - return if !defined $obj_idx; - my $model_object = $self->{model}->objects->[$obj_idx]; - # Ask user for a file name to write into. - my $output_file = $self->_get_export_file('STL') or return; - $model_object->mesh->write_binary($output_file); - $self->statusbar->SetStatusText(L("STL file exported to ").$output_file); -} - -# XXX: not done -sub fix_through_netfabb { - my ($self) = @_; - my ($obj_idx, $object) = $self->selected_object; - return if !defined $obj_idx; - my $model_object = $self->{model}->objects->[$obj_idx]; - my $model_fixed = Slic3r::Model->new; - Slic3r::GUI::fix_model_by_win10_sdk_gui($model_object, $self->{print}, $model_fixed); - - my @new_obj_idx = $self->load_model_objects(@{$model_fixed->objects}); - return if !@new_obj_idx; - - foreach my $new_obj_idx (@new_obj_idx) { - my $o = $self->{model}->objects->[$new_obj_idx]; - $o->clear_instances; - $o->add_instance($_) for @{$model_object->instances}; - #$o->invalidate_bounding_box; - - if ($o->volumes_count == $model_object->volumes_count) { - for my $i (0..($o->volumes_count-1)) { - $o->get_volume($i)->config->apply($model_object->get_volume($i)->config); - } - } - #FIXME restore volumes and their configs, layer_height_ranges, layer_height_profile, layer_height_profile_valid, - } - - $self->remove($obj_idx); -} - -# XXX: VK: done -sub export_amf { - my ($self) = @_; - return if !@{$self->{objects}}; - # Ask user for a file name to write into. - my $output_file = $self->_get_export_file('AMF') or return; - my $res = $self->{model}->store_amf($output_file, $self->{print}, $self->{export_option}); - if ($res) - { - $self->statusbar->SetStatusText(L("AMF file exported to ").$output_file); - } - else - { - $self->statusbar->SetStatusText(L("Error exporting AMF file ").$output_file); - } -} - -# XXX: VK: done -sub export_3mf { - my ($self) = @_; - return if !@{$self->{objects}}; - # Ask user for a file name to write into. - my $output_file = $self->_get_export_file('3MF') or return; - my $res = $self->{model}->store_3mf($output_file, $self->{print}, $self->{export_option}); - if ($res) - { - $self->statusbar->SetStatusText(L("3MF file exported to ").$output_file); - } - else - { - $self->statusbar->SetStatusText(L("Error exporting 3MF file ").$output_file); - } -} - -# XXX: VK: done -# Ask user to select an output file for a given file format (STl, AMF, 3MF). -# Propose a default file name based on the 'output_filename_format' configuration value. -sub _get_export_file { - my ($self, $format) = @_; - my $suffix = ''; - my $wildcard = 'known'; - if ($format eq 'STL') - { - $suffix = '.stl'; - $wildcard = 'stl'; - } - elsif ($format eq 'AMF') - { - if (&Wx::wxMAC) { - # It seems that MacOS does not like double extension - $suffix = '.amf'; - } else { - $suffix = '.zip.amf'; - } - $wildcard = 'amf'; - } - elsif ($format eq '3MF') - { - $suffix = '.3mf'; - $wildcard = 'threemf'; - } - # Copy the names of active presets into the placeholder parser. - wxTheApp->{preset_bundle}->export_selections_pp($self->{print}->placeholder_parser); - my $output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') }; - Slic3r::GUI::catch_error($self) and return undef; - $output_file =~ s/\.[gG][cC][oO][dD][eE]$/$suffix/; - my $dlg = Wx::FileDialog->new($self, L("Save ").$format.L(" file as:"), dirname($output_file), - basename($output_file), &Slic3r::GUI::FILE_WILDCARDS->{$wildcard}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - Slic3r::GUI::add_export_option($dlg, $format); - if ($dlg->ShowModal != wxID_OK) { - $dlg->Destroy; - return undef; - } - $output_file = $dlg->GetPath; - $self->{export_option} = Slic3r::GUI::get_export_option($dlg); - $dlg->Destroy; - return $output_file; -} - -#sub reset_thumbnail { -# my ($self, $obj_idx) = @_; -# $self->{objects}[$obj_idx]->thumbnail(undef); -#} - -# XXX: VK: done -# this method gets called whenever print center is changed or the objects' bounding box changes -# (i.e. when an object is added/removed/moved/rotated/scaled) -sub update { - my ($self, $force_autocenter) = @_; - $self->Freeze; - if (wxTheApp->{app_config}->get("autocenter") || $force_autocenter) { - $self->{model}->center_instances_around_point($self->bed_centerf); - } - $self->stop_background_process; - $self->{print}->reload_model_instances(); - $self->{canvas}->reload_scene if $self->{canvas}; -# $self->{canvas}->reload_scene if $self->{canvas}; - my $selections = $self->collect_selections; - Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); - Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 0); - $self->{preview_iface}->reset_gcode_preview_data if $self->{preview_iface}; - $self->{preview_iface}->reload_print if $self->{preview_iface}; -# $self->{preview3D}->reset_gcode_preview_data if $self->{preview3D}; -# $self->{preview3D}->reload_print if $self->{preview3D}; - $self->schedule_background_process; - $self->Thaw; -} - -# XXX: YS: done -# When a printer technology is changed, the UI needs to be updated to show/hide needed preset combo boxes. -sub show_preset_comboboxes{ - my ($self, $showSLA) = @_; #if showSLA is oposite value to "ptFFF" - - my $choices = $self->{preset_choosers}{filament}; - my $print_filament_ctrls_cnt = 2 + 2 * ($#$choices+1); - - foreach (0..$print_filament_ctrls_cnt-1){ - $self->{presets_sizer}->Show($_, !$showSLA); - } - $self->{presets_sizer}->Show($print_filament_ctrls_cnt , $showSLA); - $self->{presets_sizer}->Show($print_filament_ctrls_cnt+1, $showSLA); - - $self->{frequently_changed_parameters_sizer}->Show(0,!$showSLA); - - $self->Layout; -} - -# XXX: YS: done -# When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder. -# Also the wxTheApp->{preset_bundle}->filament_presets needs to be resized accordingly -# and some reasonable default has to be selected for the additional extruders. -sub on_extruders_change { - my ($self, $num_extruders) = @_; - my $choices = $self->{preset_choosers}{filament}; - - while (int(@$choices) < $num_extruders) { - # copy strings from first choice - my @presets = $choices->[0]->GetStrings; - - # initialize new choice -# my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY); - my $choice = Wx::BitmapComboBox->new($self->{scrolled_window_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY); - my $extruder_idx = scalar @$choices; - EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down($extruder_idx, @_); } ); - push @$choices, $choice; - # copy icons from first choice - $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; - # insert new choice into sizer - $self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0); - $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, 0); - # setup the listener - EVT_COMBOBOX($choice, $choice, sub { - my ($choice) = @_; - wxTheApp->CallAfter(sub { - $self->_on_select_preset('filament', $choice, $extruder_idx); - }); - }); - # initialize selection - wxTheApp->{preset_bundle}->update_platter_filament_ui($extruder_idx, $choice); - } - - # remove unused choices if any - while (@$choices > $num_extruders) { - $self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # label - $self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # wxChoice - $choices->[-1]->Destroy; - pop @$choices; - } - $self->{right_panel}->Layout; - $self->Layout; -} - -# XXX: not done -sub on_config_change { - my ($self, $config) = @_; - - my $update_scheduled; - foreach my $opt_key (@{$self->{config}->diff($config)}) { - $self->{config}->set($opt_key, $config->get($opt_key)); - if ($opt_key eq 'bed_shape') { -# $self->{canvas}->update_bed_size; - Slic3r::GUI::_3DScene::set_bed_shape($self->{canvas3D}, $self->{config}->bed_shape) if $self->{canvas3D}; - $self->{preview_iface}->set_bed_shape($self->{config}->bed_shape) if ($self->{preview_iface}); -# Slic3r::GUI::_3DScene::set_bed_shape($self->{preview3D}->canvas, $self->{config}->bed_shape) if $self->{preview3D}; - $update_scheduled = 1; - } elsif ($opt_key =~ '^wipe_tower' || $opt_key eq 'single_extruder_multi_material') { - $update_scheduled = 1; - } elsif ($opt_key eq 'serial_port') { - $self->{btn_print}->Show($config->get('serial_port')); - $self->Layout; - } elsif ($opt_key eq 'print_host') { - $self->{btn_send_gcode}->Show($config->get('print_host')); - $self->Layout; - } elsif ($opt_key eq 'variable_layer_height') { - if ($config->get('variable_layer_height') != 1) { -# if ($self->{htoolbar}) { -# $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0); -# $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, 0); -# } else { -# $self->{"btn_layer_editing"}->Disable; -# $self->{"btn_layer_editing"}->SetValue(0); -# } - Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 0); - Slic3r::GUI::_3DScene::enable_layers_editing($self->{canvas3D}, 0); - $self->{canvas3D}->Refresh; - $self->{canvas3D}->Update; - } elsif (Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D})) { - # Want to allow the layer editing, but do it only if the OpenGL supports it. -# if ($self->{htoolbar}) { -# $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 1); -# } else { -# $self->{"btn_layer_editing"}->Enable; -# } - Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 1); - } - } elsif ($opt_key eq 'extruder_colour') { - $update_scheduled = 1; - my $extruder_colors = $config->get('extruder_colour'); - $self->{preview_iface}->set_number_extruders(scalar(@{$extruder_colors})); -# $self->{preview3D}->set_number_extruders(scalar(@{$extruder_colors})); - } elsif ($opt_key eq 'max_print_height') { - $update_scheduled = 1; - } elsif ($opt_key eq 'printer_model') { - # update to force bed selection (for texturing) - Slic3r::GUI::_3DScene::set_bed_shape($self->{canvas3D}, $self->{config}->bed_shape) if $self->{canvas3D}; - $self->{preview_iface}->set_bed_shape($self->{config}->bed_shape) if ($self->{preview_iface}); -# Slic3r::GUI::_3DScene::set_bed_shape($self->{preview3D}->canvas, $self->{config}->bed_shape) if $self->{preview3D}; - $update_scheduled = 1; - } - } - - $self->update if $update_scheduled; - - return if !$self->GetFrame->is_loaded; - - # (re)start timer - $self->schedule_background_process; -} - -# XXX: YS: WIP -sub item_changed_selection { - my ($self, $obj_idx) = @_; - - if (($obj_idx >= 0) && ($obj_idx < 1000)) { # skip if wipe tower selected - if ($self->{canvas3D}) { - Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas3D}); - if ($obj_idx >= 0) { - my $selections = $self->collect_selections; - Slic3r::GUI::_3DScene::update_volumes_selection($self->{canvas3D}, \@$selections); - } -# Slic3r::GUI::_3DScene::render($self->{canvas3D}); - } - } -} - -# XXX: VK: done -sub collect_selections { - my ($self) = @_; - my $selections = []; - foreach my $o (@{$self->{objects}}) { - push(@$selections, $o->selected); - } - return $selections; -} - -# XXX: YS: done, lambda on LEFT_DOWN -# Called when clicked on the filament preset combo box. -# When clicked on the icon, show the color picker. -sub filament_color_box_lmouse_down -{ - my ($self, $extruder_idx, $combobox, $event) = @_; - my $pos = $event->GetLogicalPosition(Wx::ClientDC->new($combobox)); - my( $x, $y ) = ( $pos->x, $pos->y ); - if ($x > 24) { - # Let the combo box process the mouse click. - $event->Skip; - } else { - # Swallow the mouse click and open the color picker. - my $data = Wx::ColourData->new; - $data->SetChooseFull(1); - my $dialog = Wx::ColourDialog->new($self->GetFrame, $data); - if ($dialog->ShowModal == wxID_OK) { - my $cfg = Slic3r::Config->new; - my $colors = wxTheApp->{preset_bundle}->full_config->get('extruder_colour'); - $colors->[$extruder_idx] = $dialog->GetColourData->GetColour->GetAsString(wxC2S_HTML_SYNTAX); - $cfg->set('extruder_colour', $colors); - $self->GetFrame->{options_tabs}{printer}->load_config($cfg); - wxTheApp->{preset_bundle}->update_platter_filament_ui($extruder_idx, $combobox); - } - $dialog->Destroy(); - } -} - -#sub object_cut_dialog { -# my ($self, $obj_idx) = @_; -# -# if (!defined $obj_idx) { -# ($obj_idx, undef) = $self->selected_object; -# } -# -# if (!$Slic3r::GUI::have_OpenGL) { -# Slic3r::GUI::show_error($self, L("Please install the OpenGL modules to use this feature (see build instructions).")); -# return; -# } -# -# my $dlg = Slic3r::GUI::Plater::ObjectCutDialog->new($self, -# object => $self->{objects}[$obj_idx], -# model_object => $self->{model}->objects->[$obj_idx], -# ); -# return unless $dlg->ShowModal == wxID_OK; -# -# if (my @new_objects = $dlg->NewModelObjects) { -# $self->remove($obj_idx); -# $self->load_model_objects(grep defined($_), @new_objects); -# $self->arrange; -# Slic3r::GUI::_3DScene::zoom_to_volumes($self->{canvas3D}) if $self->{canvas3D}; -# } -#} - -# XXX: YS: done -sub changed_object_settings { - my ($self, $obj_idx, $parts_changed, $part_settings_changed) = @_; - - # update thumbnail since parts may have changed - if ($parts_changed) { - # recenter and re-align to Z = 0 - my $model_object = $self->{model}->objects->[$obj_idx]; - $model_object->center_around_origin; -# $self->reset_thumbnail($obj_idx); - } - - # update print - if ($parts_changed || $part_settings_changed) { - $self->stop_background_process; - $self->{print}->reload_object($obj_idx); - $self->schedule_background_process; - $self->{canvas}->reload_scene if $self->{canvas}; - my $selections = $self->collect_selections; - Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); - Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 0); - } else { - $self->schedule_background_process; - } -} - -# XXX: VK: done -# Called to update various buttons depending on whether there are any objects or -# whether background processing (export of a G-code, sending to Octoprint, forced background re-slicing) is active. -sub object_list_changed { - my $self = shift; - - # Enable/disable buttons depending on whether there are any objects on the platter. - my $have_objects = @{$self->{objects}} ? 1 : 0; -# if ($self->{htoolbar}) { -# # On OSX or Linux -# $self->{htoolbar}->EnableTool($_, $have_objects) -# for (TB_RESET, TB_ARRANGE); -# } else { -# # On MSW -# my $method = $have_objects ? 'Enable' : 'Disable'; -# $self->{"btn_$_"}->$method -# for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode); -# } - - for my $toolbar_item (qw(deleteall arrange)) { - Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, $toolbar_item, $have_objects); - } - - my $export_in_progress = $self->{export_gcode_output_file} || $self->{send_gcode_file}; - my $model_fits = $self->{canvas3D} ? Slic3r::GUI::_3DScene::check_volumes_outside_state($self->{canvas3D}, $self->{config}) : 1; - # $model_fits == 1 -> ModelInstance::PVS_Partly_Outside - my $method = ($have_objects && ! $export_in_progress && ($model_fits != 1)) ? 'Enable' : 'Disable'; - $self->{"btn_$_"}->$method - for grep $self->{"btn_$_"}, qw(reslice export_gcode print send_gcode); -} - -# XXX: VK: WIP -# Selection of an active 3D object changed. -sub selection_changed { - my ($self) = @_; - my ($obj_idx, $object) = $self->selected_object; - my $have_sel = defined $obj_idx; - my $layers_height_allowed = $self->{config}->variable_layer_height && Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D}) && $have_sel; - - $self->{right_panel}->Freeze; -# if ($self->{htoolbar}) { -# # On OSX or Linux -# $self->{htoolbar}->EnableTool($_, $have_sel) -# for (TB_REMOVE, TB_MORE, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS); -# -# $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, $layers_height_allowed); -# -# if ($have_sel) { -# my $model_object = $self->{model}->objects->[$obj_idx]; -# $self->{htoolbar}->EnableTool(TB_FEWER, $model_object->instances_count > 1); -# } else { -# $self->{htoolbar}->EnableTool(TB_FEWER, 0); -# } -# -# } else { -# # On MSW -# my $method = $have_sel ? 'Enable' : 'Disable'; -# $self->{"btn_$_"}->$method -# for grep $self->{"btn_$_"}, qw(remove increase rotate45cw rotate45ccw changescale split cut settings); -# -# if ($layers_height_allowed) { -# $self->{"btn_layer_editing"}->Enable; -# } else { -# $self->{"btn_layer_editing"}->Disable; -# } -# -# if ($have_sel) { -# my $model_object = $self->{model}->objects->[$obj_idx]; -# if ($model_object->instances_count > 1) { -# $self->{"btn_decrease"}->Enable; -# } else { -# $self->{"btn_decrease"}->Disable; -# } -# } else { -# $self->{"btn_decrease"}->Disable; -# } -# } - - for my $toolbar_item (qw(delete more fewer split cut settings)) { - Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, $toolbar_item, $have_sel); - } - - Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", $layers_height_allowed); - - my $can_select_by_parts = 0; - - if ($have_sel) { - my $model_object = $self->{model}->objects->[$obj_idx]; - $can_select_by_parts = ($obj_idx >= 0) && ($obj_idx < 1000) && ($model_object->volumes_count > 1); - Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "fewer", $model_object->instances_count > 1); - } - - if ($can_select_by_parts) { - # first disable to let the item in the toolbar to switch to the unpressed state - Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "selectbyparts", 0); - Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "selectbyparts", 1); - } else { - Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "selectbyparts", 0); - Slic3r::GUI::_3DScene::set_select_by($self->{canvas3D}, 'object'); - } - - if ($self->{object_info_size}) { # have we already loaded the info pane? - if ($have_sel) { - my $model_object = $self->{model}->objects->[$obj_idx]; - #FIXME print_info runs model fixing in two rounds, it is very slow, it should not be performed here! - # $model_object->print_info; - my $model_instance = $model_object->instances->[0]; - $self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size})); - $self->{object_info_materials}->SetLabel($model_object->materials_count); - - if (my $stats = $model_object->mesh_stats) { - $self->{object_info_volume}->SetLabel(sprintf('%.2f', $stats->{volume} * ($model_instance->scaling_factor**3))); - $self->{object_info_facets}->SetLabel(sprintf(L('%d (%d shells)'), $model_object->facets_count, $stats->{number_of_parts})); - if (my $errors = sum(@$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)})) { - $self->{object_info_manifold}->SetLabel(sprintf(L("Auto-repaired (%d errors)"), $errors)); - #$self->{object_info_manifold_warning_icon}->Show; - $self->{"object_info_manifold_warning_icon_show"}->(1); - - # we don't show normals_fixed because we never provide normals - # to admesh, so it generates normals for all facets - my $message = sprintf L('%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges'), - @$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)}; - $self->{object_info_manifold}->SetToolTipString($message); - $self->{object_info_manifold_warning_icon}->SetToolTipString($message); - } else { - $self->{object_info_manifold}->SetLabel(L("Yes")); - #$self->{object_info_manifold_warning_icon}->Hide; - $self->{"object_info_manifold_warning_icon_show"}->(0); - $self->{object_info_manifold}->SetToolTipString(""); - $self->{object_info_manifold_warning_icon}->SetToolTipString(""); - } - } else { - $self->{object_info_facets}->SetLabel($object->facets); - } - } else { - $self->{"object_info_$_"}->SetLabel("") for qw(size volume facets materials manifold); - #$self->{object_info_manifold_warning_icon}->Hide; - $self->{"object_info_manifold_warning_icon_show"}->(0); - $self->{object_info_manifold}->SetToolTipString(""); - $self->{object_info_manifold_warning_icon}->SetToolTipString(""); - } - $self->Layout; - } - - # prepagate the event to the frame (a custom Wx event would be cleaner) - $self->GetFrame->on_plater_selection_changed($have_sel); - $self->{right_panel}->Thaw; -} - -# XXX: VK: done -sub select_object { - my ($self, $obj_idx, $child) = @_; - - # remove current selection - foreach my $o (0..$#{$self->{objects}}) { - $self->{objects}->[$o]->selected(0); - } - - if (defined $obj_idx) { - $self->{objects}->[$obj_idx]->selected(1); - # Select current object in the list on c++ side, if item isn't child -# if (!defined $child){ -# Slic3r::GUI::select_current_object($obj_idx);} # all selections in the object list is on c++ side - } else { - # Unselect all objects in the list on c++ side -# Slic3r::GUI::unselect_objects(); # all selections in the object list is on c++ side - } - $self->selection_changed(1); -} - -# XXX: YS: WIP -sub select_object_from_cpp { - my ($self, $obj_idx, $vol_idx) = @_; - - # remove current selection - foreach my $o (0..$#{$self->{objects}}) { - $self->{objects}->[$o]->selected(0); - } - - my $curr = Slic3r::GUI::_3DScene::get_select_by($self->{canvas3D}); - - if (defined $obj_idx) { - if ($vol_idx == -1){ - if ($curr eq 'object') { - $self->{objects}->[$obj_idx]->selected(1); - } - elsif ($curr eq 'volume') { - Slic3r::GUI::_3DScene::set_select_by($self->{canvas3D}, 'object'); - } - - my $selections = $self->collect_selections; - Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); - Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1); - } - else { - if ($curr eq 'object') { - Slic3r::GUI::_3DScene::set_select_by($self->{canvas3D}, 'volume'); - } - - my $selections = []; - Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); - Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas3D}); - Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1); - my $volume_idx = Slic3r::GUI::_3DScene::get_first_volume_id($self->{canvas3D}, $obj_idx); - - my $inst_cnt = $self->{model}->objects->[$obj_idx]->instances_count; - for (0..$inst_cnt-1){ - Slic3r::GUI::_3DScene::select_volume($self->{canvas3D}, $vol_idx*$inst_cnt + $_ + $volume_idx) if ($volume_idx != -1); - } - } - } - - $self->selection_changed(1); -} - -# XXX: VK: done -sub selected_object { - my ($self) = @_; - my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} }; - return defined $obj_idx ? ($obj_idx, $self->{objects}[$obj_idx]) : undef; -} - -# XXX: VK: done -sub statusbar { - return $_[0]->GetFrame->{statusbar}; -} - -# XXX: not done, to be removed (?) -sub object_menu { - my ($self) = @_; - - my $frame = $self->GetFrame; - my $menu = Wx::Menu->new; - my $accel = ($^O eq 'MSWin32') ? sub { $_[0] . "\t\xA0" . $_[1] } : sub { $_[0] }; - $frame->_append_menu_item($menu, $accel->(L('Delete'), 'Del'), L('Remove the selected object'), sub { - $self->remove; - }, undef, 'brick_delete.png'); - $frame->_append_menu_item($menu, $accel->(L('Increase copies'), '+'), L('Place one more copy of the selected object'), sub { - $self->increase; - }, undef, 'add.png'); - $frame->_append_menu_item($menu, $accel->(L('Decrease copies'), '-'), L('Remove one copy of the selected object'), sub { - $self->decrease; - }, undef, 'delete.png'); - $frame->_append_menu_item($menu, L("Set number of copies…"), L('Change the number of copies of the selected object'), sub { - $self->set_number_of_copies; - }, undef, 'textfield.png'); - $menu->AppendSeparator(); - $frame->_append_menu_item($menu, $accel->(L('Rotate 45° clockwise'), 'l'), L('Rotate the selected object by 45° clockwise'), sub { - $self->rotate(-45, Z, 'relative'); - }, undef, 'arrow_rotate_clockwise.png'); - $frame->_append_menu_item($menu, $accel->(L('Rotate 45° counter-clockwise'), 'r'), L('Rotate the selected object by 45° counter-clockwise'), sub { - $self->rotate(+45, Z, 'relative'); - }, undef, 'arrow_rotate_anticlockwise.png'); - - my $rotateMenu = Wx::Menu->new; - my $rotateMenuItem = $menu->AppendSubMenu($rotateMenu, L("Rotate"), L('Rotate the selected object by an arbitrary angle')); - $frame->_set_menu_item_icon($rotateMenuItem, 'textfield.png'); - $frame->_append_menu_item($rotateMenu, L("Around X axis…"), L('Rotate the selected object by an arbitrary angle around X axis'), sub { - $self->rotate(undef, X); - }, undef, 'bullet_red.png'); - $frame->_append_menu_item($rotateMenu, L("Around Y axis…"), L('Rotate the selected object by an arbitrary angle around Y axis'), sub { - $self->rotate(undef, Y); - }, undef, 'bullet_green.png'); - $frame->_append_menu_item($rotateMenu, L("Around Z axis…"), L('Rotate the selected object by an arbitrary angle around Z axis'), sub { - $self->rotate(undef, Z); - }, undef, 'bullet_blue.png'); - - my $mirrorMenu = Wx::Menu->new; - my $mirrorMenuItem = $menu->AppendSubMenu($mirrorMenu, L("Mirror"), L('Mirror the selected object')); - $frame->_set_menu_item_icon($mirrorMenuItem, 'shape_flip_horizontal.png'); - $frame->_append_menu_item($mirrorMenu, L("Along X axis…"), L('Mirror the selected object along the X axis'), sub { - $self->mirror(X); - }, undef, 'bullet_red.png'); - $frame->_append_menu_item($mirrorMenu, L("Along Y axis…"), L('Mirror the selected object along the Y axis'), sub { - $self->mirror(Y); - }, undef, 'bullet_green.png'); - $frame->_append_menu_item($mirrorMenu, L("Along Z axis…"), L('Mirror the selected object along the Z axis'), sub { - $self->mirror(Z); - }, undef, 'bullet_blue.png'); - - my $scaleMenu = Wx::Menu->new; - my $scaleMenuItem = $menu->AppendSubMenu($scaleMenu, L("Scale"), L('Scale the selected object along a single axis')); - $frame->_set_menu_item_icon($scaleMenuItem, 'arrow_out.png'); - $frame->_append_menu_item($scaleMenu, $accel->(L('Uniformly…'), 's'), L('Scale the selected object along the XYZ axes'), sub { - $self->changescale(undef); - }); - $frame->_append_menu_item($scaleMenu, L("Along X axis…"), L('Scale the selected object along the X axis'), sub { - $self->changescale(X); - }, undef, 'bullet_red.png'); - $frame->_append_menu_item($scaleMenu, L("Along Y axis…"), L('Scale the selected object along the Y axis'), sub { - $self->changescale(Y); - }, undef, 'bullet_green.png'); - $frame->_append_menu_item($scaleMenu, L("Along Z axis…"), L('Scale the selected object along the Z axis'), sub { - $self->changescale(Z); - }, undef, 'bullet_blue.png'); - - my $scaleToSizeMenu = Wx::Menu->new; - my $scaleToSizeMenuItem = $menu->AppendSubMenu($scaleToSizeMenu, L("Scale to size"), L('Scale the selected object along a single axis')); - $frame->_set_menu_item_icon($scaleToSizeMenuItem, 'arrow_out.png'); - $frame->_append_menu_item($scaleToSizeMenu, L("Uniformly…"), L('Scale the selected object along the XYZ axes'), sub { - $self->changescale(undef, 1); - }); - $frame->_append_menu_item($scaleToSizeMenu, L("Along X axis…"), L('Scale the selected object along the X axis'), sub { - $self->changescale(X, 1); - }, undef, 'bullet_red.png'); - $frame->_append_menu_item($scaleToSizeMenu, L("Along Y axis…"), L('Scale the selected object along the Y axis'), sub { - $self->changescale(Y, 1); - }, undef, 'bullet_green.png'); - $frame->_append_menu_item($scaleToSizeMenu, L("Along Z axis…"), L('Scale the selected object along the Z axis'), sub { - $self->changescale(Z, 1); - }, undef, 'bullet_blue.png'); - - $frame->_append_menu_item($menu, L("Split"), L('Split the selected object into individual parts'), sub { - $self->split_object; - }, undef, 'shape_ungroup.png'); - $frame->_append_menu_item($menu, L("Cut…"), L('Open the 3D cutting tool'), sub { - $self->object_cut_dialog; - }, undef, 'package.png'); - $menu->AppendSeparator(); - $frame->_append_menu_item($menu, L("Settings…"), L('Open the object editor dialog'), sub { - $self->object_settings_dialog; - }, undef, 'cog.png'); - $menu->AppendSeparator(); - $frame->_append_menu_item($menu, L("Reload from Disk"), L('Reload the selected file from Disk'), sub { - $self->reload_from_disk; - }, undef, 'arrow_refresh.png'); - $frame->_append_menu_item($menu, L("Export object as STL…"), L('Export this single object as STL file'), sub { - $self->export_object_stl; - }, undef, 'brick_go.png'); - if (Slic3r::GUI::is_windows10) { - $frame->_append_menu_item($menu, L("Fix STL through Netfabb"), L('Fix the model by sending it to a Netfabb cloud service through Windows 10 API'), sub { - $self->fix_through_netfabb; - }, undef, 'brick_go.png'); - } - - return $menu; -} - -# XXX: not done -# Set a camera direction, zoom to all objects. -sub select_view { - my ($self, $direction) = @_; - my $idx_page = $self->{preview_notebook}->GetSelection; - my $page = ($idx_page == &Wx::wxNOT_FOUND) ? L('3D') : $self->{preview_notebook}->GetPageText($idx_page); - if ($page eq L('Preview')) { - $self->{preview_iface}->select_view($direction); - $self->{preview_iface}->set_viewport_into_scene($self->{canvas3D}); -# Slic3r::GUI::_3DScene::select_view($self->{preview3D}->canvas, $direction); -# Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{canvas3D}, $self->{preview3D}->canvas); - } else { - Slic3r::GUI::_3DScene::select_view($self->{canvas3D}, $direction); - $self->{preview_iface}->set_viewport_from_scene($self->{canvas3D}); -# Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{preview3D}->canvas, $self->{canvas3D}); - } -} - - -# XXX: VK: done, in PlaterDropTarget -package Slic3r::GUI::Plater::DropTarget; -use Wx::DND; -use base 'Wx::FileDropTarget'; - -sub new { - my ($class, $window) = @_; - my $self = $class->SUPER::new; - $self->{window} = $window; - return $self; -} - -sub OnDropFiles { - my ($self, $x, $y, $filenames) = @_; - # stop scalars leaking on older perl - # https://rt.perl.org/rt3/Public/Bug/Display.html?id=70602 - @_ = (); - # only accept STL, OBJ, AMF, 3MF and PRUSA files - return 0 if grep !/\.(?:[sS][tT][lL]|[oO][bB][jJ]|[aA][mM][fF]|[3][mM][fF]|[aA][mM][fF].[xX][mM][lL]|[zZ][iI][pP].[aA][mM][lL]|[pP][rR][uU][sS][aA])$/, @$filenames; - $self->{window}->load_files($filenames); -} - -# 2D preview of an object. Each object is previewed by its convex hull. -package Slic3r::GUI::Plater::Object; -use Moo; - -has 'name' => (is => 'rw', required => 1); -has 'selected' => (is => 'rw', default => sub { 0 }); - -1; diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm deleted file mode 100644 index 0b770b31cb..0000000000 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ /dev/null @@ -1,26 +0,0 @@ -package Slic3r::GUI::Plater::3D; -use strict; -use warnings; -use utf8; - -use List::Util qw(); -use Wx qw(:misc :pen :brush :sizer :font :cursor :keycode wxTAB_TRAVERSAL); -use base qw(Slic3r::GUI::3DScene Class::Accessor); - -sub new { - my $class = shift; - my ($parent, $objects, $model, $print, $config) = @_; - - my $self = $class->SUPER::new($parent); - Slic3r::GUI::_3DScene::enable_picking($self, 1); - Slic3r::GUI::_3DScene::enable_moving($self, 1); - Slic3r::GUI::_3DScene::set_select_by($self, 'object'); - Slic3r::GUI::_3DScene::set_drag_by($self, 'instance'); - Slic3r::GUI::_3DScene::set_model($self, $model); - Slic3r::GUI::_3DScene::set_print($self, $print); - Slic3r::GUI::_3DScene::set_config($self, $config); - - return $self; -} - -1; diff --git a/lib/Slic3r/GUI/Plater/3DPreview.pm b/lib/Slic3r/GUI/Plater/3DPreview.pm deleted file mode 100644 index 06d1a798b5..0000000000 --- a/lib/Slic3r/GUI/Plater/3DPreview.pm +++ /dev/null @@ -1,555 +0,0 @@ -package Slic3r::GUI::Plater::3DPreview; -use strict; -use warnings; -use utf8; - -use Slic3r::Print::State ':steps'; -use Wx qw(:misc :sizer :slider :statictext :keycode wxWHITE wxCB_READONLY); -use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN EVT_CHECKBOX EVT_CHOICE EVT_CHECKLISTBOX EVT_SIZE); -use base qw(Wx::Panel Class::Accessor); - -use Wx::Locale gettext => 'L'; - -__PACKAGE__->mk_accessors(qw(print gcode_preview_data enabled _loaded canvas slider_low slider_high single_layer double_slider_sizer)); - -sub new { - my $class = shift; - my ($parent, $print, $gcode_preview_data, $config) = @_; - - my $self = $class->SUPER::new($parent, -1, wxDefaultPosition); - $self->{config} = $config; - $self->{number_extruders} = 1; - # Show by feature type by default. - $self->{preferred_color_mode} = 'feature'; - - # init GUI elements - my $canvas = Slic3r::GUI::3DScene->new($self); - Slic3r::GUI::_3DScene::enable_shader($canvas, 1); - Slic3r::GUI::_3DScene::set_config($canvas, $config); - $self->canvas($canvas); -# my $slider_low = Wx::Slider->new( -# $self, -1, -# 0, # default -# 0, # min - # we set max to a bogus non-zero value because the MSW implementation of wxSlider - # will skip drawing the slider if max <= min: -# 1, # max -# wxDefaultPosition, -# wxDefaultSize, -# wxVERTICAL | wxSL_INVERSE, -# ); -# $self->slider_low($slider_low); -# my $slider_high = Wx::Slider->new( -# $self, -1, -# 0, # default -# 0, # min - # we set max to a bogus non-zero value because the MSW implementation of wxSlider - # will skip drawing the slider if max <= min: -# 1, # max -# wxDefaultPosition, -# wxDefaultSize, -# wxVERTICAL | wxSL_INVERSE, -# ); -# $self->slider_high($slider_high); - -# my $z_label_low = $self->{z_label_low} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, -# [40,-1], wxALIGN_CENTRE_HORIZONTAL); -# $z_label_low->SetFont($Slic3r::GUI::small_font); -# my $z_label_high = $self->{z_label_high} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, -# [40,-1], wxALIGN_CENTRE_HORIZONTAL); -# $z_label_high->SetFont($Slic3r::GUI::small_font); - -# my $z_label_low_idx = $self->{z_label_low_idx} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, -# [40,-1], wxALIGN_CENTRE_HORIZONTAL); -# $z_label_low_idx->SetFont($Slic3r::GUI::small_font); -# my $z_label_high_idx = $self->{z_label_high_idx} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, -# [40,-1], wxALIGN_CENTRE_HORIZONTAL); -# $z_label_high_idx->SetFont($Slic3r::GUI::small_font); - -# $self->single_layer(0); -# my $checkbox_singlelayer = $self->{checkbox_singlelayer} = Wx::CheckBox->new($self, -1, L("1 Layer")); - - my $label_view_type = $self->{label_view_type} = Wx::StaticText->new($self, -1, L("View")); - - my $choice_view_type = $self->{choice_view_type} = Wx::Choice->new($self, -1); - $choice_view_type->Append(L("Feature type")); - $choice_view_type->Append(L("Height")); - $choice_view_type->Append(L("Width")); - $choice_view_type->Append(L("Speed")); - $choice_view_type->Append(L("Volumetric flow rate")); - $choice_view_type->Append(L("Tool")); - $choice_view_type->SetSelection(0); - - # the following value needs to be changed if new items are added into $choice_view_type before "Tool" - $self->{tool_idx} = 5; - - my $label_show_features = $self->{label_show_features} = Wx::StaticText->new($self, -1, L("Show")); - - my $combochecklist_features = $self->{combochecklist_features} = Wx::ComboCtrl->new(); - $combochecklist_features->Create($self, -1, L("Feature types"), wxDefaultPosition, [200, -1], wxCB_READONLY); - my $feature_text = L("Feature types"); - my $feature_items = L("Perimeter")."|" - .L("External perimeter")."|" - .L("Overhang perimeter")."|" - .L("Internal infill")."|" - .L("Solid infill")."|" - .L("Top solid infill")."|" - .L("Bridge infill")."|" - .L("Gap fill")."|" - .L("Skirt")."|" - .L("Support material")."|" - .L("Support material interface")."|" - .L("Wipe tower")."|" - .L("Custom"); - Slic3r::GUI::create_combochecklist($combochecklist_features, $feature_text, $feature_items, 1); - - my $double_slider_sizer = Wx::BoxSizer->new(wxHORIZONTAL); - Slic3r::GUI::create_double_slider($self, $double_slider_sizer, $self->canvas); - $self->double_slider_sizer($double_slider_sizer); - - my $checkbox_travel = $self->{checkbox_travel} = Wx::CheckBox->new($self, -1, L("Travel")); - my $checkbox_retractions = $self->{checkbox_retractions} = Wx::CheckBox->new($self, -1, L("Retractions")); - my $checkbox_unretractions = $self->{checkbox_unretractions} = Wx::CheckBox->new($self, -1, L("Unretractions")); - my $checkbox_shells = $self->{checkbox_shells} = Wx::CheckBox->new($self, -1, L("Shells")); - -# my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); -# my $vsizer = Wx::BoxSizer->new(wxVERTICAL); -# my $vsizer_outer = Wx::BoxSizer->new(wxVERTICAL); -# $vsizer->Add($slider_low, 3, wxALIGN_CENTER_HORIZONTAL, 0); -# $vsizer->Add($z_label_low_idx, 0, wxALIGN_CENTER_HORIZONTAL, 0); -# $vsizer->Add($z_label_low, 0, wxALIGN_CENTER_HORIZONTAL, 0); -# $hsizer->Add($vsizer, 0, wxEXPAND, 0); -# $vsizer = Wx::BoxSizer->new(wxVERTICAL); -# $vsizer->Add($slider_high, 3, wxALIGN_CENTER_HORIZONTAL, 0); -# $vsizer->Add($z_label_high_idx, 0, wxALIGN_CENTER_HORIZONTAL, 0); -# $vsizer->Add($z_label_high, 0, 0, 0); -# $hsizer->Add($vsizer, 0, wxEXPAND, 0); -# $vsizer_outer->Add($hsizer, 3, wxALIGN_CENTER_HORIZONTAL, 0); -# $vsizer_outer->Add($double_slider_sizer, 3, wxEXPAND, 0); -# $vsizer_outer->Add($checkbox_singlelayer, 0, wxTOP | wxALIGN_CENTER_HORIZONTAL, 5); - - my $bottom_sizer = Wx::BoxSizer->new(wxHORIZONTAL); - $bottom_sizer->Add($label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); - $bottom_sizer->Add($choice_view_type, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5); - $bottom_sizer->AddSpacer(10); - $bottom_sizer->Add($label_show_features, 0, wxALIGN_CENTER_VERTICAL, 5); - $bottom_sizer->Add($combochecklist_features, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5); - $bottom_sizer->AddSpacer(20); - $bottom_sizer->Add($checkbox_travel, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5); - $bottom_sizer->AddSpacer(10); - $bottom_sizer->Add($checkbox_retractions, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5); - $bottom_sizer->AddSpacer(10); - $bottom_sizer->Add($checkbox_unretractions, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5); - $bottom_sizer->AddSpacer(10); - $bottom_sizer->Add($checkbox_shells, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5); - - my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); - $sizer->Add($canvas, 1, wxALL | wxEXPAND, 0); -# $sizer->Add($vsizer_outer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5); - $sizer->Add($double_slider_sizer, 0, wxEXPAND, 0);#wxTOP | wxBOTTOM | wxEXPAND, 5); - - my $main_sizer = Wx::BoxSizer->new(wxVERTICAL); - $main_sizer->Add($sizer, 1, wxALL | wxEXPAND, 0); - $main_sizer->Add($bottom_sizer, 0, wxALL | wxEXPAND, 0); - -# EVT_SLIDER($self, $slider_low, sub { -# $slider_high->SetValue($slider_low->GetValue) if $self->single_layer; -# $self->set_z_idx_low ($slider_low ->GetValue) -# }); -# EVT_SLIDER($self, $slider_high, sub { -# $slider_low->SetValue($slider_high->GetValue) if $self->single_layer; -# $self->set_z_idx_high($slider_high->GetValue) -# }); -# EVT_KEY_DOWN($canvas, sub { -# my ($s, $event) = @_; -# Slic3r::GUI::update_double_slider_from_canvas($event); -# my $key = $event->GetKeyCode; -# if ($event->HasModifiers) { -# $event->Skip; -# } else { -# if ($key == ord('U')) { -# $slider_high->SetValue($slider_high->GetValue + 1); -# $slider_low->SetValue($slider_high->GetValue) if ($event->ShiftDown()); -# $self->set_z_idx_high($slider_high->GetValue); -# } elsif ($key == ord('D')) { -# $slider_high->SetValue($slider_high->GetValue - 1); -# $slider_low->SetValue($slider_high->GetValue) if ($event->ShiftDown()); -# $self->set_z_idx_high($slider_high->GetValue); -# } elsif ($key == ord('S')) { -# $checkbox_singlelayer->SetValue(! $checkbox_singlelayer->GetValue()); -# $self->single_layer($checkbox_singlelayer->GetValue()); -# if ($self->single_layer) { -# $slider_low->SetValue($slider_high->GetValue); -# $self->set_z_idx_high($slider_high->GetValue); -# } -# } else { -# $event->Skip; -# } -# } -# }); -# EVT_KEY_DOWN($slider_low, sub { -# my ($s, $event) = @_; -# my $key = $event->GetKeyCode; -# if ($event->HasModifiers) { -# $event->Skip; -# } else { -# if ($key == WXK_LEFT) { -# } elsif ($key == WXK_RIGHT) { -# $slider_high->SetFocus; -# } else { -# $event->Skip; -# } -# } -# }); -# EVT_KEY_DOWN($slider_high, sub { -# my ($s, $event) = @_; -# my $key = $event->GetKeyCode; -# if ($event->HasModifiers) { -# $event->Skip; -# } else { -# if ($key == WXK_LEFT) { -# $slider_low->SetFocus; -# } elsif ($key == WXK_RIGHT) { -# } else { -# $event->Skip; -# } -# } -# }); -# EVT_CHECKBOX($self, $checkbox_singlelayer, sub { -# $self->single_layer($checkbox_singlelayer->GetValue()); -# if ($self->single_layer) { -# $slider_low->SetValue($slider_high->GetValue); -# $self->set_z_idx_high($slider_high->GetValue); -# } -# }); - EVT_CHOICE($self, $choice_view_type, sub { - my $selection = $choice_view_type->GetCurrentSelection(); - $self->{preferred_color_mode} = ($selection == $self->{tool_idx}) ? 'tool' : 'feature'; - $self->gcode_preview_data->set_type($selection); - $self->reload_print; - }); - EVT_CHECKLISTBOX($self, $combochecklist_features, sub { - my $flags = Slic3r::GUI::combochecklist_get_flags($combochecklist_features); - - $self->gcode_preview_data->set_extrusion_flags($flags); - $self->refresh_print; - }); - EVT_CHECKBOX($self, $checkbox_travel, sub { - $self->gcode_preview_data->set_travel_visible($checkbox_travel->IsChecked()); - $self->refresh_print; - }); - EVT_CHECKBOX($self, $checkbox_retractions, sub { - $self->gcode_preview_data->set_retractions_visible($checkbox_retractions->IsChecked()); - $self->refresh_print; - }); - EVT_CHECKBOX($self, $checkbox_unretractions, sub { - $self->gcode_preview_data->set_unretractions_visible($checkbox_unretractions->IsChecked()); - $self->refresh_print; - }); - EVT_CHECKBOX($self, $checkbox_shells, sub { - $self->gcode_preview_data->set_shells_visible($checkbox_shells->IsChecked()); - $self->refresh_print; - }); - - EVT_SIZE($self, sub { - my ($s, $event) = @_; - $event->Skip; - $self->Refresh; - }); - - $self->SetSizer($main_sizer); - $self->SetMinSize($self->GetSize); - $sizer->SetSizeHints($self); - - # init canvas - $self->print($print); - $self->gcode_preview_data($gcode_preview_data); - - # sets colors for gcode preview extrusion roles - my @extrusion_roles_colors = ( - 'Perimeter' => 'FFFF66', - 'External perimeter' => 'FFA500', - 'Overhang perimeter' => '0000FF', - 'Internal infill' => 'B1302A', - 'Solid infill' => 'D732D7', - 'Top solid infill' => 'FF1A1A', - 'Bridge infill' => '9999FF', - 'Gap fill' => 'FFFFFF', - 'Skirt' => '845321', - 'Support material' => '00FF00', - 'Support material interface' => '008000', - 'Wipe tower' => 'B3E3AB', - 'Custom' => '28CC94', - ); - $self->gcode_preview_data->set_extrusion_paths_colors(\@extrusion_roles_colors); - - $self->show_hide_ui_elements('none'); - $self->reload_print; - - return $self; -} - -sub reload_print { - my ($self, $force) = @_; - - Slic3r::GUI::_3DScene::reset_volumes($self->canvas); - $self->_loaded(0); - - if (! $self->IsShown && ! $force) { -# $self->{reload_delayed} = 1; - return; - } - - $self->load_print; -} - -sub refresh_print { - my ($self) = @_; - - $self->_loaded(0); - - if (! $self->IsShown) { - return; - } - - $self->load_print; -} - -sub reset_gcode_preview_data { - my ($self) = @_; - $self->gcode_preview_data->reset; - Slic3r::GUI::_3DScene::reset_legend_texture(); -} - -sub load_print { - my ($self) = @_; - - return if $self->_loaded; - - # we require that there's at least one object and the posSlice step - # is performed on all of them (this ensures that _shifted_copies was - # populated and we know the number of layers) - my $n_layers = 0; - if ($self->print->object_step_done(STEP_SLICE)) { - my %z = (); # z => 1 - foreach my $object (@{$self->{print}->objects}) { - foreach my $layer (@{$object->layers}, @{$object->support_layers}) { - $z{$layer->print_z} = 1; - } - } - $self->{layers_z} = [ sort { $a <=> $b } keys %z ]; - $n_layers = scalar(@{$self->{layers_z}}); - } - - if ($n_layers == 0) { - $self->reset_sliders; - Slic3r::GUI::_3DScene::reset_legend_texture(); - $self->canvas->Refresh; # clears canvas - return; - } - - if ($self->{preferred_color_mode} eq 'tool_or_feature') { - # It is left to Slic3r to decide whether the print shall be colored by the tool or by the feature. - # Color by feature if it is a single extruder print. - my $extruders = $self->{print}->extruders; - my $type = (scalar(@{$extruders}) > 1) ? $self->{tool_idx} : 0; - $self->gcode_preview_data->set_type($type); - $self->{choice_view_type}->SetSelection($type); - # If the ->SetSelection changed the following line, revert it to "decide yourself". - $self->{preferred_color_mode} = 'tool_or_feature'; - } - - # Collect colors per extruder. - my @colors = (); - if (! $self->gcode_preview_data->empty() || $self->gcode_preview_data->type == $self->{tool_idx}) { - my @extruder_colors = @{$self->{config}->extruder_colour}; - my @filament_colors = @{$self->{config}->filament_colour}; - for (my $i = 0; $i <= $#extruder_colors; $i += 1) { - my $color = $extruder_colors[$i]; - $color = $filament_colors[$i] if (! defined($color) || $color !~ m/^#[[:xdigit:]]{6}/); - $color = '#FFFFFF' if (! defined($color) || $color !~ m/^#[[:xdigit:]]{6}/); - push @colors, $color; - } - } - - if ($self->IsShown) { - # used to set the sliders to the extremes of the current zs range - $self->{force_sliders_full_range} = 0; - - if ($self->gcode_preview_data->empty) { - # load skirt and brim - Slic3r::GUI::_3DScene::set_print($self->canvas, $self->print); - Slic3r::GUI::_3DScene::load_preview($self->canvas, \@colors); - $self->show_hide_ui_elements('simple'); - } else { - $self->{force_sliders_full_range} = (Slic3r::GUI::_3DScene::get_volumes_count($self->canvas) == 0); - Slic3r::GUI::_3DScene::set_print($self->canvas, $self->print); - Slic3r::GUI::_3DScene::load_gcode_preview($self->canvas, $self->gcode_preview_data, \@colors); - $self->show_hide_ui_elements('full'); - - # recalculates zs and update sliders accordingly - $self->{layers_z} = Slic3r::GUI::_3DScene::get_current_print_zs($self->canvas, 1); - $n_layers = scalar(@{$self->{layers_z}}); - if ($n_layers == 0) { - # all layers filtered out - $self->reset_sliders; - $self->canvas->Refresh; # clears canvas - } - } - - $self->update_sliders($n_layers) if ($n_layers > 0); - $self->_loaded(1); - } -} - -sub reset_sliders { - my ($self) = @_; - $self->enabled(0); -# $self->set_z_range(0,0); -# $self->slider_low->Hide; -# $self->slider_high->Hide; -# $self->{z_label_low}->SetLabel(""); -# $self->{z_label_high}->SetLabel(""); -# $self->{z_label_low_idx}->SetLabel(""); -# $self->{z_label_high_idx}->SetLabel(""); - - Slic3r::GUI::reset_double_slider(); - $self->double_slider_sizer->Hide(0); -} - -sub update_sliders -{ - my ($self, $n_layers) = @_; - -# my $z_idx_low = $self->slider_low->GetValue; -# my $z_idx_high = $self->slider_high->GetValue; - $self->enabled(1); -# $self->slider_low->SetRange(0, $n_layers - 1); -# $self->slider_high->SetRange(0, $n_layers - 1); - -# if ($self->{force_sliders_full_range}) { -# $z_idx_low = 0; -# $z_idx_high = $n_layers - 1; -# } elsif ($z_idx_high < $n_layers && ($self->single_layer || $z_idx_high != 0)) { -# # search new indices for nearest z (size of $self->{layers_z} may change in dependence of what is shown) -# if (defined($self->{z_low})) { -# for (my $i = scalar(@{$self->{layers_z}}) - 1; $i >= 0; $i -= 1) { -# if ($self->{layers_z}[$i] <= $self->{z_low}) { -# $z_idx_low = $i; -# last; -# } -# } -# } -# if (defined($self->{z_high})) { -# for (my $i = scalar(@{$self->{layers_z}}) - 1; $i >= 0; $i -= 1) { -# if ($self->{layers_z}[$i] <= $self->{z_high}) { -# $z_idx_high = $i; -# last; -# } -# } -# } -# } elsif ($z_idx_high >= $n_layers) { -# # Out of range. Disable 'single layer' view. -# $self->single_layer(0); -# $self->{checkbox_singlelayer}->SetValue(0); -# $z_idx_low = 0; -# $z_idx_high = $n_layers - 1; -# } else { -# $z_idx_low = 0; -# $z_idx_high = $n_layers - 1; -# } - -# $self->slider_low->SetValue($z_idx_low); -# $self->slider_high->SetValue($z_idx_high); -# $self->slider_low->Show; -# $self->slider_high->Show; -# $self->set_z_range($self->{layers_z}[$z_idx_low], $self->{layers_z}[$z_idx_high]); - - Slic3r::GUI::update_double_slider($self->{force_sliders_full_range}); - $self->double_slider_sizer->Show(0); - - $self->Layout; -} - -sub set_z_range -{ - my ($self, $z_low, $z_high) = @_; - - return if !$self->enabled; - $self->{z_low} = $z_low; - $self->{z_high} = $z_high; - $self->{z_label_low}->SetLabel(sprintf '%.2f', $z_low); - $self->{z_label_high}->SetLabel(sprintf '%.2f', $z_high); - - my $layers_z = Slic3r::GUI::_3DScene::get_current_print_zs($self->canvas, 0); - for (my $i = 0; $i < scalar(@{$layers_z}); $i += 1) { - if (($z_low - 1e-6 < @{$layers_z}[$i]) && (@{$layers_z}[$i] < $z_low + 1e-6)) { - $self->{z_label_low_idx}->SetLabel(sprintf '%d', $i + 1); - last; - } - } - for (my $i = 0; $i < scalar(@{$layers_z}); $i += 1) { - if (($z_high - 1e-6 < @{$layers_z}[$i]) && (@{$layers_z}[$i] < $z_high + 1e-6)) { - $self->{z_label_high_idx}->SetLabel(sprintf '%d', $i + 1); - last; - } - } - - Slic3r::GUI::_3DScene::set_toolpaths_range($self->canvas, $z_low - 1e-6, $z_high + 1e-6); - $self->canvas->Refresh if $self->IsShown; -} - -sub set_z_idx_low -{ - my ($self, $idx_low) = @_; - if ($self->enabled) { - my $idx_high = $self->slider_high->GetValue; - if ($idx_low >= $idx_high) { - $idx_high = $idx_low; - $self->slider_high->SetValue($idx_high); - } - $self->set_z_range($self->{layers_z}[$idx_low], $self->{layers_z}[$idx_high]); - } -} - -sub set_z_idx_high -{ - my ($self, $idx_high) = @_; - if ($self->enabled) { - my $idx_low = $self->slider_low->GetValue; - if ($idx_low > $idx_high) { - $idx_low = $idx_high; - $self->slider_low->SetValue($idx_low); - } - $self->set_z_range($self->{layers_z}[$idx_low], $self->{layers_z}[$idx_high]); - } -} - -sub set_number_extruders { - my ($self, $number_extruders) = @_; - if ($self->{number_extruders} != $number_extruders) { - $self->{number_extruders} = $number_extruders; - my $type = ($number_extruders > 1) ? - $self->{tool_idx} # color by a tool number - : 0; # color by a feature type - $self->{choice_view_type}->SetSelection($type); - $self->gcode_preview_data->set_type($type); - $self->{preferred_color_mode} = ($type == $self->{tool_idx}) ? 'tool_or_feature' : 'feature'; - } -} - -sub show_hide_ui_elements { - my ($self, $what) = @_; - my $method = ($what eq 'full') ? 'Enable' : 'Disable'; - $self->{$_}->$method for qw(label_show_features combochecklist_features checkbox_travel checkbox_retractions checkbox_unretractions checkbox_shells); - $method = ($what eq 'none') ? 'Disable' : 'Enable'; - $self->{$_}->$method for qw(label_view_type choice_view_type); -} - -# Called by the Platter wxNotebook when this page is activated. -sub OnActivate { -# my ($self) = @_; -# $self->reload_print(1) if ($self->{reload_delayed}); -} - -1; diff --git a/resources/fonts/NotoSans-hinted.zip b/resources/fonts/NotoSans-hinted.zip deleted file mode 100644 index 861c1be8c4..0000000000 Binary files a/resources/fonts/NotoSans-hinted.zip and /dev/null differ diff --git a/resources/icons/bed/mk2.svg b/resources/icons/bed/mk2.svg new file mode 100644 index 0000000000..b8fa8d0cd7 --- /dev/null +++ b/resources/icons/bed/mk2.svg @@ -0,0 +1 @@ +MK2_bottom \ No newline at end of file diff --git a/resources/icons/bed/mk3.svg b/resources/icons/bed/mk3.svg new file mode 100644 index 0000000000..c8f53373b5 --- /dev/null +++ b/resources/icons/bed/mk3.svg @@ -0,0 +1 @@ +MK3_bottom \ No newline at end of file diff --git a/resources/icons/bed/sl1.svg b/resources/icons/bed/sl1.svg new file mode 100644 index 0000000000..c1098b9df8 --- /dev/null +++ b/resources/icons/bed/sl1.svg @@ -0,0 +1 @@ +SL1_Bottom \ No newline at end of file diff --git a/resources/localization/uk/Slic3rPE.mo b/resources/localization/uk/Slic3rPE.mo index 56af728d6e..08bf555b58 100644 Binary files a/resources/localization/uk/Slic3rPE.mo and b/resources/localization/uk/Slic3rPE.mo differ diff --git a/resources/localization/uk/Slic3rPE_uk.po b/resources/localization/uk/Slic3rPE_uk.po index 9db2b919af..7880453bba 100644 --- a/resources/localization/uk/Slic3rPE_uk.po +++ b/resources/localization/uk/Slic3rPE_uk.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2019-01-17 13:39+0100\n" -"PO-Revision-Date: 2019-01-21 11:25+0100\n" +"PO-Revision-Date: 2019-02-19 14:42+0100\n" "Last-Translator: Oleksandra Iushchenko \n" "Language-Team: \n" "Language: uk\n" @@ -624,48 +624,48 @@ msgstr "" #: src/slic3r/GUI/GLGizmo.cpp:2207 msgid "Left mouse click - add point" -msgstr "" +msgstr "Ліва кнопка миші - додати точку" #: src/slic3r/GUI/GLGizmo.cpp:2208 msgid "Right mouse click - remove point" -msgstr "" +msgstr "Права кнопка миші - видалити точку" #: src/slic3r/GUI/GLGizmo.cpp:2211 msgid "Generate points automatically" -msgstr "" +msgstr "Генерувати точки автоматично" #: src/slic3r/GUI/GLGizmo.cpp:2212 msgid "Remove all points" -msgstr "" +msgstr "Видалити всі точки" #: src/slic3r/GUI/GLGizmo.cpp:2245 msgid "SLA Support Points" -msgstr "" +msgstr "Точки SLA підтримки" #: src/slic3r/GUI/GLGizmo.cpp:2268 src/slic3r/GUI/GLGizmo.cpp:2468 msgid "Rotate lower part upwards" -msgstr "" +msgstr "Повернути нижню частину вгору" #: src/slic3r/GUI/GLGizmo.cpp:2269 src/slic3r/GUI/GLGizmo.cpp:2470 msgid "Perform cut" -msgstr "" +msgstr "Виконати розріз" #: src/slic3r/GUI/GLGizmo.cpp:2276 msgid "Cut object:" -msgstr "" +msgstr "Розрізати об'єкт:" #: src/slic3r/GUI/GLGizmo.cpp:2356 src/slic3r/GUI/GLGizmo.cpp:2461 #: src/libslic3r/PrintConfig.cpp:3016 msgid "Cut" -msgstr "" +msgstr "Розрізати" #: src/slic3r/GUI/GLGizmo.cpp:2466 msgid "Keep upper part" -msgstr "" +msgstr "Залишити верхню частину" #: src/slic3r/GUI/GLGizmo.cpp:2467 msgid "Keep lower part" -msgstr "" +msgstr "Залишити нижню частину" #: src/slic3r/GUI/GUI.cpp:242 msgid "Notice" @@ -5636,7 +5636,7 @@ msgstr "" #: src/libslic3r/PrintConfig.cpp:3017 msgid "Cut model at the given Z." -msgstr "" +msgstr "Розрізати модель за заданим Z." #: src/libslic3r/PrintConfig.cpp:3022 msgid "Dont arrange" diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index a8d169c684..407883544a 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,8 +1,15 @@ +min_slic3r_version = 1.42.0-alpha6 +0.8.0-alpha7 +0.8.0-alpha6 min_slic3r_version = 1.42.0-alpha +0.8.0-alpha 0.4.0-alpha4 Updated SLA profiles 0.4.0-alpha3 Update of SLA profiles 0.4.0-alpha2 First SLA profiles +min_slic3r_version = 1.41.3-alpha +0.4.0 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt min_slic3r_version = 1.41.1 +0.3.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt 0.3.3 Prusament PETG released 0.3.2 New MK2.5 and MK3 FW versions 0.3.1 New MK2.5 and MK3 FW versions @@ -34,6 +41,7 @@ min_slic3r_version = 1.41.0-alpha 0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters min_slic3r_version = 1.40.0 +0.1.12 New MK2.5 and MK3 FW versions 0.1.11 fw version changed to 3.3.1 0.1.10 MK3 jerk and acceleration update 0.1.9 edited support extrusion width for 0.25 and 0.6 nozzles diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 306867044b..a471700fb8 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -1,1694 +1,2034 @@ -# Print profiles for the Prusa Research printers. - -[vendor] -# Vendor name will be shown by the Config Wizard. -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.4.0-alpha4 -# Where to get the updates from? -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 -technology = FFF - -[printer_model:MK2.5] -name = Original Prusa i3 MK2.5 -variants = 0.4; 0.25; 0.6 -technology = FFF - -[printer_model:MK2S] -name = Original Prusa i3 MK2/S -variants = 0.4; 0.25; 0.6 -technology = FFF - -[printer_model:MK3MMU2] -name = Original Prusa i3 MK3 MMU 2.0 -variants = 0.4 -technology = FFF - -[printer_model:MK2SMM] -name = Original Prusa i3 MK2/S MMU 1.0 -variants = 0.4; 0.6 -technology = FFF - -[printer_model:MK2.5MMU2] -name = Original Prusa i3 MK2.5 MMU 2.0 -variants = 0.4 -technology = FFF - -[printer_model:SL1] -name = Original Prusa SL1 -variants = default -technology = SLA - -# 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 = 20 -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}_{layer_height}mm_{filament_type[0]}_{printer_model}.gcode -perimeters = 2 -perimeter_extruder = 1 -perimeter_extrusion_width = 0.45 -post_process = -print_settings_id = -raft_layers = 0 -resolution = 0 -seam_position = nearest -single_extruder_multi_material_priming = 1 -skirts = 1 -skirt_distance = 2 -skirt_height = 3 -small_perimeter_speed = 25 -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.1 -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 = 55 -support_material_with_sheath = 0 -support_material_xy_spacing = 50% -thin_walls = 0 -top_infill_extrusion_width = 0.45 -top_solid_infill_speed = 40 -travel_speed = 180 -wipe_tower = 1 -wipe_tower_bridging = 10 -wipe_tower_rotation_angle = 0 -wipe_tower_width = 60 -wipe_tower_x = 170 -wipe_tower_y = 140 -xy_size_compensation = 0 - -[print:*MK3*] -fill_pattern = grid -single_extruder_multi_material_priming = 0 -travel_speed = 180 -wipe_tower_x = 170 -wipe_tower_y = 125 - -# 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.2 -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 -support_material_extrusion_width = 0.55 - -[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 - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.05mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - -[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 = 20 -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* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 -infill_extrusion_width = 0.5 - -[print:0.05mm ULTRADETAIL MK3] -inherits = *0.05mm*; *MK3* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material -top_infill_extrusion_width = 0.4 - -[print:0.05mm ULTRADETAIL 0.25 nozzle] -inherits = *0.05mm*; *0.25nozzle* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 -fill_density = 20% -infill_speed = 20 -max_print_speed = 100 -perimeter_speed = 20 -small_perimeter_speed = 15 -solid_infill_speed = 20 -support_material_speed = 20 - -[print:0.05mm ULTRADETAIL 0.25 nozzle MK3] -inherits = *0.05mm*; *0.25nozzle*; *MK3* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.10mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - -[print:*0.10mm*] -inherits = *common* -bottom_solid_layers = 7 -bridge_flow_ratio = 0.7 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 -layer_height = 0.1 -perimeter_acceleration = 800 -top_solid_layers = 9 - -[print:0.10mm DETAIL] -inherits = *0.10mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 -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*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material -external_perimeter_speed = 35 -infill_acceleration = 1250 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 200 -top_infill_extrusion_width = 0.4 -top_solid_infill_speed = 50 - -[print:0.10mm DETAIL 0.25 nozzle] -inherits = *0.10mm*; *0.25nozzle* -bridge_acceleration = 600 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 -external_perimeter_speed = 20 -infill_acceleration = 1600 -infill_speed = 40 -perimeter_acceleration = 600 -perimeter_speed = 25 -small_perimeter_speed = 15 -solid_infill_speed = 40 -top_solid_infill_speed = 30 - -[print:0.10mm DETAIL 0.25 nozzle MK3] -inherits = *0.10mm*; *0.25nozzle*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 -external_perimeter_speed = 35 -infill_acceleration = 1250 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 200 -top_solid_infill_speed = 50 - -[print:0.10mm DETAIL 0.6 nozzle MK3] -inherits = *0.10mm*; *0.6nozzle*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1250 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 200 -top_solid_infill_speed = 50 - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.15mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - -[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 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 -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* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 -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 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 -external_perimeter_speed = 20 -infill_acceleration = 1600 -infill_speed = 40 -perimeter_acceleration = 600 -perimeter_speed = 25 -small_perimeter_speed = 15 -solid_infill_speed = 40 -top_solid_infill_speed = 30 - -[print:0.15mm OPTIMAL 0.6 nozzle] -inherits = *0.15mm*; *0.6nozzle* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 - -[print:0.15mm OPTIMAL MK3] -inherits = *0.15mm*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 35 -infill_acceleration = 1250 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 200 -top_solid_infill_speed = 50 - -[print:0.15mm OPTIMAL MK3 SOLUBLE FULL] -inherits = 0.15mm OPTIMAL MK3; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder -support_material_extruder = 5 -support_material_interface_extruder = 5 -perimeter_speed = 40 -solid_infill_speed = 40 -top_infill_extrusion_width = 0.45 -top_solid_infill_speed = 30 - -[print:0.15mm OPTIMAL MK3 SOLUBLE INTERFACE] -inherits = 0.15mm OPTIMAL MK3 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 - -[print:0.15mm OPTIMAL SOLUBLE FULL] -inherits = *0.15mm*; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -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 - -[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*; *0.25nozzle*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 -external_perimeter_speed = 35 -infill_acceleration = 1250 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 200 -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*; *0.6nozzle*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1250 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 200 -top_solid_infill_speed = 50 - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.20mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - -[print:0.20mm 100mms Linear Advance] -inherits = *0.20mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 -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*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 -external_perimeter_speed = 35 -infill_acceleration = 1250 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 200 -top_solid_infill_speed = 50 - -[print:0.20mm FAST MK3 SOLUBLE FULL] -inherits = 0.20mm FAST MK3; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder -support_material_extruder = 5 -support_material_interface_extruder = 5 -perimeter_speed = 40 -solid_infill_speed = 40 -top_infill_extrusion_width = 0.45 -top_solid_infill_speed = 30 - -[print:0.20mm FAST MK3 SOLUBLE INTERFACE] -inherits = 0.20mm FAST MK3 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 - -[print:0.20mm NORMAL] -inherits = *0.20mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 - -[print:0.20mm NORMAL 0.6 nozzle] -inherits = *0.20mm*; *0.6nozzle* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 - -[print:0.20mm NORMAL SOLUBLE FULL] -inherits = *0.20mm*; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -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*; *0.6nozzle*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1250 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 200 -top_solid_infill_speed = 50 - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.35mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - -[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 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 -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* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 - -[print:0.35mm FAST sol full 0.6 nozzle] -inherits = *0.35mm*; *0.6nozzle*; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 -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_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% - -# XXXXXXXXXXXXXXXXXXXXXX -# XXX----- MK2.5 ----XXX -# XXXXXXXXXXXXXXXXXXXXXX - -[print:0.15mm 100mms Linear Advance MK2.5] -inherits = 0.15mm 100mms Linear Advance -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 - -[print:0.15mm OPTIMAL MK2.5] -inherits = 0.15mm OPTIMAL -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 - -[print:0.15mm OPTIMAL SOLUBLE FULL MK2.5] -inherits = 0.15mm OPTIMAL SOLUBLE FULL -support_material_extruder = 5 -support_material_interface_extruder = 5 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 - -[print:0.15mm OPTIMAL SOLUBLE INTERFACE MK2.5] -inherits = 0.15mm OPTIMAL SOLUBLE INTERFACE -support_material_extruder = 0 -support_material_interface_extruder = 5 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 - -[print:0.20mm 100mms Linear Advance MK2.5] -inherits = 0.20mm 100mms Linear Advance -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 - -[print:0.20mm NORMAL MK2.5] -inherits = 0.20mm NORMAL -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 - -[print:0.20mm NORMAL SOLUBLE FULL MK2.5] -inherits = 0.20mm NORMAL SOLUBLE FULL -support_material_extruder = 5 -support_material_interface_extruder = 5 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -single_extruder_multi_material_priming = 0 - -[print:0.20mm NORMAL SOLUBLE INTERFACE MK2.5] -inherits = 0.20mm NORMAL SOLUBLE INTERFACE -support_material_extruder = 0 -support_material_interface_extruder = 5 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 -single_extruder_multi_material_priming = 0 - -[print:0.35mm FAST MK2.5] -inherits = 0.35mm FAST -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 -single_extruder_multi_material_priming = 0 - -# XXXXXXxxXXXXXXXXXXXXXX -# XXX--- filament ---XXX -# XXXXXXXXxxXXXXXXXXXXXX - -[filament:*common*] -cooling = 1 -compatible_printers = -# For now, all but selected filaments are disabled for the MMU 2.0 -compatible_printers_condition = ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) -end_filament_gcode = "; Filament-specific end gcode" -extrusion_multiplier = 1 -filament_loading_speed = 28 -filament_loading_speed_start = 3 -filament_unloading_speed = 90 -filament_unloading_speed_start = 100 -filament_toolchange_delay = 0 -filament_cooling_moves = 4 -filament_cooling_initial_speed = 2.2 -filament_cooling_final_speed = 3.4 -filament_load_time = 0 -filament_unload_time = 0 -filament_ramming_parameters = "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" -filament_minimal_purge_on_wipe_tower = 15 -filament_cost = 0 -filament_density = 0 -filament_diameter = 1.75 -filament_notes = "" -filament_settings_id = "" -filament_soluble = 0 -min_print_speed = 15 -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_ramming_parameters = "120 100 5.70968 6.03226 7 8.25806 9 9.19355 9.3871 9.77419 10.129 10.3226 10.4516 10.5161| 0.05 5.69677 0.45 6.15484 0.95 8.76774 1.45 9.20323 1.95 9.95806 2.45 10.3871 2.95 10.5677 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" -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* -bed_temperature = 50 -bridge_fan_speed = 100 -# For now, all but selected filaments are disabled for the MMU 2.0 -compatible_printers_condition = nozzle_diameter[0]>0.35 and num_extruders==1 && ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) -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* -# For now, all but selected filaments are disabled for the MMU 2.0 -compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) -extrusion_multiplier = 1.2 -filament_cost = 80.65 -filament_density = 4 -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 -filament_cost = 58.66 -filament_density = 1.18 -first_layer_bed_temperature = 105 -first_layer_temperature = 270 -max_fan_speed = 20 -min_fan_speed = 10 -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_cost = 55.5 -filament_density = 1.24 - -[filament:ColorFabb Woodfil] -inherits = *PLA* -# For now, all but selected filaments are disabled for the MMU 2.0 -compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) -extrusion_multiplier = 1.2 -filament_cost = 62.9 -filament_density = 1.15 -filament_colour = #804040 -filament_max_volumetric_speed = 10 -first_layer_temperature = 200 -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 = PET -filament_cost = 62.9 -filament_density = 1.27 -first_layer_bed_temperature = 90 -first_layer_temperature = 260 -temperature = 270 - -[filament:ColorFabb XT-CF20] -inherits = *PET* -compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) -extrusion_multiplier = 1.2 -filament_cost = 80.65 -filament_density = 1.35 -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* -filament_cost = 21.2 -filament_density = 1.2 -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* -filament_cost = 0 -filament_density = 1 -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_cost = 56.9 -filament_density = 1.26 -filament_notes = "List of manufacturers tested with standart PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" - -[filament:E3D PC-ABS] -inherits = *ABS* -filament_cost = 0 -filament_density = 1.05 -first_layer_temperature = 270 -temperature = 270 - -[filament:Fillamentum ABS] -inherits = *ABS* -filament_cost = 32.4 -filament_density = 1.04 -first_layer_temperature = 240 -temperature = 240 - -[filament:Fillamentum ASA] -inherits = *ABS* -filament_cost = 38.7 -filament_density = 1.04 -fan_always_on = 1 -first_layer_temperature = 265 -temperature = 265 - -[filament:Fillamentum CPE HG100 HM100] -inherits = *PET* -filament_cost = 54.1 -filament_density = 1.25 -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* -# For now, all but selected filaments are disabled for the MMU 2.0 -compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) -extrusion_multiplier = 1.2 -filament_cost = 68 -filament_density = 1.15 -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_cost = 27.82 -filament_density = 1.04 -filament_notes = "List of materials tested with standart ABS print settings:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladec ABS" - -[filament:Generic PET] -inherits = *PET* -filament_cost = 27.82 -filament_density = 1.27 -filament_notes = "List of manufacturers tested with standart PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" - -[filament:Generic PLA] -inherits = *PLA* -filament_cost = 25.4 -filament_density = 1.24 -filament_notes = "List of materials tested with standart PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" - -[filament:Polymaker PC-Max] -inherits = *ABS* -filament_cost = 77.3 -filament_density = 1.20 -bed_temperature = 115 -filament_colour = #3A80CA -first_layer_bed_temperature = 100 -first_layer_temperature = 270 -temperature = 270 - -[filament:Primavalue PVA] -inherits = *PLA* -filament_cost = 108 -filament_density = 1.23 -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:\n\nPrimaSelect PVA+\nICE FILAMENTS PVA 'NAUGHTY NATURAL'\nVerbatim BVOH" -filament_ramming_parameters = "120 100 8.3871 8.6129 8.93548 9.22581 9.48387 9.70968 9.87097 10.0323 10.2258 10.4194 10.6452 10.8065| 0.05 8.34193 0.45 8.73548 0.95 9.34836 1.45 9.78385 1.95 10.0871 2.45 10.5161 2.95 10.8903 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" -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:Prusa ABS] -inherits = *ABS* -filament_cost = 27.82 -filament_density = 1.08 -filament_notes = "List of materials tested with standart ABS print settings:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladec ABS" - -[filament:*ABS MMU2*] -inherits = Prusa ABS -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -filament_cooling_final_speed = 50 -filament_cooling_initial_speed = 10 -filament_cooling_moves = 5 -filament_ramming_parameters = "120 110 5.32258 5.45161 5.67742 6 6.48387 7.12903 7.90323 8.70968 9.3871 9.83871 10.0968 10.2258| 0.05 5.30967 0.45 5.50967 0.95 6.1871 1.45 7.39677 1.95 9.05484 2.45 10 2.95 10.3098 3.45 13.0839 3.95 7.6 4.45 7.6 4.95 7.6"; - -[filament:Generic ABS MMU2] -inherits = *ABS MMU2* - -[filament:Prusa ABS MMU2] -inherits = *ABS MMU2* - -[filament:Prusa HIPS] -inherits = *ABS* -filament_cost = 27.3 -filament_density = 1.04 -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:Prusa PET] -inherits = *PET* -filament_cost = 27.82 -filament_density = 1.27 -filament_notes = "List of manufacturers tested with standart PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" - -[filament:Prusament PETG] -inherits = *PET* -first_layer_temperature = 240 -temperature = 250 -filament_cost = 24.99 -filament_density = 1.27 - -[filament:*PET MMU2*] -inherits = Prusa PET -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -temperature = 230 -first_layer_temperature = 230 -filament_cooling_final_speed = 1 -filament_cooling_initial_speed = 2 -filament_cooling_moves = 1 -filament_load_time = 12 -filament_loading_speed = 14 -filament_notes = PET -filament_ramming_parameters = "120 140 4.70968 4.74194 4.77419 4.80645 4.83871 4.87097 4.90323 5 5.25806 5.67742 6.29032 7.06452 7.83871 8.3871| 0.05 4.72901 0.45 4.73545 0.95 4.83226 1.45 4.88067 1.95 5.05483 2.45 5.93553 2.95 7.53556 3.45 8.6323 3.95 7.6 4.45 7.6 4.95 7.6" -filament_unload_time = 11 -filament_unloading_speed = 20 -filament_unloading_speed_start = 120 - -[filament:Generic PET MMU2] -inherits = *PET MMU2* - -[filament:Prusa PET MMU2] -inherits = *PET MMU2* - -[filament:Prusament PET MMU2] -inherits = *PET MMU2* - -[filament:Prusa PLA] -inherits = *PLA* -filament_cost = 25.4 -filament_density = 1.24 -filament_notes = "List of materials tested with standart PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" - -[filament:Prusament PLA] -inherits = *PLA* -temperature = 215 -filament_cost = 24.99 -filament_density = 1.24 -filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa" - -[filament:*PLA MMU2*] -inherits = Prusa PLA -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -temperature = 205 -filament_cooling_final_speed = 1 -filament_cooling_initial_speed = 2 -filament_cooling_moves = 1 -filament_load_time = 12 -filament_loading_speed = 14 -filament_ramming_parameters = "120 110 2.70968 2.93548 3.32258 3.83871 4.58065 5.54839 6.51613 7.35484 7.93548 8.16129| 0.05 2.66451 0.45 3.05805 0.95 4.05807 1.45 5.97742 1.95 7.69999 2.45 8.1936 2.95 11.342 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" -filament_unload_time = 11 -filament_unloading_speed = 20 - -[filament:Generic PLA MMU2] -inherits = *PLA MMU2* - -[filament:Prusa PLA MMU2] -inherits = *PLA MMU2* - -[filament:Prusament PLA MMU2] -inherits = *PLA MMU2* - -[filament:SemiFlex or Flexfill 98A] -inherits = *FLEX* -filament_cost = 82 -filament_density = 1.22 - -[filament:Taulman Bridge] -inherits = *common* -filament_cost = 40 -filament_density = 1.13 -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 -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* -filament_cost = 40 -filament_density = 1.27 -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* -filament_cost = 218 -filament_density = 1.23 -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 = 4 -filament_notes = "List of materials tested with standart PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec 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 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" -temperature = 210 - -[filament:Verbatim BVOH MMU2] -inherits = Verbatim BVOH -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -temperature = 195 -filament_notes = BVOH -fan_always_on = 1 -first_layer_temperature = 200 -filament_cooling_final_speed = 1 -filament_cooling_initial_speed = 2 -filament_max_volumetric_speed = 4 -filament_type = PVA -filament_cooling_moves = 1 -filament_load_time = 12 -filament_loading_speed = 14 -filament_ramming_parameters = "120 110 1.74194 1.90323 2.16129 2.48387 2.83871 3.25806 3.83871 4.6129 5.41935 5.96774| 0.05 1.69677 0.45 1.96128 0.95 2.63872 1.45 3.46129 1.95 4.99031 2.45 6.12908 2.95 8.30974 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" -filament_unload_time = 11 -filament_unloading_speed = 20 -filament_unloading_speed_start = 100 - -[filament:Verbatim PP] -inherits = *common* -filament_cost = 72 -filament_density = 0.89 -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:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec 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 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" -temperature = 220 - -[sla_print:*common*] -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_SL1.*/ -layer_height = 0.05 -output_filename_format = [input_filename_base].dwz -pad_edge_radius = 0.5 -pad_enable = 1 -pad_max_merge_distance = 50 -pad_wall_height = 3 -pad_wall_thickness = 1 -support_base_diameter = 3 -support_base_height = 0.5 -support_critical_angle = 45 -support_density_at_45 = 250 -support_density_at_horizontal = 500 -support_head_front_diameter = 0.4 -support_head_penetration = 0.4 -support_head_width = 3 -support_max_bridge_length = 10 -support_minimal_z = 0 -support_object_elevation = 5 -support_pillar_diameter = 1 -support_pillar_widening_factor = 0 -supports_enable = 1 - -[sla_print:0.025 UltraDetail] -inherits = *common* -layer_height = 0.025 -support_head_width = 2 - -[sla_print:0.035 Detail] -inherits = *common* -layer_height = 0.035 - -[sla_print:0.05 Normal] -inherits = *common* -layer_height = 0.05 - -[sla_print:0.1 Fast] -inherits = *common* -layer_height = 0.1 - -########### Materials 0.025 - -[sla_material:*common 0.05*] -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_SL1.*/ -compatible_prints_condition = layer_height == 0.05 -exposure_time = 12 -initial_exposure_time = 45 -initial_layer_height = 0.05 -material_correction_curing = 1,1,1 -material_correction_printing = 1,1,1 -material_notes = - -[sla_material:*common 0.025*] -inherits = *common 0.05* -compatible_prints_condition = layer_height == 0.025 -exposure_time = 10 -initial_exposure_time = 35 -initial_layer_height = 0.025 - -[sla_material:*common 0.035*] -inherits = *common 0.05* -compatible_prints_condition = layer_height == 0.035 -exposure_time = 13 -initial_exposure_time = 40 -initial_layer_height = 0.035 - -[sla_material:*common 0.1*] -inherits = *common 0.05* -compatible_prints_condition = layer_height == 0.1 -exposure_time = 20 -initial_exposure_time = 90 -initial_layer_height = 0.1 - -########### Materials 0.025 - -[sla_material:Bluecast Phrozen Wax 0.025] -inherits = *common 0.025* -exposure_time = 8 -initial_exposure_time = 45 - -[sla_material:Jamg He PJHC-30 Orange 0.025] -inherits = *common 0.025* -exposure_time = 5 -initial_exposure_time = 35 - -########### Materials 0.05 - -[sla_material:3DM-HTR140 (high temperature) 0.05] -inherits = *common 0.05* -exposure_time = 12 -initial_exposure_time = 45 - -[sla_material:Bluecast Ecogray 0.05] -inherits = *common 0.05* -exposure_time = 8 -initial_exposure_time = 45 - -[sla_material:Bluecast Keramaster 0.05] -inherits = *common 0.05* -exposure_time = 8 -initial_exposure_time = 45 - -[sla_material:Bluecast Phrozen Wax 0.05] -inherits = *common 0.05* -exposure_time = 10 -initial_exposure_time = 55 - -[sla_material:Jamg He PJHC-00 Yellow 0.05] -inherits = *common 0.05* -exposure_time = 7 -initial_exposure_time = 45 - -[sla_material:Jamg He PJHC-19 Skin 0.05] -inherits = *common 0.05* -exposure_time = 6 -initial_exposure_time = 45 - -[sla_material:Jamg He PJHC-30 Orange 0.05] -inherits = *common 0.05* -exposure_time = 7 -initial_exposure_time = 45 - -[sla_material:Jamg He PJHC-60 Gray 0.05] -inherits = *common 0.05* -exposure_time = 6 -initial_exposure_time = 45 - -[sla_material:Jamg He PJHC-70 Black 0.05] -inherits = *common 0.05* -exposure_time = 6 -initial_exposure_time = 45 - -[sla_material:Monocure 3D Black Rapid Resin 0.05] -inherits = *common 0.05* -exposure_time = 6 -initial_exposure_time = 40 - -[sla_material:Monocure 3D Blue Rapid Resin 0.05] -inherits = *common 0.05* -exposure_time = 7 -initial_exposure_time = 40 - -[sla_material:Monocure 3D Clear Rapid Resin 0.05] -inherits = *common 0.05* -exposure_time = 8 -initial_exposure_time = 40 - -[sla_material:Monocure 3D Gray Rapid Resin 0.05] -inherits = *common 0.05* -exposure_time = 7 -initial_exposure_time = 40 - -[sla_material:Monocure 3D White Rapid Resin 0.05] -inherits = *common 0.05* -exposure_time = 7 -initial_exposure_time = 40 - -########### Materials 0.035 - -## [sla_material:Jamg He Transparent Clear 0.035] -## inherits = *common 0.035* - -## [sla_material:Jamg He Transparent Green 0.035] -## inherits = *common 0.035* - -## [sla_material:Jamg He Transparent Orange 0.035] -## inherits = *common 0.035* - -## [sla_material:Jamg He Transparent Red 0.035] -## inherits = *common 0.035* - -########### Materials 0.1 - -## [sla_material:Jamg He Transparent Clear 0.1] -## inherits = *common 0.1* - -## [sla_material:Jamg He Transparent Green 0.1] -## inherits = *common 0.1* - -## [sla_material:Jamg He Transparent Orange 0.1] -## inherits = *common 0.1* - -## [sla_material:Jamg He Transparent Red 0.1] -## inherits = *common 0.1* - -[printer:*common*] -printer_technology = FFF -bed_shape = 0x0,250x0,250x210,0x210 -before_layer_gcode = ;BEFORE_LAYER_CHANGE\nG92 E0.0\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\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors -extruder_colour = #FFFF00 -extruder_offset = 0x0 -gcode_flavor = marlin -silent_mode = 0 -remaining_times = 0 -machine_max_acceleration_e = 10000 -machine_max_acceleration_extruding = 2000 -machine_max_acceleration_retracting = 1500 -machine_max_acceleration_x = 9000 -machine_max_acceleration_y = 9000 -machine_max_acceleration_z = 500 -machine_max_feedrate_e = 120 -machine_max_feedrate_x = 500 -machine_max_feedrate_y = 500 -machine_max_feedrate_z = 12 -machine_max_jerk_e = 2.5 -machine_max_jerk_x = 10 -machine_max_jerk_y = 10 -machine_max_jerk_z = 0.2 -machine_min_extruding_rate = 0 -machine_min_travel_rate = 0 -layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] -max_layer_height = 0.25 -min_layer_height = 0.07 -max_print_height = 200 -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_PRUSA3D\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\nM83 ; extruder relative mode\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\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 = MK2S -printer_variant = 0.4 -default_print_profile = 0.15mm OPTIMAL -default_filament_profile = Prusament 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 -parking_pos_retraction = 92 -cooling_tube_length = 5 -cooling_tube_retraction = 91.5 -single_extruder_multi_material = 1 -variable_layer_height = 1 -printer_model = MK2SMM - -[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 ; turn off fan\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\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\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\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\nG92 E0.0 -default_print_profile = 0.15mm OPTIMAL - -[printer:*mm-multi*] -inherits = *multimaterial* -high_current_on_filament_swap = 1 -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 ; turn off fan\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors -extruder_colour = #FFAA55;#E37BA0;#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_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN -start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\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_single_extruder_multi_material_priming}\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\n{endif}\nG92 E0.0 -default_print_profile = 0.15mm OPTIMAL - -# XXXXXXXXXXXXXXXXX -# XXX--- MK2 ---XXX -# XXXXXXXXXXXXXXXXX - -[printer:Original Prusa i3 MK2] -inherits = *common* - -[printer:Original Prusa i3 MK2 0.25 nozzle] -inherits = *common* -max_layer_height = 0.15 -min_layer_height = 0.05 -nozzle_diameter = 0.25 -retract_length = 1 -retract_speed = 50 -variable_layer_height = 1 -printer_variant = 0.25 -default_print_profile = 0.10mm DETAIL 0.25 nozzle - -[printer:Original Prusa 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 -default_print_profile = 0.20mm NORMAL 0.6 nozzle - -# XXXXXXXXXXXXXXXXXXX -# XXX--- MK2MM ---XXX -# XXXXXXXXXXXXXXXXXXX - -[printer:Original Prusa i3 MK2 MMU1 Single] -inherits = *mm-single* -max_layer_height = 0.25 -min_layer_height = 0.07 - -[printer:Original Prusa i3 MK2 MMU1 Single 0.6 nozzle] -inherits = *mm-single* -nozzle_diameter = 0.6 -printer_variant = 0.6 -default_print_profile = 0.20mm NORMAL 0.6 nozzle -max_layer_height = 0.35 -min_layer_height = 0.1 - -[printer:Original Prusa i3 MK2 MMU1] -inherits = *mm-multi* -nozzle_diameter = 0.4,0.4,0.4,0.4 -max_layer_height = 0.25 -min_layer_height = 0.07 - -[printer:Original Prusa i3 MK2 MMU1 0.6 nozzle] -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 -max_layer_height = 0.35 -min_layer_height = 0.1 - -# XXXXXXXXXXXXXXXXXXX -# XXX--- MK2.5 ---XXX -# XXXXXXXXXXXXXXXXXXX - -[printer:Original Prusa i3 MK2.5] -inherits = Original Prusa i3 MK2 -printer_model = MK2.5 -remaining_times = 1 -start_gcode = M115 U3.5.1 ; tell printer latest fw version\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 - -[printer:Original Prusa i3 MK2.5 MMU2 Single] -inherits = Original Prusa i3 MK2.5; *mm2* -printer_model = MK2.5MMU2 -single_extruder_multi_material = 0 -max_print_height = 200 -remaining_times = 1 -silent_mode = 0 -retract_lift_below = 199 -machine_max_acceleration_e = 10000 -machine_max_acceleration_extruding = 2000 -machine_max_acceleration_retracting = 1500 -machine_max_acceleration_x = 9000 -machine_max_acceleration_y = 9000 -machine_max_acceleration_z = 500 -machine_max_feedrate_e = 120 -machine_max_feedrate_x = 500 -machine_max_feedrate_y = 500 -machine_max_feedrate_z = 12 -machine_max_jerk_e = 2.5 -machine_max_jerk_x = 10 -machine_max_jerk_y = 10 -machine_max_jerk_z = 0.2 -machine_min_extruding_rate = 0 -machine_min_travel_rate = 0 -default_print_profile = 0.15mm OPTIMAL MK2.5 -default_filament_profile = Prusament PLA -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.5\n -start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\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\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n -end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors - -[printer:Original Prusa i3 MK2.5 MMU2] -inherits = Original Prusa i3 MK2.5; *mm2* -printer_model = MK2.5MMU2 -max_print_height = 200 -remaining_times = 1 -silent_mode = 0 -retract_lift_below = 199 -machine_max_acceleration_e = 10000 -machine_max_acceleration_extruding = 2000 -machine_max_acceleration_retracting = 1500 -machine_max_acceleration_x = 9000 -machine_max_acceleration_y = 9000 -machine_max_acceleration_z = 500 -machine_max_feedrate_e = 120 -machine_max_feedrate_x = 500 -machine_max_feedrate_y = 500 -machine_max_feedrate_z = 12 -machine_max_jerk_e = 2.5 -machine_max_jerk_x = 10 -machine_max_jerk_y = 10 -machine_max_jerk_z = 0.2 -machine_min_extruding_rate = 0 -machine_min_travel_rate = 0 -default_print_profile = 0.15mm OPTIMAL MK2.5 -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.5\n -single_extruder_multi_material = 1 -# The 5x nozzle diameter defines the number of extruders. Other extruder parameters -# (for example the retract values) are duplicaed from the first value, so they do not need -# to be defined explicitely. -nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 -extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\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\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n -end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n - -[printer:Original Prusa i3 MK2.5 0.25 nozzle] -inherits = Original Prusa i3 MK2 0.25 nozzle -printer_model = MK2.5 -remaining_times = 1 -start_gcode = M115 U3.5.1 ; tell printer latest fw version\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 - -[printer:Original Prusa i3 MK2.5 0.6 nozzle] -inherits = Original Prusa i3 MK2 0.6 nozzle -printer_model = MK2.5 -remaining_times = 1 -start_gcode = M115 U3.5.1 ; tell printer latest fw version\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 - -# XXXXXXXXXXXXXXXXX -# XXX--- MK3 ---XXX -# XXXXXXXXXXXXXXXXX - -[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\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors -machine_max_acceleration_e = 5000,5000 -machine_max_acceleration_extruding = 1250,1250 -machine_max_acceleration_retracting = 1250,1250 -machine_max_acceleration_x = 1000,960 -machine_max_acceleration_y = 1000,960 -machine_max_acceleration_z = 1000,1000 -machine_max_feedrate_e = 120,120 -machine_max_feedrate_x = 200,100 -machine_max_feedrate_y = 200,100 -machine_max_feedrate_z = 12,12 -machine_max_jerk_e = 1.5,1.5 -machine_max_jerk_x = 8,8 -machine_max_jerk_y = 8,8 -machine_max_jerk_z = 0.4,0.4 -machine_min_extruding_rate = 0,0 -machine_min_travel_rate = 0,0 -silent_mode = 1 -remaining_times = 1 -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.5.1 ; tell printer latest fw version\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 - -[printer:Original Prusa i3 MK3 0.25 nozzle] -inherits = Original Prusa i3 MK3 -nozzle_diameter = 0.25 -max_layer_height = 0.15 -min_layer_height = 0.05 -printer_variant = 0.25 -default_print_profile = 0.10mm DETAIL 0.25 nozzle MK3 - -[printer:Original Prusa i3 MK3 0.6 nozzle] -inherits = Original Prusa i3 MK3 -nozzle_diameter = 0.6 -max_layer_height = 0.35 -min_layer_height = 0.1 -printer_variant = 0.6 -default_print_profile = 0.15mm OPTIMAL 0.6 nozzle MK3 - -[printer:*mm2*] -inherits = Original Prusa i3 MK3 -single_extruder_multi_material = 1 -cooling_tube_length = 10 -cooling_tube_retraction = 30 -parking_pos_retraction = 85 -retract_length_toolchange = 3 -extra_loading_move = -13 -printer_model = MK3MMU2 -default_print_profile = 0.15mm OPTIMAL MK3 -default_filament_profile = Prusament PLA MMU2 - -[printer:Original Prusa i3 MK3 MMU2 Single] -inherits = *mm2* -single_extruder_multi_material = 0 -default_filament_profile = Prusament PLA -start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\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\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n -end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors - -[printer:Original Prusa i3 MK3 MMU2] -inherits = *mm2* -# The 5x nozzle diameter defines the number of extruders. Other extruder parameters -# (for example the retract values) are duplicaed from the first value, so they do not need -# to be defined explicitely. -machine_max_acceleration_e = 8000,8000 -nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 -extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\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\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n -end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n - -[printer:Original Prusa SL1] -printer_technology = SLA -printer_model = SL1 -printer_variant = default -default_sla_material_profile = Jamg He Transparent Green 0.05 -default_sla_print_profile = 0.05 Normal -bed_shape = 0.98x1.02,119.98x1.02,119.98x68.02,0.98x68.02 -display_height = 68.04 -display_orientation = portrait -display_pixels_x = 2560 -display_pixels_y = 1440 -display_width = 120.96 -max_print_height = 150 -printer_correction = 1,1,1 -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_SL1\n - -# The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. -[obsolete_presets] -print="0.05mm DETAIL 0.25 nozzle";"0.05mm DETAIL MK3";"0.05mm DETAIL";"0.20mm NORMAL MK3";"0.35mm FAST MK3";"print:0.15mm OPTIMAL MK3 MMU2";"print:0.20mm FAST MK3 MMU2" -filament="ColorFabb Brass Bronze 1.75mm";"ColorFabb HT 1.75mm";"ColorFabb nGen 1.75mm";"ColorFabb Woodfil 1.75mm";"ColorFabb XT 1.75mm";"ColorFabb XT-CF20 1.75mm";"E3D PC-ABS 1.75mm";"Fillamentum ABS 1.75mm";"Fillamentum ASA 1.75mm";"Generic ABS 1.75mm";"Generic PET 1.75mm";"Generic PLA 1.75mm";"Prusa ABS 1.75mm";"Prusa HIPS 1.75mm";"Prusa PET 1.75mm";"Prusa PLA 1.75mm";"Taulman Bridge 1.75mm";"Taulman T-Glase 1.75mm" +# Print profiles for the Prusa Research printers. + +[vendor] +# Vendor name will be shown by the Config Wizard. +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.8.0-alpha7 +# Where to get the updates from? +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:MK3S] +name = Original Prusa i3 MK3S +variants = 0.4; 0.25; 0.6 +technology = FFF +family = MK3 + +[printer_model:MK3] +name = Original Prusa i3 MK3 +variants = 0.4; 0.25; 0.6 +technology = FFF +family = MK3 + +[printer_model:MK3SMMU2S] +name = Original Prusa i3 MK3S MMU2S +variants = 0.4 +technology = FFF +family = MK3 + +[printer_model:MK3MMU2] +name = Original Prusa i3 MK3 MMU2 +variants = 0.4 +technology = FFF +family = MK3 + +[printer_model:MK2.5S] +name = Original Prusa i3 MK2.5S +variants = 0.4; 0.25; 0.6 +technology = FFF +family = MK2.5 + +[printer_model:MK2.5] +name = Original Prusa i3 MK2.5 +variants = 0.4; 0.25; 0.6 +technology = FFF +family = MK2.5 + +[printer_model:MK2.5SMMU2S] +name = Original Prusa i3 MK2.5S MMU2S +variants = 0.4 +technology = FFF +family = MK2.5 + +[printer_model:MK2.5MMU2] +name = Original Prusa i3 MK2.5 MMU2 +variants = 0.4 +technology = FFF +family = MK2.5 + +[printer_model:MK2S] +name = Original Prusa i3 MK2S +variants = 0.4; 0.25; 0.6 +technology = FFF +family = MK2 + +[printer_model:MK2SMM] +name = Original Prusa i3 MK2S MMU1 +variants = 0.4; 0.6 +technology = FFF +family = MK2 + +[printer_model:SL1] +name = Original Prusa SL1 +variants = default +technology = SLA +family = SL1 + +# 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 = 20 +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}_{layer_height}mm_{filament_type[0]}_{printer_model}.gcode +perimeters = 2 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +single_extruder_multi_material_priming = 1 +skirts = 1 +skirt_distance = 2 +skirt_height = 3 +small_perimeter_speed = 25 +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.1 +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 = 55 +support_material_with_sheath = 0 +support_material_xy_spacing = 50% +thin_walls = 0 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 40 +travel_speed = 180 +wipe_tower = 1 +wipe_tower_bridging = 10 +wipe_tower_rotation_angle = 0 +wipe_tower_width = 60 +wipe_tower_x = 170 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:*MK3*] +fill_pattern = grid +single_extruder_multi_material_priming = 0 +travel_speed = 180 +wipe_tower_x = 170 +wipe_tower_y = 125 + +# 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.2 +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 +support_material_extrusion_width = 0.55 + +[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_bridging = 8 + +# XXXXXXXXXXXXXXXXXXXX +# XXX--- 0.05mm ---XXX +# XXXXXXXXXXXXXXXXXXXX + +[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 = 20 +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* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 +infill_extrusion_width = 0.5 + +# MK3 # +[print:0.05mm ULTRADETAIL MK3] +inherits = *0.05mm*; *MK3* +fill_pattern = gyroid +fill_density = 15% +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material +top_infill_extrusion_width = 0.4 + +# MK2 # +[print:0.05mm ULTRADETAIL 0.25 nozzle] +inherits = *0.05mm*; *0.25nozzle* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 +fill_density = 20% +infill_speed = 20 +max_print_speed = 100 +perimeter_speed = 20 +small_perimeter_speed = 15 +solid_infill_speed = 20 +support_material_speed = 20 + +# MK3 # +[print:0.05mm ULTRADETAIL 0.25 nozzle MK3] +inherits = *0.05mm*; *0.25nozzle*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 + +# XXXXXXXXXXXXXXXXXXXX +# XXX--- 0.07mm ---XXX +# XXXXXXXXXXXXXXXXXXXX + +[print:*0.07mm*] +inherits = *common* +bottom_solid_layers = 8 +bridge_acceleration = 300 +bridge_flow_ratio = 0.7 +bridge_speed = 20 +default_acceleration = 500 +external_perimeter_speed = 20 +fill_density = 15% +first_layer_acceleration = 500 +gap_fill_speed = 20 +infill_acceleration = 800 +infill_speed = 40 +max_print_speed = 80 +small_perimeter_speed = 20 +solid_infill_speed = 40 +support_material_extrusion_width = 0.3 +support_material_spacing = 1.5 +layer_height = 0.07 +perimeter_acceleration = 300 +perimeter_speed = 30 +perimeters = 3 +support_material_speed = 40 +top_solid_infill_speed = 30 +top_solid_layers = 11 + +# MK3 # +[print:0.07mm ULTRADETAIL MK3] +inherits = *0.07mm*; *MK3* +fill_pattern = gyroid +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material +top_infill_extrusion_width = 0.4 + +# XXXXXXXXXXXXXXXXXXXX +# XXX--- 0.10mm ---XXX +# XXXXXXXXXXXXXXXXXXXX + +# MK2 # +[print:*0.10mm*] +inherits = *common* +bottom_solid_layers = 7 +bridge_flow_ratio = 0.7 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 +layer_height = 0.1 +perimeter_acceleration = 800 +top_solid_layers = 9 + +# MK2 # +[print:0.10mm DETAIL] +inherits = *0.10mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +perimeter_speed = 50 +solid_infill_speed = 50 + +# MK3 # +[print:0.10mm DETAIL MK3] +inherits = *0.10mm*; *MK3* +bridge_speed = 30 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material +external_perimeter_speed = 25 +infill_acceleration = 1250 +infill_speed = 80 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 80 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 40 +fill_pattern = gyroid +fill_density = 15% + +# MK2 # +[print:0.10mm DETAIL 0.25 nozzle] +inherits = *0.10mm*; *0.25nozzle* +bridge_acceleration = 600 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 +external_perimeter_speed = 20 +infill_acceleration = 1600 +infill_speed = 40 +perimeter_acceleration = 600 +perimeter_speed = 25 +small_perimeter_speed = 15 +solid_infill_speed = 40 +top_solid_infill_speed = 30 + +# MK3 # +[print:0.10mm DETAIL 0.25 nozzle MK3] +inherits = *0.10mm*; *0.25nozzle*; *MK3* +bridge_speed = 30 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 +external_perimeter_speed = 35 +infill_acceleration = 1250 +infill_speed = 200 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 200 +top_solid_infill_speed = 50 + +# MK3 # +[print:0.10mm DETAIL 0.6 nozzle MK3] +inherits = *0.10mm*; *0.6nozzle*; *MK3* +bridge_speed = 30 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1250 +infill_speed = 200 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 200 +top_solid_infill_speed = 50 + +# XXXXXXXXXXXXXXXXXXXX +# XXX--- 0.15mm ---XXX +# XXXXXXXXXXXXXXXXXXXX + +[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 + +# MK2 # +[print:0.15mm 100mms Linear Advance] +inherits = *0.15mm* +bridge_flow_ratio = 0.95 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 +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 + +# MK2 # +[print:0.15mm OPTIMAL] +inherits = *0.15mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 +top_infill_extrusion_width = 0.45 + +# MK2 # +[print:0.15mm OPTIMAL 0.25 nozzle] +inherits = *0.15mm*; *0.25nozzle* +bridge_acceleration = 600 +bridge_flow_ratio = 0.7 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 +external_perimeter_speed = 20 +infill_acceleration = 1600 +infill_speed = 40 +perimeter_acceleration = 600 +perimeter_speed = 25 +small_perimeter_speed = 15 +solid_infill_speed = 40 +top_solid_infill_speed = 30 + +# MK2 # +[print:0.15mm OPTIMAL 0.6 nozzle] +inherits = *0.15mm*; *0.6nozzle* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 + +# MK3 # +[print:0.15mm QUALITY MK3] +inherits = *0.15mm*; *MK3* +bridge_speed = 30 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 25 +infill_acceleration = 1250 +infill_speed = 80 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 80 +top_solid_infill_speed = 40 +fill_pattern = gyroid +fill_density = 15% + +[print:0.15mm SPEED MK3] +inherits = *0.15mm*; *MK3* +bridge_speed = 30 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 35 +infill_acceleration = 1250 +infill_speed = 200 +max_print_speed = 200 +perimeter_speed = 60 +solid_infill_speed = 200 +top_solid_infill_speed = 50 + +# MK3 MMU # +[print:0.15mm SOLUBLE FULL MK3] +inherits = 0.15mm SPEED MK3; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +support_material_extruder = 5 +support_material_interface_extruder = 5 +perimeter_speed = 40 +solid_infill_speed = 40 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 + +# MK3 MMU # +[print:0.15mm SOLUBLE INTERFACE MK3] +inherits = 0.15mm SOLUBLE FULL MK3 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 + +# MK2 MMU # +[print:0.15mm OPTIMAL SOLUBLE FULL] +inherits = *0.15mm*; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +external_perimeter_speed = 25 +notes = Set your soluble 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 + +# MK2 MMU # +[print:0.15mm OPTIMAL SOLUBLE INTERFACE] +inherits = 0.15mm OPTIMAL SOLUBLE FULL +notes = Set your soluble 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% + +# MK3 # +[print:0.15mm OPTIMAL 0.25 nozzle MK3] +inherits = *0.15mm*; *0.25nozzle*; *MK3* +bridge_speed = 30 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 +external_perimeter_speed = 35 +infill_acceleration = 1250 +infill_speed = 200 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 200 +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 + +# MK3 # +[print:0.15mm OPTIMAL 0.6 nozzle MK3] +inherits = *0.15mm*; *0.6nozzle*; *MK3* +bridge_speed = 30 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1250 +infill_speed = 200 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 200 +top_solid_infill_speed = 50 + +# XXXXXXXXXXXXXXXXXXXX +# XXX--- 0.20mm ---XXX +# XXXXXXXXXXXXXXXXXXXX + +# MK2 # +[print:0.20mm 100mms Linear Advance] +inherits = *0.20mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 +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 + +# MK3 # +[print:0.20mm QUALITY MK3] +inherits = *0.20mm*; *MK3* +bridge_speed = 30 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 25 +infill_acceleration = 1250 +infill_speed = 80 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 80 +top_solid_infill_speed = 40 +fill_pattern = gyroid +fill_density = 15% + +[print:0.20mm SPEED MK3] +inherits = *0.20mm*; *MK3* +bridge_speed = 30 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 35 +infill_acceleration = 1250 +infill_speed = 200 +max_print_speed = 200 +perimeter_speed = 60 +solid_infill_speed = 200 +top_solid_infill_speed = 50 + +# MK3 MMU # +[print:0.20mm SOLUBLE FULL MK3] +inherits = 0.20mm SPEED MK3; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +support_material_extruder = 5 +support_material_interface_extruder = 5 +perimeter_speed = 40 +solid_infill_speed = 40 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 + +# MK3 MMU # +[print:0.20mm SOLUBLE INTERFACE MK3] +inherits = 0.20mm SOLUBLE FULL MK3 +notes = Set your soluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 + +# MK2 # +[print:0.20mm NORMAL] +inherits = *0.20mm* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 + +# MK2 # +[print:0.20mm NORMAL 0.6 nozzle] +inherits = *0.20mm*; *0.6nozzle* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 + +# MK2 MMU # +[print:0.20mm NORMAL SOLUBLE FULL] +inherits = *0.20mm*; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +external_perimeter_speed = 30 +notes = Set your soluble 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 + +# MK2 MMU # +[print:0.20mm NORMAL SOLUBLE INTERFACE] +inherits = 0.20mm NORMAL SOLUBLE FULL +notes = Set your soluble 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% + +# MK3 # +[print:0.20mm FAST 0.6 nozzle MK3] +inherits = *0.20mm*; *0.6nozzle*; *MK3* +bridge_speed = 30 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1250 +infill_speed = 200 +max_print_speed = 200 +perimeter_speed = 45 +solid_infill_speed = 200 +top_solid_infill_speed = 50 + +# XXXXXXXXXXXXXXXXXXXX +# XXX--- 0.35mm ---XXX +# XXXXXXXXXXXXXXXXXXXX + +[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 + +# MK2 # +[print:0.35mm FAST] +inherits = *0.35mm* +bridge_flow_ratio = 0.95 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 +first_layer_extrusion_width = 0.42 +perimeter_extrusion_width = 0.43 +solid_infill_extrusion_width = 0.7 +top_infill_extrusion_width = 0.43 + +# MK2 # +[print:0.35mm FAST 0.6 nozzle] +inherits = *0.35mm*; *0.6nozzle* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 + +# MK2 MMU # +[print:0.35mm FAST sol full 0.6 nozzle] +inherits = *0.35mm*; *0.6nozzle*; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 +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_interface_layers = 3 +support_material_xy_spacing = 120% +top_infill_extrusion_width = 0.57 + +# MK2 MMU # +[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% + +# XXXXXXXXXXXXXXXXXXXXXX +# XXX----- MK2.5 ----XXX +# XXXXXXXXXXXXXXXXXXXXXX + +# MK2.5 # +[print:0.15mm 100mms Linear Advance MK2.5] +inherits = 0.15mm 100mms Linear Advance +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +# MK2.5 # +[print:0.15mm OPTIMAL MK2.5] +inherits = 0.15mm OPTIMAL +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +# MK2.5 MMU2 # +[print:0.15mm OPTIMAL SOLUBLE FULL MK2.5] +inherits = 0.15mm OPTIMAL SOLUBLE FULL +support_material_extruder = 5 +support_material_interface_extruder = 5 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 + +# MK2.5 MMU2 # +[print:0.15mm OPTIMAL SOLUBLE INTERFACE MK2.5] +inherits = 0.15mm OPTIMAL SOLUBLE INTERFACE +support_material_extruder = 0 +support_material_interface_extruder = 5 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 + +# MK2.5 # +[print:0.20mm 100mms Linear Advance MK2.5] +inherits = 0.20mm 100mms Linear Advance +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +# MK2.5 # +[print:0.20mm NORMAL MK2.5] +inherits = 0.20mm NORMAL +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +# MK2.5 MMU2 # +[print:0.20mm NORMAL SOLUBLE FULL MK2.5] +inherits = 0.20mm NORMAL SOLUBLE FULL +support_material_extruder = 5 +support_material_interface_extruder = 5 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +single_extruder_multi_material_priming = 0 + +# MK2.5 MMU2 # +[print:0.20mm NORMAL SOLUBLE INTERFACE MK2.5] +inherits = 0.20mm NORMAL SOLUBLE INTERFACE +support_material_extruder = 0 +support_material_interface_extruder = 5 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +single_extruder_multi_material_priming = 0 + +# MK2.5 # +[print:0.35mm FAST MK2.5] +inherits = 0.35mm FAST +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +# XXXXXXxxXXXXXXXXXXXXXX +# XXX--- filament ---XXX +# XXXXXXXXxxXXXXXXXXXXXX + +[filament:*common*] +cooling = 1 +compatible_printers = +# For now, all but selected filaments are disabled for the MMU 2.0 +compatible_printers_condition = ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +filament_loading_speed = 28 +filament_loading_speed_start = 3 +filament_unloading_speed = 90 +filament_unloading_speed_start = 100 +filament_toolchange_delay = 0 +filament_cooling_moves = 4 +filament_cooling_initial_speed = 2.2 +filament_cooling_final_speed = 3.4 +filament_load_time = 0 +filament_unload_time = 0 +filament_ramming_parameters = "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_minimal_purge_on_wipe_tower = 15 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_notes = "" +filament_settings_id = "" +filament_soluble = 0 +min_print_speed = 15 +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_ramming_parameters = "120 100 5.70968 6.03226 7 8.25806 9 9.19355 9.3871 9.77419 10.129 10.3226 10.4516 10.5161| 0.05 5.69677 0.45 6.15484 0.95 8.76774 1.45 9.20323 1.95 9.95806 2.45 10.3871 2.95 10.5677 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +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* +bed_temperature = 50 +bridge_fan_speed = 100 +# For now, all but selected filaments are disabled for the MMU 2.0 +compatible_printers_condition = nozzle_diameter[0]>0.35 and num_extruders==1 && ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) +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* +# For now, all but selected filaments are disabled for the MMU 2.0 +compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +extrusion_multiplier = 1.2 +filament_cost = 80.65 +filament_density = 4 +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 +filament_cost = 58.66 +filament_density = 1.18 +first_layer_bed_temperature = 105 +first_layer_temperature = 270 +max_fan_speed = 20 +min_fan_speed = 10 +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_cost = 55.5 +filament_density = 1.24 + +[filament:ColorFabb Woodfil] +inherits = *PLA* +# For now, all but selected filaments are disabled for the MMU 2.0 +compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +extrusion_multiplier = 1.2 +filament_cost = 62.9 +filament_density = 1.15 +filament_colour = #804040 +filament_max_volumetric_speed = 10 +first_layer_temperature = 200 +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 = PET +filament_cost = 62.9 +filament_density = 1.27 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 270 + +[filament:ColorFabb XT-CF20] +inherits = *PET* +compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +extrusion_multiplier = 1.2 +filament_cost = 80.65 +filament_density = 1.35 +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* +filament_cost = 21.2 +filament_density = 1.2 +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* +filament_cost = 0 +filament_density = 1 +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_cost = 56.9 +filament_density = 1.26 +filament_notes = "List of manufacturers tested with standart PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" + +[filament:E3D PC-ABS] +inherits = *ABS* +filament_cost = 0 +filament_density = 1.05 +first_layer_temperature = 270 +temperature = 270 + +[filament:Fillamentum ABS] +inherits = *ABS* +filament_cost = 32.4 +filament_density = 1.04 +first_layer_temperature = 240 +temperature = 240 + +[filament:Fillamentum ASA] +inherits = *ABS* +filament_cost = 38.7 +filament_density = 1.04 +fan_always_on = 1 +first_layer_temperature = 265 +temperature = 265 + +[filament:Fillamentum CPE HG100 HM100] +inherits = *PET* +filament_cost = 54.1 +filament_density = 1.25 +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* +# For now, all but selected filaments are disabled for the MMU 2.0 +compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +extrusion_multiplier = 1.2 +filament_cost = 68 +filament_density = 1.15 +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_cost = 27.82 +filament_density = 1.04 +filament_notes = "List of materials tested with standart ABS print settings:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladec ABS" + +[filament:Generic PET] +inherits = *PET* +filament_cost = 27.82 +filament_density = 1.27 +filament_notes = "List of manufacturers tested with standart PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" + +[filament:Generic PLA] +inherits = *PLA* +filament_cost = 25.4 +filament_density = 1.24 +filament_notes = "List of materials tested with standart PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" + +[filament:Polymaker PC-Max] +inherits = *ABS* +filament_cost = 77.3 +filament_density = 1.20 +bed_temperature = 115 +filament_colour = #3A80CA +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +temperature = 270 + +[filament:PrimaSelect PVA+] +inherits = *PLA* +filament_cost = 108 +filament_density = 1.23 +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:\n\nPrimaSelect PVA+\nICE FILAMENTS PVA 'NAUGHTY NATURAL'\nVerbatim BVOH" +filament_ramming_parameters = "120 100 8.3871 8.6129 8.93548 9.22581 9.48387 9.70968 9.87097 10.0323 10.2258 10.4194 10.6452 10.8065| 0.05 8.34193 0.45 8.73548 0.95 9.34836 1.45 9.78385 1.95 10.0871 2.45 10.5161 2.95 10.8903 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +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:Prusa ABS] +inherits = *ABS* +filament_cost = 27.82 +filament_density = 1.08 +filament_notes = "List of materials tested with standart ABS print settings:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladec ABS" + +[filament:*ABS MMU2*] +inherits = Prusa ABS +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material +filament_cooling_final_speed = 50 +filament_cooling_initial_speed = 10 +filament_cooling_moves = 5 +filament_ramming_parameters = "120 110 5.32258 5.45161 5.67742 6 6.48387 7.12903 7.90323 8.70968 9.3871 9.83871 10.0968 10.2258| 0.05 5.30967 0.45 5.50967 0.95 6.1871 1.45 7.39677 1.95 9.05484 2.45 10 2.95 10.3098 3.45 13.0839 3.95 7.6 4.45 7.6 4.95 7.6"; +filament_loading_speed_start = 19 +filament_load_time = 15 +filament_unload_time = 12 +filament_loading_speed = 14 +filament_unloading_speed = 20 + +[filament:Generic ABS MMU2] +inherits = *ABS MMU2* + +[filament:Prusa ABS MMU2] +inherits = *ABS MMU2* + +[filament:Prusa HIPS] +inherits = *ABS* +filament_cost = 27.3 +filament_density = 1.04 +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:Prusa PET] +inherits = *PET* +filament_cost = 27.82 +filament_density = 1.27 +filament_notes = "List of manufacturers tested with standart PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" + +[filament:Prusament PETG] +inherits = *PET* +first_layer_temperature = 240 +temperature = 250 +filament_cost = 24.99 +filament_density = 1.27 + +[filament:*PET MMU2*] +inherits = Prusa PET +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material +temperature = 230 +first_layer_temperature = 230 +filament_cooling_final_speed = 1 +filament_cooling_initial_speed = 2 +filament_cooling_moves = 1 +filament_load_time = 15 +filament_loading_speed = 14 +filament_notes = PET +filament_ramming_parameters = "120 140 4.70968 4.74194 4.77419 4.80645 4.83871 4.87097 4.90323 5 5.25806 5.67742 6.29032 7.06452 7.83871 8.3871| 0.05 4.72901 0.45 4.73545 0.95 4.83226 1.45 4.88067 1.95 5.05483 2.45 5.93553 2.95 7.53556 3.45 8.6323 3.95 7.6 4.45 7.6 4.95 7.6" +filament_unload_time = 12 +filament_unloading_speed = 20 +filament_unloading_speed_start = 120 +filament_loading_speed_start = 19 + +[filament:Generic PET MMU2] +inherits = *PET MMU2* + +[filament:Prusa PET MMU2] +inherits = *PET MMU2* + +[filament:Prusament PETG MMU2] +inherits = *PET MMU2* + +[filament:Prusa PLA] +inherits = *PLA* +filament_cost = 25.4 +filament_density = 1.24 +filament_notes = "List of materials tested with standart PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" + +[filament:Prusament PLA] +inherits = *PLA* +temperature = 215 +filament_cost = 24.99 +filament_density = 1.24 +filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa" + +[filament:*PLA MMU2*] +inherits = Prusa PLA +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material +temperature = 205 +filament_cooling_final_speed = 2 +filament_cooling_initial_speed = 3 +filament_cooling_moves = 1 +filament_load_time = 15 +filament_loading_speed = 14 +filament_ramming_parameters = "130 120 2.70968 2.93548 3.32258 3.83871 4.58065 5.54839 6.51613 7.35484 7.93548 8.16129| 0.05 2.66451 0.45 3.05805 0.95 4.05807 1.45 5.97742 1.95 7.69999 2.45 8.1936 2.95 11.342 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" +filament_unload_time = 12 +filament_unloading_speed = 20 +filament_loading_speed_start = 19 +filament_minimal_purge_on_wipe_tower = 15 +filament_unloading_speed_start = 100 + +[filament:Generic PLA MMU2] +inherits = *PLA MMU2* + +[filament:Prusa PLA MMU2] +inherits = *PLA MMU2* + +[filament:Prusament PLA MMU2] +inherits = *PLA MMU2* + +[filament:SemiFlex or Flexfill 98A] +inherits = *FLEX* +filament_cost = 82 +filament_density = 1.22 + +[filament:Taulman Bridge] +inherits = *common* +filament_cost = 40 +filament_density = 1.13 +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 +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* +filament_cost = 40 +filament_density = 1.27 +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* +filament_cost = 218 +filament_density = 1.23 +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 = 4 +filament_notes = "List of materials tested with standart PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec 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 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 210 + +[filament:Verbatim BVOH MMU2] +inherits = Verbatim BVOH +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material +temperature = 195 +filament_notes = BVOH +fan_always_on = 1 +first_layer_temperature = 200 +filament_cooling_final_speed = 1 +filament_cooling_initial_speed = 2 +filament_max_volumetric_speed = 4 +filament_type = PLA +filament_cooling_moves = 1 +filament_load_time = 15 +filament_loading_speed = 14 +filament_ramming_parameters = "120 110 1.74194 1.90323 2.16129 2.48387 2.83871 3.25806 3.83871 4.6129 5.41935 5.96774| 0.05 1.69677 0.45 1.96128 0.95 2.63872 1.45 3.46129 1.95 4.99031 2.45 6.12908 2.95 8.30974 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" +filament_unload_time = 12 +filament_unloading_speed = 20 +filament_unloading_speed_start = 100 +filament_loading_speed_start = 19 + +[filament:PrimaSelect PVA+ MMU2] +inherits = *common* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material +bed_temperature = 60 +bridge_fan_speed = 100 +cooling = 0 +disable_fan_first_layers = 1 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #FFFFD7 +filament_cooling_final_speed = 2 +filament_cooling_initial_speed = 4 +filament_cooling_moves = 2 +filament_cost = 25.4 +filament_density = 1.24 +filament_diameter = 1.75 +filament_load_time = 15 +filament_loading_speed = 14 +filament_loading_speed_start = 19 +filament_max_volumetric_speed = 4 +filament_minimal_purge_on_wipe_tower = 5 +filament_notes = PVA +filament_ramming_parameters = "120 110 3.83871 3.90323 3.96774 4.03226 4.09677 4.19355 4.3871 4.83871 5.67742 6.93548 8.54839 10.3226 11.9677 13.2581 14.129 14.5806| 0.05 3.8258 0.45 3.89676 0.95 4.05807 1.45 4.23548 1.95 5.18386 2.45 7.80651 2.95 11.5356 3.45 13.9872 3.95 14.7613 4.45 7.6 4.95 7.6" +filament_soluble = 1 +filament_toolchange_delay = 0 +filament_type = PLA +filament_unload_time = 12 +filament_unloading_speed = 20 +filament_unloading_speed_start = 100 +first_layer_bed_temperature = 60 +first_layer_temperature = 200 +max_fan_speed = 100 +min_fan_speed = 100 +min_print_speed = 15 +slowdown_below_layer_time = 20 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" +temperature = 195 + +[filament:Verbatim PP] +inherits = *common* +filament_cost = 72 +filament_density = 0.89 +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:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec 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 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 220 + +[sla_print:*common*] +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_SL1.*/ +layer_height = 0.05 +output_filename_format = [input_filename_base].dwz +pad_edge_radius = 0.5 +pad_enable = 1 +pad_max_merge_distance = 50 +pad_wall_height = 3 +pad_wall_thickness = 1 +support_base_diameter = 3 +support_base_height = 0.5 +support_critical_angle = 45 +support_density_at_45 = 250 +support_density_at_horizontal = 500 +support_head_front_diameter = 0.4 +support_head_penetration = 0.4 +support_head_width = 3 +support_max_bridge_length = 10 +support_minimal_z = 0 +support_object_elevation = 5 +support_pillar_diameter = 1 +support_pillar_widening_factor = 0 +supports_enable = 1 + +[sla_print:0.025 UltraDetail] +inherits = *common* +layer_height = 0.025 +support_head_width = 2 + +[sla_print:0.035 Detail] +inherits = *common* +layer_height = 0.035 + +[sla_print:0.05 Normal] +inherits = *common* +layer_height = 0.05 + +[sla_print:0.1 Fast] +inherits = *common* +layer_height = 0.1 + +########### Materials 0.025 + +[sla_material:*common 0.05*] +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_SL1.*/ +compatible_prints_condition = layer_height == 0.05 +exposure_time = 12 +initial_exposure_time = 45 +initial_layer_height = 0.05 +material_correction_curing = 1,1,1 +material_correction_printing = 1,1,1 +material_notes = + +[sla_material:*common 0.025*] +inherits = *common 0.05* +compatible_prints_condition = layer_height == 0.025 +exposure_time = 10 +initial_exposure_time = 35 +initial_layer_height = 0.025 + +[sla_material:*common 0.035*] +inherits = *common 0.05* +compatible_prints_condition = layer_height == 0.035 +exposure_time = 13 +initial_exposure_time = 40 +initial_layer_height = 0.035 + +[sla_material:*common 0.1*] +inherits = *common 0.05* +compatible_prints_condition = layer_height == 0.1 +exposure_time = 20 +initial_exposure_time = 90 +initial_layer_height = 0.1 + +########### Materials 0.025 + +[sla_material:Bluecast Phrozen Wax 0.025] +inherits = *common 0.025* +exposure_time = 8 +initial_exposure_time = 45 + +[sla_material:Jamg He PJHC-30 Orange 0.025] +inherits = *common 0.025* +exposure_time = 5 +initial_exposure_time = 35 + +########### Materials 0.05 + +[sla_material:3DM-HTR140 (high temperature) 0.05] +inherits = *common 0.05* +exposure_time = 12 +initial_exposure_time = 45 + +[sla_material:Bluecast Ecogray 0.05] +inherits = *common 0.05* +exposure_time = 8 +initial_exposure_time = 45 + +[sla_material:Bluecast Keramaster 0.05] +inherits = *common 0.05* +exposure_time = 8 +initial_exposure_time = 45 + +[sla_material:Bluecast Phrozen Wax 0.05] +inherits = *common 0.05* +exposure_time = 10 +initial_exposure_time = 55 + +[sla_material:Jamg He PJHC-00 Yellow 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 45 + +[sla_material:Jamg He PJHC-19 Skin 0.05] +inherits = *common 0.05* +exposure_time = 6 +initial_exposure_time = 45 + +[sla_material:Jamg He PJHC-30 Orange 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 45 + +[sla_material:Jamg He PJHC-60 Gray 0.05] +inherits = *common 0.05* +exposure_time = 6 +initial_exposure_time = 45 + +[sla_material:Jamg He PJHC-70 Black 0.05] +inherits = *common 0.05* +exposure_time = 6 +initial_exposure_time = 45 + +[sla_material:Monocure 3D Black Rapid Resin 0.05] +inherits = *common 0.05* +exposure_time = 6 +initial_exposure_time = 40 + +[sla_material:Monocure 3D Blue Rapid Resin 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 40 + +[sla_material:Monocure 3D Clear Rapid Resin 0.05] +inherits = *common 0.05* +exposure_time = 8 +initial_exposure_time = 40 + +[sla_material:Monocure 3D Gray Rapid Resin 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 40 + +[sla_material:Monocure 3D White Rapid Resin 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 40 + +# v2 + +[sla_material:3DM-ABS 0.05] +inherits = *common 0.05* +exposure_time = 9 +initial_exposure_time = 35 + +[sla_material:3DM-DENT 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 45 + +[sla_material:3DM-HR Green 0.05] +inherits = *common 0.05* +exposure_time = 15 +initial_exposure_time = 40 + +[sla_material:3DM-HR Red Wine 0.05] +inherits = *common 0.05* +exposure_time = 9 +initial_exposure_time = 35 + +[sla_material:3DM-XPRO White 0.05] +inherits = *common 0.05* +exposure_time = 9 +initial_exposure_time = 35 + +[sla_material:FTD Ash Grey 0.05] +inherits = *common 0.05* +exposure_time = 9 +initial_exposure_time = 40 + +[sla_material:Jamg He LOC-19 Super Low Odor Skin 0.05] +inherits = *common 0.05* +exposure_time = 7.5 +initial_exposure_time = 40 + +[sla_material:Jamg He LOC-20 Super Low Odor White 0.05] +inherits = *common 0.05* +exposure_time = 6.5 +initial_exposure_time = 40 + +[sla_material:Jamg He LOC-60 Super Low Odor Grey 0.05] +inherits = *common 0.05* +exposure_time = 6.5 +initial_exposure_time = 40 + +########### Materials 0.035 + +[sla_material:Jamg He PJHC-30 Orange 0.035] +inherits = *common 0.035* +exposure_time = 9 +initial_exposure_time = 35 + +########### Materials 0.1 + +[sla_material:Jamg He PJHC-30 Orange 0.1] +inherits = *common 0.1* +exposure_time = 10 +initial_exposure_time = 45 + +[printer:*common*] +printer_technology = FFF +bed_shape = 0x0,250x0,250x210,0x210 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\nG92 E0.0\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\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors +extruder_colour = #FFFF00 +extruder_offset = 0x0 +gcode_flavor = marlin +silent_mode = 0 +remaining_times = 0 +machine_max_acceleration_e = 10000 +machine_max_acceleration_extruding = 2000 +machine_max_acceleration_retracting = 1500 +machine_max_acceleration_x = 9000 +machine_max_acceleration_y = 9000 +machine_max_acceleration_z = 500 +machine_max_feedrate_e = 120 +machine_max_feedrate_x = 500 +machine_max_feedrate_y = 500 +machine_max_feedrate_z = 12 +machine_max_jerk_e = 2.5 +machine_max_jerk_x = 10 +machine_max_jerk_y = 10 +machine_max_jerk_z = 0.2 +machine_min_extruding_rate = 0 +machine_min_travel_rate = 0 +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.25 +min_layer_height = 0.07 +max_print_height = 200 +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_PRUSA3D\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\nM83 ; extruder relative mode\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\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 = MK2S +printer_variant = 0.4 +default_print_profile = 0.15mm OPTIMAL +default_filament_profile = Prusament 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 +parking_pos_retraction = 92 +cooling_tube_length = 5 +cooling_tube_retraction = 91.5 +single_extruder_multi_material = 1 +variable_layer_height = 1 +printer_model = MK2SMM + +[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 ; turn off fan\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\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\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\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\nG92 E0.0 +default_print_profile = 0.15mm OPTIMAL + +[printer:*mm-multi*] +inherits = *multimaterial* +high_current_on_filament_swap = 1 +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 ; turn off fan\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors +extruder_colour = #FFAA55;#E37BA0;#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_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\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_single_extruder_multi_material_priming}\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\n{endif}\nG92 E0.0 +default_print_profile = 0.15mm OPTIMAL + +# XXXXXXXXXXXXXXXXX +# XXX--- MK2 ---XXX +# XXXXXXXXXXXXXXXXX + +[printer:Original Prusa i3 MK2S] +inherits = *common* + +[printer:Original Prusa i3 MK2S 0.25 nozzle] +inherits = *common* +max_layer_height = 0.15 +min_layer_height = 0.05 +nozzle_diameter = 0.25 +retract_length = 1 +retract_speed = 50 +variable_layer_height = 1 +printer_variant = 0.25 +default_print_profile = 0.10mm DETAIL 0.25 nozzle + +[printer:Original Prusa i3 MK2S 0.6 nozzle] +inherits = *common* +max_layer_height = 0.35 +min_layer_height = 0.1 +nozzle_diameter = 0.6 +printer_variant = 0.6 +default_print_profile = 0.20mm NORMAL 0.6 nozzle + +# XXXXXXXXXXXXXXXXXXX +# XXX--- MK2MM ---XXX +# XXXXXXXXXXXXXXXXXXX + +[printer:Original Prusa i3 MK2S MMU1 Single] +inherits = *mm-single* +max_layer_height = 0.25 +min_layer_height = 0.07 + +[printer:Original Prusa i3 MK2S MMU1 Single 0.6 nozzle] +inherits = *mm-single* +nozzle_diameter = 0.6 +printer_variant = 0.6 +default_print_profile = 0.20mm NORMAL 0.6 nozzle +max_layer_height = 0.35 +min_layer_height = 0.1 + +[printer:Original Prusa i3 MK2S MMU1] +inherits = *mm-multi* +nozzle_diameter = 0.4,0.4,0.4,0.4 +max_layer_height = 0.25 +min_layer_height = 0.07 + +[printer:Original Prusa i3 MK2S MMU1 0.6 nozzle] +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 +max_layer_height = 0.35 +min_layer_height = 0.1 + +# XXXXXXXXXXXXXXXXXXX +# XXX--- MK2.5 ---XXX +# XXXXXXXXXXXXXXXXXXX + +[printer:Original Prusa i3 MK2.5] +inherits = Original Prusa i3 MK2S +printer_model = MK2.5 +remaining_times = 1 +start_gcode = M115 U3.5.1 ; tell printer latest fw version\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 + +[printer:Original Prusa i3 MK2.5 0.25 nozzle] +inherits = Original Prusa i3 MK2S 0.25 nozzle +printer_model = MK2.5 +remaining_times = 1 +start_gcode = M115 U3.5.1 ; tell printer latest fw version\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 + +[printer:Original Prusa i3 MK2.5 0.6 nozzle] +inherits = Original Prusa i3 MK2S 0.6 nozzle +printer_model = MK2.5 +remaining_times = 1 +start_gcode = M115 U3.5.1 ; tell printer latest fw version\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 + +[printer:Original Prusa i3 MK2.5 MMU2 Single] +inherits = Original Prusa i3 MK2.5; *mm2* +printer_model = MK2.5MMU2 +single_extruder_multi_material = 0 +max_print_height = 200 +remaining_times = 1 +silent_mode = 0 +retract_lift_below = 199 +machine_max_acceleration_e = 10000 +machine_max_acceleration_extruding = 2000 +machine_max_acceleration_retracting = 1500 +machine_max_acceleration_x = 9000 +machine_max_acceleration_y = 9000 +machine_max_acceleration_z = 500 +machine_max_feedrate_e = 120 +machine_max_feedrate_x = 500 +machine_max_feedrate_y = 500 +machine_max_feedrate_z = 12 +machine_max_jerk_e = 2.5 +machine_max_jerk_x = 10 +machine_max_jerk_y = 10 +machine_max_jerk_z = 0.2 +machine_min_extruding_rate = 0 +machine_min_travel_rate = 0 +default_print_profile = 0.15mm OPTIMAL MK2.5 +default_filament_profile = Prusament PLA +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.5\n +start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\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\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors + +[printer:Original Prusa i3 MK2.5 MMU2] +inherits = Original Prusa i3 MK2.5; *mm2* +printer_model = MK2.5MMU2 +max_print_height = 200 +remaining_times = 1 +silent_mode = 0 +retract_lift_below = 199 +machine_max_acceleration_e = 10000 +machine_max_acceleration_extruding = 2000 +machine_max_acceleration_retracting = 1500 +machine_max_acceleration_x = 9000 +machine_max_acceleration_y = 9000 +machine_max_acceleration_z = 500 +machine_max_feedrate_e = 120 +machine_max_feedrate_x = 500 +machine_max_feedrate_y = 500 +machine_max_feedrate_z = 12 +machine_max_jerk_e = 2.5 +machine_max_jerk_x = 10 +machine_max_jerk_y = 10 +machine_max_jerk_z = 0.2 +machine_min_extruding_rate = 0 +machine_min_travel_rate = 0 +default_print_profile = 0.15mm OPTIMAL MK2.5 +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.5\n +single_extruder_multi_material = 1 +# The 5x nozzle diameter defines the number of extruders. Other extruder parameters +# (for example the retract values) are duplicaed from the first value, so they do not need +# to be defined explicitely. +nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 +extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F +start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\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\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n + +[printer:Original Prusa i3 MK2.5S] +inherits = Original Prusa i3 MK2.5 +printer_model = MK2.5S + +[printer:Original Prusa i3 MK2.5S 0.25 nozzle] +inherits = Original Prusa i3 MK2.5 0.25 nozzle +printer_model = MK2.5S + +[printer:Original Prusa i3 MK2.5S 0.6 nozzle] +inherits = Original Prusa i3 MK2.5 0.6 nozzle +printer_model = MK2.5S + +[printer:Original Prusa i3 MK2.5S MMU2S Single] +inherits = Original Prusa i3 MK2.5; *mm2s* +printer_model = MK2.5SMMU2S +single_extruder_multi_material = 0 +max_print_height = 200 +remaining_times = 1 +silent_mode = 0 +retract_lift_below = 199 +machine_max_acceleration_e = 10000 +machine_max_acceleration_extruding = 2000 +machine_max_acceleration_retracting = 1500 +machine_max_acceleration_x = 9000 +machine_max_acceleration_y = 9000 +machine_max_acceleration_z = 500 +machine_max_feedrate_e = 120 +machine_max_feedrate_x = 500 +machine_max_feedrate_y = 500 +machine_max_feedrate_z = 12 +machine_max_jerk_e = 2.5 +machine_max_jerk_x = 10 +machine_max_jerk_y = 10 +machine_max_jerk_z = 0.2 +machine_min_extruding_rate = 0 +machine_min_travel_rate = 0 +default_print_profile = 0.15mm OPTIMAL MK2.5 +default_filament_profile = Prusament PLA +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.5\n +start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\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\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors + +[printer:Original Prusa i3 MK2.5S MMU2S] +inherits = Original Prusa i3 MK2.5; *mm2s* +printer_model = MK2.5SMMU2S +max_print_height = 200 +remaining_times = 1 +silent_mode = 0 +retract_lift_below = 199 +machine_max_acceleration_e = 10000 +machine_max_acceleration_extruding = 2000 +machine_max_acceleration_retracting = 1500 +machine_max_acceleration_x = 9000 +machine_max_acceleration_y = 9000 +machine_max_acceleration_z = 500 +machine_max_feedrate_e = 120 +machine_max_feedrate_x = 500 +machine_max_feedrate_y = 500 +machine_max_feedrate_z = 12 +machine_max_jerk_e = 2.5 +machine_max_jerk_x = 10 +machine_max_jerk_y = 10 +machine_max_jerk_z = 0.2 +machine_min_extruding_rate = 0 +machine_min_travel_rate = 0 +default_print_profile = 0.15mm OPTIMAL MK2.5 +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.5\n +single_extruder_multi_material = 1 +# The 5x nozzle diameter defines the number of extruders. Other extruder parameters +# (for example the retract values) are duplicaed from the first value, so they do not need +# to be defined explicitely. +nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 +extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F +start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\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\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n + + +# XXXXXXXXXXXXXXXXX +# XXX--- MK3 ---XXX +# XXXXXXXXXXXXXXXXX + +[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\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors +machine_max_acceleration_e = 5000,5000 +machine_max_acceleration_extruding = 1250,1250 +machine_max_acceleration_retracting = 1250,1250 +machine_max_acceleration_x = 1000,960 +machine_max_acceleration_y = 1000,960 +machine_max_acceleration_z = 1000,1000 +machine_max_feedrate_e = 120,120 +machine_max_feedrate_x = 200,100 +machine_max_feedrate_y = 200,100 +machine_max_feedrate_z = 12,12 +machine_max_jerk_e = 1.5,1.5 +machine_max_jerk_x = 8,8 +machine_max_jerk_y = 8,8 +machine_max_jerk_z = 0.4,0.4 +machine_min_extruding_rate = 0,0 +machine_min_travel_rate = 0,0 +silent_mode = 1 +remaining_times = 1 +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.5.1 ; tell printer latest fw version\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.075}100{else}95{endif} +printer_model = MK3 +default_print_profile = 0.15mm QUALITY MK3 + +[printer:Original Prusa i3 MK3 0.25 nozzle] +inherits = Original Prusa i3 MK3 +nozzle_diameter = 0.25 +max_layer_height = 0.15 +min_layer_height = 0.05 +printer_variant = 0.25 +default_print_profile = 0.10mm DETAIL 0.25 nozzle MK3 + +[printer:Original Prusa i3 MK3 0.6 nozzle] +inherits = Original Prusa i3 MK3 +nozzle_diameter = 0.6 +max_layer_height = 0.35 +min_layer_height = 0.1 +printer_variant = 0.6 +default_print_profile = 0.15mm OPTIMAL 0.6 nozzle MK3 + +[printer:Original Prusa i3 MK3S] +inherits = Original Prusa i3 MK3 +printer_model = MK3S + +[printer:Original Prusa i3 MK3S 0.25 nozzle] +inherits = Original Prusa i3 MK3 0.25 nozzle +printer_model = MK3S + +[printer:Original Prusa i3 MK3S 0.6 nozzle] +inherits = Original Prusa i3 MK3 0.6 nozzle +printer_model = MK3S + +[printer:*mm2*] +inherits = Original Prusa i3 MK3 +single_extruder_multi_material = 1 +cooling_tube_length = 10 +cooling_tube_retraction = 30 +parking_pos_retraction = 85 +retract_length_toolchange = 3 +extra_loading_move = -13 +printer_model = MK3MMU2 +default_print_profile = 0.15mm QUALITY MK3 +default_filament_profile = Prusament PLA MMU2 + +[printer:*mm2s*] +inherits = Original Prusa i3 MK3 +single_extruder_multi_material = 1 +cooling_tube_length = 20 +cooling_tube_retraction = 40 +parking_pos_retraction = 85 +retract_length_toolchange = 3 +extra_loading_move = -25 +printer_model = MK3SMMU2S +default_print_profile = 0.15mm QUALITY MK3 +default_filament_profile = Prusament PLA MMU2 + +[printer:Original Prusa i3 MK3 MMU2 Single] +inherits = *mm2* +single_extruder_multi_material = 0 +default_filament_profile = Prusament PLA +start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\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\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors + +[printer:Original Prusa i3 MK3 MMU2] +inherits = *mm2* +# The 5x nozzle diameter defines the number of extruders. Other extruder parameters +# (for example the retract values) are duplicaed from the first value, so they do not need +# to be defined explicitely. +machine_max_acceleration_e = 8000,8000 +nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 +extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F +start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\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\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n + +[printer:Original Prusa i3 MK3S MMU2S Single] +inherits = *mm2s* +single_extruder_multi_material = 0 +default_filament_profile = Prusament PLA +start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\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\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors + +[printer:Original Prusa i3 MK3S MMU2S] +inherits = *mm2s* +machine_max_acceleration_e = 8000,8000 +nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 +extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F +start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\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\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n + +[printer:Original Prusa SL1] +printer_technology = SLA +printer_model = SL1 +printer_variant = default +default_sla_material_profile = Jamg He Transparent Green 0.05 +default_sla_print_profile = 0.05 Normal +bed_shape = 0.98x1.02,119.98x1.02,119.98x68.02,0.98x68.02 +display_height = 68.04 +display_orientation = portrait +display_pixels_x = 2560 +display_pixels_y = 1440 +display_width = 120.96 +max_print_height = 150 +printer_correction = 1,1,1 +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_SL1\n + +# The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. +[obsolete_presets] +print="0.05mm DETAIL 0.25 nozzle";"0.05mm DETAIL MK3";"0.05mm DETAIL";"0.20mm NORMAL MK3";"0.35mm FAST MK3";"print:0.15mm OPTIMAL MK3 MMU2";"print:0.20mm FAST MK3 MMU2" +filament="ColorFabb Brass Bronze 1.75mm";"ColorFabb HT 1.75mm";"ColorFabb nGen 1.75mm";"ColorFabb Woodfil 1.75mm";"ColorFabb XT 1.75mm";"ColorFabb XT-CF20 1.75mm";"E3D PC-ABS 1.75mm";"Fillamentum ABS 1.75mm";"Fillamentum ASA 1.75mm";"Generic ABS 1.75mm";"Generic PET 1.75mm";"Generic PLA 1.75mm";"Prusa ABS 1.75mm";"Prusa HIPS 1.75mm";"Prusa PET 1.75mm";"Prusa PLA 1.75mm";"Taulman Bridge 1.75mm";"Taulman T-Glase 1.75mm" diff --git a/resources/shaders/printbed.fs b/resources/shaders/printbed.fs new file mode 100644 index 0000000000..be14347c21 --- /dev/null +++ b/resources/shaders/printbed.fs @@ -0,0 +1,20 @@ +#version 110 + +const vec3 back_color_dark = vec3(0.235, 0.235, 0.235); +const vec3 back_color_light = vec3(0.365, 0.365, 0.365); + +uniform sampler2D texture; +uniform bool transparent_background; + +varying vec2 tex_coords; + +void main() +{ + // calculates radial gradient + vec3 back_color = vec3(mix(back_color_light, back_color_dark, smoothstep(0.0, 0.5, length(abs(tex_coords.xy) - vec2(0.5))))); + + vec4 fore_color = texture2D(texture, tex_coords); + + // blends foreground with background + gl_FragColor = vec4(mix(back_color, fore_color.rgb, fore_color.a), transparent_background ? fore_color.a : 1.0); +} \ No newline at end of file diff --git a/resources/shaders/printbed.vs b/resources/shaders/printbed.vs new file mode 100644 index 0000000000..968bcce16a --- /dev/null +++ b/resources/shaders/printbed.vs @@ -0,0 +1,11 @@ +#version 110 + +attribute vec2 v_tex_coords; + +varying vec2 tex_coords; + +void main() +{ + gl_Position = ftransform(); + tex_coords = v_tex_coords; +} \ No newline at end of file diff --git a/sandboxes/slabasebed/CMakeLists.txt b/sandboxes/slabasebed/CMakeLists.txt index bff5ca5887..9d731a1333 100644 --- a/sandboxes/slabasebed/CMakeLists.txt +++ b/sandboxes/slabasebed/CMakeLists.txt @@ -1,2 +1,2 @@ add_executable(slabasebed EXCLUDE_FROM_ALL slabasebed.cpp) -target_link_libraries(slabasebed libslic3r) \ No newline at end of file +target_link_libraries(slabasebed libslic3r ${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES} ${CMAKE_DL_LIBS}) diff --git a/sandboxes/slabasebed/slabasebed.cpp b/sandboxes/slabasebed/slabasebed.cpp index 9804ea3c94..3237416097 100644 --- a/sandboxes/slabasebed/slabasebed.cpp +++ b/sandboxes/slabasebed/slabasebed.cpp @@ -1,15 +1,29 @@ #include +#include #include #include #include #include +#include #include const std::string USAGE_STR = { "Usage: slabasebed stlfilename.stl" }; +namespace Slic3r { namespace sla { + +Contour3D convert(const Polygons& triangles, coord_t z, bool dir); +Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling, + double floor_z_mm, double ceiling_z_mm, + double offset_difference_mm, ThrowOnCancel thr); + +void offset(ExPolygon& sh, coord_t distance); + +} +} + int main(const int argc, const char *argv[]) { using namespace Slic3r; using std::cout; using std::endl; @@ -26,18 +40,43 @@ int main(const int argc, const char *argv[]) { model.align_to_origin(); ExPolygons ground_slice; - TriangleMesh basepool; + sla::Contour3D mesh; +// TriangleMesh basepool; sla::base_plate(model, ground_slice, 0.1f); + if(ground_slice.empty()) return EXIT_FAILURE; + + ExPolygon bottom_plate = ground_slice.front(); + ExPolygon top_plate = bottom_plate; + sla::offset(top_plate, coord_t(3.0/SCALING_FACTOR)); + sla::offset(bottom_plate, coord_t(1.0/SCALING_FACTOR)); + bench.start(); - sla::create_base_pool(ground_slice, basepool); + + Polygons top_plate_triangles, bottom_plate_triangles; + top_plate.triangulate_p2t(&top_plate_triangles); + bottom_plate.triangulate_p2t(&bottom_plate_triangles); + + auto top_plate_mesh = sla::convert(top_plate_triangles, coord_t(3.0/SCALING_FACTOR), false); + auto bottom_plate_mesh = sla::convert(bottom_plate_triangles, 0, true); + + mesh.merge(bottom_plate_mesh); + mesh.merge(top_plate_mesh); + + sla::Contour3D w = sla::walls(bottom_plate.contour, top_plate.contour, 0, 3, 2.0, [](){}); + + mesh.merge(w); +// sla::create_base_pool(ground_slice, basepool); bench.stop(); cout << "Base pool creation time: " << std::setprecision(10) << bench.getElapsedSec() << " seconds." << endl; - basepool.write_ascii("out.stl"); +// basepool.write_ascii("out.stl"); + + std::fstream outstream("out.obj", std::fstream::out); + mesh.to_obj(outstream); return EXIT_SUCCESS; } diff --git a/src/avrdude/ac_cfg.h b/src/avrdude/ac_cfg.h index 2461bf3071..41d648bf12 100644 --- a/src/avrdude/ac_cfg.h +++ b/src/avrdude/ac_cfg.h @@ -169,22 +169,22 @@ #define LT_OBJDIR ".libs/" /* Name of package */ -#define PACKAGE "avrdude" +#define PACKAGE "avrdude-slic3r" /* Define to the address where bug reports for this package should be sent. */ -#define PACKAGE_BUGREPORT "avrdude-dev@nongnu.org" +#define PACKAGE_BUGREPORT "https://github.com/prusa3d/Slic3r/issues" /* Define to the full name of this package. */ -#define PACKAGE_NAME "avrdude" +#define PACKAGE_NAME "avrdude-slic3r" /* Define to the full name and version of this package. */ #define PACKAGE_STRING "avrdude 6.3-20160220" /* Define to the one symbol short name of this package. */ -#define PACKAGE_TARNAME "avrdude" +#define PACKAGE_TARNAME "avrdude-slic3r" /* Define to the home page for this package. */ -#define PACKAGE_URL "" +#define PACKAGE_URL "https://github.com/prusa3d/Slic3r" /* Define to the version of this package. */ #define PACKAGE_VERSION "6.3-20160220" diff --git a/src/avrdude/avrdude-slic3r.cpp b/src/avrdude/avrdude-slic3r.cpp index 3037f52848..debf204a80 100644 --- a/src/avrdude/avrdude-slic3r.cpp +++ b/src/avrdude/avrdude-slic3r.cpp @@ -96,13 +96,20 @@ void AvrDude::priv::unset_handlers() int AvrDude::priv::run_one(const std::vector &args) { - std::vector c_args {{ const_cast(PACKAGE_NAME) }}; + std::vector c_args {{ const_cast(PACKAGE) }}; + std::string command_line { PACKAGE }; + for (const auto &arg : args) { c_args.push_back(const_cast(arg.data())); + command_line.push_back(' '); + command_line.append(arg); } + command_line.push_back('\n'); HandlerGuard guard(*this); + message_fn(command_line.c_str(), command_line.size()); + const auto res = ::avrdude_main(static_cast(c_args.size()), c_args.data(), sys_config.c_str()); return res; diff --git a/src/avrdude/main.c b/src/avrdude/main.c index 9ada27be35..80a9b3737a 100644 --- a/src/avrdude/main.c +++ b/src/avrdude/main.c @@ -1082,6 +1082,7 @@ int avrdude_main(int argc, char * argv [], const char *sys_config) if (rc < 0) { exitrc = 1; pgm->ppidata = 0; /* clear all bits at exit */ + avrdude_message(MSG_INFO, "%s: Could not open port: %s\n", progname, port); goto main_exit; } is_open = 1; diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 016d162d84..75952e4c23 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -69,7 +69,9 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) if (surface.is_solid() && (!surface.is_bridge() || layerm.layer()->id() == 0)) { group_attrib[i].is_solid = true; group_attrib[i].flow_width = (surface.surface_type == stTop) ? top_solid_infill_flow.width : solid_infill_flow.width; - group_attrib[i].pattern = surface.is_external() ? layerm.region()->config().external_fill_pattern.value : ipRectilinear; + group_attrib[i].pattern = surface.is_external() ? + (surface.is_top() ? layerm.region()->config().top_fill_pattern.value : layerm.region()->config().bottom_fill_pattern.value) : + ipRectilinear; } } // Loop through solid groups, find compatible groups and append them to this one. @@ -161,7 +163,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) if (surface.is_solid()) { density = 100.; fill_pattern = (surface.is_external() && ! is_bridge) ? - layerm.region()->config().external_fill_pattern.value : + (surface.is_top() ? layerm.region()->config().top_fill_pattern.value : layerm.region()->config().bottom_fill_pattern.value) : ipRectilinear; } else if (density <= 0) continue; diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 8b0c28cd6f..47f78b3606 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -323,7 +323,7 @@ namespace Slic3r { typedef std::map IdToMetadataMap; typedef std::map IdToGeometryMap; typedef std::map> IdToLayerHeightsProfileMap; - typedef std::map> IdToSlaSupportPointsMap; + typedef std::map> IdToSlaSupportPointsMap; // Version of the 3mf file unsigned int m_version; @@ -776,10 +776,19 @@ namespace Slic3r { std::vector objects; boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); + // Info on format versioning - see 3mf.hpp + int version = 0; + if (!objects.empty() && objects[0].find("support_points_format_version=") != std::string::npos) { + objects[0].erase(objects[0].begin(), objects[0].begin() + 30); // removes the string + version = std::stoi(objects[0]); + objects.erase(objects.begin()); // pop the header + } + 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"); @@ -811,10 +820,24 @@ namespace Slic3r { std::vector object_data_points; boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); - std::vector sla_support_points; + std::vector sla_support_points; - for (unsigned int i=0; i()); + if (version == 0) { + for (unsigned int i=0; iname = metadata.value; else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1")) - volume->set_type(ModelVolume::PARAMETER_MODIFIER); + volume->set_type(ModelVolumeType::PARAMETER_MODIFIER); else if (metadata.key == VOLUME_TYPE_KEY) volume->set_type(ModelVolume::type_from_string(metadata.value)); else @@ -1961,7 +1984,7 @@ namespace Slic3r { for (const ModelObject* object : model.objects) { ++count; - const std::vector& sla_support_points = object->sla_support_points; + const std::vector& sla_support_points = object->sla_support_points; if (!sla_support_points.empty()) { sprintf(buffer, "object_id=%d|", count); @@ -1970,7 +1993,7 @@ namespace Slic3r { // Store the layer height profile as a single space separated list. for (size_t i = 0; i < sla_support_points.size(); ++i) { - sprintf(buffer, (i==0 ? "%f %f %f" : " %f %f %f"), sla_support_points[i](0), sla_support_points[i](1), sla_support_points[i](2)); + sprintf(buffer, (i==0 ? "%f %f %f %f %f" : " %f %f %f %f %f"), sla_support_points[i].pos(0), sla_support_points[i].pos(1), sla_support_points[i].pos(2), sla_support_points[i].head_front_radius, (float)sla_support_points[i].is_new_island); out += buffer; } out += "\n"; @@ -1979,6 +2002,9 @@ namespace Slic3r { if (!out.empty()) { + // Adds version header at the beginning: + out = std::string("support_points_format_version=") + std::to_string(support_points_format_version) + std::string("\n") + out; + if (!mz_zip_writer_add_mem(&archive, SLA_SUPPORT_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { add_error("Unable to add sla support points file to archive"); diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index 44b37c1aee..b5927651ec 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -3,6 +3,23 @@ namespace Slic3r { + /* The format for saving the SLA points was changing in the past. This enum holds the latest version that is being currently used. + * Examples of the Slic3r_PE_sla_support_points.txt for historically used versions: + + * version 0 : object_id=1|-12.055421 -2.658771 10.000000 + object_id=2|-14.051745 -3.570338 5.000000 + // no header and x,y,z positions of the points) + + * version 1 : ThreeMF_support_points_version=1 + object_id=1|-12.055421 -2.658771 10.000000 0.4 0.0 + object_id=2|-14.051745 -3.570338 5.000000 0.6 1.0 + // introduced header with version number; x,y,z,head_size,is_new_island) + */ + + enum { + support_points_format_version = 1 + }; + class Model; class DynamicPrintConfig; diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 9791b15022..2f5284db94 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -583,7 +583,7 @@ void AMFParserContext::endElement(const char * /* name */) else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "sla_support_points") == 0) { // Parse object's layer height profile, a semicolon separated list of floats. unsigned char coord_idx = 0; - Vec3f point(Vec3f::Zero()); + Eigen::Matrix point(Eigen::Matrix::Zero()); char *p = const_cast(m_value[1].c_str()); for (;;) { char *end = strchr(p, ';'); @@ -591,8 +591,8 @@ void AMFParserContext::endElement(const char * /* name */) *end = 0; point(coord_idx) = atof(p); - if (++coord_idx == 3) { - m_object->sla_support_points.push_back(point); + if (++coord_idx == 5) { + m_object->sla_support_points.push_back(sla::SupportPoint(point)); coord_idx = 0; } if (end == nullptr) @@ -604,7 +604,7 @@ void AMFParserContext::endElement(const char * /* name */) if (strcmp(opt_key, "modifier") == 0) { // Is this volume a modifier volume? // "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag. - m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); + m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART); } else if (strcmp(opt_key, "volume_type") == 0) { m_volume->set_type(ModelVolume::type_from_string(m_value[1])); } @@ -900,14 +900,14 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) } //FIXME Store the layer height ranges (ModelObject::layer_height_ranges) - const std::vector& sla_support_points = object->sla_support_points; + const std::vector& sla_support_points = object->sla_support_points; if (!sla_support_points.empty()) { // Store the SLA supports as a single semicolon separated list. stream << " "; for (size_t i = 0; i < sla_support_points.size(); ++i) { if (i != 0) stream << ";"; - stream << sla_support_points[i](0) << ";" << sla_support_points[i](1) << ";" << sla_support_points[i](2); + stream << sla_support_points[i].pos(0) << ";" << sla_support_points[i].pos(1) << ";" << sla_support_points[i].pos(2) << ";" << sla_support_points[i].head_front_radius << ";" << sla_support_points[i].is_new_island; } stream << "\n \n"; } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 4227172796..6e291412c2 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1480,32 +1480,32 @@ const TriangleMesh& ModelVolume::get_convex_hull() const return m_convex_hull; } -ModelVolume::Type ModelVolume::type_from_string(const std::string &s) +ModelVolumeType ModelVolume::type_from_string(const std::string &s) { // Legacy support if (s == "1") - return PARAMETER_MODIFIER; + return ModelVolumeType::PARAMETER_MODIFIER; // New type (supporting the support enforcers & blockers) if (s == "ModelPart") - return MODEL_PART; + return ModelVolumeType::MODEL_PART; if (s == "ParameterModifier") - return PARAMETER_MODIFIER; + return ModelVolumeType::PARAMETER_MODIFIER; if (s == "SupportEnforcer") - return SUPPORT_ENFORCER; + return ModelVolumeType::SUPPORT_ENFORCER; if (s == "SupportBlocker") - return SUPPORT_BLOCKER; + return ModelVolumeType::SUPPORT_BLOCKER; assert(s == "0"); // Default value if invalud type string received. - return MODEL_PART; + return ModelVolumeType::MODEL_PART; } -std::string ModelVolume::type_to_string(const Type t) +std::string ModelVolume::type_to_string(const ModelVolumeType t) { switch (t) { - case MODEL_PART: return "ModelPart"; - case PARAMETER_MODIFIER: return "ParameterModifier"; - case SUPPORT_ENFORCER: return "SupportEnforcer"; - case SUPPORT_BLOCKER: return "SupportBlocker"; + case ModelVolumeType::MODEL_PART: return "ModelPart"; + case ModelVolumeType::PARAMETER_MODIFIER: return "ParameterModifier"; + case ModelVolumeType::SUPPORT_ENFORCER: return "SupportEnforcer"; + case ModelVolumeType::SUPPORT_BLOCKER: return "SupportBlocker"; default: assert(false); return "ModelPart"; @@ -1671,7 +1671,7 @@ bool model_object_list_extended(const Model &model_old, const Model &model_new) return true; } -bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolume::Type type) +bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type) { bool modifiers_differ = false; size_t i_old, i_new; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index b998fbb7db..a215f9b9f1 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -12,6 +12,7 @@ #include #include #include "Geometry.hpp" +#include namespace Slic3r { @@ -48,6 +49,8 @@ struct ModelID bool operator<=(const ModelID &rhs) const { return this->id <= rhs.id; } bool operator>=(const ModelID &rhs) const { return this->id >= rhs.id; } + bool valid() const { return id != 0; } + size_t id; }; @@ -175,7 +178,8 @@ public: // This vector holds position of selected support points for SLA. The data are // saved in mesh coordinates to allow using them for several instances. - std::vector sla_support_points; + // The format is (x, y, z, point_size, supports_island) + std::vector sla_support_points; /* This vector accumulates the total translation applied to the object by the center_around_origin() method. Callers might want to apply the same translation @@ -291,6 +295,15 @@ private: mutable bool m_raw_mesh_bounding_box_valid; }; +// Declared outside of ModelVolume, so it could be forward declared. +enum class ModelVolumeType : int { + INVALID = -1, + MODEL_PART = 0, + PARAMETER_MODIFIER, + SUPPORT_ENFORCER, + SUPPORT_BLOCKER, +}; + // An object STL, or a modifier volume, over which a different set of parameters shall be applied. // ModelVolume instances are owned by a ModelObject. class ModelVolume : public ModelBase @@ -303,23 +316,15 @@ public: // overriding the global Slic3r settings and the ModelObject settings. DynamicPrintConfig config; - enum Type { - MODEL_TYPE_INVALID = -1, - MODEL_PART = 0, - PARAMETER_MODIFIER, - SUPPORT_ENFORCER, - SUPPORT_BLOCKER, - }; - // A parent object owning this modifier volume. ModelObject* get_object() const { return this->object; }; - Type type() const { return m_type; } - void set_type(const Type t) { m_type = t; } - bool is_model_part() const { return m_type == MODEL_PART; } - bool is_modifier() const { return m_type == PARAMETER_MODIFIER; } - bool is_support_enforcer() const { return m_type == SUPPORT_ENFORCER; } - bool is_support_blocker() const { return m_type == SUPPORT_BLOCKER; } - bool is_support_modifier() const { return m_type == SUPPORT_BLOCKER || m_type == SUPPORT_ENFORCER; } + ModelVolumeType type() const { return m_type; } + void set_type(const ModelVolumeType t) { m_type = t; } + bool is_model_part() const { return m_type == ModelVolumeType::MODEL_PART; } + bool is_modifier() const { return m_type == ModelVolumeType::PARAMETER_MODIFIER; } + bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; } + bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; } + bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; } t_model_material_id material_id() const { return m_material_id; } void set_material_id(t_model_material_id material_id); ModelMaterial* material() const; @@ -353,8 +358,8 @@ public: const TriangleMesh& get_convex_hull() const; // Helpers for loading / storing into AMF / 3MF files. - static Type type_from_string(const std::string &s); - static std::string type_to_string(const Type t); + static ModelVolumeType type_from_string(const std::string &s); + static std::string type_to_string(const ModelVolumeType t); const Geometry::Transformation& get_transformation() const { return m_transformation; } void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } @@ -399,7 +404,7 @@ private: // Parent object owning this ModelVolume. ModelObject* object; // Is it an object to be printed, or a modifier volume? - Type m_type; + ModelVolumeType m_type; t_model_material_id m_material_id; // The convex hull of this model's mesh. TriangleMesh m_convex_hull; @@ -411,13 +416,13 @@ private: // 1 -> is splittable int m_is_splittable {-1}; - ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(MODEL_PART), object(object) + ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(ModelVolumeType::MODEL_PART), object(object) { if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); } ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : - mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(MODEL_PART), object(object) {} + mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(ModelVolumeType::MODEL_PART), object(object) {} // Copying an existing volume, therefore this volume will get a copy of the ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other) : @@ -629,7 +634,7 @@ extern bool model_object_list_extended(const Model &model_old, const Model &mode // Test whether the new ModelObject contains a different set of volumes (or sorted in a different order) // than the old ModelObject. -extern bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolume::Type type); +extern bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type); #ifndef NDEBUG // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index d456fb9c6c..a03e6f436a 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -22,6 +22,7 @@ typedef Point Vector; // Vector types with a fixed point coordinate base type. typedef Eigen::Matrix Vec2crd; typedef Eigen::Matrix Vec3crd; +typedef Eigen::Matrix Vec2i; typedef Eigen::Matrix Vec3i; typedef Eigen::Matrix Vec2i64; typedef Eigen::Matrix Vec3i64; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 6416a709ad..87e78f11c1 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -280,7 +280,7 @@ bool Print::is_step_done(PrintObjectStep step) const return false; tbb::mutex::scoped_lock lock(this->state_mutex()); for (const PrintObject *object : m_objects) - if (! object->m_state.is_done_unguarded(step)) + if (! object->is_step_done_unguarded(step)) return false; return true; } @@ -549,10 +549,14 @@ void Print::model_volume_list_update_supports(ModelObject &model_object_dst, con assert(! it->second); // not consumed yet it->second = true; ModelVolume *model_volume_dst = const_cast(it->first); - assert(model_volume_dst->type() == model_volume_src->type()); + // For support modifiers, the type may have been switched from blocker to enforcer and vice versa. + assert((model_volume_dst->is_support_modifier() && model_volume_src->is_support_modifier()) || model_volume_dst->type() == model_volume_src->type()); model_object_dst.volumes.emplace_back(model_volume_dst); - if (model_volume_dst->is_support_modifier()) - model_volume_dst->set_transformation(model_volume_src->get_transformation()); + if (model_volume_dst->is_support_modifier()) { + // For support modifiers, the type may have been switched from blocker to enforcer and vice versa. + model_volume_dst->set_type(model_volume_src->type()); + model_volume_dst->set_transformation(model_volume_src->get_transformation()); + } assert(model_volume_dst->get_matrix().isApprox(model_volume_src->get_matrix())); } else { // The volume was not found in the old list. Create a new copy. @@ -567,7 +571,7 @@ void Print::model_volume_list_update_supports(ModelObject &model_object_dst, con delete mv_with_status.first; } -static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, const ModelObject &model_object_src, const ModelVolume::Type type) +static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, const ModelObject &model_object_src, const ModelVolumeType type) { size_t i_src, i_dst; for (i_src = 0, i_dst = 0; i_src < model_object_src.volumes.size() && i_dst < model_object_dst.volumes.size();) { @@ -713,7 +717,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co if (model.id() != m_model.id()) { // Kill everything, initialize from scratch. // Stop background processing. - this->call_cancell_callback(); + this->call_cancel_callback(); update_apply_status(this->invalidate_all_steps()); for (PrintObject *object : m_objects) { model_object_status.emplace(object->model_object()->id(), ModelObjectStatus::Deleted); @@ -745,7 +749,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co } else { // Reorder the objects, add new objects. // First stop background processing before shuffling or deleting the PrintObjects in the object list. - this->call_cancell_callback(); + this->call_cancel_callback(); update_apply_status(this->invalidate_step(psGCodeExport)); // Second create a new list of objects. std::vector model_objects_old(std::move(m_model.objects)); @@ -837,10 +841,10 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); const ModelObject &model_object_new = *model.objects[idx_model_object]; // Check whether a model part volume was added or removed, their transformations or order changed. - bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::MODEL_PART); - bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::PARAMETER_MODIFIER); - bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::SUPPORT_BLOCKER); - bool support_enforcers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::SUPPORT_ENFORCER); + bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); + bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER); + bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER); + bool support_enforcers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); if (model_parts_differ || modifiers_differ || model_object.origin_translation != model_object_new.origin_translation || model_object.layer_height_ranges != model_object_new.layer_height_ranges || @@ -855,7 +859,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co model_object.assign_copy(model_object_new); } else if (support_blockers_differ || support_enforcers_differ) { // First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list. - this->call_cancell_callback(); + this->call_cancel_callback(); update_apply_status(false); // Invalidate just the supports step. auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); @@ -882,8 +886,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co } // Synchronize (just copy) the remaining data of ModelVolumes (name, config). //FIXME What to do with m_material_id? - model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolume::MODEL_PART); - model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolume::PARAMETER_MODIFIER); + model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART); + model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER); // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step. model_object.name = model_object_new.name; model_object.input_file = model_object_new.input_file; @@ -956,7 +960,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co } } if (m_objects != print_objects_new) { - this->call_cancell_callback(); + this->call_cancel_callback(); update_apply_status(this->invalidate_all_steps()); m_objects = print_objects_new; // Delete the PrintObjects marked as Unknown or Deleted. @@ -1502,7 +1506,8 @@ void Print::export_gcode(const std::string &path_template, GCodePreviewData *pre // The following call may die if the output_filename_format template substitution fails. std::string path = this->output_filepath(path_template); std::string message = "Exporting G-code"; - if (! path.empty()) { + if (! path.empty() && preview_data == nullptr) { + // Only show the path if preview_data is not set -> running from command line. message += " to "; message += path; } @@ -1863,7 +1868,7 @@ std::string Print::output_filename() const DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders(); return this->PrintBase::output_filename(m_config.output_filename_format.value, "gcode", &config); } - +/* // Shorten the dhms time by removing the seconds, rounding the dhm to full minutes // and removing spaces. static std::string short_time(const std::string &time) @@ -1903,7 +1908,7 @@ static std::string short_time(const std::string &time) ::sprintf(buffer, "%ds", seconds); return buffer; } - +*/ DynamicConfig PrintStatistics::config() const { DynamicConfig config; diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 84d04d26fe..e01b678a53 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -62,6 +62,10 @@ public: return state; } + bool is_started(StepType step, tbb::mutex &mtx) const { + return this->state_with_timestamp(step, mtx).state == STARTED; + } + bool is_done(StepType step, tbb::mutex &mtx) const { return this->state_with_timestamp(step, mtx).state == DONE; } @@ -70,6 +74,10 @@ public: return m_state[step]; } + bool is_started_unguarded(StepType step) const { + return this->state_with_timestamp_unguarded(step).state == STARTED; + } + bool is_done_unguarded(StepType step) const { return this->state_with_timestamp_unguarded(step).state == DONE; } @@ -235,7 +243,24 @@ public: virtual ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) = 0; const Model& model() const { return m_model; } + struct TaskParams { + TaskParams() : single_model_object(0), single_model_instance_only(false), to_object_step(-1), to_print_step(-1) {} + // If non-empty, limit the processing to this ModelObject. + ModelID single_model_object; + // If set, only process single_model_object. Otherwise process everything, but single_model_object first. + bool single_model_instance_only; + // If non-negative, stop processing at the successive object step. + int to_object_step; + // If non-negative, stop processing at the successive print step. + int to_print_step; + }; + // After calling the apply() function, call set_task() to limit the task to be processed by process(). + virtual void set_task(const TaskParams ¶ms) {} + // Perform the calculation. This is the only method that is to be called at a worker thread. virtual void process() = 0; + // Clean up after process() finished, either with success, error or if canceled. + // The adjustments on the Print / PrintObject data due to set_task() are to be reverted here. + virtual void finalize() {} struct SlicingStatus { SlicingStatus(int percent, const std::string &text, unsigned int flags = 0) : percent(percent), text(text), flags(flags) {} @@ -244,8 +269,9 @@ public: // Bitmap of flags. enum FlagBits { DEFAULT, - NO_RELOAD_SCENE = 0, - RELOAD_SCENE = 1, + NO_RELOAD_SCENE = 0, + RELOAD_SCENE = 1 << 1, + RELOAD_SLA_SUPPORT_POINTS = 1 << 2, }; // Bitmap of FlagBits unsigned int flags; @@ -300,7 +326,7 @@ protected: tbb::mutex& state_mutex() const { return m_state_mutex; } std::function cancel_callback() { return m_cancel_callback; } - void call_cancell_callback() { m_cancel_callback(); } + void call_cancel_callback() { m_cancel_callback(); } // If the background processing stop was requested, throw CanceledException. // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. @@ -349,6 +375,9 @@ protected: bool invalidate_all_steps() { return m_state.invalidate_all(this->cancel_callback()); } + bool is_step_started_unguarded(PrintStepEnum step) const { return m_state.is_started_unguarded(step); } + bool is_step_done_unguarded(PrintStepEnum step) const { return m_state.is_done_unguarded(step); } + private: PrintState m_state; }; @@ -382,6 +411,9 @@ protected: bool invalidate_all_steps() { return m_state.invalidate_all(PrintObjectBase::cancel_callback(m_print)); } + bool is_step_started_unguarded(PrintObjectStepEnum step) const { return m_state.is_started_unguarded(step); } + bool is_step_done_unguarded(PrintObjectStepEnum step) const { return m_state.is_done_unguarded(step); } + protected: // If the background processing stop was requested, throw CanceledException. // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 3e1ee9f9f8..cc644fc748 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -362,12 +362,11 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); - def = this->add("external_fill_pattern", coEnum); - def->label = L("Top/bottom fill pattern"); + auto def_top_fill_pattern = def = this->add("top_fill_pattern", coEnum); + def->label = L("Top fill pattern"); def->category = L("Infill"); - def->tooltip = L("Fill pattern for top/bottom infill. This only affects the external visible layer, " - "and not its adjacent solid shells."); - def->cli = "external-fill-pattern|solid-fill-pattern=s"; + def->tooltip = L("Fill pattern for top infill. This only affects the top visible layer, and not its adjacent solid shells."); + def->cli = "top-fill-pattern|external-fill-pattern|solid-fill-pattern=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("rectilinear"); def->enum_values.push_back("concentric"); @@ -379,8 +378,15 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Hilbert Curve")); def->enum_labels.push_back(L("Archimedean Chords")); def->enum_labels.push_back(L("Octagram Spiral")); - // solid_fill_pattern is an obsolete equivalent to external_fill_pattern. - def->aliases = { "solid_fill_pattern" }; + // solid_fill_pattern is an obsolete equivalent to top_fill_pattern/bottom_fill_pattern. + def->aliases = { "solid_fill_pattern", "external_fill_pattern" }; + def->default_value = new ConfigOptionEnum(ipRectilinear); + + def = this->add("bottom_fill_pattern", coEnum); + *def = *def_top_fill_pattern; + def->label = L("Bottom Pattern"); + def->tooltip = L("Fill pattern for bottom infill. This only affects the bottom external visible layer, and not its adjacent solid shells."); + def->cli = "bottom-fill-pattern|external-fill-pattern|solid-fill-pattern=s"; def->default_value = new ConfigOptionEnum(ipRectilinear); def = this->add("external_perimeter_extrusion_width", coFloatOrPercent); @@ -2435,6 +2441,32 @@ void PrintConfigDef::init_sla_params() def->enum_labels.push_back(L("Portrait")); def->default_value = new ConfigOptionEnum(sladoPortrait); + def = this->add("fast_tilt_time", coFloat); + def->label = L("Fast"); + def->full_label = L("Fast tilt"); + def->tooltip = L("Time of the fast tilt"); + def->sidetext = L("s"); + def->min = 0; + def->mode = comExpert; + def->default_value = new ConfigOptionFloat(5.); + + def = this->add("slow_tilt_time", coFloat); + def->label = L("Slow"); + def->full_label = L("Slow tilt"); + def->tooltip = L("Time of the slow tilt"); + def->sidetext = L("s"); + def->min = 0; + def->mode = comExpert; + def->default_value = new ConfigOptionFloat(8.); + + def = this->add("area_fill", coFloat); + def->label = L("Area fill"); + def->tooltip = L("The percentage of the bed area. \nIf the print area exceeds the specified value, \nthen a slow tilt will be used, otherwise - a fast tilt"); + def->sidetext = L("%"); + def->min = 0; + def->mode = comExpert; + def->default_value = new ConfigOptionFloat(50.); + def = this->add("printer_correction", coFloats); def->full_label = L("Printer scaling correction"); def->tooltip = L("Printer scaling correction"); @@ -2450,6 +2482,14 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->default_value = new ConfigOptionFloat(0.3); + def = this->add("faded_layers", coInt); + def->label = L("Faded layers"); + def->tooltip = L("Number of the layers needed for the exposure time fade from initial exposure time to the exposure time"); + def->min = 3; + def->max = 20; + def->mode = comExpert; + def->default_value = new ConfigOptionInt(10); + def = this->add("exposure_time", coFloat); def->label = L("Exposure time"); def->tooltip = L("Exposure time"); @@ -2630,28 +2670,19 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->default_value = new ConfigOptionFloat(5.0); - def = this->add("support_density_at_horizontal", coInt); - def->label = L("Density on horizontal surfaces"); + def = this->add("support_points_density_relative", coInt); + def->label = L("Support points density"); def->category = L("Supports"); - def->tooltip = L("How many support points (approximately) should be placed on horizontal surface."); - def->sidetext = L("points per square dm"); + def->tooltip = L("This is a relative measure of support points density."); + def->sidetext = L("%"); def->cli = ""; def->min = 0; - def->default_value = new ConfigOptionInt(500); + def->default_value = new ConfigOptionInt(100); - def = this->add("support_density_at_45", coInt); - def->label = L("Density on surfaces at 45 degrees"); + def = this->add("support_points_minimal_distance", coFloat); + def->label = L("Minimal distance of the support points"); def->category = L("Supports"); - def->tooltip = L("How many support points (approximately) should be placed on surface sloping at 45 degrees."); - def->sidetext = L("points per square dm"); - def->cli = ""; - def->min = 0; - def->default_value = new ConfigOptionInt(250); - - def = this->add("support_minimal_z", coFloat); - def->label = L("Minimal support point height"); - def->category = L("Supports"); - def->tooltip = L("No support points will be placed lower than this value from the bottom."); + def->tooltip = L("No support points will be placed closer than this threshold."); def->sidetext = L("mm"); def->cli = ""; def->min = 0; @@ -2699,6 +2730,17 @@ void PrintConfigDef::init_sla_params() def->cli = ""; def->min = 0; def->default_value = new ConfigOptionFloat(1.0); + + def = this->add("pad_wall_tilt", coFloat); + def->label = L("Pad wall tilt"); + def->category = L("Pad"); + def->tooltip = L("The tilt of the pad wall relative to the bed plane. " + "90 degrees means straight walls."); + def->sidetext = L("degrees"); + def->cli = ""; + def->min = 0.1; // What should be the minimum? + def->max = 90; + def->default_value = new ConfigOptionFloat(45.0); } void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) @@ -2914,13 +2956,17 @@ std::string FullPrintConfig::validate() if (! print_config_def.get("fill_pattern")->has_enum_value(this->fill_pattern.serialize())) return "Invalid value for --fill-pattern"; - // --external-fill-pattern - if (! print_config_def.get("external_fill_pattern")->has_enum_value(this->external_fill_pattern.serialize())) - return "Invalid value for --external-fill-pattern"; + // --top-fill-pattern + if (! print_config_def.get("top_fill_pattern")->has_enum_value(this->top_fill_pattern.serialize())) + return "Invalid value for --top-fill-pattern"; + + // --bottom-fill-pattern + if (! print_config_def.get("bottom_fill_pattern")->has_enum_value(this->bottom_fill_pattern.serialize())) + return "Invalid value for --bottom-fill-pattern"; // --fill-density if (fabs(this->fill_density.value - 100.) < EPSILON && - ! print_config_def.get("external_fill_pattern")->has_enum_value(this->fill_pattern.serialize())) + ! print_config_def.get("top_fill_pattern")->has_enum_value(this->fill_pattern.serialize())) return "The selected fill pattern is not supposed to work at 100% density"; // --infill-every-layers diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 20c089d1c0..21bc32ed95 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -462,7 +462,8 @@ public: ConfigOptionFloat bridge_flow_ratio; ConfigOptionFloat bridge_speed; ConfigOptionBool ensure_vertical_shell_thickness; - ConfigOptionEnum external_fill_pattern; + ConfigOptionEnum top_fill_pattern; + ConfigOptionEnum bottom_fill_pattern; ConfigOptionFloatOrPercent external_perimeter_extrusion_width; ConfigOptionFloatOrPercent external_perimeter_speed; ConfigOptionBool external_perimeters_first; @@ -504,7 +505,8 @@ protected: OPT_PTR(bridge_flow_ratio); OPT_PTR(bridge_speed); OPT_PTR(ensure_vertical_shell_thickness); - OPT_PTR(external_fill_pattern); + OPT_PTR(top_fill_pattern); + OPT_PTR(bottom_fill_pattern); OPT_PTR(external_perimeter_extrusion_width); OPT_PTR(external_perimeter_speed); OPT_PTR(external_perimeters_first); @@ -958,6 +960,9 @@ class SLAPrintObjectConfig : public StaticPrintConfig public: ConfigOptionFloat layer_height; + //Number of the layers needed for the exposure time fade [3;20] + ConfigOptionInt faded_layers /*= 10*/; + // Enabling or disabling support creation ConfigOptionBool supports_enable; @@ -1002,9 +1007,8 @@ public: ConfigOptionFloat support_object_elevation /*= 5.0*/; /////// Following options influence automatic support points placement: - ConfigOptionInt support_density_at_horizontal; - ConfigOptionInt support_density_at_45; - ConfigOptionFloat support_minimal_z; + ConfigOptionInt support_points_density_relative; + ConfigOptionFloat support_points_minimal_distance; // Now for the base pool (pad) ///////////////////////////////////////////// @@ -1024,10 +1028,14 @@ public: // The smoothing radius of the pad edges ConfigOptionFloat pad_edge_radius /*= 1*/; + // The tilt of the pad wall... + ConfigOptionFloat pad_wall_tilt; + protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { OPT_PTR(layer_height); + OPT_PTR(faded_layers); OPT_PTR(supports_enable); OPT_PTR(support_head_front_diameter); OPT_PTR(support_head_penetration); @@ -1040,15 +1048,15 @@ protected: OPT_PTR(support_base_height); OPT_PTR(support_critical_angle); OPT_PTR(support_max_bridge_length); - OPT_PTR(support_density_at_horizontal); - OPT_PTR(support_density_at_45); - OPT_PTR(support_minimal_z); + OPT_PTR(support_points_density_relative); + OPT_PTR(support_points_minimal_distance); OPT_PTR(support_object_elevation); OPT_PTR(pad_enable); OPT_PTR(pad_wall_thickness); OPT_PTR(pad_wall_height); OPT_PTR(pad_max_merge_distance); OPT_PTR(pad_edge_radius); + OPT_PTR(pad_wall_tilt); } }; @@ -1085,6 +1093,9 @@ public: ConfigOptionInt display_pixels_y; ConfigOptionEnum display_orientation; ConfigOptionFloats printer_correction; + ConfigOptionFloat fast_tilt_time; + ConfigOptionFloat slow_tilt_time; + ConfigOptionFloat area_fill; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { @@ -1097,6 +1108,9 @@ protected: OPT_PTR(display_pixels_y); OPT_PTR(display_orientation); OPT_PTR(printer_correction); + OPT_PTR(fast_tilt_time); + OPT_PTR(slow_tilt_time); + OPT_PTR(area_fill); } }; diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp index 5cfb552176..0df4058468 100644 --- a/src/libslic3r/PrintExport.hpp +++ b/src/libslic3r/PrintExport.hpp @@ -14,6 +14,17 @@ namespace Slic3r { +// Used for addressing parameters of FilePrinter::set_statistics() +enum ePrintStatistics +{ + psUsedMaterial = 0, + psNumFade, + psNumSlow, + psNumFast, + + psCnt +}; + enum class FilePrinterFormat { SLA_PNGZIP, SVG @@ -118,32 +129,39 @@ template<> class FilePrinter double m_layer_height = .0; Raster::Origin m_o = Raster::Origin::TOP_LEFT; + double m_used_material = 0.0; + int m_cnt_fade_layers = 0; + int m_cnt_slow_layers = 0; + int m_cnt_fast_layers = 0; + std::string createIniContent(const std::string& projectname) { - double layer_height = m_layer_height; +// double layer_height = m_layer_height; using std::string; using std::to_string; auto expt_str = to_string(m_exp_time_s); auto expt_first_str = to_string(m_exp_time_first_s); - auto stepnum_str = to_string(static_cast(800*layer_height)); - auto layerh_str = to_string(layer_height); +// auto stepnum_str = to_string(static_cast(800*layer_height)); + auto layerh_str = to_string(m_layer_height); + + const std::string cnt_fade_layers = to_string(m_cnt_fade_layers); + const std::string cnt_slow_layers = to_string(m_cnt_slow_layers); + const std::string cnt_fast_layers = to_string(m_cnt_fast_layers); + const std::string used_material = to_string(m_used_material); return string( "action = print\n" "jobDir = ") + projectname + "\n" + "expTime = " + expt_str + "\n" "expTimeFirst = " + expt_first_str + "\n" - "stepNum = " + stepnum_str + "\n" - "wifiOn = 1\n" - "tiltSlow = 60\n" - "tiltFast = 15\n" - "numFade = 10\n" - "startdelay = 0\n" + "numFade = " + cnt_fade_layers + "\n" "layerHeight = " + layerh_str + "\n" - "noteInfo = " - "expTime="+expt_str+"+resinType=generic+layerHeight=" - +layerh_str+"+printer=DWARF3\n"; + "expTime = "+expt_str+" + resinType = generic+layerHeight = " + +layerh_str+" + printer = DWARF3\n" + "usedMaterial = " + used_material + "\n" + "numSlow = " + cnt_slow_layers + "\n" + "numFast = " + cnt_fast_layers + "\n"; } public: @@ -277,6 +295,17 @@ public: out.close(); m_layers_rst[i].first.reset(); } + + void set_statistics(const std::vector statistics) + { + if (statistics.size() != psCnt) + return; + + m_used_material = statistics[psUsedMaterial]; + m_cnt_fade_layers = int(statistics[psNumFade]); + m_cnt_slow_layers = int(statistics[psNumSlow]); + m_cnt_fast_layers = int(statistics[psNumFast]); + } }; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d516153a92..8fa1436818 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -498,7 +498,8 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector + #include "SLAAutoSupports.hpp" #include "Model.hpp" #include "ExPolygon.hpp" #include "SVG.hpp" #include "Point.hpp" #include "ClipperUtils.hpp" +#include "Tesselate.hpp" +#include "libslic3r.h" #include #include namespace Slic3r { -SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, const std::vector& heights, - const Config& config, std::function throw_on_cancel) -: m_config(config), m_V(emesh.V()), m_F(emesh.F()), m_throw_on_cancel(throw_on_cancel) -{ - // FIXME: It might be safer to get rid of the rand() calls altogether, because it is probably - // not always thread-safe and can be slow if it is. - srand(time(NULL)); // rand() is used by igl::random_point_on_mesh - - // Find all separate islands that will need support. The coord_t number denotes height - // of a point just below the mesh (so that we can later project the point precisely - // on the mesh by raycasting (done by igl) and not risking we will place the point inside). - std::vector> islands = find_islands(slices, heights); - - // Uniformly cover each of the islands with support points. - for (const auto& island : islands) { - std::vector points = uniformly_cover(island); - m_throw_on_cancel(); - project_upward_onto_mesh(points); - m_output.insert(m_output.end(), points.begin(), points.end()); - m_throw_on_cancel(); - } - - // We are done with the islands. Let's sprinkle the rest of the mesh. - // The function appends to m_output. - sprinkle_mesh(mesh); -} - - -float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2) +/*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2) { n1.normalize(); n2.normalize(); @@ -59,115 +35,6 @@ float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3 } -void SLAAutoSupports::sprinkle_mesh(const TriangleMesh& mesh) -{ - std::vector points; - // Loads the ModelObject raw_mesh and transforms it by first instance's transformation matrix (disregarding translation). - // Instances only differ in z-rotation, so it does not matter which of them will be used for the calculation. - // The supports point will be calculated on this mesh (so scaling ang vertical direction is correctly accounted for). - // Results will be inverse-transformed to raw_mesh coordinates. - //TriangleMesh mesh = m_model_object.raw_mesh(); - //Transform3d transformation_matrix = m_model_object.instances[0]->get_matrix(true/*dont_translate*/); - //mesh.transform(transformation_matrix); - - // Check that the object is thick enough to produce any support points - BoundingBoxf3 bb = mesh.bounding_box(); - if (bb.size()(2) < m_config.minimal_z) - return; - - // All points that we curretly have must be transformed too, so distance to them is correcly calculated. - //for (Vec3f& point : m_model_object.sla_support_points) - // point = transformation_matrix.cast() * point; - - - // In order to calculate distance to already placed points, we must keep know which facet the point lies on. - std::vector facets_normals; - - // Only points belonging to islands were added so far - they all lie on horizontal surfaces: - for (unsigned int i=0; i aabb; - aabb.init(V, F); - for (unsigned int i=0; i dump; - Eigen::MatrixXf query_point = m_model_object.sla_support_points[i]; - aabb.squared_distance(V, F, query_point, facet_idx, dump); - Vec3f a1 = V.row(F(facet_idx,1)) - V.row(F(facet_idx,0)); - Vec3f a2 = V.row(F(facet_idx,2)) - V.row(F(facet_idx,0)); - Vec3f normal = a1.cross(a2); - normal.normalize(); - facets_normals.push_back(normal); - }*/ - - // New potential support point is randomly generated on the mesh and distance to all already placed points is calculated. - // In case it is never smaller than certain limit (depends on the new point's facet normal), the point is accepted. - // The process stops after certain number of points is refused in a row. - Vec3d point; - Vec3d normal; - int added_points = 0; - int refused_points = 0; - const int refused_limit = 30; - // Angle at which the density reaches zero: - const float threshold_angle = std::min(M_PI_2, M_PI_4 * acos(0.f/m_config.density_at_horizontal) / acos(m_config.density_at_45/m_config.density_at_horizontal)); - - size_t cancel_test_cntr = 0; - while (refused_points < refused_limit) { - if (++ cancel_test_cntr == 500) { - // Don't call the cancellation routine too often as the multi-core cache synchronization - // may be pretty expensive. - m_throw_on_cancel(); - cancel_test_cntr = 0; - } - // Place a random point on the mesh and calculate corresponding facet's normal: - Eigen::VectorXi FI; - Eigen::MatrixXd B; - igl::random_points_on_mesh(1, m_V, m_F, B, FI); - point = B(0,0)*m_V.row(m_F(FI(0),0)) + - B(0,1)*m_V.row(m_F(FI(0),1)) + - B(0,2)*m_V.row(m_F(FI(0),2)); - if (point(2) - bb.min(2) < m_config.minimal_z) - continue; - - Vec3d a1 = m_V.row(m_F(FI(0),1)) - m_V.row(m_F(FI(0),0)); - Vec3d a2 = m_V.row(m_F(FI(0),2)) - m_V.row(m_F(FI(0),0)); - normal = a1.cross(a2); - normal.normalize(); - - // calculate angle between the normal and vertical: - float angle = angle_from_normal(normal.cast()); - if (angle > threshold_angle) - continue; - - const float limit = distance_limit(angle); - bool add_it = true; - for (unsigned int i=0; i()); - facets_normals.push_back(normal); - ++added_points; - refused_points = 0; - } - } - - m_output.insert(m_output.end(), points.begin(), points.end()); - - // Now transform all support points to mesh coordinates: - //for (Vec3f& point : m_model_object.sla_support_points) - // point = transformation_matrix.inverse().cast() * point; -} - - - float SLAAutoSupports::get_required_density(float angle) const { // calculation would be density_0 * cos(angle). To provide one more degree of freedom, we will scale the angle @@ -179,10 +46,470 @@ float SLAAutoSupports::get_required_density(float angle) const float SLAAutoSupports::distance_limit(float angle) const { return 1./(2.4*get_required_density(angle)); +}*/ + +SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, const std::vector& heights, + const Config& config, std::function throw_on_cancel) +: m_config(config), m_emesh(emesh), m_throw_on_cancel(throw_on_cancel) +{ + process(slices, heights); + project_onto_mesh(m_output); +} + +void SLAAutoSupports::project_onto_mesh(std::vector& points) const +{ + // The function makes sure that all the points are really exactly placed on the mesh. + igl::Hit hit_up{0, 0, 0.f, 0.f, 0.f}; + igl::Hit hit_down{0, 0, 0.f, 0.f, 0.f}; + + // Use a reasonable granularity to account for the worker thread synchronization cost. + tbb::parallel_for(tbb::blocked_range(0, points.size(), 64), + [this, &points](const tbb::blocked_range& range) { + for (size_t point_id = range.begin(); point_id < range.end(); ++ point_id) { + if ((point_id % 16) == 0) + // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. + m_throw_on_cancel(); + Vec3f& p = points[point_id].pos; + // Project the point upward and downward and choose the closer intersection with the mesh. + //bool up = igl::ray_mesh_intersect(p.cast(), Vec3f(0., 0., 1.), m_V, m_F, hit_up); + //bool down = igl::ray_mesh_intersect(p.cast(), Vec3f(0., 0., -1.), m_V, m_F, hit_down); + + sla::EigenMesh3D::hit_result hit_up = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., 1.)); + sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., -1.)); + + bool up = hit_up.face() != -1; + bool down = hit_down.face() != -1; + + if (!up && !down) + continue; + + sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down; + //int fid = hit.face(); + //Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); + //p = (bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2))).cast(); + + p = p + (hit.distance() * hit.direction()).cast(); + } + }); +} + +static std::vector make_layers( + const std::vector& slices, const std::vector& heights, + std::function throw_on_cancel) +{ + assert(slices.size() == heights.size()); + + // Allocate empty layers. + std::vector layers; + layers.reserve(slices.size()); + for (size_t i = 0; i < slices.size(); ++ i) + layers.emplace_back(i, heights[i]); + + // FIXME: calculate actual pixel area from printer config: + //const float pixel_area = pow(wxGetApp().preset_bundle->project_config.option("display_width") / wxGetApp().preset_bundle->project_config.option("display_pixels_x"), 2.f); // + const float pixel_area = pow(0.047f, 2.f); + + // Use a reasonable granularity to account for the worker thread synchronization cost. + tbb::parallel_for(tbb::blocked_range(0, layers.size(), 32), + [&layers, &slices, &heights, pixel_area, throw_on_cancel](const tbb::blocked_range& range) { + for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { + if ((layer_id % 8) == 0) + // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. + throw_on_cancel(); + SLAAutoSupports::MyLayer &layer = layers[layer_id]; + const ExPolygons &islands = slices[layer_id]; + //FIXME WTF? + const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0])); + layer.islands.reserve(islands.size()); + for (const ExPolygon &island : islands) { + float area = float(island.area() * SCALING_FACTOR * SCALING_FACTOR); + if (area >= pixel_area) + //FIXME this is not a correct centroid of a polygon with holes. + layer.islands.emplace_back(layer, island, get_extents(island.contour), Slic3r::unscale(island.contour.centroid()).cast(), area, height); + } + } + }); + + // Calculate overlap of successive layers. Link overlapping islands. + tbb::parallel_for(tbb::blocked_range(1, layers.size(), 8), + [&layers, &heights, throw_on_cancel](const tbb::blocked_range& range) { + for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) { + if ((layer_id % 2) == 0) + // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. + throw_on_cancel(); + SLAAutoSupports::MyLayer &layer_above = layers[layer_id]; + SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1]; + //FIXME WTF? + const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0])); + const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]); + const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports + const float between_layers_offset = float(scale_(layer_height / std::tan(safe_angle))); + //FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands. + for (SLAAutoSupports::Structure &top : layer_above.islands) { + for (SLAAutoSupports::Structure &bottom : layer_below.islands) { + float overlap_area = top.overlap_area(bottom); + if (overlap_area > 0) { + top.islands_below.emplace_back(&bottom, overlap_area); + bottom.islands_above.emplace_back(&top, overlap_area); + } + } + if (! top.islands_below.empty()) { + Polygons top_polygons = to_polygons(*top.polygon); + Polygons bottom_polygons = top.polygons_below(); + top.overhangs = diff_ex(top_polygons, bottom_polygons); + if (! top.overhangs.empty()) { + top.overhangs_area = 0.f; + std::vector> expolys_with_areas; + for (ExPolygon &ex : top.overhangs) { + float area = float(ex.area()); + expolys_with_areas.emplace_back(&ex, area); + top.overhangs_area += area; + } + std::sort(expolys_with_areas.begin(), expolys_with_areas.end(), + [](const std::pair &p1, const std::pair &p2) + { return p1.second > p2.second; }); + ExPolygons overhangs_sorted; + for (auto &p : expolys_with_areas) + overhangs_sorted.emplace_back(std::move(*p.first)); + top.overhangs = std::move(overhangs_sorted); + top.overhangs_area *= float(SCALING_FACTOR * SCALING_FACTOR); + top.dangling_areas = diff_ex(top_polygons, offset(bottom_polygons, between_layers_offset)); + } + } + } + } + }); + + return layers; +} + +void SLAAutoSupports::process(const std::vector& slices, const std::vector& heights) +{ +#ifdef SLA_AUTOSUPPORTS_DEBUG + std::vector> islands; +#endif /* SLA_AUTOSUPPORTS_DEBUG */ + + std::vector layers = make_layers(slices, heights, m_throw_on_cancel); + + PointGrid3D point_grid; + point_grid.cell_size = Vec3f(10.f, 10.f, 10.f); + + for (unsigned int layer_id = 0; layer_id < layers.size(); ++ layer_id) { + SLAAutoSupports::MyLayer *layer_top = &layers[layer_id]; + SLAAutoSupports::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr; + std::vector support_force_bottom; + if (layer_bottom != nullptr) { + support_force_bottom.assign(layer_bottom->islands.size(), 0.f); + for (size_t i = 0; i < layer_bottom->islands.size(); ++ i) + support_force_bottom[i] = layer_bottom->islands[i].supports_force_total(); + } + for (Structure &top : layer_top->islands) + for (Structure::Link &bottom_link : top.islands_below) { + Structure &bottom = *bottom_link.island; + float centroids_dist = (bottom.centroid - top.centroid).norm(); + // Penalization resulting from centroid offset: +// bottom.supports_force *= std::min(1.f, 1.f - std::min(1.f, (1600.f * layer_height) * centroids_dist * centroids_dist / bottom.area)); + float &support_force = support_force_bottom[&bottom - layer_bottom->islands.data()]; +//FIXME this condition does not reflect a bifurcation into a one large island and one tiny island well, it incorrectly resets the support force to zero. +// One should rather work with the overlap area vs overhang area. +// support_force *= std::min(1.f, 1.f - std::min(1.f, 0.1f * centroids_dist * centroids_dist / bottom.area)); + // Penalization resulting from increasing polygon area: + support_force *= std::min(1.f, 20.f * bottom.area / top.area); + } + // Let's assign proper support force to each of them: + if (layer_id > 0) { + for (Structure &below : layer_bottom->islands) { + float below_support_force = support_force_bottom[&below - layer_bottom->islands.data()]; + float above_overlap_area = 0.f; + for (Structure::Link &above_link : below.islands_above) + above_overlap_area += above_link.overlap_area; + for (Structure::Link &above_link : below.islands_above) + above_link.island->supports_force_inherited += below_support_force * above_link.overlap_area / above_overlap_area; + } + } + // Now iterate over all polygons and append new points if needed. + for (Structure &s : layer_top->islands) { + // Penalization resulting from large diff from the last layer: +// s.supports_force_inherited /= std::max(1.f, (layer_height / 0.3f) * e_area / s.area); + s.supports_force_inherited /= std::max(1.f, 0.17f * (s.overhangs_area) / s.area); + + float force_deficit = s.support_force_deficit(m_config.tear_pressure()); + if (s.islands_below.empty()) { // completely new island - needs support no doubt + uniformly_cover({ *s.polygon }, s, point_grid, true); + } else if (! s.dangling_areas.empty()) { + // Let's see if there's anything that overlaps enough to need supports: + // What we now have in polygons needs support, regardless of what the forces are, so we can add them. + //FIXME is it an island point or not? Vojtech thinks it is. + uniformly_cover(s.dangling_areas, s, point_grid); + } else if (! s.overhangs.empty()) { + //FIXME add the support force deficit as a parameter, only cover until the defficiency is covered. + uniformly_cover(s.overhangs, s, point_grid); + } + } + + m_throw_on_cancel(); + +#ifdef SLA_AUTOSUPPORTS_DEBUG + /*std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i); + output_expolygons(expolys_top, "top" + layer_num_str + ".svg"); + output_expolygons(diff, "diff" + layer_num_str + ".svg"); + if (!islands.empty()) + output_expolygons(islands, "islands" + layer_num_str + ".svg");*/ +#endif /* SLA_AUTOSUPPORTS_DEBUG */ + } +} + +std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng) +{ + // Triangulate the polygon with holes into triplets of 3D points. + std::vector triangles = Slic3r::triangulate_expolygon_2f(expoly); + + std::vector out; + if (! triangles.empty()) + { + // Calculate area of each triangle. + std::vector areas; + areas.reserve(triangles.size() / 3); + for (size_t i = 0; i < triangles.size(); ) { + const Vec2f &a = triangles[i ++]; + const Vec2f v1 = triangles[i ++] - a; + const Vec2f v2 = triangles[i ++] - a; + areas.emplace_back(0.5f * std::abs(cross2(v1, v2))); + if (i != 3) + // Prefix sum of the areas. + areas.back() += areas[areas.size() - 2]; + } + + size_t num_samples = size_t(ceil(areas.back() * samples_per_mm2)); + std::uniform_real_distribution<> random_triangle(0., double(areas.back())); + std::uniform_real_distribution<> random_float(0., 1.); + for (size_t i = 0; i < num_samples; ++ i) { + double r = random_triangle(rng); + size_t idx_triangle = std::min(std::upper_bound(areas.begin(), areas.end(), (float)r) - areas.begin(), areas.size() - 1) * 3; + // Select a random point on the triangle. + double u = float(sqrt(random_float(rng))); + double v = float(random_float(rng)); + const Vec2f &a = triangles[idx_triangle ++]; + const Vec2f &b = triangles[idx_triangle++]; + const Vec2f &c = triangles[idx_triangle]; + const Vec2f x = a * (1.f - u) + b * (u * (1.f - v)) + c * (v * u); + out.emplace_back(x); + } + } + return out; +} + +std::vector sample_expolygon_with_boundary(const ExPolygon &expoly, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng) +{ + std::vector out = sample_expolygon(expoly, samples_per_mm2, rng); + double point_stepping_scaled = scale_(1.f) / samples_per_mm_boundary; + for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) { + const Polygon &contour = (i_contour == 0) ? expoly.contour : expoly.holes[i_contour - 1]; + const Points pts = contour.equally_spaced_points(point_stepping_scaled); + for (size_t i = 0; i < pts.size(); ++ i) + out.emplace_back(unscale(pts[i].x()), unscale(pts[i].y())); + } + return out; +} + +std::vector sample_expolygon_with_boundary(const ExPolygons &expolys, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng) +{ + std::vector out; + for (const ExPolygon &expoly : expolys) + append(out, sample_expolygon_with_boundary(expoly, samples_per_mm2, samples_per_mm_boundary, rng)); + return out; +} + +template +static inline std::vector poisson_disk_from_samples(const std::vector &raw_samples, float radius, REFUSE_FUNCTION refuse_function) +{ + Vec2f corner_min(std::numeric_limits::max(), std::numeric_limits::max()); + for (const Vec2f &pt : raw_samples) { + corner_min.x() = std::min(corner_min.x(), pt.x()); + corner_min.y() = std::min(corner_min.y(), pt.y()); + } + + // Assign the raw samples to grid cells, sort the grid cells lexicographically. + struct RawSample { + Vec2f coord; + Vec2i cell_id; + }; + std::vector raw_samples_sorted; + RawSample sample; + for (const Vec2f &pt : raw_samples) { + sample.coord = pt; + sample.cell_id = ((pt - corner_min) / radius).cast(); + raw_samples_sorted.emplace_back(sample); + } + std::sort(raw_samples_sorted.begin(), raw_samples_sorted.end(), [](const RawSample &lhs, const RawSample &rhs) + { return lhs.cell_id.x() < rhs.cell_id.x() || (lhs.cell_id.x() == rhs.cell_id.x() && lhs.cell_id.y() < rhs.cell_id.y()); }); + + struct PoissonDiskGridEntry { + // Resulting output sample points for this cell: + enum { + max_positions = 4 + }; + Vec2f poisson_samples[max_positions]; + int num_poisson_samples = 0; + + // Index into raw_samples: + int first_sample_idx; + int sample_cnt; + }; + + struct CellIDHash { + std::size_t operator()(const Vec2i &cell_id) const { + return std::hash()(cell_id.x()) ^ std::hash()(cell_id.y() * 593); + } + }; + + // Map from cell IDs to hash_data. Each hash_data points to the range in raw_samples corresponding to that cell. + // (We could just store the samples in hash_data. This implementation is an artifact of the reference paper, which + // is optimizing for GPU acceleration that we haven't implemented currently.) + typedef std::unordered_map Cells; + Cells cells; + { + typename Cells::iterator last_cell_id_it; + Vec2i last_cell_id(-1, -1); + for (int i = 0; i < raw_samples_sorted.size(); ++ i) { + const RawSample &sample = raw_samples_sorted[i]; + if (sample.cell_id == last_cell_id) { + // This sample is in the same cell as the previous, so just increase the count. Cells are + // always contiguous, since we've sorted raw_samples_sorted by cell ID. + ++ last_cell_id_it->second.sample_cnt; + } else { + // This is a new cell. + PoissonDiskGridEntry data; + data.first_sample_idx = i; + data.sample_cnt = 1; + auto result = cells.insert({sample.cell_id, data}); + last_cell_id = sample.cell_id; + last_cell_id_it = result.first; + } + } + } + + const int max_trials = 5; + const float radius_squared = radius * radius; + for (int trial = 0; trial < max_trials; ++ trial) { + // Create sample points for each entry in cells. + for (auto &it : cells) { + const Vec2i &cell_id = it.first; + PoissonDiskGridEntry &cell_data = it.second; + // This cell's raw sample points start at first_sample_idx. On trial 0, try the first one. On trial 1, try first_sample_idx + 1. + int next_sample_idx = cell_data.first_sample_idx + trial; + if (trial >= cell_data.sample_cnt) + // There are no more points to try for this cell. + continue; + const RawSample &candidate = raw_samples_sorted[next_sample_idx]; + // See if this point conflicts with any other points in this cell, or with any points in + // neighboring cells. Note that it's possible to have more than one point in the same cell. + bool conflict = refuse_function(candidate.coord); + for (int i = -1; i < 2 && ! conflict; ++ i) { + for (int j = -1; j < 2; ++ j) { + const auto &it_neighbor = cells.find(cell_id + Vec2i(i, j)); + if (it_neighbor != cells.end()) { + const PoissonDiskGridEntry &neighbor = it_neighbor->second; + for (int i_sample = 0; i_sample < neighbor.num_poisson_samples; ++ i_sample) + if ((neighbor.poisson_samples[i_sample] - candidate.coord).squaredNorm() < radius_squared) { + conflict = true; + break; + } + } + } + } + if (! conflict) { + // Store the new sample. + assert(cell_data.num_poisson_samples < cell_data.max_positions); + if (cell_data.num_poisson_samples < cell_data.max_positions) + cell_data.poisson_samples[cell_data.num_poisson_samples ++] = candidate.coord; + } + } + } + + // Copy the results to the output. + std::vector out; + for (const auto& it : cells) + for (int i = 0; i < it.second.num_poisson_samples; ++ i) + out.emplace_back(it.second.poisson_samples[i]); + return out; +} + +void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one) +{ + //int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force)); + + const float support_force_deficit = structure.support_force_deficit(m_config.tear_pressure()); + if (support_force_deficit < 0) + return; + + // Number of newly added points. + const size_t poisson_samples_target = size_t(ceil(support_force_deficit / m_config.support_force())); + + const float density_horizontal = m_config.tear_pressure() / m_config.support_force(); + //FIXME why? + float poisson_radius = std::max(m_config.minimal_distance, 1.f / (5.f * density_horizontal)); +// const float poisson_radius = 1.f / (15.f * density_horizontal); + const float samples_per_mm2 = 30.f / (float(M_PI) * poisson_radius * poisson_radius); + // Minimum distance between samples, in 3D space. +// float min_spacing = poisson_radius / 3.f; + float min_spacing = poisson_radius; + + //FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon. + std::random_device rd; + std::mt19937 rng(rd()); + std::vector raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng); + std::vector poisson_samples; + for (size_t iter = 0; iter < 4; ++ iter) { + poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius, + [&structure, &grid3d, min_spacing](const Vec2f &pos) { + return grid3d.collides_with(pos, &structure, min_spacing); + }); + if (poisson_samples.size() >= poisson_samples_target || m_config.minimal_distance > poisson_radius-EPSILON) + break; + float coeff = 0.5f; + if (poisson_samples.size() * 2 > poisson_samples_target) + coeff = float(poisson_samples.size()) / float(poisson_samples_target); + poisson_radius = std::max(m_config.minimal_distance, poisson_radius * coeff); + min_spacing = std::max(m_config.minimal_distance, min_spacing * coeff); + } + +#ifdef SLA_AUTOSUPPORTS_DEBUG + { + static int irun = 0; + Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(islands)); + for (const ExPolygon &island : islands) + svg.draw(island); + for (const Vec2f &pt : raw_samples) + svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "red"); + for (const Vec2f &pt : poisson_samples) + svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "blue"); + } +#endif /* NDEBUG */ + +// assert(! poisson_samples.empty()); + if (poisson_samples_target < poisson_samples.size()) { + std::shuffle(poisson_samples.begin(), poisson_samples.end(), rng); + poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end()); + } + for (const Vec2f &pt : poisson_samples) { + m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, 0.2f, is_new_island); + structure.supports_force_this_layer += m_config.support_force(); + grid3d.insert(pt, &structure); + } } #ifdef SLA_AUTOSUPPORTS_DEBUG -void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, std::string filename) const +void SLAAutoSupports::output_structures(const std::vector& structures) +{ + for (unsigned int i=0 ; i{*structures[i].polygon}, ss.str()); + } +} + +void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, const std::string &filename) { BoundingBox bb(Point(-30000000, -30000000), Point(30000000, 30000000)); Slic3r::SVG svg_cummulative(filename, bb); @@ -198,138 +525,6 @@ void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, std::string f svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05)); } } -#endif /* SLA_AUTOSUPPORTS_DEBUG */ - -std::vector> SLAAutoSupports::find_islands(const std::vector& slices, const std::vector& heights) const -{ - std::vector> islands; - - struct PointAccessor { - const Point* operator()(const Point &pt) const { return &pt; } - }; - typedef ClosestPointInRadiusLookup ClosestPointLookupType; - - for (unsigned int i = 0; i SLAAutoSupports::uniformly_cover(const std::pair& island) -{ - int num_of_points = std::max(1, (int)(island.first.area()*pow(SCALING_FACTOR, 2) * get_required_density(0))); - - // In case there is just one point to place, we'll place it into the polygon's centroid (unless it lies in a hole). - if (num_of_points == 1) { - Point out(island.first.contour.centroid()); - - for (const auto& hole : island.first.holes) - if (hole.contains(out)) - goto HOLE_HIT; - return std::vector{unscale(out(0), out(1), island.second)}; - } - -HOLE_HIT: - // In this case either the centroid lies in a hole, or there are multiple points - // to place. We will cover the island another way. - // For now we'll just place the points randomly not too close to the others. - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_real_distribution<> dis(0., 1.); - - std::vector island_new_points; - const BoundingBox& bb = get_extents(island.first); - const int refused_limit = 30; - int refused_points = 0; - while (refused_points < refused_limit) { - Point out(bb.min(0) + bb.size()(0) * dis(gen), - bb.min(1) + bb.size()(1) * dis(gen)) ; - Vec3d unscaled_out = unscale(out(0), out(1), island.second); - bool add_it = true; - - if (!island.first.contour.contains(out)) - add_it = false; - else - for (const Polygon& hole : island.first.holes) - if (hole.contains(out)) - add_it = false; - - if (add_it) { - for (const Vec3d& p : island_new_points) { - if ((p - unscaled_out).squaredNorm() < distance_limit(0)) { - add_it = false; - ++refused_points; - break; - } - } - } - if (add_it) - island_new_points.emplace_back(unscaled_out); - } - return island_new_points; -} - -void SLAAutoSupports::project_upward_onto_mesh(std::vector& points) const -{ - Vec3f dir(0., 0., 1.); - igl::Hit hit{0, 0, 0.f, 0.f, 0.f}; - for (Vec3d& p : points) { - igl::ray_mesh_intersect(p.cast(), dir, m_V, m_F, hit); - int fid = hit.id; - Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); - p = (bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2))).cast(); - } -} - +#endif } // namespace Slic3r diff --git a/src/libslic3r/SLA/SLAAutoSupports.hpp b/src/libslic3r/SLA/SLAAutoSupports.hpp index 311d7b0c73..0d5cc64f17 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.hpp +++ b/src/libslic3r/SLA/SLAAutoSupports.hpp @@ -1,9 +1,12 @@ #ifndef SLAAUTOSUPPORTS_HPP_ #define SLAAUTOSUPPORTS_HPP_ +#include #include #include -#include +#include + +#include // #define SLA_AUTOSUPPORTS_DEBUG @@ -12,36 +15,184 @@ namespace Slic3r { class SLAAutoSupports { public: struct Config { - float density_at_horizontal; - float density_at_45; - float minimal_z; + float density_relative; + float minimal_distance; + /////////////// + inline float support_force() const { return 10.f / density_relative; } // a force one point can support (arbitrary force unit) + inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2) }; - SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, - const std::vector& heights, const Config& config, std::function throw_on_cancel); - const std::vector& output() { return m_output; } + SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, + const std::vector& heights, const Config& config, std::function throw_on_cancel); + const std::vector& output() { return m_output; } -private: - std::vector m_output; - std::vector m_normals; - TriangleMesh mesh; - static float angle_from_normal(const stl_normal& normal) { return acos((-normal.normalized())(2)); } - float get_required_density(float angle) const; - float distance_limit(float angle) const; - static float approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2); - std::vector> find_islands(const std::vector& slices, const std::vector& heights) const; - void sprinkle_mesh(const TriangleMesh& mesh); - std::vector uniformly_cover(const std::pair& island); - void project_upward_onto_mesh(std::vector& points) const; + struct MyLayer; + struct Structure { + Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f ¢roid, float area, float h) : + layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(h) #ifdef SLA_AUTOSUPPORTS_DEBUG - void output_expolygons(const ExPolygons& expolys, std::string filename) const; + , unique_id(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch())) +#endif /* SLA_AUTOSUPPORTS_DEBUG */ + {} + MyLayer *layer; + const ExPolygon* polygon = nullptr; + const BoundingBox bbox; + const Vec2f centroid = Vec2f::Zero(); + const float area = 0.f; + float height = 0; + // How well is this ExPolygon held to the print base? + // Positive number, the higher the better. + float supports_force_this_layer = 0.f; + float supports_force_inherited = 0.f; + float supports_force_total() const { return this->supports_force_this_layer + this->supports_force_inherited; } +#ifdef SLA_AUTOSUPPORTS_DEBUG + std::chrono::milliseconds unique_id; #endif /* SLA_AUTOSUPPORTS_DEBUG */ + struct Link { + Link(Structure *island, float overlap_area) : island(island), overlap_area(overlap_area) {} + Structure *island; + float overlap_area; + }; + +#ifdef NDEBUG + // In release mode, use the optimized container. + boost::container::small_vector islands_above; + boost::container::small_vector islands_below; +#else + // In debug mode, use the standard vector, which is well handled by debugger visualizer. + std::vector islands_above; + std::vector islands_below; +#endif + ExPolygons dangling_areas; + ExPolygons overhangs; + float overhangs_area; + + bool overlaps(const Structure &rhs) const { + return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon)); + } + float overlap_area(const Structure &rhs) const { + double out = 0.; + if (this->bbox.overlap(rhs.bbox)) { + Polygons polys = intersection(to_polygons(*this->polygon), to_polygons(*rhs.polygon), false); + for (const Polygon &poly : polys) + out += poly.area(); + } + return float(out); + } + float area_below() const { + float area = 0.f; + for (const Link &below : this->islands_below) + area += below.island->area; + return area; + } + Polygons polygons_below() const { + size_t cnt = 0; + for (const Link &below : this->islands_below) + cnt += 1 + below.island->polygon->holes.size(); + Polygons out; + out.reserve(cnt); + for (const Link &below : this->islands_below) { + out.emplace_back(below.island->polygon->contour); + append(out, below.island->polygon->holes); + } + return out; + } + ExPolygons expolygons_below() const { + ExPolygons out; + out.reserve(this->islands_below.size()); + for (const Link &below : this->islands_below) + out.emplace_back(*below.island->polygon); + return out; + } + // Positive deficit of the supports. If negative, this area is well supported. If positive, more supports need to be added. + float support_force_deficit(const float tear_pressure) const { return this->area * tear_pressure - this->supports_force_total(); } + }; + + struct MyLayer { + MyLayer(const size_t layer_id, coordf_t print_z) : layer_id(layer_id), print_z(print_z) {} + size_t layer_id; + coordf_t print_z; + std::vector islands; + }; + + struct RichSupportPoint { + Vec3f position; + Structure *island; + }; + + struct PointGrid3D { + struct GridHash { + std::size_t operator()(const Vec3i &cell_id) const { + return std::hash()(cell_id.x()) ^ std::hash()(cell_id.y() * 593) ^ std::hash()(cell_id.z() * 7919); + } + }; + typedef std::unordered_multimap Grid; + + Vec3f cell_size; + Grid grid; + + Vec3i cell_id(const Vec3f &pos) { + return Vec3i(int(floor(pos.x() / cell_size.x())), + int(floor(pos.y() / cell_size.y())), + int(floor(pos.z() / cell_size.z()))); + } + + void insert(const Vec2f &pos, Structure *island) { + RichSupportPoint pt; + pt.position = Vec3f(pos.x(), pos.y(), float(island->layer->print_z)); + pt.island = island; + grid.emplace(cell_id(pt.position), pt); + } + + bool collides_with(const Vec2f &pos, Structure *island, float radius) { + Vec3f pos3d(pos.x(), pos.y(), float(island->layer->print_z)); + Vec3i cell = cell_id(pos3d); + std::pair it_pair = grid.equal_range(cell); + if (collides_with(pos3d, radius, it_pair.first, it_pair.second)) + return true; + for (int i = -1; i < 2; ++ i) + for (int j = -1; j < 2; ++ j) + for (int k = -1; k < 1; ++ k) { + if (i == 0 && j == 0 && k == 0) + continue; + it_pair = grid.equal_range(cell + Vec3i(i, j, k)); + if (collides_with(pos3d, radius, it_pair.first, it_pair.second)) + return true; + } + return false; + } + + private: + bool collides_with(const Vec3f &pos, float radius, Grid::const_iterator it_begin, Grid::const_iterator it_end) { + for (Grid::const_iterator it = it_begin; it != it_end; ++ it) { + float dist2 = (it->second.position - pos).squaredNorm(); + if (dist2 < radius * radius) + return true; + } + return false; + } + }; + +private: + std::vector m_output; + SLAAutoSupports::Config m_config; + + float m_supports_force_total = 0.f; + + void process(const std::vector& slices, const std::vector& heights); + void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island = false, bool just_one = false); + void project_onto_mesh(std::vector& points) const; + +#ifdef SLA_AUTOSUPPORTS_DEBUG + static void output_expolygons(const ExPolygons& expolys, const std::string &filename); + static void output_structures(const std::vector &structures); +#endif // SLA_AUTOSUPPORTS_DEBUG + std::function m_throw_on_cancel; - const Eigen::MatrixXd& m_V; - const Eigen::MatrixXi& m_F; + const sla::EigenMesh3D& m_emesh; }; diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index a235d52baf..42b22acb99 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -4,78 +4,177 @@ #include "boost/log/trivial.hpp" #include "SLABoostAdapter.hpp" #include "ClipperUtils.hpp" +#include "Tesselate.hpp" +// For debugging: +//#include +//#include //#include "SVG.hpp" -//#include "benchmark.h" namespace Slic3r { namespace sla { -/// Convert the triangulation output to an intermediate mesh. -Contour3D convert(const Polygons& triangles, coord_t z, bool dir) { - - Pointf3s points; - points.reserve(3*triangles.size()); - Indices indices; - indices.reserve(points.size()); - - for(auto& tr : triangles) { - auto c = coord_t(points.size()), b = c++, a = c++; - if(dir) indices.emplace_back(a, b, c); - else indices.emplace_back(c, b, a); - for(auto& p : tr.points) { - points.emplace_back(unscale(x(p), y(p), z)); - } - } - - return {points, indices}; -} - -Contour3D walls(const ExPolygon& floor_plate, const ExPolygon& ceiling, - double floor_z_mm, double ceiling_z_mm, - ThrowOnCancel thr) +/// This function will return a triangulation of a sheet connecting an upper +/// and a lower plate given as input polygons. It will not triangulate the +/// plates themselves only the sheet. The caller has to specify the lower and +/// upper z levels in world coordinates as well as the offset difference +/// between the sheets. If the lower_z_mm is higher than upper_z_mm or the +/// offset difference is negative, the resulting triangle orientation will be +/// reversed. +/// +/// IMPORTANT: This is not a universal triangulation algorithm. It assumes +/// that the lower and upper polygons are offsetted versions of the same +/// original polygon. In general, it assumes that one of the polygons is +/// completely inside the other. The offset difference is the reference +/// distance from the inner polygon's perimeter to the outer polygon's +/// perimeter. The real distance will be variable as the clipper offset has +/// different strategies (rounding, etc...). This algorithm should have +/// O(2n + 3m) complexity where n is the number of upper vertices and m is the +/// number of lower vertices. +Contour3D walls(const Polygon& lower, const Polygon& upper, + double lower_z_mm, double upper_z_mm, + double offset_difference_mm, ThrowOnCancel thr) { - using std::transform; using std::back_inserter; - - ExPolygon poly; - poly.contour.points = floor_plate.contour.points; - poly.holes.emplace_back(ceiling.contour); - auto& h = poly.holes.front(); - std::reverse(h.points.begin(), h.points.end()); - Polygons tri = triangulate(poly); - Contour3D ret; - ret.points.reserve(tri.size() * 3); - double fz = floor_z_mm; - double cz = ceiling_z_mm; - auto& rp = ret.points; - auto& rpi = ret.indices; - ret.indices.reserve(tri.size() * 3); + if(upper.points.size() < 3 || lower.size() < 3) return ret; - coord_t idx = 0; + // The concept of the algorithm is relatively simple. It will try to find + // the closest vertices from the upper and the lower polygon and use those + // as starting points. Then it will create the triangles sequentially using + // an edge from the upper polygon and a vertex from the lower or vice versa, + // depending on the resulting triangle's quality. + // The quality is measured by a scalar value. So far it looks like it is + // enough to derive it from the slope of the triangle's two edges connecting + // the upper and the lower part. A reference slope is calculated from the + // height and the offset difference. - auto hlines = h.lines(); - auto is_upper = [&hlines](const Point& p) { - return std::any_of(hlines.begin(), hlines.end(), - [&p](const Line& l) { - return l.distance_to(p) < mm(1e-6); - }); + // Offset in the index array for the ceiling + const auto offs = upper.points.size(); + + // Shorthand for the vertex arrays + auto& upoints = upper.points, &lpoints = lower.points; + auto& rpts = ret.points; auto& rfaces = ret.indices; + + // If the Z levels are flipped, or the offset difference is negative, we + // will interpret that as the triangles normals should be inverted. + bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0; + + // Copy the points into the mesh, convert them from 2D to 3D + rpts.reserve(upoints.size() + lpoints.size()); + rfaces.reserve(2*upoints.size() + 2*lpoints.size()); + const double sf = SCALING_FACTOR; + for(auto& p : upoints) rpts.emplace_back(p.x()*sf, p.y()*sf, upper_z_mm); + for(auto& p : lpoints) rpts.emplace_back(p.x()*sf, p.y()*sf, lower_z_mm); + + // Create pointing indices into vertex arrays. u-upper, l-lower + size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1; + + // Simple squared distance calculation. + auto distfn = [](const Vec3d& p1, const Vec3d& p2) { + auto p = p1 - p2; return p.transpose() * p; }; - std::for_each(tri.begin(), tri.end(), - [&rp, &rpi, thr, &idx, is_upper, fz, cz](const Polygon& pp) - { - thr(); // may throw if cancellation was requested + // We need to find the closest point on lower polygon to the first point on + // the upper polygon. These will be our starting points. + double distmin = std::numeric_limits::max(); + for(size_t l = lidx; l < rpts.size(); ++l) { + thr(); + double d = distfn(rpts[l], rpts[uidx]); + if(d < distmin) { lidx = l; distmin = d; } + } - for(auto& p : pp.points) - if(is_upper(p)) - rp.emplace_back(unscale(x(p), y(p), mm(cz))); - else rp.emplace_back(unscale(x(p), y(p), mm(fz))); + // Set up lnextidx to be ahead of lidx in cyclic mode + lnextidx = lidx + 1; + if(lnextidx == rpts.size()) lnextidx = offs; - coord_t a = idx++, b = idx++, c = idx++; - if(fz > cz) rpi.emplace_back(c, b, a); - else rpi.emplace_back(a, b, c); - }); + // This will be the flip switch to toggle between upper and lower triangle + // creation mode + enum class Proceed { + UPPER, // A segment from the upper polygon and one vertex from the lower + LOWER // A segment from the lower polygon and one vertex from the upper + } proceed = Proceed::UPPER; + + // Flags to help evaluating loop termination. + bool ustarted = false, lstarted = false; + + // The variables for the fitness values, one for the actual and one for the + // previous. + double current_fit = 0, prev_fit = 0; + + // Every triangle of the wall has two edges connecting the upper plate with + // the lower plate. From the length of these two edges and the zdiff we + // can calculate the momentary squared offset distance at a particular + // position on the wall. The average of the differences from the reference + // (squared) offset distance will give us the driving fitness value. + const double offsdiff2 = std::pow(offset_difference_mm, 2); + const double zdiff2 = std::pow(upper_z_mm - lower_z_mm, 2); + + // Mark the current vertex iterator positions. If the iterators return to + // the same position, the loop can be terminated. + size_t uendidx = uidx, lendidx = lidx; + + do { thr(); // check throw if canceled + + prev_fit = current_fit; + + switch(proceed) { // proceed depending on the current state + case Proceed::UPPER: + if(!ustarted || uidx != uendidx) { // there are vertices remaining + // Get the 3D vertices in order + const Vec3d& p_up1 = rpts[size_t(uidx)]; + const Vec3d& p_low = rpts[size_t(lidx)]; + const Vec3d& p_up2 = rpts[size_t(unextidx)]; + + // Calculate fitness: the average of the two connecting edges + double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2); + double b = offsdiff2 - (distfn(p_up2, p_low) - zdiff2); + current_fit = (std::abs(a) + std::abs(b)) / 2; + + if(current_fit > prev_fit) { // fit is worse than previously + proceed = Proceed::LOWER; + } else { // good to go, create the triangle + inverted? rfaces.emplace_back(unextidx, lidx, uidx) : + rfaces.emplace_back(uidx, lidx, unextidx) ; + + // Increment the iterators, rotate if necessary + ++uidx; ++unextidx; + if(unextidx == offs) unextidx = 0; + if(uidx == offs) uidx = 0; + + ustarted = true; // mark the movement of the iterators + // so that the comparison to uendidx can be made correctly + } + } else proceed = Proceed::LOWER; + + break; + case Proceed::LOWER: + // Mode with lower segment, upper vertex. Same structure: + if(!lstarted || lidx != lendidx) { + const Vec3d& p_low1 = rpts[size_t(lidx)]; + const Vec3d& p_low2 = rpts[size_t(lnextidx)]; + const Vec3d& p_up = rpts[size_t(uidx)]; + + double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2); + double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2); + current_fit = (std::abs(a) + std::abs(b)) / 2; + + if(current_fit > prev_fit) { + proceed = Proceed::UPPER; + } else { + inverted? rfaces.emplace_back(uidx, lnextidx, lidx) : + rfaces.emplace_back(lidx, lnextidx, uidx); + + ++lidx; ++lnextidx; + if(lnextidx == rpts.size()) lnextidx = offs; + if(lidx == rpts.size()) lidx = offs; + + lstarted = true; + } + } else proceed = Proceed::UPPER; + + break; + } // end of switch + } while(!ustarted || !lstarted || uidx != uendidx || lidx != lendidx); return ret; } @@ -207,20 +306,31 @@ ExPolygons unify(const ExPolygons& shapes) { /// Only a debug function to generate top and bottom plates from a 2D shape. /// It is not used in the algorithm directly. inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) { - Polygons triangles = triangulate(poly); - - auto lower = convert(triangles, 0, false); - auto upper = convert(triangles, z_distance, true); - lower.merge(upper); - return lower; + auto lower = triangulate_expolygon_3d(poly); + auto upper = triangulate_expolygon_3d(poly, z_distance*SCALING_FACTOR, true); + Contour3D ret; + ret.merge(lower); ret.merge(upper); + return ret; } +/// This method will create a rounded edge around a flat polygon in 3d space. +/// 'base_plate' parameter is the target plate. +/// 'radius' is the radius of the edges. +/// 'degrees' is tells how much of a circle should be created as the rounding. +/// It should be in degrees, not radians. +/// 'ceilheight_mm' is the Z coordinate of the flat polygon in 3D space. +/// 'dir' Is the direction of the round edges: inward or outward +/// 'thr' Throws if a cancel signal was received +/// 'last_offset' An auxiliary output variable to save the last offsetted +/// version of 'base_plate' +/// 'last_height' An auxiliary output to save the last z coordinate of the +/// offsetted base_plate. In other words, where the rounded edges end. Contour3D round_edges(const ExPolygon& base_plate, double radius_mm, double degrees, double ceilheight_mm, bool dir, - ThrowOnCancel throw_on_cancel, + ThrowOnCancel thr, ExPolygon& last_offset, double& last_height) { auto ob = base_plate; @@ -236,10 +346,10 @@ Contour3D round_edges(const ExPolygon& base_plate, // we use sin for x distance because we interpret the angle starting from // PI/2 int tos = degrees < 90? - int(radius_mm*std::cos(degrees * PI / 180 - PI/2) / stepx) : steps; + int(radius_mm*std::cos(degrees * PI / 180 - PI/2) / stepx) : steps; for(int i = 1; i <= tos; ++i) { - throw_on_cancel(); + thr(); ob = base_plate; @@ -252,7 +362,8 @@ Contour3D round_edges(const ExPolygon& base_plate, wh = ceilheight_mm - radius_mm + stepy; Contour3D pwalls; - pwalls = walls(ob, ob_prev, wh, wh_prev, throw_on_cancel); + double prev_x = xx - (i - 1) * stepx; + pwalls = walls(ob.contour, ob_prev.contour, wh, wh_prev, s*prev_x, thr); curvedwalls.merge(pwalls); ob_prev = ob; @@ -264,7 +375,7 @@ Contour3D round_edges(const ExPolygon& base_plate, int tos = int(tox / stepx); for(int i = 1; i <= tos; ++i) { - throw_on_cancel(); + thr(); ob = base_plate; double r2 = radius_mm * radius_mm; @@ -275,7 +386,9 @@ Contour3D round_edges(const ExPolygon& base_plate, wh = ceilheight_mm - radius_mm - stepy; Contour3D pwalls; - pwalls = walls(ob_prev, ob, wh_prev, wh, throw_on_cancel); + double prev_x = xx - radius_mm + (i - 1)*stepx; + pwalls = + walls(ob_prev.contour, ob.contour, wh_prev, wh, s*prev_x, thr); curvedwalls.merge(pwalls); ob_prev = ob; @@ -291,15 +404,17 @@ Contour3D round_edges(const ExPolygon& base_plate, /// Generating the concave part of the 3D pool with the bottom plate and the /// side walls. -Contour3D inner_bed(const ExPolygon& poly, double depth_mm, - double begin_h_mm = 0) { - - Polygons triangles = triangulate(poly); +Contour3D inner_bed(const ExPolygon& poly, + double depth_mm, + double begin_h_mm = 0) +{ + Contour3D bottom; + Pointf3s triangles = triangulate_expolygon_3d(poly, -depth_mm + begin_h_mm); + bottom.merge(triangles); coord_t depth = mm(depth_mm); coord_t begin_h = mm(begin_h_mm); - auto bottom = convert(triangles, -depth + begin_h, false); auto lines = poly.lines(); // Generate outer walls @@ -469,6 +584,9 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, const PoolConfig& cfg) { + // for debugging: + // Benchmark bench; + // bench.start(); double mergedist = 2*(1.8*cfg.min_wall_thickness_mm + 4*cfg.edge_radius_mm)+ cfg.max_merge_distance_mm; @@ -478,27 +596,28 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, // serve as the bottom plate of the pad. We will offset this concave hull // and then offset back the result with clipper with rounding edges ON. This // trick will create a nice rounded pad shape. - auto concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); + ExPolygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); const double thickness = cfg.min_wall_thickness_mm; const double wingheight = cfg.min_wall_height_mm; const double fullheight = wingheight + thickness; - const double tilt = PI/4; + const double tilt = cfg.wall_tilt; const double wingdist = wingheight / std::tan(tilt); // scaled values const coord_t s_thickness = mm(thickness); const coord_t s_eradius = mm(cfg.edge_radius_mm); const coord_t s_safety_dist = 2*s_eradius + coord_t(0.8*s_thickness); - // const coord_t wheight = mm(cfg.min_wall_height_mm); - coord_t s_wingdist = mm(wingdist); + const coord_t s_wingdist = mm(wingdist); auto& thrcl = cfg.throw_on_cancel; + Contour3D pool; + for(ExPolygon& concaveh : concavehs) { if(concaveh.contour.points.empty()) return; - // Get rif of any holes in the concave hull output. + // Get rid of any holes in the concave hull output. concaveh.holes.clear(); // Here lies the trick that does the smooting only with clipper offset @@ -508,15 +627,22 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, auto outer_base = concaveh; outer_base.holes.clear(); offset(outer_base, s_safety_dist + s_wingdist + s_thickness); - auto inner_base = outer_base; - offset(inner_base, -(s_thickness + s_wingdist)); + + + ExPolygon bottom_poly = outer_base; + bottom_poly.holes.clear(); + if(s_wingdist > 0) offset(bottom_poly, -s_wingdist); // Punching a hole in the top plate for the cavity ExPolygon top_poly; ExPolygon middle_base; + ExPolygon inner_base; top_poly.contour = outer_base.contour; if(wingheight > 0) { + inner_base = outer_base; + offset(inner_base, -(s_thickness + s_wingdist + s_eradius)); + middle_base = outer_base; offset(middle_base, -s_thickness); top_poly.holes.emplace_back(middle_base.contour); @@ -524,8 +650,6 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, std::reverse(tph.begin(), tph.end()); } - Contour3D pool; - ExPolygon ob = outer_base; double wh = 0; // now we will calculate the angle or portion of the circle from @@ -557,60 +681,53 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, // Generate the smoothed edge geometry - auto walledges = round_edges(ob, - r, - phi, - 0, // z position of the input plane - true, - thrcl, - ob, wh); - pool.merge(walledges); + pool.merge(round_edges(ob, + r, + phi, + 0, // z position of the input plane + true, + thrcl, + ob, wh)); - // Now that we have the rounded edge connencting the top plate with + // Now that we have the rounded edge connecting the top plate with // the outer side walls, we can generate and merge the sidewall geometry - auto pwalls = walls(ob, inner_base, wh, -fullheight, thrcl); - pool.merge(pwalls); + pool.merge(walls(ob.contour, bottom_poly.contour, wh, -fullheight, + wingdist, thrcl)); if(wingheight > 0) { // Generate the smoothed edge geometry - auto cavityedges = round_edges(middle_base, - r, - phi - 90, // from tangent lines - 0, - false, - thrcl, - ob, wh); - pool.merge(cavityedges); + pool.merge(round_edges(middle_base, + r, + phi - 90, // from tangent lines + 0, // z position of the input plane + false, + thrcl, + ob, wh)); // Next is the cavity walls connecting to the top plate's // artificially created hole. - auto cavitywalls = walls(inner_base, ob, -wingheight, wh, thrcl); - pool.merge(cavitywalls); + pool.merge(walls(inner_base.contour, ob.contour, -wingheight, + wh, -wingdist, thrcl)); } // Now we need to triangulate the top and bottom plates as well as the // cavity bottom plate which is the same as the bottom plate but it is - // eleveted by the thickness. - Polygons top_triangles, bottom_triangles; + // elevated by the thickness. + pool.merge(triangulate_expolygon_3d(top_poly)); + pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); - triangulate(top_poly, top_triangles); - triangulate(inner_base, bottom_triangles); + if(wingheight > 0) + pool.merge(triangulate_expolygon_3d(inner_base, -wingheight)); - auto top_plate = convert(top_triangles, 0, false); - auto bottom_plate = convert(bottom_triangles, -mm(fullheight), true); - - pool.merge(top_plate); - pool.merge(bottom_plate); - - if(wingheight > 0) { - Polygons middle_triangles; - triangulate(inner_base, middle_triangles); - auto middle_plate = convert(middle_triangles, -mm(wingheight), false); - pool.merge(middle_plate); - } - - out.merge(mesh(pool)); } + + // For debugging: + // bench.stop(); + // std::cout << "Pad creation time: " << bench.getElapsedSec() << std::endl; + // std::fstream fout("pad_debug.obj", std::fstream::out); + // if(fout.good()) pool.to_obj(fout); + + out.merge(mesh(pool)); } } diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp index 3917d995b7..69b4561b15 100644 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ b/src/libslic3r/SLA/SLABasePool.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace Slic3r { @@ -27,15 +28,17 @@ struct PoolConfig { double min_wall_height_mm = 5; double max_merge_distance_mm = 50; double edge_radius_mm = 1; + double wall_tilt = std::atan(1.0); // Universal constant for Pi/4 ThrowOnCancel throw_on_cancel = [](){}; inline PoolConfig() {} - inline PoolConfig(double wt, double wh, double md, double er): + inline PoolConfig(double wt, double wh, double md, double er, double tilt): min_wall_thickness_mm(wt), min_wall_height_mm(wh), max_merge_distance_mm(md), - edge_radius_mm(er) {} + edge_radius_mm(er), + wall_tilt(tilt) {} }; /// Calculate the pool for the mesh for SLA printing diff --git a/src/libslic3r/SLA/SLABoilerPlate.hpp b/src/libslic3r/SLA/SLABoilerPlate.hpp index c1096206a2..602121af9a 100644 --- a/src/libslic3r/SLA/SLABoilerPlate.hpp +++ b/src/libslic3r/SLA/SLABoilerPlate.hpp @@ -36,14 +36,6 @@ inline coord_t x(const Vec3crd& p) { return p(0); } inline coord_t y(const Vec3crd& p) { return p(1); } inline coord_t z(const Vec3crd& p) { return p(2); } -inline void triangulate(const ExPolygon& expoly, Polygons& triangles) { - expoly.triangulate_p2t(&triangles); -} - -inline Polygons triangulate(const ExPolygon& expoly) { - Polygons tri; triangulate(expoly, tri); return tri; -} - using Indices = std::vector; /// Intermediate struct for a 3D mesh @@ -63,6 +55,15 @@ struct Contour3D { } } + void merge(const Pointf3s& triangles) { + const size_t offs = points.size(); + points.insert(points.end(), triangles.begin(), triangles.end()); + indices.reserve(indices.size() + points.size() / 3); + + for(int i = (int)offs; i < (int)points.size(); i += 3) + indices.emplace_back(i, i + 1, i + 2); + } + // Write the index triangle structure to OBJ file for debugging purposes. void to_obj(std::ostream& stream) { for(auto& p : points) { @@ -75,13 +76,9 @@ struct Contour3D { } }; -//using PointSet = Eigen::Matrix; //Eigen::MatrixXd; using ClusterEl = std::vector; using ClusteredPoints = std::vector; -/// Convert the triangulation output to an intermediate mesh. -Contour3D convert(const Polygons& triangles, coord_t z, bool dir); - /// Mesh from an existing contour. inline TriangleMesh mesh(const Contour3D& ctour) { return {ctour.points, ctour.indices}; diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp new file mode 100644 index 0000000000..f7c0acf332 --- /dev/null +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -0,0 +1,137 @@ +#ifndef SLACOMMON_HPP +#define SLACOMMON_HPP + +#include + +// #define SLIC3R_SLA_NEEDS_WINDTREE + +namespace Slic3r { + +// Typedefs from Point.hpp +typedef Eigen::Matrix Vec3f; +typedef Eigen::Matrix Vec3d; + +class TriangleMesh; + +namespace sla { + +struct SupportPoint { + Vec3f pos; + float head_front_radius; + bool is_new_island; + + SupportPoint() : + pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false) {} + + SupportPoint(float pos_x, float pos_y, float pos_z, float head_radius, bool new_island) : + pos(pos_x, pos_y, pos_z), head_front_radius(head_radius), is_new_island(new_island) {} + + SupportPoint(Vec3f position, float head_radius, bool new_island) : + pos(position), head_front_radius(head_radius), is_new_island(new_island) {} + + SupportPoint(Eigen::Matrix data) : + pos(data(0), data(1), data(2)), head_front_radius(data(3)), is_new_island(data(4) != 0.f) {} + + bool operator==(const SupportPoint& sp) const { return (pos==sp.pos) && head_front_radius==sp.head_front_radius && is_new_island==sp.is_new_island; } + bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); } +}; + + +/// An index-triangle structure for libIGL functions. Also serves as an +/// alternative (raw) input format for the SLASupportTree +/*struct EigenMesh3D { + Eigen::MatrixXd V; + Eigen::MatrixXi F; + double ground_level = 0; +};*/ + +/// An index-triangle structure for libIGL functions. Also serves as an +/// alternative (raw) input format for the SLASupportTree +class EigenMesh3D { + class AABBImpl; + + Eigen::MatrixXd m_V; + Eigen::MatrixXi m_F; + double m_ground_level = 0; + + std::unique_ptr m_aabb; +public: + + EigenMesh3D(const TriangleMesh&); + EigenMesh3D(const EigenMesh3D& other); + EigenMesh3D& operator=(const EigenMesh3D&); + + ~EigenMesh3D(); + + inline double ground_level() const { return m_ground_level; } + + inline const Eigen::MatrixXd& V() const { return m_V; } + inline const Eigen::MatrixXi& F() const { return m_F; } + + // Result of a raycast + class hit_result { + double m_t = std::numeric_limits::infinity(); + int m_face_id = -1; + const EigenMesh3D& m_mesh; + Vec3d m_dir; + inline hit_result(const EigenMesh3D& em): m_mesh(em) {} + friend class EigenMesh3D; + public: + + inline double distance() const { return m_t; } + inline const Vec3d& direction() const { return m_dir; } + inline int face() const { return m_face_id; } + + inline Vec3d normal() const { + if(m_face_id < 0) return {}; + auto trindex = m_mesh.m_F.row(m_face_id); + const Vec3d& p1 = m_mesh.V().row(trindex(0)); + const Vec3d& p2 = m_mesh.V().row(trindex(1)); + const Vec3d& p3 = m_mesh.V().row(trindex(2)); + Eigen::Vector3d U = p2 - p1; + Eigen::Vector3d V = p3 - p1; + return U.cross(V).normalized(); + } + + inline bool is_inside() { + return m_face_id >= 0 && normal().dot(m_dir) > 0; + } + }; + + // Casting a ray on the mesh, returns the distance where the hit occures. + hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const; + + class si_result { + double m_value; + int m_fidx; + Vec3d m_p; + si_result(double val, int i, const Vec3d& c): + m_value(val), m_fidx(i), m_p(c) {} + friend class EigenMesh3D; + public: + + si_result() = delete; + + double value() const { return m_value; } + operator double() const { return m_value; } + const Vec3d& point_on_mesh() const { return m_p; } + int F_idx() const { return m_fidx; } + }; + +#ifdef SLIC3R_SLA_NEEDS_WINDTREE + // The signed distance from a point to the mesh. Outputs the distance, + // the index of the triangle and the closest point in mesh coordinate space. + si_result signed_distance(const Vec3d& p) const; + + bool inside(const Vec3d& p) const; +#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ +}; + + + + +} // namespace sla +} // namespace Slic3r + + +#endif // SLASUPPORTTREE_HPP \ No newline at end of file diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 7cf6015cb8..913e9bedab 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -551,10 +551,16 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers X, Y, Z }; -PointSet to_point_set(const std::vector &v) +PointSet to_point_set(const std::vector &v) { PointSet ret(v.size(), 3); - { long i = 0; for(const Vec3d& p : v) ret.row(i++) = p; } + long i = 0; + for(const SupportPoint& support_point : v) { + ret.row(i)(0) = support_point.pos(0); + ret.row(i)(1) = support_point.pos(1); + ret.row(i)(2) = support_point.pos(2); + ++i; + } return ret; } @@ -679,6 +685,7 @@ double pinhead_mesh_intersect(const Vec3d& s, return *mit; } + // Checking bridge (pillar and stick as well) intersection with the model. If // the function is used for headless sticks, the ins_check parameter have to be // true as the beginning of the stick might be inside the model geometry. diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 7d23a981fb..c29b2a5712 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -7,6 +7,9 @@ #include #include +#include "SLACommon.hpp" + + namespace Slic3r { // Needed types from Point.hpp @@ -105,86 +108,6 @@ struct Controller { std::function cancelfn = [](){}; }; -/// An index-triangle structure for libIGL functions. Also serves as an -/// alternative (raw) input format for the SLASupportTree -class EigenMesh3D { - class AABBImpl; - - Eigen::MatrixXd m_V; - Eigen::MatrixXi m_F; - double m_ground_level = 0; - - std::unique_ptr m_aabb; -public: - - EigenMesh3D(const TriangleMesh&); - EigenMesh3D(const EigenMesh3D& other); - EigenMesh3D& operator=(const EigenMesh3D&); - - ~EigenMesh3D(); - - inline double ground_level() const { return m_ground_level; } - - inline const Eigen::MatrixXd& V() const { return m_V; } - inline const Eigen::MatrixXi& F() const { return m_F; } - - // Result of a raycast - class hit_result { - double m_t = std::numeric_limits::infinity(); - int m_face_id = -1; - const EigenMesh3D& m_mesh; - Vec3d m_dir; - inline hit_result(const EigenMesh3D& em): m_mesh(em) {} - friend class EigenMesh3D; - public: - - inline double distance() const { return m_t; } - - inline int face() const { return m_face_id; } - - inline Vec3d normal() const { - if(m_face_id < 0) return {}; - auto trindex = m_mesh.m_F.row(m_face_id); - const Vec3d& p1 = m_mesh.V().row(trindex(0)); - const Vec3d& p2 = m_mesh.V().row(trindex(1)); - const Vec3d& p3 = m_mesh.V().row(trindex(2)); - Eigen::Vector3d U = p2 - p1; - Eigen::Vector3d V = p3 - p1; - return U.cross(V).normalized(); - } - - inline bool is_inside() { - return m_face_id >= 0 && normal().dot(m_dir) > 0; - } - }; - - // Casting a ray on the mesh, returns the distance where the hit occures. - hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const; - - class si_result { - double m_value; - int m_fidx; - Vec3d m_p; - si_result(double val, int i, const Vec3d& c): - m_value(val), m_fidx(i), m_p(c) {} - friend class EigenMesh3D; - public: - - si_result() = delete; - - double value() const { return m_value; } - operator double() const { return m_value; } - const Vec3d& point_on_mesh() const { return m_p; } - int F_idx() const { return m_fidx; } - }; - - // The signed distance from a point to the mesh. Outputs the distance, - // the index of the triangle and the closest point in mesh coordinate space. - si_result signed_distance(const Vec3d& p) const; - - bool inside(const Vec3d& p) const; -}; - using PointSet = Eigen::MatrixXd; //EigenMesh3D to_eigenmesh(const TriangleMesh& m); @@ -193,7 +116,7 @@ using PointSet = Eigen::MatrixXd; //EigenMesh3D to_eigenmesh(const ModelObject& model); // Simple conversion of 'vector of points' to an Eigen matrix -PointSet to_point_set(const std::vector&); +PointSet to_point_set(const std::vector&); /* ************************************************************************** */ diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index d3af1eac89..25638fe692 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -95,7 +95,9 @@ size_t SpatIndex::size() const class EigenMesh3D::AABBImpl: public igl::AABB { public: +#ifdef SLIC3R_SLA_NEEDS_WINDTREE igl::WindingNumberAABB windtree; +#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ }; EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { @@ -136,7 +138,9 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { // Build the AABB accelaration tree m_aabb->init(m_V, m_F); +#ifdef SLIC3R_SLA_NEEDS_WINDTREE m_aabb->windtree.set_mesh(m_V, m_F); +#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ } EigenMesh3D::~EigenMesh3D() {} @@ -168,6 +172,7 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const return ret; } +#ifdef SLIC3R_SLA_NEEDS_WINDTREE EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const { double sign = 0; double sqdst = 0; int i = 0; Vec3d c; igl::signed_distance_winding_number(*m_aabb, m_V, m_F, m_aabb->windtree, @@ -179,6 +184,7 @@ EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const { bool EigenMesh3D::inside(const Vec3d &p) const { return m_aabb->windtree.inside(p); } +#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ /* **************************************************************************** * Misc functions @@ -199,9 +205,11 @@ template double distance(const Vec& pp1, const Vec& pp2) { return std::sqrt(p.transpose() * p); } -PointSet normals(const PointSet& points, const EigenMesh3D& mesh, +PointSet normals(const PointSet& points, + const EigenMesh3D& mesh, double eps, - std::function throw_on_cancel) { + std::function throw_on_cancel) +{ if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0) return {}; @@ -222,7 +230,7 @@ PointSet normals(const PointSet& points, const EigenMesh3D& mesh, const Vec3d& p3 = mesh.V().row(trindex(2)); // We should check if the point lies on an edge of the hosting triangle. - // If it does than all the other triangles using the same two points + // If it does then all the other triangles using the same two points // have to be searched and the final normal should be some kind of // aggregation of the participating triangle normals. We should also // consider the cases where the support point lies right on a vertex diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 142428f1db..d83e756e83 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -2,12 +2,14 @@ #include "SLA/SLASupportTree.hpp" #include "SLA/SLABasePool.hpp" #include "SLA/SLAAutoSupports.hpp" +#include "ClipperUtils.hpp" #include "MTUtils.hpp" #include #include #include +#include #include //#include //#include "tbb/mutex.h" @@ -25,7 +27,7 @@ using SupportTreePtr = std::unique_ptr; class SLAPrintObject::SupportData { public: sla::EigenMesh3D emesh; // index-triangle representation - sla::PointSet support_points; // all the support points (manual/auto) + std::vector support_points; // all the support points (manual/auto) SupportTreePtr support_tree_ptr; // the supports SlicedSupports support_slices; // sliced supports std::vector level_ids; @@ -51,7 +53,7 @@ const std::array OBJ_STEP_LABELS = L("Slicing model"), // slaposObjectSlice, L("Generating support points"), // slaposSupportPoints, L("Generating support tree"), // slaposSupportTree, - L("Generating base pool"), // slaposBasePool, + L("Generating pad"), // slaposBasePool, L("Slicing supports"), // slaposSliceSupports, L("Slicing supports") // slaposIndexSlices, }; @@ -182,7 +184,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf if (model.id() != m_model.id()) { // Kill everything, initialize from scratch. // Stop background processing. - this->call_cancell_callback(); + this->call_cancel_callback(); update_apply_status(this->invalidate_all_steps()); for (SLAPrintObject *object : m_objects) { model_object_status.emplace(object->model_object()->id(), ModelObjectStatus::Deleted); @@ -211,7 +213,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf } else { // Reorder the objects, add new objects. // First stop background processing before shuffling or deleting the PrintObjects in the object list. - this->call_cancell_callback(); + this->call_cancel_callback(); update_apply_status(this->invalidate_step(slapsRasterize)); // Second create a new list of objects. std::vector model_objects_old(std::move(m_model.objects)); @@ -308,7 +310,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf if (it_print_object_status != print_object_status.end() && it_print_object_status->id != model_object.id()) it_print_object_status = print_object_status.end(); // Check whether a model part volume was added or removed, their transformations or order changed. - bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::MODEL_PART); + bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); bool sla_trafo_differs = model_object.instances.empty() != model_object_new.instances.empty() || (! model_object.instances.empty() && ! sla_trafo(model_object).isApprox(sla_trafo(model_object_new))); if (model_parts_differ || sla_trafo_differs) { @@ -354,14 +356,18 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf std::vector new_instances = sla_instances(model_object); if (it_print_object_status != print_object_status.end() && it_print_object_status->status != PrintObjectStatus::Deleted) { // The SLAPrintObject is already there. - if (new_instances != it_print_object_status->print_object->instances()) { - // Instances changed. - it_print_object_status->print_object->set_instances(new_instances); - update_apply_status(this->invalidate_step(slapsRasterize)); - } - print_objects_new.emplace_back(it_print_object_status->print_object); - const_cast(*it_print_object_status).status = PrintObjectStatus::Reused; - } else { + if (new_instances.empty()) { + const_cast(*it_print_object_status).status = PrintObjectStatus::Deleted; + } else { + if (new_instances != it_print_object_status->print_object->instances()) { + // Instances changed. + it_print_object_status->print_object->set_instances(new_instances); + update_apply_status(this->invalidate_step(slapsRasterize)); + } + print_objects_new.emplace_back(it_print_object_status->print_object); + const_cast(*it_print_object_status).status = PrintObjectStatus::Reused; + } + } else if (! new_instances.empty()) { auto print_object = new SLAPrintObject(this, &model_object); // FIXME: this invalidates the transformed mesh in SLAPrintObject @@ -376,7 +382,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf } if (m_objects != print_objects_new) { - this->call_cancell_callback(); + this->call_cancel_callback(); update_apply_status(this->invalidate_all_steps()); m_objects = print_objects_new; // Delete the PrintObjects marked as Unknown or Deleted. @@ -398,6 +404,113 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf return static_cast(apply_status); } +// After calling the apply() function, set_task() may be called to limit the task to be processed by process(). +void SLAPrint::set_task(const TaskParams ¶ms) +{ + // Grab the lock for the Print / PrintObject milestones. + tbb::mutex::scoped_lock lock(this->state_mutex()); + + int n_object_steps = int(params.to_object_step) + 1; + if (n_object_steps == 0) + n_object_steps = (int)slaposCount; + + if (params.single_model_object.valid()) { + // Find the print object to be processed with priority. + SLAPrintObject *print_object = nullptr; + size_t idx_print_object = 0; + for (; idx_print_object < m_objects.size(); ++ idx_print_object) + if (m_objects[idx_print_object]->model_object()->id() == params.single_model_object) { + print_object = m_objects[idx_print_object]; + break; + } + assert(print_object != nullptr); + // Find out whether the priority print object is being currently processed. + bool running = false; + for (int istep = 0; istep < n_object_steps; ++ istep) { + if (! print_object->m_stepmask[istep]) + // Step was skipped, cancel. + break; + if (print_object->is_step_started_unguarded(SLAPrintObjectStep(istep))) { + // No step was skipped, and a wanted step is being processed. Don't cancel. + running = true; + break; + } + } + if (! running) + this->call_cancel_callback(); + + // Now the background process is either stopped, or it is inside one of the print object steps to be calculated anyway. + if (params.single_model_instance_only) { + // Suppress all the steps of other instances. + for (SLAPrintObject *po : m_objects) + for (int istep = 0; istep < (int)slaposCount; ++ istep) + po->m_stepmask[istep] = false; + } else if (! running) { + // Swap the print objects, so that the selected print_object is first in the row. + // At this point the background processing must be stopped, so it is safe to shuffle print objects. + if (idx_print_object != 0) + std::swap(m_objects.front(), m_objects[idx_print_object]); + } + // and set the steps for the current object. + for (int istep = 0; istep < n_object_steps; ++ istep) + print_object->m_stepmask[istep] = true; + for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep) + print_object->m_stepmask[istep] = false; + } else { + // Slicing all objects. + bool running = false; + for (SLAPrintObject *print_object : m_objects) + for (int istep = 0; istep < n_object_steps; ++ istep) { + if (! print_object->m_stepmask[istep]) { + // Step may have been skipped. Restart. + goto loop_end; + } + if (print_object->is_step_started_unguarded(SLAPrintObjectStep(istep))) { + // This step is running, and the state cannot be changed due to the this->state_mutex() being locked. + // It is safe to manipulate m_stepmask of other SLAPrintObjects and SLAPrint now. + running = true; + goto loop_end; + } + } + loop_end: + if (! running) + this->call_cancel_callback(); + for (SLAPrintObject *po : m_objects) { + for (int istep = 0; istep < n_object_steps; ++ istep) + po->m_stepmask[istep] = true; + for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep) + po->m_stepmask[istep] = false; + } + } + + if (params.to_object_step != -1 || params.to_print_step != -1) { + // Limit the print steps. + size_t istep = (params.to_object_step != -1) ? 0 : size_t(params.to_print_step) + 1; + for (; istep < m_stepmask.size(); ++ istep) + m_stepmask[istep] = false; + } +} + +// Clean up after process() finished, either with success, error or if canceled. +// The adjustments on the SLAPrint / SLAPrintObject data due to set_task() are to be reverted here. +void SLAPrint::finalize() +{ + for (SLAPrintObject *po : m_objects) + for (int istep = 0; istep < (int)slaposCount; ++ istep) + po->m_stepmask[istep] = true; + for (int istep = 0; istep < (int)slapsCount; ++ istep) + m_stepmask[istep] = true; +} + +// Generate a recommended output file name based on the format template, default extension, and template parameters +// (timestamps, object placeholders derived from the model, current placeholder prameters and print statistics. +// Use the final print statistics if available, or just keep the print statistics placeholders if not available yet (before the output is finalized). +std::string SLAPrint::output_filename() const +{ + DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders(); + return this->PrintBase::output_filename(m_print_config.output_filename_format.value, "zip", &config); +} + namespace { // Compile the argument for support creation from the static print config. sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { @@ -472,7 +585,7 @@ void SLAPrint::process() const size_t objcount = m_objects.size(); const unsigned min_objstatus = 0; // where the per object operations start - const unsigned max_objstatus = 80; // where the per object operations end + const unsigned max_objstatus = PRINT_STEP_LEVELS[slapsRasterize]; // where the per object operations end // the coefficient that multiplies the per object status values which // are set up for <0, 100>. They need to be scaled into the whole process @@ -532,9 +645,8 @@ void SLAPrint::process() this->throw_if_canceled(); SLAAutoSupports::Config config; const SLAPrintObjectConfig& cfg = po.config(); - config.minimal_z = float(cfg.support_minimal_z); - config.density_at_45 = cfg.support_density_at_45 / 10000.f; - config.density_at_horizontal = cfg.support_density_at_horizontal / 10000.f; + config.density_relative = float(cfg.support_points_density_relative / 100.f); // the config value is in percents + config.minimal_distance = float(cfg.support_points_minimal_distance); // Construction of this object does the calculation. this->throw_if_canceled(); @@ -546,17 +658,19 @@ void SLAPrint::process() [this]() { throw_if_canceled(); }); // Now let's extract the result. - const std::vector& points = auto_supports.output(); + const std::vector& points = auto_supports.output(); this->throw_if_canceled(); - po.m_supportdata->support_points = sla::to_point_set(points); + po.m_supportdata->support_points = points; BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " - << po.m_supportdata->support_points.rows(); + << po.m_supportdata->support_points.size(); + + // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass the update status to GLGizmoSlaSupports + report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); } else { // There are some points on the front-end, no calculation will be done. - po.m_supportdata->support_points = - sla::to_point_set(po.transformed_support_points()); + po.m_supportdata->support_points = po.transformed_support_points(); } }; @@ -587,6 +701,8 @@ void SLAPrint::process() ctl.statuscb = [this, init, d](unsigned st, const std::string& msg) { + //FIXME this status line scaling does not seem to be correct. + // How does it account for an increasing object index? report_status(*this, int(init + st*d), msg); }; @@ -594,7 +710,7 @@ void SLAPrint::process() ctl.cancelfn = [this]() { throw_if_canceled(); }; po.m_supportdata->support_tree_ptr.reset( - new SLASupportTree(po.m_supportdata->support_points, + new SLASupportTree(sla::to_point_set(po.m_supportdata->support_points), po.m_supportdata->emesh, scfg, ctl)); // Create the unified mesh @@ -605,7 +721,7 @@ void SLAPrint::process() po.m_supportdata->support_tree_ptr->merged_mesh(); BOOST_LOG_TRIVIAL(debug) << "Processed support point count " - << po.m_supportdata->support_points.rows(); + << po.m_supportdata->support_points.size(); // Check the mesh for later troubleshooting. if(po.support_mesh().empty()) @@ -635,11 +751,13 @@ void SLAPrint::process() double wt = po.m_config.pad_wall_thickness.getFloat(); double h = po.m_config.pad_wall_height.getFloat(); double md = po.m_config.pad_max_merge_distance.getFloat(); - double er = po.m_config.pad_edge_radius.getFloat(); + // Radius is disabled for now... + double er = 0; // po.m_config.pad_edge_radius.getFloat(); + double tilt = po.m_config.pad_wall_tilt.getFloat() * PI / 180.0; double lh = po.m_config.layer_height.getFloat(); double elevation = po.m_config.support_object_elevation.getFloat(); if(!po.m_config.supports_enable.getBool()) elevation = 0; - sla::PoolConfig pcfg(wt, h, md, er); + sla::PoolConfig pcfg(wt, h, md, er, tilt); ExPolygons bp; double pad_h = sla::get_pad_fullheight(pcfg); @@ -650,8 +768,7 @@ void SLAPrint::process() if(elevation < pad_h) { // we have to count with the model geometry for the base plate - sla::base_plate(trmesh, bp, float(pad_h), float(lh), - thrfn); + sla::base_plate(trmesh, bp, float(pad_h), float(lh), thrfn); } pcfg.throw_on_cancel = thrfn; @@ -878,21 +995,20 @@ void SLAPrint::process() // Print all the layers in parallel tbb::parallel_for(0, lvlcnt, lvlfn); + + // Fill statistics + this->fill_statistics(); + // Set statistics values to the printer + m_printer->set_statistics({(m_print_statistics.objects_used_material + m_print_statistics.support_used_material)/1000, + double(m_default_object_config.faded_layers.getInt()), + double(m_print_statistics.slow_layers_count), + double(m_print_statistics.fast_layers_count) + }); }; using slaposFn = std::function; using slapsFn = std::function; - // This is the actual order of steps done on each PrintObject - std::array objectsteps = { - slaposObjectSlice, // SupportPoints will need this step - slaposSupportPoints, - slaposSupportTree, - slaposBasePool, - slaposSliceSupports, - slaposIndexSlices - }; - std::array pobj_program = { slice_model, @@ -916,28 +1032,32 @@ void SLAPrint::process() // TODO: this loop could run in parallel but should not exhaust all the CPU // power available - for(SLAPrintObject * po : m_objects) { + // Calculate the support structures first before slicing the supports, so that the preview will get displayed ASAP for all objects. + std::vector step_ranges = { slaposObjectSlice, slaposSliceSupports, slaposCount }; + for (size_t idx_range = 0; idx_range + 1 < step_ranges.size(); ++ idx_range) { + for(SLAPrintObject * po : m_objects) { - BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name; + BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name; - for(size_t s = 0; s < objectsteps.size(); ++s) { - auto currentstep = objectsteps[s]; + for (int s = (int)step_ranges[idx_range]; s < (int)step_ranges[idx_range + 1]; ++s) { + auto currentstep = (SLAPrintObjectStep)s; - // Cancellation checking. Each step will check for cancellation - // on its own and return earlier gracefully. Just after it returns - // execution gets to this point and throws the canceled signal. - throw_if_canceled(); - - st += unsigned(incr * ostepd); - - if(po->m_stepmask[currentstep] && po->set_started(currentstep)) { - report_status(*this, int(st), OBJ_STEP_LABELS[currentstep]); - pobj_program[currentstep](*po); + // Cancellation checking. Each step will check for cancellation + // on its own and return earlier gracefully. Just after it returns + // execution gets to this point and throws the canceled signal. throw_if_canceled(); - po->set_done(currentstep); - } - incr = OBJ_STEP_LEVELS[currentstep]; + st += unsigned(incr * ostepd); + + if(po->m_stepmask[currentstep] && po->set_started(currentstep)) { + report_status(*this, int(st), OBJ_STEP_LABELS[currentstep]); + pobj_program[currentstep](*po); + throw_if_canceled(); + po->set_done(currentstep); + } + + incr = OBJ_STEP_LEVELS[currentstep]; + } } } @@ -994,7 +1114,10 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector steps; @@ -1027,6 +1150,166 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector& instances) { + const size_t inst_cnt = instances.size(); + + size_t polygon_cnt = 0; + for (const ExPolygon& polygon : input_polygons) + polygon_cnt += polygon.holes.size() + 1; + + Polygons polygons; + polygons.reserve(polygon_cnt * inst_cnt); + for (const ExPolygon& polygon : input_polygons) { + for (size_t i = 0; i < inst_cnt; ++i) + { + ExPolygon tmp = polygon; + tmp.rotate(Geometry::rad2deg(instances[i].rotation)); + tmp.translate(instances[i].shift.x(), instances[i].shift.y()); + polygons_append(polygons, to_polygons(std::move(tmp))); + } + } + return polygons; + }; + + double supports_volume = 0.0; + double models_volume = 0.0; + + double estim_time = 0.0; + + size_t slow_layers = 0; + size_t fast_layers = 0; + + // find highest object + // Which is a better bet? To compare by max_z or by number of layers in the index? + double max_z = 0.; + size_t max_layers_cnt = 0; + size_t highest_obj_idx = 0; + for (SLAPrintObject *&po : m_objects) { + const SLAPrintObject::SliceIndex& slice_index = po->get_slice_index(); + if (! slice_index.empty()) { + double z = (-- slice_index.end())->first; + size_t cnt = slice_index.size(); + //if (z > max_z) { + if (cnt > max_layers_cnt) { + max_layers_cnt = cnt; + max_z = z; + highest_obj_idx = &po - &m_objects.front(); + } + } + } + + const SLAPrintObject * highest_obj = m_objects[highest_obj_idx]; + const SLAPrintObject::SliceIndex& highest_obj_slice_index = highest_obj->get_slice_index(); + + const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1); + double fade_layer_time = init_exp_time; + + int sliced_layer_cnt = 0; + for (const auto& layer : highest_obj_slice_index) + { + const double l_height = (layer.first == highest_obj_slice_index.begin()->first) ? init_layer_height : layer_height; + + // Calculation of the consumed material + + Polygons model_polygons; + Polygons supports_polygons; + + for (SLAPrintObject * po : m_objects) + { + const SLAPrintObject::SliceRecord *record = nullptr; + { + const SLAPrintObject::SliceIndex& index = po->get_slice_index(); + auto key = layer.first; + const SLAPrintObject::SliceIndex::const_iterator it_key = index.lower_bound(key - float(EPSILON)); + if (it_key == index.end() || it_key->first > key + EPSILON) + continue; + record = &it_key->second; + } + + if (record->model_slices_idx != SLAPrintObject::SliceRecord::NONE) + append(model_polygons, get_all_polygons(po->get_model_slices()[record->model_slices_idx], po->instances())); + + if (record->support_slices_idx != SLAPrintObject::SliceRecord::NONE) + append(supports_polygons, get_all_polygons(po->get_support_slices()[record->support_slices_idx], po->instances())); + } + + model_polygons = union_(model_polygons); + double layer_model_area = 0; + for (const Polygon& polygon : model_polygons) + layer_model_area += polygon.area(); + + if (layer_model_area != 0) + models_volume += layer_model_area * l_height; + + if (!supports_polygons.empty() && !model_polygons.empty()) + supports_polygons = diff(supports_polygons, model_polygons); + double layer_support_area = 0; + for (const Polygon& polygon : supports_polygons) + layer_support_area += polygon.area(); + + if (layer_support_area != 0) + supports_volume += layer_support_area * l_height; + + // Calculation of the slow and fast layers to the future controlling those values on FW + + const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill; + const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt; + if (is_fast_layer) + fast_layers++; + else + slow_layers++; + + + // Calculation of the printing time + + if (sliced_layer_cnt < 3) + estim_time += init_exp_time; + else if (fade_layer_time > exp_time) + { + fade_layer_time -= delta_fade_time; + estim_time += fade_layer_time; + } + else + estim_time += exp_time; + + estim_time += tilt_time; + + sliced_layer_cnt++; + } + + m_print_statistics.support_used_material = supports_volume * SCALING_FACTOR * SCALING_FACTOR; + m_print_statistics.objects_used_material = models_volume * SCALING_FACTOR * SCALING_FACTOR; + + // Estimated printing time + // A layers count o the highest object + if (max_layers_cnt == 0) + m_print_statistics.estimated_print_time = "N/A"; + else + m_print_statistics.estimated_print_time = get_time_dhms(float(estim_time)); + + m_print_statistics.fast_layers_count = fast_layers; + m_print_statistics.slow_layers_count = slow_layers; +} + // Returns true if an object step is done on all objects and there's at least one object. bool SLAPrint::is_step_done(SLAPrintObjectStep step) const { @@ -1034,7 +1317,7 @@ bool SLAPrint::is_step_done(SLAPrintObjectStep step) const return false; tbb::mutex::scoped_lock lock(this->state_mutex()); for (const SLAPrintObject *object : m_objects) - if (! object->m_state.is_done_unguarded(step)) + if (! object->is_step_done_unguarded(step)) return false; return true; } @@ -1060,9 +1343,13 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector steps; bool invalidated = false; for (const t_config_option_key &opt_key : opt_keys) { - if (opt_key == "layer_height") { + if ( opt_key == "layer_height" + || opt_key == "faded_layers") { steps.emplace_back(slaposObjectSlice); - } else if (opt_key == "supports_enable") { + } else if ( + opt_key == "supports_enable" + || opt_key == "support_points_density_relative" + || opt_key == "support_points_minimal_distance") { steps.emplace_back(slaposSupportPoints); } else if ( opt_key == "support_head_front_diameter" @@ -1082,6 +1369,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector EMPTY_SLICES; const TriangleMesh EMPTY_MESH; } -const Eigen::MatrixXd& SLAPrintObject::get_support_points() const +const std::vector& SLAPrintObject::get_support_points() const { return m_supportdata->support_points; } @@ -1244,17 +1532,60 @@ const TriangleMesh &SLAPrintObject::transformed_mesh() const { return m_transformed_rmesh.get(); } -std::vector SLAPrintObject::transformed_support_points() const +std::vector SLAPrintObject::transformed_support_points() const { assert(m_model_object != nullptr); - auto& spts = m_model_object->sla_support_points; + std::vector& spts = m_model_object->sla_support_points; // this could be cached as well - std::vector ret; ret.reserve(spts.size()); + std::vector ret; + ret.reserve(spts.size()); - for(auto& sp : spts) ret.emplace_back( trafo() * Vec3d(sp.cast())); + for(sla::SupportPoint& sp : spts) { + Vec3d transformed_pos = trafo() * Vec3d(sp.pos(0), sp.pos(1), sp.pos(2)); + ret.emplace_back(transformed_pos(0), transformed_pos(1), transformed_pos(2), sp.head_front_radius, sp.is_new_island); + } return ret; } +DynamicConfig SLAPrintStatistics::config() const +{ + DynamicConfig config; + const std::string print_time = Slic3r::short_time(this->estimated_print_time); + config.set_key_value("print_time", new ConfigOptionString(print_time)); + config.set_key_value("objects_used_material", new ConfigOptionFloat(this->objects_used_material)); + config.set_key_value("support_used_material", new ConfigOptionFloat(this->support_used_material)); + config.set_key_value("total_cost", new ConfigOptionFloat(this->total_cost)); + config.set_key_value("total_weight", new ConfigOptionFloat(this->total_weight)); + return config; +} + +DynamicConfig SLAPrintStatistics::placeholders() +{ + DynamicConfig config; + for (const std::string &key : { + "print_time", "total_cost", "total_weight", + "objects_used_material", "support_used_material" }) + config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}")); + return config; +} + +std::string SLAPrintStatistics::finalize_output_path(const std::string &path_in) const +{ + std::string final_path; + try { + boost::filesystem::path path(path_in); + DynamicConfig cfg = this->config(); + PlaceholderParser pp; + std::string new_stem = pp.process(path.stem().string(), 0, &cfg); + final_path = (path.parent_path() / (new_stem + path.extension().string())).string(); + } + catch (const std::exception &ex) { + BOOST_LOG_TRIVIAL(error) << "Failed to apply the print statistics to the export file name: " << ex.what(); + final_path = path_in; + } + return final_path; +} + } // namespace Slic3r diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 21503c6f6a..4ac17f7b5c 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -2,7 +2,6 @@ #define slic3r_SLAPrint_hpp_ #include - #include "PrintBase.hpp" #include "PrintExport.hpp" #include "Point.hpp" @@ -70,7 +69,7 @@ public: // This will return the transformed mesh which is cached const TriangleMesh& transformed_mesh() const; - std::vector transformed_support_points() const; + std::vector transformed_support_points() const; // Get the needed Z elevation for the model geometry if supports should be // displayed. This Z offset should also be applied to the support @@ -91,7 +90,7 @@ public: const std::vector& get_support_slices() const; // This method returns the support points of this SLAPrintObject. - const Eigen::MatrixXd& get_support_points() const; + const std::vector& get_support_points() const; // An index record referencing the slices // (get_model_slices(), get_support_slices()) where the keys are the height @@ -139,6 +138,10 @@ protected: // Invalidate steps based on a set of parameters changed. bool invalidate_state_by_config_options(const std::vector &opt_keys); + // Which steps have to be performed. Implicitly: all + // to be accessible from SLAPrint + std::vector m_stepmask; + private: // Object specific configuration, pulled from the configuration layer. SLAPrintObjectConfig m_config; @@ -146,9 +149,6 @@ private: Transform3d m_trafo = Transform3d::Identity(); std::vector m_instances; - // Which steps have to be performed. Implicitly: all - std::vector m_stepmask; - // Individual 2d slice polygons from lower z to higher z levels std::vector m_model_slices; @@ -171,6 +171,35 @@ using PrintObjects = std::vector; class TriangleMesh; +struct SLAPrintStatistics +{ + SLAPrintStatistics() { clear(); } + std::string estimated_print_time; + double objects_used_material; + double support_used_material; + size_t slow_layers_count; + size_t fast_layers_count; + double total_cost; + double total_weight; + + // Config with the filled in print statistics. + DynamicConfig config() const; + // Config with the statistics keys populated with placeholder strings. + static DynamicConfig placeholders(); + // Replace the print statistics placeholders in the path. + std::string finalize_output_path(const std::string &path_in) const; + + void clear() { + estimated_print_time.clear(); + objects_used_material = 0.; + support_used_material = 0.; + slow_layers_count = 0; + fast_layers_count = 0; + total_cost = 0.; + total_weight = 0.; + } +}; + /** * @brief This class is the high level FSM for the SLA printing process. * @@ -194,7 +223,9 @@ public: void clear() override; bool empty() const override { return m_objects.empty(); } ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; + void set_task(const TaskParams ¶ms) override; void process() override; + void finalize() override; // Returns true if an object step is done on all objects and there's at least one object. bool is_step_done(SLAPrintObjectStep step) const; // Returns true if the last step was finished with success. @@ -205,8 +236,9 @@ public: } const PrintObjects& objects() const { return m_objects; } - std::string output_filename() const override - { return this->PrintBase::output_filename(m_print_config.output_filename_format.value, "zip"); } + std::string output_filename() const override; + + const SLAPrintStatistics& print_statistics() const { return m_print_statistics; } private: using SLAPrinter = FilePrinter; @@ -215,6 +247,8 @@ private: // Invalidate steps based on a set of parameters changed. bool invalidate_state_by_config_options(const std::vector &opt_keys); + void fill_statistics(); + SLAPrintConfig m_print_config; SLAPrinterConfig m_printer_config; SLAMaterialConfig m_material_config; @@ -246,6 +280,9 @@ private: // The printer itself SLAPrinterPtr m_printer; + // Estimated print time, material consumed. + SLAPrintStatistics m_print_statistics; + friend SLAPrintObject; }; diff --git a/src/libslic3r/Surface.cpp b/src/libslic3r/Surface.cpp index 0e9eca7fd6..acc19e631a 100644 --- a/src/libslic3r/Surface.cpp +++ b/src/libslic3r/Surface.cpp @@ -42,6 +42,12 @@ Surface::is_internal() const || this->surface_type == stInternalVoid; } +bool +Surface::is_top() const +{ + return this->surface_type == stTop; +} + bool Surface::is_bottom() const { diff --git a/src/libslic3r/Surface.hpp b/src/libslic3r/Surface.hpp index c2cec37931..e0f7e1ea3d 100644 --- a/src/libslic3r/Surface.hpp +++ b/src/libslic3r/Surface.hpp @@ -98,6 +98,7 @@ public: bool is_solid() const; bool is_external() const; bool is_internal() const; + bool is_top() const; bool is_bottom() const; bool is_bridge() const; }; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 8d788ed51e..755ca5ffac 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -23,21 +23,10 @@ // Scene's GUI made using imgui library #define ENABLE_IMGUI (1 && ENABLE_1_42_0_ALPHA1) #define DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI (1 && ENABLE_IMGUI) -// Modified Sla support gizmo -#define ENABLE_SLA_SUPPORT_GIZMO_MOD (1 && ENABLE_1_42_0_ALPHA1) // Use wxDataViewRender instead of wxDataViewCustomRenderer #define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1) -//==================== -// 1.42.0.alpha2 techs -//==================== -#define ENABLE_1_42_0_ALPHA2 1 - -// Adds print bed models to 3D scene -#define ENABLE_PRINT_BED_MODELS (1 && ENABLE_1_42_0_ALPHA2) - - //==================== // 1.42.0.alpha4 techs //==================== @@ -49,10 +38,6 @@ #define ENABLE_MOVE_MIN_THRESHOLD (1 && ENABLE_1_42_0_ALPHA4) // Modified initial default placement of generic subparts #define ENABLE_GENERIC_SUBPARTS_PLACEMENT (1 && ENABLE_1_42_0_ALPHA4) -// Reworked management of bed shape changes -#define ENABLE_REWORKED_BED_SHAPE_CHANGE (1 && ENABLE_1_42_0_ALPHA4) -// Use anisotropic filtering on bed plate texture -#define ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES (1 && ENABLE_1_42_0_ALPHA4) // Bunch of fixes related to volumes centering #define ENABLE_VOLUMES_CENTERING_FIXES (1 && ENABLE_1_42_0_ALPHA4) @@ -65,4 +50,13 @@ // Toolbar items hidden/shown in dependence of the user mode #define ENABLE_MODE_AWARE_TOOLBAR_ITEMS (1 && ENABLE_1_42_0_ALPHA5) + +//==================== +// 1.42.0.alpha7 techs +//==================== +#define ENABLE_1_42_0_ALPHA7 1 + +// Printbed textures generated from svg files +#define ENABLE_TEXTURES_FROM_SVG (1 && ENABLE_1_42_0_ALPHA7) + #endif // _technologies_h_ diff --git a/src/libslic3r/Tesselate.cpp b/src/libslic3r/Tesselate.cpp index f4ce0703de..febb3d0e74 100644 --- a/src/libslic3r/Tesselate.cpp +++ b/src/libslic3r/Tesselate.cpp @@ -20,7 +20,7 @@ public: gluDeleteTess(m_tesselator); } - Pointf3s tesselate(const ExPolygon &expoly, double z_, bool flipped_) + std::vector tesselate3d(const ExPolygon &expoly, double z_, bool flipped_) { m_z = z_; m_flipped = flipped_; @@ -56,7 +56,7 @@ public: return std::move(m_output_triangles); } - Pointf3s tesselate(const ExPolygons &expolygons, double z_, bool flipped_) + std::vector tesselate3d(const ExPolygons &expolygons, double z_, bool flipped_) { m_z = z_; m_flipped = flipped_; @@ -189,16 +189,60 @@ private: bool m_flipped; }; -Pointf3s triangulate_expolygons_3df(const ExPolygon &poly, coordf_t z, bool flip) +std::vector triangulate_expolygon_3d(const ExPolygon &poly, coordf_t z, bool flip) { GluTessWrapper tess; - return tess.tesselate(poly, z, flip); + return tess.tesselate3d(poly, z, flip); } -Pointf3s triangulate_expolygons_3df(const ExPolygons &polys, coordf_t z, bool flip) +std::vector triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z, bool flip) { GluTessWrapper tess; - return tess.tesselate(polys, z, flip); + return tess.tesselate3d(polys, z, flip); +} + +std::vector triangulate_expolygon_2d(const ExPolygon &poly, bool flip) +{ + GluTessWrapper tess; + std::vector triangles = tess.tesselate3d(poly, 0, flip); + std::vector out; + out.reserve(triangles.size()); + for (const Vec3d &pt : triangles) + out.emplace_back(pt.x(), pt.y()); + return out; +} + +std::vector triangulate_expolygons_2d(const ExPolygons &polys, bool flip) +{ + GluTessWrapper tess; + std::vector triangles = tess.tesselate3d(polys, 0, flip); + std::vector out; + out.reserve(triangles.size()); + for (const Vec3d &pt : triangles) + out.emplace_back(pt.x(), pt.y()); + return out; +} + +std::vector triangulate_expolygon_2f(const ExPolygon &poly, bool flip) +{ + GluTessWrapper tess; + std::vector triangles = tess.tesselate3d(poly, 0, flip); + std::vector out; + out.reserve(triangles.size()); + for (const Vec3d &pt : triangles) + out.emplace_back(float(pt.x()), float(pt.y())); + return out; +} + +std::vector triangulate_expolygons_2f(const ExPolygons &polys, bool flip) +{ + GluTessWrapper tess; + std::vector triangles = tess.tesselate3d(polys, 0, flip); + std::vector out; + out.reserve(triangles.size()); + for (const Vec3d &pt : triangles) + out.emplace_back(float(pt.x()), float(pt.y())); + return out; } } // namespace Slic3r diff --git a/src/libslic3r/Tesselate.hpp b/src/libslic3r/Tesselate.hpp index ed12df888b..02e86eb332 100644 --- a/src/libslic3r/Tesselate.hpp +++ b/src/libslic3r/Tesselate.hpp @@ -10,8 +10,12 @@ namespace Slic3r { class ExPolygon; typedef std::vector ExPolygons; -extern Pointf3s triangulate_expolygons_3df(const ExPolygon &poly, coordf_t z = 0, bool flip = false); -extern Pointf3s triangulate_expolygons_3df(const ExPolygons &polys, coordf_t z = 0, bool flip = false); +extern std::vector triangulate_expolygon_3d (const ExPolygon &poly, coordf_t z = 0, bool flip = false); +extern std::vector triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = false); +extern std::vector triangulate_expolygon_2d (const ExPolygon &poly, bool flip = false); +extern std::vector triangulate_expolygons_2d(const ExPolygons &polys, bool flip = false); +extern std::vector triangulate_expolygon_2f (const ExPolygon &poly, bool flip = false); +extern std::vector triangulate_expolygons_2f(const ExPolygons &polys, bool flip = false); } // namespace Slic3r diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index b93ce12b5b..0784b44f25 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1781,7 +1781,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating upper part"; ExPolygons section; this->make_expolygons_simple(upper_lines, §ion); - Pointf3s triangles = triangulate_expolygons_3df(section, z, true); + Pointf3s triangles = triangulate_expolygons_3d(section, z, true); stl_facet facet; facet.normal = stl_normal(0, 0, -1.f); for (size_t i = 0; i < triangles.size(); ) { @@ -1795,7 +1795,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part"; ExPolygons section; this->make_expolygons_simple(lower_lines, §ion); - Pointf3s triangles = triangulate_expolygons_3df(section, z, false); + Pointf3s triangles = triangulate_expolygons_3d(section, z, false); stl_facet facet; facet.normal = stl_normal(0, 0, -1.f); for (size_t i = 0; i < triangles.size(); ) { diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 046745e6f5..c13df45462 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -206,6 +206,69 @@ public: void reset() { closure = Closure(); } }; +// Shorten the dhms time by removing the seconds, rounding the dhm to full minutes +// and removing spaces. +static std::string short_time(const std::string &time) +{ + // Parse the dhms time format. + int days = 0; + int hours = 0; + int minutes = 0; + int seconds = 0; + if (time.find('d') != std::string::npos) + ::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds); + else if (time.find('h') != std::string::npos) + ::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds); + else if (time.find('m') != std::string::npos) + ::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds); + else if (time.find('s') != std::string::npos) + ::sscanf(time.c_str(), "%ds", &seconds); + // Round to full minutes. + if (days + hours + minutes > 0 && seconds >= 30) { + if (++minutes == 60) { + minutes = 0; + if (++hours == 24) { + hours = 0; + ++days; + } + } + } + // Format the dhm time. + char buffer[64]; + if (days > 0) + ::sprintf(buffer, "%dd%dh%dm", days, hours, minutes); + else if (hours > 0) + ::sprintf(buffer, "%dh%dm", hours, minutes); + else if (minutes > 0) + ::sprintf(buffer, "%dm", minutes); + else + ::sprintf(buffer, "%ds", seconds); + return buffer; +} + +// Returns the given time is seconds in format DDd HHh MMm SSs +static std::string get_time_dhms(float time_in_secs) +{ + int days = (int)(time_in_secs / 86400.0f); + time_in_secs -= (float)days * 86400.0f; + int hours = (int)(time_in_secs / 3600.0f); + time_in_secs -= (float)hours * 3600.0f; + int minutes = (int)(time_in_secs / 60.0f); + time_in_secs -= (float)minutes * 60.0f; + + char buffer[64]; + if (days > 0) + ::sprintf(buffer, "%dd %dh %dm %ds", days, hours, minutes, (int)time_in_secs); + else if (hours > 0) + ::sprintf(buffer, "%dh %dm %ds", hours, minutes, (int)time_in_secs); + else if (minutes > 0) + ::sprintf(buffer, "%dm %ds", minutes, (int)time_in_secs); + else + ::sprintf(buffer, "%ds", (int)time_in_secs); + + return buffer; +} + } // namespace Slic3r #if WIN32 diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 6727bb799a..4fc0324715 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -42,22 +42,27 @@ namespace Slic3r { static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error; -void set_logging_level(unsigned int level) +static boost::log::trivial::severity_level level_to_boost(unsigned level) { switch (level) { // Report fatal errors only. - case 0: logSeverity = boost::log::trivial::fatal; break; + case 0: return boost::log::trivial::fatal; // Report fatal errors and errors. - case 1: logSeverity = boost::log::trivial::error; break; + case 1: return boost::log::trivial::error; // Report fatal errors, errors and warnings. - case 2: logSeverity = boost::log::trivial::warning; break; + case 2: return boost::log::trivial::warning; // Report all errors, warnings and infos. - case 3: logSeverity = boost::log::trivial::info; break; + case 3: return boost::log::trivial::info; // Report all errors, warnings, infos and debugging. - case 4: logSeverity = boost::log::trivial::debug; break; + case 4: return boost::log::trivial::debug; // Report everyting including fine level tracing information. - default: logSeverity = boost::log::trivial::trace; break; + default: return boost::log::trivial::trace; } +} + +void set_logging_level(unsigned int level) +{ + logSeverity = level_to_boost(level); boost::log::core::get()->set_filter ( @@ -73,6 +78,7 @@ unsigned get_logging_level() case boost::log::trivial::warning : return 2; case boost::log::trivial::info : return 3; case boost::log::trivial::debug : return 4; + case boost::log::trivial::trace : return 5; default: return 1; } } @@ -88,21 +94,7 @@ static struct RunOnInit { void trace(unsigned int level, const char *message) { - boost::log::trivial::severity_level severity = boost::log::trivial::trace; - switch (level) { - // Report fatal errors only. - case 0: severity = boost::log::trivial::fatal; break; - // Report fatal errors and errors. - case 1: severity = boost::log::trivial::error; break; - // Report fatal errors, errors and warnings. - case 2: severity = boost::log::trivial::warning; break; - // Report all errors, warnings and infos. - case 3: severity = boost::log::trivial::info; break; - // Report all errors, warnings, infos and debugging. - case 4: severity = boost::log::trivial::debug; break; - // Report everyting including fine level tracing information. - default: severity = boost::log::trivial::trace; break; - } + boost::log::trivial::severity_level severity = level_to_boost(level); BOOST_LOG_STREAM_WITH_PARAMS(::boost::log::trivial::logger::get(),\ (::boost::log::keywords::severity = severity)) << message; diff --git a/src/nanosvg/nanosvg.h b/src/nanosvg/nanosvg.h new file mode 100644 index 0000000000..8c8b061cd1 --- /dev/null +++ b/src/nanosvg/nanosvg.h @@ -0,0 +1,2975 @@ +/* + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example + * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/) + * + * Arc calculation code based on canvg (https://code.google.com/p/canvg/) + * + * Bounding box calculation based on http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html + * + */ + +#ifndef NANOSVG_H +#define NANOSVG_H + +#ifndef NANOSVG_CPLUSPLUS +#ifdef __cplusplus +extern "C" { +#endif +#endif + +// NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of cubic bezier shapes. +// +// The library suits well for anything from rendering scalable icons in your editor application to prototyping a game. +// +// NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create a pull request! +// +// The shapes in the SVG images are transformed by the viewBox and converted to specified units. +// That is, you should get the same looking data as your designed in your favorite app. +// +// NanoSVG can return the paths in few different units. For example if you want to render an image, you may choose +// to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters. +// +// The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'. +// DPI (dots-per-inch) controls how the unit conversion is done. +// +// If you don't know or care about the units stuff, "px" and 96 should get you going. + + +/* Example Usage: + // Load SVG + NSVGimage* image; + image = nsvgParseFromFile("test.svg", "px", 96); + printf("size: %f x %f\n", image->width, image->height); + // Use... + for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) { + for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { + for (int i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]); + } + } + } + // Delete + nsvgDelete(image); +*/ + +enum NSVGpaintType { + NSVG_PAINT_NONE = 0, + NSVG_PAINT_COLOR = 1, + NSVG_PAINT_LINEAR_GRADIENT = 2, + NSVG_PAINT_RADIAL_GRADIENT = 3 +}; + +enum NSVGspreadType { + NSVG_SPREAD_PAD = 0, + NSVG_SPREAD_REFLECT = 1, + NSVG_SPREAD_REPEAT = 2 +}; + +enum NSVGlineJoin { + NSVG_JOIN_MITER = 0, + NSVG_JOIN_ROUND = 1, + NSVG_JOIN_BEVEL = 2 +}; + +enum NSVGlineCap { + NSVG_CAP_BUTT = 0, + NSVG_CAP_ROUND = 1, + NSVG_CAP_SQUARE = 2 +}; + +enum NSVGfillRule { + NSVG_FILLRULE_NONZERO = 0, + NSVG_FILLRULE_EVENODD = 1 +}; + +enum NSVGflags { + NSVG_FLAGS_VISIBLE = 0x01 +}; + +typedef struct NSVGgradientStop { + unsigned int color; + float offset; +} NSVGgradientStop; + +typedef struct NSVGgradient { + float xform[6]; + char spread; + float fx, fy; + int nstops; + NSVGgradientStop stops[1]; +} NSVGgradient; + +typedef struct NSVGpaint { + char type; + union { + unsigned int color; + NSVGgradient* gradient; + }; +} NSVGpaint; + +typedef struct NSVGpath +{ + float* pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... + int npts; // Total number of bezier points. + char closed; // Flag indicating if shapes should be treated as closed. + float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + struct NSVGpath* next; // Pointer to next path, or NULL if last element. +} NSVGpath; + +typedef struct NSVGshape +{ + char id[64]; // Optional 'id' attr of the shape or its group + NSVGpaint fill; // Fill paint + NSVGpaint stroke; // Stroke paint + float opacity; // Opacity of the shape. + float strokeWidth; // Stroke width (scaled). + float strokeDashOffset; // Stroke dash offset (scaled). + float strokeDashArray[8]; // Stroke dash array (scaled). + char strokeDashCount; // Number of dash values in dash array. + char strokeLineJoin; // Stroke join type. + char strokeLineCap; // Stroke cap type. + float miterLimit; // Miter limit + char fillRule; // Fill rule, see NSVGfillRule. + unsigned char flags; // Logical or of NSVG_FLAGS_* flags + float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + NSVGpath* paths; // Linked list of paths in the image. + struct NSVGshape* next; // Pointer to next shape, or NULL if last element. +} NSVGshape; + +typedef struct NSVGimage +{ + float width; // Width of the image. + float height; // Height of the image. + NSVGshape* shapes; // Linked list of shapes in the image. +} NSVGimage; + +// Parses SVG file from a file, returns SVG image as paths. +NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi); + +// Parses SVG file from a null terminated string, returns SVG image as paths. +// Important note: changes the string. +NSVGimage* nsvgParse(char* input, const char* units, float dpi); + +// Duplicates a path. +NSVGpath* nsvgDuplicatePath(NSVGpath* p); + +// Deletes an image. +void nsvgDelete(NSVGimage* image); + +#ifndef NANOSVG_CPLUSPLUS +#ifdef __cplusplus +} +#endif +#endif + +#endif // NANOSVG_H + +#ifdef NANOSVG_IMPLEMENTATION + +#include +#include +#include + +#define NSVG_PI (3.14159265358979323846264338327f) +#define NSVG_KAPPA90 (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs. + +#define NSVG_ALIGN_MIN 0 +#define NSVG_ALIGN_MID 1 +#define NSVG_ALIGN_MAX 2 +#define NSVG_ALIGN_NONE 0 +#define NSVG_ALIGN_MEET 1 +#define NSVG_ALIGN_SLICE 2 + +#define NSVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0) +#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16)) + +#ifdef _MSC_VER + #pragma warning (disable: 4996) // Switch off security warnings + #pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings + #ifdef __cplusplus + #define NSVG_INLINE inline + #else + #define NSVG_INLINE + #endif +#else + #define NSVG_INLINE inline +#endif + + +static int nsvg__isspace(char c) +{ + return strchr(" \t\n\v\f\r", c) != 0; +} + +static int nsvg__isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int nsvg__isnum(char c) +{ + return strchr("0123456789+-.eE", c) != 0; +} + +static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; } +static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; } + + +// Simple XML parser + +#define NSVG_XML_TAG 1 +#define NSVG_XML_CONTENT 2 +#define NSVG_XML_MAX_ATTRIBS 256 + +static void nsvg__parseContent(char* s, + void (*contentCb)(void* ud, const char* s), + void* ud) +{ + // Trim start white spaces + while (*s && nsvg__isspace(*s)) s++; + if (!*s) return; + + if (contentCb) + (*contentCb)(ud, s); +} + +static void nsvg__parseElement(char* s, + void (*startelCb)(void* ud, const char* el, const char** attr), + void (*endelCb)(void* ud, const char* el), + void* ud) +{ + const char* attr[NSVG_XML_MAX_ATTRIBS]; + int nattr = 0; + char* name; + int start = 0; + int end = 0; + char quote; + + // Skip white space after the '<' + while (*s && nsvg__isspace(*s)) s++; + + // Check if the tag is end tag + if (*s == '/') { + s++; + end = 1; + } else { + start = 1; + } + + // Skip comments, data and preprocessor stuff. + if (!*s || *s == '?' || *s == '!') + return; + + // Get tag name + name = s; + while (*s && !nsvg__isspace(*s)) s++; + if (*s) { *s++ = '\0'; } + + // Get attribs + while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS-3) { + char* name = NULL; + char* value = NULL; + + // Skip white space before the attrib name + while (*s && nsvg__isspace(*s)) s++; + if (!*s) break; + if (*s == '/') { + end = 1; + break; + } + name = s; + // Find end of the attrib name. + while (*s && !nsvg__isspace(*s) && *s != '=') s++; + if (*s) { *s++ = '\0'; } + // Skip until the beginning of the value. + while (*s && *s != '\"' && *s != '\'') s++; + if (!*s) break; + quote = *s; + s++; + // Store value and find the end of it. + value = s; + while (*s && *s != quote) s++; + if (*s) { *s++ = '\0'; } + + // Store only well formed attributes + if (name && value) { + attr[nattr++] = name; + attr[nattr++] = value; + } + } + + // List terminator + attr[nattr++] = 0; + attr[nattr++] = 0; + + // Call callbacks. + if (start && startelCb) + (*startelCb)(ud, name, attr); + if (end && endelCb) + (*endelCb)(ud, name); +} + +int nsvg__parseXML(char* input, + void (*startelCb)(void* ud, const char* el, const char** attr), + void (*endelCb)(void* ud, const char* el), + void (*contentCb)(void* ud, const char* s), + void* ud) +{ + char* s = input; + char* mark = s; + int state = NSVG_XML_CONTENT; + while (*s) { + if (*s == '<' && state == NSVG_XML_CONTENT) { + // Start of a tag + *s++ = '\0'; + nsvg__parseContent(mark, contentCb, ud); + mark = s; + state = NSVG_XML_TAG; + } else if (*s == '>' && state == NSVG_XML_TAG) { + // Start of a content or new tag. + *s++ = '\0'; + nsvg__parseElement(mark, startelCb, endelCb, ud); + mark = s; + state = NSVG_XML_CONTENT; + } else { + s++; + } + } + + return 1; +} + + +/* Simple SVG parser. */ + +#define NSVG_MAX_ATTR 128 + +enum NSVGgradientUnits { + NSVG_USER_SPACE = 0, + NSVG_OBJECT_SPACE = 1 +}; + +#define NSVG_MAX_DASHES 8 + +enum NSVGunits { + NSVG_UNITS_USER, + NSVG_UNITS_PX, + NSVG_UNITS_PT, + NSVG_UNITS_PC, + NSVG_UNITS_MM, + NSVG_UNITS_CM, + NSVG_UNITS_IN, + NSVG_UNITS_PERCENT, + NSVG_UNITS_EM, + NSVG_UNITS_EX +}; + +typedef struct NSVGcoordinate { + float value; + int units; +} NSVGcoordinate; + +typedef struct NSVGlinearData { + NSVGcoordinate x1, y1, x2, y2; +} NSVGlinearData; + +typedef struct NSVGradialData { + NSVGcoordinate cx, cy, r, fx, fy; +} NSVGradialData; + +typedef struct NSVGgradientData +{ + char id[64]; + char ref[64]; + char type; + union { + NSVGlinearData linear; + NSVGradialData radial; + }; + char spread; + char units; + float xform[6]; + int nstops; + NSVGgradientStop* stops; + struct NSVGgradientData* next; +} NSVGgradientData; + +typedef struct NSVGattrib +{ + char id[64]; + float xform[6]; + unsigned int fillColor; + unsigned int strokeColor; + float opacity; + float fillOpacity; + float strokeOpacity; + char fillGradient[64]; + char strokeGradient[64]; + float strokeWidth; + float strokeDashOffset; + float strokeDashArray[NSVG_MAX_DASHES]; + int strokeDashCount; + char strokeLineJoin; + char strokeLineCap; + float miterLimit; + char fillRule; + float fontSize; + unsigned int stopColor; + float stopOpacity; + float stopOffset; + char hasFill; + char hasStroke; + char visible; +} NSVGattrib; + +typedef struct NSVGparser +{ + NSVGattrib attr[NSVG_MAX_ATTR]; + int attrHead; + float* pts; + int npts; + int cpts; + NSVGpath* plist; + NSVGimage* image; + NSVGgradientData* gradients; + NSVGshape* shapesTail; + float viewMinx, viewMiny, viewWidth, viewHeight; + int alignX, alignY, alignType; + float dpi; + char pathFlag; + char defsFlag; +} NSVGparser; + +static void nsvg__xformIdentity(float* t) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = 0.0f; t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetTranslation(float* t, float tx, float ty) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = 0.0f; t[3] = 1.0f; + t[4] = tx; t[5] = ty; +} + +static void nsvg__xformSetScale(float* t, float sx, float sy) +{ + t[0] = sx; t[1] = 0.0f; + t[2] = 0.0f; t[3] = sy; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetSkewX(float* t, float a) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = tanf(a); t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetSkewY(float* t, float a) +{ + t[0] = 1.0f; t[1] = tanf(a); + t[2] = 0.0f; t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetRotation(float* t, float a) +{ + float cs = cosf(a), sn = sinf(a); + t[0] = cs; t[1] = sn; + t[2] = -sn; t[3] = cs; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformMultiply(float* t, float* s) +{ + float t0 = t[0] * s[0] + t[1] * s[2]; + float t2 = t[2] * s[0] + t[3] * s[2]; + float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; + t[1] = t[0] * s[1] + t[1] * s[3]; + t[3] = t[2] * s[1] + t[3] * s[3]; + t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; + t[0] = t0; + t[2] = t2; + t[4] = t4; +} + +static void nsvg__xformInverse(float* inv, float* t) +{ + double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1]; + if (det > -1e-6 && det < 1e-6) { + nsvg__xformIdentity(t); + return; + } + invdet = 1.0 / det; + inv[0] = (float)(t[3] * invdet); + inv[2] = (float)(-t[2] * invdet); + inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); + inv[1] = (float)(-t[1] * invdet); + inv[3] = (float)(t[0] * invdet); + inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); +} + +static void nsvg__xformPremultiply(float* t, float* s) +{ + float s2[6]; + memcpy(s2, s, sizeof(float)*6); + nsvg__xformMultiply(s2, t); + memcpy(t, s2, sizeof(float)*6); +} + +static void nsvg__xformPoint(float* dx, float* dy, float x, float y, float* t) +{ + *dx = x*t[0] + y*t[2] + t[4]; + *dy = x*t[1] + y*t[3] + t[5]; +} + +static void nsvg__xformVec(float* dx, float* dy, float x, float y, float* t) +{ + *dx = x*t[0] + y*t[2]; + *dy = x*t[1] + y*t[3]; +} + +#define NSVG_EPSILON (1e-12) + +static int nsvg__ptInBounds(float* pt, float* bounds) +{ + return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3]; +} + + +static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3) +{ + double it = 1.0-t; + return it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3; +} + +static void nsvg__curveBounds(float* bounds, float* curve) +{ + int i, j, count; + double roots[2], a, b, c, b2ac, t, v; + float* v0 = &curve[0]; + float* v1 = &curve[2]; + float* v2 = &curve[4]; + float* v3 = &curve[6]; + + // Start the bounding box by end points + bounds[0] = nsvg__minf(v0[0], v3[0]); + bounds[1] = nsvg__minf(v0[1], v3[1]); + bounds[2] = nsvg__maxf(v0[0], v3[0]); + bounds[3] = nsvg__maxf(v0[1], v3[1]); + + // Bezier curve fits inside the convex hull of it's control points. + // If control points are inside the bounds, we're done. + if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds)) + return; + + // Add bezier curve inflection points in X and Y. + for (i = 0; i < 2; i++) { + a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i]; + b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i]; + c = 3.0 * v1[i] - 3.0 * v0[i]; + count = 0; + if (fabs(a) < NSVG_EPSILON) { + if (fabs(b) > NSVG_EPSILON) { + t = -c / b; + if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) + roots[count++] = t; + } + } else { + b2ac = b*b - 4.0*c*a; + if (b2ac > NSVG_EPSILON) { + t = (-b + sqrt(b2ac)) / (2.0 * a); + if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) + roots[count++] = t; + t = (-b - sqrt(b2ac)) / (2.0 * a); + if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) + roots[count++] = t; + } + } + for (j = 0; j < count; j++) { + v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]); + bounds[0+i] = nsvg__minf(bounds[0+i], (float)v); + bounds[2+i] = nsvg__maxf(bounds[2+i], (float)v); + } + } +} + +static NSVGparser* nsvg__createParser() +{ + NSVGparser* p; + p = (NSVGparser*)malloc(sizeof(NSVGparser)); + if (p == NULL) goto error; + memset(p, 0, sizeof(NSVGparser)); + + p->image = (NSVGimage*)malloc(sizeof(NSVGimage)); + if (p->image == NULL) goto error; + memset(p->image, 0, sizeof(NSVGimage)); + + // Init style + nsvg__xformIdentity(p->attr[0].xform); + memset(p->attr[0].id, 0, sizeof p->attr[0].id); + p->attr[0].fillColor = NSVG_RGB(0,0,0); + p->attr[0].strokeColor = NSVG_RGB(0,0,0); + p->attr[0].opacity = 1; + p->attr[0].fillOpacity = 1; + p->attr[0].strokeOpacity = 1; + p->attr[0].stopOpacity = 1; + p->attr[0].strokeWidth = 1; + p->attr[0].strokeLineJoin = NSVG_JOIN_MITER; + p->attr[0].strokeLineCap = NSVG_CAP_BUTT; + p->attr[0].miterLimit = 4; + p->attr[0].fillRule = NSVG_FILLRULE_NONZERO; + p->attr[0].hasFill = 1; + p->attr[0].visible = 1; + + return p; + +error: + if (p) { + if (p->image) free(p->image); + free(p); + } + return NULL; +} + +static void nsvg__deletePaths(NSVGpath* path) +{ + while (path) { + NSVGpath *next = path->next; + if (path->pts != NULL) + free(path->pts); + free(path); + path = next; + } +} + +static void nsvg__deletePaint(NSVGpaint* paint) +{ + if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT) + free(paint->gradient); +} + +static void nsvg__deleteGradientData(NSVGgradientData* grad) +{ + NSVGgradientData* next; + while (grad != NULL) { + next = grad->next; + free(grad->stops); + free(grad); + grad = next; + } +} + +static void nsvg__deleteParser(NSVGparser* p) +{ + if (p != NULL) { + nsvg__deletePaths(p->plist); + nsvg__deleteGradientData(p->gradients); + nsvgDelete(p->image); + free(p->pts); + free(p); + } +} + +static void nsvg__resetPath(NSVGparser* p) +{ + p->npts = 0; +} + +static void nsvg__addPoint(NSVGparser* p, float x, float y) +{ + if (p->npts+1 > p->cpts) { + p->cpts = p->cpts ? p->cpts*2 : 8; + p->pts = (float*)realloc(p->pts, p->cpts*2*sizeof(float)); + if (!p->pts) return; + } + p->pts[p->npts*2+0] = x; + p->pts[p->npts*2+1] = y; + p->npts++; +} + +static void nsvg__moveTo(NSVGparser* p, float x, float y) +{ + if (p->npts > 0) { + p->pts[(p->npts-1)*2+0] = x; + p->pts[(p->npts-1)*2+1] = y; + } else { + nsvg__addPoint(p, x, y); + } +} + +static void nsvg__lineTo(NSVGparser* p, float x, float y) +{ + float px,py, dx,dy; + if (p->npts > 0) { + px = p->pts[(p->npts-1)*2+0]; + py = p->pts[(p->npts-1)*2+1]; + dx = x - px; + dy = y - py; + nsvg__addPoint(p, px + dx/3.0f, py + dy/3.0f); + nsvg__addPoint(p, x - dx/3.0f, y - dy/3.0f); + nsvg__addPoint(p, x, y); + } +} + +static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) +{ + nsvg__addPoint(p, cpx1, cpy1); + nsvg__addPoint(p, cpx2, cpy2); + nsvg__addPoint(p, x, y); +} + +static NSVGattrib* nsvg__getAttr(NSVGparser* p) +{ + return &p->attr[p->attrHead]; +} + +static void nsvg__pushAttr(NSVGparser* p) +{ + if (p->attrHead < NSVG_MAX_ATTR-1) { + p->attrHead++; + memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead-1], sizeof(NSVGattrib)); + } +} + +static void nsvg__popAttr(NSVGparser* p) +{ + if (p->attrHead > 0) + p->attrHead--; +} + +static float nsvg__actualOrigX(NSVGparser* p) +{ + return p->viewMinx; +} + +static float nsvg__actualOrigY(NSVGparser* p) +{ + return p->viewMiny; +} + +static float nsvg__actualWidth(NSVGparser* p) +{ + return p->viewWidth; +} + +static float nsvg__actualHeight(NSVGparser* p) +{ + return p->viewHeight; +} + +static float nsvg__actualLength(NSVGparser* p) +{ + float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p); + return sqrtf(w*w + h*h) / sqrtf(2.0f); +} + +static float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig, float length) +{ + NSVGattrib* attr = nsvg__getAttr(p); + switch (c.units) { + case NSVG_UNITS_USER: return c.value; + case NSVG_UNITS_PX: return c.value; + case NSVG_UNITS_PT: return c.value / 72.0f * p->dpi; + case NSVG_UNITS_PC: return c.value / 6.0f * p->dpi; + case NSVG_UNITS_MM: return c.value / 25.4f * p->dpi; + case NSVG_UNITS_CM: return c.value / 2.54f * p->dpi; + case NSVG_UNITS_IN: return c.value * p->dpi; + case NSVG_UNITS_EM: return c.value * attr->fontSize; + case NSVG_UNITS_EX: return c.value * attr->fontSize * 0.52f; // x-height of Helvetica. + case NSVG_UNITS_PERCENT: return orig + c.value / 100.0f * length; + default: return c.value; + } + return c.value; +} + +static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id) +{ + NSVGgradientData* grad = p->gradients; + while (grad) { + if (strcmp(grad->id, id) == 0) + return grad; + grad = grad->next; + } + return NULL; +} + +static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, char* paintType) +{ + NSVGattrib* attr = nsvg__getAttr(p); + NSVGgradientData* data = NULL; + NSVGgradientData* ref = NULL; + NSVGgradientStop* stops = NULL; + NSVGgradient* grad; + float ox, oy, sw, sh, sl; + int nstops = 0; + + data = nsvg__findGradientData(p, id); + if (data == NULL) return NULL; + + // TODO: use ref to fill in all unset values too. + ref = data; + while (ref != NULL) { + if (stops == NULL && ref->stops != NULL) { + stops = ref->stops; + nstops = ref->nstops; + break; + } + ref = nsvg__findGradientData(p, ref->ref); + } + if (stops == NULL) return NULL; + + grad = (NSVGgradient*)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(nstops-1)); + if (grad == NULL) return NULL; + + // The shape width and height. + if (data->units == NSVG_OBJECT_SPACE) { + ox = localBounds[0]; + oy = localBounds[1]; + sw = localBounds[2] - localBounds[0]; + sh = localBounds[3] - localBounds[1]; + } else { + ox = nsvg__actualOrigX(p); + oy = nsvg__actualOrigY(p); + sw = nsvg__actualWidth(p); + sh = nsvg__actualHeight(p); + } + sl = sqrtf(sw*sw + sh*sh) / sqrtf(2.0f); + + if (data->type == NSVG_PAINT_LINEAR_GRADIENT) { + float x1, y1, x2, y2, dx, dy; + x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw); + y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh); + x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw); + y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh); + // Calculate transform aligned to the line + dx = x2 - x1; + dy = y2 - y1; + grad->xform[0] = dy; grad->xform[1] = -dx; + grad->xform[2] = dx; grad->xform[3] = dy; + grad->xform[4] = x1; grad->xform[5] = y1; + } else { + float cx, cy, fx, fy, r; + cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw); + cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh); + fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw); + fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh); + r = nsvg__convertToPixels(p, data->radial.r, 0, sl); + // Calculate transform aligned to the circle + grad->xform[0] = r; grad->xform[1] = 0; + grad->xform[2] = 0; grad->xform[3] = r; + grad->xform[4] = cx; grad->xform[5] = cy; + grad->fx = fx / r; + grad->fy = fy / r; + } + + nsvg__xformMultiply(grad->xform, data->xform); + nsvg__xformMultiply(grad->xform, attr->xform); + + grad->spread = data->spread; + memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop)); + grad->nstops = nstops; + + *paintType = data->type; + + return grad; +} + +static float nsvg__getAverageScale(float* t) +{ + float sx = sqrtf(t[0]*t[0] + t[2]*t[2]); + float sy = sqrtf(t[1]*t[1] + t[3]*t[3]); + return (sx + sy) * 0.5f; +} + +static void nsvg__getLocalBounds(float* bounds, NSVGshape *shape, float* xform) +{ + NSVGpath* path; + float curve[4*2], curveBounds[4]; + int i, first = 1; + for (path = shape->paths; path != NULL; path = path->next) { + nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform); + for (i = 0; i < path->npts-1; i += 3) { + nsvg__xformPoint(&curve[2], &curve[3], path->pts[(i+1)*2], path->pts[(i+1)*2+1], xform); + nsvg__xformPoint(&curve[4], &curve[5], path->pts[(i+2)*2], path->pts[(i+2)*2+1], xform); + nsvg__xformPoint(&curve[6], &curve[7], path->pts[(i+3)*2], path->pts[(i+3)*2+1], xform); + nsvg__curveBounds(curveBounds, curve); + if (first) { + bounds[0] = curveBounds[0]; + bounds[1] = curveBounds[1]; + bounds[2] = curveBounds[2]; + bounds[3] = curveBounds[3]; + first = 0; + } else { + bounds[0] = nsvg__minf(bounds[0], curveBounds[0]); + bounds[1] = nsvg__minf(bounds[1], curveBounds[1]); + bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]); + bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]); + } + curve[0] = curve[6]; + curve[1] = curve[7]; + } + } +} + +static void nsvg__addShape(NSVGparser* p) +{ + NSVGattrib* attr = nsvg__getAttr(p); + float scale = 1.0f; + NSVGshape* shape; + NSVGpath* path; + int i; + + if (p->plist == NULL) + return; + + shape = (NSVGshape*)malloc(sizeof(NSVGshape)); + if (shape == NULL) goto error; + memset(shape, 0, sizeof(NSVGshape)); + + memcpy(shape->id, attr->id, sizeof shape->id); + scale = nsvg__getAverageScale(attr->xform); + shape->strokeWidth = attr->strokeWidth * scale; + shape->strokeDashOffset = attr->strokeDashOffset * scale; + shape->strokeDashCount = (char)attr->strokeDashCount; + for (i = 0; i < attr->strokeDashCount; i++) + shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale; + shape->strokeLineJoin = attr->strokeLineJoin; + shape->strokeLineCap = attr->strokeLineCap; + shape->miterLimit = attr->miterLimit; + shape->fillRule = attr->fillRule; + shape->opacity = attr->opacity; + + shape->paths = p->plist; + p->plist = NULL; + + // Calculate shape bounds + shape->bounds[0] = shape->paths->bounds[0]; + shape->bounds[1] = shape->paths->bounds[1]; + shape->bounds[2] = shape->paths->bounds[2]; + shape->bounds[3] = shape->paths->bounds[3]; + for (path = shape->paths->next; path != NULL; path = path->next) { + shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]); + shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]); + shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]); + shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]); + } + + // Set fill + if (attr->hasFill == 0) { + shape->fill.type = NSVG_PAINT_NONE; + } else if (attr->hasFill == 1) { + shape->fill.type = NSVG_PAINT_COLOR; + shape->fill.color = attr->fillColor; + shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24; + } else if (attr->hasFill == 2) { + float inv[6], localBounds[4]; + nsvg__xformInverse(inv, attr->xform); + nsvg__getLocalBounds(localBounds, shape, inv); + shape->fill.gradient = nsvg__createGradient(p, attr->fillGradient, localBounds, &shape->fill.type); + if (shape->fill.gradient == NULL) { + shape->fill.type = NSVG_PAINT_NONE; + } + } + + // Set stroke + if (attr->hasStroke == 0) { + shape->stroke.type = NSVG_PAINT_NONE; + } else if (attr->hasStroke == 1) { + shape->stroke.type = NSVG_PAINT_COLOR; + shape->stroke.color = attr->strokeColor; + shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24; + } else if (attr->hasStroke == 2) { + float inv[6], localBounds[4]; + nsvg__xformInverse(inv, attr->xform); + nsvg__getLocalBounds(localBounds, shape, inv); + shape->stroke.gradient = nsvg__createGradient(p, attr->strokeGradient, localBounds, &shape->stroke.type); + if (shape->stroke.gradient == NULL) + shape->stroke.type = NSVG_PAINT_NONE; + } + + // Set flags + shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00); + + // Add to tail + if (p->image->shapes == NULL) + p->image->shapes = shape; + else + p->shapesTail->next = shape; + p->shapesTail = shape; + + return; + +error: + if (shape) free(shape); +} + +static void nsvg__addPath(NSVGparser* p, char closed) +{ + NSVGattrib* attr = nsvg__getAttr(p); + NSVGpath* path = NULL; + float bounds[4]; + float* curve; + int i; + + if (p->npts < 4) + return; + + if (closed) + nsvg__lineTo(p, p->pts[0], p->pts[1]); + + path = (NSVGpath*)malloc(sizeof(NSVGpath)); + if (path == NULL) goto error; + memset(path, 0, sizeof(NSVGpath)); + + path->pts = (float*)malloc(p->npts*2*sizeof(float)); + if (path->pts == NULL) goto error; + path->closed = closed; + path->npts = p->npts; + + // Transform path. + for (i = 0; i < p->npts; ++i) + nsvg__xformPoint(&path->pts[i*2], &path->pts[i*2+1], p->pts[i*2], p->pts[i*2+1], attr->xform); + + // Find bounds + for (i = 0; i < path->npts-1; i += 3) { + curve = &path->pts[i*2]; + nsvg__curveBounds(bounds, curve); + if (i == 0) { + path->bounds[0] = bounds[0]; + path->bounds[1] = bounds[1]; + path->bounds[2] = bounds[2]; + path->bounds[3] = bounds[3]; + } else { + path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]); + path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]); + path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]); + path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]); + } + } + + path->next = p->plist; + p->plist = path; + + return; + +error: + if (path != NULL) { + if (path->pts != NULL) free(path->pts); + free(path); + } +} + +// We roll our own string to float because the std library one uses locale and messes things up. +static double nsvg__atof(const char* s) +{ + char* cur = (char*)s; + char* end = NULL; + double res = 0.0, sign = 1.0; + long long intPart = 0, fracPart = 0; + char hasIntPart = 0, hasFracPart = 0; + + // Parse optional sign + if (*cur == '+') { + cur++; + } else if (*cur == '-') { + sign = -1; + cur++; + } + + // Parse integer part + if (nsvg__isdigit(*cur)) { + // Parse digit sequence + intPart = (double)strtoll(cur, &end, 10); + if (cur != end) { + res = (double)intPart; + hasIntPart = 1; + cur = end; + } + } + + // Parse fractional part. + if (*cur == '.') { + cur++; // Skip '.' + if (nsvg__isdigit(*cur)) { + // Parse digit sequence + fracPart = strtoll(cur, &end, 10); + if (cur != end) { + res += (double)fracPart / pow(10.0, (double)(end - cur)); + hasFracPart = 1; + cur = end; + } + } + } + + // A valid number should have integer or fractional part. + if (!hasIntPart && !hasFracPart) + return 0.0; + + // Parse optional exponent + if (*cur == 'e' || *cur == 'E') { + int expPart = 0; + cur++; // skip 'E' + expPart = strtol(cur, &end, 10); // Parse digit sequence with sign + if (cur != end) { + res *= pow(10.0, (double)expPart); + } + } + + return res * sign; +} + + +static const char* nsvg__parseNumber(const char* s, char* it, const int size) +{ + const int last = size-1; + int i = 0; + + // sign + if (*s == '-' || *s == '+') { + if (i < last) it[i++] = *s; + s++; + } + // integer part + while (*s && nsvg__isdigit(*s)) { + if (i < last) it[i++] = *s; + s++; + } + if (*s == '.') { + // decimal point + if (i < last) it[i++] = *s; + s++; + // fraction part + while (*s && nsvg__isdigit(*s)) { + if (i < last) it[i++] = *s; + s++; + } + } + // exponent + if (*s == 'e' || *s == 'E') { + if (i < last) it[i++] = *s; + s++; + if (*s == '-' || *s == '+') { + if (i < last) it[i++] = *s; + s++; + } + while (*s && nsvg__isdigit(*s)) { + if (i < last) it[i++] = *s; + s++; + } + } + it[i] = '\0'; + + return s; +} + +static const char* nsvg__getNextPathItem(const char* s, char* it) +{ + it[0] = '\0'; + // Skip white spaces and commas + while (*s && (nsvg__isspace(*s) || *s == ',')) s++; + if (!*s) return s; + if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) { + s = nsvg__parseNumber(s, it, 64); + } else { + // Parse command + it[0] = *s++; + it[1] = '\0'; + return s; + } + + return s; +} + +static unsigned int nsvg__parseColorHex(const char* str) +{ + unsigned int c = 0, r = 0, g = 0, b = 0; + int n = 0; + str++; // skip # + // Calculate number of characters. + while(str[n] && !nsvg__isspace(str[n])) + n++; + if (n == 6) { + sscanf(str, "%x", &c); + } else if (n == 3) { + sscanf(str, "%x", &c); + c = (c&0xf) | ((c&0xf0) << 4) | ((c&0xf00) << 8); + c |= c<<4; + } + r = (c >> 16) & 0xff; + g = (c >> 8) & 0xff; + b = c & 0xff; + return NSVG_RGB(r,g,b); +} + +static unsigned int nsvg__parseColorRGB(const char* str) +{ + int r = -1, g = -1, b = -1; + char s1[32]="", s2[32]=""; + sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b); + if (strchr(s1, '%')) { + return NSVG_RGB((r*255)/100,(g*255)/100,(b*255)/100); + } else { + return NSVG_RGB(r,g,b); + } +} + +typedef struct NSVGNamedColor { + const char* name; + unsigned int color; +} NSVGNamedColor; + +NSVGNamedColor nsvg__colors[] = { + + { "red", NSVG_RGB(255, 0, 0) }, + { "green", NSVG_RGB( 0, 128, 0) }, + { "blue", NSVG_RGB( 0, 0, 255) }, + { "yellow", NSVG_RGB(255, 255, 0) }, + { "cyan", NSVG_RGB( 0, 255, 255) }, + { "magenta", NSVG_RGB(255, 0, 255) }, + { "black", NSVG_RGB( 0, 0, 0) }, + { "grey", NSVG_RGB(128, 128, 128) }, + { "gray", NSVG_RGB(128, 128, 128) }, + { "white", NSVG_RGB(255, 255, 255) }, + +#ifdef NANOSVG_ALL_COLOR_KEYWORDS + { "aliceblue", NSVG_RGB(240, 248, 255) }, + { "antiquewhite", NSVG_RGB(250, 235, 215) }, + { "aqua", NSVG_RGB( 0, 255, 255) }, + { "aquamarine", NSVG_RGB(127, 255, 212) }, + { "azure", NSVG_RGB(240, 255, 255) }, + { "beige", NSVG_RGB(245, 245, 220) }, + { "bisque", NSVG_RGB(255, 228, 196) }, + { "blanchedalmond", NSVG_RGB(255, 235, 205) }, + { "blueviolet", NSVG_RGB(138, 43, 226) }, + { "brown", NSVG_RGB(165, 42, 42) }, + { "burlywood", NSVG_RGB(222, 184, 135) }, + { "cadetblue", NSVG_RGB( 95, 158, 160) }, + { "chartreuse", NSVG_RGB(127, 255, 0) }, + { "chocolate", NSVG_RGB(210, 105, 30) }, + { "coral", NSVG_RGB(255, 127, 80) }, + { "cornflowerblue", NSVG_RGB(100, 149, 237) }, + { "cornsilk", NSVG_RGB(255, 248, 220) }, + { "crimson", NSVG_RGB(220, 20, 60) }, + { "darkblue", NSVG_RGB( 0, 0, 139) }, + { "darkcyan", NSVG_RGB( 0, 139, 139) }, + { "darkgoldenrod", NSVG_RGB(184, 134, 11) }, + { "darkgray", NSVG_RGB(169, 169, 169) }, + { "darkgreen", NSVG_RGB( 0, 100, 0) }, + { "darkgrey", NSVG_RGB(169, 169, 169) }, + { "darkkhaki", NSVG_RGB(189, 183, 107) }, + { "darkmagenta", NSVG_RGB(139, 0, 139) }, + { "darkolivegreen", NSVG_RGB( 85, 107, 47) }, + { "darkorange", NSVG_RGB(255, 140, 0) }, + { "darkorchid", NSVG_RGB(153, 50, 204) }, + { "darkred", NSVG_RGB(139, 0, 0) }, + { "darksalmon", NSVG_RGB(233, 150, 122) }, + { "darkseagreen", NSVG_RGB(143, 188, 143) }, + { "darkslateblue", NSVG_RGB( 72, 61, 139) }, + { "darkslategray", NSVG_RGB( 47, 79, 79) }, + { "darkslategrey", NSVG_RGB( 47, 79, 79) }, + { "darkturquoise", NSVG_RGB( 0, 206, 209) }, + { "darkviolet", NSVG_RGB(148, 0, 211) }, + { "deeppink", NSVG_RGB(255, 20, 147) }, + { "deepskyblue", NSVG_RGB( 0, 191, 255) }, + { "dimgray", NSVG_RGB(105, 105, 105) }, + { "dimgrey", NSVG_RGB(105, 105, 105) }, + { "dodgerblue", NSVG_RGB( 30, 144, 255) }, + { "firebrick", NSVG_RGB(178, 34, 34) }, + { "floralwhite", NSVG_RGB(255, 250, 240) }, + { "forestgreen", NSVG_RGB( 34, 139, 34) }, + { "fuchsia", NSVG_RGB(255, 0, 255) }, + { "gainsboro", NSVG_RGB(220, 220, 220) }, + { "ghostwhite", NSVG_RGB(248, 248, 255) }, + { "gold", NSVG_RGB(255, 215, 0) }, + { "goldenrod", NSVG_RGB(218, 165, 32) }, + { "greenyellow", NSVG_RGB(173, 255, 47) }, + { "honeydew", NSVG_RGB(240, 255, 240) }, + { "hotpink", NSVG_RGB(255, 105, 180) }, + { "indianred", NSVG_RGB(205, 92, 92) }, + { "indigo", NSVG_RGB( 75, 0, 130) }, + { "ivory", NSVG_RGB(255, 255, 240) }, + { "khaki", NSVG_RGB(240, 230, 140) }, + { "lavender", NSVG_RGB(230, 230, 250) }, + { "lavenderblush", NSVG_RGB(255, 240, 245) }, + { "lawngreen", NSVG_RGB(124, 252, 0) }, + { "lemonchiffon", NSVG_RGB(255, 250, 205) }, + { "lightblue", NSVG_RGB(173, 216, 230) }, + { "lightcoral", NSVG_RGB(240, 128, 128) }, + { "lightcyan", NSVG_RGB(224, 255, 255) }, + { "lightgoldenrodyellow", NSVG_RGB(250, 250, 210) }, + { "lightgray", NSVG_RGB(211, 211, 211) }, + { "lightgreen", NSVG_RGB(144, 238, 144) }, + { "lightgrey", NSVG_RGB(211, 211, 211) }, + { "lightpink", NSVG_RGB(255, 182, 193) }, + { "lightsalmon", NSVG_RGB(255, 160, 122) }, + { "lightseagreen", NSVG_RGB( 32, 178, 170) }, + { "lightskyblue", NSVG_RGB(135, 206, 250) }, + { "lightslategray", NSVG_RGB(119, 136, 153) }, + { "lightslategrey", NSVG_RGB(119, 136, 153) }, + { "lightsteelblue", NSVG_RGB(176, 196, 222) }, + { "lightyellow", NSVG_RGB(255, 255, 224) }, + { "lime", NSVG_RGB( 0, 255, 0) }, + { "limegreen", NSVG_RGB( 50, 205, 50) }, + { "linen", NSVG_RGB(250, 240, 230) }, + { "maroon", NSVG_RGB(128, 0, 0) }, + { "mediumaquamarine", NSVG_RGB(102, 205, 170) }, + { "mediumblue", NSVG_RGB( 0, 0, 205) }, + { "mediumorchid", NSVG_RGB(186, 85, 211) }, + { "mediumpurple", NSVG_RGB(147, 112, 219) }, + { "mediumseagreen", NSVG_RGB( 60, 179, 113) }, + { "mediumslateblue", NSVG_RGB(123, 104, 238) }, + { "mediumspringgreen", NSVG_RGB( 0, 250, 154) }, + { "mediumturquoise", NSVG_RGB( 72, 209, 204) }, + { "mediumvioletred", NSVG_RGB(199, 21, 133) }, + { "midnightblue", NSVG_RGB( 25, 25, 112) }, + { "mintcream", NSVG_RGB(245, 255, 250) }, + { "mistyrose", NSVG_RGB(255, 228, 225) }, + { "moccasin", NSVG_RGB(255, 228, 181) }, + { "navajowhite", NSVG_RGB(255, 222, 173) }, + { "navy", NSVG_RGB( 0, 0, 128) }, + { "oldlace", NSVG_RGB(253, 245, 230) }, + { "olive", NSVG_RGB(128, 128, 0) }, + { "olivedrab", NSVG_RGB(107, 142, 35) }, + { "orange", NSVG_RGB(255, 165, 0) }, + { "orangered", NSVG_RGB(255, 69, 0) }, + { "orchid", NSVG_RGB(218, 112, 214) }, + { "palegoldenrod", NSVG_RGB(238, 232, 170) }, + { "palegreen", NSVG_RGB(152, 251, 152) }, + { "paleturquoise", NSVG_RGB(175, 238, 238) }, + { "palevioletred", NSVG_RGB(219, 112, 147) }, + { "papayawhip", NSVG_RGB(255, 239, 213) }, + { "peachpuff", NSVG_RGB(255, 218, 185) }, + { "peru", NSVG_RGB(205, 133, 63) }, + { "pink", NSVG_RGB(255, 192, 203) }, + { "plum", NSVG_RGB(221, 160, 221) }, + { "powderblue", NSVG_RGB(176, 224, 230) }, + { "purple", NSVG_RGB(128, 0, 128) }, + { "rosybrown", NSVG_RGB(188, 143, 143) }, + { "royalblue", NSVG_RGB( 65, 105, 225) }, + { "saddlebrown", NSVG_RGB(139, 69, 19) }, + { "salmon", NSVG_RGB(250, 128, 114) }, + { "sandybrown", NSVG_RGB(244, 164, 96) }, + { "seagreen", NSVG_RGB( 46, 139, 87) }, + { "seashell", NSVG_RGB(255, 245, 238) }, + { "sienna", NSVG_RGB(160, 82, 45) }, + { "silver", NSVG_RGB(192, 192, 192) }, + { "skyblue", NSVG_RGB(135, 206, 235) }, + { "slateblue", NSVG_RGB(106, 90, 205) }, + { "slategray", NSVG_RGB(112, 128, 144) }, + { "slategrey", NSVG_RGB(112, 128, 144) }, + { "snow", NSVG_RGB(255, 250, 250) }, + { "springgreen", NSVG_RGB( 0, 255, 127) }, + { "steelblue", NSVG_RGB( 70, 130, 180) }, + { "tan", NSVG_RGB(210, 180, 140) }, + { "teal", NSVG_RGB( 0, 128, 128) }, + { "thistle", NSVG_RGB(216, 191, 216) }, + { "tomato", NSVG_RGB(255, 99, 71) }, + { "turquoise", NSVG_RGB( 64, 224, 208) }, + { "violet", NSVG_RGB(238, 130, 238) }, + { "wheat", NSVG_RGB(245, 222, 179) }, + { "whitesmoke", NSVG_RGB(245, 245, 245) }, + { "yellowgreen", NSVG_RGB(154, 205, 50) }, +#endif +}; + +static unsigned int nsvg__parseColorName(const char* str) +{ + int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor); + + for (i = 0; i < ncolors; i++) { + if (strcmp(nsvg__colors[i].name, str) == 0) { + return nsvg__colors[i].color; + } + } + + return NSVG_RGB(128, 128, 128); +} + +static unsigned int nsvg__parseColor(const char* str) +{ + size_t len = 0; + while(*str == ' ') ++str; + len = strlen(str); + if (len >= 1 && *str == '#') + return nsvg__parseColorHex(str); + else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(') + return nsvg__parseColorRGB(str); + return nsvg__parseColorName(str); +} + +static float nsvg__parseOpacity(const char* str) +{ + float val = nsvg__atof(str); + if (val < 0.0f) val = 0.0f; + if (val > 1.0f) val = 1.0f; + return val; +} + +static float nsvg__parseMiterLimit(const char* str) +{ + float val = nsvg__atof(str); + if (val < 0.0f) val = 0.0f; + return val; +} + +static int nsvg__parseUnits(const char* units) +{ + if (units[0] == 'p' && units[1] == 'x') + return NSVG_UNITS_PX; + else if (units[0] == 'p' && units[1] == 't') + return NSVG_UNITS_PT; + else if (units[0] == 'p' && units[1] == 'c') + return NSVG_UNITS_PC; + else if (units[0] == 'm' && units[1] == 'm') + return NSVG_UNITS_MM; + else if (units[0] == 'c' && units[1] == 'm') + return NSVG_UNITS_CM; + else if (units[0] == 'i' && units[1] == 'n') + return NSVG_UNITS_IN; + else if (units[0] == '%') + return NSVG_UNITS_PERCENT; + else if (units[0] == 'e' && units[1] == 'm') + return NSVG_UNITS_EM; + else if (units[0] == 'e' && units[1] == 'x') + return NSVG_UNITS_EX; + return NSVG_UNITS_USER; +} + +static NSVGcoordinate nsvg__parseCoordinateRaw(const char* str) +{ + NSVGcoordinate coord = {0, NSVG_UNITS_USER}; + char buf[64]; + coord.units = nsvg__parseUnits(nsvg__parseNumber(str, buf, 64)); + coord.value = nsvg__atof(buf); + return coord; +} + +static NSVGcoordinate nsvg__coord(float v, int units) +{ + NSVGcoordinate coord = {v, units}; + return coord; +} + +static float nsvg__parseCoordinate(NSVGparser* p, const char* str, float orig, float length) +{ + NSVGcoordinate coord = nsvg__parseCoordinateRaw(str); + return nsvg__convertToPixels(p, coord, orig, length); +} + +static int nsvg__parseTransformArgs(const char* str, float* args, int maxNa, int* na) +{ + const char* end; + const char* ptr; + char it[64]; + + *na = 0; + ptr = str; + while (*ptr && *ptr != '(') ++ptr; + if (*ptr == 0) + return 1; + end = ptr; + while (*end && *end != ')') ++end; + if (*end == 0) + return 1; + + while (ptr < end) { + if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) { + if (*na >= maxNa) return 0; + ptr = nsvg__parseNumber(ptr, it, 64); + args[(*na)++] = (float)nsvg__atof(it); + } else { + ++ptr; + } + } + return (int)(end - str); +} + + +static int nsvg__parseMatrix(float* xform, const char* str) +{ + float t[6]; + int na = 0; + int len = nsvg__parseTransformArgs(str, t, 6, &na); + if (na != 6) return len; + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseTranslate(float* xform, const char* str) +{ + float args[2]; + float t[6]; + int na = 0; + int len = nsvg__parseTransformArgs(str, args, 2, &na); + if (na == 1) args[1] = 0.0; + + nsvg__xformSetTranslation(t, args[0], args[1]); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseScale(float* xform, const char* str) +{ + float args[2]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 2, &na); + if (na == 1) args[1] = args[0]; + nsvg__xformSetScale(t, args[0], args[1]); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseSkewX(float* xform, const char* str) +{ + float args[1]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 1, &na); + nsvg__xformSetSkewX(t, args[0]/180.0f*NSVG_PI); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseSkewY(float* xform, const char* str) +{ + float args[1]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 1, &na); + nsvg__xformSetSkewY(t, args[0]/180.0f*NSVG_PI); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseRotate(float* xform, const char* str) +{ + float args[3]; + int na = 0; + float m[6]; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 3, &na); + if (na == 1) + args[1] = args[2] = 0.0f; + nsvg__xformIdentity(m); + + if (na > 1) { + nsvg__xformSetTranslation(t, -args[1], -args[2]); + nsvg__xformMultiply(m, t); + } + + nsvg__xformSetRotation(t, args[0]/180.0f*NSVG_PI); + nsvg__xformMultiply(m, t); + + if (na > 1) { + nsvg__xformSetTranslation(t, args[1], args[2]); + nsvg__xformMultiply(m, t); + } + + memcpy(xform, m, sizeof(float)*6); + + return len; +} + +static void nsvg__parseTransform(float* xform, const char* str) +{ + float t[6]; + nsvg__xformIdentity(xform); + while (*str) + { + if (strncmp(str, "matrix", 6) == 0) + str += nsvg__parseMatrix(t, str); + else if (strncmp(str, "translate", 9) == 0) + str += nsvg__parseTranslate(t, str); + else if (strncmp(str, "scale", 5) == 0) + str += nsvg__parseScale(t, str); + else if (strncmp(str, "rotate", 6) == 0) + str += nsvg__parseRotate(t, str); + else if (strncmp(str, "skewX", 5) == 0) + str += nsvg__parseSkewX(t, str); + else if (strncmp(str, "skewY", 5) == 0) + str += nsvg__parseSkewY(t, str); + else{ + ++str; + continue; + } + + nsvg__xformPremultiply(xform, t); + } +} + +static void nsvg__parseUrl(char* id, const char* str) +{ + int i = 0; + str += 4; // "url("; + if (*str == '#') + str++; + while (i < 63 && *str != ')') { + id[i] = *str++; + i++; + } + id[i] = '\0'; +} + +static char nsvg__parseLineCap(const char* str) +{ + if (strcmp(str, "butt") == 0) + return NSVG_CAP_BUTT; + else if (strcmp(str, "round") == 0) + return NSVG_CAP_ROUND; + else if (strcmp(str, "square") == 0) + return NSVG_CAP_SQUARE; + // TODO: handle inherit. + return NSVG_CAP_BUTT; +} + +static char nsvg__parseLineJoin(const char* str) +{ + if (strcmp(str, "miter") == 0) + return NSVG_JOIN_MITER; + else if (strcmp(str, "round") == 0) + return NSVG_JOIN_ROUND; + else if (strcmp(str, "bevel") == 0) + return NSVG_JOIN_BEVEL; + // TODO: handle inherit. + return NSVG_JOIN_MITER; +} + +static char nsvg__parseFillRule(const char* str) +{ + if (strcmp(str, "nonzero") == 0) + return NSVG_FILLRULE_NONZERO; + else if (strcmp(str, "evenodd") == 0) + return NSVG_FILLRULE_EVENODD; + // TODO: handle inherit. + return NSVG_FILLRULE_NONZERO; +} + +static const char* nsvg__getNextDashItem(const char* s, char* it) +{ + int n = 0; + it[0] = '\0'; + // Skip white spaces and commas + while (*s && (nsvg__isspace(*s) || *s == ',')) s++; + // Advance until whitespace, comma or end. + while (*s && (!nsvg__isspace(*s) && *s != ',')) { + if (n < 63) + it[n++] = *s; + s++; + } + it[n++] = '\0'; + return s; +} + +static int nsvg__parseStrokeDashArray(NSVGparser* p, const char* str, float* strokeDashArray) +{ + char item[64]; + int count = 0, i; + float sum = 0.0f; + + // Handle "none" + if (str[0] == 'n') + return 0; + + // Parse dashes + while (*str) { + str = nsvg__getNextDashItem(str, item); + if (!*item) break; + if (count < NSVG_MAX_DASHES) + strokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p))); + } + + for (i = 0; i < count; i++) + sum += strokeDashArray[i]; + if (sum <= 1e-6f) + count = 0; + + return count; +} + +static void nsvg__parseStyle(NSVGparser* p, const char* str); + +static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value) +{ + float xform[6]; + NSVGattrib* attr = nsvg__getAttr(p); + if (!attr) return 0; + + if (strcmp(name, "style") == 0) { + nsvg__parseStyle(p, value); + } else if (strcmp(name, "display") == 0) { + if (strcmp(value, "none") == 0) + attr->visible = 0; + // Don't reset ->visible on display:inline, one display:none hides the whole subtree + + } else if (strcmp(name, "fill") == 0) { + if (strcmp(value, "none") == 0) { + attr->hasFill = 0; + } else if (strncmp(value, "url(", 4) == 0) { + attr->hasFill = 2; + nsvg__parseUrl(attr->fillGradient, value); + } else { + attr->hasFill = 1; + attr->fillColor = nsvg__parseColor(value); + } + } else if (strcmp(name, "opacity") == 0) { + attr->opacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "fill-opacity") == 0) { + attr->fillOpacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "stroke") == 0) { + if (strcmp(value, "none") == 0) { + attr->hasStroke = 0; + } else if (strncmp(value, "url(", 4) == 0) { + attr->hasStroke = 2; + nsvg__parseUrl(attr->strokeGradient, value); + } else { + attr->hasStroke = 1; + attr->strokeColor = nsvg__parseColor(value); + } + } else if (strcmp(name, "stroke-width") == 0) { + attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } else if (strcmp(name, "stroke-dasharray") == 0) { + attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray); + } else if (strcmp(name, "stroke-dashoffset") == 0) { + attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } else if (strcmp(name, "stroke-opacity") == 0) { + attr->strokeOpacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "stroke-linecap") == 0) { + attr->strokeLineCap = nsvg__parseLineCap(value); + } else if (strcmp(name, "stroke-linejoin") == 0) { + attr->strokeLineJoin = nsvg__parseLineJoin(value); + } else if (strcmp(name, "stroke-miterlimit") == 0) { + attr->miterLimit = nsvg__parseMiterLimit(value); + } else if (strcmp(name, "fill-rule") == 0) { + attr->fillRule = nsvg__parseFillRule(value); + } else if (strcmp(name, "font-size") == 0) { + attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } else if (strcmp(name, "transform") == 0) { + nsvg__parseTransform(xform, value); + nsvg__xformPremultiply(attr->xform, xform); + } else if (strcmp(name, "stop-color") == 0) { + attr->stopColor = nsvg__parseColor(value); + } else if (strcmp(name, "stop-opacity") == 0) { + attr->stopOpacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "offset") == 0) { + attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f); + } else if (strcmp(name, "id") == 0) { + strncpy(attr->id, value, 63); + attr->id[63] = '\0'; + } else { + return 0; + } + return 1; +} + +static int nsvg__parseNameValue(NSVGparser* p, const char* start, const char* end) +{ + const char* str; + const char* val; + char name[512]; + char value[512]; + int n; + + str = start; + while (str < end && *str != ':') ++str; + + val = str; + + // Right Trim + while (str > start && (*str == ':' || nsvg__isspace(*str))) --str; + ++str; + + n = (int)(str - start); + if (n > 511) n = 511; + if (n) memcpy(name, start, n); + name[n] = 0; + + while (val < end && (*val == ':' || nsvg__isspace(*val))) ++val; + + n = (int)(end - val); + if (n > 511) n = 511; + if (n) memcpy(value, val, n); + value[n] = 0; + + return nsvg__parseAttr(p, name, value); +} + +static void nsvg__parseStyle(NSVGparser* p, const char* str) +{ + const char* start; + const char* end; + + while (*str) { + // Left Trim + while(*str && nsvg__isspace(*str)) ++str; + start = str; + while(*str && *str != ';') ++str; + end = str; + + // Right Trim + while (end > start && (*end == ';' || nsvg__isspace(*end))) --end; + ++end; + + nsvg__parseNameValue(p, start, end); + if (*str) ++str; + } +} + +static void nsvg__parseAttribs(NSVGparser* p, const char** attr) +{ + int i; + for (i = 0; attr[i]; i += 2) + { + if (strcmp(attr[i], "style") == 0) + nsvg__parseStyle(p, attr[i + 1]); + else + nsvg__parseAttr(p, attr[i], attr[i + 1]); + } +} + +static int nsvg__getArgsPerElement(char cmd) +{ + switch (cmd) { + case 'v': + case 'V': + case 'h': + case 'H': + return 1; + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + return 2; + case 'q': + case 'Q': + case 's': + case 'S': + return 4; + case 'c': + case 'C': + return 6; + case 'a': + case 'A': + return 7; + } + return 0; +} + +static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) { + *cpx += args[0]; + *cpy += args[1]; + } else { + *cpx = args[0]; + *cpy = args[1]; + } + nsvg__moveTo(p, *cpx, *cpy); +} + +static void nsvg__pathLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) { + *cpx += args[0]; + *cpy += args[1]; + } else { + *cpx = args[0]; + *cpy = args[1]; + } + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathHLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) + *cpx += args[0]; + else + *cpx = args[0]; + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathVLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) + *cpy += args[0]; + else + *cpy = args[0]; + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathCubicBezTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x2, y2, cx1, cy1, cx2, cy2; + + if (rel) { + cx1 = *cpx + args[0]; + cy1 = *cpy + args[1]; + cx2 = *cpx + args[2]; + cy2 = *cpy + args[3]; + x2 = *cpx + args[4]; + y2 = *cpy + args[5]; + } else { + cx1 = args[0]; + cy1 = args[1]; + cx2 = args[2]; + cy2 = args[3]; + x2 = args[4]; + y2 = args[5]; + } + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathCubicBezShortTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + cx2 = *cpx + args[0]; + cy2 = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } else { + cx2 = args[0]; + cy2 = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + cx1 = 2*x1 - *cpx2; + cy1 = 2*y1 - *cpy2; + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathQuadBezTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + cx = *cpx + args[0]; + cy = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } else { + cx = args[0]; + cy = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + // Convert to cubic bezier + cx1 = x1 + 2.0f/3.0f*(cx - x1); + cy1 = y1 + 2.0f/3.0f*(cy - y1); + cx2 = x2 + 2.0f/3.0f*(cx - x2); + cy2 = y2 + 2.0f/3.0f*(cy - y2); + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathQuadBezShortTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + x2 = *cpx + args[0]; + y2 = *cpy + args[1]; + } else { + x2 = args[0]; + y2 = args[1]; + } + + cx = 2*x1 - *cpx2; + cy = 2*y1 - *cpy2; + + // Convert to cubix bezier + cx1 = x1 + 2.0f/3.0f*(cx - x1); + cy1 = y1 + 2.0f/3.0f*(cy - y1); + cx2 = x2 + 2.0f/3.0f*(cx - x2); + cy2 = y2 + 2.0f/3.0f*(cy - y2); + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + +static float nsvg__sqr(float x) { return x*x; } +static float nsvg__vmag(float x, float y) { return sqrtf(x*x + y*y); } + +static float nsvg__vecrat(float ux, float uy, float vx, float vy) +{ + return (ux*vx + uy*vy) / (nsvg__vmag(ux,uy) * nsvg__vmag(vx,vy)); +} + +static float nsvg__vecang(float ux, float uy, float vx, float vy) +{ + float r = nsvg__vecrat(ux,uy, vx,vy); + if (r < -1.0f) r = -1.0f; + if (r > 1.0f) r = 1.0f; + return ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r); +} + +static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + // Ported from canvg (https://code.google.com/p/canvg/) + float rx, ry, rotx; + float x1, y1, x2, y2, cx, cy, dx, dy, d; + float x1p, y1p, cxp, cyp, s, sa, sb; + float ux, uy, vx, vy, a1, da; + float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; + float sinrx, cosrx; + int fa, fs; + int i, ndivs; + float hda, kappa; + + rx = fabsf(args[0]); // y radius + ry = fabsf(args[1]); // x radius + rotx = args[2] / 180.0f * NSVG_PI; // x rotation angle + fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc + fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction + x1 = *cpx; // start point + y1 = *cpy; + if (rel) { // end point + x2 = *cpx + args[5]; + y2 = *cpy + args[6]; + } else { + x2 = args[5]; + y2 = args[6]; + } + + dx = x1 - x2; + dy = y1 - y2; + d = sqrtf(dx*dx + dy*dy); + if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) { + // The arc degenerates to a line + nsvg__lineTo(p, x2, y2); + *cpx = x2; + *cpy = y2; + return; + } + + sinrx = sinf(rotx); + cosrx = cosf(rotx); + + // Convert to center point parameterization. + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + // 1) Compute x1', y1' + x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; + y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; + d = nsvg__sqr(x1p)/nsvg__sqr(rx) + nsvg__sqr(y1p)/nsvg__sqr(ry); + if (d > 1) { + d = sqrtf(d); + rx *= d; + ry *= d; + } + // 2) Compute cx', cy' + s = 0.0f; + sa = nsvg__sqr(rx)*nsvg__sqr(ry) - nsvg__sqr(rx)*nsvg__sqr(y1p) - nsvg__sqr(ry)*nsvg__sqr(x1p); + sb = nsvg__sqr(rx)*nsvg__sqr(y1p) + nsvg__sqr(ry)*nsvg__sqr(x1p); + if (sa < 0.0f) sa = 0.0f; + if (sb > 0.0f) + s = sqrtf(sa / sb); + if (fa == fs) + s = -s; + cxp = s * rx * y1p / ry; + cyp = s * -ry * x1p / rx; + + // 3) Compute cx,cy from cx',cy' + cx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp; + cy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp; + + // 4) Calculate theta1, and delta theta. + ux = (x1p - cxp) / rx; + uy = (y1p - cyp) / ry; + vx = (-x1p - cxp) / rx; + vy = (-y1p - cyp) / ry; + a1 = nsvg__vecang(1.0f,0.0f, ux,uy); // Initial angle + da = nsvg__vecang(ux,uy, vx,vy); // Delta angle + +// if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; +// if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; + + if (fs == 0 && da > 0) + da -= 2 * NSVG_PI; + else if (fs == 1 && da < 0) + da += 2 * NSVG_PI; + + // Approximate the arc using cubic spline segments. + t[0] = cosrx; t[1] = sinrx; + t[2] = -sinrx; t[3] = cosrx; + t[4] = cx; t[5] = cy; + + // Split arc into max 90 degree segments. + // The loop assumes an iteration per end point (including start and end), this +1. + ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f); + hda = (da / (float)ndivs) / 2.0f; + kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda)); + if (da < 0.0f) + kappa = -kappa; + + for (i = 0; i <= ndivs; i++) { + a = a1 + da * ((float)i/(float)ndivs); + dx = cosf(a); + dy = sinf(a); + nsvg__xformPoint(&x, &y, dx*rx, dy*ry, t); // position + nsvg__xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent + if (i > 0) + nsvg__cubicBezTo(p, px+ptanx,py+ptany, x-tanx, y-tany, x, y); + px = x; + py = y; + ptanx = tanx; + ptany = tany; + } + + *cpx = x2; + *cpy = y2; +} + +static void nsvg__parsePath(NSVGparser* p, const char** attr) +{ + const char* s = NULL; + char cmd = '\0'; + float args[10]; + int nargs; + int rargs = 0; + float cpx, cpy, cpx2, cpy2; + const char* tmp[4]; + char closedFlag; + int i; + char item[64]; + + for (i = 0; attr[i]; i += 2) { + if (strcmp(attr[i], "d") == 0) { + s = attr[i + 1]; + } else { + tmp[0] = attr[i]; + tmp[1] = attr[i + 1]; + tmp[2] = 0; + tmp[3] = 0; + nsvg__parseAttribs(p, tmp); + } + } + + if (s) { + nsvg__resetPath(p); + cpx = 0; cpy = 0; + cpx2 = 0; cpy2 = 0; + closedFlag = 0; + nargs = 0; + + while (*s) { + s = nsvg__getNextPathItem(s, item); + if (!*item) break; + if (nsvg__isnum(item[0])) { + if (nargs < 10) + args[nargs++] = (float)nsvg__atof(item); + if (nargs >= rargs) { + switch (cmd) { + case 'm': + case 'M': + nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0); + // Moveto can be followed by multiple coordinate pairs, + // which should be treated as linetos. + cmd = (cmd == 'm') ? 'l' : 'L'; + rargs = nsvg__getArgsPerElement(cmd); + cpx2 = cpx; cpy2 = cpy; + break; + case 'l': + case 'L': + nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'H': + case 'h': + nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'V': + case 'v': + nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'C': + case 'c': + nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); + break; + case 'S': + case 's': + nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); + break; + case 'Q': + case 'q': + nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); + break; + case 'T': + case 't': + nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0); + break; + case 'A': + case 'a': + nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + default: + if (nargs >= 2) { + cpx = args[nargs-2]; + cpy = args[nargs-1]; + cpx2 = cpx; cpy2 = cpy; + } + break; + } + nargs = 0; + } + } else { + cmd = item[0]; + rargs = nsvg__getArgsPerElement(cmd); + if (cmd == 'M' || cmd == 'm') { + // Commit path. + if (p->npts > 0) + nsvg__addPath(p, closedFlag); + // Start new subpath. + nsvg__resetPath(p); + closedFlag = 0; + nargs = 0; + } else if (cmd == 'Z' || cmd == 'z') { + closedFlag = 1; + // Commit path. + if (p->npts > 0) { + // Move current point to first point + cpx = p->pts[0]; + cpy = p->pts[1]; + cpx2 = cpx; cpy2 = cpy; + nsvg__addPath(p, closedFlag); + } + // Start new subpath. + nsvg__resetPath(p); + nsvg__moveTo(p, cpx, cpy); + closedFlag = 0; + nargs = 0; + } + } + } + // Commit path. + if (p->npts) + nsvg__addPath(p, closedFlag); + } + + nsvg__addShape(p); +} + +static void nsvg__parseRect(NSVGparser* p, const char** attr) +{ + float x = 0.0f; + float y = 0.0f; + float w = 0.0f; + float h = 0.0f; + float rx = -1.0f; // marks not set + float ry = -1.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "x") == 0) x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y") == 0) y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "width") == 0) w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)); + if (strcmp(attr[i], "height") == 0) h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)); + if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); + if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); + } + } + + if (rx < 0.0f && ry > 0.0f) rx = ry; + if (ry < 0.0f && rx > 0.0f) ry = rx; + if (rx < 0.0f) rx = 0.0f; + if (ry < 0.0f) ry = 0.0f; + if (rx > w/2.0f) rx = w/2.0f; + if (ry > h/2.0f) ry = h/2.0f; + + if (w != 0.0f && h != 0.0f) { + nsvg__resetPath(p); + + if (rx < 0.00001f || ry < 0.0001f) { + nsvg__moveTo(p, x, y); + nsvg__lineTo(p, x+w, y); + nsvg__lineTo(p, x+w, y+h); + nsvg__lineTo(p, x, y+h); + } else { + // Rounded rectangle + nsvg__moveTo(p, x+rx, y); + nsvg__lineTo(p, x+w-rx, y); + nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry); + nsvg__lineTo(p, x+w, y+h-ry); + nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h); + nsvg__lineTo(p, x+rx, y+h); + nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry); + nsvg__lineTo(p, x, y+ry); + nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y); + } + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseCircle(NSVGparser* p, const char** attr) +{ + float cx = 0.0f; + float cy = 0.0f; + float r = 0.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "r") == 0) r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p))); + } + } + + if (r > 0.0f) { + nsvg__resetPath(p); + + nsvg__moveTo(p, cx+r, cy); + nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r); + nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy); + nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r); + nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy); + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseEllipse(NSVGparser* p, const char** attr) +{ + float cx = 0.0f; + float cy = 0.0f; + float rx = 0.0f; + float ry = 0.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); + if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); + } + } + + if (rx > 0.0f && ry > 0.0f) { + + nsvg__resetPath(p); + + nsvg__moveTo(p, cx+rx, cy); + nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry); + nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy); + nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry); + nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy); + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseLine(NSVGparser* p, const char** attr) +{ + float x1 = 0.0; + float y1 = 0.0; + float x2 = 0.0; + float y2 = 0.0; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "x1") == 0) x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y1") == 0) y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "x2") == 0) x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y2") == 0) y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + } + } + + nsvg__resetPath(p); + + nsvg__moveTo(p, x1, y1); + nsvg__lineTo(p, x2, y2); + + nsvg__addPath(p, 0); + + nsvg__addShape(p); +} + +static void nsvg__parsePoly(NSVGparser* p, const char** attr, int closeFlag) +{ + int i; + const char* s; + float args[2]; + int nargs, npts = 0; + char item[64]; + + nsvg__resetPath(p); + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "points") == 0) { + s = attr[i + 1]; + nargs = 0; + while (*s) { + s = nsvg__getNextPathItem(s, item); + args[nargs++] = (float)nsvg__atof(item); + if (nargs >= 2) { + if (npts == 0) + nsvg__moveTo(p, args[0], args[1]); + else + nsvg__lineTo(p, args[0], args[1]); + nargs = 0; + npts++; + } + } + } + } + } + + nsvg__addPath(p, (char)closeFlag); + + nsvg__addShape(p); +} + +static void nsvg__parseSVG(NSVGparser* p, const char** attr) +{ + int i; + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "width") == 0) { + p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); + } else if (strcmp(attr[i], "height") == 0) { + p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); + } else if (strcmp(attr[i], "viewBox") == 0) { + const char *s = attr[i + 1]; + char buf[64]; + s = nsvg__parseNumber(s, buf, 64); + p->viewMinx = nsvg__atof(buf); + while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; + if (!*s) return; + s = nsvg__parseNumber(s, buf, 64); + p->viewMiny = nsvg__atof(buf); + while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; + if (!*s) return; + s = nsvg__parseNumber(s, buf, 64); + p->viewWidth = nsvg__atof(buf); + while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; + if (!*s) return; + s = nsvg__parseNumber(s, buf, 64); + p->viewHeight = nsvg__atof(buf); + } else if (strcmp(attr[i], "preserveAspectRatio") == 0) { + if (strstr(attr[i + 1], "none") != 0) { + // No uniform scaling + p->alignType = NSVG_ALIGN_NONE; + } else { + // Parse X align + if (strstr(attr[i + 1], "xMin") != 0) + p->alignX = NSVG_ALIGN_MIN; + else if (strstr(attr[i + 1], "xMid") != 0) + p->alignX = NSVG_ALIGN_MID; + else if (strstr(attr[i + 1], "xMax") != 0) + p->alignX = NSVG_ALIGN_MAX; + // Parse X align + if (strstr(attr[i + 1], "yMin") != 0) + p->alignY = NSVG_ALIGN_MIN; + else if (strstr(attr[i + 1], "yMid") != 0) + p->alignY = NSVG_ALIGN_MID; + else if (strstr(attr[i + 1], "yMax") != 0) + p->alignY = NSVG_ALIGN_MAX; + // Parse meet/slice + p->alignType = NSVG_ALIGN_MEET; + if (strstr(attr[i + 1], "slice") != 0) + p->alignType = NSVG_ALIGN_SLICE; + } + } + } + } +} + +static void nsvg__parseGradient(NSVGparser* p, const char** attr, char type) +{ + int i; + NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData)); + if (grad == NULL) return; + memset(grad, 0, sizeof(NSVGgradientData)); + grad->units = NSVG_OBJECT_SPACE; + grad->type = type; + if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) { + grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT); + grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + } else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) { + grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + } + + nsvg__xformIdentity(grad->xform); + + for (i = 0; attr[i]; i += 2) { + if (strcmp(attr[i], "id") == 0) { + strncpy(grad->id, attr[i+1], 63); + grad->id[63] = '\0'; + } else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "gradientUnits") == 0) { + if (strcmp(attr[i+1], "objectBoundingBox") == 0) + grad->units = NSVG_OBJECT_SPACE; + else + grad->units = NSVG_USER_SPACE; + } else if (strcmp(attr[i], "gradientTransform") == 0) { + nsvg__parseTransform(grad->xform, attr[i + 1]); + } else if (strcmp(attr[i], "cx") == 0) { + grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "cy") == 0) { + grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "r") == 0) { + grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "fx") == 0) { + grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "fy") == 0) { + grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "x1") == 0) { + grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "y1") == 0) { + grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "x2") == 0) { + grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "y2") == 0) { + grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "spreadMethod") == 0) { + if (strcmp(attr[i+1], "pad") == 0) + grad->spread = NSVG_SPREAD_PAD; + else if (strcmp(attr[i+1], "reflect") == 0) + grad->spread = NSVG_SPREAD_REFLECT; + else if (strcmp(attr[i+1], "repeat") == 0) + grad->spread = NSVG_SPREAD_REPEAT; + } else if (strcmp(attr[i], "xlink:href") == 0) { + const char *href = attr[i+1]; + strncpy(grad->ref, href+1, 62); + grad->ref[62] = '\0'; + } + } + } + + grad->next = p->gradients; + p->gradients = grad; +} + +static void nsvg__parseGradientStop(NSVGparser* p, const char** attr) +{ + NSVGattrib* curAttr = nsvg__getAttr(p); + NSVGgradientData* grad; + NSVGgradientStop* stop; + int i, idx; + + curAttr->stopOffset = 0; + curAttr->stopColor = 0; + curAttr->stopOpacity = 1.0f; + + for (i = 0; attr[i]; i += 2) { + nsvg__parseAttr(p, attr[i], attr[i + 1]); + } + + // Add stop to the last gradient. + grad = p->gradients; + if (grad == NULL) return; + + grad->nstops++; + grad->stops = (NSVGgradientStop*)realloc(grad->stops, sizeof(NSVGgradientStop)*grad->nstops); + if (grad->stops == NULL) return; + + // Insert + idx = grad->nstops-1; + for (i = 0; i < grad->nstops-1; i++) { + if (curAttr->stopOffset < grad->stops[i].offset) { + idx = i; + break; + } + } + if (idx != grad->nstops-1) { + for (i = grad->nstops-1; i > idx; i--) + grad->stops[i] = grad->stops[i-1]; + } + + stop = &grad->stops[idx]; + stop->color = curAttr->stopColor; + stop->color |= (unsigned int)(curAttr->stopOpacity*255) << 24; + stop->offset = curAttr->stopOffset; +} + +static void nsvg__startElement(void* ud, const char* el, const char** attr) +{ + NSVGparser* p = (NSVGparser*)ud; + + if (p->defsFlag) { + // Skip everything but gradients in defs + if (strcmp(el, "linearGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); + } else if (strcmp(el, "radialGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); + } else if (strcmp(el, "stop") == 0) { + nsvg__parseGradientStop(p, attr); + } + return; + } + + if (strcmp(el, "g") == 0) { + nsvg__pushAttr(p); + nsvg__parseAttribs(p, attr); + } else if (strcmp(el, "path") == 0) { + if (p->pathFlag) // Do not allow nested paths. + return; + nsvg__pushAttr(p); + nsvg__parsePath(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "rect") == 0) { + nsvg__pushAttr(p); + nsvg__parseRect(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "circle") == 0) { + nsvg__pushAttr(p); + nsvg__parseCircle(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "ellipse") == 0) { + nsvg__pushAttr(p); + nsvg__parseEllipse(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "line") == 0) { + nsvg__pushAttr(p); + nsvg__parseLine(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "polyline") == 0) { + nsvg__pushAttr(p); + nsvg__parsePoly(p, attr, 0); + nsvg__popAttr(p); + } else if (strcmp(el, "polygon") == 0) { + nsvg__pushAttr(p); + nsvg__parsePoly(p, attr, 1); + nsvg__popAttr(p); + } else if (strcmp(el, "linearGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); + } else if (strcmp(el, "radialGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); + } else if (strcmp(el, "stop") == 0) { + nsvg__parseGradientStop(p, attr); + } else if (strcmp(el, "defs") == 0) { + p->defsFlag = 1; + } else if (strcmp(el, "svg") == 0) { + nsvg__parseSVG(p, attr); + } +} + +static void nsvg__endElement(void* ud, const char* el) +{ + NSVGparser* p = (NSVGparser*)ud; + + if (strcmp(el, "g") == 0) { + nsvg__popAttr(p); + } else if (strcmp(el, "path") == 0) { + p->pathFlag = 0; + } else if (strcmp(el, "defs") == 0) { + p->defsFlag = 0; + } +} + +static void nsvg__content(void* ud, const char* s) +{ + NSVG_NOTUSED(ud); + NSVG_NOTUSED(s); + // empty +} + +static void nsvg__imageBounds(NSVGparser* p, float* bounds) +{ + NSVGshape* shape; + shape = p->image->shapes; + if (shape == NULL) { + bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0; + return; + } + bounds[0] = shape->bounds[0]; + bounds[1] = shape->bounds[1]; + bounds[2] = shape->bounds[2]; + bounds[3] = shape->bounds[3]; + for (shape = shape->next; shape != NULL; shape = shape->next) { + bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]); + bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]); + bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]); + bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]); + } +} + +static float nsvg__viewAlign(float content, float container, int type) +{ + if (type == NSVG_ALIGN_MIN) + return 0; + else if (type == NSVG_ALIGN_MAX) + return container - content; + // mid + return (container - content) * 0.5f; +} + +static void nsvg__scaleGradient(NSVGgradient* grad, float tx, float ty, float sx, float sy) +{ + float t[6]; + nsvg__xformSetTranslation(t, tx, ty); + nsvg__xformMultiply (grad->xform, t); + + nsvg__xformSetScale(t, sx, sy); + nsvg__xformMultiply (grad->xform, t); +} + +static void nsvg__scaleToViewbox(NSVGparser* p, const char* units) +{ + NSVGshape* shape; + NSVGpath* path; + float tx, ty, sx, sy, us, bounds[4], t[6], avgs; + int i; + float* pt; + + // Guess image size if not set completely. + nsvg__imageBounds(p, bounds); + + if (p->viewWidth == 0) { + if (p->image->width > 0) { + p->viewWidth = p->image->width; + } else { + p->viewMinx = bounds[0]; + p->viewWidth = bounds[2] - bounds[0]; + } + } + if (p->viewHeight == 0) { + if (p->image->height > 0) { + p->viewHeight = p->image->height; + } else { + p->viewMiny = bounds[1]; + p->viewHeight = bounds[3] - bounds[1]; + } + } + if (p->image->width == 0) + p->image->width = p->viewWidth; + if (p->image->height == 0) + p->image->height = p->viewHeight; + + tx = -p->viewMinx; + ty = -p->viewMiny; + sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0; + sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0; + // Unit scaling + us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f); + + // Fix aspect ratio + if (p->alignType == NSVG_ALIGN_MEET) { + // fit whole image into viewbox + sx = sy = nsvg__minf(sx, sy); + tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; + ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; + } else if (p->alignType == NSVG_ALIGN_SLICE) { + // fill whole viewbox with image + sx = sy = nsvg__maxf(sx, sy); + tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; + ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; + } + + // Transform + sx *= us; + sy *= us; + avgs = (sx+sy) / 2.0f; + for (shape = p->image->shapes; shape != NULL; shape = shape->next) { + shape->bounds[0] = (shape->bounds[0] + tx) * sx; + shape->bounds[1] = (shape->bounds[1] + ty) * sy; + shape->bounds[2] = (shape->bounds[2] + tx) * sx; + shape->bounds[3] = (shape->bounds[3] + ty) * sy; + for (path = shape->paths; path != NULL; path = path->next) { + path->bounds[0] = (path->bounds[0] + tx) * sx; + path->bounds[1] = (path->bounds[1] + ty) * sy; + path->bounds[2] = (path->bounds[2] + tx) * sx; + path->bounds[3] = (path->bounds[3] + ty) * sy; + for (i =0; i < path->npts; i++) { + pt = &path->pts[i*2]; + pt[0] = (pt[0] + tx) * sx; + pt[1] = (pt[1] + ty) * sy; + } + } + + if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) { + nsvg__scaleGradient(shape->fill.gradient, tx,ty, sx,sy); + memcpy(t, shape->fill.gradient->xform, sizeof(float)*6); + nsvg__xformInverse(shape->fill.gradient->xform, t); + } + if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) { + nsvg__scaleGradient(shape->stroke.gradient, tx,ty, sx,sy); + memcpy(t, shape->stroke.gradient->xform, sizeof(float)*6); + nsvg__xformInverse(shape->stroke.gradient->xform, t); + } + + shape->strokeWidth *= avgs; + shape->strokeDashOffset *= avgs; + for (i = 0; i < shape->strokeDashCount; i++) + shape->strokeDashArray[i] *= avgs; + } +} + +NSVGimage* nsvgParse(char* input, const char* units, float dpi) +{ + NSVGparser* p; + NSVGimage* ret = 0; + + p = nsvg__createParser(); + if (p == NULL) { + return NULL; + } + p->dpi = dpi; + + nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p); + + // Scale to viewBox + nsvg__scaleToViewbox(p, units); + + ret = p->image; + p->image = NULL; + + nsvg__deleteParser(p); + + return ret; +} + +NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) +{ + FILE* fp = NULL; + size_t size; + char* data = NULL; + NSVGimage* image = NULL; + + fp = fopen(filename, "rb"); + if (!fp) goto error; + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, 0, SEEK_SET); + data = (char*)malloc(size+1); + if (data == NULL) goto error; + if (fread(data, 1, size, fp) != size) goto error; + data[size] = '\0'; // Must be null terminated. + fclose(fp); + image = nsvgParse(data, units, dpi); + free(data); + + return image; + +error: + if (fp) fclose(fp); + if (data) free(data); + if (image) nsvgDelete(image); + return NULL; +} + +NSVGpath* nsvgDuplicatePath(NSVGpath* p) +{ + NSVGpath* res = NULL; + + if (p == NULL) + return NULL; + + res = (NSVGpath*)malloc(sizeof(NSVGpath)); + if (res == NULL) goto error; + memset(res, 0, sizeof(NSVGpath)); + + res->pts = (float*)malloc(p->npts*2*sizeof(float)); + if (res->pts == NULL) goto error; + memcpy(res->pts, p->pts, p->npts * sizeof(float) * 2); + res->npts = p->npts; + + memcpy(res->bounds, p->bounds, sizeof(p->bounds)); + + res->closed = p->closed; + + return res; + +error: + if (res != NULL) { + free(res->pts); + free(res); + } + return NULL; +} + +void nsvgDelete(NSVGimage* image) +{ + NSVGshape *snext, *shape; + if (image == NULL) return; + shape = image->shapes; + while (shape != NULL) { + snext = shape->next; + nsvg__deletePaths(shape->paths); + nsvg__deletePaint(&shape->fill); + nsvg__deletePaint(&shape->stroke); + free(shape); + shape = snext; + } + free(image); +} + +#endif diff --git a/src/nanosvg/nanosvgrast.h b/src/nanosvg/nanosvgrast.h new file mode 100644 index 0000000000..b740c316ca --- /dev/null +++ b/src/nanosvg/nanosvgrast.h @@ -0,0 +1,1452 @@ +/* + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * The polygon rasterization is heavily based on stb_truetype rasterizer + * by Sean Barrett - http://nothings.org/ + * + */ + +#ifndef NANOSVGRAST_H +#define NANOSVGRAST_H + +#ifndef NANOSVGRAST_CPLUSPLUS +#ifdef __cplusplus +extern "C" { +#endif +#endif + +typedef struct NSVGrasterizer NSVGrasterizer; + +/* Example Usage: + // Load SVG + NSVGimage* image; + image = nsvgParseFromFile("test.svg", "px", 96); + + // Create rasterizer (can be used to render multiple images). + struct NSVGrasterizer* rast = nsvgCreateRasterizer(); + // Allocate memory for image + unsigned char* img = malloc(w*h*4); + // Rasterize + nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4); +*/ + +// Allocated rasterizer context. +NSVGrasterizer* nsvgCreateRasterizer(); + +// Rasterizes SVG image, returns RGBA image (non-premultiplied alpha) +// r - pointer to rasterizer context +// image - pointer to image to rasterize +// tx,ty - image offset (applied after scaling) +// scale - image scale +// dst - pointer to destination image data, 4 bytes per pixel (RGBA) +// w - width of the image to render +// h - height of the image to render +// stride - number of bytes per scaleline in the destination buffer +void nsvgRasterize(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, float scale, + unsigned char* dst, int w, int h, int stride); + +// Deletes rasterizer context. +void nsvgDeleteRasterizer(NSVGrasterizer*); + + +#ifndef NANOSVGRAST_CPLUSPLUS +#ifdef __cplusplus +} +#endif +#endif + +#endif // NANOSVGRAST_H + +#ifdef NANOSVGRAST_IMPLEMENTATION + +#include + +#define NSVG__SUBSAMPLES 5 +#define NSVG__FIXSHIFT 10 +#define NSVG__FIX (1 << NSVG__FIXSHIFT) +#define NSVG__FIXMASK (NSVG__FIX-1) +#define NSVG__MEMPAGE_SIZE 1024 + +typedef struct NSVGedge { + float x0,y0, x1,y1; + int dir; + struct NSVGedge* next; +} NSVGedge; + +typedef struct NSVGpoint { + float x, y; + float dx, dy; + float len; + float dmx, dmy; + unsigned char flags; +} NSVGpoint; + +typedef struct NSVGactiveEdge { + int x,dx; + float ey; + int dir; + struct NSVGactiveEdge *next; +} NSVGactiveEdge; + +typedef struct NSVGmemPage { + unsigned char mem[NSVG__MEMPAGE_SIZE]; + int size; + struct NSVGmemPage* next; +} NSVGmemPage; + +typedef struct NSVGcachedPaint { + char type; + char spread; + float xform[6]; + unsigned int colors[256]; +} NSVGcachedPaint; + +struct NSVGrasterizer +{ + float px, py; + + float tessTol; + float distTol; + + NSVGedge* edges; + int nedges; + int cedges; + + NSVGpoint* points; + int npoints; + int cpoints; + + NSVGpoint* points2; + int npoints2; + int cpoints2; + + NSVGactiveEdge* freelist; + NSVGmemPage* pages; + NSVGmemPage* curpage; + + unsigned char* scanline; + int cscanline; + + unsigned char* bitmap; + int width, height, stride; +}; + +NSVGrasterizer* nsvgCreateRasterizer() +{ + NSVGrasterizer* r = (NSVGrasterizer*)malloc(sizeof(NSVGrasterizer)); + if (r == NULL) goto error; + memset(r, 0, sizeof(NSVGrasterizer)); + + r->tessTol = 0.25f; + r->distTol = 0.01f; + + return r; + +error: + nsvgDeleteRasterizer(r); + return NULL; +} + +void nsvgDeleteRasterizer(NSVGrasterizer* r) +{ + NSVGmemPage* p; + + if (r == NULL) return; + + p = r->pages; + while (p != NULL) { + NSVGmemPage* next = p->next; + free(p); + p = next; + } + + if (r->edges) free(r->edges); + if (r->points) free(r->points); + if (r->points2) free(r->points2); + if (r->scanline) free(r->scanline); + + free(r); +} + +static NSVGmemPage* nsvg__nextPage(NSVGrasterizer* r, NSVGmemPage* cur) +{ + NSVGmemPage *newp; + + // If using existing chain, return the next page in chain + if (cur != NULL && cur->next != NULL) { + return cur->next; + } + + // Alloc new page + newp = (NSVGmemPage*)malloc(sizeof(NSVGmemPage)); + if (newp == NULL) return NULL; + memset(newp, 0, sizeof(NSVGmemPage)); + + // Add to linked list + if (cur != NULL) + cur->next = newp; + else + r->pages = newp; + + return newp; +} + +static void nsvg__resetPool(NSVGrasterizer* r) +{ + NSVGmemPage* p = r->pages; + while (p != NULL) { + p->size = 0; + p = p->next; + } + r->curpage = r->pages; +} + +static unsigned char* nsvg__alloc(NSVGrasterizer* r, int size) +{ + unsigned char* buf; + if (size > NSVG__MEMPAGE_SIZE) return NULL; + if (r->curpage == NULL || r->curpage->size+size > NSVG__MEMPAGE_SIZE) { + r->curpage = nsvg__nextPage(r, r->curpage); + } + buf = &r->curpage->mem[r->curpage->size]; + r->curpage->size += size; + return buf; +} + +static int nsvg__ptEquals(float x1, float y1, float x2, float y2, float tol) +{ + float dx = x2 - x1; + float dy = y2 - y1; + return dx*dx + dy*dy < tol*tol; +} + +static void nsvg__addPathPoint(NSVGrasterizer* r, float x, float y, int flags) +{ + NSVGpoint* pt; + + if (r->npoints > 0) { + pt = &r->points[r->npoints-1]; + if (nsvg__ptEquals(pt->x,pt->y, x,y, r->distTol)) { + pt->flags = (unsigned char)(pt->flags | flags); + return; + } + } + + if (r->npoints+1 > r->cpoints) { + r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; + r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); + if (r->points == NULL) return; + } + + pt = &r->points[r->npoints]; + pt->x = x; + pt->y = y; + pt->flags = (unsigned char)flags; + r->npoints++; +} + +static void nsvg__appendPathPoint(NSVGrasterizer* r, NSVGpoint pt) +{ + if (r->npoints+1 > r->cpoints) { + r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; + r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); + if (r->points == NULL) return; + } + r->points[r->npoints] = pt; + r->npoints++; +} + +static void nsvg__duplicatePoints(NSVGrasterizer* r) +{ + if (r->npoints > r->cpoints2) { + r->cpoints2 = r->npoints; + r->points2 = (NSVGpoint*)realloc(r->points2, sizeof(NSVGpoint) * r->cpoints2); + if (r->points2 == NULL) return; + } + + memcpy(r->points2, r->points, sizeof(NSVGpoint) * r->npoints); + r->npoints2 = r->npoints; +} + +static void nsvg__addEdge(NSVGrasterizer* r, float x0, float y0, float x1, float y1) +{ + NSVGedge* e; + + // Skip horizontal edges + if (y0 == y1) + return; + + if (r->nedges+1 > r->cedges) { + r->cedges = r->cedges > 0 ? r->cedges * 2 : 64; + r->edges = (NSVGedge*)realloc(r->edges, sizeof(NSVGedge) * r->cedges); + if (r->edges == NULL) return; + } + + e = &r->edges[r->nedges]; + r->nedges++; + + if (y0 < y1) { + e->x0 = x0; + e->y0 = y0; + e->x1 = x1; + e->y1 = y1; + e->dir = 1; + } else { + e->x0 = x1; + e->y0 = y1; + e->x1 = x0; + e->y1 = y0; + e->dir = -1; + } +} + +static float nsvg__normalize(float *x, float* y) +{ + float d = sqrtf((*x)*(*x) + (*y)*(*y)); + if (d > 1e-6f) { + float id = 1.0f / d; + *x *= id; + *y *= id; + } + return d; +} + +static float nsvg__absf(float x) { return x < 0 ? -x : x; } + +static void nsvg__flattenCubicBez(NSVGrasterizer* r, + float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4, + int level, int type) +{ + float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; + float dx,dy,d2,d3; + + if (level > 10) return; + + x12 = (x1+x2)*0.5f; + y12 = (y1+y2)*0.5f; + x23 = (x2+x3)*0.5f; + y23 = (y2+y3)*0.5f; + x34 = (x3+x4)*0.5f; + y34 = (y3+y4)*0.5f; + x123 = (x12+x23)*0.5f; + y123 = (y12+y23)*0.5f; + + dx = x4 - x1; + dy = y4 - y1; + d2 = nsvg__absf(((x2 - x4) * dy - (y2 - y4) * dx)); + d3 = nsvg__absf(((x3 - x4) * dy - (y3 - y4) * dx)); + + if ((d2 + d3)*(d2 + d3) < r->tessTol * (dx*dx + dy*dy)) { + nsvg__addPathPoint(r, x4, y4, type); + return; + } + + x234 = (x23+x34)*0.5f; + y234 = (y23+y34)*0.5f; + x1234 = (x123+x234)*0.5f; + y1234 = (y123+y234)*0.5f; + + nsvg__flattenCubicBez(r, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); + nsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); +} + +static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale) +{ + int i, j; + NSVGpath* path; + + for (path = shape->paths; path != NULL; path = path->next) { + r->npoints = 0; + // Flatten path + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); + for (i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0); + } + // Close path + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); + // Build edges + for (i = 0, j = r->npoints-1; i < r->npoints; j = i++) + nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y); + } +} + +enum NSVGpointFlags +{ + NSVG_PT_CORNER = 0x01, + NSVG_PT_BEVEL = 0x02, + NSVG_PT_LEFT = 0x04 +}; + +static void nsvg__initClosed(NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + float len = nsvg__normalize(&dx, &dy); + float px = p0->x + dx*len*0.5f, py = p0->y + dy*len*0.5f; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__buttCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) +{ + float w = lineWidth * 0.5f; + float px = p->x, py = p->y; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + + nsvg__addEdge(r, lx, ly, rx, ry); + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__squareCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) +{ + float w = lineWidth * 0.5f; + float px = p->x - dx*w, py = p->y - dy*w; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + + nsvg__addEdge(r, lx, ly, rx, ry); + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +#ifndef NSVG_PI +#define NSVG_PI (3.14159265358979323846264338327f) +#endif + +static void nsvg__roundCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int ncap, int connect) +{ + int i; + float w = lineWidth * 0.5f; + float px = p->x, py = p->y; + float dlx = dy, dly = -dx; + float lx = 0, ly = 0, rx = 0, ry = 0, prevx = 0, prevy = 0; + + for (i = 0; i < ncap; i++) { + float a = (float)i/(float)(ncap-1)*NSVG_PI; + float ax = cosf(a) * w, ay = sinf(a) * w; + float x = px - dlx*ax - dx*ay; + float y = py - dly*ax - dy*ay; + + if (i > 0) + nsvg__addEdge(r, prevx, prevy, x, y); + + prevx = x; + prevy = y; + + if (i == 0) { + lx = x; ly = y; + } else if (i == ncap-1) { + rx = x; ry = y; + } + } + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__bevelJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float lx0 = p1->x - (dlx0 * w), ly0 = p1->y - (dly0 * w); + float rx0 = p1->x + (dlx0 * w), ry0 = p1->y + (dly0 * w); + float lx1 = p1->x - (dlx1 * w), ly1 = p1->y - (dly1 * w); + float rx1 = p1->x + (dlx1 * w), ry1 = p1->y + (dly1 * w); + + nsvg__addEdge(r, lx0, ly0, left->x, left->y); + nsvg__addEdge(r, lx1, ly1, lx0, ly0); + + nsvg__addEdge(r, right->x, right->y, rx0, ry0); + nsvg__addEdge(r, rx0, ry0, rx1, ry1); + + left->x = lx1; left->y = ly1; + right->x = rx1; right->y = ry1; +} + +static void nsvg__miterJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float lx0, rx0, lx1, rx1; + float ly0, ry0, ly1, ry1; + + if (p1->flags & NSVG_PT_LEFT) { + lx0 = lx1 = p1->x - p1->dmx * w; + ly0 = ly1 = p1->y - p1->dmy * w; + nsvg__addEdge(r, lx1, ly1, left->x, left->y); + + rx0 = p1->x + (dlx0 * w); + ry0 = p1->y + (dly0 * w); + rx1 = p1->x + (dlx1 * w); + ry1 = p1->y + (dly1 * w); + nsvg__addEdge(r, right->x, right->y, rx0, ry0); + nsvg__addEdge(r, rx0, ry0, rx1, ry1); + } else { + lx0 = p1->x - (dlx0 * w); + ly0 = p1->y - (dly0 * w); + lx1 = p1->x - (dlx1 * w); + ly1 = p1->y - (dly1 * w); + nsvg__addEdge(r, lx0, ly0, left->x, left->y); + nsvg__addEdge(r, lx1, ly1, lx0, ly0); + + rx0 = rx1 = p1->x + p1->dmx * w; + ry0 = ry1 = p1->y + p1->dmy * w; + nsvg__addEdge(r, right->x, right->y, rx1, ry1); + } + + left->x = lx1; left->y = ly1; + right->x = rx1; right->y = ry1; +} + +static void nsvg__roundJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth, int ncap) +{ + int i, n; + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float a0 = atan2f(dly0, dlx0); + float a1 = atan2f(dly1, dlx1); + float da = a1 - a0; + float lx, ly, rx, ry; + + if (da < NSVG_PI) da += NSVG_PI*2; + if (da > NSVG_PI) da -= NSVG_PI*2; + + n = (int)ceilf((nsvg__absf(da) / NSVG_PI) * (float)ncap); + if (n < 2) n = 2; + if (n > ncap) n = ncap; + + lx = left->x; + ly = left->y; + rx = right->x; + ry = right->y; + + for (i = 0; i < n; i++) { + float u = (float)i/(float)(n-1); + float a = a0 + u*da; + float ax = cosf(a) * w, ay = sinf(a) * w; + float lx1 = p1->x - ax, ly1 = p1->y - ay; + float rx1 = p1->x + ax, ry1 = p1->y + ay; + + nsvg__addEdge(r, lx1, ly1, lx, ly); + nsvg__addEdge(r, rx, ry, rx1, ry1); + + lx = lx1; ly = ly1; + rx = rx1; ry = ry1; + } + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__straightJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float lx = p1->x - (p1->dmx * w), ly = p1->y - (p1->dmy * w); + float rx = p1->x + (p1->dmx * w), ry = p1->y + (p1->dmy * w); + + nsvg__addEdge(r, lx, ly, left->x, left->y); + nsvg__addEdge(r, right->x, right->y, rx, ry); + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static int nsvg__curveDivs(float r, float arc, float tol) +{ + float da = acosf(r / (r + tol)) * 2.0f; + int divs = (int)ceilf(arc / da); + if (divs < 2) divs = 2; + return divs; +} + +static void nsvg__expandStroke(NSVGrasterizer* r, NSVGpoint* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth) +{ + int ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r->tessTol); // Calculate divisions per half circle. + NSVGpoint left = {0,0,0,0,0,0,0,0}, right = {0,0,0,0,0,0,0,0}, firstLeft = {0,0,0,0,0,0,0,0}, firstRight = {0,0,0,0,0,0,0,0}; + NSVGpoint* p0, *p1; + int j, s, e; + + // Build stroke edges + if (closed) { + // Looping + p0 = &points[npoints-1]; + p1 = &points[0]; + s = 0; + e = npoints; + } else { + // Add cap + p0 = &points[0]; + p1 = &points[1]; + s = 1; + e = npoints-1; + } + + if (closed) { + nsvg__initClosed(&left, &right, p0, p1, lineWidth); + firstLeft = left; + firstRight = right; + } else { + // Add cap + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + nsvg__normalize(&dx, &dy); + if (lineCap == NSVG_CAP_BUTT) + nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0); + else if (lineCap == NSVG_CAP_SQUARE) + nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0); + else if (lineCap == NSVG_CAP_ROUND) + nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0); + } + + for (j = s; j < e; ++j) { + if (p1->flags & NSVG_PT_CORNER) { + if (lineJoin == NSVG_JOIN_ROUND) + nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap); + else if (lineJoin == NSVG_JOIN_BEVEL || (p1->flags & NSVG_PT_BEVEL)) + nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth); + else + nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth); + } else { + nsvg__straightJoin(r, &left, &right, p1, lineWidth); + } + p0 = p1++; + } + + if (closed) { + // Loop it + nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y); + nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y); + } else { + // Add cap + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + nsvg__normalize(&dx, &dy); + if (lineCap == NSVG_CAP_BUTT) + nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); + else if (lineCap == NSVG_CAP_SQUARE) + nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); + else if (lineCap == NSVG_CAP_ROUND) + nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1); + } +} + +static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoin) +{ + int i, j; + NSVGpoint* p0, *p1; + + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + for (i = 0; i < r->npoints; i++) { + // Calculate segment direction and length + p0->dx = p1->x - p0->x; + p0->dy = p1->y - p0->y; + p0->len = nsvg__normalize(&p0->dx, &p0->dy); + // Advance + p0 = p1++; + } + + // calculate joins + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + for (j = 0; j < r->npoints; j++) { + float dlx0, dly0, dlx1, dly1, dmr2, cross; + dlx0 = p0->dy; + dly0 = -p0->dx; + dlx1 = p1->dy; + dly1 = -p1->dx; + // Calculate extrusions + p1->dmx = (dlx0 + dlx1) * 0.5f; + p1->dmy = (dly0 + dly1) * 0.5f; + dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy; + if (dmr2 > 0.000001f) { + float s2 = 1.0f / dmr2; + if (s2 > 600.0f) { + s2 = 600.0f; + } + p1->dmx *= s2; + p1->dmy *= s2; + } + + // Clear flags, but keep the corner. + p1->flags = (p1->flags & NSVG_PT_CORNER) ? NSVG_PT_CORNER : 0; + + // Keep track of left turns. + cross = p1->dx * p0->dy - p0->dx * p1->dy; + if (cross > 0.0f) + p1->flags |= NSVG_PT_LEFT; + + // Check to see if the corner needs to be beveled. + if (p1->flags & NSVG_PT_CORNER) { + if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NSVG_JOIN_BEVEL || lineJoin == NSVG_JOIN_ROUND) { + p1->flags |= NSVG_PT_BEVEL; + } + } + + p0 = p1++; + } +} + +static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale) +{ + int i, j, closed; + NSVGpath* path; + NSVGpoint* p0, *p1; + float miterLimit = shape->miterLimit; + int lineJoin = shape->strokeLineJoin; + int lineCap = shape->strokeLineCap; + float lineWidth = shape->strokeWidth * scale; + + for (path = shape->paths; path != NULL; path = path->next) { + // Flatten path + r->npoints = 0; + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER); + for (i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER); + } + if (r->npoints < 2) + continue; + + closed = path->closed; + + // If the first and last points are the same, remove the last, mark as closed path. + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) { + r->npoints--; + p0 = &r->points[r->npoints-1]; + closed = 1; + } + + if (shape->strokeDashCount > 0) { + int idash = 0, dashState = 1; + float totalDist = 0, dashLen, allDashLen, dashOffset; + NSVGpoint cur; + + if (closed) + nsvg__appendPathPoint(r, r->points[0]); + + // Duplicate points -> points2. + nsvg__duplicatePoints(r); + + r->npoints = 0; + cur = r->points2[0]; + nsvg__appendPathPoint(r, cur); + + // Figure out dash offset. + allDashLen = 0; + for (j = 0; j < shape->strokeDashCount; j++) + allDashLen += shape->strokeDashArray[j]; + if (shape->strokeDashCount & 1) + allDashLen *= 2.0f; + // Find location inside pattern + dashOffset = fmodf(shape->strokeDashOffset, allDashLen); + if (dashOffset < 0.0f) + dashOffset += allDashLen; + + while (dashOffset > shape->strokeDashArray[idash]) { + dashOffset -= shape->strokeDashArray[idash]; + idash = (idash + 1) % shape->strokeDashCount; + } + dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale; + + for (j = 1; j < r->npoints2; ) { + float dx = r->points2[j].x - cur.x; + float dy = r->points2[j].y - cur.y; + float dist = sqrtf(dx*dx + dy*dy); + + if ((totalDist + dist) > dashLen) { + // Calculate intermediate point + float d = (dashLen - totalDist) / dist; + float x = cur.x + dx * d; + float y = cur.y + dy * d; + nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER); + + // Stroke + if (r->npoints > 1 && dashState) { + nsvg__prepareStroke(r, miterLimit, lineJoin); + nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); + } + // Advance dash pattern + dashState = !dashState; + idash = (idash+1) % shape->strokeDashCount; + dashLen = shape->strokeDashArray[idash] * scale; + // Restart + cur.x = x; + cur.y = y; + cur.flags = NSVG_PT_CORNER; + totalDist = 0.0f; + r->npoints = 0; + nsvg__appendPathPoint(r, cur); + } else { + totalDist += dist; + cur = r->points2[j]; + nsvg__appendPathPoint(r, cur); + j++; + } + } + // Stroke any leftover path + if (r->npoints > 1 && dashState) + nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); + } else { + nsvg__prepareStroke(r, miterLimit, lineJoin); + nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, lineCap, lineWidth); + } + } +} + +static int nsvg__cmpEdge(const void *p, const void *q) +{ + const NSVGedge* a = (const NSVGedge*)p; + const NSVGedge* b = (const NSVGedge*)q; + + if (a->y0 < b->y0) return -1; + if (a->y0 > b->y0) return 1; + return 0; +} + + +static NSVGactiveEdge* nsvg__addActive(NSVGrasterizer* r, NSVGedge* e, float startPoint) +{ + NSVGactiveEdge* z; + + if (r->freelist != NULL) { + // Restore from freelist. + z = r->freelist; + r->freelist = z->next; + } else { + // Alloc new edge. + z = (NSVGactiveEdge*)nsvg__alloc(r, sizeof(NSVGactiveEdge)); + if (z == NULL) return NULL; + } + + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); +// STBTT_assert(e->y0 <= start_point); + // round dx down to avoid going too far + if (dxdy < 0) + z->dx = (int)(-floorf(NSVG__FIX * -dxdy)); + else + z->dx = (int)floorf(NSVG__FIX * dxdy); + z->x = (int)floorf(NSVG__FIX * (e->x0 + dxdy * (startPoint - e->y0))); +// z->x -= off_x * FIX; + z->ey = e->y1; + z->next = 0; + z->dir = e->dir; + + return z; +} + +static void nsvg__freeActive(NSVGrasterizer* r, NSVGactiveEdge* z) +{ + z->next = r->freelist; + r->freelist = z; +} + +static void nsvg__fillScanline(unsigned char* scanline, int len, int x0, int x1, int maxWeight, int* xmin, int* xmax) +{ + int i = x0 >> NSVG__FIXSHIFT; + int j = x1 >> NSVG__FIXSHIFT; + if (i < *xmin) *xmin = i; + if (j > *xmax) *xmax = j; + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = (unsigned char)(scanline[i] + ((x1 - x0) * maxWeight >> NSVG__FIXSHIFT)); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = (unsigned char)(scanline[i] + (((NSVG__FIX - (x0 & NSVG__FIXMASK)) * maxWeight) >> NSVG__FIXSHIFT)); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = (unsigned char)(scanline[j] + (((x1 & NSVG__FIXMASK) * maxWeight) >> NSVG__FIXSHIFT)); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = (unsigned char)(scanline[i] + maxWeight); + } + } +} + +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void nsvg__fillActiveEdges(unsigned char* scanline, int len, NSVGactiveEdge* e, int maxWeight, int* xmin, int* xmax, char fillRule) +{ + // non-zero winding fill + int x0 = 0, w = 0; + + if (fillRule == NSVG_FILLRULE_NONZERO) { + // Non-zero + while (e != NULL) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->dir; + } else { + int x1 = e->x; w += e->dir; + // if we went to zero, we need to draw + if (w == 0) + nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); + } + e = e->next; + } + } else if (fillRule == NSVG_FILLRULE_EVENODD) { + // Even-odd + while (e != NULL) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w = 1; + } else { + int x1 = e->x; w = 0; + nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); + } + e = e->next; + } + } +} + +static float nsvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); } + +static unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return (r) | (g << 8) | (b << 16) | (a << 24); +} + +static unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u) +{ + int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); + int r = (((c0) & 0xff)*(256-iu) + (((c1) & 0xff)*iu)) >> 8; + int g = (((c0>>8) & 0xff)*(256-iu) + (((c1>>8) & 0xff)*iu)) >> 8; + int b = (((c0>>16) & 0xff)*(256-iu) + (((c1>>16) & 0xff)*iu)) >> 8; + int a = (((c0>>24) & 0xff)*(256-iu) + (((c1>>24) & 0xff)*iu)) >> 8; + return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); +} + +static unsigned int nsvg__applyOpacity(unsigned int c, float u) +{ + int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); + int r = (c) & 0xff; + int g = (c>>8) & 0xff; + int b = (c>>16) & 0xff; + int a = (((c>>24) & 0xff)*iu) >> 8; + return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); +} + +static inline int nsvg__div255(int x) +{ + return ((x+1) * 257) >> 16; +} + +static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y, + float tx, float ty, float scale, NSVGcachedPaint* cache) +{ + + if (cache->type == NSVG_PAINT_COLOR) { + int i, cr, cg, cb, ca; + cr = cache->colors[0] & 0xff; + cg = (cache->colors[0] >> 8) & 0xff; + cb = (cache->colors[0] >> 16) & 0xff; + ca = (cache->colors[0] >> 24) & 0xff; + + for (i = 0; i < count; i++) { + int r,g,b; + int a = nsvg__div255((int)cover[0] * ca); + int ia = 255 - a; + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + } + } else if (cache->type == NSVG_PAINT_LINEAR_GRADIENT) { + // TODO: spread modes. + // TODO: plenty of opportunities to optimize. + float fx, fy, dx, gy; + float* t = cache->xform; + int i, cr, cg, cb, ca; + unsigned int c; + + fx = ((float)x - tx) / scale; + fy = ((float)y - ty) / scale; + dx = 1.0f / scale; + + for (i = 0; i < count; i++) { + int r,g,b,a,ia; + gy = fx*t[1] + fy*t[3] + t[5]; + c = cache->colors[(int)nsvg__clampf(gy*255.0f, 0, 255.0f)]; + cr = (c) & 0xff; + cg = (c >> 8) & 0xff; + cb = (c >> 16) & 0xff; + ca = (c >> 24) & 0xff; + + a = nsvg__div255((int)cover[0] * ca); + ia = 255 - a; + + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + fx += dx; + } + } else if (cache->type == NSVG_PAINT_RADIAL_GRADIENT) { + // TODO: spread modes. + // TODO: plenty of opportunities to optimize. + // TODO: focus (fx,fy) + float fx, fy, dx, gx, gy, gd; + float* t = cache->xform; + int i, cr, cg, cb, ca; + unsigned int c; + + fx = ((float)x - tx) / scale; + fy = ((float)y - ty) / scale; + dx = 1.0f / scale; + + for (i = 0; i < count; i++) { + int r,g,b,a,ia; + gx = fx*t[0] + fy*t[2] + t[4]; + gy = fx*t[1] + fy*t[3] + t[5]; + gd = sqrtf(gx*gx + gy*gy); + c = cache->colors[(int)nsvg__clampf(gd*255.0f, 0, 255.0f)]; + cr = (c) & 0xff; + cg = (c >> 8) & 0xff; + cb = (c >> 16) & 0xff; + ca = (c >> 24) & 0xff; + + a = nsvg__div255((int)cover[0] * ca); + ia = 255 - a; + + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + fx += dx; + } + } +} + +static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule) +{ + NSVGactiveEdge *active = NULL; + int y, s; + int e = 0; + int maxWeight = (255 / NSVG__SUBSAMPLES); // weight per vertical scanline + int xmin, xmax; + + for (y = 0; y < r->height; y++) { + memset(r->scanline, 0, r->width); + xmin = r->width; + xmax = 0; + for (s = 0; s < NSVG__SUBSAMPLES; ++s) { + // find center of pixel for this scanline + float scany = (float)(y*NSVG__SUBSAMPLES + s) + 0.5f; + NSVGactiveEdge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + NSVGactiveEdge *z = *step; + if (z->ey <= scany) { + *step = z->next; // delete from list +// NSVG__assert(z->valid); + nsvg__freeActive(r, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for (;;) { + int changed = 0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + NSVGactiveEdge* t = *step; + NSVGactiveEdge* q = t->next; + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e < r->nedges && r->edges[e].y0 <= scany) { + if (r->edges[e].y1 > scany) { + NSVGactiveEdge* z = nsvg__addActive(r, &r->edges[e], scany); + if (z == NULL) break; + // find insertion point + if (active == NULL) { + active = z; + } else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + NSVGactiveEdge* p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + e++; + } + + // now process all active edges in non-zero fashion + if (active != NULL) + nsvg__fillActiveEdges(r->scanline, r->width, active, maxWeight, &xmin, &xmax, fillRule); + } + // Blit + if (xmin < 0) xmin = 0; + if (xmax > r->width-1) xmax = r->width-1; + if (xmin <= xmax) { + nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache); + } + } + +} + +static void nsvg__unpremultiplyAlpha(unsigned char* image, int w, int h, int stride) +{ + int x,y; + + // Unpremultiply + for (y = 0; y < h; y++) { + unsigned char *row = &image[y*stride]; + for (x = 0; x < w; x++) { + int r = row[0], g = row[1], b = row[2], a = row[3]; + if (a != 0) { + row[0] = (unsigned char)(r*255/a); + row[1] = (unsigned char)(g*255/a); + row[2] = (unsigned char)(b*255/a); + } + row += 4; + } + } + + // Defringe + for (y = 0; y < h; y++) { + unsigned char *row = &image[y*stride]; + for (x = 0; x < w; x++) { + int r = 0, g = 0, b = 0, a = row[3], n = 0; + if (a == 0) { + if (x-1 > 0 && row[-1] != 0) { + r += row[-4]; + g += row[-3]; + b += row[-2]; + n++; + } + if (x+1 < w && row[7] != 0) { + r += row[4]; + g += row[5]; + b += row[6]; + n++; + } + if (y-1 > 0 && row[-stride+3] != 0) { + r += row[-stride]; + g += row[-stride+1]; + b += row[-stride+2]; + n++; + } + if (y+1 < h && row[stride+3] != 0) { + r += row[stride]; + g += row[stride+1]; + b += row[stride+2]; + n++; + } + if (n > 0) { + row[0] = (unsigned char)(r/n); + row[1] = (unsigned char)(g/n); + row[2] = (unsigned char)(b/n); + } + } + row += 4; + } + } +} + + +static void nsvg__initPaint(NSVGcachedPaint* cache, NSVGpaint* paint, float opacity) +{ + int i, j; + NSVGgradient* grad; + + cache->type = paint->type; + + if (paint->type == NSVG_PAINT_COLOR) { + cache->colors[0] = nsvg__applyOpacity(paint->color, opacity); + return; + } + + grad = paint->gradient; + + cache->spread = grad->spread; + memcpy(cache->xform, grad->xform, sizeof(float)*6); + + if (grad->nstops == 0) { + for (i = 0; i < 256; i++) + cache->colors[i] = 0; + } if (grad->nstops == 1) { + for (i = 0; i < 256; i++) + cache->colors[i] = nsvg__applyOpacity(grad->stops[i].color, opacity); + } else { + unsigned int ca, cb = 0; + float ua, ub, du, u; + int ia, ib, count; + + ca = nsvg__applyOpacity(grad->stops[0].color, opacity); + ua = nsvg__clampf(grad->stops[0].offset, 0, 1); + ub = nsvg__clampf(grad->stops[grad->nstops-1].offset, ua, 1); + ia = (int)(ua * 255.0f); + ib = (int)(ub * 255.0f); + for (i = 0; i < ia; i++) { + cache->colors[i] = ca; + } + + for (i = 0; i < grad->nstops-1; i++) { + ca = nsvg__applyOpacity(grad->stops[i].color, opacity); + cb = nsvg__applyOpacity(grad->stops[i+1].color, opacity); + ua = nsvg__clampf(grad->stops[i].offset, 0, 1); + ub = nsvg__clampf(grad->stops[i+1].offset, 0, 1); + ia = (int)(ua * 255.0f); + ib = (int)(ub * 255.0f); + count = ib - ia; + if (count <= 0) continue; + u = 0; + du = 1.0f / (float)count; + for (j = 0; j < count; j++) { + cache->colors[ia+j] = nsvg__lerpRGBA(ca,cb,u); + u += du; + } + } + + for (i = ib; i < 256; i++) + cache->colors[i] = cb; + } + +} + +/* +static void dumpEdges(NSVGrasterizer* r, const char* name) +{ + float xmin = 0, xmax = 0, ymin = 0, ymax = 0; + NSVGedge *e = NULL; + int i; + if (r->nedges == 0) return; + FILE* fp = fopen(name, "w"); + if (fp == NULL) return; + + xmin = xmax = r->edges[0].x0; + ymin = ymax = r->edges[0].y0; + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + xmin = nsvg__minf(xmin, e->x0); + xmin = nsvg__minf(xmin, e->x1); + xmax = nsvg__maxf(xmax, e->x0); + xmax = nsvg__maxf(xmax, e->x1); + ymin = nsvg__minf(ymin, e->y0); + ymin = nsvg__minf(ymin, e->y1); + ymax = nsvg__maxf(ymax, e->y0); + ymax = nsvg__maxf(ymax, e->y1); + } + + fprintf(fp, "", xmin, ymin, (xmax - xmin), (ymax - ymin)); + + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + fprintf(fp ,"", e->x0,e->y0, e->x1,e->y1); + } + + for (i = 0; i < r->npoints; i++) { + if (i+1 < r->npoints) + fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i+1].x, r->points[i+1].y); + fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i].flags == 0 ? "#f00" : "#0f0"); + } + + fprintf(fp, ""); + fclose(fp); +} +*/ + +void nsvgRasterize(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, float scale, + unsigned char* dst, int w, int h, int stride) +{ + NSVGshape *shape = NULL; + NSVGedge *e = NULL; + NSVGcachedPaint cache; + int i; + + r->bitmap = dst; + r->width = w; + r->height = h; + r->stride = stride; + + if (w > r->cscanline) { + r->cscanline = w; + r->scanline = (unsigned char*)realloc(r->scanline, w); + if (r->scanline == NULL) return; + } + + for (i = 0; i < h; i++) + memset(&dst[i*stride], 0, w*4); + + for (shape = image->shapes; shape != NULL; shape = shape->next) { + if (!(shape->flags & NSVG_FLAGS_VISIBLE)) + continue; + + if (shape->fill.type != NSVG_PAINT_NONE) { + nsvg__resetPool(r); + r->freelist = NULL; + r->nedges = 0; + + nsvg__flattenShape(r, shape, scale); + + // Scale and translate edges + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + e->x0 = tx + e->x0; + e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; + e->x1 = tx + e->x1; + e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; + } + + // Rasterize edges + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + nsvg__initPaint(&cache, &shape->fill, shape->opacity); + + nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule); + } + if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) { + nsvg__resetPool(r); + r->freelist = NULL; + r->nedges = 0; + + nsvg__flattenShapeStroke(r, shape, scale); + +// dumpEdges(r, "edge.svg"); + + // Scale and translate edges + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + e->x0 = tx + e->x0; + e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; + e->x1 = tx + e->x1; + e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; + } + + // Rasterize edges + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + nsvg__initPaint(&cache, &shape->stroke, shape->opacity); + + nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO); + } + } + + nsvg__unpremultiplyAlpha(dst, w, h, stride); + + r->bitmap = NULL; + r->width = 0; + r->height = 0; + r->stride = 0; +} + +#endif diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 9d65a479fe..157fc9011f 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -74,6 +74,8 @@ set(SLIC3R_GUI_SOURCES GUI/BedShapeDialog.hpp GUI/2DBed.cpp GUI/2DBed.hpp + GUI/3DBed.cpp + GUI/3DBed.hpp GUI/wxExtensions.cpp GUI/wxExtensions.hpp GUI/WipeTowerDialog.cpp diff --git a/src/slic3r/GUI/2DBed.cpp b/src/slic3r/GUI/2DBed.cpp index c90d54cb01..eb55d5fdcd 100644 --- a/src/slic3r/GUI/2DBed.cpp +++ b/src/slic3r/GUI/2DBed.cpp @@ -1,4 +1,5 @@ #include "2DBed.hpp" +#include "GUI_App.hpp" #include @@ -9,6 +10,19 @@ namespace Slic3r { namespace GUI { + +Bed_2D::Bed_2D(wxWindow* parent) : +wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(25 * wxGetApp().em_unit(), -1), wxTAB_TRAVERSAL) +{ + SetBackgroundStyle(wxBG_STYLE_PAINT); // to avoid assert message after wxAutoBufferedPaintDC +#ifdef __APPLE__ + m_user_drawn_background = false; +#endif /*__APPLE__*/ + Bind(wxEVT_PAINT, ([this](wxPaintEvent e) { repaint(); })); + Bind(wxEVT_LEFT_DOWN, ([this](wxMouseEvent event) { mouse_event(event); })); + Bind(wxEVT_MOTION, ([this](wxMouseEvent event) { mouse_event(event); })); + Bind(wxEVT_SIZE, ([this](wxSizeEvent e) { Refresh(); })); +} void Bed_2D::repaint() { wxAutoBufferedPaintDC dc(this); diff --git a/src/slic3r/GUI/2DBed.hpp b/src/slic3r/GUI/2DBed.hpp index 4635619533..579ef44458 100644 --- a/src/slic3r/GUI/2DBed.hpp +++ b/src/slic3r/GUI/2DBed.hpp @@ -25,21 +25,7 @@ class Bed_2D : public wxPanel void set_pos(Vec2d pos); public: - Bed_2D(wxWindow* parent) - { - Create(parent, wxID_ANY, wxDefaultPosition, wxSize(250, -1), wxTAB_TRAVERSAL); - SetBackgroundStyle(wxBG_STYLE_PAINT); // to avoid assert message after wxAutoBufferedPaintDC -// m_user_drawn_background = $^O ne 'darwin'; -#ifdef __APPLE__ - m_user_drawn_background = false; -#endif /*__APPLE__*/ - Bind(wxEVT_PAINT, ([this](wxPaintEvent e) { repaint(); })); -// EVT_ERASE_BACKGROUND($self, sub{}) if $self->{user_drawn_background}; -// Bind(EVT_MOUSE_EVENTS, ([this](wxMouseEvent event) {/*mouse_event()*/; })); - Bind(wxEVT_LEFT_DOWN, ([this](wxMouseEvent event) { mouse_event(event); })); - Bind(wxEVT_MOTION, ([this](wxMouseEvent event) { mouse_event(event); })); - Bind(wxEVT_SIZE, ([this](wxSizeEvent e) { Refresh(); })); - } + Bed_2D(wxWindow* parent); ~Bed_2D() {} std::vector m_bed_shape; diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp new file mode 100644 index 0000000000..d6d25b550b --- /dev/null +++ b/src/slic3r/GUI/3DBed.cpp @@ -0,0 +1,796 @@ +#include "libslic3r/libslic3r.h" + +#include "3DBed.hpp" + +#include "libslic3r/Polygon.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/BoundingBox.hpp" + +#include "GUI_App.hpp" +#include "PresetBundle.hpp" + +#include + +#include + +static const float GROUND_Z = -0.02f; + +namespace Slic3r { +namespace GUI { + +bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords) +{ +#if ENABLE_TEXTURES_FROM_SVG + m_vertices.clear(); + unsigned int v_size = 3 * (unsigned int)triangles.size(); + + if (v_size == 0) + return false; + + m_vertices = std::vector(v_size, Vertex()); + + float min_x = unscale(triangles[0].points[0](0)); + float min_y = unscale(triangles[0].points[0](1)); + float max_x = min_x; + float max_y = min_y; + + unsigned int v_count = 0; + for (const Polygon& t : triangles) + { + for (unsigned int i = 0; i < 3; ++i) + { + Vertex& v = m_vertices[v_count]; + + const Point& p = t.points[i]; + float x = unscale(p(0)); + float y = unscale(p(1)); + + v.position[0] = x; + v.position[1] = y; + v.position[2] = z; + + if (generate_tex_coords) + { + v.tex_coords[0] = x; + v.tex_coords[1] = y; + + min_x = std::min(min_x, x); + max_x = std::max(max_x, x); + min_y = std::min(min_y, y); + max_y = std::max(max_y, y); + } + + ++v_count; + } + } + + if (generate_tex_coords) + { + float size_x = max_x - min_x; + float size_y = max_y - min_y; + + if ((size_x != 0.0f) && (size_y != 0.0f)) + { + float inv_size_x = 1.0f / size_x; + float inv_size_y = -1.0f / size_y; + for (Vertex& v : m_vertices) + { + v.tex_coords[0] = (v.tex_coords[0] - min_x) * inv_size_x; + v.tex_coords[1] = (v.tex_coords[1] - min_y) * inv_size_y; + } + } + } +#else + m_vertices.clear(); + m_tex_coords.clear(); + + unsigned int v_size = 9 * (unsigned int)triangles.size(); + unsigned int t_size = 6 * (unsigned int)triangles.size(); + if (v_size == 0) + return false; + + m_vertices = std::vector(v_size, 0.0f); + if (generate_tex_coords) + m_tex_coords = std::vector(t_size, 0.0f); + + float min_x = unscale(triangles[0].points[0](0)); + float min_y = unscale(triangles[0].points[0](1)); + float max_x = min_x; + float max_y = min_y; + + unsigned int v_coord = 0; + unsigned int t_coord = 0; + for (const Polygon& t : triangles) + { + for (unsigned int v = 0; v < 3; ++v) + { + const Point& p = t.points[v]; + float x = unscale(p(0)); + float y = unscale(p(1)); + + m_vertices[v_coord++] = x; + m_vertices[v_coord++] = y; + m_vertices[v_coord++] = z; + + if (generate_tex_coords) + { + m_tex_coords[t_coord++] = x; + m_tex_coords[t_coord++] = y; + + min_x = std::min(min_x, x); + max_x = std::max(max_x, x); + min_y = std::min(min_y, y); + max_y = std::max(max_y, y); + } + } + } + + if (generate_tex_coords) + { + float size_x = max_x - min_x; + float size_y = max_y - min_y; + + if ((size_x != 0.0f) && (size_y != 0.0f)) + { + float inv_size_x = 1.0f / size_x; + float inv_size_y = -1.0f / size_y; + for (unsigned int i = 0; i < m_tex_coords.size(); i += 2) + { + m_tex_coords[i] = (m_tex_coords[i] - min_x) * inv_size_x; + m_tex_coords[i + 1] = (m_tex_coords[i + 1] - min_y) * inv_size_y; + } + } + } +#endif // ENABLE_TEXTURES_FROM_SVG + + return true; +} + +bool GeometryBuffer::set_from_lines(const Lines& lines, float z) +{ +#if ENABLE_TEXTURES_FROM_SVG + m_vertices.clear(); + + unsigned int v_size = 2 * (unsigned int)lines.size(); + if (v_size == 0) + return false; + + m_vertices = std::vector(v_size, Vertex()); + + unsigned int v_count = 0; + for (const Line& l : lines) + { + Vertex& v1 = m_vertices[v_count]; + v1.position[0] = unscale(l.a(0)); + v1.position[1] = unscale(l.a(1)); + v1.position[2] = z; + ++v_count; + + Vertex& v2 = m_vertices[v_count]; + v2.position[0] = unscale(l.b(0)); + v2.position[1] = unscale(l.b(1)); + v2.position[2] = z; + ++v_count; + } +#else + m_vertices.clear(); + m_tex_coords.clear(); + + unsigned int size = 6 * (unsigned int)lines.size(); + if (size == 0) + return false; + + m_vertices = std::vector(size, 0.0f); + + unsigned int coord = 0; + for (const Line& l : lines) + { + m_vertices[coord++] = unscale(l.a(0)); + m_vertices[coord++] = unscale(l.a(1)); + m_vertices[coord++] = z; + m_vertices[coord++] = unscale(l.b(0)); + m_vertices[coord++] = unscale(l.b(1)); + m_vertices[coord++] = z; + } +#endif // ENABLE_TEXTURES_FROM_SVG + + return true; +} + +#if ENABLE_TEXTURES_FROM_SVG +const float* GeometryBuffer::get_vertices_data() const +{ + return (m_vertices.size() > 0) ? (const float*)m_vertices.data() : nullptr; +} +#endif // ENABLE_TEXTURES_FROM_SVG + +const double Bed3D::Axes::Radius = 0.5; +const double Bed3D::Axes::ArrowBaseRadius = 2.5 * Bed3D::Axes::Radius; +const double Bed3D::Axes::ArrowLength = 5.0; + +Bed3D::Axes::Axes() +: origin(Vec3d::Zero()) +, length(Vec3d::Zero()) +{ + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +} + +Bed3D::Axes::~Axes() +{ + if (m_quadric != nullptr) + ::gluDeleteQuadric(m_quadric); +} + +void Bed3D::Axes::render() const +{ + if (m_quadric == nullptr) + return; + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_LIGHTING)); + + // x axis + glsafe(::glColor3f(1.0f, 0.0f, 0.0f)); + glsafe(::glPushMatrix()); + glsafe(::glTranslated(origin(0), origin(1), origin(2))); + glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); + render_axis(length(0)); + glsafe(::glPopMatrix()); + + // y axis + glsafe(::glColor3f(0.0f, 1.0f, 0.0f)); + glsafe(::glPushMatrix()); + glsafe(::glTranslated(origin(0), origin(1), origin(2))); + glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); + render_axis(length(1)); + glsafe(::glPopMatrix()); + + // z axis + glsafe(::glColor3f(0.0f, 0.0f, 1.0f)); + glsafe(::glPushMatrix()); + glsafe(::glTranslated(origin(0), origin(1), origin(2))); + render_axis(length(2)); + glsafe(::glPopMatrix()); + + glsafe(::glDisable(GL_LIGHTING)); +} + +void Bed3D::Axes::render_axis(double length) const +{ + ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); + ::gluCylinder(m_quadric, Radius, Radius, length, 32, 1); + ::gluQuadricOrientation(m_quadric, GLU_INSIDE); + ::gluDisk(m_quadric, 0.0, Radius, 32, 1); + glsafe(::glTranslated(0.0, 0.0, length)); + ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); + ::gluCylinder(m_quadric, ArrowBaseRadius, 0.0, ArrowLength, 32, 1); + ::gluQuadricOrientation(m_quadric, GLU_INSIDE); + ::gluDisk(m_quadric, 0.0, ArrowBaseRadius, 32, 1); +} + +Bed3D::Bed3D() + : m_type(Custom) +#if ENABLE_TEXTURES_FROM_SVG + , m_vbo_id(0) +#endif // ENABLE_TEXTURES_FROM_SVG + , m_scale_factor(1.0f) +{ +} + +bool Bed3D::set_shape(const Pointfs& shape) +{ + EType new_type = detect_type(shape); + if (m_shape == shape && m_type == new_type) + // No change, no need to update the UI. + return false; + + m_shape = shape; + m_type = new_type; + + calc_bounding_box(); + + ExPolygon poly; + for (const Vec2d& p : m_shape) + { + poly.contour.append(Point(scale_(p(0)), scale_(p(1)))); + } + + calc_triangles(poly); + + const BoundingBox& bed_bbox = poly.contour.bounding_box(); + calc_gridlines(poly, bed_bbox); + + m_polygon = offset_ex(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0].contour; + +#if ENABLE_TEXTURES_FROM_SVG + reset(); +#endif // ENABLE_TEXTURES_FROM_SVG + + // Set the origin and size for painting of the coordinate system axes. + m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z); + m_axes.length = 0.1 * get_bounding_box().max_size() * Vec3d::Ones(); + + // Let the calee to update the UI. + return true; +} + +bool Bed3D::contains(const Point& point) const +{ + return m_polygon.contains(point); +} + +Point Bed3D::point_projection(const Point& point) const +{ + return m_polygon.point_projection(point); +} + +#if ENABLE_TEXTURES_FROM_SVG +void Bed3D::render(float theta, bool useVBOs, float scale_factor) const +{ + m_scale_factor = scale_factor; + + EType type = useVBOs ? m_type : Custom; + switch (type) + + { + case MK2: + { + render_prusa("mk2", theta > 90.0f); + break; + } + case MK3: + { + render_prusa("mk3", theta > 90.0f); + break; + } + case SL1: + { + render_prusa("sl1", theta > 90.0f); + break; + } + default: + case Custom: + { + render_custom(); + break; + } + } +} +#else +void Bed3D::render(float theta, bool useVBOs, float scale_factor) const +{ + m_scale_factor = scale_factor; + + if (m_shape.empty()) + return; + + switch (m_type) + { + case MK2: + { + render_prusa("mk2", theta, useVBOs); + break; + } + case MK3: + { + render_prusa("mk3", theta, useVBOs); + break; + } + case SL1: + { + render_prusa("sl1", theta, useVBOs); + break; + } + default: + case Custom: + { + render_custom(); + break; + } + } +} +#endif // ENABLE_TEXTURES_FROM_SVG + +void Bed3D::render_axes() const +{ + if (!m_shape.empty()) + m_axes.render(); +} + +void Bed3D::calc_bounding_box() +{ + m_bounding_box = BoundingBoxf3(); + for (const Vec2d& p : m_shape) + { + m_bounding_box.merge(Vec3d(p(0), p(1), 0.0)); + } +} + +void Bed3D::calc_triangles(const ExPolygon& poly) +{ + Polygons triangles; + poly.triangulate(&triangles); + + if (!m_triangles.set_from_triangles(triangles, GROUND_Z, m_type != Custom)) + printf("Unable to create bed triangles\n"); +} + +void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox) +{ + Polylines axes_lines; + for (coord_t x = bed_bbox.min(0); x <= bed_bbox.max(0); x += scale_(10.0)) + { + Polyline line; + line.append(Point(x, bed_bbox.min(1))); + line.append(Point(x, bed_bbox.max(1))); + axes_lines.push_back(line); + } + for (coord_t y = bed_bbox.min(1); y <= bed_bbox.max(1); y += scale_(10.0)) + { + Polyline line; + line.append(Point(bed_bbox.min(0), y)); + line.append(Point(bed_bbox.max(0), y)); + axes_lines.push_back(line); + } + + // clip with a slightly grown expolygon because our lines lay on the contours and may get erroneously clipped + Lines gridlines = to_lines(intersection_pl(axes_lines, offset(poly, (float)SCALED_EPSILON))); + + // append bed contours + Lines contour_lines = to_lines(poly); + std::copy(contour_lines.begin(), contour_lines.end(), std::back_inserter(gridlines)); + + if (!m_gridlines.set_from_lines(gridlines, GROUND_Z)) + printf("Unable to create bed grid lines\n"); +} + +Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const +{ + EType type = Custom; + + auto bundle = wxGetApp().preset_bundle; + if (bundle != nullptr) + { + const Preset* curr = &bundle->printers.get_selected_preset(); + while (curr != nullptr) + { + if (curr->config.has("bed_shape")) + { + if ((curr->vendor != nullptr) && (curr->vendor->name == "Prusa Research") && (shape == dynamic_cast(curr->config.option("bed_shape"))->values)) + { + if (boost::contains(curr->name, "SL1")) + { + type = SL1; + break; + } + else if (boost::contains(curr->name, "MK3") || boost::contains(curr->name, "MK2.5")) + { + type = MK3; + break; + } + else if (boost::contains(curr->name, "MK2")) + { + type = MK2; + break; + } + } + } + + curr = bundle->printers.get_preset_parent(*curr); + } + } + + return type; +} + +#if ENABLE_TEXTURES_FROM_SVG +void Bed3D::render_prusa(const std::string &key, bool bottom) const +{ + std::string tex_path = resources_dir() + "/icons/bed/" + key; + + std::string model_path = resources_dir() + "/models/" + key; + + // use anisotropic filter if graphic card allows + GLfloat max_anisotropy = 0.0f; + if (glewIsSupported("GL_EXT_texture_filter_anisotropic")) + ::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy); + + // use higher resolution images if graphic card allows + GLint max_tex_size; + ::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size); + + // clamp or the texture generation becomes too slow + max_tex_size = std::min(max_tex_size, 8192); + + std::string filename = tex_path + ".svg"; + + if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename)) + { + if (!m_texture.load_from_svg_file(filename, true, max_tex_size)) + { + render_custom(); + return; + } + + if (max_anisotropy > 0.0f) + { + ::glBindTexture(GL_TEXTURE_2D, m_texture.get_id()); + ::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy); + ::glBindTexture(GL_TEXTURE_2D, 0); + } + } + + if (!bottom) + { + filename = model_path + "_bed.stl"; + if ((m_model.get_filename() != filename) && m_model.init_from_file(filename, true)) { + Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); + if (key == "mk2") + // hardcoded value to match the stl model + offset += Vec3d(0.0, 7.5, -0.03); + else if (key == "mk3") + // hardcoded value to match the stl model + offset += Vec3d(0.0, 5.5, 2.43); + else if (key == "sl1") + // hardcoded value to match the stl model + offset += Vec3d(0.0, 0.0, -0.03); + + m_model.center_around(offset); + } + + if (!m_model.get_filename().empty()) + { + ::glEnable(GL_LIGHTING); + m_model.render(); + ::glDisable(GL_LIGHTING); + } + } + + unsigned int triangles_vcount = m_triangles.get_vertices_count(); + if (triangles_vcount > 0) + { + if (m_vbo_id == 0) + { + ::glGenBuffers(1, &m_vbo_id); + ::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id); + ::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW); + ::glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_position_offset()); + ::glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_tex_coords_offset()); + ::glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + ::glEnable(GL_DEPTH_TEST); + ::glDepthMask(GL_FALSE); + + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + ::glEnable(GL_TEXTURE_2D); + ::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + if (bottom) + ::glFrontFace(GL_CW); + + render_prusa_shader(triangles_vcount, bottom); + + if (bottom) + ::glFrontFace(GL_CCW); + + ::glDisable(GL_TEXTURE_2D); + + ::glDisable(GL_BLEND); + ::glDepthMask(GL_TRUE); + } +} + +void Bed3D::render_prusa_shader(unsigned int vertices_count, bool transparent) const +{ + if (m_shader.get_shader_program_id() == 0) + m_shader.init("printbed.vs", "printbed.fs"); + + if (m_shader.is_initialized()) + { + m_shader.start_using(); + m_shader.set_uniform("transparent_background", transparent); + + ::glBindTexture(GL_TEXTURE_2D, (GLuint)m_texture.get_id()); + ::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id); + ::glEnableVertexAttribArray(0); + ::glEnableVertexAttribArray(1); + ::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertices_count); + ::glDisableVertexAttribArray(1); + ::glDisableVertexAttribArray(0); + ::glBindBuffer(GL_ARRAY_BUFFER, 0); + ::glBindTexture(GL_TEXTURE_2D, 0); + + m_shader.stop_using(); + } +} + +#else +void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) const +{ + std::string tex_path = resources_dir() + "/icons/bed/" + key; + + // use higher resolution images if graphic card allows + GLint max_tex_size; + ::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size); + + // temporary set to lowest resolution + max_tex_size = 2048; + + if (max_tex_size >= 8192) + tex_path += "_8192"; + else if (max_tex_size >= 4096) + tex_path += "_4096"; + + std::string model_path = resources_dir() + "/models/" + key; + + // use anisotropic filter if graphic card allows + GLfloat max_anisotropy = 0.0f; + if (glewIsSupported("GL_EXT_texture_filter_anisotropic")) + ::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy); + + std::string filename = tex_path + "_top.png"; + if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) + { + if (!m_top_texture.load_from_file(filename, true)) + { + render_custom(); + return; + } + + if (max_anisotropy > 0.0f) + { + glsafe(::glBindTexture(GL_TEXTURE_2D, m_top_texture.get_id())); + glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + } + } + + filename = tex_path + "_bottom.png"; + if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename)) + { + if (!m_bottom_texture.load_from_file(filename, true)) + { + render_custom(); + return; + } + + if (max_anisotropy > 0.0f) + { + glsafe(::glBindTexture(GL_TEXTURE_2D, m_bottom_texture.get_id())); + glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + } + } + + if (theta <= 90.0f) + { + filename = model_path + "_bed.stl"; + if ((m_model.get_filename() != filename) && m_model.init_from_file(filename, useVBOs)) { + Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); + if (key == "mk2") + // hardcoded value to match the stl model + offset += Vec3d(0.0, 7.5, -0.03); + else if (key == "mk3") + // hardcoded value to match the stl model + offset += Vec3d(0.0, 5.5, 2.43); + else if (key == "sl1") + // hardcoded value to match the stl model + offset += Vec3d(0.0, 0.0, -0.03); + + m_model.center_around(offset); + } + + if (!m_model.get_filename().empty()) + { + glsafe(::glEnable(GL_LIGHTING)); + m_model.render(); + glsafe(::glDisable(GL_LIGHTING)); + } + } + + unsigned int triangles_vcount = m_triangles.get_vertices_count(); + if (triangles_vcount > 0) + { + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDepthMask(GL_FALSE)); + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + glsafe(::glEnable(GL_TEXTURE_2D)); + glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)); + + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + glsafe(::glEnableClientState(GL_TEXTURE_COORD_ARRAY)); + + if (theta > 90.0f) + glsafe(::glFrontFace(GL_CW)); + + glsafe(::glBindTexture(GL_TEXTURE_2D, (theta <= 90.0f) ? (GLuint)m_top_texture.get_id() : (GLuint)m_bottom_texture.get_id())); + glsafe(::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices())); + glsafe(::glTexCoordPointer(2, GL_FLOAT, 0, (GLvoid*)m_triangles.get_tex_coords())); + glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); + + if (theta > 90.0f) + glsafe(::glFrontFace(GL_CCW)); + + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + glsafe(::glDisableClientState(GL_TEXTURE_COORD_ARRAY)); + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glDisable(GL_TEXTURE_2D)); + + glsafe(::glDisable(GL_BLEND)); + glsafe(::glDepthMask(GL_TRUE)); + } +} +#endif // ENABLE_TEXTURES_FROM_SVG + +void Bed3D::render_custom() const +{ +#if ENABLE_TEXTURES_FROM_SVG + m_texture.reset(); +#else + m_top_texture.reset(); + m_bottom_texture.reset(); +#endif // ENABLE_TEXTURES_FROM_SVG + + unsigned int triangles_vcount = m_triangles.get_vertices_count(); + if (triangles_vcount > 0) + { + glsafe(::glEnable(GL_LIGHTING)); + glsafe(::glDisable(GL_DEPTH_TEST)); + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glColor4f(0.35f, 0.35f, 0.35f, 0.4f)); + glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); +#if ENABLE_TEXTURES_FROM_SVG + ::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data()); +#else + glsafe(::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices())); +#endif // ENABLE_TEXTURES_FROM_SVG + glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); + + // draw grid + unsigned int gridlines_vcount = m_gridlines.get_vertices_count(); + + // we need depth test for grid, otherwise it would disappear when looking the object from below + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glLineWidth(3.0f * m_scale_factor)); + glsafe(::glColor4f(0.2f, 0.2f, 0.2f, 0.4f)); +#if ENABLE_TEXTURES_FROM_SVG + ::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data()); +#else + glsafe(::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_gridlines.get_vertices())); +#endif // ENABLE_TEXTURES_FROM_SVG + glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)gridlines_vcount)); + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glDisable(GL_BLEND)); + glsafe(::glDisable(GL_LIGHTING)); + } +} + +#if ENABLE_TEXTURES_FROM_SVG +void Bed3D::reset() +{ + if (m_vbo_id > 0) + { + ::glDeleteBuffers(1, &m_vbo_id); + m_vbo_id = 0; + } +} +#endif // ENABLE_TEXTURES_FROM_SVG + +} // GUI +} // Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp new file mode 100644 index 0000000000..edf3e5ee76 --- /dev/null +++ b/src/slic3r/GUI/3DBed.hpp @@ -0,0 +1,147 @@ +#ifndef slic3r_3DBed_hpp_ +#define slic3r_3DBed_hpp_ + +#include "GLTexture.hpp" +#include "3DScene.hpp" +#if ENABLE_TEXTURES_FROM_SVG +#include "GLShader.hpp" +#endif // ENABLE_TEXTURES_FROM_SVG + +class GLUquadric; +typedef class GLUquadric GLUquadricObj; + +namespace Slic3r { +namespace GUI { + +class GeometryBuffer +{ +#if ENABLE_TEXTURES_FROM_SVG + struct Vertex + { + float position[3]; + float tex_coords[2]; + + Vertex() + { + position[0] = 0.0f; position[1] = 0.0f; position[2] = 0.0f; + tex_coords[0] = 0.0f; tex_coords[1] = 0.0f; + } + }; + + std::vector m_vertices; +#else + std::vector m_vertices; + std::vector m_tex_coords; +#endif // ENABLE_TEXTURES_FROM_SVG + +public: + bool set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords); + bool set_from_lines(const Lines& lines, float z); + +#if ENABLE_TEXTURES_FROM_SVG + const float* get_vertices_data() const; + unsigned int get_vertices_data_size() const { return (unsigned int)m_vertices.size() * get_vertex_data_size(); } + unsigned int get_vertex_data_size() const { return (unsigned int)(5 * sizeof(float)); } + unsigned int get_position_offset() const { return 0; } + unsigned int get_tex_coords_offset() const { return (unsigned int)(3 * sizeof(float)); } + unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size(); } +#else + const float* get_vertices() const { return m_vertices.data(); } + const float* get_tex_coords() const { return m_tex_coords.data(); } + unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size() / 3; } +#endif // ENABLE_TEXTURES_FROM_SVG +}; + +class Bed3D +{ + struct Axes + { + static const double Radius; + static const double ArrowBaseRadius; + static const double ArrowLength; + Vec3d origin; + Vec3d length; + GLUquadricObj* m_quadric; + + Axes(); + ~Axes(); + + void render() const; + + private: + void render_axis(double length) const; + }; + +public: + enum EType : unsigned char + { + MK2, + MK3, + SL1, + Custom, + Num_Types + }; + +private: + EType m_type; + Pointfs m_shape; + BoundingBoxf3 m_bounding_box; + Polygon m_polygon; + GeometryBuffer m_triangles; + GeometryBuffer m_gridlines; +#if ENABLE_TEXTURES_FROM_SVG + mutable GLTexture m_texture; + mutable Shader m_shader; + mutable unsigned int m_vbo_id; +#else + mutable GLTexture m_top_texture; + mutable GLTexture m_bottom_texture; +#endif // ENABLE_TEXTURES_FROM_SVG + mutable GLBed m_model; + Axes m_axes; + + mutable float m_scale_factor; + +public: + Bed3D(); +#if ENABLE_TEXTURES_FROM_SVG + ~Bed3D() { reset(); } +#endif // ENABLE_TEXTURES_FROM_SVG + + EType get_type() const { return m_type; } + + bool is_prusa() const { return (m_type == MK2) || (m_type == MK3) || (m_type == SL1); } + bool is_custom() const { return m_type == Custom; } + + const Pointfs& get_shape() const { return m_shape; } + // Return true if the bed shape changed, so the calee will update the UI. + bool set_shape(const Pointfs& shape); + + const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; } + bool contains(const Point& point) const; + Point point_projection(const Point& point) const; + + void render(float theta, bool useVBOs, float scale_factor) const; + void render_axes() const; + +private: + void calc_bounding_box(); + void calc_triangles(const ExPolygon& poly); + void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); + EType detect_type(const Pointfs& shape) const; +#if ENABLE_TEXTURES_FROM_SVG + void render_prusa(const std::string& key, bool bottom) const; + void render_prusa_shader(unsigned int vertices_count, bool transparent) const; +#else + void render_prusa(const std::string &key, float theta, bool useVBOs) const; +#endif // ENABLE_TEXTURES_FROM_SVG + void render_custom() const; +#if ENABLE_TEXTURES_FROM_SVG + void reset(); +#endif // ENABLE_TEXTURES_FROM_SVG +}; + +} // GUI +} // Slic3r + +#endif // slic3r_3DBed_hpp_ diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 6b1df6ab36..88815d9a68 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -11,9 +11,7 @@ #include "libslic3r/Slicing.hpp" #include "libslic3r/GCode/Analyzer.hpp" #include "slic3r/GUI/PresetBundle.hpp" -#if ENABLE_PRINT_BED_MODELS #include "libslic3r/Format/STL.hpp" -#endif // ENABLE_PRINT_BED_MODELS #include #include @@ -23,10 +21,8 @@ #include -#if ENABLE_PRINT_BED_MODELS #include #include -#endif // ENABLE_PRINT_BED_MODELS #include #include @@ -793,7 +789,7 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool glsafe(::glDisable(GL_BLEND)); } -void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface) const +void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface, std::function filter_func) const { glsafe(glEnable(GL_BLEND)); glsafe(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -805,7 +801,7 @@ void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface) glsafe(glEnableClientState(GL_VERTEX_ARRAY)); glsafe(glEnableClientState(GL_NORMAL_ARRAY)); - GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, std::function()); + GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, filter_func); for (GLVolumeWithZ& volume : to_render) { volume.first->set_render_color(); @@ -1671,27 +1667,19 @@ GUI::GLCanvas3DManager _3DScene::s_canvas_mgr; GLModel::GLModel() : m_useVBOs(false) -#if ENABLE_PRINT_BED_MODELS , m_filename("") -#endif // ENABLE_PRINT_BED_MODELS { m_volume.shader_outside_printer_detection_enabled = false; } GLModel::~GLModel() { -#if ENABLE_PRINT_BED_MODELS reset(); -#else - m_volume.release_geometry(); -#endif // ENABLE_PRINT_BED_MODELS } void GLModel::set_color(const float* color, unsigned int size) { -#if ENABLE_PRINT_BED_MODELS ::memcpy((void*)m_volume.color, (const void*)color, (size_t)(std::min((unsigned int)4, size) * sizeof(float))); -#endif // ENABLE_PRINT_BED_MODELS m_volume.set_render_color(color, size); } @@ -1725,13 +1713,11 @@ void GLModel::set_scale(const Vec3d& scale) m_volume.set_volume_scaling_factor(scale); } -#if ENABLE_PRINT_BED_MODELS void GLModel::reset() { m_volume.release_geometry(); m_filename = ""; } -#endif // ENABLE_PRINT_BED_MODELS void GLModel::render() const { @@ -1968,7 +1954,6 @@ bool GLCurvedArrow::on_init(bool useVBOs) return true; } -#if ENABLE_PRINT_BED_MODELS bool GLBed::on_init_from_file(const std::string& filename, bool useVBOs) { reset(); @@ -2011,7 +1996,6 @@ bool GLBed::on_init_from_file(const std::string& filename, bool useVBOs) return true; } -#endif // ENABLE_PRINT_BED_MODELS std::string _3DScene::get_gl_info(bool format_as_html, bool extensions) { diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 2732b5a137..46cb5b8704 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -456,7 +456,7 @@ public: // Render the volumes by OpenGL. void render_VBOs(ERenderType type, bool disable_cullface, std::function filter_func = std::function()) const; - void render_legacy(ERenderType type, bool disable_cullface) const; + void render_legacy(ERenderType type, bool disable_cullface, std::function filter_func = std::function()) const; // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects @@ -498,20 +498,16 @@ class GLModel protected: GLVolume m_volume; bool m_useVBOs; -#if ENABLE_PRINT_BED_MODELS std::string m_filename; -#endif // ENABLE_PRINT_BED_MODELS public: GLModel(); virtual ~GLModel(); bool init(bool useVBOs) { return on_init(useVBOs); } -#if ENABLE_PRINT_BED_MODELS bool init_from_file(const std::string& filename, bool useVBOs) { return on_init_from_file(filename, useVBOs); } void center_around(const Vec3d& center) { m_volume.set_volume_offset(center - m_volume.bounding_box.center()); } -#endif // ENABLE_PRINT_BED_MODELS void set_color(const float* color, unsigned int size); const Vec3d& get_offset() const; @@ -521,22 +517,16 @@ public: const Vec3d& get_scale() const; void set_scale(const Vec3d& scale); -#if ENABLE_PRINT_BED_MODELS const std::string& get_filename() const { return m_filename; } const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box; } void reset(); -#endif // ENABLE_PRINT_BED_MODELS void render() const; protected: -#if ENABLE_PRINT_BED_MODELS virtual bool on_init(bool useVBOs) { return false; } virtual bool on_init_from_file(const std::string& filename, bool useVBOs) { return false; } -#else - virtual bool on_init(bool useVBOs) = 0; -#endif // ENABLE_PRINT_BED_MODELS private: void render_VBOs() const; @@ -560,13 +550,11 @@ protected: virtual bool on_init(bool useVBOs); }; -#if ENABLE_PRINT_BED_MODELS class GLBed : public GLModel { protected: virtual bool on_init_from_file(const std::string& filename, bool useVBOs); }; -#endif // ENABLE_PRINT_BED_MODELS class _3DScene { diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 08c8839c7e..fb57cfeb9e 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -2,6 +2,8 @@ #include "I18N.hpp" #include "libslic3r/Utils.hpp" +#include "GUI_App.hpp" +#include "wxExtensions.hpp" namespace Slic3r { namespace GUI { @@ -40,17 +42,13 @@ AboutDialog::AboutDialog() main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 20); // logo - 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, 1, wxEXPAND | wxTOP | wxBOTTOM, 35); +// 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)); + auto *logo = new wxStaticBitmap(this, wxID_ANY, create_scaled_bitmap("Slic3r_192px.png")); + hsizer->Add(logo, 1, wxALIGN_CENTER_VERTICAL); - wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); -#ifdef __WXMSW__ - int proportion = 2; -#else - int proportion = 3; -#endif - hsizer->Add(vsizer, proportion, wxEXPAND|wxLEFT, 20); + wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); + hsizer->Add(vsizer, 2, wxEXPAND|wxLEFT, 20); // title { @@ -80,6 +78,7 @@ AboutDialog::AboutDialog() // text wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO/*NEVER*/); { + html->SetMinSize(wxSize(-1, 16 * wxGetApp().em_unit())); wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 463be8397e..7c8164f6ba 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -196,6 +196,7 @@ void BackgroundSlicingProcess::thread_proc() } catch (...) { error = "Unknown C++ exception."; } + m_print->finalize(); lck.lock(); m_state = m_print->canceled() ? STATE_CANCELED : STATE_FINISHED; if (m_print->cancel_status() != Print::CANCELED_INTERNAL) { @@ -362,6 +363,12 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn return invalidated; } +void BackgroundSlicingProcess::set_task(const PrintBase::TaskParams ¶ms) +{ + assert(m_print != nullptr); + m_print->set_task(params); +} + // Set the output path of the G-code. void BackgroundSlicingProcess::schedule_export(const std::string &path) { diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 5911c8a02c..a2299e7bfc 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -78,6 +78,9 @@ public: // Apply config over the print. Returns false, if the new config values caused any of the already // processed steps to be invalidated, therefore the task will need to be restarted. Print::ApplyStatus apply(const Model &model, const DynamicPrintConfig &config); + // After calling the apply() function, set_task() may be called to limit the task to be processed by process(). + // This is useful for calculating SLA supports for a single object only. + void set_task(const PrintBase::TaskParams ¶ms); // After calling apply, the empty() call will report whether there is anything to slice. bool empty() const; // Validate the print. Returns an empty string if valid, returns an error message if invalid. @@ -94,6 +97,7 @@ public: void reset_export(); // Once the G-code export is scheduled, the apply() methods will do nothing. bool is_export_scheduled() const { return ! m_export_path.empty(); } + bool is_upload_scheduled() const { return ! m_upload_job.empty(); } enum State { // m_thread is not running yet, or it did not reach the STATE_IDLE yet (it does not wait on the condition yet). diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index feb44a922d..7bbf1ac7f7 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -44,7 +44,8 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL); // shape options - m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(300, -1), wxCHB_TOP); + m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, + wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); sbsizer->Add(m_shape_options_book); auto optgroup = init_shape_options_page(_(L("Rectangular"))); @@ -124,7 +125,7 @@ ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(wxString title) ConfigOptionsGroupShp optgroup; optgroup = std::make_shared(panel, _(L("Settings"))); - optgroup->label_width = 100; + optgroup->label_width = 10*wxGetApp().em_unit();//100; optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { update_shape(); }; diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index 84752c3fc0..538fccc344 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -42,7 +42,7 @@ class BedShapeDialog : public wxDialog BedShapePanel* m_panel; public: BedShapeDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _(L("Bed Shape")), - wxDefaultPosition, wxSize(350, 700), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {} + wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {} ~BedShapeDialog() {} void build_dialog(ConfigOptionPoints* default_pt); diff --git a/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/src/slic3r/GUI/ConfigSnapshotDialog.cpp index dc396895b7..422f683b33 100644 --- a/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -5,6 +5,7 @@ #include "../Utils/Time.hpp" #include "libslic3r/Utils.hpp" +#include "GUI_App.hpp" namespace Slic3r { namespace GUI { @@ -94,7 +95,9 @@ static wxString generate_html_page(const Config::SnapshotDB &snapshot_db, const } ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const wxString &on_snapshot) - : wxDialog(NULL, wxID_ANY, _(L("Configuration Snapshots")), wxDefaultPosition, wxSize(600, 500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX) + : wxDialog(NULL, wxID_ANY, _(L("Configuration Snapshots")), wxDefaultPosition, + wxSize(45 * wxGetApp().em_unit(), 40 * wxGetApp().em_unit()), + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX) { this->SetBackgroundColour(*wxWHITE); diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 8159eff0b5..e49a0edabc 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -65,9 +65,9 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt auto *sizer = new wxBoxSizer(wxVERTICAL); - const auto font_title = GetFont().MakeBold().Scaled(1.3); + const auto font_title = GetFont().MakeBold().Scaled(1.3f); const auto font_name = GetFont().MakeBold(); - const auto font_alt_nozzle = GetFont().Scaled(0.9); + const auto font_alt_nozzle = GetFont().Scaled(0.9f); // wxGrid appends widgets by rows, but we need to construct them in columns. // These vectors are used to hold the elements so that they can be appended in the right order. @@ -789,7 +789,7 @@ void ConfigWizardIndex::on_mouse_move(wxMouseEvent &evt) const ssize_t item_hover_new = pos.y / item_height(); - if (item_hover_new < items.size() && item_hover_new != item_hover) { + if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) { item_hover = item_hover_new; Refresh(); } diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 28b0d94aad..0d65e0ef51 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -631,14 +631,19 @@ void Choice::set_value(const boost::any& value, bool change_event) break; ++idx; } - idx == m_opt.enum_values.size() ? - dynamic_cast(window)->SetValue(text_value) : + if (idx == m_opt.enum_values.size()) { + // For editable Combobox under OSX is needed to set selection to -1 explicitly, + // otherwise selection doesn't be changed + dynamic_cast(window)->SetSelection(-1); + dynamic_cast(window)->SetValue(text_value); + } + else dynamic_cast(window)->SetSelection(idx); break; } case coEnum: { int val = boost::any_cast(value); - if (m_opt_id.compare("external_fill_pattern") == 0) + if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern") { if (!m_opt.enum_values.empty()) { std::string key; @@ -707,7 +712,7 @@ boost::any& Choice::get_value() if (m_opt.type == coEnum) { int ret_enum = static_cast(window)->GetSelection(); - if (m_opt_id.compare("external_fill_pattern") == 0) + if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern") { if (!m_opt.enum_values.empty()) { std::string key = m_opt.enum_values[ret_enum]; @@ -785,14 +790,9 @@ boost::any& ColourPicker::get_value() void PointCtrl::BUILD() { - auto size = wxSize(wxDefaultSize); - if (m_opt.height >= 0) size.SetHeight(m_opt.height); - if (m_opt.width >= 0) size.SetWidth(m_opt.width); - auto temp = new wxBoxSizer(wxHORIZONTAL); - // $self->wxSizer($sizer); - // - wxSize field_size(40, -1); + + const wxSize field_size(4 * wxGetApp().em_unit(), -1); auto default_pt = static_cast(m_opt.default_value)->values.at(0); double val = default_pt(0); diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index 2df1f0bc99..20ea0c16da 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -13,6 +13,7 @@ #include "libslic3r/Utils.hpp" #include "avrdude/avrdude-slic3r.hpp" #include "GUI.hpp" +#include "GUI_App.hpp" #include "I18N.hpp" #include "MsgDialog.hpp" #include "../Utils/HexFile.hpp" @@ -36,7 +37,6 @@ #include #include #include -#include "GUI_App.hpp" namespace fs = boost::filesystem; @@ -446,7 +446,7 @@ void FirmwareDialog::priv::prepare_common() "-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(), }}; - BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " + BOOST_LOG_TRIVIAL(info) << "Preparing arguments avrdude: " << std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) { return a + ' ' + b; }); @@ -492,7 +492,7 @@ void FirmwareDialog::priv::prepare_mk3() "-U", (boost::format("flash:w:1:%1%:i") % hex_file.path.string()).str(), }}; - BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: " + BOOST_LOG_TRIVIAL(info) << "Preparing avrdude arguments for external flash flashing: " << std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) { return a + ' ' + b; }); @@ -522,7 +522,7 @@ void FirmwareDialog::priv::prepare_mm_control() "-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(), }}; - BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " + BOOST_LOG_TRIVIAL(info) << "Preparing avrdude arguments: " << std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) { return a + ' ' + b; }); @@ -588,6 +588,13 @@ void FirmwareDialog::priv::perform_upload() auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId()); auto wxmsg = wxString::FromUTF8(msg); +#ifdef WIN32 + // The string might be in local encoding + if (wxmsg.IsEmpty() && *msg != '\0') { + wxmsg = wxString(msg); + } +#endif + evt->SetExtraLong(AE_MESSAGE); evt->SetString(std::move(wxmsg)); wxQueueEvent(q, evt); @@ -693,11 +700,16 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : enum { DIALOG_MARGIN = 15, SPACING = 10, - MIN_WIDTH = 600, - MIN_HEIGHT = 200, - MIN_HEIGHT_EXPANDED = 500, + MIN_WIDTH = 50, + MIN_HEIGHT = 18, + MIN_HEIGHT_EXPANDED = 40, }; + const int em = GUI::wxGetApp().em_unit(); + int min_width = MIN_WIDTH * em; + int min_height = MIN_HEIGHT * em; + int min_height_expanded = MIN_HEIGHT_EXPANDED * em; + wxFont status_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); status_font.MakeBold(); wxFont mono_font(wxFontInfo().Family(wxFONTFAMILY_TELETYPE)); @@ -769,10 +781,10 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : auto *topsizer = new wxBoxSizer(wxVERTICAL); topsizer->Add(panel, 1, wxEXPAND | wxALL, DIALOG_MARGIN); - SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT)); + SetMinSize(wxSize(min_width, min_height)); SetSizerAndFit(topsizer); const auto size = GetSize(); - SetSize(std::max(size.GetWidth(), static_cast(MIN_WIDTH)), std::max(size.GetHeight(), static_cast(MIN_HEIGHT))); + SetSize(std::max(size.GetWidth(), static_cast(min_width)), std::max(size.GetHeight(), static_cast(min_height))); Layout(); SetEscapeId(wxID_CLOSE); // To close the dialog using "Esc" button @@ -786,13 +798,13 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : } }); - p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [this](wxCollapsiblePaneEvent &evt) { + p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [=](wxCollapsiblePaneEvent &evt) { if (evt.GetCollapsed()) { - this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT)); + this->SetMinSize(wxSize(min_width, min_height)); const auto new_height = this->GetSize().GetHeight() - this->p->txt_stdout->GetSize().GetHeight(); this->SetSize(this->GetSize().GetWidth(), new_height); } else { - this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT_EXPANDED)); + this->SetMinSize(wxSize(min_width, min_height_expanded)); } this->Layout(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index bea4bd0b47..24fcd4a703 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -84,112 +84,6 @@ static const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f namespace Slic3r { namespace GUI { -bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords) -{ - m_vertices.clear(); - m_tex_coords.clear(); - - unsigned int v_size = 9 * (unsigned int)triangles.size(); - unsigned int t_size = 6 * (unsigned int)triangles.size(); - if (v_size == 0) - return false; - - m_vertices = std::vector(v_size, 0.0f); - if (generate_tex_coords) - m_tex_coords = std::vector(t_size, 0.0f); - - float min_x = unscale(triangles[0].points[0](0)); - float min_y = unscale(triangles[0].points[0](1)); - float max_x = min_x; - float max_y = min_y; - - unsigned int v_coord = 0; - unsigned int t_coord = 0; - for (const Polygon& t : triangles) - { - for (unsigned int v = 0; v < 3; ++v) - { - const Point& p = t.points[v]; - float x = unscale(p(0)); - float y = unscale(p(1)); - - m_vertices[v_coord++] = x; - m_vertices[v_coord++] = y; - m_vertices[v_coord++] = z; - - if (generate_tex_coords) - { - m_tex_coords[t_coord++] = x; - m_tex_coords[t_coord++] = y; - - min_x = std::min(min_x, x); - max_x = std::max(max_x, x); - min_y = std::min(min_y, y); - max_y = std::max(max_y, y); - } - } - } - - if (generate_tex_coords) - { - float size_x = max_x - min_x; - float size_y = max_y - min_y; - - if ((size_x != 0.0f) && (size_y != 0.0f)) - { - float inv_size_x = 1.0f / size_x; - float inv_size_y = -1.0f / size_y; - for (unsigned int i = 0; i < m_tex_coords.size(); i += 2) - { - m_tex_coords[i] = (m_tex_coords[i] - min_x) * inv_size_x; - m_tex_coords[i + 1] = (m_tex_coords[i + 1] - min_y) * inv_size_y; - } - } - } - - return true; -} - -bool GeometryBuffer::set_from_lines(const Lines& lines, float z) -{ - m_vertices.clear(); - m_tex_coords.clear(); - - unsigned int size = 6 * (unsigned int)lines.size(); - if (size == 0) - return false; - - m_vertices = std::vector(size, 0.0f); - - unsigned int coord = 0; - for (const Line& l : lines) - { - m_vertices[coord++] = unscale(l.a(0)); - m_vertices[coord++] = unscale(l.a(1)); - m_vertices[coord++] = z; - m_vertices[coord++] = unscale(l.b(0)); - m_vertices[coord++] = unscale(l.b(1)); - m_vertices[coord++] = z; - } - - return true; -} - -const float* GeometryBuffer::get_vertices() const -{ - return m_vertices.data(); -} - -const float* GeometryBuffer::get_tex_coords() const -{ - return m_tex_coords.data(); -} - -unsigned int GeometryBuffer::get_vertices_count() const -{ - return (unsigned int)m_vertices.size() / 3; -} - Size::Size() : m_width(0) , m_height(0) @@ -344,502 +238,7 @@ void GLCanvas3D::Camera::set_scene_box(const BoundingBoxf3& box, GLCanvas3D& can } } -GLCanvas3D::Bed::Bed() - : m_type(Custom) - , m_scale_factor(1.0f) -{ -} - -bool GLCanvas3D::Bed::is_prusa() const -{ - return (m_type == MK2) || (m_type == MK3) || (m_type == SL1); -} - -bool GLCanvas3D::Bed::is_custom() const -{ - return m_type == Custom; -} - -const Pointfs& GLCanvas3D::Bed::get_shape() const -{ - return m_shape; -} - -bool GLCanvas3D::Bed::set_shape(const Pointfs& shape) -{ -#if ENABLE_REWORKED_BED_SHAPE_CHANGE - EType new_type = _detect_type(shape); -#else - EType new_type = _detect_type(); -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE - if (m_shape == shape && m_type == new_type) - // No change, no need to update the UI. - return false; - - m_shape = shape; - m_type = new_type; - - _calc_bounding_box(); - - ExPolygon poly; - for (const Vec2d& p : m_shape) - { - poly.contour.append(Point(scale_(p(0)), scale_(p(1)))); - } - - _calc_triangles(poly); - - const BoundingBox& bed_bbox = poly.contour.bounding_box(); - _calc_gridlines(poly, bed_bbox); - - m_polygon = offset_ex(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0].contour; - // Let the calee to update the UI. - return true; -} - -const BoundingBoxf3& GLCanvas3D::Bed::get_bounding_box() const -{ - return m_bounding_box; -} - -bool GLCanvas3D::Bed::contains(const Point& point) const -{ - return m_polygon.contains(point); -} - -Point GLCanvas3D::Bed::point_projection(const Point& point) const -{ - return m_polygon.point_projection(point); -} - -#if ENABLE_PRINT_BED_MODELS -void GLCanvas3D::Bed::render(float theta, bool useVBOs, float scale_factor) const -{ - m_scale_factor = scale_factor; - - switch (m_type) - { - case MK2: - { - _render_prusa("mk2", theta, useVBOs); - break; - } - case MK3: - { - _render_prusa("mk3", theta, useVBOs); - break; - } - case SL1: - { - _render_prusa("sl1", theta, useVBOs); - break; - } - default: - case Custom: - { - _render_custom(); - break; - } - } -} -#else -void GLCanvas3D::Bed::render(float theta, float scale_factor) const -{ - m_scale_factor = scale_factor; - - switch (m_type) - { - case MK2: - { - _render_prusa("mk2", theta); - break; - } - case MK3: - { - _render_prusa("mk3", theta); - break; - } - case SL1: - { - _render_prusa("sl1", theta); - break; - } - default: - case Custom: - { - _render_custom(); - break; - } - } -} -#endif // ENABLE_PRINT_BED_MODELS - -void GLCanvas3D::Bed::_calc_bounding_box() -{ - m_bounding_box = BoundingBoxf3(); - for (const Vec2d& p : m_shape) - { - m_bounding_box.merge(Vec3d(p(0), p(1), 0.0)); - } -} - -void GLCanvas3D::Bed::_calc_triangles(const ExPolygon& poly) -{ - Polygons triangles; - poly.triangulate(&triangles); - - if (!m_triangles.set_from_triangles(triangles, GROUND_Z, m_type != Custom)) - printf("Unable to create bed triangles\n"); -} - -void GLCanvas3D::Bed::_calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox) -{ - Polylines axes_lines; - for (coord_t x = bed_bbox.min(0); x <= bed_bbox.max(0); x += scale_(10.0)) - { - Polyline line; - line.append(Point(x, bed_bbox.min(1))); - line.append(Point(x, bed_bbox.max(1))); - axes_lines.push_back(line); - } - for (coord_t y = bed_bbox.min(1); y <= bed_bbox.max(1); y += scale_(10.0)) - { - Polyline line; - line.append(Point(bed_bbox.min(0), y)); - line.append(Point(bed_bbox.max(0), y)); - axes_lines.push_back(line); - } - - // clip with a slightly grown expolygon because our lines lay on the contours and may get erroneously clipped - Lines gridlines = to_lines(intersection_pl(axes_lines, offset(poly, (float)SCALED_EPSILON))); - - // append bed contours - Lines contour_lines = to_lines(poly); - std::copy(contour_lines.begin(), contour_lines.end(), std::back_inserter(gridlines)); - - if (!m_gridlines.set_from_lines(gridlines, GROUND_Z)) - printf("Unable to create bed grid lines\n"); -} - -#if ENABLE_REWORKED_BED_SHAPE_CHANGE -GLCanvas3D::Bed::EType GLCanvas3D::Bed::_detect_type(const Pointfs& shape) const -#else -GLCanvas3D::Bed::EType GLCanvas3D::Bed::_detect_type() const -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE -{ - EType type = Custom; - - auto bundle = wxGetApp().preset_bundle; - if (bundle != nullptr) - { - const Preset* curr = &bundle->printers.get_selected_preset(); - while (curr != nullptr) - { - if (curr->config.has("bed_shape")) - { -#if ENABLE_REWORKED_BED_SHAPE_CHANGE - if ((curr->vendor != nullptr) && (curr->vendor->name == "Prusa Research") && (shape == dynamic_cast(curr->config.option("bed_shape"))->values)) - { - if (boost::contains(curr->name, "SL1")) - { - type = SL1; - break; - } - else if (boost::contains(curr->name, "MK3") || boost::contains(curr->name, "MK2.5")) - { - type = MK3; - break; - } - else if (boost::contains(curr->name, "MK2")) - { - type = MK2; - break; - } - } -#else - if (boost::contains(curr->name, "SL1")) - { - //FIXME add a condition on the size of the print bed? - type = SL1; - break; - } - else if (_are_equal(m_shape, dynamic_cast(curr->config.option("bed_shape"))->values)) - { - if ((curr->vendor != nullptr) && (curr->vendor->name == "Prusa Research")) - { - if (boost::contains(curr->name, "MK3") || boost::contains(curr->name, "MK2.5")) - { - type = MK3; - break; - } else if (boost::contains(curr->name, "MK2")) - { - type = MK2; - break; - } - } - } -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE - } - - curr = bundle->printers.get_preset_parent(*curr); - } - } - - return type; -} - -#if ENABLE_PRINT_BED_MODELS -void GLCanvas3D::Bed::_render_prusa(const std::string &key, float theta, bool useVBOs) const -#else -void GLCanvas3D::Bed::_render_prusa(const std::string &key, float theta) const -#endif // ENABLE_PRINT_BED_MODELS -{ - std::string tex_path = resources_dir() + "/icons/bed/" + key; - - // use higher resolution images if graphic card allows - GLint max_tex_size; - ::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size); - - // temporary set to lowest resolution - max_tex_size = 2048; - - if (max_tex_size >= 8192) - tex_path += "_8192"; - else if (max_tex_size >= 4096) - tex_path += "_4096"; - -#if ENABLE_PRINT_BED_MODELS - std::string model_path = resources_dir() + "/models/" + key; -#endif // ENABLE_PRINT_BED_MODELS - -#if ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES - // use anisotropic filter if graphic card allows - GLfloat max_anisotropy = 0.0f; - if (glewIsSupported("GL_EXT_texture_filter_anisotropic")) - ::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy); -#endif // ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES - - std::string filename = tex_path + "_top.png"; - if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) - { - if (!m_top_texture.load_from_file(filename, true)) - { - _render_custom(); - return; - } -#if ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES - if (max_anisotropy > 0.0f) - { - ::glBindTexture(GL_TEXTURE_2D, m_top_texture.get_id()); - ::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy); - ::glBindTexture(GL_TEXTURE_2D, 0); - } -#endif // ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES - } - - filename = tex_path + "_bottom.png"; - if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename)) - { - if (!m_bottom_texture.load_from_file(filename, true)) - { - _render_custom(); - return; - } -#if ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES - if (max_anisotropy > 0.0f) - { - ::glBindTexture(GL_TEXTURE_2D, m_bottom_texture.get_id()); - ::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy); - ::glBindTexture(GL_TEXTURE_2D, 0); - } -#endif // ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES - } - -#if ENABLE_PRINT_BED_MODELS - if (theta <= 90.0f) - { - filename = model_path + "_bed.stl"; - if ((m_model.get_filename() != filename) && m_model.init_from_file(filename, useVBOs)) { - Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); - if (key == "mk2") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 7.5, -0.03); - else if (key == "mk3") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 5.5, 2.43); - else if (key == "sl1") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 0.0, -0.03); - - m_model.center_around(offset); - } - - if (!m_model.get_filename().empty()) - { - ::glEnable(GL_LIGHTING); - m_model.render(); - ::glDisable(GL_LIGHTING); - } - } -#endif // ENABLE_PRINT_BED_MODELS - - unsigned int triangles_vcount = m_triangles.get_vertices_count(); - if (triangles_vcount > 0) - { - ::glEnable(GL_DEPTH_TEST); - ::glDepthMask(GL_FALSE); - - ::glEnable(GL_BLEND); - ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - ::glEnable(GL_TEXTURE_2D); - ::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - - ::glEnableClientState(GL_VERTEX_ARRAY); - ::glEnableClientState(GL_TEXTURE_COORD_ARRAY); - - if (theta > 90.0f) - ::glFrontFace(GL_CW); - - ::glBindTexture(GL_TEXTURE_2D, (theta <= 90.0f) ? (GLuint)m_top_texture.get_id() : (GLuint)m_bottom_texture.get_id()); - ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices()); - ::glTexCoordPointer(2, GL_FLOAT, 0, (GLvoid*)m_triangles.get_tex_coords()); - ::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount); - - if (theta > 90.0f) - ::glFrontFace(GL_CCW); - - ::glBindTexture(GL_TEXTURE_2D, 0); - ::glDisableClientState(GL_TEXTURE_COORD_ARRAY); - ::glDisableClientState(GL_VERTEX_ARRAY); - - ::glDisable(GL_TEXTURE_2D); - - ::glDisable(GL_BLEND); - ::glDepthMask(GL_TRUE); - } -} - -void GLCanvas3D::Bed::_render_custom() const -{ - m_top_texture.reset(); - m_bottom_texture.reset(); - - unsigned int triangles_vcount = m_triangles.get_vertices_count(); - if (triangles_vcount > 0) - { - ::glEnable(GL_LIGHTING); - ::glDisable(GL_DEPTH_TEST); - - ::glEnable(GL_BLEND); - ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - ::glEnableClientState(GL_VERTEX_ARRAY); - - ::glColor4f(0.35f, 0.35f, 0.35f, 0.4f); - ::glNormal3d(0.0f, 0.0f, 1.0f); - ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices()); - ::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount); - - // draw grid - unsigned int gridlines_vcount = m_gridlines.get_vertices_count(); - - // we need depth test for grid, otherwise it would disappear when looking the object from below - ::glEnable(GL_DEPTH_TEST); - ::glLineWidth(3.0f * m_scale_factor); - ::glColor4f(0.2f, 0.2f, 0.2f, 0.4f); - ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_gridlines.get_vertices()); - ::glDrawArrays(GL_LINES, 0, (GLsizei)gridlines_vcount); - - ::glDisableClientState(GL_VERTEX_ARRAY); - - ::glDisable(GL_BLEND); - ::glDisable(GL_LIGHTING); - } -} - -#if !ENABLE_REWORKED_BED_SHAPE_CHANGE -bool GLCanvas3D::Bed::_are_equal(const Pointfs& bed_1, const Pointfs& bed_2) -{ - if (bed_1.size() != bed_2.size()) - return false; - - for (unsigned int i = 0; i < (unsigned int)bed_1.size(); ++i) - { - if (bed_1[i] != bed_2[i]) - return false; - } - - return true; -} -#endif // !ENABLE_REWORKED_BED_SHAPE_CHANGE - -const double GLCanvas3D::Axes::Radius = 0.5; -const double GLCanvas3D::Axes::ArrowBaseRadius = 2.5 * GLCanvas3D::Axes::Radius; -const double GLCanvas3D::Axes::ArrowLength = 5.0; - -GLCanvas3D::Axes::Axes() - : origin(Vec3d::Zero()) - , length(Vec3d::Zero()) -{ - m_quadric = ::gluNewQuadric(); - if (m_quadric != nullptr) - ::gluQuadricDrawStyle(m_quadric, GLU_FILL); -} - -GLCanvas3D::Axes::~Axes() -{ - if (m_quadric != nullptr) - ::gluDeleteQuadric(m_quadric); -} - -void GLCanvas3D::Axes::render() const -{ - if (m_quadric == nullptr) - return; - - ::glEnable(GL_DEPTH_TEST); - ::glEnable(GL_LIGHTING); - - // x axis - ::glColor3f(1.0f, 0.0f, 0.0f); - ::glPushMatrix(); - ::glTranslated(origin(0), origin(1), origin(2)); - ::glRotated(90.0, 0.0, 1.0, 0.0); - render_axis(length(0)); - ::glPopMatrix(); - - // y axis - ::glColor3f(0.0f, 1.0f, 0.0f); - ::glPushMatrix(); - ::glTranslated(origin(0), origin(1), origin(2)); - ::glRotated(-90.0, 1.0, 0.0, 0.0); - render_axis(length(1)); - ::glPopMatrix(); - - // z axis - ::glColor3f(0.0f, 0.0f, 1.0f); - ::glPushMatrix(); - ::glTranslated(origin(0), origin(1), origin(2)); - render_axis(length(2)); - ::glPopMatrix(); - - ::glDisable(GL_LIGHTING); -} - -void GLCanvas3D::Axes::render_axis(double length) const -{ - ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); - ::gluCylinder(m_quadric, Radius, Radius, length, 32, 1); - ::gluQuadricOrientation(m_quadric, GLU_INSIDE); - ::gluDisk(m_quadric, 0.0, Radius, 32, 1); - ::glTranslated(0.0, 0.0, length); - ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); - ::gluCylinder(m_quadric, ArrowBaseRadius, 0.0, ArrowLength, 32, 1); - ::gluQuadricOrientation(m_quadric, GLU_INSIDE); - ::gluDisk(m_quadric, 0.0, ArrowBaseRadius, 32, 1); -} - +#if !ENABLE_TEXTURES_FROM_SVG GLCanvas3D::Shader::Shader() : m_shader(nullptr) { @@ -918,6 +317,7 @@ void GLCanvas3D::Shader::_reset() m_shader = nullptr; } } +#endif // !ENABLE_TEXTURES_FROM_SVG GLCanvas3D::LayersEditing::LayersEditing() : m_use_legacy_opengl(false) @@ -3158,6 +2558,7 @@ void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec float cnv_h = (float)canvas.get_canvas_size().get_height(); float height = _get_total_overlay_height(); float top_y = 0.5f * (cnv_h - height) + m_overlay_border; + for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) @@ -3440,42 +2841,28 @@ void GLCanvas3D::Gizmos::set_flattening_data(const ModelObject* model_object) reinterpret_cast(it->second)->set_flattening_data(model_object); } -#if ENABLE_SLA_SUPPORT_GIZMO_MOD void GLCanvas3D::Gizmos::set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection) -#else -void GLCanvas3D::Gizmos::set_model_object_ptr(ModelObject* model_object) -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD { if (!m_enabled) return; GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); if (it != m_gizmos.end()) -#if ENABLE_SLA_SUPPORT_GIZMO_MOD reinterpret_cast(it->second)->set_sla_support_data(model_object, selection); -#else - reinterpret_cast(it->second)->set_model_object_ptr(model_object); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD } -void GLCanvas3D::Gizmos::clicked_on_object(const Vec2d& mouse_position) + +// Returns true if the gizmo used the event to do something, false otherwise. +bool GLCanvas3D::Gizmos::mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down) { if (!m_enabled) - return; + return false; GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); if (it != m_gizmos.end()) - reinterpret_cast(it->second)->clicked_on_object(mouse_position); -} + return reinterpret_cast(it->second)->mouse_event(action, mouse_position, shift_down); -void GLCanvas3D::Gizmos::delete_current_grabber(bool delete_all) -{ - if (!m_enabled) - return; - - GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); - if (it != m_gizmos.end()) - reinterpret_cast(it->second)->delete_current_grabber(delete_all); + return false; } void GLCanvas3D::Gizmos::render_current_gizmo(const GLCanvas3D::Selection& selection) const @@ -3690,7 +3077,40 @@ GLCanvas3D::WarningTexture::WarningTexture() { } -bool GLCanvas3D::WarningTexture::generate(const std::string& msg, const GLCanvas3D& canvas) +void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool state, const GLCanvas3D& canvas) +{ + auto it = std::find(m_warnings.begin(), m_warnings.end(), warning); + + if (state) { + if (it != m_warnings.end()) // this warning is already set to be shown + return; + + m_warnings.push_back(warning); + std::sort(m_warnings.begin(), m_warnings.end()); + } + else { + if (it == m_warnings.end()) // deactivating something that is not active is an easy task + return; + + m_warnings.erase(it); + if (m_warnings.empty()) { // nothing remains to be shown + reset(); + return; + } + } + + // Look at the end of our vector and generate proper texture. + std::string text; + switch (m_warnings.back()) { + case ObjectOutside : text = L("Detected object outside print volume"); break; + case ToolpathOutside : text = L("Detected toolpath outside print volume"); break; + case SomethingNotShown : text = L("Some objects are not visible when editing supports"); break; + } + + _generate(text, canvas); // GUI::GLTexture::reset() is called at the beginning of generate(...) +} + +bool GLCanvas3D::WarningTexture::_generate(const std::string& msg, const GLCanvas3D& canvas) { reset(); @@ -3763,6 +3183,9 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg, const GLCanvas void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const { + if (m_warnings.empty()) + return; + if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0)) { ::glDisable(GL_DEPTH_TEST); @@ -3856,7 +3279,8 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c wxMemoryDC mask_memDC; // calculate scaling - const float scale = canvas.get_canvas_size().get_scale_factor(); + const float scale_gl = canvas.get_canvas_size().get_scale_factor(); + const float scale = scale_gl * wxGetApp().em_unit()*0.1; // get scale from em_unit() value, because of get_scale_factor() return 1 const int scaled_square = std::floor((float)Px_Square * scale); const int scaled_title_offset = Px_Title_Offset * scale; const int scaled_text_offset = Px_Text_Offset * scale; @@ -3864,7 +3288,7 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c const int scaled_border = Px_Border * scale; // select default font - const wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Scale(scale); + const wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Scale(scale_gl); memDC.SetFont(font); mask_memDC.SetFont(font); @@ -4061,6 +3485,8 @@ wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event); wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>); wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) : m_canvas(canvas) @@ -4069,6 +3495,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_retina_helper(nullptr) #endif , m_in_render(false) + , m_bed(nullptr) , m_toolbar(GLToolbar::Normal) , m_view_toolbar(nullptr) , m_use_clipping_planes(false) @@ -4079,15 +3506,10 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_dirty(true) , m_initialized(false) , m_use_VBOs(false) -#if ENABLE_REWORKED_BED_SHAPE_CHANGE , m_requires_zoom_to_bed(false) -#else - , m_force_zoom_to_bed_enabled(false) -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE , m_apply_zoom_to_volumes_filter(false) , m_hover_volume_id(-1) , m_toolbar_action_running(false) - , m_warning_texture_enabled(false) , m_legend_texture_enabled(false) , m_picking_enabled(false) , m_moving_enabled(false) @@ -4095,8 +3517,10 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_multisample_allowed(false) , m_regenerate_volumes(true) , m_moving(false) + , m_tab_down(false) , m_color_by("volume") , m_reload_delayed(false) + , m_render_sla_auxiliaries(true) #if !ENABLE_IMGUI , m_external_gizmo_widgets_parent(nullptr) #endif // not ENABLE_IMGUI @@ -4244,8 +3668,7 @@ void GLCanvas3D::reset_volumes() m_dirty = true; } - enable_warning_texture(false); - _reset_warning_texture(); + _set_warning_texture(WarningTexture::ObjectOutside, false); } int GLCanvas3D::check_volumes_outside_state() const @@ -4255,6 +3678,34 @@ int GLCanvas3D::check_volumes_outside_state() const return (int)state; } +void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible) +{ + for (GLVolume* vol : m_volumes.volumes) { + if (vol->composite_id.volume_id < 0) + vol->is_active = visible; + } + + m_render_sla_auxiliaries = visible; +} + +void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx) +{ + for (GLVolume* vol : m_volumes.volumes) { + if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) + && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)) + vol->is_active = visible; + } + if (visible && !mo) + toggle_sla_auxiliaries_visibility(true); + + if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1)) + _set_warning_texture(WarningTexture::SomethingNotShown, true); + + if (!mo && visible) + _set_warning_texture(WarningTexture::SomethingNotShown, false); +} + + void GLCanvas3D::set_config(const DynamicPrintConfig* config) { m_config = config; @@ -4272,36 +3723,12 @@ void GLCanvas3D::set_model(Model* model) m_selection.set_model(m_model); } -void GLCanvas3D::set_bed_shape(const Pointfs& shape) +void GLCanvas3D::bed_shape_changed() { - bool new_shape = m_bed.set_shape(shape); - -#if ENABLE_REWORKED_BED_SHAPE_CHANGE - if (new_shape) - { - // Set the origin and size for painting of the coordinate system axes. - m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z); - set_bed_axes_length(0.1 * m_bed.get_bounding_box().max_size()); - m_camera.set_scene_box(scene_bounding_box(), *this); - m_requires_zoom_to_bed = true; - - m_dirty = true; - } -#else - // Set the origin and size for painting of the coordinate system axes. - m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z); - set_bed_axes_length(0.1 * m_bed.get_bounding_box().max_size()); - - if (new_shape) - zoom_to_bed(); + m_camera.set_scene_box(scene_bounding_box(), *this); + m_requires_zoom_to_bed = true; m_dirty = true; -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE -} - -void GLCanvas3D::set_bed_axes_length(double length) -{ - m_axes.length = length * Vec3d::Ones(); } void GLCanvas3D::set_color_by(const std::string& value) @@ -4328,7 +3755,9 @@ BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const BoundingBoxf3 GLCanvas3D::scene_bounding_box() const { BoundingBoxf3 bb = volumes_bounding_box(); - bb.merge(m_bed.get_bounding_box()); + if (m_bed != nullptr) + bb.merge(m_bed->get_bounding_box()); + if (m_config != nullptr) { double h = m_config->opt_float("max_print_height"); @@ -4365,11 +3794,6 @@ void GLCanvas3D::enable_layers_editing(bool enable) } } -void GLCanvas3D::enable_warning_texture(bool enable) -{ - m_warning_texture_enabled = enable; -} - void GLCanvas3D::enable_legend_texture(bool enable) { m_legend_texture_enabled = enable; @@ -4396,13 +3820,6 @@ void GLCanvas3D::enable_toolbar(bool enable) m_toolbar.set_enabled(enable); } -#if !ENABLE_REWORKED_BED_SHAPE_CHANGE -void GLCanvas3D::enable_force_zoom_to_bed(bool enable) -{ - m_force_zoom_to_bed_enabled = enable; -} -#endif // !ENABLE_REWORKED_BED_SHAPE_CHANGE - void GLCanvas3D::enable_dynamic_background(bool enable) { m_dynamic_background_enabled = enable; @@ -4428,7 +3845,8 @@ bool GLCanvas3D::is_toolbar_item_pressed(const std::string& name) const void GLCanvas3D::zoom_to_bed() { - _zoom_to_bounding_box(m_bed.get_bounding_box()); + if (m_bed != nullptr) + _zoom_to_bounding_box(m_bed->get_bounding_box()); } void GLCanvas3D::zoom_to_volumes() @@ -4541,12 +3959,10 @@ void GLCanvas3D::render() if (!_set_current() || !_3DScene::init(m_canvas)) return; -#if ENABLE_REWORKED_BED_SHAPE_CHANGE - if (m_bed.get_shape().empty()) + if ((m_bed != nullptr) && m_bed->get_shape().empty()) { // this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE - if (m_config != nullptr) - set_bed_shape(m_config->opt("bed_shape")->values); + post_event(SimpleEvent(EVT_GLCANVAS_UPDATE_BED_SHAPE)); } if (m_requires_zoom_to_bed) @@ -4556,10 +3972,6 @@ void GLCanvas3D::render() _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); m_requires_zoom_to_bed = false; } -#else - if (m_force_zoom_to_bed_enabled) - _force_zoom_to_bed(); -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE _camera_tranform(); @@ -4573,7 +3985,7 @@ void GLCanvas3D::render() // absolute value of the rotation theta = 360.f - theta; - bool is_custom_bed = m_bed.is_custom(); + bool is_custom_bed = (m_bed == nullptr) || m_bed->is_custom(); #if ENABLE_IMGUI wxGetApp().imgui()->new_frame(); @@ -4609,9 +4021,9 @@ void GLCanvas3D::render() // this position is used later into on_mouse() to drag the objects m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast()); + _render_current_gizmo(); _render_selection_sidebar_hints(); - _render_current_gizmo(); #if ENABLE_SHOW_CAMERA_TARGET _render_camera_target(); #endif // ENABLE_SHOW_CAMERA_TARGET @@ -4855,10 +4267,6 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re if (m_reload_delayed) return; -#if !ENABLE_REWORKED_BED_SHAPE_CHANGE - set_bed_shape(dynamic_cast(m_config->option("bed_shape"))->values); -#endif // !ENABLE_REWORKED_BED_SHAPE_CHANGE - if (m_regenerate_volumes) { m_volumes.volumes = std::move(glvolumes_new); @@ -4988,22 +4396,19 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re if (!contained) { - enable_warning_texture(true); - _generate_warning_texture(L("Detected object outside print volume")); + _set_warning_texture(WarningTexture::ObjectOutside, true); post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, state == ModelInstance::PVS_Fully_Outside)); } else { - enable_warning_texture(false); m_volumes.reset_outside_state(); - _reset_warning_texture(); + _set_warning_texture(WarningTexture::ObjectOutside, false); post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, !m_model->objects.empty())); } } else { - enable_warning_texture(false); - _reset_warning_texture(); + _set_warning_texture(WarningTexture::ObjectOutside, false); post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); } @@ -5119,6 +4524,8 @@ void GLCanvas3D::bind_event_handlers() m_canvas->Bind(wxEVT_SIZE, &GLCanvas3D::on_size, this); m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, this); + m_canvas->Bind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key, this); + m_canvas->Bind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this); m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); @@ -5144,6 +4551,8 @@ void GLCanvas3D::unbind_event_handlers() m_canvas->Unbind(wxEVT_SIZE, &GLCanvas3D::on_size, this); m_canvas->Unbind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); m_canvas->Unbind(wxEVT_CHAR, &GLCanvas3D::on_char, this); + m_canvas->Unbind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key, this); + m_canvas->Unbind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this); m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); @@ -5180,6 +4589,15 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) // see include/wx/defs.h enum wxKeyCode int keyCode = evt.GetKeyCode(); int ctrlMask = wxMOD_CONTROL; + +#if ENABLE_IMGUI + auto imgui = wxGetApp().imgui(); + if (imgui->update_key_data(evt)) { + render(); + return; + } +#endif // ENABLE_IMGUI + //#ifdef __APPLE__ // ctrlMask |= wxMOD_RAW_CONTROL; //#endif /* __APPLE__ */ @@ -5187,7 +4605,12 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) switch (keyCode) { case 'a': case 'A': - case WXK_CONTROL_A: post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); break; + case WXK_CONTROL_A: + if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::SelectAll)) // Sla gizmo selects all support points + m_dirty = true; + else + post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); + break; #ifdef __APPLE__ case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead. #else /* __APPLE__ */ @@ -5202,13 +4625,30 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) switch (keyCode) { // key ESC - case WXK_ESCAPE: { m_gizmos.reset_all_states(); m_dirty = true; break; } + case WXK_ESCAPE: { + if (m_gizmos.get_current_type() != Gizmos::SlaSupports || !m_gizmos.mouse_event(SLAGizmoEventType::DiscardChanges)) + m_gizmos.reset_all_states(); + m_dirty = true; + break; + } + + case WXK_RETURN: { + if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::ApplyChanges)) + m_dirty = true; + break; + } + #ifdef __APPLE__ case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead. #else /* __APPLE__ */ case WXK_DELETE: #endif /* __APPLE__ */ - post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; + if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::Delete)) + m_dirty = true; + else + post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); + break; + case '0': { select_view("iso"); break; } case '1': { select_view("top"); break; } case '2': { select_view("bottom"); break; } @@ -5220,11 +4660,25 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case '-': { post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); break; } case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; } case 'A': - case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } + case 'a': { + if (m_gizmos.get_current_type() == Gizmos::SlaSupports) { + if (m_gizmos.mouse_event(SLAGizmoEventType::AutomaticGeneration)) + m_dirty = true; + } + else + post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); + break; + } case 'B': case 'b': { zoom_to_bed(); break; } case 'I': case 'i': { set_camera_zoom(1.0f); break; } + case 'M': + case 'm': { + if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::ManualEditing)) + m_dirty = true; + break; + } case 'O': case 'o': { set_camera_zoom(-1.0f); break; } case 'Z': @@ -5245,6 +4699,38 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) } } +void GLCanvas3D::on_key(wxKeyEvent& evt) +{ + const int keyCode = evt.GetKeyCode(); + +#if ENABLE_IMGUI + auto imgui = wxGetApp().imgui(); + if (imgui->update_key_data(evt)) { + render(); + } else +#endif // ENABLE_IMGUI + if (evt.GetEventType() == wxEVT_KEY_UP) { + if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) { + // Enable switching between 3D and Preview with Tab + // m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux + post_event(SimpleEvent(EVT_GLCANVAS_TAB)); + } else if (m_gizmos.get_current_type() == Gizmos::SlaSupports && keyCode == WXK_SHIFT && m_gizmos.mouse_event(SLAGizmoEventType::ShiftUp)) { + // shift has been just released - SLA gizmo might want to close rectangular selection. + m_dirty = true; + } + } else if (evt.GetEventType() == wxEVT_KEY_DOWN) { + m_tab_down = keyCode == WXK_TAB && !evt.HasAnyModifiers(); + } + + if (keyCode != WXK_TAB + && keyCode != WXK_LEFT + && keyCode != WXK_UP + && keyCode != WXK_RIGHT + && keyCode != WXK_DOWN) { + evt.Skip(); // Needed to have EVT_CHAR generated as well + } +} + void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) { // Ignore the wheel events if the middle button is pressed. @@ -5295,17 +4781,22 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) evt.SetY(evt.GetY() * scale); #endif + Point pos(evt.GetX(), evt.GetY()); + #if ENABLE_IMGUI - auto imgui = wxGetApp().imgui(); + ImGuiWrapper *imgui = wxGetApp().imgui(); if (imgui->update_mouse_data(evt)) { + m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast(); render(); - if (imgui->want_any_input()) { - return; - } + return; } #endif // ENABLE_IMGUI - Point pos(evt.GetX(), evt.GetY()); + if (! evt.Entering() && ! evt.Leaving() && m_mouse.position.x() == -1.0) { + // Workaround for SPE-832: There seems to be a mouse event sent to the window before evt.Entering() + m_mouse.position = pos.cast(); + render(); + } if (m_picking_enabled) _set_current(); @@ -5332,14 +4823,21 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // Set focus in order to remove it from sidebar fields if (m_canvas != nullptr) { // Only set focus, if the top level window of this canvas is active. - auto p = dynamic_cast(evt.GetEventObject()); + auto p = dynamic_cast(evt.GetEventObject()); while (p->GetParent()) p = p->GetParent(); auto *top_level_wnd = dynamic_cast(p); if (top_level_wnd && top_level_wnd->IsActive()) + { m_canvas->SetFocus(); - } + // forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while + // the context menu is shown, ensuring it to disappear if the mouse is outside any volume and to + // change the volume hover state if any is under the mouse + m_mouse.position = pos.cast(); + render(); + } + } m_mouse.set_start_position_2D_as_invalid(); //#endif } @@ -5385,22 +4883,16 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_dirty = true; } } -#if !ENABLE_IMGUI - else if ((m_gizmos.get_current_type() == Gizmos::SlaSupports) && gizmo_reset_rect_contains(*this, pos(0), pos(1))) - { - if (evt.LeftDown()) - { - m_gizmos.delete_current_grabber(true); - m_dirty = true; - } - } -#endif // not ENABLE_IMGUI else if (!m_selection.is_empty() && gizmos_overlay_contains_mouse) { m_gizmos.update_on_off_state(*this, m_mouse.position, m_selection); _update_gizmos_data(); m_dirty = true; } + else if (evt.LeftDown() && m_gizmos.get_current_type() == Gizmos::SlaSupports && evt.ShiftDown() && m_gizmos.mouse_event(SLAGizmoEventType::LeftDown, Vec2d(pos(0), pos(1)), evt.ShiftDown())) + { + // the gizmo got the event and took some action, there is no need to do anything more + } else if (evt.LeftDown() && !m_selection.is_empty() && m_gizmos.grabber_contains_mouse()) { _update_gizmos_data(); @@ -5416,9 +4908,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_dirty = true; } - else if ((selected_object_idx != -1) && m_gizmos.grabber_contains_mouse() && evt.RightDown()) { - if (m_gizmos.get_current_type() == Gizmos::SlaSupports) - m_gizmos.delete_current_grabber(); + else if ((selected_object_idx != -1) && evt.RightDown() && m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::RightDown)) + { + // event was taken care of by the SlaSupports gizmo } else if (view_toolbar_contains_mouse != -1) { @@ -5474,7 +4966,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } // propagate event through callback - if (m_hover_volume_id != -1) { if (evt.LeftDown() && m_moving_enabled && (m_mouse.drag.move_volume_idx == -1)) @@ -5493,9 +4984,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } else if (evt.RightDown()) { + m_mouse.position = pos.cast(); // forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while - // the context menu is already shown, ensuring it to disappear if the mouse is outside any volume - m_mouse.position = Vec2d((double)pos(0), (double)pos(1)); + // the context menu is already shown render(); if (m_hover_volume_id != -1) { @@ -5509,14 +5000,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); _update_gizmos_data(); wxGetApp().obj_manipul()->update_settings_value(m_selection); - // forces a frame render to update the view before the context menu is shown - render(); - +// // forces a frame render to update the view before the context menu is shown +// render(); + Vec2d logical_pos = pos.cast(); #if ENABLE_RETINA_GL const float factor = m_retina_helper->get_scale_factor(); logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor)); -#endif +#endif // ENABLE_RETINA_GL post_event(Vec2dEvent(EVT_GLCANVAS_RIGHT_CLICK, logical_pos)); } } @@ -5524,7 +5015,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } } - else if (evt.Dragging() && evt.LeftIsDown() && !gizmos_overlay_contains_mouse && (m_layers_editing.state == LayersEditing::Unknown) && (m_mouse.drag.move_volume_idx != -1)) + else if (evt.Dragging() && evt.LeftIsDown() && !gizmos_overlay_contains_mouse && (m_layers_editing.state == LayersEditing::Unknown) + && (m_mouse.drag.move_volume_idx != -1) && m_gizmos.get_current_type() != Gizmos::SlaSupports /* don't allow dragging objects with the Sla gizmo on */) { #if ENABLE_MOVE_MIN_THRESHOLD if (!m_mouse.drag.move_requires_threshold) @@ -5584,6 +5076,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_dirty = true; } + else if (evt.Dragging() && m_gizmos.get_current_type() == Gizmos::SlaSupports && evt.ShiftDown() && m_gizmos.mouse_event(SLAGizmoEventType::Dragging, Vec2d(pos(0), pos(1)), evt.ShiftDown())) + { + // the gizmo got the event and took some action, no need to do anything more here + m_dirty = true; + } else if (evt.Dragging() && !gizmos_overlay_contains_mouse) { m_mouse.dragging = true; @@ -5639,6 +5136,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) _stop_timer(); m_layers_editing.accept_changes(*this); } + else if (evt.LeftUp() && m_gizmos.get_current_type() == Gizmos::SlaSupports && !m_gizmos.is_dragging() + && !m_mouse.dragging && m_gizmos.mouse_event(SLAGizmoEventType::LeftUp, Vec2d(pos(0), pos(1)), evt.ShiftDown())) + { + // the gizmo got the event and took some action, no need to do anything more + } else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging) { m_regenerate_volumes = false; @@ -5648,16 +5150,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // of the scene with the background processing data should be performed. post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); } - else if (evt.LeftUp() && m_gizmos.get_current_type() == Gizmos::SlaSupports && m_hover_volume_id != -1) + else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() + && !is_layers_editing_enabled() && (m_gizmos.get_current_type() != Gizmos::SlaSupports || !m_gizmos.mouse_event(SLAGizmoEventType::LeftUp, Vec2d(pos(0), pos(1)), evt.ShiftDown()))) { - int id = m_selection.get_object_idx(); + // SLA gizmo cannot be deselected by clicking in canvas area to avoid inadvertent unselection and losing manual changes + // that's why the mouse_event function was called so that the gizmo can refuse the deselection in manual editing mode - if ((id != -1) && (m_model != nullptr)) { - m_gizmos.clicked_on_object(Vec2d(pos(0), pos(1))); - } - } - else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled()) - { // deselect and propagate event through callback if (!evt.ShiftDown() && m_picking_enabled && !m_toolbar_action_running && !m_mouse.ignore_up_event) { @@ -5690,10 +5188,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) do_rotate(); break; } - case Gizmos::SlaSupports: - // End of mouse dragging, update the SLAPrint/SLAPrintObjects with the new support points. - post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - break; default: break; } @@ -6093,14 +5587,6 @@ bool GLCanvas3D::_is_shown_on_screen() const return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; } -#if !ENABLE_REWORKED_BED_SHAPE_CHANGE -void GLCanvas3D::_force_zoom_to_bed() -{ - zoom_to_bed(); - m_force_zoom_to_bed_enabled = false; -} -#endif // !ENABLE_REWORKED_BED_SHAPE_CHANGE - bool GLCanvas3D::_init_toolbar() { if (!m_toolbar.is_enabled()) @@ -6143,7 +5629,6 @@ bool GLCanvas3D::_init_toolbar() item.name = "add"; item.tooltip = GUI::L_str("Add...") + " [" + GUI::shortkey_ctrl_prefix() + "I]"; item.sprite_id = 0; - item.is_toggable = false; item.action_event = EVT_GLTOOLBAR_ADD; if (!m_toolbar.add_item(item)) return false; @@ -6151,7 +5636,6 @@ bool GLCanvas3D::_init_toolbar() item.name = "delete"; item.tooltip = GUI::L_str("Delete") + " [Del]"; item.sprite_id = 1; - item.is_toggable = false; item.action_event = EVT_GLTOOLBAR_DELETE; if (!m_toolbar.add_item(item)) return false; @@ -6159,7 +5643,6 @@ bool GLCanvas3D::_init_toolbar() item.name = "deleteall"; item.tooltip = GUI::L_str("Delete all") + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; item.sprite_id = 2; - item.is_toggable = false; item.action_event = EVT_GLTOOLBAR_DELETE_ALL; if (!m_toolbar.add_item(item)) return false; @@ -6167,7 +5650,6 @@ bool GLCanvas3D::_init_toolbar() item.name = "arrange"; item.tooltip = GUI::L_str("Arrange [A]"); item.sprite_id = 3; - item.is_toggable = false; item.action_event = EVT_GLTOOLBAR_ARRANGE; if (!m_toolbar.add_item(item)) return false; @@ -6178,7 +5660,6 @@ bool GLCanvas3D::_init_toolbar() item.name = "more"; item.tooltip = GUI::L_str("Add instance [+]"); item.sprite_id = 4; - item.is_toggable = false; item.action_event = EVT_GLTOOLBAR_MORE; if (!m_toolbar.add_item(item)) return false; @@ -6186,7 +5667,6 @@ bool GLCanvas3D::_init_toolbar() item.name = "fewer"; item.tooltip = GUI::L_str("Remove instance [-]"); item.sprite_id = 5; - item.is_toggable = false; item.action_event = EVT_GLTOOLBAR_FEWER; if (!m_toolbar.add_item(item)) return false; @@ -6197,7 +5677,6 @@ bool GLCanvas3D::_init_toolbar() item.name = "splitobjects"; item.tooltip = GUI::L_str("Split to objects"); item.sprite_id = 6; - item.is_toggable = false; item.action_event = EVT_GLTOOLBAR_SPLIT_OBJECTS; if (!m_toolbar.add_item(item)) return false; @@ -6205,7 +5684,6 @@ bool GLCanvas3D::_init_toolbar() item.name = "splitvolumes"; item.tooltip = GUI::L_str("Split to parts"); item.sprite_id = 8; - item.is_toggable = false; item.action_event = EVT_GLTOOLBAR_SPLIT_VOLUMES; if (!m_toolbar.add_item(item)) return false; @@ -6317,8 +5795,9 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) BoundingBoxf3 GLCanvas3D::_max_bounding_box() const { - BoundingBoxf3 bb = m_bed.get_bounding_box(); - bb.merge(volumes_bounding_box()); + BoundingBoxf3 bb = volumes_bounding_box(); + if (m_bed != nullptr) + bb.merge(m_bed->get_bounding_box()); return bb; } @@ -6334,11 +5813,7 @@ void GLCanvas3D::_zoom_to_bounding_box(const BoundingBoxf3& bbox) viewport_changed(); -#if ENABLE_REWORKED_BED_SHAPE_CHANGE m_dirty = true; -#else - _refresh_if_shown_on_screen(); -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE } } @@ -6418,15 +5893,7 @@ void GLCanvas3D::_refresh_if_shown_on_screen() // Because of performance problems on macOS, where PaintEvents are not delivered // frequently enough, we call render() here directly when we can. -#if ENABLE_REWORKED_BED_SHAPE_CHANGE render(); -#else - // We can't do that when m_force_zoom_to_bed_enabled == true, because then render() - // ends up calling back here via _force_zoom_to_bed(), causing a stack overflow. - if (m_canvas != nullptr) { - m_force_zoom_to_bed_enabled ? m_canvas->Refresh() : render(); - } -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE } } @@ -6535,16 +6002,14 @@ void GLCanvas3D::_render_bed(float theta) const scale_factor = m_retina_helper->get_scale_factor(); #endif -#if ENABLE_PRINT_BED_MODELS - m_bed.render(theta, m_use_VBOs, scale_factor); -#else - m_bed.render(theta, scale_factor); -#endif // ENABLE_PRINT_BED_MODELS + if (m_bed != nullptr) + m_bed->render(theta, m_use_VBOs, scale_factor); } void GLCanvas3D::_render_axes() const { - m_axes.render(); + if (m_bed != nullptr) + m_bed->render_axes(); } void GLCanvas3D::_render_objects() const @@ -6562,9 +6027,9 @@ void GLCanvas3D::_render_objects() const // Update the layer editing selection to the first object selected, update the current object maximum Z. const_cast(m_layers_editing).select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); - if (m_config != nullptr) + if ((m_config != nullptr) && (m_bed != nullptr)) { - const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(); + const BoundingBoxf3& bed_bb = m_bed->get_bounding_box(); m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height")); m_volumes.check_outside_state(m_config, nullptr); } @@ -6586,7 +6051,9 @@ void GLCanvas3D::_render_objects() const m_layers_editing.render_volumes(*this, this->m_volumes); } else { // do not cull backfaces to show broken geometry, if any - m_volumes.render_VBOs(GLVolumeCollection::Opaque, m_picking_enabled); + m_volumes.render_VBOs(GLVolumeCollection::Opaque, m_picking_enabled, [this](const GLVolume& volume) { + return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); + }); } m_volumes.render_VBOs(GLVolumeCollection::Transparent, false); m_shader.stop_using(); @@ -6602,7 +6069,9 @@ void GLCanvas3D::_render_objects() const } // do not cull backfaces to show broken geometry, if any - m_volumes.render_legacy(GLVolumeCollection::Opaque, m_picking_enabled); + m_volumes.render_legacy(GLVolumeCollection::Opaque, m_picking_enabled, [this](const GLVolume& volume) { + return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); + }); m_volumes.render_legacy(GLVolumeCollection::Transparent, false); if (m_use_clipping_planes) @@ -6636,9 +6105,6 @@ void GLCanvas3D::_render_selection_center() const void GLCanvas3D::_render_warning_texture() const { - if (!m_warning_texture_enabled) - return; - m_warning_texture.render(*this); } @@ -6683,7 +6149,7 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const ::glColor4fv(vol->render_color); } - if (!fake_colors || !vol->disabled) + if ((!fake_colors || !vol->disabled) && (vol->composite_id.volume_id >= 0 || m_render_sla_auxiliaries)) vol->render(); ++volume_id; @@ -6834,20 +6300,20 @@ void GLCanvas3D::_render_sla_slices() const { // calculate model bottom cap if (bottom_obj_triangles.empty() && (it_min_z->second.model_slices_idx < model_slices.size())) - bottom_obj_triangles = triangulate_expolygons_3df(model_slices[it_min_z->second.model_slices_idx], min_z, true); + bottom_obj_triangles = triangulate_expolygons_3d(model_slices[it_min_z->second.model_slices_idx], min_z, true); // calculate support bottom cap if (bottom_sup_triangles.empty() && (it_min_z->second.support_slices_idx < support_slices.size())) - bottom_sup_triangles = triangulate_expolygons_3df(support_slices[it_min_z->second.support_slices_idx], min_z, true); + bottom_sup_triangles = triangulate_expolygons_3d(support_slices[it_min_z->second.support_slices_idx], min_z, true); } if (it_max_z != index.end()) { // calculate model top cap if (top_obj_triangles.empty() && (it_max_z->second.model_slices_idx < model_slices.size())) - top_obj_triangles = triangulate_expolygons_3df(model_slices[it_max_z->second.model_slices_idx], max_z, false); + top_obj_triangles = triangulate_expolygons_3d(model_slices[it_max_z->second.model_slices_idx], max_z, false); // calculate support top cap if (top_sup_triangles.empty() && (it_max_z->second.support_slices_idx < support_slices.size())) - top_sup_triangles = triangulate_expolygons_3df(support_slices[it_max_z->second.support_slices_idx], max_z, false); + top_sup_triangles = triangulate_expolygons_3d(support_slices[it_max_z->second.support_slices_idx], max_z, false); } } @@ -6960,11 +6426,7 @@ void GLCanvas3D::_update_gizmos_data() m_gizmos.set_rotation(Vec3d::Zero()); ModelObject* model_object = m_model->objects[m_selection.get_object_idx()]; m_gizmos.set_flattening_data(model_object); -#if ENABLE_SLA_SUPPORT_GIZMO_MOD m_gizmos.set_sla_support_data(model_object, m_selection); -#else - m_gizmos.set_model_object_ptr(model_object); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD } else if (m_selection.is_single_volume() || m_selection.is_single_modifier()) { @@ -6972,22 +6434,14 @@ void GLCanvas3D::_update_gizmos_data() m_gizmos.set_scale(volume->get_volume_scaling_factor()); m_gizmos.set_rotation(Vec3d::Zero()); m_gizmos.set_flattening_data(nullptr); -#if ENABLE_SLA_SUPPORT_GIZMO_MOD m_gizmos.set_sla_support_data(nullptr, m_selection); -#else - m_gizmos.set_model_object_ptr(nullptr); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD } else { m_gizmos.set_scale(Vec3d::Ones()); m_gizmos.set_rotation(Vec3d::Zero()); m_gizmos.set_flattening_data(m_selection.is_from_single_object() ? m_model->objects[m_selection.get_object_idx()] : nullptr); -#if ENABLE_SLA_SUPPORT_GIZMO_MOD m_gizmos.set_sla_support_data(nullptr, m_selection); -#else - m_gizmos.set_model_object_ptr(nullptr); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD } } @@ -8185,17 +7639,7 @@ void GLCanvas3D::_update_toolpath_volumes_outside_state() void GLCanvas3D::_show_warning_texture_if_needed() { _set_current(); - - if (_is_any_volume_outside()) - { - enable_warning_texture(true); - _generate_warning_texture(L("Detected toolpath outside print volume")); - } - else - { - enable_warning_texture(false); - _reset_warning_texture(); - } + _set_warning_texture(WarningTexture::ToolpathOutside, _is_any_volume_outside()); } std::vector GLCanvas3D::_parse_colors(const std::vector& colors) @@ -8228,14 +7672,9 @@ void GLCanvas3D::_generate_legend_texture(const GCodePreviewData& preview_data, m_legend_texture.generate(preview_data, tool_colors, *this, m_dynamic_background_enabled && _is_any_volume_outside()); } -void GLCanvas3D::_generate_warning_texture(const std::string& msg) +void GLCanvas3D::_set_warning_texture(WarningTexture::Warning warning, bool state) { - m_warning_texture.generate(msg, *this); -} - -void GLCanvas3D::_reset_warning_texture() -{ - m_warning_texture.reset(); + m_warning_texture.activate(warning, state, *this); } bool GLCanvas3D::_is_any_volume_outside() const diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index db2279ee41..008e770562 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -8,6 +8,7 @@ #include "3DScene.hpp" #include "GLToolbar.hpp" #include "Event.hpp" +#include "3DBed.hpp" #include @@ -25,9 +26,6 @@ class wxGLCanvas; // Support for Retina OpenGL on Mac OS #define ENABLE_RETINA_GL __APPLE__ -class GLUquadric; -typedef class GLUquadric GLUquadricObj; - namespace Slic3r { class GLShader; @@ -45,21 +43,6 @@ class GLGizmoBase; class RetinaHelper; #endif -class GeometryBuffer -{ - std::vector m_vertices; - std::vector m_tex_coords; - -public: - bool set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords); - bool set_from_lines(const Lines& lines, float z); - - const float* get_vertices() const; - const float* get_tex_coords() const; - - unsigned int get_vertices_count() const; -}; - class Size { int m_width; @@ -131,6 +114,24 @@ wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event); wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>); wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); + +// this describes events being passed from GLCanvas3D to SlaSupport gizmo +enum class SLAGizmoEventType { + LeftDown = 1, + LeftUp, + RightDown, + Dragging, + Delete, + SelectAll, + ShiftUp, + ApplyChanges, + DiscardChanges, + AutomaticGeneration, + ManualEditing +}; + class GLCanvas3D { @@ -196,95 +197,7 @@ class GLCanvas3D void set_scene_box(const BoundingBoxf3& box, GLCanvas3D& canvas); }; - class Bed - { - public: - enum EType : unsigned char - { - MK2, - MK3, - SL1, - Custom, - Num_Types - }; - - private: - EType m_type; - Pointfs m_shape; - BoundingBoxf3 m_bounding_box; - Polygon m_polygon; - GeometryBuffer m_triangles; - GeometryBuffer m_gridlines; - mutable GLTexture m_top_texture; - mutable GLTexture m_bottom_texture; -#if ENABLE_PRINT_BED_MODELS - mutable GLBed m_model; -#endif // ENABLE_PRINT_BED_MODELS - - mutable float m_scale_factor; - - public: - Bed(); - -#if ENABLE_REWORKED_BED_SHAPE_CHANGE - EType get_type() const { return m_type; } -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE - - bool is_prusa() const; - bool is_custom() const; - - const Pointfs& get_shape() const; - // Return true if the bed shape changed, so the calee will update the UI. - bool set_shape(const Pointfs& shape); - - const BoundingBoxf3& get_bounding_box() const; - bool contains(const Point& point) const; - Point point_projection(const Point& point) const; - -#if ENABLE_PRINT_BED_MODELS - void render(float theta, bool useVBOs, float scale_factor) const; -#else - void render(float theta, float scale_factor) const; -#endif // ENABLE_PRINT_BED_MODELS - - private: - void _calc_bounding_box(); - void _calc_triangles(const ExPolygon& poly); - void _calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); -#if ENABLE_REWORKED_BED_SHAPE_CHANGE - EType _detect_type(const Pointfs& shape) const; -#else - EType _detect_type() const; -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE -#if ENABLE_PRINT_BED_MODELS - void _render_prusa(const std::string &key, float theta, bool useVBOs) const; -#else - void _render_prusa(const std::string &key, float theta) const; -#endif // ENABLE_PRINT_BED_MODELS - void _render_custom() const; -#if !ENABLE_REWORKED_BED_SHAPE_CHANGE - static bool _are_equal(const Pointfs& bed_1, const Pointfs& bed_2); -#endif // !ENABLE_REWORKED_BED_SHAPE_CHANGE - }; - - struct Axes - { - static const double Radius; - static const double ArrowBaseRadius; - static const double ArrowLength; - Vec3d origin; - Vec3d length; - GLUquadricObj* m_quadric; - - Axes(); - ~Axes(); - - void render() const; - - private: - void render_axis(double length) const; - }; - +#if !ENABLE_TEXTURES_FROM_SVG class Shader { GLShader* m_shader; @@ -308,6 +221,7 @@ class GLCanvas3D private: void _reset(); }; +#endif // !ENABLE_TEXTURES_FROM_SVG class LayersEditing { @@ -788,12 +702,8 @@ private: void set_flattening_data(const ModelObject* model_object); -#if ENABLE_SLA_SUPPORT_GIZMO_MOD void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection); -#else - void set_model_object_ptr(ModelObject* model_object); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD - void clicked_on_object(const Vec2d& mouse_position); + bool mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false); void delete_current_grabber(bool delete_all = false); void render_current_gizmo(const Selection& selection) const; @@ -835,18 +745,32 @@ private: class WarningTexture : public GUI::GLTexture { + public: + WarningTexture(); + + enum Warning { + ObjectOutside, + ToolpathOutside, + SomethingNotShown + }; + + // Sets a warning of the given type to be active/inactive. If several warnings are active simultaneously, + // only the last one is shown (decided by the order in the enum above). + void activate(WarningTexture::Warning warning, bool state, const GLCanvas3D& canvas); + void render(const GLCanvas3D& canvas) const; + + private: static const unsigned char Background_Color[3]; static const unsigned char Opacity; int m_original_width; int m_original_height; - public: - WarningTexture(); + // Information about which warnings are currently active. + std::vector m_warnings; - bool generate(const std::string& msg, const GLCanvas3D& canvas); - - void render(const GLCanvas3D& canvas) const; + // Generates the texture with given text. + bool _generate(const std::string& msg, const GLCanvas3D& canvas); }; class LegendTexture : public GUI::GLTexture @@ -884,8 +808,7 @@ private: WarningTexture m_warning_texture; wxTimer m_timer; Camera m_camera; - Bed m_bed; - Axes m_axes; + Bed3D* m_bed; LayersEditing m_layers_editing; Shader m_shader; Mouse m_mouse; @@ -907,11 +830,7 @@ private: bool m_dirty; bool m_initialized; bool m_use_VBOs; -#if ENABLE_REWORKED_BED_SHAPE_CHANGE bool m_requires_zoom_to_bed; -#else - bool m_force_zoom_to_bed_enabled; -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE bool m_apply_zoom_to_volumes_filter; mutable int m_hover_volume_id; bool m_toolbar_action_running; @@ -923,6 +842,8 @@ private: bool m_multisample_allowed; bool m_regenerate_volumes; bool m_moving; + bool m_tab_down; + bool m_render_sla_auxiliaries; std::string m_color_by; @@ -943,6 +864,8 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas; } const wxGLCanvas* get_wxglcanvas() const { return m_canvas; } + void set_bed(Bed3D* bed) { m_bed = bed; } + void set_view_toolbar(GLToolbar* toolbar) { m_view_toolbar = toolbar; } bool init(bool useVBOs, bool use_legacy_opengl); @@ -954,6 +877,9 @@ public: void reset_volumes(); int check_volumes_outside_state() const; + void toggle_sla_auxiliaries_visibility(bool visible); + void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); + void set_config(const DynamicPrintConfig* config); void set_process(BackgroundSlicingProcess* process); void set_model(Model* model); @@ -961,12 +887,7 @@ public: const Selection& get_selection() const { return m_selection; } Selection& get_selection() { return m_selection; } - // Set the bed shape to a single closed 2D polygon(array of two element arrays), - // triangulate the bed and store the triangles into m_bed.m_triangles, - // fills the m_bed.m_grid_lines and sets m_bed.m_origin. - // Sets m_bed.m_polygon to limit the object placement. - void set_bed_shape(const Pointfs& shape); - void set_bed_axes_length(double length); + void bed_shape_changed(); void set_clipping_plane(unsigned int id, const ClippingPlane& plane) { @@ -991,15 +912,11 @@ public: bool is_reload_delayed() const; void enable_layers_editing(bool enable); - void enable_warning_texture(bool enable); void enable_legend_texture(bool enable); void enable_picking(bool enable); void enable_moving(bool enable); void enable_gizmos(bool enable); void enable_toolbar(bool enable); -#if !ENABLE_REWORKED_BED_SHAPE_CHANGE - void enable_force_zoom_to_bed(bool enable); -#endif // !ENABLE_REWORKED_BED_SHAPE_CHANGE void enable_dynamic_background(bool enable); void allow_multisample(bool allow); @@ -1050,6 +967,7 @@ public: void on_size(wxSizeEvent& evt); void on_idle(wxIdleEvent& evt); void on_char(wxKeyEvent& evt); + void on_key(wxKeyEvent& evt); void on_mouse_wheel(wxMouseEvent& evt); void on_timer(wxTimerEvent& evt); void on_mouse(wxMouseEvent& evt); @@ -1084,9 +1002,6 @@ public: private: bool _is_shown_on_screen() const; -#if !ENABLE_REWORKED_BED_SHAPE_CHANGE - void _force_zoom_to_bed(); -#endif // !ENABLE_REWORKED_BED_SHAPE_CHANGE bool _init_toolbar(); @@ -1176,8 +1091,7 @@ private: void _generate_legend_texture(const GCodePreviewData& preview_data, const std::vector& tool_colors); // generates a warning texture containing the given message - void _generate_warning_texture(const std::string& msg); - void _reset_warning_texture(); + void _set_warning_texture(WarningTexture::Warning warning, bool state); bool _is_any_volume_outside() const; diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index 8f2e5b219c..71299f777d 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -233,7 +233,7 @@ wxGLCanvas* GLCanvas3DManager::create_wxglcanvas(wxWindow *parent) attribList[4] = 0; } - return new wxGLCanvas(parent, wxID_ANY, attribList); + return new wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS); } GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp index 1660976a1f..f1a4589feb 100644 --- a/src/slic3r/GUI/GLGizmo.cpp +++ b/src/slic3r/GUI/GLGizmo.cpp @@ -7,7 +7,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Geometry.hpp" #include "libslic3r/Utils.hpp" -#include "libslic3r/SLA/SLASupportTree.hpp" +#include "libslic3r/SLA/SLACommon.hpp" #include "libslic3r/SLAPrint.hpp" #include @@ -1445,8 +1445,8 @@ void GLGizmoFlatten::on_render(const GLCanvas3D::Selection& selection) const { const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); ::glPushMatrix(); - ::glMultMatrixd(m.data()); ::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()); + ::glMultMatrixd(m.data()); if (this->is_plane_update_necessary()) const_cast(this)->update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) @@ -1479,8 +1479,8 @@ void GLGizmoFlatten::on_render_for_picking(const GLCanvas3D::Selection& selectio { const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); ::glPushMatrix(); - ::glMultMatrixd(m.data()); ::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()); + ::glMultMatrixd(m.data()); if (this->is_plane_update_necessary()) const_cast(this)->update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) @@ -1514,7 +1514,7 @@ void GLGizmoFlatten::update_planes() TriangleMesh ch; for (const ModelVolume* vol : m_model_object->volumes) { - if (vol->type() != ModelVolume::Type::MODEL_PART) + if (vol->type() != ModelVolumeType::MODEL_PART) continue; TriangleMesh vol_ch = vol->get_convex_hull(); vol_ch.transform(vol->get_matrix()); @@ -1741,28 +1741,20 @@ Vec3d GLGizmoFlatten::get_flattening_normal() const } GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent) -#if ENABLE_SLA_SUPPORT_GIZMO_MOD : GLGizmoBase(parent), m_starting_center(Vec3d::Zero()), m_quadric(nullptr) -#else - : GLGizmoBase(parent), m_starting_center(Vec3d::Zero()) -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD { -#if ENABLE_SLA_SUPPORT_GIZMO_MOD m_quadric = ::gluNewQuadric(); if (m_quadric != nullptr) // using GLU_FILL does not work when the instance's transformation // contains mirroring (normals are reverted) ::gluQuadricDrawStyle(m_quadric, GLU_FILL); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD } -#if ENABLE_SLA_SUPPORT_GIZMO_MOD GLGizmoSlaSupports::~GLGizmoSlaSupports() { if (m_quadric != nullptr) ::gluDeleteQuadric(m_quadric); } -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD bool GLGizmoSlaSupports::on_init() { @@ -1782,7 +1774,6 @@ bool GLGizmoSlaSupports::on_init() return true; } -#if ENABLE_SLA_SUPPORT_GIZMO_MOD void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection) { m_starting_center = Vec3d::Zero(); @@ -1791,217 +1782,165 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const G if (selection.is_empty()) m_old_instance_id = -1; - if ((model_object != nullptr) && selection.is_from_single_instance()) + m_active_instance = selection.get_instance_idx(); + + if (model_object && selection.is_from_single_instance()) { if (is_mesh_update_necessary()) update_mesh(); // If there are no points, let's ask the backend if it calculated some. - if (model_object->sla_support_points.empty() && m_parent.sla_print()->is_step_done(slaposSupportPoints)) { - for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { - if (po->model_object()->id() == model_object->id()) { - const Eigen::MatrixXd& points = po->get_support_points(); - for (unsigned int i=0; isla_support_points.push_back(Vec3f(po->trafo().inverse().cast() * Vec3f(points(i,0), points(i,1), points(i,2)))); - break; - } - } + if (m_editing_mode_cache.empty()) + get_data_from_backend(); + + if (m_model_object != m_old_model_object) + m_editing_mode = false; + if (m_state == On) { + m_parent.toggle_model_objects_visibility(false); + m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); } } } -#else -void GLGizmoSlaSupports::set_model_object_ptr(ModelObject* model_object) -{ - if (model_object != nullptr) { - m_starting_center = Vec3d::Zero(); - m_model_object = model_object; - - int selected_instance = m_parent.get_selection().get_instance_idx(); - assert(selected_instance < (int)model_object->instances.size()); - - m_instance_matrix = model_object->instances[selected_instance]->get_matrix(); - if (is_mesh_update_necessary()) - update_mesh(); - } -} -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD void GLGizmoSlaSupports::on_render(const GLCanvas3D::Selection& selection) const { ::glEnable(GL_BLEND); ::glEnable(GL_DEPTH_TEST); -#if !ENABLE_SLA_SUPPORT_GIZMO_MOD - // the dragged_offset is a vector measuring where was the object moved - // with the gizmo being on. This is reset in set_model_object_ptr and - // does not work correctly when there are multiple copies. - - if (m_starting_center == Vec3d::Zero()) - m_starting_center = selection.get_bounding_box().center(); - Vec3d dragged_offset = selection.get_bounding_box().center() - m_starting_center; -#endif // !ENABLE_SLA_SUPPORT_GIZMO_MOD - - - for (auto& g : m_grabbers) { - g.color[0] = 1.f; - g.color[1] = 0.f; - g.color[2] = 0.f; - } - -#if ENABLE_SLA_SUPPORT_GIZMO_MOD - render_grabbers(selection, false); -#else - //::glTranslatef((GLfloat)dragged_offset(0), (GLfloat)dragged_offset(1), (GLfloat)dragged_offset(2)); - render_grabbers(false); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + render_points(selection, false); + render_selection_rectangle(); #if !ENABLE_IMGUI render_tooltip_texture(); #endif // not ENABLE_IMGUI + ::glDisable(GL_BLEND); } +void GLGizmoSlaSupports::render_selection_rectangle() const +{ + if (!m_selection_rectangle_active) + return; + + ::glLineWidth(1.5f); + float render_color[3] = {1.f, 0.f, 0.f}; + ::glColor3fv(render_color); + + ::glPushAttrib(GL_TRANSFORM_BIT); // remember current MatrixMode + + ::glMatrixMode(GL_MODELVIEW); // cache modelview matrix and set to identity + ::glPushMatrix(); + ::glLoadIdentity(); + + ::glMatrixMode(GL_PROJECTION); // cache projection matrix and set to identity + ::glPushMatrix(); + ::glLoadIdentity(); + + ::glOrtho(0.f, m_canvas_width, m_canvas_height, 0.f, -1.f, 1.f); // set projection matrix so that world coords = window coords + + // render the selection rectangle (window coordinates): + ::glPushAttrib(GL_ENABLE_BIT); + ::glLineStipple(4, 0xAAAA); + ::glEnable(GL_LINE_STIPPLE); + + ::glBegin(GL_LINE_LOOP); + ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); + ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); + ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); + ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); + ::glEnd(); + ::glPopAttrib(); + + ::glPopMatrix(); // restore former projection matrix + ::glMatrixMode(GL_MODELVIEW); + ::glPopMatrix(); // restore former modelview matrix + ::glPopAttrib(); // restore former MatrixMode +} void GLGizmoSlaSupports::on_render_for_picking(const GLCanvas3D::Selection& selection) const { ::glEnable(GL_DEPTH_TEST); - for (unsigned int i=0; iget_sla_shift_z(); - - ::glPushMatrix(); - ::glTranslated(0.0, 0.0, z_shift); - - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); - ::glMultMatrixd(m.data()); - if (!picking) ::glEnable(GL_LIGHTING); + const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + double z_shift = vol->get_sla_shift_z(); + const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); + const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix(); + + ::glPushMatrix(); + ::glTranslated(0.0, 0.0, z_shift); + ::glMultMatrixd(instance_matrix.data()); + float render_color[3]; - for (int i = 0; i < (int)m_grabbers.size(); ++i) + for (int i = 0; i < (int)m_editing_mode_cache.size(); ++i) { - // first precalculate the grabber position in world coordinates, so that the grabber - // is not scaled with the object (as it would be if rendered with current gl matrix). - Eigen::Matrix glmatrix; - glGetFloatv (GL_MODELVIEW_MATRIX, glmatrix.data()); - Eigen::Matrix grabber_pos; - for (int j=0; j<3; ++j) - grabber_pos(j) = m_grabbers[i].center(j); - grabber_pos[3] = 1.f; - Eigen::Matrix grabber_world_position = glmatrix * grabber_pos; + const sla::SupportPoint& support_point = m_editing_mode_cache[i].first; + const bool& point_selected = m_editing_mode_cache[i].second; - if (!picking && (m_hover_id == i)) - { - render_color[0] = 1.0f - m_grabbers[i].color[0]; - render_color[1] = 1.0f - m_grabbers[i].color[1]; - render_color[2] = 1.0f - m_grabbers[i].color[2]; + // First decide about the color of the point. + if (picking) { + render_color[0] = 1.0f; + render_color[1] = 1.0f; + render_color[2] = picking_color_component(i); + } + else { + if ((m_hover_id == i && m_editing_mode)) { // ignore hover state unless editing mode is active + render_color[0] = 0.f; + render_color[1] = 1.0f; + render_color[2] = 1.0f; + } + else { // neigher hover nor picking + bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].first.is_new_island; + if (m_editing_mode) { + render_color[0] = point_selected ? 1.0f : (supports_new_island ? 0.3f : 0.7f); + render_color[1] = point_selected ? 0.3f : (supports_new_island ? 0.3f : 0.7f); + render_color[2] = point_selected ? 0.3f : (supports_new_island ? 1.0f : 0.7f); + } + else + for (unsigned char i=0; i<3; ++i) render_color[i] = 0.5f; + } } - else - ::memcpy((void*)render_color, (const void*)m_grabbers[i].color, 3 * sizeof(float)); - ::glColor3fv(render_color); + float render_color_emissive[4] = { 0.5f * render_color[0], 0.5f * render_color[1], 0.5f * render_color[2], 1.f}; + ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive); + + // Now render the sphere. Inverse matrix of the instance scaling is applied so that the + // sphere does not scale with the object. ::glPushMatrix(); - ::glLoadIdentity(); - ::glTranslated(grabber_world_position(0), grabber_world_position(1), grabber_world_position(2) + z_shift); - const float diameter = 0.8f; - ::gluSphere(m_quadric, diameter/2.f, 64, 36); + ::glTranslated(support_point.pos(0), support_point.pos(1), support_point.pos(2)); + ::glMultMatrixd(instance_scaling_matrix_inverse.data()); + ::gluSphere(m_quadric, m_editing_mode_cache[i].first.head_front_radius * RenderPointScale, 64, 36); ::glPopMatrix(); } + { + // Reset emissive component to zero (the default value) + float render_color_emissive[4] = { 0.f, 0.f, 0.f, 1.f }; + ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive); + } + if (!picking) ::glDisable(GL_LIGHTING); ::glPopMatrix(); } -#else -void GLGizmoSlaSupports::render_grabbers(bool picking) const -{ - if (m_parent.get_selection().is_empty()) - return; - - float z_shift = m_parent.get_selection().get_volume(0)->get_sla_shift_z(); - ::glTranslatef((GLfloat)0, (GLfloat)0, (GLfloat)z_shift); - - int selected_instance = m_parent.get_selection().get_instance_idx(); - assert(selected_instance < (int)m_model_object->instances.size()); - - float render_color_inactive[3] = { 0.5f, 0.5f, 0.5f }; - - for (const ModelInstance* inst : m_model_object->instances) { - bool active = inst == m_model_object->instances[selected_instance]; - if (picking && ! active) - continue; - for (int i = 0; i < (int)m_grabbers.size(); ++i) - { - if (!m_grabbers[i].enabled) - continue; - - float render_color[3]; - if (! picking && active && m_hover_id == i) { - render_color[0] = 1.0f - m_grabbers[i].color[0]; - render_color[1] = 1.0f - m_grabbers[i].color[1]; - render_color[2] = 1.0f - m_grabbers[i].color[2]; - } - else - ::memcpy((void*)render_color, active ? (const void*)m_grabbers[i].color : (const void*)render_color_inactive, 3 * sizeof(float)); - if (!picking) - ::glEnable(GL_LIGHTING); - ::glColor3f((GLfloat)render_color[0], (GLfloat)render_color[1], (GLfloat)render_color[2]); - ::glPushMatrix(); - Vec3d center = inst->get_matrix() * m_grabbers[i].center; - ::glTranslatef((GLfloat)center(0), (GLfloat)center(1), (GLfloat)center(2)); - GLUquadricObj *quadric; - quadric = ::gluNewQuadric(); - ::gluQuadricDrawStyle(quadric, GLU_FILL ); - ::gluSphere( quadric , 0.4, 64 , 32 ); - ::gluDeleteQuadric(quadric); - ::glPopMatrix(); - if (!picking) - ::glDisable(GL_LIGHTING); - } - } - - ::glTranslatef((GLfloat)0, (GLfloat)0, (GLfloat)-z_shift); -} -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD bool GLGizmoSlaSupports::is_mesh_update_necessary() const { -#if ENABLE_SLA_SUPPORT_GIZMO_MOD return (m_state == On) && (m_model_object != nullptr) && (m_model_object != m_old_model_object) && !m_model_object->instances.empty(); -#else - return m_state == On && m_model_object && !m_model_object->instances.empty() && !m_instance_matrix.isApprox(m_source_data.matrix); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD //if (m_state != On || !m_model_object || m_model_object->instances.empty() || ! m_instance_matrix.isApprox(m_source_data.matrix)) // return false; - - // following should detect direct mesh changes (can be removed after the mesh is made completely immutable): - /*const float* first_vertex = m_model_object->volumes.front()->get_convex_hull().first_vertex(); - Vec3d first_point((double)first_vertex[0], (double)first_vertex[1], (double)first_vertex[2]); - if (first_point != m_source_data.mesh_first_point) - return true;*/ } void GLGizmoSlaSupports::update_mesh() @@ -2027,27 +1966,14 @@ void GLGizmoSlaSupports::update_mesh() m_AABB = igl::AABB(); m_AABB.init(m_V, m_F); -#if !ENABLE_SLA_SUPPORT_GIZMO_MOD - m_source_data.matrix = m_instance_matrix; -#endif // !ENABLE_SLA_SUPPORT_GIZMO_MOD - - // we'll now reload Grabbers (selection might have changed): - m_grabbers.clear(); - - for (const Vec3f& point : m_model_object->sla_support_points) { - m_grabbers.push_back(Grabber()); - m_grabbers.back().center = point.cast(); - } + // we'll now reload support points (selection might have changed): + editing_mode_reload_cache(); } Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) { // if the gizmo doesn't have the V, F structures for igl, calculate them first: -#if ENABLE_SLA_SUPPORT_GIZMO_MOD if (m_V.size() == 0) -#else - if (m_V.size() == 0 || is_mesh_update_necessary()) -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD update_mesh(); Eigen::Matrix viewport; @@ -2064,20 +1990,15 @@ Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) igl::Hit hit; -#if ENABLE_SLA_SUPPORT_GIZMO_MOD const GLCanvas3D::Selection& selection = m_parent.get_selection(); const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); double z_offset = volume->get_sla_shift_z(); -#else - double z_offset = m_parent.get_selection().get_volume(0)->get_sla_shift_z(); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + point1(2) -= z_offset; point2(2) -= z_offset; -#if ENABLE_SLA_SUPPORT_GIZMO_MOD + Transform3d inv = volume->get_instance_transformation().get_matrix().inverse(); -#else - Transform3d inv = m_instance_matrix.inverse(); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + point1 = inv * point1; point2 = inv * point2; @@ -2089,68 +2010,202 @@ Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) return bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); } -void GLGizmoSlaSupports::clicked_on_object(const Vec2d& mouse_position) +// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. +// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is +// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo +// concludes that the event was not intended for it, it should return false. +bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down) { -#if ENABLE_SLA_SUPPORT_GIZMO_MOD - int instance_id = m_parent.get_selection().get_instance_idx(); - if (m_old_instance_id != instance_id) - { - bool something_selected = (m_old_instance_id != -1); - m_old_instance_id = instance_id; - if (something_selected) - return; + if (m_editing_mode) { + + // left down - show the selection rectangle: + if (action == SLAGizmoEventType::LeftDown && shift_down) { + if (m_hover_id == -1) { + m_selection_rectangle_active = true; + m_selection_rectangle_start_corner = mouse_position; + m_selection_rectangle_end_corner = mouse_position; + m_canvas_width = m_parent.get_canvas_size().get_width(); + m_canvas_height = m_parent.get_canvas_size().get_height(); + } + else + select_point(m_hover_id); + + return true; + } + + // dragging the selection rectangle: + if (action == SLAGizmoEventType::Dragging && m_selection_rectangle_active) { + m_selection_rectangle_end_corner = mouse_position; + return true; + } + + // mouse up without selection rectangle - place point on the mesh: + if (action == SLAGizmoEventType::LeftUp && !m_selection_rectangle_active && !shift_down) { + if (m_ignore_up_event) { + m_ignore_up_event = false; + return false; + } + + int instance_id = m_parent.get_selection().get_instance_idx(); + if (m_old_instance_id != instance_id) + { + bool something_selected = (m_old_instance_id != -1); + m_old_instance_id = instance_id; + if (something_selected) + return false; + } + if (instance_id == -1) + return false; + + // If there is some selection, don't add new point and deselect everything instead. + if (m_selection_empty) { + Vec3f new_pos; + try { + new_pos = unproject_on_mesh(mouse_position); // this can throw - we don't want to create a new point in that case + m_editing_mode_cache.emplace_back(std::make_pair(sla::SupportPoint(new_pos, m_new_point_head_diameter/2.f, false), false)); + m_unsaved_changes = true; + } + catch (...) { // not clicked on object + return true; // prevents deselection of the gizmo by GLCanvas3D + } + } + else + select_point(NoPoints); + + return true; + } + + // left up with selection rectangle - select points inside the rectangle: + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp) + && m_selection_rectangle_active) { + if (action == SLAGizmoEventType::ShiftUp) + m_ignore_up_event = true; + const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix(); + GLint viewport[4]; + ::glGetIntegerv(GL_VIEWPORT, viewport); + GLdouble modelview_matrix[16]; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix); + GLdouble projection_matrix[16]; + ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix); + + const GLCanvas3D::Selection& selection = m_parent.get_selection(); + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + double z_offset = volume->get_sla_shift_z(); + + // bounding box created from the rectangle corners - will take care of order of the corners + BoundingBox rectangle(Points{Point(m_selection_rectangle_start_corner.cast()), Point(m_selection_rectangle_end_corner.cast())}); + + const Transform3d& instance_matrix_no_translation = volume->get_instance_transformation().get_matrix(true); + // we'll recover current look direction from the modelview matrix (in world coords)... + Vec3f direction_to_camera(modelview_matrix[2], modelview_matrix[6], modelview_matrix[10]); + // ...and transform it to model coords. + direction_to_camera = (instance_matrix_no_translation.inverse().cast() * direction_to_camera).normalized().eval(); + + // Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh: + for (unsigned int i=0; i() * support_point.pos; + pos(2) += z_offset; + GLdouble out_x, out_y, out_z; + ::gluProject((GLdouble)pos(0), (GLdouble)pos(1), (GLdouble)pos(2), modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z); + out_y = m_canvas_height - out_y; + + if (rectangle.contains(Point(out_x, out_y))) { + bool is_obscured = false; + // Cast a ray in the direction of the camera and look for intersection with the mesh: + std::vector hits; + // Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies. + if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera * (support_point.head_front_radius + EPSILON), direction_to_camera, hits)) + // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. + // Also, the threshold is in mesh coordinates, not in actual dimensions. + if (hits.size() > 1 || hits.front().t > 0.001f) + is_obscured = true; + + if (!is_obscured) + select_point(i); + } + } + m_selection_rectangle_active = false; + return true; + } + + if (action == SLAGizmoEventType::Delete) { + // delete key pressed + delete_selected_points(); + return true; + } + + if (action == SLAGizmoEventType::ApplyChanges) { + editing_mode_apply_changes(); + return true; + } + + if (action == SLAGizmoEventType::DiscardChanges) { + editing_mode_discard_changes(); + return true; + } + + if (action == SLAGizmoEventType::RightDown) { + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + delete_selected_points(); + return true; + } + return false; + } + + if (action == SLAGizmoEventType::SelectAll) { + select_point(AllPoints); + return true; + } } - if (instance_id == -1) - return; -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD - Vec3f new_pos; - try { - new_pos = unproject_on_mesh(mouse_position); // this can throw - we don't want to create a new grabber in that case + if (!m_editing_mode) { + if (action == SLAGizmoEventType::AutomaticGeneration) { + auto_generate(); + return true; + } + + if (action == SLAGizmoEventType::ManualEditing) { + switch_to_editing_mode(); + return true; + } } - catch (...) { return; } - m_grabbers.push_back(Grabber()); - m_grabbers.back().center = new_pos.cast(); - m_model_object->sla_support_points.push_back(new_pos); - - // This should trigger the support generation - // wxGetApp().plater()->reslice(); - - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + return false; } -void GLGizmoSlaSupports::delete_current_grabber(bool delete_all) +void GLGizmoSlaSupports::delete_selected_points() { - if (delete_all) { - m_grabbers.clear(); - m_model_object->sla_support_points.clear(); + if (!m_editing_mode) + return; - // This should trigger the support generation - // wxGetApp().plater()->reslice(); - } - else - if (m_hover_id != -1) { - m_grabbers.erase(m_grabbers.begin() + m_hover_id); - m_model_object->sla_support_points.erase(m_model_object->sla_support_points.begin() + m_hover_id); - m_hover_id = -1; - - // This should trigger the support generation - // wxGetApp().plater()->reslice(); + for (unsigned int idx=0; idxreslice_SLA_supports(*m_model_object); + } + + select_point(NoPoints); + + //m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } void GLGizmoSlaSupports::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) { - if (m_hover_id != -1 && data.mouse_pos) { + if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].first.is_new_island || !m_lock_unique_islands)) { Vec3f new_pos; try { new_pos = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); } catch (...) { return; } - m_grabbers[m_hover_id].center = new_pos.cast(); - m_model_object->sla_support_points[m_hover_id] = new_pos; + m_editing_mode_cache[m_hover_id].first.pos = new_pos; + m_editing_mode_cache[m_hover_id].first.is_new_island = false; + m_unsaved_changes = true; // Do not update immediately, wait until the mouse is released. // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } @@ -2196,37 +2251,119 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, const GLCanvas RENDER_AGAIN: m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); m_imgui->set_next_window_bg_alpha(0.5f); - m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove |/* ImGuiWindowFlags_NoResize | */ImGuiWindowFlags_NoCollapse); ImGui::PushItemWidth(100.0f); - m_imgui->text(_(L("Left mouse click - add point"))); - m_imgui->text(_(L("Right mouse click - remove point"))); - m_imgui->text(" "); - bool generate = m_imgui->button(_(L("Generate points automatically"))); - bool remove_all_clicked = m_imgui->button(_(L("Remove all points")) + (m_model_object == nullptr ? "" : " (" + std::to_string(m_model_object->sla_support_points.size())+")")); + bool force_refresh = false; + bool remove_selected = false; + + if (m_editing_mode) { + m_imgui->text(_(L("Left mouse click - add point"))); + m_imgui->text(_(L("Right mouse click - remove point"))); + m_imgui->text(_(L("Shift + Left (+ drag) - select point(s)"))); + m_imgui->text(" "); // vertical gap + + std::vector options = {"0.2", "0.4", "0.6", "0.8", "1.0"}; + std::stringstream ss; + ss << std::setprecision(1) << m_new_point_head_diameter; + wxString str = ss.str(); + + bool old_combo_state = m_combo_box_open; + m_combo_box_open = m_imgui->combo(_(L("Head diameter")), options, str); + force_refresh |= (old_combo_state != m_combo_box_open); + + float current_number = atof(str); + if (old_combo_state && !m_combo_box_open) // closing the combo must always change the sizes (even if the selection did not change) + for (auto& point_and_selection : m_editing_mode_cache) + if (point_and_selection.second) { + point_and_selection.first.head_front_radius = current_number / 2.f; + m_unsaved_changes = true; + } + + if (std::abs(current_number - m_new_point_head_diameter) > 0.001) { + force_refresh = true; + m_new_point_head_diameter = current_number; + } + + bool changed = m_lock_unique_islands; + m_imgui->checkbox(_(L("Lock supports under new islands")), m_lock_unique_islands); + force_refresh |= changed != m_lock_unique_islands; + + m_imgui->disabled_begin(m_selection_empty); + remove_selected = m_imgui->button(_(L("Remove selected points"))); + m_imgui->disabled_end(); + + m_imgui->text(" "); // vertical gap + + bool apply_changes = m_imgui->button(_(L("Apply changes"))); + if (apply_changes) { + editing_mode_apply_changes(); + force_refresh = true; + } + ImGui::SameLine(); + bool discard_changes = m_imgui->button(_(L("Discard changes"))); + if (discard_changes) { + editing_mode_discard_changes(); + force_refresh = true; + } + } + else { + /* ImGui::PushItemWidth(50.0f); + m_imgui->text(_(L("Minimal points distance: "))); + ImGui::SameLine(); + bool value_changed = ImGui::InputDouble("mm", &m_minimal_point_distance, 0.0f, 0.0f, "%.2f"); + m_imgui->text(_(L("Support points density: "))); + ImGui::SameLine(); + value_changed |= ImGui::InputDouble("%", &m_density, 0.0f, 0.0f, "%.f");*/ + + bool generate = m_imgui->button(_(L("Auto-generate points [A]"))); + + if (generate) + auto_generate(); + + m_imgui->text(""); + m_imgui->text(""); + if (m_imgui->button(_(L("Manual editing [M]")))) + switch_to_editing_mode(); + } m_imgui->end(); - if (remove_all_clicked) { - delete_current_grabber(true); + if (m_editing_mode != m_old_editing_state) { // user toggled between editing/non-editing mode + m_parent.toggle_sla_auxiliaries_visibility(!m_editing_mode); + force_refresh = true; + } + m_old_editing_state = m_editing_mode; + + if (remove_selected) { + force_refresh = false; + m_parent.reload_scene(true); + delete_selected_points(); if (first_run) { first_run = false; goto RENDER_AGAIN; } } - if (remove_all_clicked || generate) { + if (force_refresh) m_parent.reload_scene(true); - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - } } #endif // ENABLE_IMGUI bool GLGizmoSlaSupports::on_is_activable(const GLCanvas3D::Selection& selection) const { - return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) - && selection.is_from_single_instance(); + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA + || !selection.is_from_single_instance()) + return false; + + // Check that none of the selected volumes is outside. + const GLCanvas3D::Selection::IndicesList& list = selection.get_volume_idxs(); + for (const auto& idx : list) + if (selection.get_volume(idx)->is_outside) + return false; + + return true; } bool GLGizmoSlaSupports::on_is_selectable() const @@ -2239,6 +2376,145 @@ std::string GLGizmoSlaSupports::on_get_name() const return L("SLA Support Points [L]"); } +void GLGizmoSlaSupports::on_set_state() +{ + if (m_state == On) { + if (is_mesh_update_necessary()) + update_mesh(); + + m_parent.toggle_model_objects_visibility(false); + if (m_model_object) + m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); + } + if (m_state == Off) { + if (m_old_state != Off && m_model_object) { // the gizmo was just turned Off + + if (m_unsaved_changes) { + wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L("Do you want to save your manually edited support points ?\n")), + _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) + editing_mode_apply_changes(); + else + editing_mode_discard_changes(); + } + + m_parent.toggle_model_objects_visibility(true); + m_editing_mode = false; // so it is not active next time the gizmo opens + } + } + m_old_state = m_state; +} + + + +void GLGizmoSlaSupports::on_start_dragging(const GLCanvas3D::Selection& selection) +{ + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + } +} + + + +void GLGizmoSlaSupports::select_point(int i) +{ + if (i == AllPoints || i == NoPoints) { + for (auto& point_and_selection : m_editing_mode_cache) + point_and_selection.second = ( i == AllPoints ); + m_selection_empty = (i == NoPoints); + } + else { + m_editing_mode_cache[i].second = true; + m_selection_empty = false; + } +} + + + +void GLGizmoSlaSupports::editing_mode_discard_changes() +{ + m_editing_mode_cache.clear(); + for (const sla::SupportPoint& point : m_model_object->sla_support_points) + m_editing_mode_cache.push_back(std::make_pair(point, false)); + m_editing_mode = false; + m_unsaved_changes = false; +} + + + +void GLGizmoSlaSupports::editing_mode_apply_changes() +{ + // If there are no changes, don't touch the front-end. The data in the cache could have been + // taken from the backend and copying them to ModelObject would needlessly invalidate them. + if (m_unsaved_changes) { + m_model_object->sla_support_points.clear(); + for (const std::pair& point_and_selection : m_editing_mode_cache) + m_model_object->sla_support_points.push_back(point_and_selection.first); + } + m_editing_mode = false; + m_unsaved_changes = false; + + // Recalculate support structures once the editing mode is left. + // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().plater()->reslice_SLA_supports(*m_model_object); +} + + + +void GLGizmoSlaSupports::editing_mode_reload_cache() +{ + m_editing_mode_cache.clear(); + for (const sla::SupportPoint& point : m_model_object->sla_support_points) + m_editing_mode_cache.push_back(std::make_pair(point, false)); + m_unsaved_changes = false; +} + + + +void GLGizmoSlaSupports::get_data_from_backend() +{ + for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { + if (po->model_object()->id() == m_model_object->id() && po->is_step_done(slaposSupportPoints)) { + const std::vector& points = po->get_support_points(); + auto mat = po->trafo().inverse().cast(); + for (unsigned int i=0; isla_support_points.empty() || dlg.ShowModal() == wxID_YES) { + m_model_object->sla_support_points.clear(); + m_editing_mode_cache.clear(); + wxGetApp().plater()->reslice_SLA_supports(*m_model_object); + } +} + + + +void GLGizmoSlaSupports::switch_to_editing_mode() +{ + editing_mode_reload_cache(); + m_editing_mode = true; +} + + + + // GLGizmoCut diff --git a/src/slic3r/GUI/GLGizmo.hpp b/src/slic3r/GUI/GLGizmo.hpp index 7a55a2392c..6996f5576c 100644 --- a/src/slic3r/GUI/GLGizmo.hpp +++ b/src/slic3r/GUI/GLGizmo.hpp @@ -403,7 +403,7 @@ private: // This holds information to decide whether recalculation is necessary: std::vector m_volumes_matrices; - std::vector m_volumes_types; + std::vector m_volumes_types; Vec3d m_first_instance_scale; Vec3d m_first_instance_mirror; @@ -437,32 +437,26 @@ protected: } }; + + class GLGizmoSlaSupports : public GLGizmoBase { private: ModelObject* m_model_object = nullptr; -#if ENABLE_SLA_SUPPORT_GIZMO_MOD ModelObject* m_old_model_object = nullptr; + int m_active_instance = -1; int m_old_instance_id = -1; -#else - Transform3d m_instance_matrix; -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD Vec3f unproject_on_mesh(const Vec2d& mouse_pos); -#if ENABLE_SLA_SUPPORT_GIZMO_MOD - GLUquadricObj* m_quadric; -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + const float RenderPointScale = 1.f; + GLUquadricObj* m_quadric; Eigen::MatrixXf m_V; // vertices Eigen::MatrixXi m_F; // facets indices igl::AABB m_AABB; struct SourceDataSummary { -#if !ENABLE_SLA_SUPPORT_GIZMO_MOD - BoundingBoxf3 bounding_box; - Transform3d matrix; -#endif // !ENABLE_SLA_SUPPORT_GIZMO_MOD - Vec3d mesh_first_point; + Geometry::Transformation transformation; }; // This holds information to decide whether recalculation is necessary: @@ -472,14 +466,10 @@ private: public: explicit GLGizmoSlaSupports(GLCanvas3D& parent); -#if ENABLE_SLA_SUPPORT_GIZMO_MOD virtual ~GLGizmoSlaSupports(); void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection); -#else - void set_model_object_ptr(ModelObject* model_object); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD - void clicked_on_object(const Vec2d& mouse_position); - void delete_current_grabber(bool delete_all); + bool mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down); + void delete_selected_points(); private: bool on_init(); @@ -487,11 +477,8 @@ private: virtual void on_render(const GLCanvas3D::Selection& selection) const; virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; -#if ENABLE_SLA_SUPPORT_GIZMO_MOD - void render_grabbers(const GLCanvas3D::Selection& selection, bool picking = false) const; -#else - void render_grabbers(bool picking = false) const; -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + void render_selection_rectangle() const; + void render_points(const GLCanvas3D::Selection& selection, bool picking = false) const; bool is_mesh_update_necessary() const; void update_mesh(); @@ -501,12 +488,42 @@ private: mutable GLTexture m_reset_texture; #endif // not ENABLE_IMGUI + bool m_lock_unique_islands = false; + bool m_editing_mode = false; + bool m_old_editing_state = false; + float m_new_point_head_diameter = 0.4f; + double m_minimal_point_distance = 20.; + double m_density = 100.; + std::vector> m_editing_mode_cache; // a support point and whether it is currently selected + + bool m_selection_rectangle_active = false; + Vec2d m_selection_rectangle_start_corner; + Vec2d m_selection_rectangle_end_corner; + bool m_ignore_up_event = false; + bool m_combo_box_open = false; + bool m_unsaved_changes = false; + bool m_selection_empty = true; + EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) + int m_canvas_width; + int m_canvas_height; + + // Methods that do the model_object and editing cache synchronization, + // editing mode selection, etc: + enum { + AllPoints = -2, + NoPoints, + }; + void select_point(int i); + void editing_mode_apply_changes(); + void editing_mode_discard_changes(); + void editing_mode_reload_cache(); + void get_data_from_backend(); + void auto_generate(); + void switch_to_editing_mode(); + protected: - void on_set_state() override { - if (m_state == On && is_mesh_update_necessary()) { - update_mesh(); - } - } + void on_set_state() override; + void on_start_dragging(const GLCanvas3D::Selection& selection) override; #if ENABLE_IMGUI virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) override; diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index 791f452efd..24d8d41b2d 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -253,4 +253,91 @@ sub SetMatrix } */ +#if ENABLE_TEXTURES_FROM_SVG +Shader::Shader() + : m_shader(nullptr) +{ +} + +Shader::~Shader() +{ + reset(); +} + +bool Shader::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename) +{ + if (is_initialized()) + return true; + + m_shader = new GLShader(); + if (m_shader != nullptr) + { + if (!m_shader->load_from_file(fragment_shader_filename.c_str(), vertex_shader_filename.c_str())) + { + std::cout << "Compilaton of shader failed:" << std::endl; + std::cout << m_shader->last_error << std::endl; + reset(); + return false; + } + } + + return true; +} + +bool Shader::is_initialized() const +{ + return (m_shader != nullptr); +} + +bool Shader::start_using() const +{ + if (is_initialized()) + { + m_shader->enable(); + return true; + } + else + return false; +} + +void Shader::stop_using() const +{ + if (m_shader != nullptr) + m_shader->disable(); +} + +void Shader::set_uniform(const std::string& name, float value) const +{ + if (m_shader != nullptr) + m_shader->set_uniform(name.c_str(), value); +} + +void Shader::set_uniform(const std::string& name, const float* matrix) const +{ + if (m_shader != nullptr) + m_shader->set_uniform(name.c_str(), matrix); +} + +void Shader::set_uniform(const std::string& name, bool value) const +{ + if (m_shader != nullptr) + m_shader->set_uniform(name.c_str(), value); +} + +unsigned int Shader::get_shader_program_id() const +{ + return (m_shader != nullptr) ? m_shader->shader_program_id : 0; +} + +void Shader::reset() +{ + if (m_shader != nullptr) + { + m_shader->release(); + delete m_shader; + m_shader = nullptr; + } +} +#endif // ENABLE_TEXTURES_FROM_SVG + } // namespace Slic3r diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index 0634cce6e9..2f88d03936 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -36,6 +36,34 @@ public: std::string last_error; }; +#if ENABLE_TEXTURES_FROM_SVG +class Shader +{ + GLShader* m_shader; + +public: + Shader(); + ~Shader(); + + bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename); + + bool is_initialized() const; + + bool start_using() const; + void stop_using() const; + + void set_uniform(const std::string& name, float value) const; + void set_uniform(const std::string& name, const float* matrix) const; + void set_uniform(const std::string& name, bool value) const; + + const GLShader* get_shader() const { return m_shader; } + unsigned int get_shader_program_id() const; + +private: + void reset(); +}; +#endif // ENABLE_TEXTURES_FROM_SVG + } #endif /* slic3r_GLShader_hpp_ */ diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 292ef472a0..e7e20b27e7 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -1,3 +1,4 @@ +#include "libslic3r/libslic3r.h" #include "GLTexture.hpp" #include @@ -5,10 +6,20 @@ #include #include +#if ENABLE_TEXTURES_FROM_SVG +#include +#endif // ENABLE_TEXTURES_FROM_SVG #include #include +#if ENABLE_TEXTURES_FROM_SVG +#define NANOSVG_IMPLEMENTATION +#include "nanosvg/nanosvg.h" +#define NANOSVGRAST_IMPLEMENTATION +#include "nanosvg/nanosvgrast.h" +#endif // ENABLE_TEXTURES_FROM_SVG + namespace Slic3r { namespace GUI { @@ -27,7 +38,34 @@ GLTexture::~GLTexture() reset(); } -bool GLTexture::load_from_file(const std::string& filename, bool generate_mipmaps) +#if ENABLE_TEXTURES_FROM_SVG +bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps) +{ + reset(); + + if (!boost::filesystem::exists(filename)) + return false; + + if (boost::algorithm::iends_with(filename, ".png")) + return load_from_png(filename, use_mipmaps); + else + return false; +} + +bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, unsigned int max_size_px) +{ + reset(); + + if (!boost::filesystem::exists(filename)) + return false; + + if (boost::algorithm::iends_with(filename, ".svg")) + return load_from_svg(filename, use_mipmaps, max_size_px); + else + return false; +} +#else +bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps) { reset(); @@ -78,10 +116,10 @@ bool GLTexture::load_from_file(const std::string& filename, bool generate_mipmap ::glGenTextures(1, &m_id); ::glBindTexture(GL_TEXTURE_2D, m_id); ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); - if (generate_mipmaps) + if (use_mipmaps) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards - unsigned int levels_count = _generate_mipmaps(image); + unsigned int levels_count = generate_mipmaps(image); ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1 + levels_count); ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); } @@ -97,6 +135,7 @@ bool GLTexture::load_from_file(const std::string& filename, bool generate_mipmap m_source = filename; return true; } +#endif // ENABLE_TEXTURES_FROM_SVG void GLTexture::reset() { @@ -109,26 +148,6 @@ void GLTexture::reset() m_source = ""; } -unsigned int GLTexture::get_id() const -{ - return m_id; -} - -int GLTexture::get_width() const -{ - return m_width; -} - -int GLTexture::get_height() const -{ - return m_height; -} - -const std::string& GLTexture::get_source() const -{ - return m_source; -} - void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top) { render_sub_texture(tex_id, left, right, bottom, top, FullTextureUVs); @@ -157,7 +176,7 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, ::glDisable(GL_BLEND); } -unsigned int GLTexture::_generate_mipmaps(wxImage& image) +unsigned int GLTexture::generate_mipmaps(wxImage& image) { int w = image.GetWidth(); int h = image.GetHeight(); @@ -195,5 +214,152 @@ unsigned int GLTexture::_generate_mipmaps(wxImage& image) return (unsigned int)level; } +#if ENABLE_TEXTURES_FROM_SVG +bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps) +{ + // Load a PNG with an alpha channel. + wxImage image; + if (!image.LoadFile(wxString::FromUTF8(filename.c_str()), wxBITMAP_TYPE_PNG)) + { + reset(); + return false; + } + + m_width = image.GetWidth(); + m_height = image.GetHeight(); + int n_pixels = m_width * m_height; + + if (n_pixels <= 0) + { + reset(); + return false; + } + + // Get RGB & alpha raw data from wxImage, pack them into an array. + unsigned char* img_rgb = image.GetData(); + if (img_rgb == nullptr) + { + reset(); + return false; + } + + unsigned char* img_alpha = image.GetAlpha(); + + std::vector data(n_pixels * 4, 0); + for (int i = 0; i < n_pixels; ++i) + { + int data_id = i * 4; + int img_id = i * 3; + data[data_id + 0] = img_rgb[img_id + 0]; + data[data_id + 1] = img_rgb[img_id + 1]; + data[data_id + 2] = img_rgb[img_id + 2]; + data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255; + } + + // sends data to gpu + ::glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + ::glGenTextures(1, &m_id); + ::glBindTexture(GL_TEXTURE_2D, m_id); + ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); + if (use_mipmaps) + { + // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards + unsigned int levels_count = generate_mipmaps(image); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1 + levels_count); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } + else + { + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + } + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + ::glBindTexture(GL_TEXTURE_2D, 0); + + m_source = filename; + + return true; +} + +bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px) +{ + NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f); + if (image == nullptr) + { +// printf("Could not open SVG image.\n"); + reset(); + return false; + } + + float scale = (float)max_size_px / std::max(image->width, image->height); + + m_width = (int)(scale * image->width); + m_height = (int)(scale * image->height); + int n_pixels = m_width * m_height; + + if (n_pixels <= 0) + { + reset(); + return false; + } + + NSVGrasterizer* rast = nsvgCreateRasterizer(); + if (rast == nullptr) + { +// printf("Could not init rasterizer.\n"); + nsvgDelete(image); + reset(); + return false; + } + + // creates the temporary buffer only once, with max size, and reuse it for all the levels, if generating mipmaps + std::vector data(n_pixels * 4, 0); + nsvgRasterize(rast, image, 0, 0, scale, data.data(), m_width, m_height, m_width * 4); + + // sends data to gpu + ::glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + ::glGenTextures(1, &m_id); + ::glBindTexture(GL_TEXTURE_2D, m_id); + ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); + if (use_mipmaps) + { + // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards + int lod_w = m_width; + int lod_h = m_height; + GLint level = 0; + while ((lod_w > 1) || (lod_h > 1)) + { + ++level; + + lod_w = std::max(lod_w / 2, 1); + lod_h = std::max(lod_h / 2, 1); + scale /= 2.0f; + + nsvgRasterize(rast, image, 0, 0, scale, data.data(), lod_w, lod_h, lod_w * 4); + ::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); + } + + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1 + level); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } + else + { + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + } + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + ::glBindTexture(GL_TEXTURE_2D, 0); + + m_source = filename; + + nsvgDeleteRasterizer(rast); + nsvgDelete(image); + + return true; +} +#endif // ENABLE_TEXTURES_FROM_SVG + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index e027bd152f..af41ac3424 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -37,20 +37,28 @@ namespace GUI { GLTexture(); virtual ~GLTexture(); - bool load_from_file(const std::string& filename, bool generate_mipmaps); + bool load_from_file(const std::string& filename, bool use_mipmaps); +#if ENABLE_TEXTURES_FROM_SVG + bool load_from_svg_file(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); +#endif // ENABLE_TEXTURES_FROM_SVG void reset(); - unsigned int get_id() const; - int get_width() const; - int get_height() const; + unsigned int get_id() const { return m_id; } + int get_width() const { return m_width; } + int get_height() const { return m_height; } - const std::string& get_source() const; + const std::string& get_source() const { return m_source; } static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs); protected: - unsigned int _generate_mipmaps(wxImage& image); + unsigned int generate_mipmaps(wxImage& image); +#if ENABLE_TEXTURES_FROM_SVG + private: + bool load_from_png(const std::string& filename, bool use_mipmaps); + bool load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); +#endif // ENABLE_TEXTURES_FROM_SVG }; } // namespace GUI diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index a15f500325..e09ce2466b 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -211,8 +211,9 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt } break; case coEnum:{ - if (opt_key.compare("external_fill_pattern") == 0 || - opt_key.compare("fill_pattern") == 0) + if (opt_key == "top_fill_pattern" || + opt_key == "bottom_fill_pattern" || + opt_key == "fill_pattern") config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); else if (opt_key.compare("gcode_flavor") == 0) config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 38a5f33875..76d5f849a1 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -3,8 +3,10 @@ #include "GUI_ObjectManipulation.hpp" #include "I18N.hpp" +#include #include #include +#include #include #include @@ -76,6 +78,7 @@ IMPLEMENT_APP(GUI_App) GUI_App::GUI_App() : wxApp() + , m_em_unit(10) #if ENABLE_IMGUI , m_imgui(new ImGuiWrapper()) #endif // ENABLE_IMGUI @@ -125,6 +128,10 @@ bool GUI_App::OnInit() app_config->save(); preset_updater = new PresetUpdater(); + Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) { + app_config->set("version_online", into_u8(evt.GetString())); + app_config->save(); + }); load_language(); @@ -154,15 +161,15 @@ bool GUI_App::OnInit() Bind(wxEVT_IDLE, [this](wxIdleEvent& event) { - if (app_config->dirty()) + if (app_config->dirty() && app_config->get("autosave") == "1") app_config->save(); // ! Temporary workaround for the correct behavior of the Scrolled sidebar panel // Do this "manipulations" only once ( after (re)create of the application ) - if (plater_ && sidebar().obj_list()->GetMinHeight() > 200) + if (plater_ && sidebar().obj_list()->GetMinHeight() > 15 * wxGetApp().em_unit()) { wxWindowUpdateLocker noUpdates_sidebar(&sidebar()); - sidebar().obj_list()->SetMinSize(wxSize(-1, 200)); + sidebar().obj_list()->SetMinSize(wxSize(-1, 15 * wxGetApp().em_unit())); // !!! to correct later layouts update_mode(); // update view mode after fix of the object_list size @@ -181,7 +188,6 @@ bool GUI_App::OnInit() mainframe->Close(); } catch (const std::exception &ex) { show_error(nullptr, ex.what()); - mainframe->Close(); } }); @@ -246,6 +252,7 @@ void GUI_App::init_fonts() { m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); + #ifdef __WXMAC__ m_small_font.SetPointSize(11); m_bold_font.SetPointSize(13); @@ -407,6 +414,7 @@ bool GUI_App::select_language( wxArrayString & names, //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only. wxSetlocale(LC_NUMERIC, "C"); Preset::update_suffix_modified(); + m_imgui->set_language(m_wxLocale->GetCanonicalName().ToUTF8().data()); return true; } return false; @@ -435,6 +443,7 @@ bool GUI_App::load_language() //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only. wxSetlocale(LC_NUMERIC, "C"); Preset::update_suffix_modified(); + m_imgui->set_language(m_wxLocale->GetCanonicalName().ToUTF8().data()); return true; } } @@ -562,7 +571,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu) mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("Simple")), _(L("Simple View Mode"))); mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _(L("Advanced")), _(L("Advanced View Mode"))); mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _(L("Expert")), _(L("Expert View Mode"))); - mode_menu->Check(config_id_base + ConfigMenuModeSimple + get_mode(), true); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Check(get_mode() == comSimple); }, config_id_base + ConfigMenuModeSimple); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Check(get_mode() == comAdvanced); }, config_id_base + ConfigMenuModeAdvanced); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Check(get_mode() == comExpert); }, config_id_base + ConfigMenuModeExpert); + local_menu->AppendSubMenu(mode_menu, _(L("Mode")), _(L("Slic3r View Mode"))); local_menu->AppendSeparator(); local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application &Language"))); @@ -681,6 +693,23 @@ void GUI_App::load_current_presets() } } +bool GUI_App::OnExceptionInMainLoop() +{ + try { + throw; + } catch (const std::exception &ex) { + const std::string error = (boost::format("Uncaught exception: %1%") % ex.what()).str(); + BOOST_LOG_TRIVIAL(error) << error; + show_error(nullptr, from_u8(error)); + } catch (...) { + const char *error = "Uncaught exception: Unknown error"; + BOOST_LOG_TRIVIAL(error) << error; + show_error(nullptr, from_u8(error)); + } + + return false; +} + #ifdef __APPLE__ // wxWidgets override to get an event on open files. void GUI_App::MacOpenFiles(const wxArrayString &fileNames) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 43938b66a8..4c23e3eb72 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -81,6 +81,9 @@ class GUI_App : public wxApp wxFont m_small_font; wxFont m_bold_font; + size_t m_em_unit; // width of a "m"-symbol in pixels for current system font + // Note: for 100% Scale m_em_unit = 10 -> it's a good enough coefficient for a size setting of controls + wxLocale* m_wxLocale{ nullptr }; #if ENABLE_IMGUI @@ -108,6 +111,8 @@ public: const wxFont& small_font() { return m_small_font; } const wxFont& bold_font() { return m_bold_font; } + size_t em_unit() const { return m_em_unit; } + void set_em_unit(const size_t em_unit) { m_em_unit = em_unit; } void recreate_GUI(); void system_info(); @@ -137,6 +142,8 @@ public: bool checked_tab(Tab* tab); void load_current_presets(); + virtual bool OnExceptionInMainLoop(); + #ifdef __APPLE__ // wxWidgets override to get an event on open files. void MacOpenFiles(const wxArrayString &fileNames) override; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index f243143dae..c5d6fe9fde 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -45,18 +45,18 @@ ObjectList::ObjectList(wxWindow* parent) : // Fill CATEGORY_ICON { // ptFFF - CATEGORY_ICON[L("Layers and Perimeters")] = wxBitmap(from_u8(var("layers.png")), wxBITMAP_TYPE_PNG); - CATEGORY_ICON[L("Infill")] = wxBitmap(from_u8(var("infill.png")), wxBITMAP_TYPE_PNG); - CATEGORY_ICON[L("Support material")] = wxBitmap(from_u8(var("building.png")), wxBITMAP_TYPE_PNG); - CATEGORY_ICON[L("Speed")] = wxBitmap(from_u8(var("time.png")), wxBITMAP_TYPE_PNG); - CATEGORY_ICON[L("Extruders")] = wxBitmap(from_u8(var("funnel.png")), wxBITMAP_TYPE_PNG); - CATEGORY_ICON[L("Extrusion Width")] = wxBitmap(from_u8(var("funnel.png")), wxBITMAP_TYPE_PNG); -// CATEGORY_ICON[L("Skirt and brim")] = wxBitmap(from_u8(var("box.png")), wxBITMAP_TYPE_PNG); -// CATEGORY_ICON[L("Speed > Acceleration")] = wxBitmap(from_u8(var("time.png")), wxBITMAP_TYPE_PNG); - CATEGORY_ICON[L("Advanced")] = wxBitmap(from_u8(var("wand.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers.png"); // wxBitmap(from_u8(var("layers.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill.png"); // wxBitmap(from_u8(var("infill.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("building.png"); // wxBitmap(from_u8(var("building.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time.png"); // wxBitmap(from_u8(var("time.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel.png"); // wxBitmap(from_u8(var("funnel.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap("funnel.png"); // wxBitmap(from_u8(var("funnel.png")), wxBITMAP_TYPE_PNG); +// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap("box.png"); // wxBitmap(from_u8(var("box.png")), wxBITMAP_TYPE_PNG); +// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap("time.png"); // wxBitmap(from_u8(var("time.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap("wand.png"); // wxBitmap(from_u8(var("wand.png")), wxBITMAP_TYPE_PNG); // ptSLA - CATEGORY_ICON[L("Supports")] = wxBitmap(from_u8(var("building.png")), wxBITMAP_TYPE_PNG); - CATEGORY_ICON[L("Pad")] = wxBitmap(from_u8(var("brick.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Supports")] = create_scaled_bitmap("building.png"); // wxBitmap(from_u8(var("building.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Pad")] = create_scaled_bitmap("brick.png"); // wxBitmap(from_u8(var("brick.png")), wxBITMAP_TYPE_PNG); } // create control @@ -116,7 +116,7 @@ void ObjectList::create_objects_ctrl() SetMinSize(wxSize(-1, 3000)); // #ys_FIXME m_sizer = new wxBoxSizer(wxVERTICAL); - m_sizer->Add(this, 1, wxGROW | wxLEFT, 20); + m_sizer->Add(this, 1, wxGROW); m_objects_model = new PrusaObjectDataViewModel; AssociateModel(m_objects_model); @@ -129,13 +129,13 @@ void ObjectList::create_objects_ctrl() // column 0(Icon+Text) of the view control: // And Icon can be consisting of several bitmaps AppendColumn(new wxDataViewColumn(_(L("Name")), new PrusaBitmapTextRenderer(), - 0, 200, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); + 0, 20*wxGetApp().em_unit()/*200*/, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); // column 1 of the view control: AppendColumn(create_objects_list_extruder_column(4)); // column 2 of the view control: - AppendBitmapColumn(" ", 2, wxDATAVIEW_CELL_INERT, 25, + AppendBitmapColumn(" ", 2, wxDATAVIEW_CELL_INERT, int(2.5 * wxGetApp().em_unit())/*25*/, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); } @@ -218,7 +218,8 @@ wxDataViewColumn* ObjectList::create_objects_list_extruder_column(int extruders_ choices.Add(wxString::Format("%d", i)); wxDataViewChoiceRenderer *c = new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, wxALIGN_CENTER_HORIZONTAL); - wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, 1, 80, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); + wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, 1, + 8*wxGetApp().em_unit()/*80*/, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); return column; } @@ -334,11 +335,18 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const void ObjectList::init_icons() { - m_bmp_modifiermesh = wxBitmap(from_u8(var("lambda.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); - m_bmp_solidmesh = wxBitmap(from_u8(var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); +// m_bmp_modifiermesh = wxBitmap(from_u8(var("lambda.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); +// m_bmp_solidmesh = wxBitmap(from_u8(var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); + +// m_bmp_support_enforcer = wxBitmap(from_u8(var("support_enforcer_.png")), wxBITMAP_TYPE_PNG); +// m_bmp_support_blocker = wxBitmap(from_u8(var("support_blocker_.png")), wxBITMAP_TYPE_PNG); + + + m_bmp_modifiermesh = create_scaled_bitmap("lambda.png"); + m_bmp_solidmesh = create_scaled_bitmap("object.png"); + m_bmp_support_enforcer = create_scaled_bitmap("support_enforcer_.png"); + m_bmp_support_blocker = create_scaled_bitmap("support_blocker_.png"); - m_bmp_support_enforcer = wxBitmap(from_u8(var("support_enforcer_.png")), wxBITMAP_TYPE_PNG); - m_bmp_support_blocker = wxBitmap(from_u8(var("support_blocker_.png")), wxBITMAP_TYPE_PNG); m_bmp_vector.reserve(4); // bitmaps for different types of parts m_bmp_vector.push_back(&m_bmp_solidmesh); // Add part @@ -348,13 +356,16 @@ void ObjectList::init_icons() m_objects_model->SetVolumeBitmaps(m_bmp_vector); // init icon for manifold warning - m_bmp_manifold_warning = wxBitmap(from_u8(var("exclamation_mark_.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); +// m_bmp_manifold_warning = wxBitmap(from_u8(var("exclamation_mark_.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); + m_bmp_manifold_warning = create_scaled_bitmap("exclamation_mark_.png"); // init bitmap for "Split to sub-objects" context menu - m_bmp_split = wxBitmap(from_u8(var("split.png")), wxBITMAP_TYPE_PNG); +// m_bmp_split = wxBitmap(from_u8(var("split.png")), wxBITMAP_TYPE_PNG); + m_bmp_split = create_scaled_bitmap("split.png"); // init bitmap for "Add Settings" context menu - m_bmp_cog = wxBitmap(from_u8(var("cog.png")), wxBITMAP_TYPE_PNG); +// m_bmp_cog = wxBitmap(from_u8(var("cog.png")), wxBITMAP_TYPE_PNG); + m_bmp_cog = create_scaled_bitmap("cog.png"); } @@ -807,7 +818,7 @@ void ObjectList::update_settings_item() } } -void ObjectList::append_menu_item_add_generic(wxMenuItem* menu, const int type) { +void ObjectList::append_menu_item_add_generic(wxMenuItem* menu, const ModelVolumeType type) { auto sub_menu = new wxMenu; if (wxGetApp().get_mode() == comExpert) { @@ -816,10 +827,9 @@ void ObjectList::append_menu_item_add_generic(wxMenuItem* menu, const int type) sub_menu->AppendSeparator(); } - std::vector menu_items = { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }; - for (auto& item : menu_items) { + for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }) { append_menu_item(sub_menu, wxID_ANY, _(item), "", - [this, type, item](wxCommandEvent&) { load_generic_subobject(_(item).ToUTF8().data(), type); }, "", menu->GetMenu()); + [this, type, item](wxCommandEvent&) { load_generic_subobject(item, type); }, "", menu->GetMenu()); } menu->SetSubMenu(sub_menu); @@ -828,10 +838,10 @@ void ObjectList::append_menu_item_add_generic(wxMenuItem* menu, const int type) void ObjectList::append_menu_items_add_volume(wxMenu* menu) { // Note: id accords to type of the sub-object, so sequence of the menu items is important - std::vector menu_object_types_items = {L("Add part"), // ~ModelVolume::MODEL_PART - L("Add modifier"), // ~ModelVolume::PARAMETER_MODIFIER - L("Add support enforcer"), // ~ModelVolume::SUPPORT_ENFORCER - L("Add support blocker") }; // ~ModelVolume::SUPPORT_BLOCKER + std::vector menu_object_types_items = {L("Add part"), // ~ModelVolumeType::MODEL_PART + L("Add modifier"), // ~ModelVolumeType::PARAMETER_MODIFIER + L("Add support enforcer"), // ~ModelVolumeType::SUPPORT_ENFORCER + L("Add support blocker") }; // ~ModelVolumeType::SUPPORT_BLOCKER // Update "add" items(delete old & create new) settings popupmenu for (auto& item : menu_object_types_items){ @@ -845,15 +855,15 @@ void ObjectList::append_menu_items_add_volume(wxMenu* menu) if (mode < comExpert) { append_menu_item(menu, wxID_ANY, _(L("Add part")), "", - [this](wxCommandEvent&) { load_subobject(ModelVolume::MODEL_PART); }, *m_bmp_vector[ModelVolume::MODEL_PART]); + [this](wxCommandEvent&) { load_subobject(ModelVolumeType::MODEL_PART); }, *m_bmp_vector[int(ModelVolumeType::MODEL_PART)]); } if (mode == comSimple) { append_menu_item(menu, wxID_ANY, _(L("Add support enforcer")), "", - [this](wxCommandEvent&) { load_generic_subobject(_(L("Box")).ToUTF8().data(), ModelVolume::SUPPORT_ENFORCER); }, - *m_bmp_vector[ModelVolume::SUPPORT_ENFORCER]); + [this](wxCommandEvent&) { load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); }, + *m_bmp_vector[int(ModelVolumeType::SUPPORT_ENFORCER)]); append_menu_item(menu, wxID_ANY, _(L("Add support blocker")), "", - [this](wxCommandEvent&) { load_generic_subobject(_(L("Box")).ToUTF8().data(), ModelVolume::SUPPORT_BLOCKER); }, - *m_bmp_vector[ModelVolume::SUPPORT_BLOCKER]); + [this](wxCommandEvent&) { load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_BLOCKER); }, + *m_bmp_vector[int(ModelVolumeType::SUPPORT_BLOCKER)]); return; } @@ -864,7 +874,7 @@ void ObjectList::append_menu_items_add_volume(wxMenu* menu) auto menu_item = new wxMenuItem(menu, wxID_ANY, _(item)); menu_item->SetBitmap(*m_bmp_vector[type]); - append_menu_item_add_generic(menu_item, type); + append_menu_item_add_generic(menu_item, ModelVolumeType(type)); menu->Append(menu_item); } @@ -913,7 +923,7 @@ wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_) menu->DestroySeparators(); // delete old separators const auto sel_vol = get_selected_model_volume(); - if (sel_vol && sel_vol->type() >= ModelVolume::SUPPORT_ENFORCER) + if (sel_vol && sel_vol->type() >= ModelVolumeType::SUPPORT_ENFORCER) return nullptr; const ConfigOptionMode mode = wxGetApp().get_mode(); @@ -937,7 +947,7 @@ wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_) menu_item->SetBitmap(m_bmp_cog); // const auto sel_vol = get_selected_model_volume(); -// if (sel_vol && sel_vol->type() >= ModelVolume::SUPPORT_ENFORCER) +// if (sel_vol && sel_vol->type() >= ModelVolumeType::SUPPORT_ENFORCER) // menu_item->Enable(false); // else menu_item->SetSubMenu(create_settings_popupmenu(menu)); @@ -1092,7 +1102,7 @@ void ObjectList::update_opt_keys(t_config_option_keys& opt_keys) opt_keys.erase(opt_keys.begin() + i); } -void ObjectList::load_subobject(int type) +void ObjectList::load_subobject(ModelVolumeType type) { auto item = GetSelection(); if (!item || m_objects_model->GetParent(item) != wxDataViewItem(0)) @@ -1115,7 +1125,7 @@ void ObjectList::load_subobject(int type) void ObjectList::load_part( ModelObject* model_object, wxArrayString& part_names, - int type) + ModelVolumeType type) { wxWindow* parent = wxGetApp().tab_panel()->GetPage(0); @@ -1148,7 +1158,7 @@ void ObjectList::load_part( ModelObject* model_object, #endif // !ENABLE_VOLUMES_CENTERING_FIXES volume->translate(delta); auto new_volume = model_object->add_volume(*volume); - new_volume->set_type(static_cast(type)); + new_volume->set_type(type); new_volume->name = boost::filesystem::path(input_file).filename().string(); part_names.Add(from_u8(new_volume->name)); @@ -1163,28 +1173,28 @@ void ObjectList::load_part( ModelObject* model_object, } -void ObjectList::load_generic_subobject(const std::string& type_name, const int type) +void ObjectList::load_generic_subobject(const std::string& type_name, const ModelVolumeType type) { const auto obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return; - const std::string name = "lambda-" + type_name; + const wxString name = _(L("Generic")) + "-" + _(type_name); TriangleMesh mesh; auto& bed_shape = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("bed_shape")->values; const auto& sz = BoundingBoxf(bed_shape).size(); const auto side = 0.1 * std::max(sz(0), sz(1)); - if (type_name == _("Box")) { + if (type_name == "Box") { mesh = make_cube(side, side, side); // box sets the base coordinate at 0, 0, move to center of plate mesh.translate(-side * 0.5, -side * 0.5, 0); } - else if (type_name == _("Cylinder")) + else if (type_name == "Cylinder") mesh = make_cylinder(0.5*side, side); - else if (type_name == _("Sphere")) + else if (type_name == "Sphere") mesh = make_sphere(0.5*side, PI/18); - else if (type_name == _("Slab")) { + else if (type_name == "Slab") { const auto& size = (*m_objects)[obj_idx]->bounding_box().size(); mesh = make_cube(size(0)*1.5, size(1)*1.5, size(2)*0.5); // box sets the base coordinate at 0, 0, move to center of plate and move it up to initial_z @@ -1193,7 +1203,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const int mesh.repair(); auto new_volume = (*m_objects)[obj_idx]->add_volume(mesh); - new_volume->set_type(static_cast(type)); + new_volume->set_type(type); #if !ENABLE_GENERIC_SUBPARTS_PLACEMENT new_volume->set_offset(Vec3d(0.0, 0.0, (*m_objects)[obj_idx]->origin_translation(2) - mesh.stl.stats.min(2))); @@ -1220,7 +1230,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const int } #endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT - new_volume->name = name; + new_volume->name = into_u8(name); // set a default extruder value, since user can't add it manually new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); @@ -1228,7 +1238,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const int parts_changed(obj_idx); const auto object_item = m_objects_model->GetTopParent(GetSelection()); - select_item(m_objects_model->AddVolumeChild(object_item, from_u8(name), type)); + select_item(m_objects_model->AddVolumeChild(object_item, name, type)); #ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); #endif //no __WXOSX__ //__WXMSW__ @@ -1360,7 +1370,7 @@ void ObjectList::split() for (auto id = 0; id < model_object->volumes.size(); id++) { const auto vol_item = m_objects_model->AddVolumeChild(parent, from_u8(model_object->volumes[id]->name), model_object->volumes[id]->is_modifier() ? - ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART, + ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, model_object->volumes[id]->config.has("extruder") ? model_object->volumes[id]->config.option("extruder")->value : 0, false); @@ -1962,15 +1972,15 @@ void ObjectList::change_part_type() if (!volume) return; - const auto type = volume->type(); - if (type == ModelVolume::MODEL_PART) + const ModelVolumeType type = volume->type(); + if (type == ModelVolumeType::MODEL_PART) { const int obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return; int model_part_cnt = 0; for (auto vol : (*m_objects)[obj_idx]->volumes) { - if (vol->type() == ModelVolume::MODEL_PART) + if (vol->type() == ModelVolumeType::MODEL_PART) ++model_part_cnt; } @@ -1982,13 +1992,13 @@ void ObjectList::change_part_type() const wxString names[] = { "Part", "Modifier", "Support Enforcer", "Support Blocker" }; - auto new_type = wxGetSingleChoiceIndex("Type: ", _(L("Select type of part")), wxArrayString(4, names), type); + auto new_type = ModelVolumeType(wxGetSingleChoiceIndex("Type: ", _(L("Select type of part")), wxArrayString(4, names), int(type))); - if (new_type == type || new_type < 0) + if (new_type == type || new_type == ModelVolumeType::INVALID) return; const auto item = GetSelection(); - volume->set_type(static_cast(new_type)); + volume->set_type(new_type); m_objects_model->SetVolumeType(item, new_type); m_parts_changed = true; @@ -1998,11 +2008,11 @@ void ObjectList::change_part_type() //(we show additional settings for Part and Modifier and hide it for Support Blocker/Enforcer) const auto settings_item = m_objects_model->GetSettingsItem(item); if (settings_item && - (new_type == ModelVolume::SUPPORT_ENFORCER || new_type == ModelVolume::SUPPORT_BLOCKER)) { + (new_type == ModelVolumeType::SUPPORT_ENFORCER || new_type == ModelVolumeType::SUPPORT_BLOCKER)) { m_objects_model->Delete(settings_item); } else if (!settings_item && - (new_type == ModelVolume::MODEL_PART || new_type == ModelVolume::PARAMETER_MODIFIER)) { + (new_type == ModelVolumeType::MODEL_PART || new_type == ModelVolumeType::PARAMETER_MODIFIER)) { select_item(m_objects_model->AddSettingsChild(item)); } } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index e572bec827..138bacac4a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -21,6 +21,7 @@ class ConfigOptionsGroup; class DynamicPrintConfig; class ModelObject; class ModelVolume; +enum class ModelVolumeType : int; // FIXME: broken build on mac os because of this is missing: typedef std::vector t_config_option_keys; @@ -173,7 +174,7 @@ public: void get_freq_settings_choice(const wxString& bundle_name); void update_settings_item(); - void append_menu_item_add_generic(wxMenuItem* menu, const int type); + void append_menu_item_add_generic(wxMenuItem* menu, const ModelVolumeType type); void append_menu_items_add_volume(wxMenu* menu); wxMenuItem* append_menu_item_split(wxMenu* menu); wxMenuItem* append_menu_item_settings(wxMenu* menu); @@ -190,9 +191,9 @@ public: void update_opt_keys(t_config_option_keys& t_optopt_keys); - void load_subobject(int type); - void load_part(ModelObject* model_object, wxArrayString& part_names, int type); - void load_generic_subobject(const std::string& type_name, const int type); + void load_subobject(ModelVolumeType type); + void load_part(ModelObject* model_object, wxArrayString& part_names, ModelVolumeType type); + void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void del_object(const int obj_idx); void del_subobject_item(wxDataViewItem& item); void del_settings_from_config(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index a00e4676bd..5afd7ae59c 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -22,7 +22,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : #endif // __APPLE__ { m_og->set_name(_(L("Object Manipulation"))); - m_og->label_width = 125; + m_og->label_width = 12 * wxGetApp().em_unit();//125; m_og->set_grid_vgap(5); m_og->m_on_change = std::bind(&ObjectManipulation::on_change, this, std::placeholders::_1, std::placeholders::_2); @@ -44,15 +44,17 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : def.label = L("Name"); def.gui_type = "legend"; def.tooltip = L("Object name"); - def.width = 200; + def.width = 21 * wxGetApp().em_unit(); def.default_value = new ConfigOptionString{ " " }; m_og->append_single_option_line(Option(def, "object_name")); + const int field_width = 5 * wxGetApp().em_unit()/*50*/; + // Legend for object modification auto line = Line{ "", "" }; def.label = ""; def.type = coString; - def.width = 50; + def.width = field_width/*50*/; std::vector axes{ "x", "y", "z" }; for (const auto axis : axes) { @@ -64,13 +66,13 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_og->append_line(line); - auto add_og_to_object_settings = [this](const std::string& option_name, const std::string& sidetext) + auto add_og_to_object_settings = [this, field_width](const std::string& option_name, const std::string& sidetext) { Line line = { _(option_name), "" }; ConfigOptionDef def; def.type = coFloat; def.default_value = new ConfigOptionFloat(0.0); - def.width = 50; + def.width = field_width/*50*/; // Add "uniform scaling" button in front of "Scale" option if (option_name == "Scale") { @@ -88,8 +90,9 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // Add empty bmp (Its size have to be equal to PrusaLockButton) in front of "Size" option to label alignment else if (option_name == "Size") { line.near_label_widget = [this](wxWindow* parent) { - return new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap, wxDefaultPosition, - wxBitmap(from_u8(var("one_layer_lock_on.png")), wxBITMAP_TYPE_PNG).GetSize()); + return new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap, wxDefaultPosition, +// wxBitmap(from_u8(var("one_layer_lock_on.png")), wxBITMAP_TYPE_PNG).GetSize()); + create_scaled_bitmap("one_layer_lock_on.png").GetSize()); }; } diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 477b0530b5..3781cbf450 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -75,7 +75,8 @@ void ObjectSettings::update_settings_list() { auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line - auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(from_u8(var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG), +// auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(from_u8(var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG), + auto btn = new wxBitmapButton(parent, wxID_ANY, create_scaled_bitmap("colorchange_delete_on.png"), wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); #ifdef __WXMSW__ btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -98,7 +99,7 @@ void ObjectSettings::update_settings_list() std::vector categories; if (!(opt_keys.size() == 1 && opt_keys[0] == "extruder"))// return; { - auto extruders_cnt = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : + const int extruders_cnt = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : wxGetApp().preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); for (auto& opt_key : opt_keys) { @@ -119,8 +120,8 @@ void ObjectSettings::update_settings_list() continue; auto optgroup = std::make_shared(m_parent, cat.first, config, false, extra_column); - optgroup->label_width = 150; - optgroup->sidetext_width = 70; + optgroup->label_width = 15 * wxGetApp().em_unit();//150; + optgroup->sidetext_width = 7 * wxGetApp().em_unit();//70; optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) { wxGetApp().obj_list()->part_settings_changed(); }; @@ -130,7 +131,7 @@ void ObjectSettings::update_settings_list() if (opt == "extruder") continue; Option option = optgroup->get_option(opt); - option.opt.width = 70; + option.opt.width = 7 * wxGetApp().em_unit();//70; optgroup->append_single_option_line(option); } optgroup->reload_config(); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 0ad6c35625..25872fc1f7 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -52,7 +52,7 @@ View3D::~View3D() bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) { - if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)) + if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) return false; m_canvas_widget = GLCanvas3DManager::create_wxglcanvas(this); @@ -69,9 +69,6 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba m_canvas->set_config(config); m_canvas->enable_gizmos(true); m_canvas->enable_toolbar(true); -#if !ENABLE_REWORKED_BED_SHAPE_CHANGE - m_canvas->enable_force_zoom_to_bed(true); -#endif // !ENABLE_REWORKED_BED_SHAPE_CHANGE #if !ENABLE_IMGUI m_gizmo_widget = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); @@ -92,6 +89,12 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba return true; } +void View3D::set_bed(Bed3D* bed) +{ + if (m_canvas != nullptr) + m_canvas->set_bed(bed); +} + void View3D::set_view_toolbar(GLToolbar* toolbar) { if (m_canvas != nullptr) @@ -104,15 +107,10 @@ void View3D::set_as_dirty() m_canvas->set_as_dirty(); } -void View3D::set_bed_shape(const Pointfs& shape) +void View3D::bed_shape_changed() { if (m_canvas != nullptr) - { - m_canvas->set_bed_shape(shape); -#if !ENABLE_REWORKED_BED_SHAPE_CHANGE - m_canvas->zoom_to_bed(); -#endif // !ENABLE_REWORKED_BED_SHAPE_CHANGE - } + m_canvas->bed_shape_changed(); } void View3D::select_view(const std::string& direction) @@ -229,7 +227,7 @@ bool Preview::init(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlici if ((config == nullptr) || (process == nullptr) || (gcode_preview_data == nullptr)) return false; - if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)) + if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) return false; m_canvas_widget = GLCanvas3DManager::create_wxglcanvas(this); @@ -259,7 +257,7 @@ bool Preview::init(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlici m_label_show_features = new wxStaticText(this, wxID_ANY, _(L("Show"))); m_combochecklist_features = new wxComboCtrl(); - m_combochecklist_features->Create(this, wxID_ANY, _(L("Feature types")), wxDefaultPosition, wxSize(200, -1), wxCB_READONLY); + m_combochecklist_features->Create(this, wxID_ANY, _(L("Feature types")), wxDefaultPosition, wxSize(15 * wxGetApp().em_unit(), -1), wxCB_READONLY); std::string feature_text = GUI::into_u8(_(L("Feature types"))); std::string feature_items = GUI::into_u8( _(L("Perimeter")) + "|" + @@ -345,6 +343,12 @@ Preview::~Preview() } } +void Preview::set_bed(Bed3D* bed) +{ + if (m_canvas != nullptr) + m_canvas->set_bed(bed); +} + void Preview::set_view_toolbar(GLToolbar* toolbar) { if (m_canvas != nullptr) @@ -376,9 +380,10 @@ void Preview::set_enabled(bool enabled) m_enabled = enabled; } -void Preview::set_bed_shape(const Pointfs& shape) +void Preview::bed_shape_changed() { - m_canvas->set_bed_shape(shape); + if (m_canvas != nullptr) + m_canvas->bed_shape_changed(); } void Preview::select_view(const std::string& direction) diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 49dbed44d9..6cd67013cc 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -27,6 +27,7 @@ namespace GUI { class GLCanvas3D; class GLToolbar; +class Bed3D; class View3D : public wxPanel { @@ -48,10 +49,11 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } GLCanvas3D* get_canvas3d() { return m_canvas; } + void set_bed(Bed3D* bed); void set_view_toolbar(GLToolbar* toolbar); void set_as_dirty(); - void set_bed_shape(const Pointfs& shape); + void bed_shape_changed(); void select_view(const std::string& direction); void select_all(); @@ -114,12 +116,13 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } GLCanvas3D* get_canvas3d() { return m_canvas; } + void set_bed(Bed3D* bed); void set_view_toolbar(GLToolbar* toolbar); void set_number_extruders(unsigned int number_extruders); void set_canvas_as_dirty(); void set_enabled(bool enabled); - void set_bed_shape(const Pointfs& shape); + void bed_shape_changed(); void select_view(const std::string& direction); void set_viewport_from_scene(GLCanvas3D* canvas); void set_viewport_into_scene(GLCanvas3D* canvas); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index e36a68eda1..61baf352bd 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -25,10 +26,12 @@ namespace GUI { ImGuiWrapper::ImGuiWrapper() - : m_font_texture(0) + : m_glyph_ranges(nullptr) + , m_font_texture(0) , m_style_scaling(1.0) , m_mouse_buttons(0) , m_disabled(false) + , m_new_frame_open(false) { } @@ -43,12 +46,43 @@ bool ImGuiWrapper::init() ImGui::CreateContext(); init_default_font(m_style_scaling); + init_input(); + init_style(); ImGui::GetIO().IniFilename = nullptr; return true; } +void ImGuiWrapper::set_language(const std::string &language) +{ + const ImWchar *ranges = nullptr; + size_t idx = language.find('_'); + std::string lang = (idx == std::string::npos) ? language : language.substr(0, idx); + static const ImWchar ranges_latin2[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x0100, 0x017F, // Latin Extended-A + 0, + }; + if (lang == "cs" || lang == "pl") { + ranges = ranges_latin2; + } else if (lang == "ru" || lang == "uk") { + ranges = ImGui::GetIO().Fonts->GetGlyphRangesCyrillic(); + } else if (lang == "jp") { + ranges = ImGui::GetIO().Fonts->GetGlyphRangesJapanese(); + } else if (lang == "kr") { + ranges = ImGui::GetIO().Fonts->GetGlyphRangesKorean(); + } else if (lang == "zh") { + ranges = ImGui::GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon(); + } + + if (ranges != m_glyph_ranges) { + m_glyph_ranges = ranges; + init_default_font(m_style_scaling); + } +} + void ImGuiWrapper::set_display_size(float w, float h) { ImGuiIO& io = ImGui::GetIO(); @@ -67,6 +101,10 @@ void ImGuiWrapper::set_style_scaling(float scaling) bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) { + if (! display_initialized()) { + return false; + } + ImGuiIO& io = ImGui::GetIO(); io.MousePos = ImVec2((float)evt.GetX(), (float)evt.GetY()); io.MouseDown[0] = evt.LeftDown(); @@ -74,23 +112,63 @@ bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) io.MouseDown[2] = evt.MiddleDown(); unsigned buttons = (evt.LeftDown() ? 1 : 0) | (evt.RightDown() ? 2 : 0) | (evt.MiddleDown() ? 4 : 0); - bool res = buttons != m_mouse_buttons; m_mouse_buttons = buttons; - return res; + + new_frame(); + return want_mouse(); +} + +bool ImGuiWrapper::update_key_data(wxKeyEvent &evt) +{ + if (! display_initialized()) { + return false; + } + + ImGuiIO& io = ImGui::GetIO(); + + if (evt.GetEventType() == wxEVT_CHAR) { + // Char event + const auto key = evt.GetUnicodeKey(); + if (key != 0) { + io.AddInputCharacter(key); + } + + new_frame(); + return want_keyboard() || want_text_input(); + } else { + // Key up/down event + int key = evt.GetKeyCode(); + wxCHECK_MSG(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown), false, "Received invalid key code"); + + io.KeysDown[key] = evt.GetEventType() == wxEVT_KEY_DOWN; + io.KeyShift = evt.ShiftDown(); + io.KeyCtrl = evt.ControlDown(); + io.KeyAlt = evt.AltDown(); + io.KeySuper = evt.MetaDown(); + + new_frame(); + return want_keyboard() || want_text_input(); + } } void ImGuiWrapper::new_frame() { + if (m_new_frame_open) { + return; + } + if (m_font_texture == 0) create_device_objects(); ImGui::NewFrame(); + m_new_frame_open = true; } void ImGuiWrapper::render() { ImGui::Render(); render_draw_data(ImGui::GetDrawData()); + m_new_frame_open = false; } void ImGuiWrapper::set_next_window_pos(float x, float y, int flag) @@ -125,6 +203,12 @@ bool ImGuiWrapper::button(const wxString &label) return ImGui::Button(label_utf8.c_str()); } +bool ImGuiWrapper::radio_button(const wxString &label, bool active) +{ + auto label_utf8 = into_u8(label); + return ImGui::RadioButton(label_utf8.c_str(), active); +} + bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) { return ImGui::InputDouble(label.c_str(), const_cast(&value), 0.0f, 0.0f, format.c_str()); @@ -161,6 +245,28 @@ void ImGuiWrapper::text(const wxString &label) ImGui::Text(label_utf8.c_str(), NULL); } + +bool ImGuiWrapper::combo(const wxString& label, const std::vector& options, wxString& selection) +{ + std::string selection_u8 = into_u8(selection); + + // this is to force the label to the left of the widget: + text(label); + ImGui::SameLine(); + + if (ImGui::BeginCombo("", selection_u8.c_str())) { + for (const wxString& option : options) { + std::string option_u8 = into_u8(option); + bool is_selected = (selection_u8.empty()) ? false : (option_u8 == selection_u8); + if (ImGui::Selectable(option_u8.c_str(), is_selected)) + selection = option_u8; + } + ImGui::EndCombo(); + return true; + } + return false; +} + void ImGuiWrapper::disabled_begin(bool disabled) { wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call"); @@ -210,7 +316,7 @@ void ImGuiWrapper::init_default_font(float scaling) ImGuiIO& io = ImGui::GetIO(); io.Fonts->Clear(); - ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf").c_str(), font_size * scaling); + ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf").c_str(), font_size * scaling, nullptr, m_glyph_ranges); if (font == nullptr) { font = io.Fonts->AddFontDefault(); if (font == nullptr) { @@ -249,6 +355,82 @@ void ImGuiWrapper::create_fonts_texture() glBindTexture(GL_TEXTURE_2D, last_texture); } +void ImGuiWrapper::init_input() +{ + ImGuiIO& io = ImGui::GetIO(); + + // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. + io.KeyMap[ImGuiKey_Tab] = WXK_TAB; + io.KeyMap[ImGuiKey_LeftArrow] = WXK_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = WXK_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = WXK_UP; + io.KeyMap[ImGuiKey_DownArrow] = WXK_DOWN; + io.KeyMap[ImGuiKey_PageUp] = WXK_PAGEUP; + io.KeyMap[ImGuiKey_PageDown] = WXK_PAGEDOWN; + io.KeyMap[ImGuiKey_Home] = WXK_HOME; + io.KeyMap[ImGuiKey_End] = WXK_END; + io.KeyMap[ImGuiKey_Insert] = WXK_INSERT; + io.KeyMap[ImGuiKey_Delete] = WXK_DELETE; + io.KeyMap[ImGuiKey_Backspace] = WXK_BACK; + io.KeyMap[ImGuiKey_Space] = WXK_SPACE; + io.KeyMap[ImGuiKey_Enter] = WXK_RETURN; + io.KeyMap[ImGuiKey_Escape] = WXK_ESCAPE; + io.KeyMap[ImGuiKey_A] = 'A'; + io.KeyMap[ImGuiKey_C] = 'C'; + io.KeyMap[ImGuiKey_V] = 'V'; + io.KeyMap[ImGuiKey_X] = 'X'; + io.KeyMap[ImGuiKey_Y] = 'Y'; + io.KeyMap[ImGuiKey_Z] = 'Z'; + + // Don't let imgui special-case Mac, wxWidgets already do that + io.ConfigMacOSXBehaviors = false; + + // Setup clipboard interaction callbacks + io.SetClipboardTextFn = clipboard_set; + io.GetClipboardTextFn = clipboard_get; + io.ClipboardUserData = this; +} + +void ImGuiWrapper::init_style() +{ + ImGuiStyle &style = ImGui::GetStyle(); + + auto set_color = [&](ImGuiCol_ col, unsigned hex_color) { + style.Colors[col] = ImVec4( + ((hex_color >> 24) & 0xff) / 255.0f, + ((hex_color >> 16) & 0xff) / 255.0f, + ((hex_color >> 8) & 0xff) / 255.0f, + (hex_color & 0xff) / 255.0f); + }; + + static const unsigned COL_GREY_DARK = 0x444444ff; + static const unsigned COL_GREY_LIGHT = 0x666666ff; + static const unsigned COL_ORANGE_DARK = 0xba5418ff; + static const unsigned COL_ORANGE_LIGHT = 0xff6f22ff; + + // Generics + set_color(ImGuiCol_TitleBgActive, COL_ORANGE_DARK); + set_color(ImGuiCol_FrameBg, COL_GREY_DARK); + set_color(ImGuiCol_FrameBgHovered, COL_GREY_LIGHT); + set_color(ImGuiCol_FrameBgActive, COL_GREY_LIGHT); + + // Text selection + set_color(ImGuiCol_TextSelectedBg, COL_ORANGE_DARK); + + // Buttons + set_color(ImGuiCol_Button, COL_ORANGE_DARK); + set_color(ImGuiCol_ButtonHovered, COL_ORANGE_LIGHT); + set_color(ImGuiCol_ButtonActive, COL_ORANGE_LIGHT); + + // Checkbox + set_color(ImGuiCol_CheckMark, COL_ORANGE_LIGHT); + + // ComboBox items + set_color(ImGuiCol_Header, COL_ORANGE_DARK); + set_color(ImGuiCol_HeaderHovered, COL_ORANGE_LIGHT); + set_color(ImGuiCol_HeaderActive, COL_ORANGE_LIGHT); +} + void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) @@ -347,6 +529,12 @@ void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); } +bool ImGuiWrapper::display_initialized() const +{ + const ImGuiIO& io = ImGui::GetIO(); + return io.DisplaySize.x >= 0.0f && io.DisplaySize.y >= 0.0f; +} + void ImGuiWrapper::destroy_device_objects() { destroy_fonts_texture(); @@ -362,6 +550,37 @@ void ImGuiWrapper::destroy_fonts_texture() } } +const char* ImGuiWrapper::clipboard_get(void* user_data) +{ + ImGuiWrapper *self = reinterpret_cast(user_data); + + const char* res = ""; + + if (wxTheClipboard->Open()) { + if (wxTheClipboard->IsSupported(wxDF_TEXT)) { + wxTextDataObject data; + wxTheClipboard->GetData(data); + + if (data.GetTextLength() > 0) { + self->m_clipboard_text = into_u8(data.GetText()); + res = self->m_clipboard_text.c_str(); + } + } + + wxTheClipboard->Close(); + } + + return res; +} + +void ImGuiWrapper::clipboard_set(void* /* user_data */, const char* text) +{ + if (wxTheClipboard->Open()) { + wxTheClipboard->SetData(new wxTextDataObject(wxString::FromUTF8(text))); // object owned by the clipboard + wxTheClipboard->Close(); + } +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 47a1fb9377..2cadc773ca 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -10,6 +10,7 @@ class wxString; class wxMouseEvent; +class wxKeyEvent; namespace Slic3r { @@ -20,10 +21,13 @@ class ImGuiWrapper typedef std::map FontsMap; FontsMap m_fonts; + const ImWchar *m_glyph_ranges; unsigned m_font_texture; float m_style_scaling; unsigned m_mouse_buttons; bool m_disabled; + bool m_new_frame_open; + std::string m_clipboard_text; public: ImGuiWrapper(); @@ -32,9 +36,11 @@ public: bool init(); void read_glsl_version(); + void set_language(const std::string &language); void set_display_size(float w, float h); void set_style_scaling(float scaling); bool update_mouse_data(wxMouseEvent &evt); + bool update_key_data(wxKeyEvent &evt); void new_frame(); void render(); @@ -47,10 +53,12 @@ public: void end(); bool button(const wxString &label); + bool radio_button(const wxString &label, bool active); bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f"); bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f"); bool checkbox(const wxString &label, bool &value); void text(const wxString &label); + bool combo(const wxString& label, const std::vector& options, wxString& current_selection); void disabled_begin(bool disabled); void disabled_end(); @@ -59,13 +67,20 @@ public: bool want_keyboard() const; bool want_text_input() const; bool want_any_input() const; + private: void init_default_font(float scaling); void create_device_objects(); void create_fonts_texture(); + void init_input(); + void init_style(); void render_draw_data(ImDrawData *draw_data); + bool display_initialized() const; void destroy_device_objects(); void destroy_fonts_texture(); + + static const char* clipboard_get(void* user_data); + static void clipboard_set(void* user_data, const char* text); }; diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index d893d6f765..66e9deec86 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -4,6 +4,7 @@ #include "GUI.hpp" #include #include "GUI_App.hpp" +#include "wxExtensions.hpp" namespace Slic3r { namespace GUI { @@ -16,7 +17,8 @@ KBShortcutsDialog::KBShortcutsDialog() auto main_sizer = new wxBoxSizer(wxVERTICAL); // logo - wxBitmap logo_bmp = wxBitmap(from_u8(Slic3r::var("Slic3r_32px.png")), wxBITMAP_TYPE_PNG); +// wxBitmap logo_bmp = wxBitmap(from_u8(Slic3r::var("Slic3r_32px.png")), wxBITMAP_TYPE_PNG); + const wxBitmap logo_bmp = create_scaled_bitmap("Slic3r_32px.png"); // fonts wxFont head_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); @@ -54,7 +56,7 @@ KBShortcutsDialog::KBShortcutsDialog() hsizer->Add(logo, 0, wxEXPAND | wxLEFT | wxRIGHT, 15); // head - wxStaticText* head = new wxStaticText(panel, wxID_ANY, sc.first, wxDefaultPosition, wxSize(200,-1)); + wxStaticText* head = new wxStaticText(panel, wxID_ANY, sc.first, wxDefaultPosition, wxSize(20 * wxGetApp().em_unit(), -1)); head->SetFont(head_font); hsizer->Add(head, 0, wxALIGN_CENTER_VERTICAL); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 91574cda67..4568110519 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -53,6 +53,11 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL SLIC3R_VERSION + _(L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases"))); + + // initialize default width_unit according to the width of the one symbol ("x") of the current system font + const wxSize size = GetTextExtent("m"); + wxGetApp().set_em_unit(size.x-1); + // initialize tabpanel and menubar init_tabpanel(); init_menubar(); @@ -105,6 +110,12 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL event.Skip(); }); + Bind(wxEVT_ACTIVATE, [this](wxActivateEvent& event) { + if (m_plater != nullptr && event.GetActive()) + m_plater->on_activate(); + event.Skip(); + }); + wxGetApp().persist_window_geometry(this); update_ui_from_settings(); // FIXME (?) @@ -136,11 +147,11 @@ void MainFrame::init_tabpanel() wxGetApp().obj_list()->create_popup_menus(); // The following event is emited by Tab implementation on config value change. - Bind(EVT_TAB_VALUE_CHANGED, &MainFrame::on_value_changed, this); + Bind(EVT_TAB_VALUE_CHANGED, &MainFrame::on_value_changed, this); // #ys_FIXME_to_delete // The following event is emited by Tab on preset selection, // or when the preset's "modified" status changes. - Bind(EVT_TAB_PRESETS_CHANGED, &MainFrame::on_presets_changed, this); + Bind(EVT_TAB_PRESETS_CHANGED, &MainFrame::on_presets_changed, this); // #ys_FIXME_to_delete create_preset_tabs(); @@ -822,6 +833,7 @@ void MainFrame::select_view(const std::string& direction) m_plater->select_view(direction); } +// #ys_FIXME_to_delete void MainFrame::on_presets_changed(SimpleEvent &event) { auto *tab = dynamic_cast(event.GetEventObject()); @@ -846,6 +858,7 @@ void MainFrame::on_presets_changed(SimpleEvent &event) } } +// #ys_FIXME_to_delete void MainFrame::on_value_changed(wxCommandEvent& event) { auto *tab = dynamic_cast(event.GetEventObject()); @@ -861,12 +874,12 @@ void MainFrame::on_value_changed(wxCommandEvent& event) m_plater->on_extruders_change(value); } } - // Don't save while loading for the first time. - if (m_loaded) { - AppConfig &cfg = *wxGetApp().app_config; - if (cfg.get("autosave") == "1") - cfg.save(); - } +} + +void MainFrame::on_config_changed(DynamicPrintConfig* config) const +{ + if (m_plater) + m_plater->on_config_change(*config); // propagate config change events to the plater } // Called after the Preferences dialog is closed and the program settings are saved. diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 31ae5d1c72..b44c73f91f 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -96,6 +96,8 @@ public: void load_config(const DynamicPrintConfig& config); void select_tab(size_t tab) const; void select_view(const std::string& direction); + // Propagate changed configuration from the Tab to the Platter and save changes to the AppConfig + void on_config_changed(DynamicPrintConfig* cfg) const ; PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; } diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index d6b8b438e9..176d19fb46 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -7,19 +7,25 @@ #include #include #include +#include + +#include #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "GUI.hpp" #include "I18N.hpp" #include "ConfigWizard.hpp" +#include "wxExtensions.hpp" +#include "GUI_App.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(parent, title, headline, wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG), button_id) + MsgDialog(parent, title, headline, create_scaled_bitmap("Slic3r_192px.png"), button_id) {} MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id) : @@ -35,7 +41,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he auto *headtext = new wxStaticText(this, wxID_ANY, headline); headtext->SetFont(boldfont); - headtext->Wrap(CONTENT_WIDTH); + headtext->Wrap(CONTENT_WIDTH*wxGetApp().em_unit()); rightsizer->Add(headtext); rightsizer->AddSpacer(VERT_SPACING); @@ -47,7 +53,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he btn_sizer->Add(button); } - rightsizer->Add(btn_sizer, 0, wxALIGN_CENTRE_HORIZONTAL); + rightsizer->Add(btn_sizer, 0, wxALIGN_RIGHT); auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(bitmap)); @@ -64,38 +70,36 @@ MsgDialog::~MsgDialog() {} 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), +// wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG), + create_scaled_bitmap("Slic3r_192px_grayscale.png"), wxID_NONE) , msg(msg) { - 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, 0)); - panel->SetScrollRate(0, 5); - - content_sizer->Add(panel, 1, wxEXPAND); - - auto *btn_copy = new wxButton(this, wxID_ANY, _(L("Copy to clipboard"))); - btn_copy->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { - if (wxTheClipboard->Open()) { - wxTheClipboard->SetData(new wxTextDataObject(this->msg)); // Note: the clipboard takes ownership of the pointer - wxTheClipboard->Close(); - } - }); + // Text shown as HTML, so that mouse selection and Ctrl-V to copy will work. + wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO); + { + html->SetMinSize(wxSize(40 * wxGetApp().em_unit(), -1)); + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + wxColour text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK); // wxSYS_COLOUR_WINDOW + auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); + auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); + const int font_size = font.GetPointSize()-1; + int size[] = {font_size, font_size, font_size, font_size, font_size, font_size, font_size}; + html->SetFonts(font.GetFaceName(), font.GetFaceName(), size); + html->SetBorders(2); + std::string msg_escaped = xml_escape(msg.ToUTF8().data()); + boost::replace_all(msg_escaped, "\r\n", "
"); + boost::replace_all(msg_escaped, "\n", "
"); + html->SetPage("" + wxString::FromUTF8(msg_escaped.data()) + ""); + content_sizer->Add(html, 1, wxEXPAND); + } auto *btn_ok = new wxButton(this, wxID_OK); btn_ok->SetFocus(); + btn_sizer->Add(btn_ok, 0, wxRIGHT, HORIZ_SPACING); - btn_sizer->Add(btn_copy, 0, wxRIGHT, HORIZ_SPACING); - btn_sizer->Add(btn_ok); - - SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT)); + SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT*wxGetApp().em_unit())); Fit(); } diff --git a/src/slic3r/GUI/MsgDialog.hpp b/src/slic3r/GUI/MsgDialog.hpp index 6064d2a9f5..09bb89e4f7 100644 --- a/src/slic3r/GUI/MsgDialog.hpp +++ b/src/slic3r/GUI/MsgDialog.hpp @@ -32,8 +32,8 @@ struct MsgDialog : wxDialog protected: enum { - CONTENT_WIDTH = 500, - CONTENT_MAX_HEIGHT = 600, + CONTENT_WIDTH = 50, + CONTENT_MAX_HEIGHT = 60, BORDER = 30, VERT_SPACING = 15, HORIZ_SPACING = 5, diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 78bbe4fecf..0e9fcd75e4 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -113,7 +113,6 @@ void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& fiel } void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = nullptr*/) { -//! if (line.sizer != nullptr || (line.widget != nullptr && line.full_width > 0)) { if ( (line.sizer != nullptr || line.widget != nullptr) && line.full_width) { if (line.sizer != nullptr) { sizer->Add(line.sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 15); @@ -135,6 +134,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // if we have a single option with no label, no sidetext just add it directly to sizer if (option_set.size() == 1 && label_width == 0 && option_set.front().opt.full_width && + option_set.front().opt.label.empty() && option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) { wxSizer* tmp_sizer; @@ -179,12 +179,12 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // Text is properly aligned only when Ellipsize is checked. label_style |= staticbox ? 0 : wxST_ELLIPSIZE_END; #endif /* __WXGTK__ */ - label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ":"), + label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ": "), wxDefaultPosition, wxSize(label_width, -1), label_style); label->SetFont(label_font); label->Wrap(label_width); // avoid a Linux/GTK bug if (!line.near_label_widget) - grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5); + grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, line.label.IsEmpty() ? 0 : 5); else { // If we're here, we have some widget near the label // so we need a horizontal sizer to arrange these things @@ -213,6 +213,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1); // If we have a single option with no sidetext just add it directly to the grid sizer if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 && + option_set.front().opt.label.empty() && option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) { const auto& option = option_set.front(); const auto& field = build_field(option, label); @@ -236,7 +237,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n wxString str_label = (option.label == "Top" || option.label == "Bottom") ? _CTX(option.label, "Layers") : _(option.label); - label = new wxStaticText(parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize); + label = new wxStaticText(parent(), wxID_ANY, str_label + ": ", wxDefaultPosition, wxDefaultSize); label->SetFont(label_font); sizer_tmp->Add(label, 0, /*wxALIGN_RIGHT |*/ wxALIGN_CENTER_VERTICAL, 0); } @@ -245,6 +246,16 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n const Option& opt_ref = opt; auto& field = build_field(opt_ref, label); add_undo_buttuns_to_sizer(sizer_tmp, field); + if (option_set.size() == 1 && option_set.front().opt.full_width) + { + const auto v_sizer = new wxBoxSizer(wxVERTICAL); + sizer_tmp->Add(v_sizer, 1, wxEXPAND); + is_sizer_field(field) ? + v_sizer->Add(field->getSizer(), 0, wxEXPAND) : + v_sizer->Add(field->getWindow(), 0, wxEXPAND); + return; + } + is_sizer_field(field) ? sizer_tmp->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) : sizer_tmp->Add(field->getWindow(), 0, wxALIGN_CENTER_VERTICAL, 0); @@ -269,7 +280,17 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n } } // add extra sizers if any - for (auto extra_widget : line.get_extra_widgets()) { + for (auto extra_widget : line.get_extra_widgets()) + { + if (line.get_extra_widgets().size() == 1 && !staticbox) + { + // extra widget for non-staticbox option group (like for the frequently used parameters on the sidebar) should be wxALIGN_RIGHT + const auto v_sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(v_sizer, 1, wxEXPAND); + v_sizer->Add(extra_widget(parent()), 0, wxALIGN_RIGHT); + return; + } + sizer->Add(extra_widget(parent())/*!.target()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); //! requires verification } } @@ -538,8 +559,9 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config ret = config.opt_int(opt_key, idx); break; case coEnum:{ - if (opt_key.compare("external_fill_pattern") == 0 || - opt_key.compare("fill_pattern") == 0 ) { + if (opt_key == "top_fill_pattern" || + opt_key == "bottom_fill_pattern" || + opt_key == "fill_pattern" ) { ret = static_cast(config.option>(opt_key)->value); } else if (opt_key.compare("gcode_flavor") == 0 ) { @@ -593,7 +615,7 @@ Field* ConfigOptionsGroup::get_fieldc(const t_config_option_key& opt_key, int op void ogStaticText::SetText(const wxString& value, bool wrap/* = true*/) { SetLabel(value); - if (wrap) Wrap(400); + if (wrap) Wrap(40 * wxGetApp().em_unit()); GetParent()->Layout(); } diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 93151f33e8..d9ff6a01a7 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -82,7 +82,7 @@ class OptionsGroup { public: const bool staticbox {true}; const wxString title {wxString("")}; - size_t label_width {200}; + size_t label_width = 20 * wxGetApp().em_unit();// {200}; wxSizer* sizer {nullptr}; column_t extra_column {nullptr}; t_change m_on_change { nullptr }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f5471cdff9..e917269d25 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -49,6 +49,7 @@ #include "GLCanvas3D.hpp" #include "GLToolbar.hpp" #include "GUI_Preview.hpp" +#include "3DBed.hpp" #include "Tab.hpp" #include "PresetBundle.hpp" #include "BackgroundSlicingProcess.hpp" @@ -98,6 +99,11 @@ public: wxStaticText *info_facets; wxStaticText *info_materials; wxStaticText *info_manifold; + + wxStaticText *label_volume; + wxStaticText *label_materials; + std::vector sla_hidden_items; + bool showing_manifold_warning_icon; void show_sizer(bool show); }; @@ -119,15 +125,16 @@ ObjectInfo::ObjectInfo(wxWindow *parent) : (*info_label)->SetFont(wxGetApp().small_font()); grid_sizer->Add(text, 0); grid_sizer->Add(*info_label, 0); + return text; }; init_info_label(&info_size, _(L("Size"))); - init_info_label(&info_volume, _(L("Volume"))); + label_volume = init_info_label(&info_volume, _(L("Volume"))); init_info_label(&info_facets, _(L("Facets"))); - init_info_label(&info_materials, _(L("Materials"))); + label_materials = init_info_label(&info_materials, _(L("Materials"))); Add(grid_sizer, 0, wxEXPAND); - auto *info_manifold_text = new wxStaticText(parent, wxID_ANY, _(L("Manifold"))); + auto *info_manifold_text = new wxStaticText(parent, wxID_ANY, _(L("Manifold")) + ":"); info_manifold_text->SetFont(wxGetApp().small_font()); info_manifold = new wxStaticText(parent, wxID_ANY, ""); info_manifold->SetFont(wxGetApp().small_font()); @@ -138,6 +145,8 @@ ObjectInfo::ObjectInfo(wxWindow *parent) : sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2); sizer_manifold->Add(info_manifold, 0, wxLEFT, 2); Add(sizer_manifold, 0, wxEXPAND | wxTOP, 4); + + sla_hidden_items = { label_volume, info_volume, label_materials, info_materials }; } void ObjectInfo::show_sizer(bool show) @@ -152,6 +161,7 @@ enum SlisedInfoIdx siFilament_m, siFilament_mm3, siFilament_g, + siMateril_unit, siCost, siEstimatedTime, siWTNumbetOfToolchanges, @@ -192,6 +202,7 @@ SlicedInfo::SlicedInfo(wxWindow *parent) : init_info_label(_(L("Used Filament (m)"))); init_info_label(_(L("Used Filament (mm³)"))); init_info_label(_(L("Used Filament (g)"))); + init_info_label(_(L("Used Material (unit)"))); init_info_label(_(L("Cost"))); init_info_label(_(L("Estimated printing time"))); init_info_label(_(L("Number of tool changes"))); @@ -212,7 +223,7 @@ void SlicedInfo::SetTextAndShow(SlisedInfoIdx idx, const wxString& text, const w } PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) : - wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200,-1), 0, nullptr, wxCB_READONLY), +wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * wxGetApp().em_unit(), -1), 0, nullptr, wxCB_READONLY), preset_type(preset_type), last_selected(wxNOT_FOUND) { @@ -305,7 +316,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : // Frequently changed parameters for FFF_technology m_og->set_config(config); - m_og->label_width = label_width; + m_og->label_width = label_width == 0 ? 1 : label_width; m_og->m_on_change = [config, this](t_config_option_key opt_key, boost::any value) { Tab* tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT); @@ -350,29 +361,35 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : tab_print->update_dirty(); }; - Option option = m_og->get_option("fill_density"); - option.opt.sidetext = ""; - option.opt.full_width = true; - m_og->append_single_option_line(option); + + Line line = Line { "", "" }; ConfigOptionDef def; - - def.label = L("Support"); + def.label = L("Supports"); def.type = coStrings; def.gui_type = "select_open"; def.tooltip = L("Select what kind of support do you need"); def.enum_labels.push_back(L("None")); def.enum_labels.push_back(L("Support on build plate only")); def.enum_labels.push_back(L("Everywhere")); - std::string selection = !config->opt_bool("support_material") ? - "None" : - config->opt_bool("support_material_buildplate_only") ? - "Support on build plate only" : - "Everywhere"; + const std::string selection = !config->opt_bool("support_material") ? + "None" : config->opt_bool("support_material_buildplate_only") ? + "Support on build plate only" : + "Everywhere"; def.default_value = new ConfigOptionStrings{ selection }; - option = Option(def, "support"); + Option option = Option(def, "support"); option.opt.full_width = true; - m_og->append_single_option_line(option); + line.append_option(option); + m_og->append_line(line); + + + line = Line { "", "" }; + + option = m_og->get_option("fill_density"); + option.opt.label = L("Infill"); + option.opt.width = 7 * wxGetApp().em_unit(); + option.opt.sidetext = " "; + line.append_option(option); m_brim_width = config->opt_float("brim_width"); def.label = L("Brim"); @@ -381,11 +398,10 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : def.gui_type = ""; def.default_value = new ConfigOptionBool{ m_brim_width > 0.0 ? true : false }; option = Option(def, "brim"); - m_og->append_single_option_line(option); + option.opt.sidetext = " "; + line.append_option(option); - - Line line = { "", "" }; - line.widget = [config, this](wxWindow* parent) { + auto wiping_dialog_btn = [config, this](wxWindow* parent) { m_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_wiping_dialog_button); @@ -402,19 +418,20 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : std::vector extruders = dlg.get_extruders(); (config.option("wiping_volumes_matrix"))->values = std::vector(matrix.begin(), matrix.end()); (config.option("wiping_volumes_extruders"))->values = std::vector(extruders.begin(), extruders.end()); - wxPostEvent(parent, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, parent)); + wxPostEvent(parent, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, parent)); } })); return sizer; }; - m_og->append_line(line); + line.append_widget(wiping_dialog_btn); + m_og->append_line(line); // Frequently changed parameters for SLA_technology m_og_sla = std::make_shared(parent, ""); DynamicPrintConfig* config_sla = &wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; m_og_sla->set_config(config_sla); - m_og_sla->label_width = label_width*2; + m_og_sla->label_width = label_width == 0 ? 1 : label_width; m_og_sla->m_on_change = [config_sla, this](t_config_option_key opt_key, boost::any value) { Tab* tab = wxGetApp().get_tab(Preset::TYPE_SLA_PRINT); @@ -428,12 +445,22 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : tab->update_dirty(); }; - m_og_sla->append_single_option_line("supports_enable"); - m_og_sla->append_single_option_line("pad_enable"); + + line = Line{ "", "" }; + + option = m_og_sla->get_option("supports_enable"); + option.opt.sidetext = " "; + line.append_option(option); + + option = m_og_sla->get_option("pad_enable"); + option.opt.sidetext = " "; + line.append_option(option); + + m_og_sla->append_line(line); m_sizer = new wxBoxSizer(wxVERTICAL); m_sizer->Add(m_og->sizer, 0, wxEXPAND); - m_sizer->Add(m_og_sla->sizer, 0, wxEXPAND | wxTOP, 5); + m_sizer->Add(m_og_sla->sizer, 0, wxEXPAND); } @@ -494,7 +521,7 @@ struct Sidebar::priv void Sidebar::priv::show_preset_comboboxes() { - const bool showSLA = plater->printer_technology() == ptSLA; + const bool showSLA = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA; wxWindowUpdateLocker noUpdates_scrolled(scrolled->GetParent()); @@ -518,7 +545,7 @@ void Sidebar::priv::show_preset_comboboxes() Sidebar::Sidebar(Plater *parent) : wxPanel(parent), p(new priv(parent)) { - p->scrolled = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(400, -1)); + p->scrolled = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(40 * wxGetApp().em_unit(), -1)); p->scrolled->SetScrollbars(0, 20, 1, 2); // Sizer in the scrolled area @@ -526,26 +553,26 @@ Sidebar::Sidebar(Plater *parent) p->scrolled->SetSizer(scrolled_sizer); // Sizer with buttons for mode changing - p->mode_sizer = new PrusaModeSizer(p->scrolled); + p->mode_sizer = new PrusaModeSizer(p->scrolled, 2 * wxGetApp().em_unit()); // The preset chooser - p->sizer_presets = new wxFlexGridSizer(5, 2, 1, 2); - p->sizer_presets->AddGrowableCol(1, 1); + p->sizer_presets = new wxFlexGridSizer(10, 1, 1, 2); + p->sizer_presets->AddGrowableCol(0, 1); p->sizer_presets->SetFlexibleDirection(wxBOTH); p->sizer_filaments = new wxBoxSizer(wxVERTICAL); auto init_combo = [this](PresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) { - auto *text = new wxStaticText(p->scrolled, wxID_ANY, label); + auto *text = new wxStaticText(p->scrolled, wxID_ANY, label+" :"); text->SetFont(wxGetApp().small_font()); *combo = new PresetComboBox(p->scrolled, preset_type); auto *sizer_presets = this->p->sizer_presets; auto *sizer_filaments = this->p->sizer_filaments; - sizer_presets->Add(text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); + sizer_presets->Add(text, 0, wxALIGN_LEFT | wxEXPAND | wxRIGHT, 4); if (! filament) { - sizer_presets->Add(*combo, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 1); + sizer_presets->Add(*combo, 0, wxEXPAND | wxBOTTOM, 1); } else { - sizer_filaments->Add(*combo, 1, wxEXPAND | wxBOTTOM, 1); + sizer_filaments->Add(*combo, 0, wxEXPAND | wxBOTTOM, 1); (*combo)->set_extruder_idx(0); sizer_presets->Add(sizer_filaments, 1, wxEXPAND); } @@ -559,29 +586,32 @@ Sidebar::Sidebar(Plater *parent) init_combo(&p->combo_printer, _(L("Printer")), Preset::TYPE_PRINTER, false); // calculate width of the preset labels - p->sizer_presets->Layout(); - const wxArrayInt& ar = p->sizer_presets->GetColWidths(); - int label_width = ar.IsEmpty() ? 100 : ar.front()-4; +// p->sizer_presets->Layout(); +// const wxArrayInt& ar = p->sizer_presets->GetColWidths(); +// const int label_width = ar.IsEmpty() ? 10*wxGetApp().em_unit() : ar.front()-4; + + const int margin_5 = int(0.5*wxGetApp().em_unit());// 5; + const int margin_10 = int(1.5*wxGetApp().em_unit());// 15; p->sizer_params = new wxBoxSizer(wxVERTICAL); // Frequently changed parameters - p->frequently_changed_parameters = new FreqChangedParams(p->scrolled, label_width); - p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxBOTTOM | wxLEFT, 2); + p->frequently_changed_parameters = new FreqChangedParams(p->scrolled, 0/*label_width*/); + p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxTOP | wxBOTTOM, margin_10); // Object List p->object_list = new ObjectList(p->scrolled); - p->sizer_params->Add(p->object_list->get_sizer(), 1, wxEXPAND | wxTOP, 20); + p->sizer_params->Add(p->object_list->get_sizer(), 1, wxEXPAND); // Object Manipulations p->object_manipulation = new ObjectManipulation(p->scrolled); p->object_manipulation->Hide(); - p->sizer_params->Add(p->object_manipulation->get_sizer(), 0, wxEXPAND | wxLEFT | wxTOP, 20); + p->sizer_params->Add(p->object_manipulation->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); // Frequently Object Settings p->object_settings = new ObjectSettings(p->scrolled); p->object_settings->Hide(); - p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxLEFT | wxTOP, 20); + p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); wxBitmap arrow_up(GUI::from_u8(Slic3r::var("brick_go.png")), wxBITMAP_TYPE_PNG); p->btn_send_gcode = new wxButton(this, wxID_ANY, _(L("Send to printer"))); @@ -594,11 +624,11 @@ Sidebar::Sidebar(Plater *parent) p->sliced_info = new SlicedInfo(p->scrolled); // Sizer in the scrolled area - scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_RIGHT/*CENTER_HORIZONTAL*/ | wxBOTTOM | wxRIGHT, 5); - scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, 2); - scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND); - scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, 20); - scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND | wxTOP | wxLEFT, 20); + scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL/*RIGHT | wxBOTTOM | wxRIGHT, 5*/); + scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5); + scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND | wxLEFT, margin_5); + scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5); + scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5); // Buttons underneath the scrolled area p->btn_export_gcode = new wxButton(this, wxID_ANY, _(L("Export G-code")) + dots); @@ -608,13 +638,13 @@ Sidebar::Sidebar(Plater *parent) enable_buttons(false); auto *btns_sizer = new wxBoxSizer(wxVERTICAL); - btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, 5); - btns_sizer->Add(p->btn_send_gcode, 0, wxEXPAND | wxTOP, 5); - btns_sizer->Add(p->btn_export_gcode, 0, wxEXPAND | wxTOP, 5); + btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, margin_5); + btns_sizer->Add(p->btn_send_gcode, 0, wxEXPAND | wxTOP, margin_5); + btns_sizer->Add(p->btn_export_gcode, 0, wxEXPAND | wxTOP, margin_5); auto *sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(p->scrolled, 1, wxEXPAND | wxTOP, 5); - sizer->Add(btns_sizer, 0, wxEXPAND | wxLEFT, 20); + sizer->Add(p->scrolled, 1, wxEXPAND); + sizer->Add(btns_sizer, 0, wxEXPAND | wxLEFT, margin_5); SetSizer(sizer); // Events @@ -652,11 +682,12 @@ void Sidebar::remove_unused_filament_combos(const int current_extruder_count) void Sidebar::update_presets(Preset::Type preset_type) { PresetBundle &preset_bundle = *wxGetApp().preset_bundle; + const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology(); switch (preset_type) { case Preset::TYPE_FILAMENT: { - const int extruder_cnt = p->plater->printer_technology() != ptFFF ? 1 : + const int extruder_cnt = print_tech != ptFFF ? 1 : dynamic_cast(preset_bundle.printers.get_edited_preset().config.option("nozzle_diameter"))->values.size(); const int filament_cnt = p->combos_filament.size() > extruder_cnt ? extruder_cnt : p->combos_filament.size(); @@ -688,7 +719,7 @@ void Sidebar::update_presets(Preset::Type preset_type) case Preset::TYPE_PRINTER: { // Update the print choosers to only contain the compatible presets, update the dirty flags. - if (p->plater->printer_technology() == ptFFF) + if (print_tech == ptFFF) preset_bundle.prints.update_platter_ui(p->combo_print); else { preset_bundle.sla_prints.update_platter_ui(p->combo_sla_print); @@ -701,7 +732,7 @@ void Sidebar::update_presets(Preset::Type preset_type) p->combo_printer->check_selection(); // Update the filament choosers to only contain the compatible presets, update the color preview, // update the dirty flags. - if (p->plater->printer_technology() == ptFFF) { + if (print_tech == ptFFF) { for (size_t i = 0; i < p->combos_filament.size(); ++ i) preset_bundle.update_platter_filament_ui(i, p->combos_filament[i]); } @@ -808,6 +839,11 @@ void Sidebar::show_info_sizer() } p->object_info->show_sizer(true); + + if (p->plater->printer_technology() == ptSLA) { + for (auto item: p->object_info->sla_hidden_items) + item->Show(false); + } } void Sidebar::show_sliced_info_sizer(const bool show) @@ -816,53 +852,83 @@ void Sidebar::show_sliced_info_sizer(const bool show) p->sliced_info->Show(show); if (show) { - const PrintStatistics& ps = p->plater->fff_print().print_statistics(); - const bool is_wipe_tower = ps.total_wipe_tower_filament > 0; + if (p->plater->printer_technology() == ptSLA) + { + const SLAPrintStatistics& ps = p->plater->sla_print().print_statistics(); + wxString new_label = _(L("Used Material (ml)")) + " :"; + const bool is_supports = ps.support_used_material > 0.0; + if (is_supports) + new_label += wxString::Format("\n - %s\n - %s", _(L("object(s)")), _(L("supports and pad"))); - wxString new_label = _(L("Used Filament (m)")); - if (is_wipe_tower) - new_label += wxString::Format(" :\n - %s\n - %s", _(L("objects")), _(L("wipe tower"))); + wxString info_text = is_supports ? + wxString::Format("%.2f \n%.2f \n%.2f", (ps.objects_used_material + ps.support_used_material) / 1000, + ps.objects_used_material / 1000, + ps.support_used_material / 1000) : + wxString::Format("%.2f", (ps.objects_used_material + ps.support_used_material) / 1000); + p->sliced_info->SetTextAndShow(siMateril_unit, info_text, new_label); - wxString info_text = is_wipe_tower ? - wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / 1000, - (ps.total_used_filament - ps.total_wipe_tower_filament) / 1000, - ps.total_wipe_tower_filament / 1000) : - wxString::Format("%.2f", ps.total_used_filament / 1000); - p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label); + p->sliced_info->SetTextAndShow(siCost, "N/A"/*wxString::Format("%.2f", ps.total_cost)*/); + p->sliced_info->SetTextAndShow(siEstimatedTime, ps.estimated_print_time, _(L("Estimated printing time")) + " :"); - p->sliced_info->SetTextAndShow(siFilament_mm3, wxString::Format("%.2f", ps.total_extruded_volume)); - p->sliced_info->SetTextAndShow(siFilament_g, wxString::Format("%.2f", ps.total_weight)); - - - new_label = _(L("Cost")); - if (is_wipe_tower) - new_label += wxString::Format(" :\n - %s\n - %s", _(L("objects")), _(L("wipe tower"))); - - info_text = is_wipe_tower ? - wxString::Format("%.2f \n%.2f \n%.2f", ps.total_cost, - (ps.total_cost - ps.total_wipe_tower_cost), - ps.total_wipe_tower_cost) : - wxString::Format("%.2f", ps.total_cost); - p->sliced_info->SetTextAndShow(siCost, info_text, new_label); - - if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") - p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); - else { - new_label = _(L("Estimated printing time")) +" :"; - info_text = ""; - if (ps.estimated_normal_print_time != "N/A") { - new_label += wxString::Format("\n - %s", _(L("normal mode"))); - info_text += wxString::Format("\n%s", ps.estimated_normal_print_time); - } - if (ps.estimated_silent_print_time != "N/A") { - new_label += wxString::Format("\n - %s", _(L("silent mode"))); - info_text += wxString::Format("\n%s", ps.estimated_silent_print_time); - } - p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); + // Hide non-SLA sliced info parameters + p->sliced_info->SetTextAndShow(siFilament_m, "N/A"); + p->sliced_info->SetTextAndShow(siFilament_mm3, "N/A"); + p->sliced_info->SetTextAndShow(siFilament_g, "N/A"); + p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, "N/A"); } + else + { + const PrintStatistics& ps = p->plater->fff_print().print_statistics(); + const bool is_wipe_tower = ps.total_wipe_tower_filament > 0; - // if there is a wipe tower, insert number of toolchanges info into the array: - p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, is_wipe_tower ? wxString::Format("%.d", p->plater->fff_print().wipe_tower_data().number_of_toolchanges) : "N/A"); + wxString new_label = _(L("Used Filament (m)")); + if (is_wipe_tower) + new_label += wxString::Format(" :\n - %s\n - %s", _(L("objects")), _(L("wipe tower"))); + + wxString info_text = is_wipe_tower ? + wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / 1000, + (ps.total_used_filament - ps.total_wipe_tower_filament) / 1000, + ps.total_wipe_tower_filament / 1000) : + wxString::Format("%.2f", ps.total_used_filament / 1000); + p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label); + + p->sliced_info->SetTextAndShow(siFilament_mm3, wxString::Format("%.2f", ps.total_extruded_volume)); + p->sliced_info->SetTextAndShow(siFilament_g, wxString::Format("%.2f", ps.total_weight)); + + + new_label = _(L("Cost")); + if (is_wipe_tower) + new_label += wxString::Format(" :\n - %s\n - %s", _(L("objects")), _(L("wipe tower"))); + + info_text = is_wipe_tower ? + wxString::Format("%.2f \n%.2f \n%.2f", ps.total_cost, + (ps.total_cost - ps.total_wipe_tower_cost), + ps.total_wipe_tower_cost) : + wxString::Format("%.2f", ps.total_cost); + p->sliced_info->SetTextAndShow(siCost, info_text, new_label); + + if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") + p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); + else { + new_label = _(L("Estimated printing time")) +" :"; + info_text = ""; + if (ps.estimated_normal_print_time != "N/A") { + new_label += wxString::Format("\n - %s", _(L("normal mode"))); + info_text += wxString::Format("\n%s", ps.estimated_normal_print_time); + } + if (ps.estimated_silent_print_time != "N/A") { + new_label += wxString::Format("\n - %s", _(L("silent mode"))); + info_text += wxString::Format("\n%s", ps.estimated_silent_print_time); + } + p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); + } + + // if there is a wipe tower, insert number of toolchanges info into the array: + p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, is_wipe_tower ? wxString::Format("%.d", p->plater->fff_print().wipe_tower_data().number_of_toolchanges) : "N/A"); + + // Hide non-FFF sliced info parameters + p->sliced_info->SetTextAndShow(siMateril_unit, "N/A"); + } } Layout(); @@ -952,6 +1018,7 @@ struct Plater::priv wxPanel* current_panel; std::vector panels; Sidebar *sidebar; + Bed3D bed; View3D* view3D; GLToolbar view_toolbar; Preview *preview; @@ -962,6 +1029,7 @@ struct Plater::priv std::atomic arranging; std::atomic rotoptimizing; bool delayed_scene_refresh; + std::string delayed_error_message; wxTimer background_process_timer; @@ -1055,6 +1123,12 @@ struct Plater::priv void update_object_menu(); + // Set the bed shape to a single closed 2D polygon(array of two element arrays), + // triangulate the bed and store the triangles into m_bed.m_triangles, + // fills the m_bed.m_grid_lines and sets m_bed.m_origin. + // Sets m_bed.m_polygon to limit the object placement. + void set_bed_shape(const Pointfs& shape); + private: bool init_object_menu(); bool init_common_menu(wxMenu* menu, const bool is_part = false); @@ -1126,9 +1200,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D = new View3D(q, &model, config, &background_process); preview = new Preview(q, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); }); - // Let the Tab key switch between the 3D view and the layer preview. - view3D->Bind(wxEVT_NAVIGATION_KEY, [this](wxNavigationKeyEvent &evt) { if (evt.IsFromTab()) this->select_next_view_3D(); }); - preview->Bind(wxEVT_NAVIGATION_KEY, [this](wxNavigationKeyEvent &evt) { if (evt.IsFromTab()) this->select_next_view_3D(); }); + + view3D->set_bed(&bed); + preview->set_bed(&bed); panels.push_back(view3D); panels.push_back(preview); @@ -1136,12 +1210,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->background_process_timer.SetOwner(this->q, 0); this->q->Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->update_restart_background_process(false, false); }); -#if !ENABLE_REWORKED_BED_SHAPE_CHANGE - auto *bed_shape = config->opt("bed_shape"); - view3D->set_bed_shape(bed_shape->values); - preview->set_bed_shape(bed_shape->values); -#endif // !ENABLE_REWORKED_BED_SHAPE_CHANGE - update(); auto *hsizer = new wxBoxSizer(wxHORIZONTAL); @@ -1181,6 +1249,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event &evt) { this->sidebar->enable_buttons(evt.data); }); view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this); view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this); + view3D_canvas->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); // 3DScene/Toolbar: view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); }); @@ -1192,10 +1261,13 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this); view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this); view3D_canvas->Bind(EVT_GLCANVAS_INIT, [this](SimpleEvent&) { init_view_toolbar(); }); + view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option("bed_shape")->values); }); // Preview events: preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option("bed_shape")->values); }); + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); view3D_canvas->Bind(EVT_GLCANVAS_INIT, [this](SimpleEvent&) { init_view_toolbar(); }); @@ -1920,6 +1992,7 @@ void Plater::priv::split_volume() void Plater::priv::schedule_background_process() { + delayed_error_message.clear(); // Trigger the timer event after 0.5s this->background_process_timer.Start(500, wxTIMER_ONE_SHOT); // Notify the Canvas3D that something has changed, so it may invalidate some of the layer editing stuff. @@ -1947,6 +2020,8 @@ unsigned int Plater::priv::update_background_process(bool force_validation) this->background_process_timer.Stop(); // Update the "out of print bed" state of ModelInstances. this->update_print_volume_state(); + // The delayed error message is no more valid. + this->delayed_error_message.clear(); // Apply new config to the possibly running background task. bool was_running = this->background_process.running(); Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), wxGetApp().preset_bundle->full_config()); @@ -1985,8 +2060,18 @@ unsigned int Plater::priv::update_background_process(bool force_validation) return_state |= UPDATE_BACKGROUND_PROCESS_RESTART; } else { // The print is not valid. - // The error returned from the Print needs to be translated into the local language. - GUI::show_error(this->q, _(err)); + // Only show the error message immediately, if the top level parent of this window is active. + auto p = dynamic_cast(this->q); + while (p->GetParent()) + p = p->GetParent(); + auto *top_level_wnd = dynamic_cast(p); + if (top_level_wnd && top_level_wnd->IsActive()) { + // The error returned from the Print needs to be translated into the local language. + GUI::show_error(this->q, _(err)); + } else { + // Show the error message once the main window gets activated. + this->delayed_error_message = _(err); + } return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; } } @@ -2000,6 +2085,14 @@ unsigned int Plater::priv::update_background_process(bool force_validation) wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); } + //FIXME update "Slice Now / Schedule background process" + //background_process.is_export_scheduled() - byl zavolan "Export G-code", background processing ma jmeno export souboru + //background_process.is_upload_scheduled() - byl zavolan "Send to OctoPrint", jeste nebylo doslajsovano (pak se preda upload fronte a background process zapomene) + //background_process.empty() - prazdna plocha + // pokud (return_state & UPDATE_BACKGROUND_PROCESS_INVALID) != 0 -> doslo k chybe (gray out "Slice now") mozna "Invalid data"??? + // jinak background_process.running() -> Zobraz "Slicing ..." + // jinak pokud ! background_process.empty() && ! background_process.finished() -> je neco ke slajsovani (povol tlacitko) "Slice Now" + return return_state; } @@ -2049,6 +2142,8 @@ void Plater::priv::export_gcode(fs::path output_path, PrintHostJob upload_job) background_process.schedule_upload(std::move(upload_job)); } + // If the SLA processing of just a single object's supports is running, restart slicing for the whole object. + this->background_process.set_task(PrintBase::TaskParams()); this->restart_background_process(priv::UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT); } @@ -2231,6 +2326,10 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) break; } } + if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS) { + // Update SLA gizmo (reload_scene calls update_gizmos_data) + q->canvas3D()->reload_scene(true); + } } void Plater::priv::on_slicing_completed(wxCommandEvent &) @@ -2352,8 +2451,15 @@ void Plater::priv::on_right_click(Vec2dEvent& evt) sidebar->obj_list()->append_menu_item_settings(menu); - if (q != nullptr) + if (q != nullptr) { +#ifdef __linux__ + // For some reason on Linux the menu isn't displayed if position is specified + // (even though the position is sane). + q->PopupMenu(menu); +#else q->PopupMenu(menu, (int)evt.data.x(), (int)evt.data.y()); +#endif + } } void Plater::priv::on_wipetower_moved(Vec3dEvent &evt) @@ -2630,6 +2736,16 @@ bool Plater::priv::can_mirror() const return get_selection().is_from_single_instance(); } +void Plater::priv::set_bed_shape(const Pointfs& shape) +{ + bool new_shape = bed.set_shape(shape); + if (new_shape) + { + if (view3D) view3D->bed_shape_changed(); + if (preview) preview->bed_shape_changed(); + } +} + void Plater::priv::update_object_menu() { sidebar->obj_list()->append_menu_items_add_volume(&object_menu); @@ -2884,6 +3000,8 @@ void Plater::export_stl(bool selection_only) const wxString path = dialog->GetPath(); const std::string path_u8 = into_u8(path); + wxBusyCursor wait; + TriangleMesh mesh; if (selection_only) { const auto &selection = p->get_selection(); @@ -2959,10 +3077,38 @@ void Plater::reslice() unsigned int state = this->p->update_background_process(true); if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) this->p->view3D->reload_scene(false); + // If the SLA processing of just a single object's supports is running, restart slicing for the whole object. + this->p->background_process.set_task(PrintBase::TaskParams()); // Only restarts if the state is valid. this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART); } +void Plater::reslice_SLA_supports(const ModelObject &object) +{ + //FIXME Don't reslice if export of G-code or sending to OctoPrint is running. + // bitmask of UpdateBackgroundProcessReturnState + unsigned int state = this->p->update_background_process(true); + if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) + this->p->view3D->reload_scene(false); + + if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)) + // Nothing to do on empty input or invalid configuration. + return; + + // Limit calculation to the single object only. + PrintBase::TaskParams task; + task.single_model_object = object.id(); + // If the background processing is not enabled, calculate supports just for the single instance. + // Otherwise calculate everything, but start with the provided object. + if (!this->p->background_processing_enabled()) { + task.single_model_instance_only = true; + task.to_object_step = slaposBasePool; + } + this->p->background_process.set_task(task); + // and let the background processing start. + this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART); +} + void Plater::send_gcode() { if (p->model.objects.empty()) { return; } @@ -3023,20 +3169,13 @@ void Plater::on_extruders_change(int num_extruders) void Plater::on_config_change(const DynamicPrintConfig &config) { bool update_scheduled = false; -#if ENABLE_REWORKED_BED_SHAPE_CHANGE bool bed_shape_changed = false; -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE for (auto opt_key : p->config->diff(config)) { p->config->set_key_value(opt_key, config.option(opt_key)->clone()); if (opt_key == "printer_technology") this->set_printer_technology(config.opt_enum(opt_key)); else if (opt_key == "bed_shape") { -#if ENABLE_REWORKED_BED_SHAPE_CHANGE bed_shape_changed = true; -#else - if (p->view3D) p->view3D->set_bed_shape(p->config->option(opt_key)->values); - if (p->preview) p->preview->set_bed_shape(p->config->option(opt_key)->values); -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE update_scheduled = true; } else if (boost::starts_with(opt_key, "wipe_tower") || @@ -3062,12 +3201,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) } else if (opt_key == "printer_model") { // update to force bed selection(for texturing) -#if ENABLE_REWORKED_BED_SHAPE_CHANGE bed_shape_changed = true; -#else - if (p->view3D) p->view3D->set_bed_shape(p->config->option("bed_shape")->values); - if (p->preview) p->preview->set_bed_shape(p->config->option("bed_shape")->values); -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE update_scheduled = true; } else if (opt_key == "host_type" && this->p->printer_technology == ptSLA) { @@ -3080,13 +3214,8 @@ void Plater::on_config_change(const DynamicPrintConfig &config) p->sidebar->show_send(prin_host_opt != nullptr && !prin_host_opt->value.empty()); } -#if ENABLE_REWORKED_BED_SHAPE_CHANGE if (bed_shape_changed) - { - if (p->view3D) p->view3D->set_bed_shape(p->config->option("bed_shape")->values); - if (p->preview) p->preview->set_bed_shape(p->config->option("bed_shape")->values); - } -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE + p->set_bed_shape(p->config->option("bed_shape")->values); if (update_scheduled) update(); @@ -3095,6 +3224,26 @@ void Plater::on_config_change(const DynamicPrintConfig &config) this->p->schedule_background_process(); } +void Plater::on_activate() +{ +#ifdef __linux__ + wxWindow *focus_window = wxWindow::FindFocus(); + // Activating the main frame, and no window has keyboard focus. + // Set the keyboard focus to the visible Canvas3D. + if (this->p->view3D->IsShown() && (!focus_window || focus_window == this->p->view3D->get_wxglcanvas())) + this->p->view3D->get_wxglcanvas()->SetFocus(); + + else if (this->p->preview->IsShown() && (!focus_window || focus_window == this->p->view3D->get_wxglcanvas())) + this->p->preview->get_wxglcanvas()->SetFocus(); +#endif + + if (! this->p->delayed_error_message.empty()) { + std::string msg = std::move(this->p->delayed_error_message); + this->p->delayed_error_message.clear(); + GUI::show_error(this, msg); + } +} + const wxString& Plater::get_project_filename() const { return p->project_filename; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index e3601b65c3..4d489c82a1 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -10,6 +10,9 @@ #include "Preset.hpp" +#include "3DScene.hpp" +#include "GLTexture.hpp" + class wxButton; class wxBoxSizer; class wxGLCanvas; @@ -19,6 +22,7 @@ class wxString; namespace Slic3r { class Model; +class ModelObject; class Print; class SLAPrint; @@ -145,12 +149,15 @@ public: void export_amf(); void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); void reslice(); + void reslice_SLA_supports(const ModelObject &object); void changed_object(int obj_idx); void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); void send_gcode(); void on_extruders_change(int extruders_count); void on_config_change(const DynamicPrintConfig &config); + // On activating the parent window. + void on_activate(); void update_object_menu(); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index b58ce5900e..5ce65b6691 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -15,7 +15,7 @@ void PreferencesDialog::build() { auto app_config = get_app_config(); m_optgroup = std::make_shared(this, _(L("General"))); - m_optgroup->label_width = 400; + m_optgroup->label_width = 40 * wxGetApp().em_unit(); //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"; }; diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 19626847f4..8b86b60314 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -356,7 +356,7 @@ const std::vector& Preset::print_options() static std::vector s_opts { "layer_height", "first_layer_height", "perimeters", "spiral_vase", "top_solid_layers", "bottom_solid_layers", "extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs", - "seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "external_fill_pattern", + "seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern", "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle", "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", "max_print_speed", "max_volumetric_speed", @@ -444,6 +444,7 @@ const std::vector& Preset::sla_print_options() if (s_opts.empty()) { s_opts = { "layer_height", + "faded_layers", "supports_enable", "support_head_front_diameter", "support_head_penetration", @@ -457,14 +458,14 @@ const std::vector& Preset::sla_print_options() "support_critical_angle", "support_max_bridge_length", "support_object_elevation", - "support_density_at_horizontal", - "support_density_at_45", - "support_minimal_z", + "support_points_density_relative", + "support_points_minimal_distance", "pad_enable", "pad_wall_thickness", "pad_wall_height", "pad_max_merge_distance", "pad_edge_radius", + "pad_wall_tilt", "output_filename_format", "default_sla_print_profile", "compatible_printers", @@ -501,6 +502,7 @@ const std::vector& Preset::sla_printer_options() "bed_shape", "max_print_height", "display_width", "display_height", "display_pixels_x", "display_pixels_y", "display_orientation", + "fast_tilt_time", "slow_tilt_time", "area_fill", "printer_correction", "print_host", "printhost_apikey", "printhost_cafile", "printer_notes", diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 5f33fd00ab..0ee7a5c6c2 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -1435,7 +1435,8 @@ bool PresetBundle::parse_color(const std::string &scolor, unsigned char *rgb_out void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui) { - if (ui == nullptr || this->printers.get_edited_preset().printer_technology() == ptSLA) + if (ui == nullptr || this->printers.get_edited_preset().printer_technology() == ptSLA || + this->filament_presets.size() <= idx_extruder ) return; unsigned char rgb[3]; diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index d6b5f3469e..0d70027c07 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -15,6 +15,7 @@ #include "GUI.hpp" #include "GUI_App.hpp" +#include "AppConfig.hpp" #include "MsgDialog.hpp" #include "I18N.hpp" #include "../Utils/PrintHost.hpp" @@ -24,10 +25,12 @@ namespace fs = boost::filesystem; namespace Slic3r { namespace GUI { +static const char *CONFIG_KEY_PATH = "printhost_path"; +static const char *CONFIG_KEY_PRINT = "printhost_print"; PrintHostSendDialog::PrintHostSendDialog(const fs::path &path) : MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE) - , txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())) + , txt_filename(new wxTextCtrl(this, wxID_ANY)) , box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload")))) { #ifdef __APPLE__ @@ -35,7 +38,7 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path) #endif auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed."))); - label_dir_hint->Wrap(CONTENT_WIDTH); + label_dir_hint->Wrap(CONTENT_WIDTH * wxGetApp().em_unit()); content_sizer->Add(txt_filename, 0, wxEXPAND); content_sizer->Add(label_dir_hint); @@ -44,11 +47,30 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path) btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL)); - txt_filename->SetFocus(); + const AppConfig *app_config = wxGetApp().app_config; + box_print->SetValue(app_config->get("recent", CONFIG_KEY_PRINT) == "1"); + + wxString recent_path = from_u8(app_config->get("recent", CONFIG_KEY_PATH)); + if (recent_path.Length() > 0 && recent_path[recent_path.Length() - 1] != '/') { + recent_path += '/'; + } + const auto recent_path_len = recent_path.Length(); + recent_path += path.filename().wstring(); wxString stem(path.stem().wstring()); - txt_filename->SetSelection(0, stem.Length()); + const auto stem_len = stem.Length(); + + txt_filename->SetValue(recent_path); + txt_filename->SetFocus(); Fit(); + + Bind(wxEVT_SHOW, [=](const wxShowEvent &) { + // Another similar case where the function only works with EVT_SHOW + CallAfter, + // this time on Mac. + CallAfter([=]() { + txt_filename->SetSelection(recent_path_len, recent_path_len + stem_len); + }); + }); } fs::path PrintHostSendDialog::filename() const @@ -61,6 +83,24 @@ bool PrintHostSendDialog::start_print() const return box_print->GetValue(); } +void PrintHostSendDialog::EndModal(int ret) +{ + if (ret == wxID_OK) { + // Persist path and print settings + wxString path = txt_filename->GetValue(); + int last_slash = path.Find('/', true); + if (last_slash != wxNOT_FOUND) { + path = path.SubString(0, last_slash); + wxGetApp().app_config->set("recent", CONFIG_KEY_PATH, into_u8(path)); + } + + bool print = box_print->GetValue(); + GUI::get_app_config()->set("recent", CONFIG_KEY_PRINT, print ? "1" : "0"); + } + + MsgDialog::EndModal(ret); +} + wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event); @@ -95,10 +135,11 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) , on_error_evt(this, EVT_PRINTHOST_ERROR, &PrintHostQueueDialog::on_error, this) , on_cancel_evt(this, EVT_PRINTHOST_CANCEL, &PrintHostQueueDialog::on_cancel, this) { - enum { HEIGHT = 800, WIDTH = 400, SPACING = 5 }; + enum { HEIGHT = 60, WIDTH = 30, SPACING = 5 }; - SetSize(wxSize(HEIGHT, WIDTH)); - SetSize(GetMinSize()); + const auto em = GetTextExtent("m").x; + + SetSize(wxSize(HEIGHT * em, WIDTH * em)); auto *topsizer = new wxBoxSizer(wxVERTICAL); diff --git a/src/slic3r/GUI/PrintHostDialogs.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp index ee3fe26d88..e7c84fefd2 100644 --- a/src/slic3r/GUI/PrintHostDialogs.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -33,6 +33,7 @@ public: boost::filesystem::path filename() const; bool start_print() const; + virtual void EndModal(int ret) override; private: wxTextCtrl *txt_filename; wxCheckBox *box_print; diff --git a/src/slic3r/GUI/RammingChart.cpp b/src/slic3r/GUI/RammingChart.cpp index cbf660f2c9..41e5cdfe74 100644 --- a/src/slic3r/GUI/RammingChart.cpp +++ b/src/slic3r/GUI/RammingChart.cpp @@ -20,7 +20,7 @@ void Chart::draw() { dc.DrawRectangle(m_rect); if (visible_area.m_width < 0.499) { - dc.DrawText(_(L("NO RAMMING AT ALL")),wxPoint(m_rect.GetLeft()+m_rect.GetWidth()/2-50,m_rect.GetBottom()-m_rect.GetHeight()/2)); + dc.DrawText(_(L("NO RAMMING AT ALL")),wxPoint(m_rect.GetLeft()+m_rect.GetWidth()/2-legend_side,m_rect.GetBottom()-m_rect.GetHeight()/2)); return; } @@ -55,9 +55,9 @@ void Chart::draw() { for (float math_x=int(visible_area.m_x*10)/10 ; math_x < (visible_area.m_x+visible_area.m_width) ; math_x+=0.1f) { int x = math_to_screen(wxPoint2DDouble(math_x,visible_area.m_y)).x; int y = m_rect.GetBottom(); - if (x-last_mark < 50) continue; + if (x-last_mark < legend_side) continue; dc.DrawLine(x,y+3,x,y-3); - dc.DrawText(wxString().Format(wxT("%.1f"), math_x),wxPoint(x-10,y+7)); + dc.DrawText(wxString().Format(wxT("%.1f"), math_x),wxPoint(x-scale_unit,y+0.5*scale_unit)); last_mark = x; } @@ -66,9 +66,9 @@ void Chart::draw() { for (int math_y=visible_area.m_y ; math_y < (visible_area.m_y+visible_area.m_height) ; math_y+=1) { int y = math_to_screen(wxPoint2DDouble(visible_area.m_x,math_y)).y; int x = m_rect.GetLeft(); - if (last_mark-y < 50) continue; + if (last_mark-y < legend_side) continue; dc.DrawLine(x-3,y,x+3,y); - dc.DrawText(wxString()<>& initial_buttons,int ramming_speed_size, float sampling) : - wxWindow(parent,wxID_ANY,rect.GetTopLeft(),rect.GetSize()) + Chart(wxWindow* parent, wxRect rect,const std::vector>& initial_buttons,int ramming_speed_size, float sampling, int scale_unit=10) : + wxWindow(parent,wxID_ANY,rect.GetTopLeft(),rect.GetSize()), + scale_unit(scale_unit), legend_side(5*scale_unit) { SetBackgroundStyle(wxBG_STYLE_PAINT); - m_rect = wxRect(wxPoint(50,0),rect.GetSize()-wxSize(50,50)); + m_rect = wxRect(wxPoint(legend_side,0),rect.GetSize()-wxSize(legend_side,legend_side)); visible_area = wxRect2DDouble(0.0, 0.0, sampling*ramming_speed_size, 20.); m_buttons.clear(); if (initial_buttons.size()>0) @@ -49,6 +50,7 @@ public: DECLARE_EVENT_TABLE() + private: static const bool fixed_x = true; @@ -56,6 +58,9 @@ private: static const bool manual_points_manipulation = false; static const int side = 10; // side of draggable button + const int scale_unit; + int legend_side; + class ButtonToDrag { public: bool operator<(const ButtonToDrag& a) const { return m_pos.m_x < a.m_pos.m_x; } diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index fd8ab5f934..dfaba71ae6 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -3,8 +3,12 @@ #include "3DScene.hpp" #include "GUI.hpp" +#include + #include #include +#include "GUI_App.hpp" +#include "wxExtensions.hpp" namespace Slic3r { namespace GUI { @@ -42,15 +46,16 @@ SysInfoDialog::SysInfoDialog() wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); SetBackgroundColour(bgr_clr); wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL); - hsizer->SetMinSize(wxSize(600, -1)); + hsizer->SetMinSize(wxSize(50 * wxGetApp().em_unit(), -1)); auto main_sizer = new wxBoxSizer(wxVERTICAL); - main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 10); + main_sizer->Add(hsizer, 1, wxEXPAND | wxALL, 10); // logo - 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 | wxTOP | wxBOTTOM, 15); +// 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)); + auto *logo = new wxStaticBitmap(this, wxID_ANY, create_scaled_bitmap("Slic3r_192px.png")); + hsizer->Add(logo, 0, wxALIGN_CENTER_VERTICAL); wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); hsizer->Add(vsizer, 1, wxEXPAND|wxLEFT, 20); @@ -63,7 +68,7 @@ SysInfoDialog::SysInfoDialog() title_font.SetFamily(wxFONTFAMILY_ROMAN); title_font.SetPointSize(22); title->SetFont(title_font); - vsizer->Add(title, 0, wxALIGN_LEFT | wxTOP, 50); + vsizer->Add(title, 0, wxEXPAND | wxALIGN_LEFT | wxTOP, wxGetApp().em_unit()/*50*/); } // main_info_text @@ -89,13 +94,13 @@ SysInfoDialog::SysInfoDialog() "", bgr_clr_str, text_clr_str, text_clr_str, get_main_info(true)); html->SetPage(text); - vsizer->Add(html, 1, wxEXPAND); + vsizer->Add(html, 1, wxEXPAND | wxBOTTOM, wxGetApp().em_unit()); } // opengl_info wxHtmlWindow* opengl_info_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO); { - opengl_info_html->SetMinSize(wxSize(-1, 200)); + opengl_info_html->SetMinSize(wxSize(-1, 16 * wxGetApp().em_unit())); opengl_info_html->SetFonts(font.GetFaceName(), font.GetFaceName(), size); opengl_info_html->SetBorders(10); const auto text = wxString::Format( diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 28b7cd248d..783527026f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -56,6 +56,8 @@ Tab::Tab(wxNotebook* parent, const wxString& title, const char* name) : m_compatible_prints.dialog_label = _(L("Select the print profiles this profile is compatible with.")); wxGetApp().tabs_list.push_back(this); + + m_em_unit = wxGetApp().em_unit(); } void Tab::set_type() @@ -96,22 +98,26 @@ void Tab::create_preset_tab() #endif //__WXOSX__ // preset chooser - m_presets_choice = new wxBitmapComboBox(panel, wxID_ANY, "", wxDefaultPosition, wxSize(270, -1), 0, 0,wxCB_READONLY); + m_presets_choice = new wxBitmapComboBox(panel, wxID_ANY, "", wxDefaultPosition, wxSize(25 * m_em_unit, -1), 0, 0, wxCB_READONLY); auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); //buttons wxBitmap bmpMenu; - bmpMenu = wxBitmap(from_u8(Slic3r::var("disk.png")), wxBITMAP_TYPE_PNG); +// bmpMenu = wxBitmap(from_u8(Slic3r::var("disk.png")), wxBITMAP_TYPE_PNG); + bmpMenu = create_scaled_bitmap("disk.png"); m_btn_save_preset = new wxBitmapButton(panel, wxID_ANY, bmpMenu, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); if (wxMSW) m_btn_save_preset->SetBackgroundColour(color); - bmpMenu = wxBitmap(from_u8(Slic3r::var("delete.png")), wxBITMAP_TYPE_PNG); +// bmpMenu = wxBitmap(from_u8(Slic3r::var("delete.png")), wxBITMAP_TYPE_PNG); + bmpMenu = create_scaled_bitmap("delete.png"); m_btn_delete_preset = new wxBitmapButton(panel, wxID_ANY, bmpMenu, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); if (wxMSW) m_btn_delete_preset->SetBackgroundColour(color); m_show_incompatible_presets = false; - 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_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_bmp_show_incompatible_presets = create_scaled_bitmap("flag-red-icon.png"); + m_bmp_hide_incompatible_presets = create_scaled_bitmap("flag-green-icon.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); @@ -134,13 +140,18 @@ void Tab::create_preset_tab() // Determine the theme color of OS (dark or light) auto luma = wxGetApp().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_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_value_lock = create_scaled_bitmap("sys_lock.png"); + m_bmp_value_unlock = create_scaled_bitmap(luma >= 128 ? "sys_unlock.png" : "sys_unlock_grey.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); +// 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); + m_bmp_value_revert = create_scaled_bitmap(luma >= 128 ? "action_undo.png" : "action_undo_grey.png"); + m_bmp_white_bullet = create_scaled_bitmap("bullet_white.png"); + m_bmp_question = create_scaled_bitmap("question_mark_01.png"); fill_icon_descriptions(); set_tooltips_text(); @@ -171,19 +182,20 @@ void Tab::create_preset_tab() // Sizer with buttons for mode changing m_mode_sizer = new PrusaModeSizer(panel); + const float scale_factor = wxGetApp().em_unit()*0.1;// GetContentScaleFactor(); m_hsizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_hsizer, 0, wxEXPAND | wxBOTTOM, 3); m_hsizer->Add(m_presets_choice, 0, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); - m_hsizer->AddSpacer(4); + m_hsizer->AddSpacer(int(4*scale_factor)); m_hsizer->Add(m_btn_save_preset, 0, wxALIGN_CENTER_VERTICAL); - m_hsizer->AddSpacer(4); + m_hsizer->AddSpacer(int(4 * scale_factor)); m_hsizer->Add(m_btn_delete_preset, 0, wxALIGN_CENTER_VERTICAL); - m_hsizer->AddSpacer(16); + m_hsizer->AddSpacer(int(16 * scale_factor)); m_hsizer->Add(m_btn_hide_incompatible_presets, 0, wxALIGN_CENTER_VERTICAL); - m_hsizer->AddSpacer(64); + m_hsizer->AddSpacer(int(64 * scale_factor)); 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(32); + m_hsizer->AddSpacer(int(32 * scale_factor)); m_hsizer->Add(m_question_btn, 0, wxALIGN_CENTER_VERTICAL); // m_hsizer->AddStretchSpacer(32); // StretchSpacer has a strange behavior under OSX, so @@ -201,10 +213,10 @@ void Tab::create_preset_tab() m_hsizer->Add(m_left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3); // tree - m_treectrl = new wxTreeCtrl(panel, wxID_ANY, wxDefaultPosition, wxSize(185, -1), + m_treectrl = new wxTreeCtrl(panel, wxID_ANY, wxDefaultPosition, wxSize(20 * m_em_unit, -1), wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_SUNKEN | wxWANTS_CHARS); m_left_sizer->Add(m_treectrl, 1, wxEXPAND); - m_icons = new wxImageList(16, 16, true, 1); + m_icons = new wxImageList(int(16 * scale_factor), int(16 * scale_factor), true, 1); // Index of the last icon inserted into $self->{icons}. m_icon_count = -1; m_treectrl->AssignImageList(m_icons); @@ -263,8 +275,9 @@ Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::str icon_idx = (m_icon_index.find(icon) == m_icon_index.end()) ? -1 : m_icon_index.at(icon); if (icon_idx == -1) { // Add a new icon to the icon list. - wxIcon img_icon(from_u8(Slic3r::var(icon)), wxBITMAP_TYPE_PNG); - m_icons->Add(img_icon); +// wxIcon img_icon(from_u8(Slic3r::var(icon)), wxBITMAP_TYPE_PNG); +// m_icons->Add(img_icon); + m_icons->Add(create_scaled_bitmap(icon)); icon_idx = ++m_icon_count; m_icon_index[icon] = icon_idx; } @@ -497,7 +510,7 @@ void TabSLAMaterial::init_options_list() void Tab::get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page) { - auto opt = m_options_list.find(opt_key); + auto opt = m_options_list.find(opt_key); if (sys_page) sys_page = (opt->second & osSystemValue) != 0; modified_page |= (opt->second & osInitValue) == 0; } @@ -738,18 +751,6 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo void Tab::on_value_change(const std::string& opt_key, const boost::any& value) { - wxCommandEvent event(EVT_TAB_VALUE_CHANGED); - event.SetEventObject(this); - event.SetString(opt_key); - if (opt_key == "extruders_count") - { - int val = boost::any_cast(value); - event.SetInt(val); - } - - wxPostEvent(this, event); - - ConfigOptionsGroup* og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(supports_printer_technology(ptFFF)); if (opt_key == "fill_density" || opt_key == "supports_enable" || opt_key == "pad_enable") { @@ -774,7 +775,29 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) if (opt_key == "wipe_tower" || opt_key == "single_extruder_multi_material" || opt_key == "extruders_count" ) update_wiping_button_visibility(); + if (opt_key == "extruders_count") + wxGetApp().plater()->on_extruders_change(boost::any_cast(value)); + update(); + + // #ys_FIXME_to_delete + // Post event to the Plater after updating of the all dirty options + // It helps to avoid needless schedule_background_processing +// if (update_completed()) +// if (m_update_stack.empty()) +// { +// // wxCommandEvent event(EVT_TAB_VALUE_CHANGED); +// // event.SetEventObject(this); +// // event.SetString(opt_key); +// // if (opt_key == "extruders_count") +// // { +// // const int val = boost::any_cast(value); +// // event.SetInt(val); +// // } +// // +// // wxPostEvent(this, event); +// wxGetApp().mainframe->on_value_changed(m_config); +// } } // Show/hide the 'purging volumes' button @@ -807,10 +830,18 @@ void Tab::on_presets_changed() // refresh the print or filament/sla_material tab page. wxGetApp().get_tab(t)->load_current_preset(); } + // clear m_dependent_tabs after first update from select_preset() + // to avoid needless preset loading from update() function + m_dependent_tabs.clear(); + + // #ys_FIXME_to_delete +// wxCommandEvent event(EVT_TAB_PRESETS_CHANGED); +// event.SetEventObject(this); +// wxPostEvent(this, event); + + // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets + wxGetApp().plater()->sidebar().update_presets(m_type); - wxCommandEvent event(EVT_TAB_PRESETS_CHANGED); - event.SetEventObject(this); - wxPostEvent(this, event); update_preset_description_line(); } @@ -952,7 +983,8 @@ void TabPrint::build() optgroup = page->new_optgroup(_(L("Infill"))); optgroup->append_single_option_line("fill_density"); optgroup->append_single_option_line("fill_pattern"); - optgroup->append_single_option_line("external_fill_pattern"); + optgroup->append_single_option_line("top_fill_pattern"); + optgroup->append_single_option_line("bottom_fill_pattern"); optgroup = page->new_optgroup(_(L("Reducing printing time"))); optgroup->append_single_option_line("infill_every_layers"); @@ -1105,14 +1137,14 @@ void TabPrint::build() optgroup = page->new_optgroup(_(L("Post-processing scripts")), 0); option = optgroup->get_option("post_process"); option.opt.full_width = true; - option.opt.height = 50; + option.opt.height = 5 * m_em_unit;//50; optgroup->append_single_option_line(option); page = add_options_page(_(L("Notes")), "note.png"); optgroup = page->new_optgroup(_(L("Notes")), 0); option = optgroup->get_option("notes"); option.opt.full_width = true; - option.opt.height = 250; + option.opt.height = 25 * m_em_unit;//250; optgroup->append_single_option_line(option); page = add_options_page(_(L("Dependencies")), "wrench.png"); @@ -1146,13 +1178,15 @@ void TabPrint::update() if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) return; // ys_FIXME + // #ys_FIXME_to_delete //! Temporary workaround for the correct updates of the SpinCtrl (like "perimeters"): // KillFocus() for the wxSpinCtrl use CallAfter function. So, // to except the duplicate call of the update() after dialog->ShowModal(), // let check if this process is already started. - if (is_msg_dlg_already_exist) - return; +// if (is_msg_dlg_already_exist) // ! It looks like a fixed problem after start to using of a m_dirty_options +// return; // ! TODO Let delete this part of code after a common aplication testing + m_update_cnt++; Freeze(); double fill_density = m_config->option("fill_density")->value; @@ -1168,7 +1202,7 @@ void TabPrint::update() "- no ensure_vertical_shell_thickness\n" "\nShall I adjust those settings in order to enable Spiral Vase?")); auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Spiral Vase")), wxICON_WARNING | wxYES | wxNO); - is_msg_dlg_already_exist = true; +// is_msg_dlg_already_exist = true; DynamicPrintConfig new_conf = *m_config; if (dialog->ShowModal() == wxID_YES) { new_conf.set_key_value("perimeters", new ConfigOptionInt(1)); @@ -1184,7 +1218,7 @@ void TabPrint::update() } load_config(new_conf); on_value_change("fill_density", fill_density); - is_msg_dlg_already_exist = false; +// is_msg_dlg_already_exist = false; } if (m_config->opt_bool("wipe_tower") && m_config->opt_bool("support_material") && @@ -1261,7 +1295,7 @@ void TabPrint::update() } } if (!str_fill_pattern.empty()) { - const std::vector &external_fill_pattern = m_config->def()->get("external_fill_pattern")->enum_values; + const std::vector &external_fill_pattern = m_config->def()->get("top_fill_pattern")->enum_values; bool correct_100p_fill = false; for (const std::string &fill : external_fill_pattern) { @@ -1302,7 +1336,7 @@ void TabPrint::update() bool have_solid_infill = m_config->opt_int("top_solid_layers") > 0 || m_config->opt_int("bottom_solid_layers") > 0; // solid_infill_extruder uses the same logic as in Print::extruders() - for (auto el : {"external_fill_pattern", "infill_first", "solid_infill_extruder", + for (auto el : {"top_fill_pattern", "bottom_fill_pattern", "infill_first", "solid_infill_extruder", "solid_infill_extrusion_width", "solid_infill_speed" }) get_field(el)->toggle(have_solid_infill); @@ -1365,6 +1399,10 @@ void TabPrint::update() from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); Thaw(); + m_update_cnt--; + + if (m_update_cnt==0) + wxGetApp().mainframe->on_config_changed(m_config); } void TabPrint::OnActivate() @@ -1468,18 +1506,20 @@ void TabFilament::build() }; optgroup->append_line(line); + const int gcode_field_height = 15 * m_em_unit; // 150 + const int notes_field_height = 25 * m_em_unit; // 250 page = add_options_page(_(L("Custom G-code")), "cog.png"); optgroup = page->new_optgroup(_(L("Start G-code")), 0); Option option = optgroup->get_option("start_filament_gcode"); option.opt.full_width = true; - option.opt.height = 150; + option.opt.height = gcode_field_height;// 150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(_(L("End G-code")), 0); option = optgroup->get_option("end_filament_gcode"); option.opt.full_width = true; - option.opt.height = 150; + option.opt.height = gcode_field_height;// 150; optgroup->append_single_option_line(option); page = add_options_page(_(L("Notes")), "note.png"); @@ -1487,7 +1527,7 @@ void TabFilament::build() optgroup->label_width = 0; option = optgroup->get_option("filament_notes"); option.opt.full_width = true; - option.opt.height = 250; + option.opt.height = notes_field_height;// 250; optgroup->append_single_option_line(option); page = add_options_page(_(L("Dependencies")), "wrench.png"); @@ -1532,6 +1572,7 @@ void TabFilament::update() if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) return; // ys_FIXME + m_update_cnt++; Freeze(); wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); m_cooling_description_line->SetText(text); @@ -1546,7 +1587,11 @@ void TabFilament::update() for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) get_field(el)->toggle(fan_always_on); - Thaw(); + Thaw(); + m_update_cnt--; + + if (m_update_cnt == 0) + wxGetApp().mainframe->on_config_changed(m_config); } void TabFilament::OnActivate() @@ -1588,7 +1633,8 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) // TODO: SLA Bonjour auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG)); +// btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG)); + btn->SetBitmap(create_scaled_bitmap("zoom.png")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1606,7 +1652,8 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) auto print_host_test = [this](wxWindow* parent) { auto btn = m_print_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); - btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG)); +// btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG)); + btn->SetBitmap(create_scaled_bitmap("wrench.png")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1642,7 +1689,8 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) { auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG)); +// btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG)); + btn->SetBitmap(create_scaled_bitmap("zoom.png")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1719,7 +1767,8 @@ void TabPrinter::build_fff() line.widget = [this](wxWindow* parent) { auto btn = new wxButton(parent, wxID_ANY, _(L(" Set "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); btn->SetFont(wxGetApp().small_font()); - btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); +// btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); + btn->SetBitmap(create_scaled_bitmap("printer_empty.png")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1846,48 +1895,50 @@ void TabPrinter::build_fff() optgroup->append_single_option_line("use_volumetric_e"); optgroup->append_single_option_line("variable_layer_height"); + const int gcode_field_height = 15 * m_em_unit; // 150 + const int notes_field_height = 25 * m_em_unit; // 250 page = add_options_page(_(L("Custom G-code")), "cog.png"); optgroup = page->new_optgroup(_(L("Start G-code")), 0); option = optgroup->get_option("start_gcode"); option.opt.full_width = true; - option.opt.height = 150; + option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(_(L("End G-code")), 0); option = optgroup->get_option("end_gcode"); option.opt.full_width = true; - option.opt.height = 150; + option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(_(L("Before layer change G-code")), 0); option = optgroup->get_option("before_layer_gcode"); option.opt.full_width = true; - option.opt.height = 150; + option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(_(L("After layer change G-code")), 0); option = optgroup->get_option("layer_gcode"); option.opt.full_width = true; - option.opt.height = 150; + option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(_(L("Tool change G-code")), 0); option = optgroup->get_option("toolchange_gcode"); option.opt.full_width = true; - option.opt.height = 150; + option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(_(L("Between objects G-code (for sequential printing)")), 0); option = optgroup->get_option("between_objects_gcode"); option.opt.full_width = true; - option.opt.height = 150; + option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); page = add_options_page(_(L("Notes")), "note.png"); optgroup = page->new_optgroup(_(L("Notes")), 0); option = optgroup->get_option("printer_notes"); option.opt.full_width = true; - option.opt.height = 250; + option.opt.height = notes_field_height;//250; optgroup->append_single_option_line(option); page = add_options_page(_(L("Dependencies")), "wrench.png"); @@ -1918,7 +1969,8 @@ void TabPrinter::build_sla() line.widget = [this](wxWindow* parent) { auto btn = new wxButton(parent, wxID_ANY, _(L(" Set ")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); // btn->SetFont(Slic3r::GUI::small_font); - btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); +// btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); + btn->SetBitmap(create_scaled_bitmap("printer_empty.png")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1949,6 +2001,13 @@ void TabPrinter::build_sla() optgroup->append_line(line); optgroup->append_single_option_line("display_orientation"); + optgroup = page->new_optgroup(_(L("Tilt"))); + line = { _(L("Tilt time")), "" }; + line.append_option(optgroup->get_option("fast_tilt_time")); + line.append_option(optgroup->get_option("slow_tilt_time")); + optgroup->append_line(line); + optgroup->append_single_option_line("area_fill"); + optgroup = page->new_optgroup(_(L("Corrections"))); line = Line{ m_config->def()->get("printer_correction")->full_label, "" }; std::vector axes{ "X", "Y", "Z" }; @@ -1964,11 +2023,13 @@ void TabPrinter::build_sla() optgroup = page->new_optgroup(_(L("Print Host upload"))); build_printhost(optgroup.get()); + const int notes_field_height = 25 * m_em_unit; // 250 + page = add_options_page(_(L("Notes")), "note.png"); optgroup = page->new_optgroup(_(L("Notes")), 0); option = optgroup->get_option("printer_notes"); option.opt.full_width = true; - option.opt.height = 250; + option.opt.height = notes_field_height;//250; optgroup->append_single_option_line(option); page = add_options_page(_(L("Dependencies")), "wrench.png"); @@ -2017,7 +2078,7 @@ PageShp TabPrinter::build_kinematics_page() // Legend for OptionsGroups auto optgroup = page->new_optgroup(""); optgroup->set_show_modified_btns_val(false); - optgroup->label_width = 230; + optgroup->label_width = 23 * m_em_unit;// 230; auto line = Line{ "", "" }; ConfigOptionDef def; @@ -2204,7 +2265,12 @@ void TabPrinter::update_pages() void TabPrinter::update() { + m_update_cnt++; m_presets->get_edited_preset().printer_technology() == ptFFF ? update_fff() : update_sla(); + m_update_cnt--; + + if (m_update_cnt == 0) + wxGetApp().mainframe->on_config_changed(m_config); } void TabPrinter::update_fff() @@ -2794,7 +2860,8 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep deps.checkbox = new wxCheckBox(parent, wxID_ANY, _(L("All"))); deps.btn = new wxButton(parent, wxID_ANY, _(L(" Set "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); - deps.btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); +// deps.btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); + deps.btn->SetBitmap(create_scaled_bitmap("printer_empty.png")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add((deps.checkbox), 0, wxALIGN_CENTER_VERTICAL); @@ -2985,7 +3052,8 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la bmp_name = mode == comExpert ? "mode_expert_.png" : mode == comAdvanced ? "mode_middle_.png" : "mode_simple_.png"; } - auto bmp = new wxStaticBitmap(parent, wxID_ANY, bmp_name.empty() ? wxNullBitmap : wxBitmap(from_u8(var(bmp_name)), wxBITMAP_TYPE_PNG)); +// auto bmp = new wxStaticBitmap(parent, wxID_ANY, bmp_name.empty() ? wxNullBitmap : wxBitmap(from_u8(var(bmp_name)), wxBITMAP_TYPE_PNG)); + auto bmp = new wxStaticBitmap(parent, wxID_ANY, bmp_name.empty() ? wxNullBitmap : create_scaled_bitmap(bmp_name)); return bmp; }; @@ -3103,7 +3171,7 @@ void TabSLAMaterial::build() optgroup->append_single_option_line("initial_exposure_time"); optgroup = page->new_optgroup(_(L("Corrections"))); - optgroup->label_width = 190; + optgroup->label_width = 19 * m_em_unit;//190; std::vector corrections = { "material_correction_printing", "material_correction_curing" }; std::vector axes{ "X", "Y", "Z" }; for (auto& opt_key : corrections) { @@ -3124,7 +3192,7 @@ void TabSLAMaterial::build() optgroup->label_width = 0; Option option = optgroup->get_option("material_notes"); option.opt.full_width = true; - option.opt.height = 250; + option.opt.height = 25 * m_em_unit;//250; optgroup->append_single_option_line(option); page = add_options_page(_(L("Dependencies")), "wrench.png"); @@ -3167,6 +3235,14 @@ void TabSLAMaterial::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) return; // #ys_FIXME + +// #ys_FIXME +// m_update_cnt++; +// ! something to update +// m_update_cnt--; +// +// if (m_update_cnt == 0) + wxGetApp().mainframe->on_config_changed(m_config); } void TabSLAPrint::build() @@ -3178,6 +3254,7 @@ void TabSLAPrint::build() auto optgroup = page->new_optgroup(_(L("Layers"))); optgroup->append_single_option_line("layer_height"); + optgroup->append_single_option_line("faded_layers"); page = add_options_page(_(L("Supports")), "building.png"); optgroup = page->new_optgroup(_(L("Supports"))); @@ -3202,9 +3279,8 @@ void TabSLAPrint::build() optgroup->append_single_option_line("support_max_bridge_length"); optgroup = page->new_optgroup(_(L("Automatic generation"))); - optgroup->append_single_option_line("support_density_at_horizontal"); - optgroup->append_single_option_line("support_density_at_45"); - optgroup->append_single_option_line("support_minimal_z"); + optgroup->append_single_option_line("support_points_density_relative"); + optgroup->append_single_option_line("support_points_minimal_distance"); page = add_options_page(_(L("Pad")), "brick.png"); optgroup = page->new_optgroup(_(L("Pad"))); @@ -3212,7 +3288,9 @@ void TabSLAPrint::build() optgroup->append_single_option_line("pad_wall_thickness"); optgroup->append_single_option_line("pad_wall_height"); optgroup->append_single_option_line("pad_max_merge_distance"); - optgroup->append_single_option_line("pad_edge_radius"); + // TODO: Disabling this parameter for the beta release +// optgroup->append_single_option_line("pad_edge_radius"); + optgroup->append_single_option_line("pad_wall_tilt"); page = add_options_page(_(L("Output options")), "page_white_go.png"); optgroup = page->new_optgroup(_(L("Output file"))); @@ -3251,6 +3329,14 @@ void TabSLAPrint::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) return; // #ys_FIXME + +// #ys_FIXME +// m_update_cnt++; +// ! something to update +// m_update_cnt--; +// +// if (m_update_cnt == 0) + wxGetApp().mainframe->on_config_changed(m_config); } } // GUI diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index e00e87b620..7ef066963e 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -203,6 +203,8 @@ protected: void set_type(); + int m_em_unit; + public: PresetBundle* m_preset_bundle; bool m_show_btn_incompatible_presets = false; @@ -210,6 +212,11 @@ public: DynamicPrintConfig* m_config; ogStaticText* m_parent_preset_description_line; wxStaticText* m_colored_Label = nullptr; + // Counter for the updating (because of an update() function can have a recursive behavior): + // 1. increase value from the very beginning of an update() function + // 2. decrease value at the end of an update() function + // 3. propagate changed configuration to the Platter when (m_update_cnt == 0) only + int m_update_cnt = 0; public: Tab(wxNotebook* parent, const wxString& title, const char* name); diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp index 346a9e231b..c4b78eb1a8 100644 --- a/src/slic3r/GUI/UpdateDialogs.cpp +++ b/src/slic3r/GUI/UpdateDialogs.cpp @@ -12,6 +12,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "GUI.hpp" +#include "GUI_App.hpp" #include "I18N.hpp" #include "ConfigWizard.hpp" @@ -34,7 +35,8 @@ MsgUpdateSlic3r::MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_on 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); + const int content_width = CONTENT_WIDTH * wxGetApp().em_unit(); + text->Wrap(content_width > link_width ? content_width : link_width); content_sizer->Add(text); content_sizer->AddSpacer(VERT_SPACING); @@ -75,7 +77,7 @@ MsgUpdateConfig::MsgUpdateConfig(const std::unordered_mapWrap(CONTENT_WIDTH); + text->Wrap(CONTENT_WIDTH * wxGetApp().em_unit()); content_sizer->Add(text); content_sizer->AddSpacer(VERT_SPACING); @@ -115,16 +117,16 @@ MsgDataIncompatible::MsgDataIncompatible(const std::unordered_mapWrap(CONTENT_WIDTH); + text->Wrap(CONTENT_WIDTH * wxGetApp().em_unit()); 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); + text2->Wrap(CONTENT_WIDTH * wxGetApp().em_unit()); content_sizer->Add(text2); content_sizer->AddSpacer(VERT_SPACING); auto *text3 = new wxStaticText(this, wxID_ANY, _(L("Incompatible bundles:"))); - text3->Wrap(CONTENT_WIDTH); + text3->Wrap(CONTENT_WIDTH * wxGetApp().em_unit()); content_sizer->Add(text3); content_sizer->AddSpacer(VERT_SPACING); @@ -175,7 +177,7 @@ MsgDataLegacy::MsgDataLegacy() : )), ConfigWizard::name() )); - text->Wrap(CONTENT_WIDTH); + text->Wrap(CONTENT_WIDTH * wxGetApp().em_unit()); content_sizer->Add(text); content_sizer->AddSpacer(VERT_SPACING); diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index 2530f5fea9..4c2b2480e6 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -3,9 +3,13 @@ #include "WipeTowerDialog.hpp" #include "GUI.hpp" #include "I18N.hpp" +#include "GUI_App.hpp" #include +int scale(const int val) { return val * Slic3r::GUI::wxGetApp().em_unit(); } +int ITEM_WIDTH() { return scale(6); } + RammingDialog::RammingDialog(wxWindow* parent,const std::string& parameters) : wxDialog(parent, wxID_ANY, _(L("Ramming customization")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/) { @@ -65,14 +69,14 @@ RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters) while (stream >> x >> y) buttons.push_back(std::make_pair(x, y)); - m_chart = new Chart(this, wxRect(10, 10, 480, 360), buttons, ramming_speed_size, 0.25f); + m_chart = new Chart(this, wxRect(scale(1),scale(1),scale(48),scale(36)), buttons, ramming_speed_size, 0.25f, scale(1)); m_chart->SetBackgroundColour(parent->GetBackgroundColour()); // see comment in RammingDialog constructor sizer_chart->Add(m_chart, 0, wxALL, 5); - m_widget_time = new wxSpinCtrlDouble(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,0.,5.0,3.,0.5); - m_widget_volume = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,0,10000,0); - m_widget_ramming_line_width_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,10,200,100); - m_widget_ramming_step_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,10,200,100); + m_widget_time = new wxSpinCtrlDouble(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),wxSP_ARROW_KEYS,0.,5.0,3.,0.5); + m_widget_volume = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),wxSP_ARROW_KEYS,0,10000,0); + m_widget_ramming_line_width_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),wxSP_ARROW_KEYS,10,200,100); + m_widget_ramming_step_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),wxSP_ARROW_KEYS,10,200,100); auto gsizer_param = new wxFlexGridSizer(2, 5, 15); gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total ramming time")) + " (" + _(L("s")) + "):")), 0, wxALIGN_CENTER_VERTICAL); @@ -86,7 +90,7 @@ RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters) gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line spacing")) + " (%):")), 0, wxALIGN_CENTER_VERTICAL); gsizer_param->Add(m_widget_ramming_step_multiplicator); - sizer_param->Add(gsizer_param, 0, wxTOP, 100); + sizer_param->Add(gsizer_param, 0, wxTOP, scale(10)); m_widget_time->SetValue(m_chart->get_time()); m_widget_time->SetDigits(2); @@ -132,7 +136,6 @@ std::string RammingPanel::get_parameters() } -#define ITEM_WIDTH 60 // Parent dialog for purging volume adjustments - it fathers WipingPanel widget (that contains all controls) and a button to toggle simple/advanced mode: WipingDialog::WipingDialog(wxWindow* parent,const std::vector& matrix, const std::vector& extruders) : wxDialog(parent, wxID_ANY, _(L("Wipe tower - Purging volume adjustment")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/) @@ -143,7 +146,7 @@ WipingDialog::WipingDialog(wxWindow* parent,const std::vector& matrix, co auto main_sizer = new wxBoxSizer(wxVERTICAL); // set min sizer width according to extruders count - const auto sizer_width = (int)((sqrt(matrix.size()) + 2.8)*ITEM_WIDTH); + const auto sizer_width = (int)((sqrt(matrix.size()) + 2.8)*ITEM_WIDTH()); main_sizer->SetMinSize(wxSize(sizer_width, -1)); main_sizer->Add(m_panel_wiping, 0, wxEXPAND | wxALL, 5); @@ -166,7 +169,10 @@ WipingDialog::WipingDialog(wxWindow* parent,const std::vector& matrix, co // This function allows to "play" with sizers parameters (like align or border) void WipingPanel::format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift/*=0*/) { - sizer->Add(new wxStaticText(page, wxID_ANY, info,wxDefaultPosition,wxSize(0,50)), 0, wxEXPAND | wxLEFT, 15); + wxSize text_size = GetTextExtent(info); + auto info_str = new wxStaticText(page, wxID_ANY, info ,wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER); + info_str->Wrap(int(0.6*text_size.x)); + sizer->Add( info_str, 0, wxALIGN_CENTER_HORIZONTAL | wxEXPAND); auto table_sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(table_sizer, 0, wxALIGN_CENTER | wxCENTER, table_lshift); table_sizer->Add(new wxStaticText(page, wxID_ANY, table_title), 0, wxALIGN_CENTER | wxTOP, 50); @@ -198,7 +204,7 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con edit_boxes.push_back(std::vector(0)); for (unsigned int j = 0; j < m_number_of_extruders; ++j) { - edit_boxes.back().push_back(new wxTextCtrl(m_page_advanced, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH, -1))); + edit_boxes.back().push_back(new wxTextCtrl(m_page_advanced, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1))); if (i == j) edit_boxes[i][j]->Disable(); else @@ -229,8 +235,8 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con gridsizer_simple->Add(new wxStaticText(m_page_simple,wxID_ANY,wxString(_(L("loaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); for (unsigned int i=0;iAdd(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("Tool #"))) << i + 1 << ": "), 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); gridsizer_simple->Add(m_old.back(),0); gridsizer_simple->Add(m_new.back(),0); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index d24db63ea7..a3eeb45f44 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -13,6 +13,7 @@ #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "libslic3r/GCode/PreviewData.hpp" +#include "I18N.hpp" using Slic3r::GUI::from_u8; @@ -41,7 +42,8 @@ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, std::function cb, const std::string& icon, wxEvtHandler* event_handler) { - const wxBitmap& bmp = !icon.empty() ? wxBitmap(from_u8(Slic3r::var(icon)), wxBITMAP_TYPE_PNG) : wxNullBitmap; +// const wxBitmap& bmp = !icon.empty() ? wxBitmap(from_u8(Slic3r::var(icon)), wxBITMAP_TYPE_PNG) : wxNullBitmap; + const wxBitmap& bmp = !icon.empty() ? create_scaled_bitmap(icon) : wxNullBitmap; return append_menu_item(menu, id, string, description, cb, bmp, event_handler); } @@ -52,7 +54,8 @@ wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxStrin wxMenuItem* item = new wxMenuItem(menu, id, string, description); if (!icon.empty()) - item->SetBitmap(wxBitmap(from_u8(Slic3r::var(icon)), wxBITMAP_TYPE_PNG)); +// item->SetBitmap(wxBitmap(from_u8(Slic3r::var(icon)), wxBITMAP_TYPE_PNG)); + item->SetBitmap(create_scaled_bitmap(icon)); item->SetSubMenu(sub_menu); menu->Append(item); @@ -402,11 +405,28 @@ void PrusaCollapsiblePaneMSW::Collapse(bool collapse) // PrusaObjectDataViewModelNode // ---------------------------------------------------------------------------- +wxBitmap create_scaled_bitmap(const std::string& bmp_name) +{ + const double scale_f = Slic3r::GUI::wxGetApp().em_unit()* 0.1;//GetContentScaleFactor(); + if (scale_f == 1.0) + return wxBitmap(Slic3r::GUI::from_u8(Slic3r::var(bmp_name)), wxBITMAP_TYPE_PNG); +// else if (scale_f == 2.0) // use biger icon +// return wxBitmap(Slic3r::GUI::from_u8(Slic3r::var(bmp_name_X2)), wxBITMAP_TYPE_PNG); + + wxImage img = wxImage(Slic3r::GUI::from_u8(Slic3r::var(bmp_name)), wxBITMAP_TYPE_PNG); + const int sz_w = int(img.GetWidth()*scale_f); + const int sz_h = int(img.GetHeight()*scale_f); + img.Rescale(sz_w, sz_h, wxIMAGE_QUALITY_BILINEAR); + return wxBitmap(img); +} + void PrusaObjectDataViewModelNode::set_object_action_icon() { - m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("add_object.png")), wxBITMAP_TYPE_PNG); +// m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("add_object.png")), wxBITMAP_TYPE_PNG); + m_action_icon = create_scaled_bitmap("add_object.png"); } void PrusaObjectDataViewModelNode::set_part_action_icon() { - m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var(m_type == itVolume ? "cog.png" : "brick_go.png")), wxBITMAP_TYPE_PNG); +// m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var(m_type == itVolume ? "cog.png" : "brick_go.png")), wxBITMAP_TYPE_PNG); + m_action_icon = create_scaled_bitmap(m_type == itVolume ? "cog.png" : "brick_go.png"); } Slic3r::GUI::BitmapCache *m_bitmap_cache = nullptr; @@ -472,7 +492,7 @@ wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name, const int ext wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &parent_item, const wxString &name, - const int volume_type, + const Slic3r::ModelVolumeType volume_type, const int extruder/* = 0*/, const bool create_frst_child/* = true*/) { @@ -498,7 +518,7 @@ wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &pa if (insert_position > 0) insert_position++; } - const auto node = new PrusaObjectDataViewModelNode(root, name, *m_volume_bmps[volume_type], extruder_str, root->m_volumes_cnt); + const auto node = new PrusaObjectDataViewModelNode(root, name, *m_volume_bmps[int(volume_type)], extruder_str, root->m_volumes_cnt); insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); // notify control const wxDataViewItem child((void*)node); @@ -1260,13 +1280,13 @@ void PrusaObjectDataViewModel::UpdateSettingsDigest(const wxDataViewItem &item, ItemChanged(item); } -void PrusaObjectDataViewModel::SetVolumeType(const wxDataViewItem &item, const int type) +void PrusaObjectDataViewModel::SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type) { if (!item.IsOk() || GetItemType(item) != itVolume) return; PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); - node->SetBitmap(*m_volume_bmps[type]); + node->SetBitmap(*m_volume_bmps[int(type)]); ItemChanged(item); } @@ -1420,22 +1440,32 @@ PrusaDoubleSlider::PrusaDoubleSlider(wxWindow *parent, SetDoubleBuffered(true); #endif //__WXOSX__ - m_bmp_thumb_higher = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("right_half_circle.png")) : - Slic3r::GUI::from_u8(Slic3r::var("up_half_circle.png")), wxBITMAP_TYPE_PNG); - m_bmp_thumb_lower = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("left_half_circle.png")) : - Slic3r::GUI::from_u8(Slic3r::var("down_half_circle.png")), wxBITMAP_TYPE_PNG); +// m_bmp_thumb_higher = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("right_half_circle.png")) : +// Slic3r::GUI::from_u8(Slic3r::var("up_half_circle.png")), wxBITMAP_TYPE_PNG); +// m_bmp_thumb_lower = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("left_half_circle.png")) : +// Slic3r::GUI::from_u8(Slic3r::var("down_half_circle.png")), wxBITMAP_TYPE_PNG); + m_bmp_thumb_higher = wxBitmap(create_scaled_bitmap(style == wxSL_HORIZONTAL ? "right_half_circle.png" : "up_half_circle.png")); + m_bmp_thumb_lower = wxBitmap(create_scaled_bitmap(style == wxSL_HORIZONTAL ? "left_half_circle.png" : "down_half_circle.png")); m_thumb_size = m_bmp_thumb_lower.GetSize(); - m_bmp_add_tick_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_on.png")), wxBITMAP_TYPE_PNG); - m_bmp_add_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_off.png")), wxBITMAP_TYPE_PNG); - m_bmp_del_tick_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG); - m_bmp_del_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_off.png")), wxBITMAP_TYPE_PNG); +// m_bmp_add_tick_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_on.png")), wxBITMAP_TYPE_PNG); +// m_bmp_add_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_off.png")), wxBITMAP_TYPE_PNG); +// m_bmp_del_tick_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG); +// m_bmp_del_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_off.png")), wxBITMAP_TYPE_PNG); + m_bmp_add_tick_on = create_scaled_bitmap("colorchange_add_on.png"); + m_bmp_add_tick_off = create_scaled_bitmap("colorchange_add_off.png"); + m_bmp_del_tick_on = create_scaled_bitmap("colorchange_delete_on.png"); + m_bmp_del_tick_off = create_scaled_bitmap("colorchange_delete_off.png"); m_tick_icon_dim = m_bmp_add_tick_on.GetSize().x; - m_bmp_one_layer_lock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_on.png")), wxBITMAP_TYPE_PNG); - m_bmp_one_layer_lock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_off.png")), wxBITMAP_TYPE_PNG); - m_bmp_one_layer_unlock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_on.png")), wxBITMAP_TYPE_PNG); - m_bmp_one_layer_unlock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_off.png")), wxBITMAP_TYPE_PNG); +// m_bmp_one_layer_lock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_on.png")), wxBITMAP_TYPE_PNG); +// m_bmp_one_layer_lock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_off.png")), wxBITMAP_TYPE_PNG); +// m_bmp_one_layer_unlock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_on.png")), wxBITMAP_TYPE_PNG); +// m_bmp_one_layer_unlock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_off.png")), wxBITMAP_TYPE_PNG); + m_bmp_one_layer_lock_on = create_scaled_bitmap("one_layer_lock_on.png"); + m_bmp_one_layer_lock_off = create_scaled_bitmap("one_layer_lock_off.png"); + m_bmp_one_layer_unlock_on = create_scaled_bitmap("one_layer_unlock_on.png"); + m_bmp_one_layer_unlock_off = create_scaled_bitmap("one_layer_unlock_off.png"); m_lock_icon_dim = m_bmp_one_layer_lock_on.GetSize().x; m_selection = ssUndef; @@ -1454,7 +1484,7 @@ PrusaDoubleSlider::PrusaDoubleSlider(wxWindow *parent, Bind(wxEVT_RIGHT_UP, &PrusaDoubleSlider::OnRightUp, this); // control's view variables - SLIDER_MARGIN = 4 + (style == wxSL_HORIZONTAL ? m_bmp_thumb_higher.GetWidth() : m_bmp_thumb_higher.GetHeight()); + SLIDER_MARGIN = 4 + Slic3r::GUI::wxGetApp().em_unit();//(style == wxSL_HORIZONTAL ? m_bmp_thumb_higher.GetWidth() : m_bmp_thumb_higher.GetHeight()); DARK_ORANGE_PEN = wxPen(wxColour(253, 84, 2)); ORANGE_PEN = wxPen(wxColour(253, 126, 66)); @@ -1480,7 +1510,7 @@ wxSize PrusaDoubleSlider::DoGetBestSize() const const wxSize size = wxControl::DoGetBestSize(); if (size.x > 1 && size.y > 1) return size; - const int new_size = is_horizontal() ? 80 : 120; + const int new_size = is_horizontal() ? 6 * Slic3r::GUI::wxGetApp().em_unit() : 8 * Slic3r::GUI::wxGetApp().em_unit(); return wxSize(new_size, new_size); } @@ -2253,10 +2283,16 @@ PrusaLockButton::PrusaLockButton( wxWindow *parent, const wxSize& size /*= wxDefaultSize*/): wxButton(parent, id, wxEmptyString, pos, size, wxBU_EXACTFIT | wxNO_BORDER) { - m_bmp_lock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_on.png")), wxBITMAP_TYPE_PNG); - m_bmp_lock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_off.png")), wxBITMAP_TYPE_PNG); - m_bmp_unlock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_on.png")), wxBITMAP_TYPE_PNG); - m_bmp_unlock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_off.png")), wxBITMAP_TYPE_PNG); +// m_bmp_lock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_on.png")), wxBITMAP_TYPE_PNG); +// m_bmp_lock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_off.png")), wxBITMAP_TYPE_PNG); +// m_bmp_unlock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_on.png")), wxBITMAP_TYPE_PNG); +// m_bmp_unlock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_off.png")), wxBITMAP_TYPE_PNG); + + m_bmp_lock_on = create_scaled_bitmap("one_layer_lock_on.png"); + m_bmp_lock_off = create_scaled_bitmap("one_layer_lock_off.png"); + m_bmp_unlock_on = create_scaled_bitmap("one_layer_unlock_on.png"); + m_bmp_unlock_off = create_scaled_bitmap("one_layer_unlock_off.png"); + #ifdef __WXMSW__ SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -2305,15 +2341,16 @@ PrusaModeButton::PrusaModeButton( wxWindow *parent, wxWindowID id, const wxString& mode/* = wxEmptyString*/, const wxBitmap& bmp_on/* = wxNullBitmap*/, - const wxPoint& pos/* = wxDefaultPosition*/, - const wxSize& size/* = wxDefaultSize*/) : - wxButton(parent, id, mode, pos, size, wxBU_EXACTFIT | wxNO_BORDER), + const wxSize& size/* = wxDefaultSize*/, + const wxPoint& pos/* = wxDefaultPosition*/) : + wxButton(parent, id, mode, pos, size, /*wxBU_EXACTFIT | */wxNO_BORDER), m_bmp_on(bmp_on) { #ifdef __WXMSW__ SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); #endif // __WXMSW__ - m_bmp_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("mode_off_sq.png")), wxBITMAP_TYPE_PNG); +// m_bmp_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("mode_off_sq.png")), wxBITMAP_TYPE_PNG); + m_bmp_off = create_scaled_bitmap("mode_off_sq.png"); SetBitmap(m_bmp_on); @@ -2339,8 +2376,8 @@ void PrusaModeButton::SetState(const bool state) void PrusaModeButton::focus_button(const bool focus) { - const wxBitmap& bmp = focus ? m_bmp_on : m_bmp_off; - SetBitmap(bmp); +// const wxBitmap& bmp = focus ? m_bmp_on : m_bmp_off; +// SetBitmap(bmp); const wxFont& new_font = focus ? Slic3r::GUI::wxGetApp().bold_font() : Slic3r::GUI::wxGetApp().small_font(); SetFont(new_font); @@ -2353,20 +2390,25 @@ void PrusaModeButton::focus_button(const bool focus) // PrusaModeSizer // ---------------------------------------------------------------------------- -PrusaModeSizer::PrusaModeSizer(wxWindow *parent) : - wxFlexGridSizer(3, 0, 5) +PrusaModeSizer::PrusaModeSizer(wxWindow *parent, int hgap/* = 10*/) : + wxFlexGridSizer(3, 0, hgap) { SetFlexibleDirection(wxHORIZONTAL); - const wxBitmap bmp_simple_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("mode_simple_sq.png")), wxBITMAP_TYPE_PNG); - const wxBitmap bmp_advanced_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("mode_middle_sq.png")), wxBITMAP_TYPE_PNG); - const wxBitmap bmp_expert_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("mode_expert_sq.png")), wxBITMAP_TYPE_PNG); - - mode_btns.reserve(3); + std::vector> buttons = { + {_(L("Simple")), create_scaled_bitmap("mode_simple_sq.png")}, + {_(L("Advanced")), create_scaled_bitmap("mode_middle_sq.png")}, + {_(L("Expert")), create_scaled_bitmap("mode_expert_sq.png")} + }; - mode_btns.push_back(new PrusaModeButton(parent, wxID_ANY, "Simple", bmp_simple_on)); - mode_btns.push_back(new PrusaModeButton(parent, wxID_ANY, "Advanced", bmp_advanced_on)); - mode_btns.push_back(new PrusaModeButton(parent, wxID_ANY, "Expert", bmp_expert_on)); + mode_btns.reserve(3); + for (const auto& button : buttons) { + int x, y; + parent->GetTextExtent(button.first, &x, &y, nullptr, nullptr, &Slic3r::GUI::wxGetApp().bold_font()); + const wxSize size = wxSize(x + button.second.GetWidth() + Slic3r::GUI::wxGetApp().em_unit(), + y + Slic3r::GUI::wxGetApp().em_unit()); + mode_btns.push_back(new PrusaModeButton(parent, wxID_ANY, button.first, button.second, size)); + } for (auto btn : mode_btns) { diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index f124750a12..1a1fe0f45d 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -16,6 +16,10 @@ #include #include +namespace Slic3r { + enum class ModelVolumeType : int; +}; + wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler = nullptr); wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, @@ -23,6 +27,8 @@ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxString& string, const wxString& description, const std::string& icon = ""); +wxBitmap create_scaled_bitmap(const std::string& bmp_name); + class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup { static const unsigned int DefaultWidth; @@ -446,7 +452,7 @@ public: wxDataViewItem Add(const wxString &name, const int extruder); wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item, const wxString &name, - const int volume_type, + const Slic3r::ModelVolumeType volume_type, const int extruder = 0, const bool create_frst_child = true); wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); @@ -514,7 +520,7 @@ public: void UpdateSettingsDigest(const wxDataViewItem &item, const std::vector& categories); void SetVolumeBitmaps(const std::vector& volume_bmps) { m_volume_bmps = volume_bmps; } - void SetVolumeType(const wxDataViewItem &item, const int type); + void SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } }; @@ -882,8 +888,8 @@ public: wxWindowID id, const wxString& mode = wxEmptyString, const wxBitmap& bmp_on = wxNullBitmap, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize); + const wxSize& size = wxDefaultSize, + const wxPoint& pos = wxDefaultPosition); ~PrusaModeButton() {} void OnButton(wxCommandEvent& event); @@ -911,7 +917,7 @@ private: class PrusaModeSizer : public wxFlexGridSizer { public: - PrusaModeSizer( wxWindow *parent); + PrusaModeSizer( wxWindow *parent, int hgap = 10); ~PrusaModeSizer() {} void SetMode(const /*ConfigOptionMode*/int mode); diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index d9db07a455..02bbc087e1 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -244,7 +244,7 @@ void Http::priv::http_perform() ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, static_cast(this)); #endif - ::curl_easy_setopt(curl, CURLOPT_VERBOSE, get_logging_level() >= 4); + ::curl_easy_setopt(curl, CURLOPT_VERBOSE, get_logging_level() >= 5); if (headerlist != nullptr) { ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index a22d463fb7..6924f86de4 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -13,7 +13,6 @@ #include #include -#include #include #include "libslic3r/libslic3r.h" @@ -90,9 +89,25 @@ struct Updates std::vector updates; }; +static Semver get_slic3r_version() +{ + auto res = Semver::parse(SLIC3R_VERSION); + + if (! res) { + const char *error = "Could not parse Slic3r version string: " SLIC3R_VERSION; + BOOST_LOG_TRIVIAL(error) << error; + throw std::runtime_error(error); + } + + return *res; +} + +wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent); + struct PresetUpdater::priv { + const Semver ver_slic3r; std::vector index_db; bool enabled_version_check; @@ -122,12 +137,13 @@ struct PresetUpdater::priv static void copy_file(const fs::path &from, const fs::path &to); }; -PresetUpdater::priv::priv() : - 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) +PresetUpdater::priv::priv() + : ver_slic3r(get_slic3r_version()) + , 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::wxGetApp().app_config); check_install_indices(); @@ -209,11 +225,10 @@ void PresetUpdater::priv::sync_version() const .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); - GUI::wxGetApp().app_config->set("version_online", body); - GUI::wxGetApp().app_config->save(); + + wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE); + evt->SetString(GUI::from_u8(body)); + GUI::wxGetApp().QueueEvent(evt); }) .perform_sync(); } @@ -260,7 +275,7 @@ void PresetUpdater::priv::sync_config(const std::set vendors) continue; } if (new_index.version() < index.version()) { - BOOST_LOG_TRIVIAL(error) << boost::format("The downloaded index %1% for vendor %2% is older than the active one. Ignoring the downloaded index.") % idx_path_temp % vendor.name; + BOOST_LOG_TRIVIAL(warning) << boost::format("The downloaded index %1% for vendor %2% is older than the active one. Ignoring the downloaded index.") % idx_path_temp % vendor.name; continue; } Slic3r::rename_file(idx_path_temp, idx_path); @@ -275,6 +290,7 @@ void PresetUpdater::priv::sync_config(const std::set vendors) 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("Got index for vendor: %1%: current version: %2%, recommended version: %3%") @@ -337,13 +353,6 @@ Updates PresetUpdater::priv::get_config_updates() const // 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()) { - auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str(); - BOOST_LOG_TRIVIAL(error) << message; - throw std::runtime_error(message); - } - // Getting a recommended version from the latest index, wich may have been downloaded // from the internet, or installed / updated from the installation resources. const auto recommended = idx.recommended(); @@ -351,15 +360,24 @@ Updates PresetUpdater::priv::get_config_updates() const 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%") + const auto ver_current = idx.find(vp.config_version); + const bool ver_current_found = ver_current != idx.end(); + if (! ver_current_found) { + auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str(); + BOOST_LOG_TRIVIAL(error) << message; + GUI::show_error(nullptr, GUI::from_u8(message)); + } + + BOOST_LOG_TRIVIAL(debug) << boost::format("Vendor: %1%, version installed: %2%%3%, version cached: %4%") % vp.name - % ver_current->config_version.to_string() + % vp.config_version.to_string() + % (ver_current_found ? "" : " (not found in index!)") % recommended->config_version.to_string(); - if (! ver_current->is_current_slic3r_supported()) { + if (ver_current_found && !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) { + } else if (recommended->config_version > vp.config_version) { // Config bundle update situation // Check if the update is already present in a snapshot @@ -528,18 +546,14 @@ void PresetUpdater::slic3r_update_notify() } auto* app_config = GUI::wxGetApp().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); + if (*ver_online > p->ver_slic3r && (! ver_online_seen || *ver_online_seen < *ver_online)) { + GUI::MsgUpdateSlic3r notification(p->ver_slic3r, *ver_online); notification.ShowModal(); if (notification.disable_version_check()) { app_config->set("version_check", "0"); diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index 451e8b2cf5..4b20c18e31 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace Slic3r { @@ -37,6 +39,8 @@ private: std::unique_ptr p; }; +wxDECLARE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent); + } #endif diff --git a/t/fill.t b/t/fill.t index 5cbd568ddd..88cc35801f 100644 --- a/t/fill.t +++ b/t/fill.t @@ -166,7 +166,8 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { my $config = Slic3r::Config::new_from_defaults; $config->set('nozzle_diameter', [0.4,0.4,0.4,0.4]); $config->set('fill_pattern', $pattern); - $config->set('external_fill_pattern', $pattern); + $config->set('top_fill_pattern', $pattern); + $config->set('bottom_fill_pattern', $pattern); $config->set('perimeters', 1); $config->set('skirts', 0); $config->set('fill_density', 20); diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 8f1d88c741..a1c8890ef5 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -258,17 +258,17 @@ ModelMaterial::attributes() bool modifier() %code%{ RETVAL = THIS->is_modifier(); %}; void set_modifier(bool modifier) - %code%{ THIS->set_type(modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); %}; + %code%{ THIS->set_type(modifier ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART); %}; bool model_part() %code%{ RETVAL = THIS->is_model_part(); %}; bool support_enforcer() %code%{ RETVAL = THIS->is_support_enforcer(); %}; void set_support_enforcer() - %code%{ THIS->set_type(ModelVolume::SUPPORT_ENFORCER); %}; + %code%{ THIS->set_type(ModelVolumeType::SUPPORT_ENFORCER); %}; bool support_blocker() %code%{ RETVAL = THIS->is_support_blocker(); %}; void set_support_blocker() - %code%{ THIS->set_type(ModelVolume::SUPPORT_BLOCKER); %}; + %code%{ THIS->set_type(ModelVolumeType::SUPPORT_BLOCKER); %}; size_t split(unsigned int max_extruders); };