Merge branch 'master' into wipe_tower_improvements

This commit is contained in:
Lukas Matena 2018-03-09 15:10:15 +01:00
commit 1c6fa6660e
79 changed files with 34645 additions and 10116 deletions

View file

@ -42,6 +42,21 @@ message("PATH: $ENV{PATH}")
message("PERL5LIB: $ENV{PERL5LIB}") message("PERL5LIB: $ENV{PERL5LIB}")
find_package(Perl REQUIRED) find_package(Perl REQUIRED)
# CMAKE_PREFIX_PATH is used to point CMake to the remaining dependencies (Boost, TBB, ...)
# We pick it from environment if it is not defined in another way
if(NOT DEFINED CMAKE_PREFIX_PATH)
if(DEFINED ENV{CMAKE_PREFIX_PATH})
set(CMAKE_PREFIX_PATH "$ENV{CMAKE_PREFIX_PATH}")
endif()
endif()
if (MSVC)
# By default the startup project in MSVC is the 'ALL_BUILD' cmake-created project,
# but we want 'slic3r' as the startup one because debugging run command is associated with it.
# (Unfortunatelly it cannot be associated with ALL_BUILD using CMake.)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT slic3r)
endif ()
add_subdirectory(xs) add_subdirectory(xs)
enable_testing () enable_testing ()

View file

@ -0,0 +1,59 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#.rst:
# FindCURL
# --------
#
# Find curl
#
# Find the native CURL headers and libraries.
#
# ::
#
# CURL_INCLUDE_DIRS - where to find curl/curl.h, etc.
# CURL_LIBRARIES - List of libraries when using curl.
# CURL_FOUND - True if curl found.
# CURL_VERSION_STRING - the version of curl found (since CMake 2.8.8)
# Look for the header file.
find_path(CURL_INCLUDE_DIR NAMES curl/curl.h)
mark_as_advanced(CURL_INCLUDE_DIR)
# Look for the library (sorted from most current/relevant entry to least).
find_library(CURL_LIBRARY NAMES
curl
# Windows MSVC Makefile:
libcurl_a
# Windows MSVC prebuilts:
curllib
libcurl_imp
curllib_static
# Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip):
libcurl
)
mark_as_advanced(CURL_LIBRARY)
if(CURL_INCLUDE_DIR)
foreach(_curl_version_header curlver.h curl.h)
if(EXISTS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}")
file(STRINGS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}" curl_version_str REGEX "^#define[\t ]+LIBCURL_VERSION[\t ]+\".*\"")
string(REGEX REPLACE "^#define[\t ]+LIBCURL_VERSION[\t ]+\"([^\"]*)\".*" "\\1" CURL_VERSION_STRING "${curl_version_str}")
unset(curl_version_str)
break()
endif()
endforeach()
endif()
find_package_handle_standard_args(CURL
REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR
VERSION_VAR CURL_VERSION_STRING)
if(CURL_FOUND)
set(CURL_LIBRARIES ${CURL_LIBRARY})
set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR})
message(STATUS " Curl libraries: = ${CURL_LIBRARIES}")
message(STATUS " Curl include dirs: = ${CURL_INCLUDE_DIRS}")
endif()

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<LocalDebuggerCommand>C:\wperl64d\bin\perl.exe</LocalDebuggerCommand>
<LocalDebuggerCommandArguments>slic3r.pl</LocalDebuggerCommandArguments>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
<LocalDebuggerWorkingDirectory>..\..</LocalDebuggerWorkingDirectory>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets">
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<ExecutablePath>$(VC_ExecutablePath_x64);$(WindowsSDK_ExecutablePath);$(VS_ExecutablePath);$(MSBuild_ExecutablePath);$(FxCopDir);$(PATH);c:\wperl64d\bin\;</ExecutablePath>
</PropertyGroup>
<ItemDefinitionGroup />
<ItemGroup />
</Project>

View file

@ -0,0 +1,2 @@
# Building Slic3r PE on Linux/UNIX

View file

@ -0,0 +1,86 @@
# Building Slic3r PE on Microsoft Windows
The currently supported way of building Slic3r PE on Windows is with MS Visual Studio 2013
using our Perl binary distribution (compiled from official Perl sources).
You can use the free [Visual Studio 2013 Community Edition](https://www.visualstudio.com/vs/older-downloads/).
Other setups (such as mingw + Strawberry Perl) _may_ work, but we cannot guarantee this will work
and cannot provide guidance.
### Geting the dependencies
First, download and upnack our Perl + wxWidgets binary distribution:
- 32 bit, release mode: [wperl32-5.24.0-2018-03-02.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=wperl32-5.24.0-2018-03-02.7z)
- 64 bit, release mode: [wperl64-5.24.0-2018-03-02.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=wperl64-5.24.0-2018-03-02.7z)
- 64 bit, release mode + debug symbols: [wperl64d-5.24.0-2018-03-02.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=wperl64d-5.24.0-2018-03-02.7z)
It is recommended to unpack this package into `C:\`.
Apart from wxWidgets and Perl, you will also need additional dependencies:
- Boost
- Intel TBB
- libcurl
We have prepared a binary package of the listed libraries:
- 32 bit: [slic3r-destdir-32.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=slic3r-destdir-32.7z)
- 64 bit: [slic3r-destdir-64.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=slic3r-destdir-64.7z)
It is recommended you unpack this package into `C:\local\` as the environment
setup script expects it there.
Alternatively you can also compile the additional dependencies yourself.
There is a [powershell script](./deps-build/windows/slic3r-makedeps.ps1) which automates this process.
### Building Slic3r PE
Once the dependencies are set up in their respective locations,
go to the `wperl*` directory extracted earlier and launch the `cmdline.lnk` file
which opens a command line prompt with appropriate environment variables set up.
In this command line, `cd` into the directory with Slic3r sources
and use these commands to build the Slic3r from the command line:
perl Build.PL
perl Build.PL --gui
mkdir build
cd build
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
nmake
ctest --verbose # TODO: ???
cd ..
perl slic3r.pl
The above commands use `nmake` Makefiles.
You may also build Slic3r PE with other build tools:
### Building with Visual Studio
To build, lanuch and/or debug Slic3r PE with Visual Studio (64 bits), replace the `cmake` command with:
cmake .. -G "Visual Studio 12 Win64" -DCMAKE_CONFIGURATION_TYPES=Release;RelWithDebInfo || exit /b
For the 32-bit variant, use:
cmake .. -G "Visual Studio 12" -DCMAKE_CONFIGURATION_TYPES=Release;RelWithDebInfo || exit /b
After `cmake` has finished, go to the `Slic3r\build` directory and open the `Slic3r.sln` solution file.
This should open Visual Studio and load all the Slic3r solution containing all the projects.
Make sure you use Visual Studio 2013 to open the solution.
You can then use the usual Visual Studio controls to build Slic3r.
If you want to run or debug Slic3r from within Visual Studio, make sure the `slic3r` project is activated.
There are multiple projects in the Slic3r solution, but only the `slic3r` project is configured with the right
commands to run Slic3r.
### Building with ninja
To use [Ninja](TODO), replace the `cmake` and `nmake` commands with:
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release
ninja

76
doc/Localization_guide.md Normal file
View file

@ -0,0 +1,76 @@
# Localization and translation guide
The purpose of this guide is to describe how to contribute to the Slic3rPE translations. We use GNUgettext for extracting string resources from the project and PoEdit for editing translations.
Those are possible to download here:
- https://sourceforge.net/directory/os:windows/?q=gnu+gettext GNUgettext package contains a set of tools to extract strings from the source code and to create the translation Catalog.
- https://poedit.net PoEdit provides good interface for the translators.
After GNUgettext is installed it is recommended to add the path to gettext/bin to PATH variable.
Full manual for GNUgettext you can see here: http://www.gnu.org/software/gettext/manual/gettext.html
### Scenario 1. How do I add a translation or fix the existing translation
1. Get PO-file from corresponding folder here:
https://github.com/prusa3d/Slic3r/tree/master/resources/localization
2. Open this file in PoEdit as "Edit a translation"
3. Apply your corrections to translation
4. Push changed Slic3rPE.po and Slic3rPE.mo (will create automatically after saving of Slic3r.po in PoEdit) back to to the enter folder.
### Scenario 2. How do I add a new language support
1. Get file Slic3rPE.pot here :
https://github.com/prusa3d/Slic3r/tree/master/resources/localization
2. Open it in PoEdit for "Create new translation"
3. Select Translation Language (for example French).
4. As a result you will have fr.po - the file contaning translation to French.
Notice. When the transtation is complete you need to:
- Rename the file to Slic3rPE.po
- Click "Save file" button. Slic3rPE.mo will be created immediatly
- Both Slic3rPE.po and Slic3rPE.mo have to be saved here:
https://github.com/prusa3d/Slic3r/tree/master/resources/localization/fr
( name of folder "fr" means "French" - the translation language).
### Scenario 3. How do I add a new text resource when implementing a feature to Slic3rPE
Each string resource in Slic3rPE available for translation needs to be explicitly marked using L() macro like this:
```C++
auto msg = L("This message to be localized")
```
To get translated text use one of needed macro/function (`_(s)`, `_CHB(s)` or `L_str(s)` ).
If you add new file resourse, add it to list of files contaned macro `L()`
### Scenario 4. How do I use GNUgettext to localize my own application taking Slic3rPE as an example
1. For conviniance create list of files with this macro `L(s)`. We have
https://github.com/prusa3d/Slic3r/tree/master/resources/localization/list.txt.
2. Create template file(*.POT) with GNUgettext command:
```
xgettext --keyword=L --from-code=UTF-8 --debug -o Slic3rPE.pot -f list.txt
```
Use flag `--from-code=UTF-8` to specify that the source strings are in UTF-8 encoding
Use flag `--debug` to correctly extract formated strings(used %d, %s etc.)
3. Create PO- and MO-files for your project as described above.
4. To merge old PO-file with strings from creaded new POT-file use command:
```
msgmerge -N -o new.po old.po new.pot
```
Use option `-N` to not using fuzzy matching when an exact match is not found.
5. To concatenate old PO-file with strings from new PO-file use command:
```
msgcat -o new.po old.po
```
6. Create an English translation catalog with command:
```
msgen -o new.po old.po
```
Notice, in this Catalog it will be totally same strings for initial text and translated.
When you have Catalog to translation open POT or PO file in PoEdit and start to translation.
It's very important to keep attention to every gaps and punctuation. Especially with
formated strings. (used %d, %s etc.)

View file

@ -0,0 +1,132 @@
#
# This makefile downloads, configures and builds Slic3r PE dependencies for Unix.
# (That is, all dependencies except perl + wxWidgets.)
# The libraries are installed in DESTDIR, which you can customize like so:
#
# DESTDIR=foo/bar make
#
# The default DESTDIR is ~/slic3r-destdir
# If the DESTDIR doesn't exits, the makefile tries to create it
#
# To pass the DESTDIR path along to cmake, set the use CMAKE_PREFIX_PATH variable
# and set it to $DESTDIR/usr/local
#
# You can also customize the NPROC variable in the same way to configure the number
# of cores the build process uses. By default this is set to what the `nproc` command says.
#
DESTDIR ?= $(HOME)/slic3r-destdir
NPROC ?= $(shell nproc)
BOOST = boost_1_66_0
TBB_SHA = a0dc9bf76d0120f917b641ed095360448cabc85b
TBB = tbb-$(TBB_SHA)
OPENSSL = openssl-OpenSSL_1_1_0g
CURL = curl-7.58.0
.PHONY: all destdir boost libcurl libopenssl libtbb
all: destdir boost libtbb libcurl
@echo
@echo "All done!"
@echo
destdir:
mkdir -p $(DESTDIR)
boost: $(BOOST).tar.gz
tar -zxvf $(BOOST).tar.gz
cd $(BOOST) && ./bootstrap.sh --with-libraries=system,filesystem,thread,log,locale,regex --prefix=$(DESTDIR)/usr/local
cd $(BOOST) && ./b2 \
-j $(NPROC) \
link=static \
variant=release \
threading=multi \
boost.locale.icu=off \
cxxflags=-fPIC cflags=-fPIC \
install
$(BOOST).tar.gz:
curl -L -o $@ https://dl.bintray.com/boostorg/release/1.66.0/source/$@
libtbb: $(TBB).tar.gz
tar -zxvf $(TBB).tar.gz
mkdir -p $(TBB)/mybuild
cd $(TBB)/mybuild && cmake .. -DTBB_BUILD_SHARED=OFF -DTBB_BUILD_TESTS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON
$(MAKE) -C $(TBB)/mybuild -j$(NPROC)
$(MAKE) -C $(TBB)/mybuild install DESTDIR=$(DESTDIR)
$(TBB).tar.gz:
curl -L -o $@ https://github.com/wjakob/tbb/archive/$(TBB_SHA).tar.gz
libopenssl: $(OPENSSL).tar.gz
tar -zxvf $(OPENSSL).tar.gz
cd $(OPENSSL) && ./config --openssldir=/etc/ssl shared no-ssl3-method no-dynamic-engine '-Wa,--noexecstack'
make -C $(OPENSSL) depend
make -C $(OPENSSL) -j$(NPROC)
make -C $(OPENSSL) install DESTDIR=$(DESTDIR)
$(OPENSSL).tar.gz:
curl -L -o $@ 'https://github.com/openssl/openssl/archive/OpenSSL_1_1_0g.tar.gz'
libcurl: libopenssl $(CURL).tar.gz
tar -zxvf $(CURL).tar.gz
# XXX: disable shared?
# Setting PKG_CONFIG_PATH should make libcurl find our previously built openssl
cd $(CURL) && PKG_CONFIG_PATH=$(DESTDIR)/usr/local/lib/pkgconfig ./configure \
--enable-static \
--enable-shared \
--with-pic \
--enable-ipv6 \
--enable-versioned-symbols \
--enable-threaded-resolver \
--with-random=/dev/urandom \
--with-ca-bundle=/etc/ssl/certs/ca-certificates.crt \
--disable-ldap \
--disable-ldaps \
--disable-manual \
--disable-rtsp \
--disable-dict \
--disable-telnet \
--disable-pop3 \
--disable-imap \
--disable-smb \
--disable-smtp \
--disable-gopher \
--disable-crypto-auth \
--without-gssapi \
--without-libpsl \
--without-libidn2 \
--without-gnutls \
--without-polarssl \
--without-mbedtls \
--without-cyassl \
--without-nss \
--without-axtls \
--without-brotli \
--without-libmetalink \
--without-libssh \
--without-libssh2 \
--without-librtmp \
--without-nghttp2 \
--without-zsh-functions-dir
$(MAKE) -C $(CURL) -j$(NPROC)
$(MAKE) -C $(CURL) install DESTDIR=$(DESTDIR)
$(CURL).tar.gz:
curl -L -o $@ https://curl.haxx.se/download/$@
clean:
rm -rf $(BOOST) $(BOOST).tar.gz $(TBB) $(TBB).tar.gz $(OPENSSL) $(OPENSSL).tar.gz $(CURL) $(CURL).tar.gz

View file

@ -0,0 +1,141 @@
#!powershell
#
# This script downloads, configures and builds Slic3r PE dependencies for Unix.
# (That is, all dependencies except perl + wxWidgets.)
#
# To use this script, launch the Visual Studio command line,
# `cd` into the directory containing this script and use this command:
#
# powershell .\slic3r-makedeps.ps1
#
# The dependencies will be downloaded and unpacked into the current dir.
# This script WILL NOT try to guess the build architecture (64 vs 32 bits),
# it will by default build the 64-bit variant. To build the 32-bit variant, use:
#
# powershell .\slic3r-makedeps.ps1 -b32
#
# Built libraries are installed into $destdir,
# which by default is C:\local\slic3r-destdir-$bits
# You can customize the $destdir using:
#
# powershell .\slic3r-makedeps.ps1 -destdir C:\foo\bar
#
# To pass the $destdir path along to cmake, set the use CMAKE_PREFIX_PATH variable
# and set it to $destdir\usr\local
#
# Script requirements: PowerShell 3.0, .NET 4.5
#
param(
[switch]$b32 = $false,
[string]$destdir = ""
)
if ($destdir -eq "") {
$destdir = "C:\local\slic3r-destdir-" + ('32', '64')[!$b32]
}
$BOOST = 'boost_1_63_0'
$CURL = 'curl-7.28.0'
$TBB_SHA = 'a0dc9bf76d0120f917b641ed095360448cabc85b'
$TBB = "tbb-$TBB_SHA"
try
{
# Set up various settings and utilities:
[Environment]::CurrentDirectory = Get-Location
$NPROC = (Get-WmiObject -class Win32_processor).NumberOfLogicalProcessors
Add-Type -A System.IO.Compression.FileSystem
# This fxies SSL/TLS errors, credit goes to Ansible; see their `win_get_url.ps1` file
$security_protcols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault
if ([Net.SecurityProtocolType].GetMember('Tls11').Count -gt 0) {
$security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls11
}
if ([Net.SecurityProtocolType].GetMember('Tls12').Count -gt 0) {
$security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls12
}
[Net.ServicePointManager]::SecurityProtocol = $security_protcols
$webclient = New-Object System.Net.WebClient
# Ensure DESTDIR exists:
mkdir $destdir -ea 0
mkdir "$destdir\usr\local" -ea 0
# Download sources:
echo 'Downloading sources ...'
if (!(Test-Path "$BOOST.zip")) { $webclient.DownloadFile("https://dl.bintray.com/boostorg/release/1.63.0/source/$BOOST.zip", "$BOOST.zip") }
if (!(Test-Path "$TBB.zip")) { $webclient.DownloadFile("https://github.com/wjakob/tbb/archive/$TBB_SHA.zip", "$TBB.zip") }
if (!(Test-Path "$CURL.zip")) { $webclient.DownloadFile("https://curl.haxx.se/download/$CURL.zip", ".\$CURL.zip") }
# Unpack sources:
echo 'Unpacking ...'
if (!(Test-Path $BOOST)) { [IO.Compression.ZipFile]::ExtractToDirectory("$BOOST.zip", '.') }
if (!(Test-Path $TBB)) { [IO.Compression.ZipFile]::ExtractToDirectory("$TBB.zip", '.') }
if (!(Test-Path $CURL)) { [IO.Compression.ZipFile]::ExtractToDirectory("$CURL.zip", '.') }
# Build libraries:
echo 'Building ...'
# Build boost
pushd "$BOOST"
.\bootstrap
$adr_mode = ('32', '64')[!$b32]
.\b2 `
-j "$NPROC" `
--with-system `
--with-filesystem `
--with-thread `
--with-log `
--with-locale `
--with-regex `
"--prefix=$destdir/usr/local" `
"address-model=$adr_mode" `
toolset=msvc-12.0 `
link=static `
variant=release `
threading=multi `
boost.locale.icu=off `
install
popd
# Build TBB
pushd "$TBB"
mkdir 'mybuild' -ea 0
cd 'mybuild'
$generator = ('Visual Studio 12', 'Visual Studio 12 Win64')[!$b32]
cmake .. `
-G "$generator" `
-DCMAKE_CONFIGURATION_TYPES=Release `
-DTBB_BUILD_SHARED=OFF `
-DTBB_BUILD_TESTS=OFF "-DCMAKE_INSTALL_PREFIX:PATH=$destdir\usr\local"
msbuild /P:Configuration=Release INSTALL.vcxproj
popd
# Build libcurl:
pushd "$CURL\winbuild"
$machine = ("x86", "x64")[!$b32]
nmake /f Makefile.vc mode=static VC=12 GEN_PDB=yes DEBUG=no "MACHINE=$machine"
Copy-Item -R -Force ..\builds\libcurl-*-winssl\include\* "$destdir\usr\local\include\"
Copy-Item -R -Force ..\builds\libcurl-*-winssl\lib\* "$destdir\usr\local\lib\"
popd
echo ""
echo "All done!"
echo ""
}
catch [Exception]
{
# This prints errors in a verbose manner
echo $_.Exception|format-list -force
}

View file

@ -26,12 +26,13 @@ use Slic3r::GUI::Plater::ObjectCutDialog;
use Slic3r::GUI::Plater::ObjectSettingsDialog; use Slic3r::GUI::Plater::ObjectSettingsDialog;
use Slic3r::GUI::Plater::LambdaObjectDialog; use Slic3r::GUI::Plater::LambdaObjectDialog;
use Slic3r::GUI::Plater::OverrideSettingsPanel; use Slic3r::GUI::Plater::OverrideSettingsPanel;
use Slic3r::GUI::Preferences;
use Slic3r::GUI::ProgressStatusBar; use Slic3r::GUI::ProgressStatusBar;
use Slic3r::GUI::OptionsGroup; use Slic3r::GUI::OptionsGroup;
use Slic3r::GUI::OptionsGroup::Field; use Slic3r::GUI::OptionsGroup::Field;
use Slic3r::GUI::SystemInfo; use Slic3r::GUI::SystemInfo;
use Wx::Locale gettext => 'L';
our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1"; our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1";
our $have_LWP = eval "use LWP::UserAgent; 1"; our $have_LWP = eval "use LWP::UserAgent; 1";
@ -40,10 +41,10 @@ use Wx::Event qw(EVT_IDLE EVT_COMMAND EVT_MENU);
use base 'Wx::App'; use base 'Wx::App';
use constant FILE_WILDCARDS => { use constant FILE_WILDCARDS => {
known => 'Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA', 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', stl => 'STL files (*.stl)|*.stl;*.STL',
obj => 'OBJ files (*.obj)|*.obj;*.OBJ', obj => 'OBJ files (*.obj)|*.obj;*.OBJ',
amf => 'AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML', amf => 'AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML',
threemf => '3MF files (*.3mf)|*.3mf;*.3MF', threemf => '3MF files (*.3mf)|*.3mf;*.3MF',
prusa => 'Prusa Control files (*.prusa)|*.prusa;*.PRUSA', prusa => 'Prusa Control files (*.prusa)|*.prusa;*.PRUSA',
ini => 'INI files *.ini|*.ini;*.INI', ini => 'INI files *.ini|*.ini;*.INI',
@ -69,7 +70,9 @@ our $grey = Wx::Colour->new(200,200,200);
# Events to be sent from a C++ menu implementation: # Events to be sent from a C++ menu implementation:
# 1) To inform about a change of the application language. # 1) To inform about a change of the application language.
our $LANGUAGE_CHANGE_EVENT = Wx::NewEventType; our $LANGUAGE_CHANGE_EVENT = Wx::NewEventType;
# 2) To inform about a change of Preferences.
our $PREFERENCES_EVENT = Wx::NewEventType;
sub OnInit { sub OnInit {
my ($self) = @_; my ($self) = @_;
@ -84,12 +87,11 @@ sub OnInit {
# Mac: "~/Library/Application Support/Slic3r" # Mac: "~/Library/Application Support/Slic3r"
Slic3r::set_data_dir($datadir || Wx::StandardPaths::Get->GetUserDataDir); Slic3r::set_data_dir($datadir || Wx::StandardPaths::Get->GetUserDataDir);
Slic3r::GUI::set_wxapp($self); Slic3r::GUI::set_wxapp($self);
Slic3r::GUI::load_language();
$self->{notifier} = Slic3r::GUI::Notifier->new; $self->{notifier} = Slic3r::GUI::Notifier->new;
$self->{app_config} = Slic3r::GUI::AppConfig->new; $self->{app_config} = Slic3r::GUI::AppConfig->new;
$self->{preset_bundle} = Slic3r::GUI::PresetBundle->new; $self->{preset_bundle} = Slic3r::GUI::PresetBundle->new;
# just checking for existence of Slic3r::data_dir is not enough: it may be an empty directory # just checking for existence of Slic3r::data_dir is not enough: it may be an empty directory
# supplied as argument to --datadir; in that case we should still run the wizard # supplied as argument to --datadir; in that case we should still run the wizard
eval { $self->{preset_bundle}->setup_directories() }; eval { $self->{preset_bundle}->setup_directories() };
@ -103,6 +105,9 @@ sub OnInit {
$self->{app_config}->set('version', $Slic3r::VERSION); $self->{app_config}->set('version', $Slic3r::VERSION);
$self->{app_config}->save; $self->{app_config}->save;
Slic3r::GUI::set_app_config($self->{app_config});
Slic3r::GUI::load_language();
# Suppress the '- default -' presets. # Suppress the '- default -' presets.
$self->{preset_bundle}->set_default_suppressed($self->{app_config}->get('no_defaults') ? 1 : 0); $self->{preset_bundle}->set_default_suppressed($self->{app_config}->get('no_defaults') ? 1 : 0);
eval { $self->{preset_bundle}->load_presets }; eval { $self->{preset_bundle}->load_presets };
@ -120,6 +125,7 @@ sub OnInit {
no_controller => $self->{app_config}->get('no_controller'), no_controller => $self->{app_config}->get('no_controller'),
no_plater => $no_plater, no_plater => $no_plater,
lang_ch_event => $LANGUAGE_CHANGE_EVENT, lang_ch_event => $LANGUAGE_CHANGE_EVENT,
preferences_event => $PREFERENCES_EVENT,
); );
$self->SetTopWindow($frame); $self->SetTopWindow($frame);
@ -144,6 +150,11 @@ sub OnInit {
EVT_COMMAND($self, -1, $LANGUAGE_CHANGE_EVENT, sub{ EVT_COMMAND($self, -1, $LANGUAGE_CHANGE_EVENT, sub{
$self->recreate_GUI; $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;
});
return 1; return 1;
} }
@ -156,6 +167,7 @@ sub recreate_GUI{
no_controller => $self->{app_config}->get('no_controller'), no_controller => $self->{app_config}->get('no_controller'),
no_plater => $no_plater, no_plater => $no_plater,
lang_ch_event => $LANGUAGE_CHANGE_EVENT, lang_ch_event => $LANGUAGE_CHANGE_EVENT,
preferences_event => $PREFERENCES_EVENT,
); );
if($topwindow) if($topwindow)
@ -270,8 +282,9 @@ sub update_ui_from_settings {
sub open_model { sub open_model {
my ($self, $window) = @_; my ($self, $window) = @_;
my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF/PRUSA):', 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, "", $self->{app_config}->get_last_dir, "",
MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) { if ($dialog->ShowModal != wxID_OK) {

View file

@ -1172,18 +1172,17 @@ sub Render {
glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0); glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
if ($self->enable_picking) { if ($self->enable_picking) {
# Render the object for picking.
# FIXME This cannot possibly work in a multi-sampled context as the color gets mangled by the anti-aliasing.
# Better to use software ray-casting on a bounding-box hierarchy.
glPushAttrib(GL_ENABLE_BIT);
glDisable(GL_MULTISAMPLE) if ($self->{can_multisample});
glDisable(GL_LIGHTING);
glDisable(GL_BLEND);
$self->draw_volumes(1);
glFlush();
glFinish();
if (my $pos = $self->_mouse_pos) { if (my $pos = $self->_mouse_pos) {
# Render the object for picking.
# FIXME This cannot possibly work in a multi-sampled context as the color gets mangled by the anti-aliasing.
# Better to use software ray-casting on a bounding-box hierarchy.
glPushAttrib(GL_ENABLE_BIT);
glDisable(GL_MULTISAMPLE) if ($self->{can_multisample});
glDisable(GL_LIGHTING);
glDisable(GL_BLEND);
$self->draw_volumes(1);
glPopAttrib();
glFlush();
my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ]; my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ];
my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256; my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256;
$self->_hover_volume_idx(undef); $self->_hover_volume_idx(undef);
@ -1199,11 +1198,8 @@ sub Render {
$self->on_hover->($volume_idx) if $self->on_hover; $self->on_hover->($volume_idx) if $self->on_hover;
} }
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
} }
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glFlush();
glFinish();
glPopAttrib();
} }
# draw fixed background # draw fixed background
@ -1337,9 +1333,6 @@ sub Render {
$self->draw_active_object_annotations; $self->draw_active_object_annotations;
$self->SwapBuffers(); $self->SwapBuffers();
# Calling glFinish has a performance penalty, but it seems to fix some OpenGL driver hang-up with extremely large scenes.
# glFinish();
} }
sub draw_volumes { sub draw_volumes {
@ -2051,4 +2044,11 @@ sub reset_legend_texture {
Slic3r::GUI::_3DScene::reset_legend_texture(); Slic3r::GUI::_3DScene::reset_legend_texture();
} }
sub get_current_print_zs {
my ($self) = @_;
my $count = $self->volumes->get_current_print_zs();
return $count;
}
1; 1;

View file

@ -14,6 +14,8 @@ use Wx qw(:frame :bitmap :id :misc :notebook :panel :sizer :menu :dialog :filedi
use Wx::Event qw(EVT_CLOSE EVT_COMMAND EVT_MENU EVT_NOTEBOOK_PAGE_CHANGED); use Wx::Event qw(EVT_CLOSE EVT_COMMAND EVT_MENU EVT_NOTEBOOK_PAGE_CHANGED);
use base 'Wx::Frame'; use base 'Wx::Frame';
use Wx::Locale gettext => 'L';
our $qs_last_input_file; our $qs_last_input_file;
our $qs_last_output_file; our $qs_last_output_file;
our $last_config; our $last_config;
@ -48,7 +50,8 @@ sub new {
$self->{no_plater} = $params{no_plater}; $self->{no_plater} = $params{no_plater};
$self->{loaded} = 0; $self->{loaded} = 0;
$self->{lang_ch_event} = $params{lang_ch_event}; $self->{lang_ch_event} = $params{lang_ch_event};
$self->{preferences_event} = $params{preferences_event};
# initialize tabpanel and menubar # initialize tabpanel and menubar
$self->_init_tabpanel; $self->_init_tabpanel;
$self->_init_menubar; $self->_init_menubar;
@ -60,7 +63,7 @@ sub new {
# initialize status bar # initialize status bar
$self->{statusbar} = Slic3r::GUI::ProgressStatusBar->new($self, -1); $self->{statusbar} = Slic3r::GUI::ProgressStatusBar->new($self, -1);
$self->{statusbar}->SetStatusText("Version $Slic3r::VERSION - Remember to check for updates at http://github.com/prusa3d/slic3r/releases"); $self->{statusbar}->SetStatusText(L("Version ").$Slic3r::VERSION.L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases"));
$self->SetStatusBar($self->{statusbar}); $self->SetStatusBar($self->{statusbar});
$self->{loaded} = 1; $self->{loaded} = 1;
@ -112,9 +115,9 @@ sub _init_tabpanel {
}); });
if (!$self->{no_plater}) { if (!$self->{no_plater}) {
$panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "Plater"); $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), L("Plater"));
if (!$self->{no_controller}) { if (!$self->{no_controller}) {
$panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), "Controller"); $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), L("Controller"));
} }
} }
@ -171,7 +174,6 @@ sub _init_tabpanel {
EVT_COMMAND($self, -1, $BUTTON_BROWSE_EVENT, sub { EVT_COMMAND($self, -1, $BUTTON_BROWSE_EVENT, sub {
my ($self, $event) = @_; my ($self, $event) = @_;
my $msg = $event->GetString; my $msg = $event->GetString;
print "BUTTON_BROWSE_EVENT: ", $msg, "\n";
# look for devices # look for devices
my $entries; my $entries;
@ -186,7 +188,7 @@ sub _init_tabpanel {
$tab->load_key_value('octoprint_host', $dlg->GetValue . ":" . $dlg->GetPort) $tab->load_key_value('octoprint_host', $dlg->GetValue . ":" . $dlg->GetPort)
if $dlg->ShowModal == wxID_OK; if $dlg->ShowModal == wxID_OK;
} else { } else {
Wx::MessageDialog->new($self, 'No Bonjour device found', 'Device Browser', wxOK | wxICON_INFORMATION)->ShowModal; Wx::MessageDialog->new($self, L('No Bonjour device found'), L('Device Browser'), wxOK | wxICON_INFORMATION)->ShowModal;
} }
}); });
# The following event is emited by the C++ Tab implementation , # The following event is emited by the C++ Tab implementation ,
@ -194,7 +196,6 @@ sub _init_tabpanel {
EVT_COMMAND($self, -1, $BUTTON_TEST_EVENT, sub { EVT_COMMAND($self, -1, $BUTTON_TEST_EVENT, sub {
my ($self, $event) = @_; my ($self, $event) = @_;
my $msg = $event->GetString; my $msg = $event->GetString;
print "BUTTON_TEST_EVENT: ", $msg, "\n";
my $ua = LWP::UserAgent->new; my $ua = LWP::UserAgent->new;
$ua->timeout(10); $ua->timeout(10);
@ -205,19 +206,19 @@ sub _init_tabpanel {
'X-Api-Key' => $config->octoprint_apikey, 'X-Api-Key' => $config->octoprint_apikey,
); );
if ($res->is_success) { if ($res->is_success) {
Slic3r::GUI::show_info($self, "Connection to OctoPrint works correctly.", "Success!"); Slic3r::GUI::show_info($self, L("Connection to OctoPrint works correctly."), _L("Success!"));
} else { } else {
Slic3r::GUI::show_error($self, Slic3r::GUI::show_error($self,
"I wasn't able to connect to OctoPrint (" . $res->status_line . "). " L("I wasn't able to connect to OctoPrint (") . $res->status_line .
. "Check hostname and OctoPrint version (at least 1.1.0 is required)."); L("). Check hostname and OctoPrint version (at least 1.1.0 is required)."));
} }
}); });
# A variable to inform C++ Tab implementation about disabling of Browse button # A variable to inform C++ Tab implementation about disabling of Browse button
$self->{is_disabled_button_browse} = (!eval "use Net::Bonjour; 1") ? 1 : 0 ; $self->{is_disabled_button_browse} = (!eval "use Net::Bonjour; 1") ? 1 : 0 ;
# A variable to inform C++ Tab implementation about user_agent # A variable to inform C++ Tab implementation about user_agent
$self->{is_user_agent} = (eval "use LWP::UserAgent; 1") ? 1 : 0 ; $self->{is_user_agent} = (eval "use LWP::UserAgent; 1") ? 1 : 0 ;
Slic3r::GUI::create_preset_tabs(wxTheApp->{preset_bundle}, wxTheApp->{app_config}, Slic3r::GUI::create_preset_tabs(wxTheApp->{preset_bundle}, $self->{no_controller},
$self->{no_controller}, $self->{is_disabled_button_browse}, $self->{is_disabled_button_browse},
$self->{is_user_agent}, $self->{is_user_agent},
$VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT, $VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT,
$BUTTON_BROWSE_EVENT, $BUTTON_TEST_EVENT); $BUTTON_BROWSE_EVENT, $BUTTON_TEST_EVENT);
@ -245,59 +246,60 @@ sub _init_menubar {
# File menu # File menu
my $fileMenu = Wx::Menu->new; my $fileMenu = Wx::Menu->new;
{ {
wxTheApp->append_menu_item($fileMenu, "Open STL/OBJ/AMF…\tCtrl+O", 'Open a model', sub { wxTheApp->append_menu_item($fileMenu, L("Open STL/OBJ/AMF…\tCtrl+O"), L('Open a model'), sub {
$self->{plater}->add if $self->{plater}; $self->{plater}->add if $self->{plater};
}, undef, undef); #'brick_add.png'); }, undef, undef); #'brick_add.png');
$self->_append_menu_item($fileMenu, "&Load Config…\tCtrl+L", 'Load exported configuration file', sub { $self->_append_menu_item($fileMenu, L("&Load Config…\tCtrl+L"), L('Load exported configuration file'), sub {
$self->load_config_file; $self->load_config_file;
}, undef, 'plugin_add.png'); }, undef, 'plugin_add.png');
$self->_append_menu_item($fileMenu, "&Export Config…\tCtrl+E", 'Export current configuration to file', sub { $self->_append_menu_item($fileMenu, L("&Export Config…\tCtrl+E"), L('Export current configuration to file'), sub {
$self->export_config; $self->export_config;
}, undef, 'plugin_go.png'); }, undef, 'plugin_go.png');
$self->_append_menu_item($fileMenu, "&Load Config Bundle…", 'Load presets from a bundle', sub { $self->_append_menu_item($fileMenu, L("&Load Config Bundle…"), L('Load presets from a bundle'), sub {
$self->load_configbundle; $self->load_configbundle;
}, undef, 'lorry_add.png'); }, undef, 'lorry_add.png');
$self->_append_menu_item($fileMenu, "&Export Config Bundle…", 'Export all presets to file', sub { $self->_append_menu_item($fileMenu, L("&Export Config Bundle…"), L('Export all presets to file'), sub {
$self->export_configbundle; $self->export_configbundle;
}, undef, 'lorry_go.png'); }, undef, 'lorry_go.png');
$fileMenu->AppendSeparator(); $fileMenu->AppendSeparator();
my $repeat; my $repeat;
$self->_append_menu_item($fileMenu, "Q&uick Slice…\tCtrl+U", 'Slice a file into a G-code', sub { $self->_append_menu_item($fileMenu, L("Q&uick Slice…\tCtrl+U"), L('Slice a file into a G-code'), sub {
wxTheApp->CallAfter(sub { wxTheApp->CallAfter(sub {
$self->quick_slice; $self->quick_slice;
$repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file); $repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file);
}); });
}, undef, 'cog_go.png'); }, undef, 'cog_go.png');
$self->_append_menu_item($fileMenu, "Quick Slice and Save &As…\tCtrl+Alt+U", 'Slice a file into a G-code, save as', sub { $self->_append_menu_item($fileMenu, L("Quick Slice and Save &As…\tCtrl+Alt+U"), L('Slice a file into a G-code, save as'), sub {
wxTheApp->CallAfter(sub { wxTheApp->CallAfter(sub {
$self->quick_slice(save_as => 1); $self->quick_slice(save_as => 1);
$repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file); $repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file);
}); });
}, undef, 'cog_go.png'); }, undef, 'cog_go.png');
$repeat = $self->_append_menu_item($fileMenu, "&Repeat Last Quick Slice\tCtrl+Shift+U", 'Repeat last quick slice', sub { $repeat = $self->_append_menu_item($fileMenu, L("&Repeat Last Quick Slice\tCtrl+Shift+U"), L('Repeat last quick slice'), sub {
wxTheApp->CallAfter(sub { wxTheApp->CallAfter(sub {
$self->quick_slice(reslice => 1); $self->quick_slice(reslice => 1);
}); });
}, undef, 'cog_go.png'); }, undef, 'cog_go.png');
$repeat->Enable(0); $repeat->Enable(0);
$fileMenu->AppendSeparator(); $fileMenu->AppendSeparator();
$self->_append_menu_item($fileMenu, "Slice to SV&G…\tCtrl+G", 'Slice file to a multi-layer SVG', sub { $self->_append_menu_item($fileMenu, L("Slice to SV&G…\tCtrl+G"), L('Slice file to a multi-layer SVG'), sub {
$self->quick_slice(save_as => 1, export_svg => 1); $self->quick_slice(save_as => 1, export_svg => 1);
}, undef, 'shape_handles.png'); }, undef, 'shape_handles.png');
$self->{menu_item_reslice_now} = $self->_append_menu_item( $self->{menu_item_reslice_now} = $self->_append_menu_item(
$fileMenu, "(&Re)Slice Now\tCtrl+S", 'Start new slicing process', $fileMenu, L("(&Re)Slice Now\tCtrl+S"), L('Start new slicing process'),
sub { $self->reslice_now; }, undef, 'shape_handles.png'); sub { $self->reslice_now; }, undef, 'shape_handles.png');
$fileMenu->AppendSeparator(); $fileMenu->AppendSeparator();
$self->_append_menu_item($fileMenu, "Repair STL file…", 'Automatically repair an STL file', sub { $self->_append_menu_item($fileMenu, L("Repair STL file…"), L('Automatically repair an STL file'), sub {
$self->repair_stl; $self->repair_stl;
}, undef, 'wrench.png'); }, undef, 'wrench.png');
$fileMenu->AppendSeparator(); $fileMenu->AppendSeparator();
# Cmd+, is standard on OS X - what about other operating systems? # Cmd+, is standard on OS X - what about other operating systems?
$self->_append_menu_item($fileMenu, "Preferences…\tCtrl+,", 'Application preferences', sub { $self->_append_menu_item($fileMenu, L("Preferences…\tCtrl+,"), L('Application preferences'), sub {
Slic3r::GUI::Preferences->new($self)->ShowModal; # Opening the C++ preferences dialog.
Slic3r::GUI::open_preferences_dialog($self->{preferences_event});
}, wxID_PREFERENCES); }, wxID_PREFERENCES);
$fileMenu->AppendSeparator(); $fileMenu->AppendSeparator();
$self->_append_menu_item($fileMenu, "&Quit", 'Quit Slic3r', sub { $self->_append_menu_item($fileMenu, L("&Quit"), L('Quit Slic3r'), sub {
$self->Close(0); $self->Close(0);
}, wxID_EXIT); }, wxID_EXIT);
} }
@ -307,16 +309,16 @@ sub _init_menubar {
my $plater = $self->{plater}; my $plater = $self->{plater};
$self->{plater_menu} = Wx::Menu->new; $self->{plater_menu} = Wx::Menu->new;
$self->_append_menu_item($self->{plater_menu}, "Export G-code...", 'Export current plate as G-code', sub { $self->_append_menu_item($self->{plater_menu}, L("Export G-code..."), L('Export current plate as G-code'), sub {
$plater->export_gcode; $plater->export_gcode;
}, undef, 'cog_go.png'); }, undef, 'cog_go.png');
$self->_append_menu_item($self->{plater_menu}, "Export plate as STL...", 'Export current plate as STL', sub { $self->_append_menu_item($self->{plater_menu}, L("Export plate as STL..."), L('Export current plate as STL'), sub {
$plater->export_stl; $plater->export_stl;
}, undef, 'brick_go.png'); }, undef, 'brick_go.png');
$self->_append_menu_item($self->{plater_menu}, "Export plate as AMF...", 'Export current plate as AMF', sub { $self->_append_menu_item($self->{plater_menu}, L("Export plate as AMF..."), L('Export current plate as AMF'), sub {
$plater->export_amf; $plater->export_amf;
}, undef, 'brick_go.png'); }, undef, 'brick_go.png');
$self->_append_menu_item($self->{plater_menu}, "Export plate as 3MF...", 'Export current plate as 3MF', sub { $self->_append_menu_item($self->{plater_menu}, L("Export plate as 3MF..."), L('Export current plate as 3MF'), sub {
$plater->export_3mf; $plater->export_3mf;
}, undef, 'brick_go.png'); }, undef, 'brick_go.png');
@ -329,13 +331,13 @@ sub _init_menubar {
{ {
my $tab_offset = 0; my $tab_offset = 0;
if (!$self->{no_plater}) { if (!$self->{no_plater}) {
$self->_append_menu_item($windowMenu, "Select &Plater Tab\tCtrl+1", 'Show the plater', sub { $self->_append_menu_item($windowMenu, L("Select &Plater Tab\tCtrl+1"), L('Show the plater'), sub {
$self->select_tab(0); $self->select_tab(0);
}, undef, 'application_view_tile.png'); }, undef, 'application_view_tile.png');
$tab_offset += 1; $tab_offset += 1;
} }
if (!$self->{no_controller}) { if (!$self->{no_controller}) {
$self->_append_menu_item($windowMenu, "Select &Controller Tab\tCtrl+T", 'Show the printer controller', sub { $self->_append_menu_item($windowMenu, L("Select &Controller Tab\tCtrl+T"), L('Show the printer controller'), sub {
$self->select_tab(1); $self->select_tab(1);
}, undef, 'printer_empty.png'); }, undef, 'printer_empty.png');
$tab_offset += 1; $tab_offset += 1;
@ -343,13 +345,13 @@ sub _init_menubar {
if ($tab_offset > 0) { if ($tab_offset > 0) {
$windowMenu->AppendSeparator(); $windowMenu->AppendSeparator();
} }
$self->_append_menu_item($windowMenu, "Select P&rint Settings Tab\tCtrl+2", 'Show the print settings', sub { $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); $self->select_tab($tab_offset+0);
}, undef, 'cog.png'); }, undef, 'cog.png');
$self->_append_menu_item($windowMenu, "Select &Filament Settings Tab\tCtrl+3", 'Show the filament settings', sub { $self->_append_menu_item($windowMenu, L("Select &Filament Settings Tab\tCtrl+3"), L('Show the filament settings'), sub {
$self->select_tab($tab_offset+1); $self->select_tab($tab_offset+1);
}, undef, 'spool.png'); }, undef, 'spool.png');
$self->_append_menu_item($windowMenu, "Select Print&er Settings Tab\tCtrl+4", 'Show the printer settings', sub { $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); $self->select_tab($tab_offset+2);
}, undef, 'printer_empty.png'); }, undef, 'printer_empty.png');
} }
@ -361,66 +363,66 @@ sub _init_menubar {
# as the simple numeric accelerators spoil all numeric data entry. # as the simple numeric accelerators spoil all numeric data entry.
# The camera control accelerators are captured by 3DScene Perl module instead. # The camera control accelerators are captured by 3DScene Perl module instead.
my $accel = ($^O eq 'MSWin32') ? sub { $_[0] . "\t\xA0" . $_[1] } : sub { $_[0] }; my $accel = ($^O eq 'MSWin32') ? sub { $_[0] . "\t\xA0" . $_[1] } : sub { $_[0] };
$self->_append_menu_item($self->{viewMenu}, $accel->('Iso', '0'), 'Iso View' , sub { $self->select_view('iso' ); }); $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->('Top', '1'), 'Top View' , sub { $self->select_view('top' ); }); $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->('Bottom', '2'), 'Bottom View' , sub { $self->select_view('bottom' ); }); $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->('Front', '3'), 'Front View' , sub { $self->select_view('front' ); }); $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->('Rear', '4'), 'Rear View' , sub { $self->select_view('rear' ); }); $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->('Left', '5'), 'Left View' , sub { $self->select_view('left' ); }); $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->('Right', '6'), 'Right View' , sub { $self->select_view('right' ); }); $self->_append_menu_item($self->{viewMenu}, $accel->(L('Right'), '6'), L('Right View') , sub { $self->select_view('right' ); });
} }
# Help menu # Help menu
my $helpMenu = Wx::Menu->new; my $helpMenu = Wx::Menu->new;
{ {
$self->_append_menu_item($helpMenu, "&Configuration $Slic3r::GUI::ConfigWizard::wizard…", "Run Configuration $Slic3r::GUI::ConfigWizard::wizard", sub { $self->_append_menu_item($helpMenu, L("&Configuration ").$Slic3r::GUI::ConfigWizard::wizard."…", L("Run Configuration ").$Slic3r::GUI::ConfigWizard::wizard, sub {
# Run the config wizard, offer the "reset user profile" checkbox. # Run the config wizard, offer the "reset user profile" checkbox.
$self->config_wizard(0); $self->config_wizard(0);
}); });
$helpMenu->AppendSeparator(); $helpMenu->AppendSeparator();
$self->_append_menu_item($helpMenu, "Prusa 3D Drivers", 'Open the Prusa3D drivers download page in your browser', sub { $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/'); Wx::LaunchDefaultBrowser('http://www.prusa3d.com/drivers/');
}); });
$self->_append_menu_item($helpMenu, "Prusa Edition Releases", 'Open the Prusa Edition releases page in your browser', sub { $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'); Wx::LaunchDefaultBrowser('http://github.com/prusa3d/slic3r/releases');
}); });
# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", 'Check for new Slic3r versions', sub { # my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", 'Check for new Slic3r versions', sub {
# wxTheApp->check_version(1); # wxTheApp->check_version(1);
# }); # });
# $versioncheck->Enable(wxTheApp->have_version_check); # $versioncheck->Enable(wxTheApp->have_version_check);
$self->_append_menu_item($helpMenu, "Slic3r &Website", 'Open the Slic3r website in your browser', sub { $self->_append_menu_item($helpMenu, L("Slic3r &Website"), L('Open the Slic3r website in your browser'), sub {
Wx::LaunchDefaultBrowser('http://slic3r.org/'); Wx::LaunchDefaultBrowser('http://slic3r.org/');
}); });
$self->_append_menu_item($helpMenu, "Slic3r &Manual", 'Open the Slic3r manual in your browser', sub { $self->_append_menu_item($helpMenu, L("Slic3r &Manual"), L('Open the Slic3r manual in your browser'), sub {
Wx::LaunchDefaultBrowser('http://manual.slic3r.org/'); Wx::LaunchDefaultBrowser('http://manual.slic3r.org/');
}); });
$helpMenu->AppendSeparator(); $helpMenu->AppendSeparator();
$self->_append_menu_item($helpMenu, "System Info", 'Show system information', sub { $self->_append_menu_item($helpMenu, L("System Info"), L('Show system information'), sub {
wxTheApp->system_info; wxTheApp->system_info;
}); });
$self->_append_menu_item($helpMenu, "Report an Issue", 'Report an issue on the Slic3r Prusa Edition', sub { $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'); Wx::LaunchDefaultBrowser('http://github.com/prusa3d/slic3r/issues/new');
}); });
$self->_append_menu_item($helpMenu, "&About Slic3r", 'Show about dialog', sub { $self->_append_menu_item($helpMenu, L("&About Slic3r"), L('Show about dialog'), sub {
wxTheApp->about; wxTheApp->about;
}); });
} }
# menubar # menubar
# assign menubar to frame after appending items, otherwise special items # assign menubar to frame after appending items, otherwise special items
# will not be handled correctly # will not be handled correctly
{ {
my $menubar = Wx::MenuBar->new; my $menubar = Wx::MenuBar->new;
$menubar->Append($fileMenu, "&File"); $menubar->Append($fileMenu, L("&File"));
$menubar->Append($self->{plater_menu}, "&Plater") if $self->{plater_menu}; $menubar->Append($self->{plater_menu}, L("&Plater")) if $self->{plater_menu};
$menubar->Append($self->{object_menu}, "&Object") if $self->{object_menu}; $menubar->Append($self->{object_menu}, L("&Object")) if $self->{object_menu};
$menubar->Append($windowMenu, "&Window"); $menubar->Append($windowMenu, L("&Window"));
$menubar->Append($self->{viewMenu}, "&View") if $self->{viewMenu}; $menubar->Append($self->{viewMenu}, L("&View")) if $self->{viewMenu};
# Add an optional debug menu # Add an optional debug menu
# (Select application language from the list of installed languages) # (Select application language from the list of installed languages)
# In production code, the add_debug_menu() call should do nothing.
Slic3r::GUI::add_debug_menu($menubar, $self->{lang_ch_event}); Slic3r::GUI::add_debug_menu($menubar, $self->{lang_ch_event});
$menubar->Append($helpMenu, "&Help"); $menubar->Append($helpMenu, L("&Help"));
# Add an optional debug menu. In production code, the add_debug_menu() call should do nothing.
$self->SetMenuBar($menubar); $self->SetMenuBar($menubar);
} }
} }
@ -451,7 +453,7 @@ sub quick_slice {
# select input file # select input file
my $input_file; my $input_file;
if (!$params{reslice}) { if (!$params{reslice}) {
my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):', my $dialog = Wx::FileDialog->new($self, L('Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):'),
wxTheApp->{app_config}->get_last_dir, "", wxTheApp->{app_config}->get_last_dir, "",
&Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) { if ($dialog->ShowModal != wxID_OK) {
@ -463,13 +465,13 @@ sub quick_slice {
$qs_last_input_file = $input_file unless $params{export_svg}; $qs_last_input_file = $input_file unless $params{export_svg};
} else { } else {
if (!defined $qs_last_input_file) { if (!defined $qs_last_input_file) {
Wx::MessageDialog->new($self, "No previously sliced file.", Wx::MessageDialog->new($self, L("No previously sliced file."),
'Error', wxICON_ERROR | wxOK)->ShowModal(); L('Error'), wxICON_ERROR | wxOK)->ShowModal();
return; return;
} }
if (! -e $qs_last_input_file) { if (! -e $qs_last_input_file) {
Wx::MessageDialog->new($self, "Previously sliced file ($qs_last_input_file) not found.", Wx::MessageDialog->new($self, L("Previously sliced file (").$qs_last_input_file.L(") not found."),
'File Not Found', wxICON_ERROR | wxOK)->ShowModal(); L('File Not Found'), wxICON_ERROR | wxOK)->ShowModal();
return; return;
} }
$input_file = $qs_last_input_file; $input_file = $qs_last_input_file;
@ -508,7 +510,7 @@ sub quick_slice {
# The following line may die if the output_filename_format template substitution fails. # The following line may die if the output_filename_format template substitution fails.
$output_file = $sprint->output_filepath; $output_file = $sprint->output_filepath;
$output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/ if $params{export_svg}; $output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/ if $params{export_svg};
my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', my $dlg = Wx::FileDialog->new($self, L('Save ') . ($params{export_svg} ? L('SVG') : L('G-code')) . L(' file as:'),
wxTheApp->{app_config}->get_last_output_dir(dirname($output_file)), wxTheApp->{app_config}->get_last_output_dir(dirname($output_file)),
basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal != wxID_OK) { if ($dlg->ShowModal != wxID_OK) {
@ -522,7 +524,7 @@ sub quick_slice {
} }
# show processbar dialog # show processbar dialog
$progress_dialog = Wx::ProgressDialog->new('Slicing…', "Processing $input_file_basename…", $progress_dialog = Wx::ProgressDialog->new(L('Slicing…'), L("Processing ").$input_file_basename."…",
100, $self, 0); 100, $self, 0);
$progress_dialog->Pulse; $progress_dialog->Pulse;
@ -542,9 +544,9 @@ sub quick_slice {
$progress_dialog->Destroy; $progress_dialog->Destroy;
undef $progress_dialog; undef $progress_dialog;
my $message = "$input_file_basename was successfully sliced."; my $message = $input_file_basename.L(" was successfully sliced.");
wxTheApp->notify($message); wxTheApp->notify($message);
Wx::MessageDialog->new($self, $message, 'Slicing Done!', Wx::MessageDialog->new($self, $message, L('Slicing Done!'),
wxOK | wxICON_INFORMATION)->ShowModal; wxOK | wxICON_INFORMATION)->ShowModal;
}; };
Slic3r::GUI::catch_error($self, sub { $progress_dialog->Destroy if $progress_dialog }); Slic3r::GUI::catch_error($self, sub { $progress_dialog->Destroy if $progress_dialog });
@ -560,7 +562,7 @@ sub repair_stl {
my $input_file; my $input_file;
{ {
my $dialog = Wx::FileDialog->new($self, 'Select the STL file to repair:', my $dialog = Wx::FileDialog->new($self, L('Select the STL file to repair:'),
wxTheApp->{app_config}->get_last_dir, "", wxTheApp->{app_config}->get_last_dir, "",
&Slic3r::GUI::FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); &Slic3r::GUI::FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) { if ($dialog->ShowModal != wxID_OK) {
@ -574,7 +576,7 @@ sub repair_stl {
my $output_file = $input_file; my $output_file = $input_file;
{ {
$output_file =~ s/\.[sS][tT][lL]$/_fixed.obj/; $output_file =~ s/\.[sS][tT][lL]$/_fixed.obj/;
my $dlg = Wx::FileDialog->new($self, "Save OBJ file (less prone to coordinate errors than STL) as:", dirname($output_file), 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); basename($output_file), &Slic3r::GUI::FILE_WILDCARDS->{obj}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal != wxID_OK) { if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy; $dlg->Destroy;
@ -588,7 +590,7 @@ sub repair_stl {
$tmesh->ReadSTLFile($input_file); $tmesh->ReadSTLFile($input_file);
$tmesh->repair; $tmesh->repair;
$tmesh->WriteOBJFile($output_file); $tmesh->WriteOBJFile($output_file);
Slic3r::GUI::show_info($self, "Your file was repaired.", "Repair"); Slic3r::GUI::show_info($self, L("Your file was repaired."), L("Repair"));
} }
sub export_config { sub export_config {
@ -599,7 +601,7 @@ sub export_config {
eval { $config->validate; }; eval { $config->validate; };
Slic3r::GUI::catch_error($self) and return; Slic3r::GUI::catch_error($self) and return;
# Ask user for the file name for the config file. # Ask user for the file name for the config file.
my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', my $dlg = Wx::FileDialog->new($self, L('Save configuration as:'),
$last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir,
$last_config ? basename($last_config) : "config.ini", $last_config ? basename($last_config) : "config.ini",
&Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
@ -617,7 +619,7 @@ sub load_config_file {
my ($self, $file) = @_; my ($self, $file) = @_;
if (!$file) { if (!$file) {
return unless $self->check_unsaved_changes; return unless $self->check_unsaved_changes;
my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', my $dlg = Wx::FileDialog->new($self, L('Select configuration to load:'),
$last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir,
"config.ini", "config.ini",
'INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g', wxFD_OPEN | wxFD_FILE_MUST_EXIST); 'INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g', wxFD_OPEN | wxFD_FILE_MUST_EXIST);
@ -640,7 +642,7 @@ sub export_configbundle {
eval { wxTheApp->{preset_bundle}->full_config->validate; }; eval { wxTheApp->{preset_bundle}->full_config->validate; };
Slic3r::GUI::catch_error($self) and return; Slic3r::GUI::catch_error($self) and return;
# Ask user for a file name. # Ask user for a file name.
my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', my $dlg = Wx::FileDialog->new($self, L('Save presets bundle as:'),
$last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir,
"Slic3r_config_bundle.ini", "Slic3r_config_bundle.ini",
&Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
@ -661,7 +663,7 @@ sub load_configbundle {
my ($self, $file, $reset_user_profile) = @_; my ($self, $file, $reset_user_profile) = @_;
return unless $self->check_unsaved_changes; return unless $self->check_unsaved_changes;
if (!$file) { if (!$file) {
my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', my $dlg = Wx::FileDialog->new($self, L('Select configuration to load:'),
$last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir,
"config.ini", "config.ini",
&Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
@ -681,7 +683,7 @@ sub load_configbundle {
$tab->load_current_preset; $tab->load_current_preset;
} }
my $message = sprintf "%d presets successfully imported.", $presets_imported; my $message = sprintf L("%d presets successfully imported."), $presets_imported;
Slic3r::GUI::show_info($self, $message); Slic3r::GUI::show_info($self, $message);
} }
@ -743,8 +745,8 @@ sub check_unsaved_changes {
if (@dirty) { if (@dirty) {
my $titles = join ', ', @dirty; my $titles = join ', ', @dirty;
my $confirm = Wx::MessageDialog->new($self, "You have unsaved changes ($titles). Discard changes and continue anyway?", my $confirm = Wx::MessageDialog->new($self, L("You have unsaved changes ").($titles).L(". Discard changes and continue anyway?"),
'Unsaved Presets', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); L('Unsaved Presets'), wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
return $confirm->ShowModal == wxID_YES; return $confirm->ShowModal == wxID_YES;
} }

View file

@ -33,6 +33,8 @@ use constant TB_CUT => &Wx::NewId;
use constant TB_SETTINGS => &Wx::NewId; use constant TB_SETTINGS => &Wx::NewId;
use constant TB_LAYER_EDITING => &Wx::NewId; use constant TB_LAYER_EDITING => &Wx::NewId;
use Wx::Locale gettext => 'L';
# package variables to avoid passing lexicals to threads # package variables to avoid passing lexicals to threads
our $PROGRESS_BAR_EVENT : shared = Wx::NewEventType; our $PROGRESS_BAR_EVENT : shared = Wx::NewEventType;
our $ERROR_EVENT : shared = Wx::NewEventType; our $ERROR_EVENT : shared = Wx::NewEventType;
@ -50,7 +52,7 @@ sub new {
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
$self->{config} = Slic3r::Config::new_from_defaults_keys([qw( $self->{config} = Slic3r::Config::new_from_defaults_keys([qw(
bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height
serial_port serial_speed octoprint_host octoprint_apikey serial_port serial_speed octoprint_host octoprint_apikey octoprint_cafile
nozzle_diameter single_extruder_multi_material nozzle_diameter single_extruder_multi_material
wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe wipe_tower_rotation_angle extruder_colour filament_colour wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe wipe_tower_rotation_angle extruder_colour filament_colour
)]); )]);
@ -99,7 +101,7 @@ sub new {
# Initialize 3D plater # Initialize 3D plater
if ($Slic3r::GUI::have_OpenGL) { if ($Slic3r::GUI::have_OpenGL) {
$self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{print}, $self->{config}); $self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{print}, $self->{config});
$self->{preview_notebook}->AddPage($self->{canvas3D}, '3D'); $self->{preview_notebook}->AddPage($self->{canvas3D}, L('3D'));
$self->{canvas3D}->set_on_select_object($on_select_object); $self->{canvas3D}->set_on_select_object($on_select_object);
$self->{canvas3D}->set_on_double_click($on_double_click); $self->{canvas3D}->set_on_double_click($on_double_click);
$self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); }); $self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); });
@ -133,7 +135,7 @@ sub new {
# Initialize 2D preview canvas # Initialize 2D preview canvas
$self->{canvas} = Slic3r::GUI::Plater::2D->new($self->{preview_notebook}, wxDefaultSize, $self->{objects}, $self->{model}, $self->{config}); $self->{canvas} = Slic3r::GUI::Plater::2D->new($self->{preview_notebook}, wxDefaultSize, $self->{objects}, $self->{model}, $self->{config});
$self->{preview_notebook}->AddPage($self->{canvas}, '2D'); $self->{preview_notebook}->AddPage($self->{canvas}, L('2D'));
$self->{canvas}->on_select_object($on_select_object); $self->{canvas}->on_select_object($on_select_object);
$self->{canvas}->on_double_click($on_double_click); $self->{canvas}->on_double_click($on_double_click);
$self->{canvas}->on_right_click(sub { $on_right_click->($self->{canvas}, @_); }); $self->{canvas}->on_right_click(sub { $on_right_click->($self->{canvas}, @_); });
@ -145,14 +147,14 @@ sub new {
$self->{preview3D}->canvas->on_viewport_changed(sub { $self->{preview3D}->canvas->on_viewport_changed(sub {
$self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas); $self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas);
}); });
$self->{preview_notebook}->AddPage($self->{preview3D}, 'Preview'); $self->{preview_notebook}->AddPage($self->{preview3D}, L('Preview'));
$self->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1; $self->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1;
} }
# Initialize toolpaths preview # Initialize toolpaths preview
if ($Slic3r::GUI::have_OpenGL) { if ($Slic3r::GUI::have_OpenGL) {
$self->{toolpaths2D} = Slic3r::GUI::Plater::2DToolpaths->new($self->{preview_notebook}, $self->{print}); $self->{toolpaths2D} = Slic3r::GUI::Plater::2DToolpaths->new($self->{preview_notebook}, $self->{print});
$self->{preview_notebook}->AddPage($self->{toolpaths2D}, 'Layers'); $self->{preview_notebook}->AddPage($self->{toolpaths2D}, L('Layers'));
} }
EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{preview_notebook}, sub { EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{preview_notebook}, sub {
@ -172,37 +174,37 @@ sub new {
if (!&Wx::wxMSW) { if (!&Wx::wxMSW) {
Wx::ToolTip::Enable(1); Wx::ToolTip::Enable(1);
$self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL); $self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL);
$self->{htoolbar}->AddTool(TB_ADD, "Add…", Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_ADD, L("Add…"), Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_REMOVE, "Delete", Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_REMOVE, L("Delete"), Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_RESET, "Delete All", Wx::Bitmap->new(Slic3r::var("cross.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_RESET, L("Delete All"), Wx::Bitmap->new(Slic3r::var("cross.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_ARRANGE, "Arrange", Wx::Bitmap->new(Slic3r::var("bricks.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_ARRANGE, L("Arrange"), Wx::Bitmap->new(Slic3r::var("bricks.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_MORE, L("More"), Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_FEWER, "Fewer", Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_FEWER, L("Fewer"), Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new(Slic3r::var("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_45CCW, L("45° ccw"), Wx::Bitmap->new(Slic3r::var("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_45CW, L("45° cw"), Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_SCALE, "Scale…", Wx::Bitmap->new(Slic3r::var("arrow_out.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_SCALE, L("Scale…"), Wx::Bitmap->new(Slic3r::var("arrow_out.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_SPLIT, L("Split"), Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_CUT, L("Cut…"), Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_SETTINGS, L("Settings…"), Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_LAYER_EDITING, 'Layer Editing', Wx::Bitmap->new(Slic3r::var("variable_layer_height.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, 0, 'Layer Editing'); $self->{htoolbar}->AddTool(TB_LAYER_EDITING, L('Layer Editing'), Wx::Bitmap->new(Slic3r::var("variable_layer_height.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, 0, 'Layer Editing');
} else { } else {
my %tbar_buttons = ( my %tbar_buttons = (
add => "Add…", add => L("Add…"),
remove => "Delete", remove => L("Delete"),
reset => "Delete All", reset => L("Delete All"),
arrange => "Arrange", arrange => L("Arrange"),
increase => "", increase => "",
decrease => "", decrease => "",
rotate45ccw => "", rotate45ccw => "",
rotate45cw => "", rotate45cw => "",
changescale => "Scale…", changescale => L("Scale…"),
split => "Split", split => L("Split"),
cut => "Cut…", cut => L("Cut…"),
settings => "Settings…", settings => L("Settings…"),
layer_editing => "Layer editing", layer_editing => L("Layer editing"),
); );
$self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL);
for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) { for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) {
@ -215,9 +217,9 @@ sub new {
$self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, wxDefaultSize, $self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, wxDefaultSize,
wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS ); wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS );
$self->{list}->InsertColumn(0, "Name", wxLIST_FORMAT_LEFT, 145); $self->{list}->InsertColumn(0, L("Name"), wxLIST_FORMAT_LEFT, 145);
$self->{list}->InsertColumn(1, "Copies", wxLIST_FORMAT_CENTER, 45); $self->{list}->InsertColumn(1, L("Copies"), wxLIST_FORMAT_CENTER, 45);
$self->{list}->InsertColumn(2, "Scale", wxLIST_FORMAT_CENTER, wxLIST_AUTOSIZE_USEHEADER); $self->{list}->InsertColumn(2, L("Scale"), wxLIST_FORMAT_CENTER, wxLIST_AUTOSIZE_USEHEADER);
EVT_LIST_ITEM_SELECTED($self, $self->{list}, \&list_item_selected); EVT_LIST_ITEM_SELECTED($self, $self->{list}, \&list_item_selected);
EVT_LIST_ITEM_DESELECTED($self, $self->{list}, \&list_item_deselected); EVT_LIST_ITEM_DESELECTED($self, $self->{list}, \&list_item_deselected);
EVT_LIST_ITEM_ACTIVATED($self, $self->{list}, \&list_item_activated); EVT_LIST_ITEM_ACTIVATED($self, $self->{list}, \&list_item_activated);
@ -231,11 +233,11 @@ sub new {
}); });
# right pane buttons # right pane buttons
$self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_export_gcode} = Wx::Button->new($self, -1, L("Export G-code…"), wxDefaultPosition, [-1, 30], wxBU_LEFT);
$self->{btn_reslice} = Wx::Button->new($self, -1, "Slice now", wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_reslice} = Wx::Button->new($self, -1, L("Slice now"), wxDefaultPosition, [-1, 30], wxBU_LEFT);
$self->{btn_print} = Wx::Button->new($self, -1, "Print…", wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_print} = Wx::Button->new($self, -1, L("Print…"), wxDefaultPosition, [-1, 30], wxBU_LEFT);
$self->{btn_send_gcode} = Wx::Button->new($self, -1, "Send to printer", wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_send_gcode} = Wx::Button->new($self, -1, L("Send to printer"), wxDefaultPosition, [-1, 30], wxBU_LEFT);
$self->{btn_export_stl} = Wx::Button->new($self, -1, "Export STL…", wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_export_stl} = Wx::Button->new($self, -1, L("Export STL…"), wxDefaultPosition, [-1, 30], wxBU_LEFT);
#$self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font); #$self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font);
#$self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font); #$self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font);
$self->{btn_print}->Hide; $self->{btn_print}->Hide;
@ -362,9 +364,9 @@ sub new {
$presets->AddGrowableCol(1, 1); $presets->AddGrowableCol(1, 1);
$presets->SetFlexibleDirection(wxHORIZONTAL); $presets->SetFlexibleDirection(wxHORIZONTAL);
my %group_labels = ( my %group_labels = (
print => 'Print settings', print => L('Print settings'),
filament => 'Filament', filament => L('Filament'),
printer => 'Printer', printer => L('Printer'),
); );
# UI Combo boxes for a print, multiple filaments, and a printer. # UI Combo boxes for a print, multiple filaments, and a printer.
# Initially a single filament combo box is created, but the number of combo boxes for the filament selection may increase, # Initially a single filament combo box is created, but the number of combo boxes for the filament selection may increase,
@ -393,7 +395,7 @@ sub new {
my $object_info_sizer; my $object_info_sizer;
{ {
my $box = Wx::StaticBox->new($self, -1, "Info"); my $box = Wx::StaticBox->new($self, -1, L("Info"));
$object_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); $object_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL);
$object_info_sizer->SetMinSize([350,-1]); $object_info_sizer->SetMinSize([350,-1]);
my $grid_sizer = Wx::FlexGridSizer->new(3, 4, 5, 5); my $grid_sizer = Wx::FlexGridSizer->new(3, 4, 5, 5);
@ -403,11 +405,11 @@ sub new {
$object_info_sizer->Add($grid_sizer, 0, wxEXPAND); $object_info_sizer->Add($grid_sizer, 0, wxEXPAND);
my @info = ( my @info = (
size => "Size", size => L("Size"),
volume => "Volume", volume => L("Volume"),
facets => "Facets", facets => L("Facets"),
materials => "Materials", materials => L("Materials"),
manifold => "Manifold", manifold => L("Manifold"),
); );
while (my $field = shift @info) { while (my $field = shift @info) {
my $label = shift @info; my $label = shift @info;
@ -433,7 +435,7 @@ sub new {
my $print_info_sizer; my $print_info_sizer;
{ {
my $box = Wx::StaticBox->new($self, -1, "Sliced Info"); my $box = Wx::StaticBox->new($self, -1, L("Sliced Info"));
$print_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); $print_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL);
$print_info_sizer->SetMinSize([350,-1]); $print_info_sizer->SetMinSize([350,-1]);
my $grid_sizer = Wx::FlexGridSizer->new(2, 2, 5, 5); my $grid_sizer = Wx::FlexGridSizer->new(2, 2, 5, 5);
@ -442,11 +444,11 @@ sub new {
$grid_sizer->AddGrowableCol(3, 1); $grid_sizer->AddGrowableCol(3, 1);
$print_info_sizer->Add($grid_sizer, 0, wxEXPAND); $print_info_sizer->Add($grid_sizer, 0, wxEXPAND);
my @info = ( my @info = (
fil_m => "Used Filament (m)", fil_m => L("Used Filament (m)"),
fil_mm3 => "Used Filament (mm\x{00B3})", fil_mm3 => L("Used Filament (mm³)"),
fil_g => "Used Filament (g)", fil_g => L("Used Filament (g)"),
cost => "Cost", cost => L("Cost"),
time => "Estimated printing time", time => L("Estimated printing time"),
); );
while (my $field = shift @info) { while (my $field = shift @info) {
my $label = shift @info; my $label = shift @info;
@ -624,7 +626,7 @@ sub load_files {
my $one_by_one = (@$nozzle_dmrs <= 1) || (@$input_files == 1) || 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); 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('Loading…', "Processing input file\n" . basename($input_files->[0]), 100, $self, 0); my $process_dialog = Wx::ProgressDialog->new(L('Loading…'), L("Processing input file\n") . basename($input_files->[0]), 100, $self, 0);
$process_dialog->Pulse; $process_dialog->Pulse;
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
@ -638,7 +640,7 @@ sub load_files {
# For all input files. # For all input files.
for (my $i = 0; $i < @$input_files; $i += 1) { for (my $i = 0; $i < @$input_files; $i += 1) {
my $input_file = $input_files->[$i]; my $input_file = $input_files->[$i];
$process_dialog->Update(100. * $i / @$input_files, "Processing input file\n" . basename($input_file)); $process_dialog->Update(100. * $i / @$input_files, L("Processing input file\n") . basename($input_file));
my $model; my $model;
if (($input_file =~ /.3[mM][fF]$/) || ($input_file =~ /.[zZ][iI][pP].[aA][mM][fF]$/)) if (($input_file =~ /.3[mM][fF]$/) || ($input_file =~ /.[zZ][iI][pP].[aA][mM][fF]$/))
@ -658,10 +660,10 @@ sub load_files {
if ($model->looks_like_multipart_object) { if ($model->looks_like_multipart_object) {
my $dialog = Wx::MessageDialog->new($self, my $dialog = Wx::MessageDialog->new($self,
"This file contains several objects positioned at multiple heights. " L("This file contains several objects positioned at multiple heights. "
. "Instead of considering them as multiple objects, should I consider\n" . "Instead of considering them as multiple objects, should I consider\n"
. "this file as a single object having multiple parts?\n", . "this file as a single object having multiple parts?\n"),
'Multi-part object detected', wxICON_WARNING | wxYES | wxNO); L('Multi-part object detected'), wxICON_WARNING | wxYES | wxNO);
$model->convert_multipart_object if $dialog->ShowModal() == wxID_YES; $model->convert_multipart_object if $dialog->ShowModal() == wxID_YES;
} }
@ -675,10 +677,10 @@ sub load_files {
if ($new_model) { if ($new_model) {
my $dialog = Wx::MessageDialog->new($self, my $dialog = Wx::MessageDialog->new($self,
"Multiple objects were loaded for a multi-material printer.\n" L("Multiple objects were loaded for a multi-material printer.\n"
. "Instead of considering them as multiple objects, should I consider\n" . "Instead of considering them as multiple objects, should I consider\n"
. "these files to represent a single object having multiple parts?\n", . "these files to represent a single object having multiple parts?\n"),
'Multi-part object detected', wxICON_WARNING | wxYES | wxNO); L('Multi-part object detected'), wxICON_WARNING | wxYES | wxNO);
$new_model->convert_multipart_object if $dialog->ShowModal() == wxID_YES; $new_model->convert_multipart_object if $dialog->ShowModal() == wxID_YES;
push @obj_idx, $self->load_model_objects(@{$new_model->objects}); push @obj_idx, $self->load_model_objects(@{$new_model->objects});
} }
@ -687,7 +689,7 @@ sub load_files {
wxTheApp->{app_config}->update_skein_dir(dirname($input_files->[-1])); wxTheApp->{app_config}->update_skein_dir(dirname($input_files->[-1]));
$process_dialog->Destroy; $process_dialog->Destroy;
$self->statusbar->SetStatusText("Loaded " . join(',', @loaded_files)); $self->statusbar->SetStatusText(L("Loaded ") . join(',', @loaded_files));
return @obj_idx; return @obj_idx;
} }
@ -739,8 +741,8 @@ sub load_model_objects {
if ($scaled_down) { if ($scaled_down) {
Slic3r::GUI::show_info( Slic3r::GUI::show_info(
$self, $self,
'Your object appears to be too large, so it was automatically scaled down to fit your print bed.', L('Your object appears to be too large, so it was automatically scaled down to fit your print bed.'),
'Object too large?', L('Object too large?'),
); );
} }
@ -895,7 +897,7 @@ sub set_number_of_copies {
my $model_object = $self->{model}->objects->[$obj_idx]; my $model_object = $self->{model}->objects->[$obj_idx];
# prompt user # prompt user
my $copies = Wx::GetNumberFromUser("", "Enter the number of copies of the selected object:", "Copies", $model_object->instances_count, 0, 1000, $self); my $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; my $diff = $copies - $model_object->instances_count;
if ($diff == 0) { if ($diff == 0) {
# no variation # no variation
@ -922,9 +924,9 @@ sub _get_number_from_user {
Wx::MessageBox( Wx::MessageBox(
$error_message . $error_message .
(($only_positive && $value <= 0) ? (($only_positive && $value <= 0) ?
": $value\nNon-positive value." : ": ".$value.L("\nNon-positive value.") :
": $value\nNot a numeric value."), ": ".$value.L("\nNot a numeric value.")),
"Slic3r Error", wxOK | wxICON_EXCLAMATION, $self); L("Slic3r Error"), wxOK | wxICON_EXCLAMATION, $self);
$default = $value; $default = $value;
} }
} }
@ -945,7 +947,7 @@ sub rotate {
if (!defined $angle) { if (!defined $angle) {
my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z'; my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z';
my $default = $axis == Z ? rad2deg($model_instance->rotation) : 0; my $default = $axis == Z ? rad2deg($model_instance->rotation) : 0;
$angle = $self->_get_number_from_user("Enter the rotation angle:", "Rotate around $axis_name axis", "Invalid rotation angle entered", $default); $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 ''; return if $angle eq '';
} }
@ -1025,12 +1027,12 @@ sub changescale {
if ($tosize) { if ($tosize) {
my $cursize = $object_size->[$axis]; my $cursize = $object_size->[$axis];
my $newsize = $self->_get_number_from_user( my $newsize = $self->_get_number_from_user(
sprintf('Enter the new size for the selected object (print bed: %smm):', unscale($bed_size->[$axis])), sprintf(L('Enter the new size for the selected object (print bed: %smm):'), unscale($bed_size->[$axis])),
"Scale along $axis_name", 'Invalid scaling value entered', $cursize, 1); L("Scale along ").$axis_name, L('Invalid scaling value entered'), $cursize, 1);
return if $newsize eq ''; return if $newsize eq '';
$scale = $newsize / $cursize * 100; $scale = $newsize / $cursize * 100;
} else { } else {
$scale = $self->_get_number_from_user('Enter the scale % for the selected object:', "Scale along $axis_name", 'Invalid scaling value entered', 100, 1); $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 ''; return if $scale eq '';
} }
@ -1051,12 +1053,12 @@ sub changescale {
my $scale; my $scale;
if ($tosize) { if ($tosize) {
my $cursize = max(@$object_size); my $cursize = max(@$object_size);
my $newsize = $self->_get_number_from_user('Enter the new max size for the selected object:', 'Scale', 'Invalid scaling value entered', $cursize, 1); 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 ''; return if ! defined($newsize) || $newsize eq '';
$scale = $model_instance->scaling_factor * $newsize / $cursize * 100; $scale = $model_instance->scaling_factor * $newsize / $cursize * 100;
} else { } else {
# max scale factor should be above 2540 to allow importing files exported in inches # max scale factor should be above 2540 to allow importing files exported in inches
$scale = $self->_get_number_from_user('Enter the scale % for the selected object:', 'Scale', 'Invalid scaling value entered', $model_instance->scaling_factor*100, 1); $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 ''; return if ! defined($scale) || $scale eq '';
} }
@ -1107,7 +1109,7 @@ sub split_object {
my $current_model_object = $new_model->get_object($obj_idx); my $current_model_object = $new_model->get_object($obj_idx);
if ($current_model_object->volumes_count > 1) { if ($current_model_object->volumes_count > 1) {
Slic3r::GUI::warning_catcher($self)->("The selected object can't be split because it contains more than one volume/material."); Slic3r::GUI::warning_catcher($self)->(L("The selected object can't be split because it contains more than one volume/material."));
return; return;
} }
@ -1116,7 +1118,7 @@ sub split_object {
my @model_objects = @{$current_model_object->split_object}; my @model_objects = @{$current_model_object->split_object};
if (@model_objects == 1) { if (@model_objects == 1) {
$self->resume_background_process; $self->resume_background_process;
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it contains only one part."); Slic3r::GUI::warning_catcher($self)->(L("The selected object couldn't be split because it contains only one part."));
$self->resume_background_process; $self->resume_background_process;
return; return;
} }
@ -1281,7 +1283,7 @@ sub reslice {
$self->async_apply_config; $self->async_apply_config;
$self->statusbar->SetCancelCallback(sub { $self->statusbar->SetCancelCallback(sub {
$self->stop_background_process; $self->stop_background_process;
$self->statusbar->SetStatusText("Slicing cancelled"); $self->statusbar->SetStatusText(L("Slicing cancelled"));
# this updates buttons status # this updates buttons status
$self->object_list_changed; $self->object_list_changed;
}); });
@ -1295,7 +1297,7 @@ sub export_gcode {
return if !@{$self->{objects}}; return if !@{$self->{objects}};
if ($self->{export_gcode_output_file}) { if ($self->{export_gcode_output_file}) {
Wx::MessageDialog->new($self, "Another export job is currently running.", 'Error', wxOK | wxICON_ERROR)->ShowModal; Wx::MessageDialog->new($self, L("Another export job is currently running."), L('Error'), wxOK | wxICON_ERROR)->ShowModal;
return; return;
} }
@ -1326,7 +1328,7 @@ sub export_gcode {
} else { } else {
my $default_output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') }; my $default_output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') };
Slic3r::GUI::catch_error($self) and return; Slic3r::GUI::catch_error($self) and return;
my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', my $dlg = Wx::FileDialog->new($self, L('Save G-code file as:'),
wxTheApp->{app_config}->get_last_output_dir(dirname($default_output_file)), wxTheApp->{app_config}->get_last_output_dir(dirname($default_output_file)),
basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal != wxID_OK) { if ($dlg->ShowModal != wxID_OK) {
@ -1343,7 +1345,7 @@ sub export_gcode {
$self->statusbar->SetCancelCallback(sub { $self->statusbar->SetCancelCallback(sub {
$self->stop_background_process; $self->stop_background_process;
$self->statusbar->SetStatusText("Export cancelled"); $self->statusbar->SetStatusText(L("Export cancelled"));
$self->{export_gcode_output_file} = undef; $self->{export_gcode_output_file} = undef;
$self->{send_gcode_file} = undef; $self->{send_gcode_file} = undef;
@ -1440,24 +1442,29 @@ sub on_export_completed {
if ($result) { if ($result) {
# G-code file exported successfully. # G-code file exported successfully.
if ($self->{print_file}) { if ($self->{print_file}) {
$message = "File added to print queue"; $message = L("File added to print queue");
$do_print = 1; $do_print = 1;
} elsif ($self->{send_gcode_file}) { } elsif ($self->{send_gcode_file}) {
$message = "Sending G-code file to the OctoPrint server..."; $message = L("Sending G-code file to the OctoPrint server...");
$send_gcode = 1; $send_gcode = 1;
} else { } else {
$message = "G-code file exported to " . $self->{export_gcode_output_file}; $message = L("G-code file exported to ") . $self->{export_gcode_output_file};
} }
} else { } else {
$message = "Export failed"; $message = L("Export failed");
} }
$self->{export_gcode_output_file} = undef; $self->{export_gcode_output_file} = undef;
$self->statusbar->SetStatusText($message); $self->statusbar->SetStatusText($message);
wxTheApp->notify($message); wxTheApp->notify($message);
$self->do_print if $do_print; $self->do_print if $do_print;
# Send $self->{send_gcode_file} to OctoPrint. # Send $self->{send_gcode_file} to OctoPrint.
$self->send_gcode if $send_gcode; if ($send_gcode) {
my $op = Slic3r::OctoPrint->new($self->{config});
$op->send_gcode($self->GetId(), $PROGRESS_BAR_EVENT, $ERROR_EVENT, $self->{send_gcode_file});
}
$self->{print_file} = undef; $self->{print_file} = undef;
$self->{send_gcode_file} = undef; $self->{send_gcode_file} = undef;
$self->{"print_info_cost"}->SetLabel(sprintf("%.2f" , $self->{print}->total_cost)); $self->{"print_info_cost"}->SetLabel(sprintf("%.2f" , $self->{print}->total_cost));
@ -1486,45 +1493,6 @@ sub do_print {
my $filament_names = wxTheApp->{preset_bundle}->filament_presets; my $filament_names = wxTheApp->{preset_bundle}->filament_presets;
$filament_stats = { map { $filament_names->[$_] => $filament_stats->{$_} } keys %$filament_stats }; $filament_stats = { map { $filament_names->[$_] => $filament_stats->{$_} } keys %$filament_stats };
$printer_panel->load_print_job($self->{print_file}, $filament_stats); $printer_panel->load_print_job($self->{print_file}, $filament_stats);
$self->GetFrame->select_tab(1);
}
# Send $self->{send_gcode_file} to OctoPrint.
#FIXME Currently this call blocks the UI. Make it asynchronous.
sub send_gcode {
my ($self) = @_;
$self->statusbar->StartBusy;
my $ua = LWP::UserAgent->new;
$ua->timeout(180);
my $res = $ua->post(
"http://" . $self->{config}->octoprint_host . "/api/files/local",
Content_Type => 'form-data',
'X-Api-Key' => $self->{config}->octoprint_apikey,
Content => [
file => [
# On Windows, the path has to be encoded in local code page for perl to be able to open it.
Slic3r::encode_path($self->{send_gcode_file}),
# Remove the UTF-8 flag from the perl string, so the LWP::UserAgent can insert
# the UTF-8 encoded string into the request as a byte stream.
Slic3r::path_to_filename_raw($self->{send_gcode_file})
],
print => $self->{send_gcode_file_print} ? 1 : 0,
],
);
$self->statusbar->StopBusy;
if ($res->is_success) {
$self->statusbar->SetStatusText("G-code file successfully uploaded to the OctoPrint server");
} else {
my $message = "Error while uploading to the OctoPrint server: " . $res->status_line;
Slic3r::GUI::show_error($self, $message);
$self->statusbar->SetStatusText($message);
}
} }
sub export_stl { sub export_stl {
@ -1534,7 +1502,7 @@ sub export_stl {
my $output_file = $self->_get_export_file('STL') or return; my $output_file = $self->_get_export_file('STL') or return;
# Store a binary STL. # Store a binary STL.
$self->{model}->store_stl($output_file, 1); $self->{model}->store_stl($output_file, 1);
$self->statusbar->SetStatusText("STL file exported to $output_file"); $self->statusbar->SetStatusText(L("STL file exported to ").$output_file);
} }
sub reload_from_disk { sub reload_from_disk {
@ -1576,7 +1544,7 @@ sub export_object_stl {
# Ask user for a file name to write into. # Ask user for a file name to write into.
my $output_file = $self->_get_export_file('STL') or return; my $output_file = $self->_get_export_file('STL') or return;
$model_object->mesh->write_binary($output_file); $model_object->mesh->write_binary($output_file);
$self->statusbar->SetStatusText("STL file exported to $output_file"); $self->statusbar->SetStatusText(L("STL file exported to ").$output_file);
} }
sub export_amf { sub export_amf {
@ -1587,11 +1555,11 @@ sub export_amf {
my $res = $self->{model}->store_amf($output_file, $self->{print}); my $res = $self->{model}->store_amf($output_file, $self->{print});
if ($res) if ($res)
{ {
$self->statusbar->SetStatusText("AMF file exported to $output_file"); $self->statusbar->SetStatusText(L("AMF file exported to ").$output_file);
} }
else else
{ {
$self->statusbar->SetStatusText("Error exporting AMF file $output_file"); $self->statusbar->SetStatusText(L("Error exporting AMF file ").$output_file);
} }
} }
@ -1603,11 +1571,11 @@ sub export_3mf {
my $res = $self->{model}->store_3mf($output_file, $self->{print}); my $res = $self->{model}->store_3mf($output_file, $self->{print});
if ($res) if ($res)
{ {
$self->statusbar->SetStatusText("3MF file exported to $output_file"); $self->statusbar->SetStatusText(L("3MF file exported to ").$output_file);
} }
else else
{ {
$self->statusbar->SetStatusText("Error exporting 3MF file $output_file"); $self->statusbar->SetStatusText(L("Error exporting 3MF file ").$output_file);
} }
} }
@ -1616,23 +1584,32 @@ sub export_3mf {
sub _get_export_file { sub _get_export_file {
my ($self, $format) = @_; my ($self, $format) = @_;
my $suffix = ''; my $suffix = '';
my $wildcard = 'known';
if ($format eq 'STL') if ($format eq 'STL')
{ {
$suffix = '.stl'; $suffix = '.stl';
$wildcard = 'stl';
} }
elsif ($format eq 'AMF') elsif ($format eq 'AMF')
{ {
$suffix = '.zip.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') elsif ($format eq '3MF')
{ {
$suffix = '.3mf'; $suffix = '.3mf';
$wildcard = 'threemf';
} }
my $output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') }; my $output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') };
Slic3r::GUI::catch_error($self) and return undef; Slic3r::GUI::catch_error($self) and return undef;
$output_file =~ s/\.[gG][cC][oO][dD][eE]$/$suffix/; $output_file =~ s/\.[gG][cC][oO][dD][eE]$/$suffix/;
my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), my $dlg = Wx::FileDialog->new($self, L("Save ").$format.L(" file as:"), dirname($output_file),
basename($output_file), &Slic3r::GUI::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); basename($output_file), &Slic3r::GUI::FILE_WILDCARDS->{$wildcard}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal != wxID_OK) { if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy; $dlg->Destroy;
return undef; return undef;
@ -1834,7 +1811,7 @@ sub object_cut_dialog {
} }
if (!$Slic3r::GUI::have_OpenGL) { if (!$Slic3r::GUI::have_OpenGL) {
Slic3r::GUI::show_error($self, "Please install the OpenGL modules to use this feature (see build instructions)."); Slic3r::GUI::show_error($self, L("Please install the OpenGL modules to use this feature (see build instructions)."));
return; return;
} }
@ -1944,19 +1921,19 @@ sub selection_changed {
if (my $stats = $model_object->mesh_stats) { if (my $stats = $model_object->mesh_stats) {
$self->{object_info_volume}->SetLabel(sprintf('%.2f', $stats->{volume} * ($model_instance->scaling_factor**3))); $self->{object_info_volume}->SetLabel(sprintf('%.2f', $stats->{volume} * ($model_instance->scaling_factor**3)));
$self->{object_info_facets}->SetLabel(sprintf('%d (%d shells)', $model_object->facets_count, $stats->{number_of_parts})); $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)})) { if (my $errors = sum(@$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)})) {
$self->{object_info_manifold}->SetLabel(sprintf("Auto-repaired (%d errors)", $errors)); $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;
# we don't show normals_fixed because we never provide normals # we don't show normals_fixed because we never provide normals
# to admesh, so it generates normals for all facets # to admesh, so it generates normals for all facets
my $message = sprintf '%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges', 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)}; @$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)};
$self->{object_info_manifold}->SetToolTipString($message); $self->{object_info_manifold}->SetToolTipString($message);
$self->{object_info_manifold_warning_icon}->SetToolTipString($message); $self->{object_info_manifold_warning_icon}->SetToolTipString($message);
} else { } else {
$self->{object_info_manifold}->SetLabel("Yes"); $self->{object_info_manifold}->SetLabel(L("Yes"));
} }
} else { } else {
$self->{object_info_facets}->SetLabel($object->facets); $self->{object_info_facets}->SetLabel($object->facets);
@ -2007,99 +1984,99 @@ sub object_menu {
my $frame = $self->GetFrame; my $frame = $self->GetFrame;
my $menu = Wx::Menu->new; my $menu = Wx::Menu->new;
my $accel = ($^O eq 'MSWin32') ? sub { $_[0] . "\t\xA0" . $_[1] } : sub { $_[0] }; my $accel = ($^O eq 'MSWin32') ? sub { $_[0] . "\t\xA0" . $_[1] } : sub { $_[0] };
$frame->_append_menu_item($menu, $accel->('Delete', 'Del'), 'Remove the selected object', sub { $frame->_append_menu_item($menu, $accel->(L('Delete'), 'Del'), L('Remove the selected object'), sub {
$self->remove; $self->remove;
}, undef, 'brick_delete.png'); }, undef, 'brick_delete.png');
$frame->_append_menu_item($menu, $accel->('Increase copies', '+'), 'Place one more copy of the selected object', sub { $frame->_append_menu_item($menu, $accel->(L('Increase copies'), '+'), L('Place one more copy of the selected object'), sub {
$self->increase; $self->increase;
}, undef, 'add.png'); }, undef, 'add.png');
$frame->_append_menu_item($menu, $accel->('Decrease copies', '-'), 'Remove one copy of the selected object', sub { $frame->_append_menu_item($menu, $accel->(L('Decrease copies'), '-'), L('Remove one copy of the selected object'), sub {
$self->decrease; $self->decrease;
}, undef, 'delete.png'); }, undef, 'delete.png');
$frame->_append_menu_item($menu, "Set number of copies…", 'Change the number of copies of the selected object', sub { $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; $self->set_number_of_copies;
}, undef, 'textfield.png'); }, undef, 'textfield.png');
$menu->AppendSeparator(); $menu->AppendSeparator();
$frame->_append_menu_item($menu, $accel->('Rotate 45° clockwise', 'l'), 'Rotate the selected object by 45° clockwise', sub { $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'); $self->rotate(-45, Z, 'relative');
}, undef, 'arrow_rotate_clockwise.png'); }, undef, 'arrow_rotate_clockwise.png');
$frame->_append_menu_item($menu, $accel->('Rotate 45° counter-clockwise', 'r'), 'Rotate the selected object by 45° counter-clockwise', sub { $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'); $self->rotate(+45, Z, 'relative');
}, undef, 'arrow_rotate_anticlockwise.png'); }, undef, 'arrow_rotate_anticlockwise.png');
my $rotateMenu = Wx::Menu->new; my $rotateMenu = Wx::Menu->new;
my $rotateMenuItem = $menu->AppendSubMenu($rotateMenu, "Rotate", 'Rotate the selected object by an arbitrary angle'); 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->_set_menu_item_icon($rotateMenuItem, 'textfield.png');
$frame->_append_menu_item($rotateMenu, "Around X axis…", 'Rotate the selected object by an arbitrary angle around X axis', sub { $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); $self->rotate(undef, X);
}, undef, 'bullet_red.png'); }, undef, 'bullet_red.png');
$frame->_append_menu_item($rotateMenu, "Around Y axis…", 'Rotate the selected object by an arbitrary angle around Y axis', sub { $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); $self->rotate(undef, Y);
}, undef, 'bullet_green.png'); }, undef, 'bullet_green.png');
$frame->_append_menu_item($rotateMenu, "Around Z axis…", 'Rotate the selected object by an arbitrary angle around Z axis', sub { $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); $self->rotate(undef, Z);
}, undef, 'bullet_blue.png'); }, undef, 'bullet_blue.png');
my $mirrorMenu = Wx::Menu->new; my $mirrorMenu = Wx::Menu->new;
my $mirrorMenuItem = $menu->AppendSubMenu($mirrorMenu, "Mirror", 'Mirror the selected object'); my $mirrorMenuItem = $menu->AppendSubMenu($mirrorMenu, L("Mirror"), L('Mirror the selected object'));
$frame->_set_menu_item_icon($mirrorMenuItem, 'shape_flip_horizontal.png'); $frame->_set_menu_item_icon($mirrorMenuItem, 'shape_flip_horizontal.png');
$frame->_append_menu_item($mirrorMenu, "Along X axis…", 'Mirror the selected object along the X axis', sub { $frame->_append_menu_item($mirrorMenu, L("Along X axis…"), L('Mirror the selected object along the X axis'), sub {
$self->mirror(X); $self->mirror(X);
}, undef, 'bullet_red.png'); }, undef, 'bullet_red.png');
$frame->_append_menu_item($mirrorMenu, "Along Y axis…", 'Mirror the selected object along the Y axis', sub { $frame->_append_menu_item($mirrorMenu, L("Along Y axis…"), L('Mirror the selected object along the Y axis'), sub {
$self->mirror(Y); $self->mirror(Y);
}, undef, 'bullet_green.png'); }, undef, 'bullet_green.png');
$frame->_append_menu_item($mirrorMenu, "Along Z axis…", 'Mirror the selected object along the Z axis', sub { $frame->_append_menu_item($mirrorMenu, L("Along Z axis…"), L('Mirror the selected object along the Z axis'), sub {
$self->mirror(Z); $self->mirror(Z);
}, undef, 'bullet_blue.png'); }, undef, 'bullet_blue.png');
my $scaleMenu = Wx::Menu->new; my $scaleMenu = Wx::Menu->new;
my $scaleMenuItem = $menu->AppendSubMenu($scaleMenu, "Scale", 'Scale the selected object along a single axis'); 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->_set_menu_item_icon($scaleMenuItem, 'arrow_out.png');
$frame->_append_menu_item($scaleMenu, $accel->('Uniformly…', 's'), 'Scale the selected object along the XYZ axes', sub { $frame->_append_menu_item($scaleMenu, $accel->(L('Uniformly…'), 's'), L('Scale the selected object along the XYZ axes'), sub {
$self->changescale(undef); $self->changescale(undef);
}); });
$frame->_append_menu_item($scaleMenu, "Along X axis…", 'Scale the selected object along the X axis', sub { $frame->_append_menu_item($scaleMenu, L("Along X axis…"), L('Scale the selected object along the X axis'), sub {
$self->changescale(X); $self->changescale(X);
}, undef, 'bullet_red.png'); }, undef, 'bullet_red.png');
$frame->_append_menu_item($scaleMenu, "Along Y axis…", 'Scale the selected object along the Y axis', sub { $frame->_append_menu_item($scaleMenu, L("Along Y axis…"), L('Scale the selected object along the Y axis'), sub {
$self->changescale(Y); $self->changescale(Y);
}, undef, 'bullet_green.png'); }, undef, 'bullet_green.png');
$frame->_append_menu_item($scaleMenu, "Along Z axis…", 'Scale the selected object along the Z axis', sub { $frame->_append_menu_item($scaleMenu, L("Along Z axis…"), L('Scale the selected object along the Z axis'), sub {
$self->changescale(Z); $self->changescale(Z);
}, undef, 'bullet_blue.png'); }, undef, 'bullet_blue.png');
my $scaleToSizeMenu = Wx::Menu->new; my $scaleToSizeMenu = Wx::Menu->new;
my $scaleToSizeMenuItem = $menu->AppendSubMenu($scaleToSizeMenu, "Scale to size", 'Scale the selected object along a single axis'); 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->_set_menu_item_icon($scaleToSizeMenuItem, 'arrow_out.png');
$frame->_append_menu_item($scaleToSizeMenu, "Uniformly…", 'Scale the selected object along the XYZ axes', sub { $frame->_append_menu_item($scaleToSizeMenu, L("Uniformly…"), L('Scale the selected object along the XYZ axes'), sub {
$self->changescale(undef, 1); $self->changescale(undef, 1);
}); });
$frame->_append_menu_item($scaleToSizeMenu, "Along X axis…", 'Scale the selected object along the X axis', sub { $frame->_append_menu_item($scaleToSizeMenu, L("Along X axis…"), L('Scale the selected object along the X axis'), sub {
$self->changescale(X, 1); $self->changescale(X, 1);
}, undef, 'bullet_red.png'); }, undef, 'bullet_red.png');
$frame->_append_menu_item($scaleToSizeMenu, "Along Y axis…", 'Scale the selected object along the Y axis', sub { $frame->_append_menu_item($scaleToSizeMenu, L("Along Y axis…"), L('Scale the selected object along the Y axis'), sub {
$self->changescale(Y, 1); $self->changescale(Y, 1);
}, undef, 'bullet_green.png'); }, undef, 'bullet_green.png');
$frame->_append_menu_item($scaleToSizeMenu, "Along Z axis…", 'Scale the selected object along the Z axis', sub { $frame->_append_menu_item($scaleToSizeMenu, L("Along Z axis…"), L('Scale the selected object along the Z axis'), sub {
$self->changescale(Z, 1); $self->changescale(Z, 1);
}, undef, 'bullet_blue.png'); }, undef, 'bullet_blue.png');
$frame->_append_menu_item($menu, "Split", 'Split the selected object into individual parts', sub { $frame->_append_menu_item($menu, L("Split"), L('Split the selected object into individual parts'), sub {
$self->split_object; $self->split_object;
}, undef, 'shape_ungroup.png'); }, undef, 'shape_ungroup.png');
$frame->_append_menu_item($menu, "Cut…", 'Open the 3D cutting tool', sub { $frame->_append_menu_item($menu, L("Cut…"), L('Open the 3D cutting tool'), sub {
$self->object_cut_dialog; $self->object_cut_dialog;
}, undef, 'package.png'); }, undef, 'package.png');
$menu->AppendSeparator(); $menu->AppendSeparator();
$frame->_append_menu_item($menu, "Settings…", 'Open the object editor dialog', sub { $frame->_append_menu_item($menu, L("Settings…"), L('Open the object editor dialog'), sub {
$self->object_settings_dialog; $self->object_settings_dialog;
}, undef, 'cog.png'); }, undef, 'cog.png');
$menu->AppendSeparator(); $menu->AppendSeparator();
$frame->_append_menu_item($menu, "Reload from Disk", 'Reload the selected file from Disk', sub { $frame->_append_menu_item($menu, L("Reload from Disk"), L('Reload the selected file from Disk'), sub {
$self->reload_from_disk; $self->reload_from_disk;
}, undef, 'arrow_refresh.png'); }, undef, 'arrow_refresh.png');
$frame->_append_menu_item($menu, "Export object as STL…", 'Export this single object as STL file', sub { $frame->_append_menu_item($menu, L("Export object as STL…"), L('Export this single object as STL file'), sub {
$self->export_object_stl; $self->export_object_stl;
}, undef, 'brick_go.png'); }, undef, 'brick_go.png');
@ -2110,8 +2087,8 @@ sub object_menu {
sub select_view { sub select_view {
my ($self, $direction) = @_; my ($self, $direction) = @_;
my $idx_page = $self->{preview_notebook}->GetSelection; my $idx_page = $self->{preview_notebook}->GetSelection;
my $page = ($idx_page == &Wx::wxNOT_FOUND) ? '3D' : $self->{preview_notebook}->GetPageText($idx_page); my $page = ($idx_page == &Wx::wxNOT_FOUND) ? L('3D') : $self->{preview_notebook}->GetPageText($idx_page);
if ($page eq 'Preview') { if ($page eq L('Preview')) {
$self->{preview3D}->canvas->select_view($direction); $self->{preview3D}->canvas->select_view($direction);
$self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas); $self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas);
} else { } else {

View file

@ -13,6 +13,8 @@ use Wx qw(wxTheApp :misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL);
use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE); use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE);
use base 'Wx::Panel'; use base 'Wx::Panel';
use Wx::Locale gettext => 'L';
sub new { sub new {
my $class = shift; my $class = shift;
my ($parent, $size, $objects, $model, $config) = @_; my ($parent, $size, $objects, $model, $config) = @_;
@ -126,8 +128,8 @@ sub repaint {
$dc->SetFont(Wx::Font->new(14, wxDEFAULT, wxNORMAL, wxNORMAL)); $dc->SetFont(Wx::Font->new(14, wxDEFAULT, wxNORMAL, wxNORMAL));
$dc->DrawLabel( $dc->DrawLabel(
join('-', +(localtime)[3,4]) eq '13-8' join('-', +(localtime)[3,4]) eq '13-8'
? 'What do you want to print today? ™' # Sept. 13, 2006. The first part ever printed by a RepRap to make another RepRap. ? L('What do you want to print today? ™') # Sept. 13, 2006. The first part ever printed by a RepRap to make another RepRap.
: 'Drag your objects here', : L('Drag your objects here'),
Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL); Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
} }

View file

@ -338,7 +338,6 @@ sub Render {
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
if (!$self->GetParent->enabled || !$self->layers) { if (!$self->GetParent->enabled || !$self->layers) {
glFlush();
$self->SwapBuffers; $self->SwapBuffers;
return; return;
} }
@ -486,7 +485,6 @@ sub Render {
} }
gluDeleteTess($tess) if $tess; gluDeleteTess($tess) if $tess;
glFlush();
$self->SwapBuffers; $self->SwapBuffers;
} }

View file

@ -8,6 +8,8 @@ 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); use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN EVT_CHECKBOX EVT_CHOICE EVT_CHECKLISTBOX);
use base qw(Wx::Panel Class::Accessor); 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 auto_zoom)); __PACKAGE__->mk_accessors(qw(print gcode_preview_data enabled _loaded canvas slider_low slider_high single_layer auto_zoom));
sub new { sub new {
@ -58,34 +60,42 @@ sub new {
$z_label_high->SetFont($Slic3r::GUI::small_font); $z_label_high->SetFont($Slic3r::GUI::small_font);
$self->single_layer(0); $self->single_layer(0);
my $checkbox_singlelayer = $self->{checkbox_singlelayer} = Wx::CheckBox->new($self, -1, "1 Layer"); 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, "View"); 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); my $choice_view_type = $self->{choice_view_type} = Wx::Choice->new($self, -1);
$choice_view_type->Append("Feature type"); $choice_view_type->Append(L("Feature type"));
$choice_view_type->Append("Height"); $choice_view_type->Append(L("Height"));
$choice_view_type->Append("Width"); $choice_view_type->Append(L("Width"));
$choice_view_type->Append("Speed"); $choice_view_type->Append(L("Speed"));
$choice_view_type->Append("Tool"); $choice_view_type->Append(L("Tool"));
$choice_view_type->SetSelection(0); $choice_view_type->SetSelection(0);
my $label_show_features = $self->{label_show_features} = Wx::StaticText->new($self, -1, "Show"); my $label_show_features = $self->{label_show_features} = Wx::StaticText->new($self, -1, L("Show"));
my $combochecklist_features = $self->{combochecklist_features} = Wx::ComboCtrl->new(); my $combochecklist_features = $self->{combochecklist_features} = Wx::ComboCtrl->new();
$combochecklist_features->Create($self, -1, "Feature types", wxDefaultPosition, [200, -1], wxCB_READONLY); $combochecklist_features->Create($self, -1, L("Feature types"), wxDefaultPosition, [200, -1], wxCB_READONLY);
#FIXME If the following line is removed, the combo box popup list will not react to mouse clicks. my $feature_text = L("Feature types");
# On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10. my $feature_items = L("Perimeter")."|"
$combochecklist_features->UseAltPopupWindow(); .L("External perimeter")."|"
$combochecklist_features->EnablePopupAnimation(0); .L("Overhang perimeter")."|"
my $feature_text = "Feature types"; .L("Internal infill")."|"
my $feature_items = "Perimeter|External perimeter|Overhang perimeter|Internal infill|Solid infill|Top solid infill|Bridge infill|Gap fill|Skirt|Support material|Support material interface|Wipe tower"; .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); Slic3r::GUI::create_combochecklist($combochecklist_features, $feature_text, $feature_items, 1);
my $checkbox_travel = $self->{checkbox_travel} = Wx::CheckBox->new($self, -1, "Travel"); my $checkbox_travel = $self->{checkbox_travel} = Wx::CheckBox->new($self, -1, L("Travel"));
my $checkbox_retractions = $self->{checkbox_retractions} = Wx::CheckBox->new($self, -1, "Retractions"); my $checkbox_retractions = $self->{checkbox_retractions} = Wx::CheckBox->new($self, -1, L("Retractions"));
my $checkbox_unretractions = $self->{checkbox_unretractions} = Wx::CheckBox->new($self, -1, "Unretractions"); my $checkbox_unretractions = $self->{checkbox_unretractions} = Wx::CheckBox->new($self, -1, L("Unretractions"));
my $checkbox_shells = $self->{checkbox_shells} = Wx::CheckBox->new($self, -1, "Shells"); my $checkbox_shells = $self->{checkbox_shells} = Wx::CheckBox->new($self, -1, L("Shells"));
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
my $vsizer = Wx::BoxSizer->new(wxVERTICAL); my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
@ -255,6 +265,7 @@ sub new {
'Support material' => '00FF00', 'Support material' => '00FF00',
'Support material interface' => '008000', 'Support material interface' => '008000',
'Wipe tower' => 'B3E3AB', 'Wipe tower' => 'B3E3AB',
'Custom' => 'FFFF00',
); );
$self->gcode_preview_data->set_extrusion_paths_colors(\@extrusion_roles_colors); $self->gcode_preview_data->set_extrusion_paths_colors(\@extrusion_roles_colors);
@ -322,31 +333,9 @@ sub load_print {
return; return;
} }
my $z_idx_low = $self->slider_low->GetValue; # used to set the sliders to the extremes of the current zs range
my $z_idx_high = $self->slider_high->GetValue; $self->{force_sliders_full_range} = 0;
$self->enabled(1);
$self->slider_low->SetRange(0, $n_layers - 1);
$self->slider_high->SetRange(0, $n_layers - 1);
if ($z_idx_high < $n_layers && ($self->single_layer || $z_idx_high != 0)) {
# use $z_idx
} else {
# 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;
}
if ($self->single_layer) {
$z_idx_low = $z_idx_high;
} elsif ($z_idx_low > $z_idx_high) {
$z_idx_low = 0;
}
$self->slider_low->SetValue($z_idx_low);
$self->slider_high->SetValue($z_idx_high);
$self->slider_low->Show;
$self->slider_high->Show;
$self->Layout;
if ($self->{preferred_color_mode} eq 'tool_or_feature') { 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. # 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. # Color by feature if it is a single extruder print.
@ -384,16 +373,58 @@ sub load_print {
} }
$self->show_hide_ui_elements('simple'); $self->show_hide_ui_elements('simple');
} else { } else {
$self->{force_sliders_full_range} = (scalar(@{$self->canvas->volumes}) == 0) && $self->auto_zoom;
$self->canvas->load_gcode_preview($self->print, $self->gcode_preview_data, \@colors); $self->canvas->load_gcode_preview($self->print, $self->gcode_preview_data, \@colors);
$self->show_hide_ui_elements('full'); $self->show_hide_ui_elements('full');
# recalculates zs and update sliders accordingly
$self->{layers_z} = $self->canvas->get_current_print_zs();
$n_layers = scalar(@{$self->{layers_z}});
} }
$self->update_sliders($n_layers);
if ($self->auto_zoom) { if ($self->auto_zoom) {
$self->canvas->zoom_to_volumes; $self->canvas->zoom_to_volumes;
} }
$self->_loaded(1); $self->_loaded(1);
} }
}
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)) {
# use $z_idx
} else {
# 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;
}
if ($self->single_layer) {
$z_idx_low = $z_idx_high;
} elsif ($z_idx_low > $z_idx_high) {
$z_idx_low = 0;
}
$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]); $self->set_z_range($self->{layers_z}[$z_idx_low], $self->{layers_z}[$z_idx_high]);
$self->Layout;
} }
sub set_z_range sub set_z_range

View file

@ -1,115 +0,0 @@
# Preferences dialog, opens from Menu: File->Preferences
package Slic3r::GUI::Preferences;
use Wx qw(:dialog :id :misc :sizer :systemsettings wxTheApp);
use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER);
use base 'Wx::Dialog';
sub new {
my ($class, $parent) = @_;
my $self = $class->SUPER::new($parent, -1, "Preferences", wxDefaultPosition, wxDefaultSize);
$self->{values} = {};
my $app_config = wxTheApp->{app_config};
my $optgroup;
$optgroup = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'General',
on_change => sub {
my ($opt_id) = @_;
$self->{values}{$opt_id} = $optgroup->get_value($opt_id);
},
label_width => 200,
);
# $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
# opt_id => 'version_check',
# type => 'bool',
# label => 'Check for updates',
# tooltip => 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.',
# default => $app_config->get("version_check") // 1,
# readonly => !wxTheApp->have_version_check,
# ));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'remember_output_path',
type => 'bool',
label => 'Remember output directory',
tooltip => 'If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files.',
default => $app_config->get("remember_output_path"),
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'autocenter',
type => 'bool',
label => 'Auto-center parts',
tooltip => 'If this is enabled, Slic3r will auto-center objects around the print bed center.',
default => $app_config->get("autocenter"),
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'background_processing',
type => 'bool',
label => 'Background processing',
tooltip => 'If this is enabled, Slic3r will pre-process objects as soon as they\'re loaded in order to save time when exporting G-code.',
default => $app_config->get("background_processing"),
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'no_controller',
type => 'bool',
label => 'Disable USB/serial connection',
tooltip => 'Disable communication with the printer over a serial / USB cable. This simplifies the user interface in case the printer is never attached to the computer.',
default => $app_config->get("no_controller"),
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'no_defaults',
type => 'bool',
label => 'Suppress "- default -" presets',
tooltip => 'Suppress "- default -" presets in the Print / Filament / Printer selections once there are any other valid presets available.',
default => $app_config->get("no_defaults"),
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'show_incompatible_presets',
type => 'bool',
label => 'Show incompatible print and filament presets',
tooltip => 'When checked, the print and filament presets are shown in the preset editor even ' .
'if they are marked as incompatible with the active printer',
default => $app_config->get("show_incompatible_presets"),
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'use_legacy_opengl',
type => 'bool',
label => 'Use legacy OpenGL 1.1 rendering',
tooltip => 'If you have rendering issues caused by a buggy OpenGL 2.0 driver, you may try to check this checkbox. This will disable the layer height editing and anti aliasing, so it is likely better to upgrade your graphics driver.',
default => $app_config->get("use_legacy_opengl"),
));
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
my $buttons = $self->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
EVT_BUTTON($self, wxID_OK, sub { $self->_accept });
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
$self->SetSizer($sizer);
$sizer->SetSizeHints($self);
return $self;
}
sub _accept {
my ($self) = @_;
if (defined($self->{values}{no_controller}) ||
defined($self->{values}{no_defaults}) ||
defined($self->{values}{use_legacy_opengl})) {
Slic3r::GUI::warning_catcher($self)->("You need to restart Slic3r to make the changes effective.");
}
my $app_config = wxTheApp->{app_config};
$app_config->set($_, $self->{values}{$_}) for keys %{$self->{values}};
$self->EndModal(wxID_OK);
$self->Close; # needed on Linux
# Nothify the UI to update itself from the ini file.
wxTheApp->update_ui_from_settings;
}
1;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,18 @@
c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp
c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.hpp
c:\src\Slic3r\xs\src\slic3r\GUI\GUI.cpp
c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp
c:\src\Slic3r\xs\src\slic3r\GUI\Tab.hpp
c:\src\Slic3r\xs\src\slic3r\GUI\Field.cpp
c:\src\Slic3r\xs\src\slic3r\GUI\OptionsGroup.cpp
c:\src\Slic3r\xs\src\slic3r\GUI\2DBed.cpp
c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp
c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.hpp
c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp
C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp
c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp
c:\src\Slic3r\lib\Slic3r\GUI.pm
c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm
c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm
c:\src\Slic3r\lib\Slic3r\GUI\Plater\2D.pm
c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -64,6 +64,8 @@ add_library(libslic3r STATIC
${LIBDIR}/libslic3r/Fill/FillConcentric.hpp ${LIBDIR}/libslic3r/Fill/FillConcentric.hpp
${LIBDIR}/libslic3r/Fill/FillHoneycomb.cpp ${LIBDIR}/libslic3r/Fill/FillHoneycomb.cpp
${LIBDIR}/libslic3r/Fill/FillHoneycomb.hpp ${LIBDIR}/libslic3r/Fill/FillHoneycomb.hpp
${LIBDIR}/libslic3r/Fill/FillGyroid.cpp
${LIBDIR}/libslic3r/Fill/FillGyroid.hpp
${LIBDIR}/libslic3r/Fill/FillPlanePath.cpp ${LIBDIR}/libslic3r/Fill/FillPlanePath.cpp
${LIBDIR}/libslic3r/Fill/FillPlanePath.hpp ${LIBDIR}/libslic3r/Fill/FillPlanePath.hpp
${LIBDIR}/libslic3r/Fill/FillRectilinear.cpp ${LIBDIR}/libslic3r/Fill/FillRectilinear.cpp
@ -173,6 +175,8 @@ add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/GUI/3DScene.hpp ${LIBDIR}/slic3r/GUI/3DScene.hpp
${LIBDIR}/slic3r/GUI/GLShader.cpp ${LIBDIR}/slic3r/GUI/GLShader.cpp
${LIBDIR}/slic3r/GUI/GLShader.hpp ${LIBDIR}/slic3r/GUI/GLShader.hpp
${LIBDIR}/slic3r/GUI/Preferences.cpp
${LIBDIR}/slic3r/GUI/Preferences.hpp
${LIBDIR}/slic3r/GUI/Preset.cpp ${LIBDIR}/slic3r/GUI/Preset.cpp
${LIBDIR}/slic3r/GUI/Preset.hpp ${LIBDIR}/slic3r/GUI/Preset.hpp
${LIBDIR}/slic3r/GUI/PresetBundle.cpp ${LIBDIR}/slic3r/GUI/PresetBundle.cpp
@ -199,7 +203,12 @@ add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/GUI/WipeTowerDialog.hpp ${LIBDIR}/slic3r/GUI/WipeTowerDialog.hpp
${LIBDIR}/slic3r/GUI/RammingChart.cpp ${LIBDIR}/slic3r/GUI/RammingChart.cpp
${LIBDIR}/slic3r/GUI/RammingChart.hpp ${LIBDIR}/slic3r/GUI/RammingChart.hpp
${LIBDIR}/slic3r/Utils/Http.cpp
${LIBDIR}/slic3r/Utils/Http.hpp
${LIBDIR}/slic3r/Utils/OctoPrint.cpp
${LIBDIR}/slic3r/Utils/OctoPrint.hpp
${LIBDIR}/slic3r/Utils/Bonjour.cpp
${LIBDIR}/slic3r/Utils/Bonjour.hpp
) )
add_library(admesh STATIC add_library(admesh STATIC
@ -339,6 +348,7 @@ set(XS_XSP_FILES
${XSP_DIR}/Surface.xsp ${XSP_DIR}/Surface.xsp
${XSP_DIR}/SurfaceCollection.xsp ${XSP_DIR}/SurfaceCollection.xsp
${XSP_DIR}/TriangleMesh.xsp ${XSP_DIR}/TriangleMesh.xsp
${XSP_DIR}/Utils_OctoPrint.xsp
${XSP_DIR}/XS.xsp ${XSP_DIR}/XS.xsp
) )
foreach (file ${XS_XSP_FILES}) foreach (file ${XS_XSP_FILES})
@ -476,6 +486,7 @@ if(SLIC3R_STATIC)
# Use boost libraries linked statically to the C++ runtime. # Use boost libraries linked statically to the C++ runtime.
# set(Boost_USE_STATIC_RUNTIME ON) # set(Boost_USE_STATIC_RUNTIME ON)
endif() endif()
#set(Boost_DEBUG ON)
find_package(Boost REQUIRED COMPONENTS system filesystem thread log locale regex) find_package(Boost REQUIRED COMPONENTS system filesystem thread log locale regex)
if(Boost_FOUND) if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS}) include_directories(${Boost_INCLUDE_DIRS})
@ -523,6 +534,27 @@ if (SLIC3R_PRUSACONTROL)
target_link_libraries(XS ${wxWidgets_LIBRARIES}) target_link_libraries(XS ${wxWidgets_LIBRARIES})
endif() endif()
find_package(CURL REQUIRED)
include_directories(${CURL_INCLUDE_DIRS})
target_link_libraries(XS ${CURL_LIBRARIES})
if (SLIC3R_STATIC)
if (NOT APPLE)
# libcurl is always linked dynamically to the system libcurl on OSX.
# On other systems, libcurl is linked statically if SLIC3R_STATIC is set.
add_definitions(-DCURL_STATICLIB)
endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
# As of now, our build system produces a statically linked libcurl,
# which links the OpenSSL library dynamically.
find_package(OpenSSL REQUIRED)
message("OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}")
message("OpenSSL libraries: ${OPENSSL_LIBRARIES}")
include_directories(${OPENSSL_INCLUDE_DIR})
target_link_libraries(XS ${OPENSSL_LIBRARIES})
endif()
endif()
## OPTIONAL packages ## OPTIONAL packages
# Find eigen3 or use bundled version # Find eigen3 or use bundled version
@ -597,6 +629,17 @@ elseif (NOT MSVC)
target_link_libraries(slic3r -lstdc++) target_link_libraries(slic3r -lstdc++)
endif () endif ()
if (MSVC)
# Here we associate some additional properties with the MSVC projects to enable compilation and debugging out of the box.
# It seems a props file needs to be copied to the same dir as the proj file, otherwise MSVC doesn't load it up.
# For copying, the configure_file() function seems to work much better than the file() function.
configure_file("${PROJECT_SOURCE_DIR}/cmake/msvc/xs.wperl64d.props" ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
set_target_properties(XS PROPERTIES VS_USER_PROPS "xs.wperl64d.props")
configure_file("${PROJECT_SOURCE_DIR}/cmake/msvc/slic3r.wperl64d.props" ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
set_target_properties(slic3r PROPERTIES VS_USER_PROPS "slic3r.wperl64d.props")
endif ()
# Installation # Installation
install(TARGETS XS DESTINATION lib/slic3r-prusa3d/auto/Slic3r/XS) install(TARGETS XS DESTINATION ${PERL_VENDORARCH}/auto/Slic3r/XS)
install(FILES lib/Slic3r/XS.pm DESTINATION lib/slic3r-prusa3d/Slic3r) install(FILES lib/Slic3r/XS.pm DESTINATION ${PERL_VENDORLIB}/Slic3r)

View file

@ -107,6 +107,11 @@ stl_fix_normal_directions(stl_file *stl) {
struct stl_normal *newn; struct stl_normal *newn;
struct stl_normal *temp; struct stl_normal *temp;
int* reversed_ids;
int reversed_count = 0;
int id;
int force_exit = 0;
if (stl->error) return; if (stl->error) return;
/* Initialize linked list. */ /* Initialize linked list. */
@ -121,13 +126,18 @@ stl_fix_normal_directions(stl_file *stl) {
norm_sw = (char*)calloc(stl->stats.number_of_facets, sizeof(char)); norm_sw = (char*)calloc(stl->stats.number_of_facets, sizeof(char));
if(norm_sw == NULL) perror("stl_fix_normal_directions"); if(norm_sw == NULL) perror("stl_fix_normal_directions");
/* Initialize list that keeps track of reversed facets. */
reversed_ids = (int*)calloc(stl->stats.number_of_facets, sizeof(int));
if (reversed_ids == NULL) perror("stl_fix_normal_directions reversed_ids");
facet_num = 0; facet_num = 0;
/* If normal vector is not within tolerance and backwards: /* If normal vector is not within tolerance and backwards:
Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances
of it being wrong randomly are low if most of the triangles are right: */ of it being wrong randomly are low if most of the triangles are right: */
if(stl_check_normal_vector(stl, 0, 0) == 2) if (stl_check_normal_vector(stl, 0, 0) == 2) {
stl_reverse_facet(stl, 0); stl_reverse_facet(stl, 0);
reversed_ids[reversed_count++] = 0;
}
/* Say that we've fixed this facet: */ /* Say that we've fixed this facet: */
norm_sw[facet_num] = 1; norm_sw[facet_num] = 1;
@ -140,8 +150,19 @@ stl_fix_normal_directions(stl_file *stl) {
/* Reverse the neighboring facets if necessary. */ /* Reverse the neighboring facets if necessary. */
if(stl->neighbors_start[facet_num].which_vertex_not[j] > 2) { if(stl->neighbors_start[facet_num].which_vertex_not[j] > 2) {
/* If the facet has a neighbor that is -1, it means that edge isn't shared by another facet */ /* If the facet has a neighbor that is -1, it means that edge isn't shared by another facet */
if(stl->neighbors_start[facet_num].neighbor[j] != -1) if(stl->neighbors_start[facet_num].neighbor[j] != -1) {
stl_reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]); if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] == 1) {
/* trying to modify a facet already marked as fixed, revert all changes made until now and exit (fixes: #716, #574, #413, #269, #262, #259, #230, #228, #206) */
for (id = reversed_count - 1; id >= 0; --id) {
stl_reverse_facet(stl, reversed_ids[id]);
}
force_exit = 1;
break;
} else {
stl_reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]);
reversed_ids[reversed_count++] = stl->neighbors_start[facet_num].neighbor[j];
}
}
} }
/* If this edge of the facet is connected: */ /* If this edge of the facet is connected: */
if(stl->neighbors_start[facet_num].neighbor[j] != -1) { if(stl->neighbors_start[facet_num].neighbor[j] != -1) {
@ -156,6 +177,10 @@ stl_fix_normal_directions(stl_file *stl) {
} }
} }
} }
/* an error occourred, quit the for loop and exit */
if (force_exit) break;
/* Get next facet to fix from top of list. */ /* Get next facet to fix from top of list. */
if(head->next != tail) { if(head->next != tail) {
facet_num = head->next->facet_num; facet_num = head->next->facet_num;
@ -179,7 +204,8 @@ stl_fix_normal_directions(stl_file *stl) {
/* This is the first facet of the next part. */ /* This is the first facet of the next part. */
facet_num = i; facet_num = i;
if(stl_check_normal_vector(stl, i, 0) == 2) { if(stl_check_normal_vector(stl, i, 0) == 2) {
stl_reverse_facet(stl, i); stl_reverse_facet(stl, i);
reversed_ids[reversed_count++] = i;
} }
norm_sw[facet_num] = 1; norm_sw[facet_num] = 1;
@ -192,6 +218,7 @@ stl_fix_normal_directions(stl_file *stl) {
} }
free(head); free(head);
free(tail); free(tail);
free(reversed_ids);
free(norm_sw); free(norm_sw);
} }

View file

@ -26,6 +26,7 @@ enum ExtrusionRole {
erSupportMaterial, erSupportMaterial,
erSupportMaterialInterface, erSupportMaterialInterface,
erWipeTower, erWipeTower,
erCustom,
// Extrusion role for a collection with multiple extrusion roles. // Extrusion role for a collection with multiple extrusion roles.
erMixed, erMixed,
}; };

View file

@ -8,6 +8,7 @@
#include "FillConcentric.hpp" #include "FillConcentric.hpp"
#include "FillHoneycomb.hpp" #include "FillHoneycomb.hpp"
#include "Fill3DHoneycomb.hpp" #include "Fill3DHoneycomb.hpp"
#include "FillGyroid.hpp"
#include "FillPlanePath.hpp" #include "FillPlanePath.hpp"
#include "FillRectilinear.hpp" #include "FillRectilinear.hpp"
#include "FillRectilinear2.hpp" #include "FillRectilinear2.hpp"
@ -21,6 +22,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
case ipConcentric: return new FillConcentric(); case ipConcentric: return new FillConcentric();
case ipHoneycomb: return new FillHoneycomb(); case ipHoneycomb: return new FillHoneycomb();
case ip3DHoneycomb: return new Fill3DHoneycomb(); case ip3DHoneycomb: return new Fill3DHoneycomb();
case ipGyroid: return new FillGyroid();
case ipRectilinear: return new FillRectilinear2(); case ipRectilinear: return new FillRectilinear2();
// case ipRectilinear: return new FillRectilinear(); // case ipRectilinear: return new FillRectilinear();
case ipLine: return new FillLine(); case ipLine: return new FillLine();

View file

@ -0,0 +1,150 @@
#include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp"
#include "../Surface.hpp"
#include <cmath>
#include <algorithm>
#include <iostream>
#include "FillGyroid.hpp"
namespace Slic3r {
static inline Polyline make_wave_vertical(
double width, double height, double x0,
double segmentSize, double scaleFactor,
double z_cos, double z_sin, bool flip)
{
Polyline polyline;
polyline.points.emplace_back(Point(coord_t(clamp(0., width, x0) * scaleFactor), 0));
double phase_offset_sin = (z_cos < 0 ? M_PI : 0) + M_PI;
double phase_offset_cos = (z_cos < 0 ? M_PI : 0) + M_PI + (flip ? M_PI : 0.);
for (double y = 0.; y < height + segmentSize; y += segmentSize) {
y = std::min(y, height);
double a = sin(y + phase_offset_sin);
double b = - z_cos;
double res = z_sin * cos(y + phase_offset_cos);
double r = sqrt(sqr(a) + sqr(b));
double x = clamp(0., width, asin(a/r) + asin(res/r) + M_PI + x0);
polyline.points.emplace_back(convert_to<Point>(Pointf(x, y) * scaleFactor));
}
if (flip)
std::reverse(polyline.points.begin(), polyline.points.end());
return polyline;
}
static inline Polyline make_wave_horizontal(
double width, double height, double y0,
double segmentSize, double scaleFactor,
double z_cos, double z_sin, bool flip)
{
Polyline polyline;
polyline.points.emplace_back(Point(0, coord_t(clamp(0., height, y0) * scaleFactor)));
double phase_offset_sin = (z_sin < 0 ? M_PI : 0) + (flip ? 0 : M_PI);
double phase_offset_cos = z_sin < 0 ? M_PI : 0.;
for (double x=0.; x < width + segmentSize; x += segmentSize) {
x = std::min(x, width);
double a = cos(x + phase_offset_cos);
double b = - z_sin;
double res = z_cos * sin(x + phase_offset_sin);
double r = sqrt(sqr(a) + sqr(b));
double y = clamp(0., height, asin(a/r) + asin(res/r) + 0.5 * M_PI + y0);
polyline.points.emplace_back(convert_to<Point>(Pointf(x, y) * scaleFactor));
}
if (flip)
std::reverse(polyline.points.begin(), polyline.points.end());
return polyline;
}
static Polylines make_gyroid_waves(double gridZ, double density, double layer_width, double width, double height)
{
double scaleFactor = scale_(layer_width) / density;
double segmentSize = 0.5 * density;
//scale factor for 5% : 8 712 388
// 1z = 10^-6 mm ?
double z = gridZ / scaleFactor;
double z_sin = sin(z);
double z_cos = cos(z);
Polylines result;
if (abs(z_sin) <= abs(z_cos)) {
// Vertical wave
double x0 = M_PI * (int)((- 0.5 * M_PI) / M_PI - 1.);
bool flip = ((int)(x0 / M_PI + 1.) & 1) != 0;
for (; x0 < width - 0.5 * M_PI; x0 += M_PI, flip = ! flip)
result.emplace_back(make_wave_vertical(width, height, x0, segmentSize, scaleFactor, z_cos, z_sin, flip));
} else {
// Horizontal wave
bool flip = true;
for (double y0 = 0.; y0 < width; y0 += M_PI, flip = !flip)
result.emplace_back(make_wave_horizontal(width, height, y0, segmentSize, scaleFactor, z_cos, z_sin, flip));
}
return result;
}
void FillGyroid::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out)
{
// no rotation is supported for this infill pattern
BoundingBox bb = expolygon.contour.bounding_box();
coord_t distance = coord_t(scale_(this->spacing) / (params.density*this->scaling));
// align bounding box to a multiple of our grid module
bb.merge(_align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
// generate pattern
Polylines polylines = make_gyroid_waves(
scale_(this->z),
params.density*this->scaling,
this->spacing,
ceil(bb.size().x / distance) + 1.,
ceil(bb.size().y / distance) + 1.);
// move pattern in place
for (Polyline &polyline : polylines)
polyline.translate(bb.min.x, bb.min.y);
// clip pattern to boundaries
polylines = intersection_pl(polylines, (Polygons)expolygon);
// connect lines
if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections
ExPolygon expolygon_off;
{
ExPolygons expolygons_off = offset_ex(expolygon, (float)SCALED_EPSILON);
if (! expolygons_off.empty()) {
// When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island.
assert(expolygons_off.size() == 1);
std::swap(expolygon_off, expolygons_off.front());
}
}
Polylines chained = PolylineCollection::chained_path_from(
std::move(polylines),
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
bool first = true;
for (Polyline &polyline : chained) {
if (! first) {
// Try to connect the lines.
Points &pts_end = polylines_out.back().points;
const Point &first_point = polyline.points.front();
const Point &last_point = pts_end.back();
// TODO: we should also check that both points are on a fill_boundary to avoid
// connecting paths on the boundaries of internal regions
// TODO: avoid crossing current infill path
if (first_point.distance_to(last_point) <= 5 * distance &&
expolygon_off.contains(Line(last_point, first_point))) {
// Append the polyline.
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
continue;
}
}
// The lines cannot be connected.
polylines_out.emplace_back(std::move(polyline));
first = false;
}
}
}
} // namespace Slic3r

View file

@ -0,0 +1,34 @@
#ifndef slic3r_FillGyroid_hpp_
#define slic3r_FillGyroid_hpp_
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class FillGyroid : public Fill
{
public:
FillGyroid() {}
virtual Fill* clone() const { return new FillGyroid(*this); }
// require bridge flow since most of this pattern hangs in air
virtual bool use_bridge_flow() const { return true; }
protected:
// mult of density, to have a good %of weight for each density parameter
float scaling = 1.75;
virtual void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out);
};
} // namespace Slic3r
#endif // slic3r_FillGyroid_hpp_

View file

@ -587,6 +587,15 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true); this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
// Set extruder(s) temperature before and after start G-code. // Set extruder(s) temperature before and after start G-code.
this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false); this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false);
if (m_enable_analyzer)
{
// adds tag for analyzer
char buf[32];
sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
_writeln(file, buf);
}
// Write the custom start G-code // Write the custom start G-code
_writeln(file, start_gcode); _writeln(file, start_gcode);
// Process filament-specific gcode in extruder order. // Process filament-specific gcode in extruder order.
@ -770,6 +779,15 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
// Write end commands to file. // Write end commands to file.
_write(file, this->retract()); _write(file, this->retract());
_write(file, m_writer.set_fan(false)); _write(file, m_writer.set_fan(false));
if (m_enable_analyzer)
{
// adds tag for analyzer
char buf[32];
sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
_writeln(file, buf);
}
// Process filament-specific gcode in extruder order. // Process filament-specific gcode in extruder order.
if (print.config.single_extruder_multi_material) { if (print.config.single_extruder_multi_material) {
// Process the end_filament_gcode for the active filament only. // Process the end_filament_gcode for the active filament only.
@ -1384,18 +1402,13 @@ void GCode::apply_print_config(const PrintConfig &print_config)
void GCode::append_full_config(const Print& print, std::string& str) void GCode::append_full_config(const Print& print, std::string& str)
{ {
char buff[4096];
const StaticPrintConfig *configs[] = { &print.config, &print.default_object_config, &print.default_region_config }; const StaticPrintConfig *configs[] = { &print.config, &print.default_object_config, &print.default_region_config };
for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++i) { for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++i) {
const StaticPrintConfig *cfg = configs[i]; const StaticPrintConfig *cfg = configs[i];
for (const std::string &key : cfg->keys()) for (const std::string &key : cfg->keys())
{ {
if (key != "compatible_printers") if (key != "compatible_printers")
{ str += "; " + key + " = " + cfg->serialize(key) + "\n";
sprintf(buff, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str());
str += buff;
}
} }
} }
} }

View file

@ -150,7 +150,13 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi
{ {
// processes 'special' comments contained in line // processes 'special' comments contained in line
if (_process_tags(line)) if (_process_tags(line))
{
#if 0
// DEBUG ONLY: puts the line back into the gcode
m_process_output += line.raw() + "\n";
#endif
return; return;
}
// sets new start position/extrusion // sets new start position/extrusion
_set_start_position(_get_end_position()); _set_start_position(_get_end_position());

View file

@ -1,6 +1,8 @@
#include "Analyzer.hpp" #include "Analyzer.hpp"
#include "PreviewData.hpp" #include "PreviewData.hpp"
#include <float.h> #include <float.h>
#include <wx/intl.h>
#include "slic3r/GUI/GUI.hpp"
namespace Slic3r { namespace Slic3r {
@ -125,26 +127,28 @@ const GCodePreviewData::Color GCodePreviewData::Extrusion::Default_Extrusion_Rol
Color(0.0f, 0.5f, 0.0f, 1.0f), // erSupportMaterial Color(0.0f, 0.5f, 0.0f, 1.0f), // erSupportMaterial
Color(0.0f, 0.0f, 0.5f, 1.0f), // erSupportMaterialInterface Color(0.0f, 0.0f, 0.5f, 1.0f), // erSupportMaterialInterface
Color(0.7f, 0.89f, 0.67f, 1.0f), // erWipeTower Color(0.7f, 0.89f, 0.67f, 1.0f), // erWipeTower
Color(1.0f, 1.0f, 0.0f, 1.0f), // erCustom
Color(0.0f, 0.0f, 0.0f, 1.0f) // erMixed Color(0.0f, 0.0f, 0.0f, 1.0f) // erMixed
}; };
// todo: merge with Slic3r::ExtrusionRole2String() from GCode.cpp // todo: merge with Slic3r::ExtrusionRole2String() from GCode.cpp
const std::string GCodePreviewData::Extrusion::Default_Extrusion_Role_Names[Num_Extrusion_Roles] const std::string GCodePreviewData::Extrusion::Default_Extrusion_Role_Names[Num_Extrusion_Roles]
{ {
"None", L("None"),
"Perimeter", L("Perimeter"),
"External perimeter", L("External perimeter"),
"Overhang perimeter", L("Overhang perimeter"),
"Internal infill", L("Internal infill"),
"Solid infill", L("Solid infill"),
"Top solid infill", L("Top solid infill"),
"Bridge infill", L("Bridge infill"),
"Gap fill", L("Gap fill"),
"Skirt", L("Skirt"),
"Support material", L("Support material"),
"Support material interface", L("Support material interface"),
"Wipe tower", L("Wipe tower"),
"Mixed" L("Custom"),
L("Mixed")
}; };
const GCodePreviewData::Extrusion::EViewType GCodePreviewData::Extrusion::Default_View_Type = GCodePreviewData::Extrusion::FeatureType; const GCodePreviewData::Extrusion::EViewType GCodePreviewData::Extrusion::Default_View_Type = GCodePreviewData::Extrusion::FeatureType;
@ -323,15 +327,15 @@ std::string GCodePreviewData::get_legend_title() const
switch (extrusion.view_type) switch (extrusion.view_type)
{ {
case Extrusion::FeatureType: case Extrusion::FeatureType:
return "Feature type"; return L("Feature type");
case Extrusion::Height: case Extrusion::Height:
return "Height (mm)"; return L("Height (mm)");
case Extrusion::Width: case Extrusion::Width:
return "Width (mm)"; return L("Width (mm)");
case Extrusion::Feedrate: case Extrusion::Feedrate:
return "Speed (mm/s)"; return L("Speed (mm/s)");
case Extrusion::Tool: case Extrusion::Tool:
return "Tool"; return L("Tool");
} }
return ""; return "";
@ -360,10 +364,13 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::
{ {
case Extrusion::FeatureType: case Extrusion::FeatureType:
{ {
items.reserve(erMixed - erPerimeter + 1); ExtrusionRole first_valid = erPerimeter;
for (unsigned int i = (unsigned int)erPerimeter; i < (unsigned int)erMixed; ++i) ExtrusionRole last_valid = erCustom;
items.reserve(last_valid - first_valid + 1);
for (unsigned int i = (unsigned int)first_valid; i <= (unsigned int)last_valid; ++i)
{ {
items.emplace_back(extrusion.role_names[i], extrusion.role_colors[i]); items.emplace_back(_CHB(extrusion.role_names[i].c_str()).data(), extrusion.role_colors[i]);
} }
break; break;
@ -389,8 +396,8 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::
items.reserve(tools_colors_count); items.reserve(tools_colors_count);
for (unsigned int i = 0; i < tools_colors_count; ++i) for (unsigned int i = 0; i < tools_colors_count; ++i)
{ {
char buf[32]; char buf[MIN_BUF_LENGTH_FOR_L];
sprintf(buf, "Extruder %d", i + 1); sprintf(buf, _CHB(L("Extruder %d")), i + 1);
GCodePreviewData::Color color; GCodePreviewData::Color color;
::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + i * 4), 4 * sizeof(float)); ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + i * 4), 4 * sizeof(float));

View file

@ -19,6 +19,18 @@ static const float DEFAULT_EXTRUDE_FACTOR_OVERRIDE_PERCENTAGE = 1.0f; // 100 per
static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f; static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f;
#if ENABLE_MOVE_STATS
static const std::string MOVE_TYPE_STR[Slic3r::GCodeTimeEstimator::Block::Num_Types] =
{
"Noop",
"Retract",
"Unretract",
"Tool_change",
"Move",
"Extrude"
};
#endif // ENABLE_MOVE_STATS
namespace Slic3r { namespace Slic3r {
void GCodeTimeEstimator::Feedrates::reset() void GCodeTimeEstimator::Feedrates::reset()
@ -139,6 +151,14 @@ namespace Slic3r {
return (acceleration == 0.0f) ? 0.0f : (2.0f * acceleration * distance - sqr(initial_rate) + sqr(final_rate)) / (4.0f * acceleration); return (acceleration == 0.0f) ? 0.0f : (2.0f * acceleration * distance - sqr(initial_rate) + sqr(final_rate)) / (4.0f * acceleration);
} }
#if ENABLE_MOVE_STATS
GCodeTimeEstimator::MoveStats::MoveStats()
: count(0)
, time(0.0f)
{
}
#endif // ENABLE_MOVE_STATS
GCodeTimeEstimator::GCodeTimeEstimator() GCodeTimeEstimator::GCodeTimeEstimator()
{ {
reset(); reset();
@ -155,6 +175,10 @@ namespace Slic3r {
_calculate_time(); _calculate_time();
#if ENABLE_MOVE_STATS
_log_moves_stats();
#endif // ENABLE_MOVE_STATS
_reset_blocks(); _reset_blocks();
_reset(); _reset();
} }
@ -166,6 +190,10 @@ namespace Slic3r {
_parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); _parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2));
_calculate_time(); _calculate_time();
#if ENABLE_MOVE_STATS
_log_moves_stats();
#endif // ENABLE_MOVE_STATS
_reset_blocks(); _reset_blocks();
_reset(); _reset();
} }
@ -180,6 +208,10 @@ namespace Slic3r {
_parser.parse_line(line, action); _parser.parse_line(line, action);
_calculate_time(); _calculate_time();
#if ENABLE_MOVE_STATS
_log_moves_stats();
#endif // ENABLE_MOVE_STATS
_reset_blocks(); _reset_blocks();
_reset(); _reset();
} }
@ -208,6 +240,11 @@ namespace Slic3r {
{ {
PROFILE_FUNC(); PROFILE_FUNC();
_calculate_time(); _calculate_time();
#if ENABLE_MOVE_STATS
_log_moves_stats();
#endif // ENABLE_MOVE_STATS
_reset_blocks(); _reset_blocks();
_reset(); _reset();
} }
@ -393,6 +430,9 @@ namespace Slic3r {
void GCodeTimeEstimator::reset() void GCodeTimeEstimator::reset()
{ {
_time = 0.0f; _time = 0.0f;
#if ENABLE_MOVE_STATS
_moves_stats.clear();
#endif // ENABLE_MOVE_STATS
_reset_blocks(); _reset_blocks();
_reset(); _reset();
} }
@ -448,9 +488,24 @@ namespace Slic3r {
for (const Block& block : _blocks) for (const Block& block : _blocks)
{ {
#if ENABLE_MOVE_STATS
float block_time = 0.0f;
block_time += block.acceleration_time();
block_time += block.cruise_time();
block_time += block.deceleration_time();
_time += block_time;
MovesStatsMap::iterator it = _moves_stats.find(block.move_type);
if (it == _moves_stats.end())
it = _moves_stats.insert(MovesStatsMap::value_type(block.move_type, MoveStats())).first;
it->second.count += 1;
it->second.time += block_time;
#else
_time += block.acceleration_time(); _time += block.acceleration_time();
_time += block.cruise_time(); _time += block.cruise_time();
_time += block.deceleration_time(); _time += block.deceleration_time();
#endif // ENABLE_MOVE_STATS
} }
} }
@ -746,6 +801,28 @@ namespace Slic3r {
set_axis_position((EAxis)a, new_pos[a]); set_axis_position((EAxis)a, new_pos[a]);
} }
#if ENABLE_MOVE_STATS
// detects block move type
block.move_type = Block::Noop;
if (block.delta_pos[E] < 0.0f)
{
if ((block.delta_pos[X] != 0.0f) || (block.delta_pos[Y] != 0.0f) || (block.delta_pos[Z] != 0.0f))
block.move_type = Block::Move;
else
block.move_type = Block::Retract;
}
else if (block.delta_pos[E] > 0.0f)
{
if ((block.delta_pos[X] == 0.0f) && (block.delta_pos[Y] == 0.0f) && (block.delta_pos[Z] == 0.0f))
block.move_type = Block::Unretract;
else if ((block.delta_pos[X] != 0.0f) || (block.delta_pos[Y] != 0.0f))
block.move_type = Block::Extrude;
}
else if ((block.delta_pos[X] != 0.0f) || (block.delta_pos[Y] != 0.0f) || (block.delta_pos[Z] != 0.0f))
block.move_type = Block::Move;
#endif // ENABLE_MOVE_STATS
// adds block to blocks list // adds block to blocks list
_blocks.emplace_back(block); _blocks.emplace_back(block);
} }
@ -1064,4 +1141,24 @@ namespace Slic3r {
next->flags.recalculate = false; next->flags.recalculate = false;
} }
} }
#if ENABLE_MOVE_STATS
void GCodeTimeEstimator::_log_moves_stats() const
{
float moves_count = 0.0f;
for (const MovesStatsMap::value_type& move : _moves_stats)
{
moves_count += (float)move.second.count;
}
for (const MovesStatsMap::value_type& move : _moves_stats)
{
std::cout << MOVE_TYPE_STR[move.first];
std::cout << ": count " << move.second.count << " (" << 100.0f * (float)move.second.count / moves_count << "%)";
std::cout << " - time: " << move.second.time << "s (" << 100.0f * move.second.time / _time << "%)";
std::cout << std::endl;
}
std::cout << std::endl;
}
#endif // ENABLE_MOVE_STATS
} }

View file

@ -5,6 +5,8 @@
#include "PrintConfig.hpp" #include "PrintConfig.hpp"
#include "GCodeReader.hpp" #include "GCodeReader.hpp"
#define ENABLE_MOVE_STATS 0
namespace Slic3r { namespace Slic3r {
// //
@ -74,6 +76,19 @@ namespace Slic3r {
public: public:
struct Block struct Block
{ {
#if ENABLE_MOVE_STATS
enum EMoveType : unsigned char
{
Noop,
Retract,
Unretract,
Tool_change,
Move,
Extrude,
Num_Types
};
#endif // ENABLE_MOVE_STATS
struct FeedrateProfile struct FeedrateProfile
{ {
float entry; // mm/s float entry; // mm/s
@ -106,6 +121,10 @@ namespace Slic3r {
bool nominal_length; bool nominal_length;
}; };
#if ENABLE_MOVE_STATS
EMoveType move_type;
#endif // ENABLE_MOVE_STATS
Flags flags; Flags flags;
float delta_pos[Num_Axis]; // mm float delta_pos[Num_Axis]; // mm
@ -156,6 +175,18 @@ namespace Slic3r {
typedef std::vector<Block> BlocksList; typedef std::vector<Block> BlocksList;
#if ENABLE_MOVE_STATS
struct MoveStats
{
unsigned int count;
float time;
MoveStats();
};
typedef std::map<Block::EMoveType, MoveStats> MovesStatsMap;
#endif // ENABLE_MOVE_STATS
private: private:
GCodeReader _parser; GCodeReader _parser;
State _state; State _state;
@ -163,6 +194,9 @@ namespace Slic3r {
Feedrates _prev; Feedrates _prev;
BlocksList _blocks; BlocksList _blocks;
float _time; // s float _time; // s
#if ENABLE_MOVE_STATS
MovesStatsMap _moves_stats;
#endif // ENABLE_MOVE_STATS
public: public:
GCodeTimeEstimator(); GCodeTimeEstimator();
@ -318,6 +352,10 @@ namespace Slic3r {
void _planner_reverse_pass_kernel(Block& curr, Block& next); void _planner_reverse_pass_kernel(Block& curr, Block& next);
void _recalculate_trapezoids(); void _recalculate_trapezoids();
#if ENABLE_MOVE_STATS
void _log_moves_stats() const;
#endif // ENABLE_MOVE_STATS
}; };
} /* namespace Slic3r */ } /* namespace Slic3r */

View file

@ -12,6 +12,7 @@
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/nowide/iostream.hpp> #include <boost/nowide/iostream.hpp>
#include <boost/algorithm/string/replace.hpp>
namespace Slic3r { namespace Slic3r {
@ -91,7 +92,15 @@ Model Model::read_from_archive(const std::string &input_file, PresetBundle* bund
throw std::runtime_error("The supplied file couldn't be read because it's empty"); throw std::runtime_error("The supplied file couldn't be read because it's empty");
for (ModelObject *o : model.objects) for (ModelObject *o : model.objects)
o->input_file = input_file; {
if (boost::algorithm::iends_with(input_file, ".zip.amf"))
{
// we remove the .zip part of the extension to avoid it be added to filenames when exporting
o->input_file = boost::ireplace_last_copy(input_file, ".zip.", ".");
}
else
o->input_file = input_file;
}
if (add_default_instances) if (add_default_instances)
model.add_default_instances(); model.add_default_instances();

File diff suppressed because it is too large Load diff

View file

@ -29,7 +29,7 @@ enum GCodeFlavor {
enum InfillPattern { enum InfillPattern {
ipRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral,
}; };
enum SupportMaterialPattern { enum SupportMaterialPattern {
@ -73,6 +73,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum<InfillPattern>::get_enu
keys_map["concentric"] = ipConcentric; keys_map["concentric"] = ipConcentric;
keys_map["honeycomb"] = ipHoneycomb; keys_map["honeycomb"] = ipHoneycomb;
keys_map["3dhoneycomb"] = ip3DHoneycomb; keys_map["3dhoneycomb"] = ip3DHoneycomb;
keys_map["gyroid"] = ipGyroid;
keys_map["hilbertcurve"] = ipHilbertCurve; keys_map["hilbertcurve"] = ipHilbertCurve;
keys_map["archimedeanchords"] = ipArchimedeanChords; keys_map["archimedeanchords"] = ipArchimedeanChords;
keys_map["octagramspiral"] = ipOctagramSpiral; keys_map["octagramspiral"] = ipOctagramSpiral;
@ -699,6 +700,7 @@ class HostConfig : public StaticPrintConfig
public: public:
ConfigOptionString octoprint_host; ConfigOptionString octoprint_host;
ConfigOptionString octoprint_apikey; ConfigOptionString octoprint_apikey;
ConfigOptionString octoprint_cafile;
ConfigOptionString serial_port; ConfigOptionString serial_port;
ConfigOptionInt serial_speed; ConfigOptionInt serial_speed;
@ -707,6 +709,7 @@ protected:
{ {
OPT_PTR(octoprint_host); OPT_PTR(octoprint_host);
OPT_PTR(octoprint_apikey); OPT_PTR(octoprint_apikey);
OPT_PTR(octoprint_cafile);
OPT_PTR(serial_port); OPT_PTR(serial_port);
OPT_PTR(serial_speed); OPT_PTR(serial_speed);
} }

View file

@ -198,9 +198,8 @@ TriangleMesh::repair() {
stl_clear_error(&stl); stl_clear_error(&stl);
} }
// commenting out the following call fixes: #574, #413, #269, #262, #259, #230, #228, #206 // normal_directions
// // normal_directions stl_fix_normal_directions(&stl);
// stl_fix_normal_directions(&stl);
// normal_values // normal_values
stl_fix_normal_values(&stl); stl_fix_normal_values(&stl);
@ -210,7 +209,7 @@ TriangleMesh::repair() {
// neighbors // neighbors
stl_verify_neighbors(&stl); stl_verify_neighbors(&stl);
this->repaired = true; this->repaired = true;
BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished";
@ -1187,40 +1186,46 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic
loops correctly in some edge cases when original model had overlapping facets loops correctly in some edge cases when original model had overlapping facets
*/ */
std::vector<double> area; /* The following lines are commented out because they can generate wrong polygons,
std::vector<size_t> sorted_area; // vector of indices see for example issue #661 */
for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++ loop) {
area.push_back(loop->area());
sorted_area.push_back(loop - loops.begin());
}
// outer first
std::sort(sorted_area.begin(), sorted_area.end(),
[&area](size_t a, size_t b) { return std::abs(area[a]) > std::abs(area[b]); });
// we don't perform a safety offset now because it might reverse cw loops //std::vector<double> area;
Polygons p_slices; //std::vector<size_t> sorted_area; // vector of indices
for (std::vector<size_t>::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++ loop_idx) { //for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++ loop) {
/* we rely on the already computed area to determine the winding order // area.push_back(loop->area());
of the loops, since the Orientation() function provided by Clipper // sorted_area.push_back(loop - loops.begin());
would do the same, thus repeating the calculation */ //}
Polygons::const_iterator loop = loops.begin() + *loop_idx; //
if (area[*loop_idx] > +EPSILON) //// outer first
p_slices.push_back(*loop); //std::sort(sorted_area.begin(), sorted_area.end(),
else if (area[*loop_idx] < -EPSILON) // [&area](size_t a, size_t b) { return std::abs(area[a]) > std::abs(area[b]); });
//FIXME This is arbitrary and possibly very slow.
// If the hole is inside a polygon, then there is no need to diff. //// we don't perform a safety offset now because it might reverse cw loops
// If the hole intersects a polygon boundary, then diff it, but then //Polygons p_slices;
// there is no guarantee of an ordering of the loops. //for (std::vector<size_t>::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++ loop_idx) {
// Maybe we can test for the intersection before running the expensive diff algorithm? // /* we rely on the already computed area to determine the winding order
p_slices = diff(p_slices, *loop); // of the loops, since the Orientation() function provided by Clipper
} // would do the same, thus repeating the calculation */
// Polygons::const_iterator loop = loops.begin() + *loop_idx;
// if (area[*loop_idx] > +EPSILON)
// p_slices.push_back(*loop);
// else if (area[*loop_idx] < -EPSILON)
// //FIXME This is arbitrary and possibly very slow.
// // If the hole is inside a polygon, then there is no need to diff.
// // If the hole intersects a polygon boundary, then diff it, but then
// // there is no guarantee of an ordering of the loops.
// // Maybe we can test for the intersection before running the expensive diff algorithm?
// p_slices = diff(p_slices, *loop);
//}
// perform a safety offset to merge very close facets (TODO: find test case for this) // perform a safety offset to merge very close facets (TODO: find test case for this)
double safety_offset = scale_(0.0499); double safety_offset = scale_(0.0499);
//FIXME see https://github.com/prusa3d/Slic3r/issues/520 //FIXME see https://github.com/prusa3d/Slic3r/issues/520
// double safety_offset = scale_(0.0001); // double safety_offset = scale_(0.0001);
ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset);
/* The following line is commented out because it can generate wrong polygons,
see for example issue #661 */
//ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset);
#ifdef SLIC3R_TRIANGLEMESH_DEBUG #ifdef SLIC3R_TRIANGLEMESH_DEBUG
size_t holes_count = 0; size_t holes_count = 0;
@ -1231,7 +1236,10 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic
#endif #endif
// append to the supplied collection // append to the supplied collection
expolygons_append(*slices, ex_slices); /* Fix for issue #661 { */
expolygons_append(*slices, offset2_ex(union_(loops, false), +safety_offset, -safety_offset));
//expolygons_append(*slices, ex_slices);
/* } */
} }
void TriangleMeshSlicer::make_expolygons(std::vector<IntersectionLine> &lines, ExPolygons* slices) const void TriangleMeshSlicer::make_expolygons(std::vector<IntersectionLine> &lines, ExPolygons* slices) const

View file

@ -64,6 +64,7 @@ REGISTER_CLASS(PresetCollection, "GUI::PresetCollection");
REGISTER_CLASS(PresetBundle, "GUI::PresetBundle"); REGISTER_CLASS(PresetBundle, "GUI::PresetBundle");
REGISTER_CLASS(PresetHints, "GUI::PresetHints"); REGISTER_CLASS(PresetHints, "GUI::PresetHints");
REGISTER_CLASS(TabIface, "GUI::Tab"); REGISTER_CLASS(TabIface, "GUI::Tab");
REGISTER_CLASS(OctoPrint, "OctoPrint");
SV* ConfigBase__as_hash(ConfigBase* THIS) SV* ConfigBase__as_hash(ConfigBase* THIS)
{ {

View file

@ -27,6 +27,8 @@
#include <wx/image.h> #include <wx/image.h>
#include <wx/settings.h> #include <wx/settings.h>
#include "GUI.hpp"
namespace Slic3r { namespace Slic3r {
void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh) void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh)
@ -454,6 +456,25 @@ void GLVolumeCollection::render_legacy() const
glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_NORMAL_ARRAY);
} }
std::vector<double> GLVolumeCollection::get_current_print_zs() const
{
std::vector<double> print_zs;
for (GLVolume *vol : this->volumes)
{
for (coordf_t z : vol->print_zs)
{
double round_z = (double)round(z * 100000.0f) / 100000.0f;
if (std::find(print_zs.begin(), print_zs.end(), round_z) == print_zs.end())
print_zs.push_back(round_z);
}
}
std::sort(print_zs.begin(), print_zs.end());
return print_zs;
}
// caller is responsible for supplying NO lines with zero length // caller is responsible for supplying NO lines with zero length
static void thick_lines_to_indexed_vertex_array( static void thick_lines_to_indexed_vertex_array(
const Lines &lines, const Lines &lines,
@ -1129,7 +1150,7 @@ bool _3DScene::LegendTexture::generate(const GCodePreviewData& preview_data, con
m_data.clear(); m_data.clear();
// collects items to render // collects items to render
const std::string& title = preview_data.get_legend_title(); auto title = GUI::L_str(preview_data.get_legend_title());
const GCodePreviewData::LegendItemsList& items = preview_data.get_legend_items(tool_colors); const GCodePreviewData::LegendItemsList& items = preview_data.get_legend_items(tool_colors);
unsigned int items_count = (unsigned int)items.size(); unsigned int items_count = (unsigned int)items.size();
@ -1151,7 +1172,7 @@ bool _3DScene::LegendTexture::generate(const GCodePreviewData& preview_data, con
unsigned int max_text_height = 0; unsigned int max_text_height = 0;
for (const GCodePreviewData::LegendItem& item : items) for (const GCodePreviewData::LegendItem& item : items)
{ {
memDC.GetTextExtent(item.text, &w, &h); memDC.GetTextExtent(GUI::from_u8(item.text), &w, &h);
max_text_width = std::max(max_text_width, (unsigned int)w); max_text_width = std::max(max_text_width, (unsigned int)w);
max_text_height = std::max(max_text_height, (unsigned int)h); max_text_height = std::max(max_text_height, (unsigned int)h);
} }
@ -1228,7 +1249,7 @@ bool _3DScene::LegendTexture::generate(const GCodePreviewData& preview_data, con
memDC.DrawRectangle(wxRect(icon_x_inner, icon_y + 1, px_inner_square, px_inner_square)); memDC.DrawRectangle(wxRect(icon_x_inner, icon_y + 1, px_inner_square, px_inner_square));
// draw text // draw text
memDC.DrawText(item.text, text_x, icon_y + text_y_offset); memDC.DrawText(GUI::from_u8(item.text), text_x, icon_y + text_y_offset);
// update y // update y
icon_y += icon_y_step; icon_y += icon_y_step;
@ -2209,6 +2230,9 @@ void _3DScene::_update_gcode_volumes_visibility(const GCodePreviewData& preview_
{ {
case GCodePreviewVolumeIndex::Extrusion: case GCodePreviewVolumeIndex::Extrusion:
{ {
if ((ExtrusionRole)s_gcode_preview_volume_index.first_volumes[i].flag == erCustom)
volume->zoom_to_volumes = false;
volume->is_active = preview_data.extrusion.is_role_flag_set((ExtrusionRole)s_gcode_preview_volume_index.first_volumes[i].flag); volume->is_active = preview_data.extrusion.is_role_flag_set((ExtrusionRole)s_gcode_preview_volume_index.first_volumes[i].flag);
break; break;
} }

View file

@ -372,6 +372,9 @@ public:
void set_render_interleaved_only_volumes(const RenderInterleavedOnlyVolumes& render_interleaved_only_volumes) { _render_interleaved_only_volumes = render_interleaved_only_volumes; } void set_render_interleaved_only_volumes(const RenderInterleavedOnlyVolumes& render_interleaved_only_volumes) { _render_interleaved_only_volumes = render_interleaved_only_volumes; }
// Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection
std::vector<double> get_current_print_zs() const;
private: private:
GLVolumeCollection(const GLVolumeCollection &other); GLVolumeCollection(const GLVolumeCollection &other);
GLVolumeCollection& operator=(const GLVolumeCollection &); GLVolumeCollection& operator=(const GLVolumeCollection &);

View file

@ -36,43 +36,43 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
{ {
// on_change(nullptr); // on_change(nullptr);
auto box = new wxStaticBox(this, wxID_ANY, _L("Shape")); auto box = new wxStaticBox(this, wxID_ANY, _(L("Shape")));
auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL); auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL);
// shape options // 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(300, -1), wxCHB_TOP);
sbsizer->Add(m_shape_options_book); sbsizer->Add(m_shape_options_book);
auto optgroup = init_shape_options_page(_L("Rectangular")); auto optgroup = init_shape_options_page(_(L("Rectangular")));
ConfigOptionDef def; ConfigOptionDef def;
def.type = coPoints; def.type = coPoints;
def.default_value = new ConfigOptionPoints{ Pointf(200, 200) }; def.default_value = new ConfigOptionPoints{ Pointf(200, 200) };
def.label = _LU8("Size"); def.label = L("Size");
def.tooltip = _LU8("Size in X and Y of the rectangular plate."); def.tooltip = L("Size in X and Y of the rectangular plate.");
Option option(def, "rect_size"); Option option(def, "rect_size");
optgroup->append_single_option_line(option); optgroup->append_single_option_line(option);
def.type = coPoints; def.type = coPoints;
def.default_value = new ConfigOptionPoints{ Pointf(0, 0) }; def.default_value = new ConfigOptionPoints{ Pointf(0, 0) };
def.label = _LU8("Origin"); def.label = L("Origin");
def.tooltip = _LU8("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle."); def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.");
option = Option(def, "rect_origin"); option = Option(def, "rect_origin");
optgroup->append_single_option_line(option); optgroup->append_single_option_line(option);
optgroup = init_shape_options_page(_L("Circular")); optgroup = init_shape_options_page(_(L("Circular")));
def.type = coFloat; def.type = coFloat;
def.default_value = new ConfigOptionFloat(200); def.default_value = new ConfigOptionFloat(200);
def.sidetext = _LU8("mm"); def.sidetext = L("mm");
def.label = _LU8("Diameter"); def.label = L("Diameter");
def.tooltip = _LU8("Diameter of the print bed. It is assumed that origin (0,0) is located in the center."); def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center.");
option = Option(def, "diameter"); option = Option(def, "diameter");
optgroup->append_single_option_line(option); optgroup->append_single_option_line(option);
optgroup = init_shape_options_page(_L("Custom")); optgroup = init_shape_options_page(_(L("Custom")));
Line line{ "", "" }; Line line{ "", "" };
line.full_width = 1; line.full_width = 1;
line.widget = [this](wxWindow* parent) { line.widget = [this](wxWindow* parent) {
auto btn = new wxButton(parent, wxID_ANY, _L("Load shape from STL..."), wxDefaultPosition, wxDefaultSize); auto btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL...")), wxDefaultPosition, wxDefaultSize);
auto sizer = new wxBoxSizer(wxHORIZONTAL); auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn); sizer->Add(btn);
@ -117,7 +117,7 @@ ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(wxString title){
auto panel = new wxPanel(m_shape_options_book); auto panel = new wxPanel(m_shape_options_book);
ConfigOptionsGroupShp optgroup; ConfigOptionsGroupShp optgroup;
optgroup = std::make_shared<ConfigOptionsGroup>(panel, _L("Settings")); optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Settings")));
optgroup->label_width = 100; optgroup->label_width = 100;
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
@ -290,12 +290,12 @@ void BedShapePanel::update_shape()
void BedShapePanel::load_stl() void BedShapePanel::load_stl()
{ {
t_file_wild_card vec_FILE_WILDCARDS = get_file_wild_card(); t_file_wild_card vec_FILE_WILDCARDS = get_file_wild_card();
std::vector<std::string> file_types = { "known", "stl", "obj", "amf", "prusa"}; std::vector<std::string> file_types = { "known", "stl", "obj", "amf", "3mf", "prusa" };
wxString MODEL_WILDCARD; wxString MODEL_WILDCARD;
for (auto file_type: file_types) for (auto file_type: file_types)
MODEL_WILDCARD += vec_FILE_WILDCARDS.at(file_type) + "|"; MODEL_WILDCARD += vec_FILE_WILDCARDS.at(file_type) + "|";
auto dialog = new wxFileDialog(this, _L("Choose a file to import bed shape from (STL/OBJ/AMF/PRUSA):"), "", "", auto dialog = new wxFileDialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "",
MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog->ShowModal() != wxID_OK) { if (dialog->ShowModal() != wxID_OK) {
dialog->Destroy(); dialog->Destroy();
@ -312,7 +312,7 @@ void BedShapePanel::load_stl()
model = Model::read_from_file(file_name); model = Model::read_from_file(file_name);
} }
catch (std::exception &e) { catch (std::exception &e) {
auto msg = _L("Error! ") + file_name + " : " + e.what() + "."; auto msg = _(L("Error! ")) + file_name + " : " + e.what() + ".";
show_error(this, msg); show_error(this, msg);
exit(1); exit(1);
} }
@ -321,11 +321,11 @@ void BedShapePanel::load_stl()
auto expolygons = mesh.horizontal_projection(); auto expolygons = mesh.horizontal_projection();
if (expolygons.size() == 0) { if (expolygons.size() == 0) {
show_error(this, _L("The selected file contains no geometry.")); show_error(this, _(L("The selected file contains no geometry.")));
return; return;
} }
if (expolygons.size() > 1) { if (expolygons.size() > 1) {
show_error(this, _L("The selected file contains several disjoint areas. This is not supported.")); show_error(this, _(L("The selected file contains several disjoint areas. This is not supported.")));
return; return;
} }

View file

@ -39,7 +39,7 @@ class BedShapeDialog : public wxDialog
{ {
BedShapePanel* m_panel; BedShapePanel* m_panel;
public: public:
BedShapeDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _L("Bed Shape"), BedShapeDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _(L("Bed Shape")),
wxDefaultPosition, wxSize(350, 700), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER){} wxDefaultPosition, wxSize(350, 700), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER){}
~BedShapeDialog(){ } ~BedShapeDialog(){ }

View file

@ -10,13 +10,20 @@
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
wxString double_to_string(double const value)
{
int precision = 10 * value - int(10 * value) == 0 ? 1 : 2;
return value - int(value) == 0 ?
wxString::Format(_T("%i"), int(value)) :
wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None);
}
void Field::on_kill_focus(wxEvent& event) { void Field::on_kill_focus(wxEvent& event) {
// Without this, there will be nasty focus bugs on Windows. // Without this, there will be nasty focus bugs on Windows.
// Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all // Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all
// non-command events to allow the default handling to take place." // non-command events to allow the default handling to take place."
event.Skip(); event.Skip();
std::cerr << "calling Field::on_kill_focus from " << m_opt_id<< "\n"; // call the registered function if it is available
// call the registered function if it is available
if (m_on_kill_focus!=nullptr) if (m_on_kill_focus!=nullptr)
m_on_kill_focus(); m_on_kill_focus();
} }
@ -30,9 +37,9 @@ namespace Slic3r { namespace GUI {
wxString Field::get_tooltip_text(const wxString& default_string) wxString Field::get_tooltip_text(const wxString& default_string)
{ {
wxString tooltip_text(""); wxString tooltip_text("");
wxString tooltip = wxString::FromUTF8(m_opt.tooltip.c_str()); wxString tooltip = L_str(m_opt.tooltip);
if (tooltip.length() > 0) if (tooltip.length() > 0)
tooltip_text = tooltip + "(" + _L("default") + ": " + tooltip_text = tooltip + "(" + _(L("default")) + ": " +
(boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") +
default_string + ")"; default_string + ")";
@ -45,7 +52,7 @@ namespace Slic3r { namespace GUI {
return std::regex_match(string, regex_pattern); return std::regex_match(string, regex_pattern);
} }
boost::any Field::get_value_by_opt_type(wxString str, ConfigOptionType type) boost::any Field::get_value_by_opt_type(wxString str)
{ {
boost::any ret_val; boost::any ret_val;
switch (m_opt.type){ switch (m_opt.type){
@ -56,23 +63,17 @@ namespace Slic3r { namespace GUI {
case coPercents: case coPercents:
case coFloats: case coFloats:
case coFloat:{ case coFloat:{
if (m_opt.type == coPercent) str.RemoveLast(); if (m_opt.type == coPercent && str.Last() == '%')
str.RemoveLast();
double val; double val;
str.ToCDouble(&val); str.ToCDouble(&val);
ret_val = val; ret_val = val;
break; } break; }
case coString: case coString:
case coStrings: case coStrings:
case coFloatOrPercent:
ret_val = str.ToStdString(); ret_val = str.ToStdString();
break; break;
case coFloatOrPercent:{
if (str.Last() == '%')
str.RemoveLast();
double val;
str.ToCDouble(&val);
ret_val = val;
break;
}
default: default:
break; break;
} }
@ -90,13 +91,9 @@ namespace Slic3r { namespace GUI {
switch (m_opt.type) { switch (m_opt.type) {
case coFloatOrPercent: case coFloatOrPercent:
{ {
if (static_cast<const ConfigOptionFloatOrPercent*>(m_opt.default_value)->percent) text_value = double_to_string(m_opt.default_value->getFloat());
{ if (static_cast<const ConfigOptionFloatOrPercent*>(m_opt.default_value)->percent)
text_value = wxString::Format(_T("%i"), int(m_opt.default_value->getFloat())); text_value += "%";
text_value += "%";
}
else
text_value = wxNumberFormatter::ToString(m_opt.default_value->getFloat(), 2);
break; break;
} }
case coPercent: case coPercent:
@ -106,29 +103,15 @@ namespace Slic3r { namespace GUI {
break; break;
} }
case coPercents: case coPercents:
{ case coFloats:
const ConfigOptionPercents *vec = static_cast<const ConfigOptionPercents*>(m_opt.default_value);
if (vec == nullptr || vec->empty()) break;
if (vec->size() > 1)
break;
double val = vec->get_at(0);
text_value = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None);
break;
}
case coFloat: case coFloat:
{ {
double val = m_opt.default_value->getFloat(); double val = m_opt.type == coFloats ?
text_value = (val - int(val)) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None); static_cast<const ConfigOptionFloats*>(m_opt.default_value)->get_at(0) :
break; m_opt.type == coFloat ?
} m_opt.default_value->getFloat() :
case coFloats: static_cast<const ConfigOptionPercents*>(m_opt.default_value)->get_at(0);
{ text_value = double_to_string(val);
const ConfigOptionFloats *vec = static_cast<const ConfigOptionFloats*>(m_opt.default_value);
if (vec == nullptr || vec->empty()) break;
if (vec->size() > 1)
break;
double val = vec->get_at(0);
text_value = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None);
break; break;
} }
case coString: case coString:
@ -174,7 +157,7 @@ namespace Slic3r { namespace GUI {
boost::any TextCtrl::get_value() boost::any TextCtrl::get_value()
{ {
wxString ret_str = static_cast<wxTextCtrl*>(window)->GetValue(); wxString ret_str = static_cast<wxTextCtrl*>(window)->GetValue();
boost::any ret_val = get_value_by_opt_type(ret_str, m_opt.type); boost::any ret_val = get_value_by_opt_type(ret_str);
return ret_val; return ret_val;
} }
@ -303,7 +286,7 @@ void Choice::set_selection()
break; break;
++idx; ++idx;
} }
if (m_opt.type == coPercent) text_value += "%"; // if (m_opt.type == coPercent) text_value += "%";
idx == m_opt.enum_values.size() ? idx == m_opt.enum_values.size() ?
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) : dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
dynamic_cast<wxComboBox*>(window)->SetSelection(idx); dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
@ -387,7 +370,7 @@ void Choice::set_value(boost::any value)
break; break;
++idx; ++idx;
} }
if (m_opt.type == coPercent) text_value += "%"; // if (m_opt.type == coPercent) text_value += "%";
idx == m_opt.enum_values.size() ? idx == m_opt.enum_values.size() ?
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) : dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
dynamic_cast<wxComboBox*>(window)->SetSelection(idx); dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
@ -429,7 +412,7 @@ boost::any Choice::get_value()
wxString ret_str = static_cast<wxComboBox*>(window)->GetValue(); wxString ret_str = static_cast<wxComboBox*>(window)->GetValue();
if (m_opt.type != coEnum) if (m_opt.type != coEnum)
ret_val = get_value_by_opt_type(ret_str, m_opt.type); ret_val = get_value_by_opt_type(ret_str);
else else
{ {
int ret_enum = static_cast<wxComboBox*>(window)->GetSelection(); int ret_enum = static_cast<wxComboBox*>(window)->GetSelection();
@ -535,21 +518,29 @@ void PointCtrl::set_value(const Pointf value)
void PointCtrl::set_value(boost::any value) void PointCtrl::set_value(boost::any value)
{ {
Pointf pt; Pointf pt;
try Pointf *ptf = boost::any_cast<Pointf>(&value);
if (!ptf)
{ {
pt = boost::any_cast<ConfigOptionPoints*>(value)->values.at(0); ConfigOptionPoints* pts = boost::any_cast<ConfigOptionPoints*>(value);
pt = pts->values.at(0);
} }
catch (const std::exception &e) else
{ pt = *ptf;
try{ // try
pt = boost::any_cast<Pointf>(value); // {
} // pt = boost::any_cast<ConfigOptionPoints*>(value)->values.at(0);
catch (const std::exception &e) // }
{ // catch (const std::exception &e)
std::cerr << "Error! Can't cast PointCtrl value" << m_opt_id << "\n"; // {
return; // try{
} // pt = boost::any_cast<Pointf>(value);
} // }
// catch (const std::exception &e)
// {
// std::cerr << "Error! Can't cast PointCtrl value" << m_opt_id << "\n";
// return;
// }
// }
set_value(pt); set_value(pt);
} }

View file

@ -26,6 +26,8 @@ using t_field = std::unique_ptr<Field>;
using t_kill_focus = std::function<void()>; using t_kill_focus = std::function<void()>;
using t_change = std::function<void(t_config_option_key, boost::any)>; using t_change = std::function<void(t_config_option_key, boost::any)>;
wxString double_to_string(double const value);
class Field { class Field {
protected: protected:
// factory function to defer and enforce creation of derived type. // factory function to defer and enforce creation of derived type.
@ -83,7 +85,7 @@ public:
virtual wxWindow* getWindow() { return nullptr; } virtual wxWindow* getWindow() { return nullptr; }
bool is_matched(std::string string, std::string pattern); bool is_matched(std::string string, std::string pattern);
boost::any get_value_by_opt_type(wxString str, ConfigOptionType type); boost::any get_value_by_opt_type(wxString str);
/// Factory method for generating new derived classes. /// Factory method for generating new derived classes.
template<class T> template<class T>

View file

@ -5,9 +5,9 @@
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/classification.hpp>
#include <boost/format.hpp>
#if __APPLE__ #if __APPLE__
#import <IOKit/pwr_mgt/IOPMLib.h> #import <IOKit/pwr_mgt/IOPMLib.h>
@ -28,7 +28,6 @@
#include <wx/app.h> #include <wx/app.h>
#include <wx/button.h> #include <wx/button.h>
#include <wx/config.h>
#include <wx/dir.h> #include <wx/dir.h>
#include <wx/filename.h> #include <wx/filename.h>
#include <wx/frame.h> #include <wx/frame.h>
@ -45,6 +44,7 @@
#include "TabIface.hpp" #include "TabIface.hpp"
#include "AppConfig.hpp" #include "AppConfig.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include "Preferences.hpp"
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
@ -171,6 +171,7 @@ void break_to_debugger()
wxApp *g_wxApp = nullptr; wxApp *g_wxApp = nullptr;
wxFrame *g_wxMainFrame = nullptr; wxFrame *g_wxMainFrame = nullptr;
wxNotebook *g_wxTabPanel = nullptr; wxNotebook *g_wxTabPanel = nullptr;
AppConfig *g_AppConfig = nullptr;
std::vector<Tab *> g_tabs_list; std::vector<Tab *> g_tabs_list;
@ -191,6 +192,11 @@ void set_tab_panel(wxNotebook *tab_panel)
g_wxTabPanel = tab_panel; g_wxTabPanel = tab_panel;
} }
void set_app_config(AppConfig *app_config)
{
g_AppConfig = app_config;
}
std::vector<Tab *>& get_tabs_list() std::vector<Tab *>& get_tabs_list()
{ {
return g_tabs_list; return g_tabs_list;
@ -215,7 +221,7 @@ bool select_language(wxArrayString & names,
wxArrayLong & identifiers) wxArrayLong & identifiers)
{ {
wxCHECK_MSG(names.Count() == identifiers.Count(), false, wxCHECK_MSG(names.Count() == identifiers.Count(), false,
_L("Array of language names and identifiers should have the same size.")); _(L("Array of language names and identifiers should have the same size.")));
int init_selection = 0; int init_selection = 0;
long current_language = g_wxLocale ? g_wxLocale->GetLanguage() : wxLANGUAGE_UNKNOWN; long current_language = g_wxLocale ? g_wxLocale->GetLanguage() : wxLANGUAGE_UNKNOWN;
for (auto lang : identifiers){ for (auto lang : identifiers){
@ -226,7 +232,7 @@ bool select_language(wxArrayString & names,
} }
if (init_selection == identifiers.size()) if (init_selection == identifiers.size())
init_selection = 0; init_selection = 0;
long index = wxGetSingleChoiceIndex(_L("Select the language"), _L("Language"), long index = wxGetSingleChoiceIndex(_(L("Select the language")), _(L("Language")),
names, init_selection); names, init_selection);
if (index != -1) if (index != -1)
{ {
@ -241,13 +247,14 @@ bool select_language(wxArrayString & names,
bool load_language() bool load_language()
{ {
wxConfig config(g_wxApp->GetAppName());
long language; long language;
if (!config.Read(wxT("wxTranslation_Language"), if (!g_AppConfig->has("translation_language"))
&language, wxLANGUAGE_UNKNOWN))
{
language = wxLANGUAGE_UNKNOWN; language = wxLANGUAGE_UNKNOWN;
else {
auto str_language = g_AppConfig->get("translation_language");
language = str_language != "" ? stol(str_language) : wxLANGUAGE_UNKNOWN;
} }
if (language == wxLANGUAGE_UNKNOWN) if (language == wxLANGUAGE_UNKNOWN)
return false; return false;
wxArrayString names; wxArrayString names;
@ -269,13 +276,13 @@ bool load_language()
void save_language() void save_language()
{ {
wxConfig config(g_wxApp->GetAppName());
long language = wxLANGUAGE_UNKNOWN; long language = wxLANGUAGE_UNKNOWN;
if (g_wxLocale) { if (g_wxLocale) {
language = g_wxLocale->GetLanguage(); language = g_wxLocale->GetLanguage();
} }
config.Write(wxT("wxTranslation_Language"), language); std::string str_language = std::to_string(language);
config.Flush(); g_AppConfig->set("translation_language", str_language);
g_AppConfig->save();
} }
void get_installed_languages(wxArrayString & names, void get_installed_languages(wxArrayString & names,
@ -290,15 +297,12 @@ void get_installed_languages(wxArrayString & names,
wxString name = wxLocale::GetLanguageName(wxLANGUAGE_DEFAULT); wxString name = wxLocale::GetLanguageName(wxLANGUAGE_DEFAULT);
if (!name.IsEmpty()) if (!name.IsEmpty())
{ {
names.Add(_L("Default")); names.Add(_(L("Default")));
identifiers.Add(wxLANGUAGE_DEFAULT); identifiers.Add(wxLANGUAGE_DEFAULT);
} }
for (bool cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_DIRS); for (bool cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_DIRS);
cont; cont = dir.GetNext(&filename)) cont; cont = dir.GetNext(&filename))
{ {
wxLogTrace(wxTraceMask(),
"L10n: Directory found = \"%s\"",
filename.GetData());
langinfo = wxLocale::FindLanguageInfo(filename); langinfo = wxLocale::FindLanguageInfo(filename);
if (langinfo != NULL) if (langinfo != NULL)
{ {
@ -318,33 +322,39 @@ void add_debug_menu(wxMenuBar *menu, int event_language_change)
{ {
//#if 0 //#if 0
auto local_menu = new wxMenu(); auto local_menu = new wxMenu();
local_menu->Append(wxWindow::NewControlId(1), _L("Change Application Language")); local_menu->Append(wxWindow::NewControlId(1), _(L("Change Application Language")));
local_menu->Bind(wxEVT_MENU, [event_language_change](wxEvent&){ local_menu->Bind(wxEVT_MENU, [event_language_change](wxEvent&){
wxArrayString names; wxArrayString names;
wxArrayLong identifiers; wxArrayLong identifiers;
get_installed_languages(names, identifiers); get_installed_languages(names, identifiers);
if (select_language(names, identifiers)){ if (select_language(names, identifiers)){
save_language(); save_language();
show_info(g_wxTabPanel, "Application will be restarted", "Attention!"); show_info(g_wxTabPanel, _(L("Application will be restarted")), _(L("Attention!")));
if (event_language_change > 0) { if (event_language_change > 0) {
wxCommandEvent event(event_language_change); wxCommandEvent event(event_language_change);
g_wxApp->ProcessEvent(event); g_wxApp->ProcessEvent(event);
} }
} }
}); });
menu->Append(local_menu, _T("&Localization")); menu->Append(local_menu, _(L("&Localization")));
//#endif //#endif
} }
void create_preset_tabs(PresetBundle *preset_bundle, AppConfig *app_config, void open_preferences_dialog(int event_preferences)
{
auto dlg = new PreferencesDialog(g_wxMainFrame, event_preferences);
dlg->ShowModal();
}
void create_preset_tabs(PresetBundle *preset_bundle,
bool no_controller, bool is_disabled_button_browse, bool is_user_agent, bool no_controller, bool is_disabled_button_browse, bool is_user_agent,
int event_value_change, int event_presets_changed, int event_value_change, int event_presets_changed,
int event_button_browse, int event_button_test) int event_button_browse, int event_button_test)
{ {
add_created_tab(new TabPrint (g_wxTabPanel, no_controller), preset_bundle, app_config); add_created_tab(new TabPrint (g_wxTabPanel, no_controller), preset_bundle);
add_created_tab(new TabFilament (g_wxTabPanel, no_controller), preset_bundle, app_config); add_created_tab(new TabFilament (g_wxTabPanel, no_controller), preset_bundle);
add_created_tab(new TabPrinter (g_wxTabPanel, no_controller, is_disabled_button_browse, is_user_agent), add_created_tab(new TabPrinter (g_wxTabPanel, no_controller, is_disabled_button_browse, is_user_agent),
preset_bundle, app_config); preset_bundle);
for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) { for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) {
Tab *tab = dynamic_cast<Tab*>(g_wxTabPanel->GetPage(i)); Tab *tab = dynamic_cast<Tab*>(g_wxTabPanel->GetPage(i));
if (! tab) if (! tab)
@ -378,8 +388,14 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
try{ try{
switch (config.def()->get(opt_key)->type){ switch (config.def()->get(opt_key)->type){
case coFloatOrPercent:{ case coFloatOrPercent:{
const auto &val = *config.option<ConfigOptionFloatOrPercent>(opt_key); std::string str = boost::any_cast<std::string>(value);
config.set_key_value(opt_key, new ConfigOptionFloatOrPercent(boost::any_cast<double>(value), val.percent)); bool percent = false;
if (str.back() == '%'){
str.pop_back();
percent = true;
}
double val = stod(str);
config.set_key_value(opt_key, new ConfigOptionFloatOrPercent(val, percent));
break;} break;}
case coPercent: case coPercent:
config.set_key_value(opt_key, new ConfigOptionPercent(boost::any_cast<double>(value))); config.set_key_value(opt_key, new ConfigOptionPercent(boost::any_cast<double>(value)));
@ -389,11 +405,15 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
val = boost::any_cast<double>(value); val = boost::any_cast<double>(value);
break; break;
} }
case coPercents: case coPercents:{
case coFloats:{ ConfigOptionPercents* vec_new = new ConfigOptionPercents{ boost::any_cast<double>(value) };
double& val = config.opt_float(opt_key, 0); config.option<ConfigOptionPercents>(opt_key)->set_at(vec_new, opt_index, opt_index);
val = boost::any_cast<double>(value);
break; break;
}
case coFloats:{
ConfigOptionFloats* vec_new = new ConfigOptionFloats{ boost::any_cast<double>(value) };
config.option<ConfigOptionFloats>(opt_key)->set_at(vec_new, opt_index, opt_index);
break;
} }
case coString: case coString:
config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast<std::string>(value))); config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast<std::string>(value)));
@ -406,7 +426,7 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
} }
else{ else{
ConfigOptionStrings* vec_new = new ConfigOptionStrings{ boost::any_cast<std::string>(value) }; ConfigOptionStrings* vec_new = new ConfigOptionStrings{ boost::any_cast<std::string>(value) };
config.option<ConfigOptionStrings>(opt_key)->set_at(vec_new, opt_index, opt_index); config.option<ConfigOptionStrings>(opt_key)->set_at(vec_new, opt_index, 0);
} }
} }
break; break;
@ -415,14 +435,14 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
break; break;
case coBools:{ case coBools:{
ConfigOptionBools* vec_new = new ConfigOptionBools{ boost::any_cast<bool>(value) }; ConfigOptionBools* vec_new = new ConfigOptionBools{ boost::any_cast<bool>(value) };
config.option<ConfigOptionBools>(opt_key)->set_at(vec_new, opt_index, opt_index); config.option<ConfigOptionBools>(opt_key)->set_at(vec_new, opt_index, 0);
break;} break;}
case coInt: case coInt:
config.set_key_value(opt_key, new ConfigOptionInt(boost::any_cast<int>(value))); config.set_key_value(opt_key, new ConfigOptionInt(boost::any_cast<int>(value)));
break; break;
case coInts:{ case coInts:{
ConfigOptionInts* vec_new = new ConfigOptionInts{ boost::any_cast<int>(value) }; ConfigOptionInts* vec_new = new ConfigOptionInts{ boost::any_cast<int>(value) };
config.option<ConfigOptionInts>(opt_key)->set_at(vec_new, opt_index, opt_index); config.option<ConfigOptionInts>(opt_key)->set_at(vec_new, opt_index, 0);
} }
break; break;
case coEnum:{ case coEnum:{
@ -455,9 +475,8 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
} }
} }
void add_created_tab(Tab* panel, PresetBundle *preset_bundle, AppConfig *app_config) void add_created_tab(Tab* panel, PresetBundle *preset_bundle)
{ {
panel->m_show_btn_incompatible_presets = app_config->get("show_incompatible_presets").empty();
panel->create_preset_tab(preset_bundle); panel->create_preset_tab(preset_bundle);
// Load the currently selected preset into the GUI, update the preset selection box. // Load the currently selected preset into the GUI, update the preset selection box.
@ -466,15 +485,22 @@ void add_created_tab(Tab* panel, PresetBundle *preset_bundle, AppConfig *app_con
} }
void show_error(wxWindow* parent, wxString message){ void show_error(wxWindow* parent, wxString message){
auto msg_wingow = new wxMessageDialog(parent, message, _L("Error"), wxOK | wxICON_ERROR); auto msg_wingow = new wxMessageDialog(parent, message, _(L("Error")), wxOK | wxICON_ERROR);
msg_wingow->ShowModal(); msg_wingow->ShowModal();
} }
void show_info(wxWindow* parent, wxString message, wxString title){ void show_info(wxWindow* parent, wxString message, wxString title){
auto msg_wingow = new wxMessageDialog(parent, message, title.empty() ? _L("Notice") : title, wxOK | wxICON_INFORMATION); auto msg_wingow = new wxMessageDialog(parent, message, title.empty() ? _(L("Notice")) : title, wxOK | wxICON_INFORMATION);
msg_wingow->ShowModal(); msg_wingow->ShowModal();
} }
void warning_catcher(wxWindow* parent, wxString message){
if (message == _(L("GLUquadricObjPtr | Attempt to free unreferenced scalar")) )
return;
auto msg = new wxMessageDialog(parent, message, _(L("Warning")), wxOK | wxICON_WARNING);
msg->ShowModal();
}
wxApp* get_app(){ wxApp* get_app(){
return g_wxApp; return g_wxApp;
} }
@ -487,17 +513,24 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string
wxCheckListBoxComboPopup* popup = new wxCheckListBoxComboPopup; wxCheckListBoxComboPopup* popup = new wxCheckListBoxComboPopup;
if (popup != nullptr) if (popup != nullptr)
{ {
// FIXME If the following line is removed, the combo box popup list will not react to mouse clicks.
// On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10.
comboCtrl->UseAltPopupWindow();
comboCtrl->EnablePopupAnimation(false);
comboCtrl->SetPopupControl(popup); comboCtrl->SetPopupControl(popup);
popup->SetStringValue(text); popup->SetStringValue(from_u8(text));
popup->Connect(wxID_ANY, wxEVT_CHECKLISTBOX, wxCommandEventHandler(wxCheckListBoxComboPopup::OnCheckListBox), nullptr, popup); popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); });
popup->Connect(wxID_ANY, wxEVT_LISTBOX, wxCommandEventHandler(wxCheckListBoxComboPopup::OnListBoxSelection), nullptr, popup); popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); });
popup->Bind(wxEVT_KEY_DOWN, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
popup->Bind(wxEVT_KEY_UP, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
std::vector<std::string> items_str; std::vector<std::string> items_str;
boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off); boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off);
for (const std::string& item : items_str) for (const std::string& item : items_str)
{ {
popup->Append(item); popup->Append(from_u8(item));
} }
for (unsigned int i = 0; i < popup->GetCount(); ++i) for (unsigned int i = 0; i < popup->GetCount(); ++i)
@ -524,4 +557,34 @@ int combochecklist_get_flags(wxComboCtrl* comboCtrl)
return flags; return flags;
} }
AppConfig* get_app_config()
{
return g_AppConfig;
}
wxString L_str(std::string str)
{
//! Explicitly specify that the source string is already in UTF-8 encoding
return wxGetTranslation(wxString(str.c_str(), wxConvUTF8));
}
wxString from_u8(std::string str)
{
return wxString::FromUTF8(str.c_str());
}
wxWindow *get_widget_by_id(int id)
{
if (g_wxMainFrame == nullptr) {
throw std::runtime_error("Main frame not set");
}
wxWindow *window = g_wxMainFrame->FindWindow(id);
if (window == nullptr) {
throw std::runtime_error((boost::format("Could not find widget by ID: %1%") % id).str());
}
return window;
}
} } } }

View file

@ -6,6 +6,7 @@
#include "Config.hpp" #include "Config.hpp"
class wxApp; class wxApp;
class wxWindow;
class wxFrame; class wxFrame;
class wxWindow; class wxWindow;
class wxMenuBar; class wxMenuBar;
@ -23,10 +24,19 @@ class AppConfig;
class DynamicPrintConfig; class DynamicPrintConfig;
class TabIface; class TabIface;
//! macro used to localization, return wxString // !!! If you needed to translate some wxString,
#define _L(s) wxGetTranslation(s) // !!! please use _(L(string))
//! macro used to localization, return const CharType * // !!! _() - is a standard wxWidgets macro to translate
#define _LU8(s) wxGetTranslation(s).ToUTF8().data() // !!! L() is used only for marking localizable string
// !!! It will be used in "xgettext" to create a Locating Message Catalog.
#define L(s) s
//! macro used to localization, return wxScopedCharBuffer
//! With wxConvUTF8 explicitly specify that the source string is already in UTF-8 encoding
#define _CHB(s) wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str()
// Minimal buffer length for translated string (char buf[MIN_BUF_LENGTH_FOR_L])
#define MIN_BUF_LENGTH_FOR_L 128
namespace GUI { namespace GUI {
@ -39,8 +49,9 @@ inline t_file_wild_card& get_file_wild_card() {
FILE_WILDCARDS["known"] = "Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA"; FILE_WILDCARDS["known"] = "Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA";
FILE_WILDCARDS["stl"] = "STL files (*.stl)|*.stl;*.STL"; FILE_WILDCARDS["stl"] = "STL files (*.stl)|*.stl;*.STL";
FILE_WILDCARDS["obj"] = "OBJ files (*.obj)|*.obj;*.OBJ"; FILE_WILDCARDS["obj"] = "OBJ files (*.obj)|*.obj;*.OBJ";
FILE_WILDCARDS["amf"] = "AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML"; FILE_WILDCARDS["amf"] = "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML";
FILE_WILDCARDS["prusa"] = "Prusa Control files (*.prusa)|*.prusa;*.PRUSA"; FILE_WILDCARDS["3mf"] = "3MF files (*.3mf)|*.3mf;*.3MF;";
FILE_WILDCARDS["prusa"] = "Prusa Control files (*.prusa)|*.prusa;*.PRUSA";
FILE_WILDCARDS["ini"] = "INI files *.ini|*.ini;*.INI"; FILE_WILDCARDS["ini"] = "INI files *.ini|*.ini;*.INI";
FILE_WILDCARDS["gcode"] = "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC"; FILE_WILDCARDS["gcode"] = "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC";
FILE_WILDCARDS["svg"] = "SVG files *.svg|*.svg;*.SVG"; FILE_WILDCARDS["svg"] = "SVG files *.svg|*.svg;*.SVG";
@ -58,22 +69,31 @@ void break_to_debugger();
void set_wxapp(wxApp *app); void set_wxapp(wxApp *app);
void set_main_frame(wxFrame *main_frame); void set_main_frame(wxFrame *main_frame);
void set_tab_panel(wxNotebook *tab_panel); void set_tab_panel(wxNotebook *tab_panel);
void set_app_config(AppConfig *app_config);
AppConfig* get_app_config();
wxApp* get_app();
void add_debug_menu(wxMenuBar *menu, int event_language_change); void add_debug_menu(wxMenuBar *menu, int event_language_change);
// Create "Preferences" dialog after selecting menu "Preferences" in Perl part
void open_preferences_dialog(int event_preferences);
// Create a new preset tab (print, filament and printer), // Create a new preset tab (print, filament and printer),
void create_preset_tabs(PresetBundle *preset_bundle, AppConfig *app_config, void create_preset_tabs(PresetBundle *preset_bundle,
bool no_controller, bool is_disabled_button_browse, bool is_user_agent, bool no_controller, bool is_disabled_button_browse, bool is_user_agent,
int event_value_change, int event_presets_changed, int event_value_change, int event_presets_changed,
int event_button_browse, int event_button_test); int event_button_browse, int event_button_test);
TabIface* get_preset_tab_iface(char *name); TabIface* get_preset_tab_iface(char *name);
// add it at the end of the tab panel. // add it at the end of the tab panel.
void add_created_tab(Tab* panel, PresetBundle *preset_bundle, AppConfig *app_config); void add_created_tab(Tab* panel, PresetBundle *preset_bundle);
// Change option value in config // Change option value in config
void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, boost::any value, int opt_index = 0); void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, boost::any value, int opt_index = 0);
void show_error(wxWindow* parent, wxString message); void show_error(wxWindow* parent, wxString message);
void show_info(wxWindow* parent, wxString message, wxString title); void show_info(wxWindow* parent, wxString message, wxString title);
void warning_catcher(wxWindow* parent, wxString message);
// load language saved at application config // load language saved at application config
bool load_language(); bool load_language();
@ -97,6 +117,13 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string
// encoded inside an int. // encoded inside an int.
int combochecklist_get_flags(wxComboCtrl* comboCtrl); int combochecklist_get_flags(wxComboCtrl* comboCtrl);
// Return translated std::string as a wxString
wxString L_str(std::string str);
// Return wxString from std::string in UTF8
wxString from_u8(std::string str);
wxWindow *get_widget_by_id(int id);
} }
} }

View file

@ -2,7 +2,6 @@
#include "ConfigExceptions.hpp" #include "ConfigExceptions.hpp"
#include <utility> #include <utility>
#include <wx/tooltip.h>
#include <wx/numformatter.h> #include <wx/numformatter.h>
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
@ -122,13 +121,13 @@ void OptionsGroup::append_line(const Line& line) {
} }
// If there's a widget, build it and add the result to the sizer. // If there's a widget, build it and add the result to the sizer.
if (line.widget != nullptr) { if (line.widget != nullptr) {
auto wgt = line.widget(parent()); auto wgt = line.widget(parent());
grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, wxOSX ? 0 : 5); grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, wxOSX ? 0 : 5);
return; return;
} }
// if we have a single option with no sidetext just add it directly to the grid sizer // if we have a single option with no sidetext just add it directly to the grid sizer
if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 && if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 &&
option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) { option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) {
const auto& option = option_set.front(); const auto& option = option_set.front();
@ -152,7 +151,12 @@ void OptionsGroup::append_line(const Line& line) {
ConfigOptionDef option = opt.opt; ConfigOptionDef option = opt.opt;
// add label if any // add label if any
if (option.label != "") { if (option.label != "") {
auto field_label = new wxStaticText(parent(), wxID_ANY, wxString::FromUTF8(option.label.c_str()) + ":", wxDefaultPosition, wxDefaultSize); wxString str_label = L_str(option.label);
//! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1
// wxString str_label = (option.label == "Top" || option.label == "Bottom") ?
// wxGETTEXT_IN_CONTEXT("Layers", wxString(option.label.c_str()):
// L_str(option.label);
auto field_label = new wxStaticText(parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize);
field_label->SetFont(label_font); field_label->SetFont(label_font);
sizer->Add(field_label, 0, wxALIGN_CENTER_VERTICAL, 0); sizer->Add(field_label, 0, wxALIGN_CENTER_VERTICAL, 0);
} }
@ -166,7 +170,7 @@ void OptionsGroup::append_line(const Line& line) {
// add sidetext if any // add sidetext if any
if (option.sidetext != "") { if (option.sidetext != "") {
auto sidetext = new wxStaticText(parent(), wxID_ANY, wxString::FromUTF8(option.sidetext.c_str()), wxDefaultPosition, wxDefaultSize); auto sidetext = new wxStaticText(parent(), wxID_ANY, L_str(option.sidetext), wxDefaultPosition, wxDefaultSize);
sidetext->SetFont(sidetext_font); sidetext->SetFont(sidetext_font);
sizer->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); sizer->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4);
} }
@ -188,7 +192,7 @@ void OptionsGroup::append_line(const Line& line) {
} }
Line OptionsGroup::create_single_option_line(const Option& option) const { Line OptionsGroup::create_single_option_line(const Option& option) const {
Line retval{ wxString::FromUTF8(option.opt.label.c_str()), wxString::FromUTF8(option.opt.tooltip.c_str()) }; Line retval{ L_str(option.opt.label), L_str(option.opt.tooltip) };
Option tmp(option); Option tmp(option);
tmp.opt.label = std::string(""); tmp.opt.label = std::string("");
retval.append_option(tmp); retval.append_option(tmp);
@ -203,7 +207,7 @@ void OptionsGroup::on_change_OG(t_config_option_key id, /*config_value*/boost::a
Option ConfigOptionsGroup::get_option(const std::string opt_key, int opt_index /*= -1*/) Option ConfigOptionsGroup::get_option(const std::string opt_key, int opt_index /*= -1*/)
{ {
if (!m_config->has(opt_key)) { if (!m_config->has(opt_key)) {
//! exception ("No $opt_key in ConfigOptionsGroup config"); std::cerr << "No " << opt_key << " in ConfigOptionsGroup config.";
} }
std::string opt_id = opt_index == -1 ? opt_key : opt_key + "#" + std::to_string(opt_index); std::string opt_id = opt_index == -1 ? opt_key : opt_key + "#" + std::to_string(opt_index);
@ -287,14 +291,6 @@ boost::any ConfigOptionsGroup::config_value(std::string opt_key, int opt_index,
} }
} }
wxString double_to_string(double const value)
{
int precision = 10 * value - int(10 * value) == 0 ? 1 : 2;
return value - int(value) == 0 ?
wxString::Format(_T("%i"), int(value)) :
wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None);
}
boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std::string opt_key, int opt_index/* = -1*/) boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std::string opt_key, int opt_index/* = -1*/)
{ {
size_t idx = opt_index == -1 ? 0 : opt_index; size_t idx = opt_index == -1 ? 0 : opt_index;
@ -325,9 +321,9 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std:
case coFloats: case coFloats:
case coFloat:{ case coFloat:{
double val = opt->type == coFloats ? double val = opt->type == coFloats ?
config.opt_float(opt_key, idx/*0opt_index*/) : config.opt_float(opt_key, idx) :
opt->type == coFloat ? config.opt_float(opt_key) : opt->type == coFloat ? config.opt_float(opt_key) :
config.option<ConfigOptionPercents>(opt_key)->values.at(idx/*0*/); config.option<ConfigOptionPercents>(opt_key)->values.at(idx);
ret = double_to_string(val); ret = double_to_string(val);
} }
break; break;
@ -338,19 +334,19 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std:
if (config.option<ConfigOptionStrings>(opt_key)->values.empty()) if (config.option<ConfigOptionStrings>(opt_key)->values.empty())
ret = text_value; ret = text_value;
else else
ret = static_cast<wxString>(config.opt_string(opt_key, static_cast<unsigned int>(idx/*0*/)/*opt_index*/)); ret = static_cast<wxString>(config.opt_string(opt_key, static_cast<unsigned int>(idx)));
break; break;
case coBool: case coBool:
ret = config.opt_bool(opt_key); ret = config.opt_bool(opt_key);
break; break;
case coBools: case coBools:
ret = config.opt_bool(opt_key, idx/*0opt_index*/); ret = config.opt_bool(opt_key, idx);
break; break;
case coInt: case coInt:
ret = config.opt_int(opt_key); ret = config.opt_int(opt_key);
break; break;
case coInts: case coInts:
ret = config.opt_int(opt_key, idx/*0/*opt_index*/); ret = config.opt_int(opt_key, idx);
break; break;
case coEnum:{ case coEnum:{
if (opt_key.compare("external_fill_pattern") == 0 || if (opt_key.compare("external_fill_pattern") == 0 ||
@ -369,7 +365,7 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std:
break; break;
case coPoints:{ case coPoints:{
const auto &value = *config.option<ConfigOptionPoints>(opt_key); const auto &value = *config.option<ConfigOptionPoints>(opt_key);
ret = value.values.at(idx/*0*/); ret = value.values.at(idx);
} }
break; break;
case coNone: case coNone:
@ -397,14 +393,5 @@ void ogStaticText::SetText(wxString value)
GetParent()->Layout(); GetParent()->Layout();
} }
void Option::translate()
{
opt.label = _LU8(opt.label);
opt.tooltip = _LU8(opt.tooltip);
opt.sidetext = _LU8(opt.sidetext);
opt.full_label = _LU8(opt.full_label);
opt.category = _LU8(opt.category);
}
} // GUI } // GUI
} // Slic3r } // Slic3r

View file

@ -35,8 +35,7 @@ struct Option {
bool readonly {false}; bool readonly {false};
Option(const ConfigOptionDef& _opt, t_config_option_key id) : Option(const ConfigOptionDef& _opt, t_config_option_key id) :
opt(_opt), opt_id(id) { translate(); } opt(_opt), opt_id(id) {}
void translate();
}; };
using t_option = std::unique_ptr<Option>; //! using t_option = std::unique_ptr<Option>; //!
@ -90,9 +89,22 @@ public:
void append_single_option_line(const Option& option) { append_line(create_single_option_line(option)); } void append_single_option_line(const Option& option) { append_line(create_single_option_line(option)); }
// return a non-owning pointer reference // return a non-owning pointer reference
inline /*const*/ Field* get_field(t_config_option_key id) const { try { return m_fields.at(id).get(); } catch (std::out_of_range e) { return nullptr; } } inline Field* get_field(t_config_option_key id) const{
bool set_value(t_config_option_key id, boost::any value) { try { m_fields.at(id)->set_value(value); return true; } catch (std::out_of_range e) { return false; } } if (m_fields.find(id) == m_fields.end()) return nullptr;
boost::any get_value(t_config_option_key id) { boost::any out; try { out = m_fields.at(id)->get_value(); } catch (std::out_of_range e) { ; } return out; } return m_fields.at(id).get();
}
bool set_value(t_config_option_key id, boost::any value) {
if (m_fields.find(id) == m_fields.end()) return false;
m_fields.at(id)->set_value(value);
return true;
}
boost::any get_value(t_config_option_key id) {
boost::any out;
if (m_fields.find(id) == m_fields.end()) ;
else
out = m_fields.at(id)->get_value();
return out;
}
inline void enable() { for (auto& field : m_fields) field.second->enable(); } inline void enable() { for (auto& field : m_fields) field.second->enable(); }
inline void disable() { for (auto& field : m_fields) field.second->disable(); } inline void disable() { for (auto& field : m_fields) field.second->disable(); }

View file

@ -0,0 +1,120 @@
#include "Preferences.hpp"
#include "AppConfig.hpp"
#include "OptionsGroup.hpp"
namespace Slic3r {
namespace GUI {
void PreferencesDialog::build()
{
auto app_config = get_app_config();
m_optgroup = std::make_shared<ConfigOptionsGroup>(this, _(L("General")));
m_optgroup->label_width = 200;
m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
};
// $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
// opt_id = > 'version_check',
// type = > 'bool',
// label = > 'Check for updates',
// tooltip = > 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.',
// default = > $app_config->get("version_check") // 1,
// readonly = > !wxTheApp->have_version_check,
// ));
ConfigOptionDef def;
def.label = L("Remember output directory");
def.type = coBool;
def.tooltip = L("If this is enabled, Slic3r will prompt the last output directory "
"instead of the one containing the input files.");
def.default_value = new ConfigOptionBool{ app_config->get("remember_output_path")[0] == '1' }; // 1;
Option option(def, "remember_output_path");
m_optgroup->append_single_option_line(option);
def.label = L("Auto-center parts");
def.type = coBool;
def.tooltip = L("If this is enabled, Slic3r will auto-center objects "
"around the print bed center.");
def.default_value = new ConfigOptionBool{ app_config->get("autocenter")[0] == '1' }; // 1;
option = Option (def,"autocenter");
m_optgroup->append_single_option_line(option);
def.label = L("Background processing");
def.type = coBool;
def.tooltip = L("If this is enabled, Slic3r will pre-process objects as soon "
"as they\'re loaded in order to save time when exporting G-code.");
def.default_value = new ConfigOptionBool{ app_config->get("background_processing")[0] == '1' }; // 1;
option = Option (def,"background_processing");
m_optgroup->append_single_option_line(option);
def.label = L("Disable USB/serial connection");
def.type = coBool;
def.tooltip = L("Disable communication with the printer over a serial / USB cable. "
"This simplifies the user interface in case the printer is never attached to the computer.");
def.default_value = new ConfigOptionBool{ app_config->get("no_controller")[0] == '1' }; // 1;
option = Option (def,"no_controller");
m_optgroup->append_single_option_line(option);
def.label = L("Suppress \" - default - \" presets");
def.type = coBool;
def.tooltip = L("Suppress \" - default - \" presets in the Print / Filament / Printer "
"selections once there are any other valid presets available.");
def.default_value = new ConfigOptionBool{ app_config->get("no_defaults")[0] == '1' }; // 1;
option = Option (def,"no_defaults");
m_optgroup->append_single_option_line(option);
def.label = L("Show incompatible print and filament presets");
def.type = coBool;
def.tooltip = L("When checked, the print and filament presets are shown in the preset editor "
"even if they are marked as incompatible with the active printer");
def.default_value = new ConfigOptionBool{ app_config->get("show_incompatible_presets")[0] == '1' }; // 1;
option = Option (def,"show_incompatible_presets");
m_optgroup->append_single_option_line(option);
def.label = L("Use legacy OpenGL 1.1 rendering");
def.type = coBool;
def.tooltip = L("If you have rendering issues caused by a buggy OpenGL 2.0 driver, "
"you may try to check this checkbox. This will disable the layer height "
"editing and anti aliasing, so it is likely better to upgrade your graphics driver.");
def.default_value = new ConfigOptionBool{ app_config->get("use_legacy_opengl")[0] == '1' }; // 1;
option = Option (def,"use_legacy_opengl");
m_optgroup->append_single_option_line(option);
auto sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); });
sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
SetSizer(sizer);
sizer->SetSizeHints(this);
}
void PreferencesDialog::accept()
{
if (m_values.find("no_controller") != m_values.end()||
m_values.find("no_defaults") != m_values.end()||
m_values.find("use_legacy_opengl")!= m_values.end()) {
warning_catcher(this, _(L("You need to restart Slic3r to make the changes effective.")));
}
auto app_config = get_app_config();
for (std::map<std::string, std::string>::iterator it = m_values.begin(); it != m_values.end(); ++it) {
app_config->set(it->first, it->second);
}
EndModal(wxID_OK);
Close(); // needed on Linux
// Nothify the UI to update itself from the ini file.
if (m_event_preferences > 0) {
wxCommandEvent event(m_event_preferences);
get_app()->ProcessEvent(event);
}
}
} // GUI
} // Slic3r

View file

@ -0,0 +1,27 @@
#include "GUI.hpp"
#include <wx/dialog.h>
#include <map>
namespace Slic3r {
namespace GUI {
class ConfigOptionsGroup;
class PreferencesDialog : public wxDialog
{
std::map<std::string, std::string> m_values;
std::shared_ptr<ConfigOptionsGroup> m_optgroup;
int m_event_preferences;
public:
PreferencesDialog(wxWindow* parent, int event_preferences) : wxDialog(parent, wxID_ANY, _(L("Preferences")),
wxDefaultPosition, wxDefaultSize), m_event_preferences(event_preferences) { build(); }
~PreferencesDialog(){ }
void build();
void accept();
};
} // GUI
} // Slic3r

View file

@ -224,7 +224,7 @@ const std::vector<std::string>& Preset::printer_options()
if (s_opts.empty()) { if (s_opts.empty()) {
s_opts = { s_opts = {
"bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed",
"octoprint_host", "octoprint_apikey", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "octoprint_host", "octoprint_apikey", "octoprint_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
"between_objects_gcode", "printer_notes", "cooling_tube_retraction", "cooling_tube_length", "parking_pos_retraction" "between_objects_gcode", "printer_notes", "cooling_tube_retraction", "cooling_tube_length", "parking_pos_retraction"
}; };

View file

@ -14,6 +14,7 @@
#include <boost/property_tree/ini_parser.hpp> #include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <boost/log/trivial.hpp>
#include <wx/dcmemory.h> #include <wx/dcmemory.h>
#include <wx/image.h> #include <wx/image.h>
@ -474,6 +475,125 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const
this->update_compatible_with_printer(false); this->update_compatible_with_printer(false);
} }
// Process the Config Bundle loaded as a Boost property tree.
// For each print, filament and printer preset (group defined by group_name), apply the inherited presets.
// The presets starting with '*' are considered non-terminal and they are
// removed through the flattening process by this function.
// This function will never fail, but it will produce error messages through boost::log.
static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree, const std::string &group_name)
{
namespace pt = boost::property_tree;
typedef std::pair<pt::ptree::key_type, pt::ptree> ptree_child_type;
// 1) For the group given by group_name, initialize the presets.
struct Prst {
Prst(const std::string &name, pt::ptree *node) : name(name), node(node) {}
// Name of this preset. If the name starts with '*', it is an intermediate preset,
// which will not make it into the result.
const std::string name;
// Link to the source boost property tree node, owned by tree.
pt::ptree *node;
// Link to the presets, from which this preset inherits.
std::vector<Prst*> inherits;
// Link to the presets, for which this preset is a direct parent.
std::vector<Prst*> parent_of;
// When running the Kahn's Topological sorting algorithm, this counter is decreased from inherits.size() to zero.
// A cycle is indicated, if the number does not drop to zero after the Kahn's algorithm finishes.
size_t num_incoming_edges_left = 0;
// Sorting by the name, to be used when inserted into std::set.
bool operator==(const Prst &rhs) const { return this->name == rhs.name; }
bool operator< (const Prst &rhs) const { return this->name < rhs.name; }
};
// Find the presets, store them into a std::map, addressed by their names.
std::set<Prst> presets;
std::string group_name_preset = group_name + ":";
for (auto &section : tree)
if (boost::starts_with(section.first, group_name_preset) && section.first.size() > group_name_preset.size())
presets.emplace(section.first.substr(group_name_preset.size()), &section.second);
// Fill in the "inherits" and "parent_of" members, report invalid inheritance fields.
for (const Prst &prst : presets) {
// Parse the list of comma separated values, possibly enclosed in quotes.
std::vector<std::string> inherits_names;
if (Slic3r::unescape_strings_cstyle(prst.node->get<std::string>("inherits", ""), inherits_names)) {
// Resolve the inheritance by name.
std::vector<Prst*> &inherits_nodes = const_cast<Prst&>(prst).inherits;
for (const std::string &node_name : inherits_names) {
auto it = presets.find(Prst(node_name, nullptr));
if (it == presets.end())
BOOST_LOG_TRIVIAL(error) << "flatten_configbundle_hierarchy: The preset " << prst.name << " inherits an unknown preset \"" << node_name << "\"";
else {
inherits_nodes.emplace_back(const_cast<Prst*>(&(*it)));
inherits_nodes.back()->parent_of.emplace_back(const_cast<Prst*>(&prst));
}
}
} else {
BOOST_LOG_TRIVIAL(error) << "flatten_configbundle_hierarchy: The preset " << prst.name << " has an invalid \"inherits\" field";
}
// Remove the "inherits" key, it has no meaning outside the config bundle.
const_cast<pt::ptree*>(prst.node)->erase("inherits");
}
// 2) Create a linear ordering for the directed acyclic graph of preset inheritance.
// https://en.wikipedia.org/wiki/Topological_sorting
// Kahn's algorithm.
std::vector<Prst*> sorted;
{
// Initialize S with the set of all nodes with no incoming edge.
std::deque<Prst*> S;
for (const Prst &prst : presets)
if (prst.inherits.empty())
S.emplace_back(const_cast<Prst*>(&prst));
else
const_cast<Prst*>(&prst)->num_incoming_edges_left = prst.inherits.size();
while (! S.empty()) {
Prst *n = S.front();
S.pop_front();
sorted.emplace_back(n);
for (Prst *m : n->parent_of) {
assert(m->num_incoming_edges_left > 0);
if (-- m->num_incoming_edges_left == 0) {
// We have visited all parents of m.
S.emplace_back(m);
}
}
}
if (sorted.size() < presets.size()) {
for (const Prst &prst : presets)
if (prst.num_incoming_edges_left)
BOOST_LOG_TRIVIAL(error) << "flatten_configbundle_hierarchy: The preset " << prst.name << " has cyclic dependencies";
}
}
// Apply the dependencies in their topological ordering.
for (Prst *prst : sorted) {
// Merge the preset nodes in their order of application.
// Iterate in a reverse order, so the last change will be placed first in merged.
for (auto it_inherits = prst->inherits.rbegin(); it_inherits != prst->inherits.rend(); ++ it_inherits)
for (auto it = (*it_inherits)->node->begin(); it != (*it_inherits)->node->end(); ++ it)
if (prst->node->find(it->first) == prst->node->not_found())
prst->node->add_child(it->first, it->second);
}
// Remove the "internal" presets from the ptree. These presets are marked with '*'.
group_name_preset += '*';
for (auto it_section = tree.begin(); it_section != tree.end(); ) {
if (boost::starts_with(it_section->first, group_name_preset) && it_section->first.size() > group_name_preset.size())
// Remove the "internal" preset from the ptree.
it_section = tree.erase(it_section);
else
// Keep the preset.
++ it_section;
}
}
static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree)
{
flatten_configbundle_hierarchy(tree, "print");
flatten_configbundle_hierarchy(tree, "filament");
flatten_configbundle_hierarchy(tree, "printer");
}
// Load a config bundle file, into presets and store the loaded presets into separate files // Load a config bundle file, into presets and store the loaded presets into separate files
// of the local configuration directory. // of the local configuration directory.
size_t PresetBundle::load_configbundle(const std::string &path, unsigned int flags) size_t PresetBundle::load_configbundle(const std::string &path, unsigned int flags)
@ -486,6 +606,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
pt::ptree tree; pt::ptree tree;
boost::nowide::ifstream ifs(path); boost::nowide::ifstream ifs(path);
pt::read_ini(ifs, tree); pt::read_ini(ifs, tree);
// Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed.
flatten_configbundle_hierarchy(tree);
// 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files. // 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files.
std::vector<std::string> loaded_prints; std::vector<std::string> loaded_prints;

View file

@ -6,8 +6,10 @@
#include "Flow.hpp" #include "Flow.hpp"
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <wx/intl.h>
#include "../../libslic3r/libslic3r.h" #include "../../libslic3r/libslic3r.h"
#include "GUI.hpp"
namespace Slic3r { namespace Slic3r {
@ -21,31 +23,31 @@ std::string PresetHints::cooling_description(const Preset &preset)
int max_fan_speed = preset.config.opt_int("max_fan_speed", 0); int max_fan_speed = preset.config.opt_int("max_fan_speed", 0);
int min_print_speed = int(preset.config.opt_float("min_print_speed", 0) + 0.5); int min_print_speed = int(preset.config.opt_float("min_print_speed", 0) + 0.5);
int fan_below_layer_time = preset.config.opt_int("fan_below_layer_time", 0); int fan_below_layer_time = preset.config.opt_int("fan_below_layer_time", 0);
sprintf(buf, "If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).", sprintf(buf, _CHB(L("If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).")),
slowdown_below_layer_time, max_fan_speed, slowdown_below_layer_time, min_print_speed); slowdown_below_layer_time, max_fan_speed, slowdown_below_layer_time, min_print_speed);
out += buf; out += buf;
if (fan_below_layer_time > slowdown_below_layer_time) { if (fan_below_layer_time > slowdown_below_layer_time) {
sprintf(buf, "\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.", sprintf(buf, _CHB(L("\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.")),
fan_below_layer_time, max_fan_speed, min_fan_speed); fan_below_layer_time, max_fan_speed, min_fan_speed);
out += buf; out += buf;
} }
out += "\nDuring the other layers, fan "; out += _CHB(L("\nDuring the other layers, fan "));
} else { } else {
out = "Fan "; out = _CHB(L("Fan "));
} }
if (preset.config.opt_bool("fan_always_on", 0)) { if (preset.config.opt_bool("fan_always_on", 0)) {
int disable_fan_first_layers = preset.config.opt_int("disable_fan_first_layers", 0); int disable_fan_first_layers = preset.config.opt_int("disable_fan_first_layers", 0);
int min_fan_speed = preset.config.opt_int("min_fan_speed", 0); int min_fan_speed = preset.config.opt_int("min_fan_speed", 0);
sprintf(buf, "will always run at %d%% ", min_fan_speed); sprintf(buf, _CHB(L("will always run at %d%% ")), min_fan_speed);
out += buf; out += buf;
if (disable_fan_first_layers > 1) { if (disable_fan_first_layers > 1) {
sprintf(buf, "except for the first %d layers", disable_fan_first_layers); sprintf(buf, _CHB(L("except for the first %d layers")), disable_fan_first_layers);
out += buf; out += buf;
} }
else if (disable_fan_first_layers == 1) else if (disable_fan_first_layers == 1)
out += "except for the first layer"; out += _CHB(L("except for the first layer"));
} else } else
out += "will be turned off."; out += _CHB(L("will be turned off."));
return out; return out;
} }
@ -146,7 +148,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
limit_by_first_layer_speed(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed)); limit_by_first_layer_speed(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed));
if (max_flow < external_perimeter_rate) { if (max_flow < external_perimeter_rate) {
max_flow = external_perimeter_rate; max_flow = external_perimeter_rate;
max_flow_extrusion_type = "external perimeters"; max_flow_extrusion_type = _CHB(L("external perimeters"));
} }
double perimeter_rate = Flow::new_from_config_width(frPerimeter, double perimeter_rate = Flow::new_from_config_width(frPerimeter,
first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width), first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width),
@ -155,7 +157,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
limit_by_first_layer_speed(std::max(perimeter_speed, small_perimeter_speed), max_print_speed)); limit_by_first_layer_speed(std::max(perimeter_speed, small_perimeter_speed), max_print_speed));
if (max_flow < perimeter_rate) { if (max_flow < perimeter_rate) {
max_flow = perimeter_rate; max_flow = perimeter_rate;
max_flow_extrusion_type = "perimeters"; max_flow_extrusion_type = _CHB(L("perimeters"));
} }
} }
if (! bridging && infill_extruder_active) { if (! bridging && infill_extruder_active) {
@ -164,7 +166,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(infill_speed, max_print_speed); nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(infill_speed, max_print_speed);
if (max_flow < infill_rate) { if (max_flow < infill_rate) {
max_flow = infill_rate; max_flow = infill_rate;
max_flow_extrusion_type = "infill"; max_flow_extrusion_type = _CHB(L("infill"));
} }
} }
if (solid_infill_extruder_active) { if (solid_infill_extruder_active) {
@ -174,7 +176,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
(bridging ? bridge_speed : limit_by_first_layer_speed(solid_infill_speed, max_print_speed)); (bridging ? bridge_speed : limit_by_first_layer_speed(solid_infill_speed, max_print_speed));
if (max_flow < solid_infill_rate) { if (max_flow < solid_infill_rate) {
max_flow = solid_infill_rate; max_flow = solid_infill_rate;
max_flow_extrusion_type = "solid infill"; max_flow_extrusion_type = _CHB(L("solid infill"));
} }
if (! bridging) { if (! bridging) {
double top_solid_infill_rate = Flow::new_from_config_width(frInfill, double top_solid_infill_rate = Flow::new_from_config_width(frInfill,
@ -182,7 +184,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(top_solid_infill_speed, max_print_speed); nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(top_solid_infill_speed, max_print_speed);
if (max_flow < top_solid_infill_rate) { if (max_flow < top_solid_infill_rate) {
max_flow = top_solid_infill_rate; max_flow = top_solid_infill_rate;
max_flow_extrusion_type = "top solid infill"; max_flow_extrusion_type = _CHB(L("top solid infill"));
} }
} }
} }
@ -193,7 +195,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
(bridging ? bridge_speed : limit_by_first_layer_speed(support_material_speed, max_print_speed)); (bridging ? bridge_speed : limit_by_first_layer_speed(support_material_speed, max_print_speed));
if (max_flow < support_material_rate) { if (max_flow < support_material_rate) {
max_flow = support_material_rate; max_flow = support_material_rate;
max_flow_extrusion_type = "support"; max_flow_extrusion_type = _CHB(L("support"));
} }
} }
if (support_material_interface_extruder_active) { if (support_material_interface_extruder_active) {
@ -203,25 +205,25 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
(bridging ? bridge_speed : limit_by_first_layer_speed(support_material_interface_speed, max_print_speed)); (bridging ? bridge_speed : limit_by_first_layer_speed(support_material_interface_speed, max_print_speed));
if (max_flow < support_material_interface_rate) { if (max_flow < support_material_interface_rate) {
max_flow = support_material_interface_rate; max_flow = support_material_interface_rate;
max_flow_extrusion_type = "support interface"; max_flow_extrusion_type = _CHB(L("support interface"));
} }
} }
//FIXME handle gap_fill_speed //FIXME handle gap_fill_speed
if (! out.empty()) if (! out.empty())
out += "\n"; out += "\n";
out += (first_layer ? "First layer volumetric" : (bridging ? "Bridging volumetric" : "Volumetric")); out += (first_layer ? _CHB(L("First layer volumetric")) : (bridging ? _CHB(L("Bridging volumetric")) : _CHB(L("Volumetric"))));
out += " flow rate is maximized "; out += _CHB(L(" flow rate is maximized "));
bool limited_by_max_volumetric_speed = max_volumetric_speed > 0 && max_volumetric_speed < max_flow; bool limited_by_max_volumetric_speed = max_volumetric_speed > 0 && max_volumetric_speed < max_flow;
out += (limited_by_max_volumetric_speed ? out += (limited_by_max_volumetric_speed ?
"by the print profile maximum" : _CHB(L("by the print profile maximum")) :
("when printing " + max_flow_extrusion_type)) (_CHB(L("when printing ")) + max_flow_extrusion_type))
+ " with a volumetric rate "; + _CHB(L(" with a volumetric rate "));
if (limited_by_max_volumetric_speed) if (limited_by_max_volumetric_speed)
max_flow = max_volumetric_speed; max_flow = max_volumetric_speed;
char buf[2048]; char buf[2048];
sprintf(buf, "%3.2f mm³/s", max_flow); sprintf(buf, _CHB(L("%3.2f mm³/s")), max_flow);
out += buf; out += buf;
sprintf(buf, " at filament speed %3.2f mm/s.", max_flow / filament_crossection); sprintf(buf, _CHB(L(" at filament speed %3.2f mm/s.")), max_flow / filament_crossection);
out += buf; out += buf;
} }
@ -238,8 +240,11 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre
bool thin_walls = print_config.opt_bool("thin_walls"); bool thin_walls = print_config.opt_bool("thin_walls");
float nozzle_diameter = float(printer_config.opt_float("nozzle_diameter", 0)); float nozzle_diameter = float(printer_config.opt_float("nozzle_diameter", 0));
if (layer_height <= 0.f) std::string out;
return "Recommended object thin wall thickness: Not available due to invalid layer height."; if (layer_height <= 0.f){
out += _CHB(L("Recommended object thin wall thickness: Not available due to invalid layer height."));
return out;
}
Flow external_perimeter_flow = Flow::new_from_config_width( Flow external_perimeter_flow = Flow::new_from_config_width(
frExternalPerimeter, frExternalPerimeter,
@ -250,18 +255,18 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre
*print_config.opt<ConfigOptionFloatOrPercent>("perimeter_extrusion_width"), *print_config.opt<ConfigOptionFloatOrPercent>("perimeter_extrusion_width"),
nozzle_diameter, layer_height, false); nozzle_diameter, layer_height, false);
std::string out;
if (num_perimeters > 0) { if (num_perimeters > 0) {
int num_lines = std::min(num_perimeters * 2, 10); int num_lines = std::min(num_perimeters * 2, 10);
char buf[256]; char buf[256];
sprintf(buf, "Recommended object thin wall thickness for layer height %.2f and ", layer_height); sprintf(buf, _CHB(L("Recommended object thin wall thickness for layer height %.2f and ")), layer_height);
out += buf; out += buf;
// Start with the width of two closely spaced // Start with the width of two closely spaced
double width = external_perimeter_flow.width + external_perimeter_flow.spacing(); double width = external_perimeter_flow.width + external_perimeter_flow.spacing();
for (int i = 2; i <= num_lines; thin_walls ? ++ i : i += 2) { for (int i = 2; i <= num_lines; thin_walls ? ++ i : i += 2) {
if (i > 2) if (i > 2)
out += ", "; out += ", ";
sprintf(buf, "%d lines: %.2lf mm", i, width); sprintf(buf, _CHB(L("%d lines: %.2lf mm")), i, width);
out += buf; out += buf;
width += perimeter_flow.spacing() * (thin_walls ? 1.f : 2.f); width += perimeter_flow.spacing() * (thin_walls ? 1.f : 2.f);
} }

File diff suppressed because it is too large Load diff

View file

@ -107,7 +107,7 @@ protected:
public: public:
PresetBundle* m_preset_bundle; PresetBundle* m_preset_bundle;
bool m_show_btn_incompatible_presets; bool m_show_btn_incompatible_presets = false;
PresetCollection* m_presets; PresetCollection* m_presets;
DynamicPrintConfig* m_config; DynamicPrintConfig* m_config;
@ -180,7 +180,7 @@ class TabPrint : public Tab
public: public:
TabPrint() {} TabPrint() {}
TabPrint(wxNotebook* parent, bool no_controller) : TabPrint(wxNotebook* parent, bool no_controller) :
Tab(parent, _L("Print Settings"), "print", no_controller) {} Tab(parent, _(L("Print Settings")), "print", no_controller) {}
~TabPrint(){} ~TabPrint(){}
ogStaticText* m_recommended_thin_wall_thickness_description_line; ogStaticText* m_recommended_thin_wall_thickness_description_line;
@ -200,7 +200,7 @@ class TabFilament : public Tab
public: public:
TabFilament() {} TabFilament() {}
TabFilament(wxNotebook* parent, bool no_controller) : TabFilament(wxNotebook* parent, bool no_controller) :
Tab(parent, _L("Filament Settings"), "filament", no_controller) {} Tab(parent, _(L("Filament Settings")), "filament", no_controller) {}
~TabFilament(){} ~TabFilament(){}
void build() override; void build() override;
@ -226,7 +226,7 @@ public:
TabPrinter() {} TabPrinter() {}
TabPrinter(wxNotebook* parent, bool no_controller, bool is_disabled_btn_browse, bool is_user_agent) : TabPrinter(wxNotebook* parent, bool no_controller, bool is_disabled_btn_browse, bool is_user_agent) :
Tab(parent, _L("Printer Settings"), "printer", no_controller), Tab(parent, _(L("Printer Settings")), "printer", no_controller),
m_is_disabled_button_browse(is_disabled_btn_browse), m_is_disabled_button_browse(is_disabled_btn_browse),
m_is_user_agent(is_user_agent) {} m_is_user_agent(is_user_agent) {}
~TabPrinter(){} ~TabPrinter(){}
@ -246,7 +246,7 @@ public:
class SavePresetWindow :public wxDialog class SavePresetWindow :public wxDialog
{ {
public: public:
SavePresetWindow(wxWindow* parent) :wxDialog(parent, wxID_ANY, _L("Save preset")){} SavePresetWindow(wxWindow* parent) :wxDialog(parent, wxID_ANY, _(L("Save preset"))){}
~SavePresetWindow(){} ~SavePresetWindow(){}
std::string m_chosen_name; std::string m_chosen_name;

View file

@ -11,7 +11,7 @@ void TabIface::load_config(DynamicPrintConfig* config) { m_tab->load_config(*con
void TabIface::load_key_value(char* opt_key, char* value){ m_tab->load_key_value(opt_key, static_cast<std::string>(value)); } void TabIface::load_key_value(char* opt_key, char* value){ m_tab->load_key_value(opt_key, static_cast<std::string>(value)); }
bool TabIface::current_preset_is_dirty() { return m_tab->current_preset_is_dirty();} bool TabIface::current_preset_is_dirty() { return m_tab->current_preset_is_dirty();}
void TabIface::OnActivate() { return m_tab->OnActivate();} void TabIface::OnActivate() { return m_tab->OnActivate();}
std::string TabIface::title() { return m_tab->title().ToStdString();} std::string TabIface::title() { return m_tab->title().ToUTF8().data(); }
DynamicPrintConfig* TabIface::get_config() { return m_tab->get_config(); } DynamicPrintConfig* TabIface::get_config() { return m_tab->get_config(); }
PresetCollection* TabIface::get_presets() { return m_tab!=nullptr ? m_tab->get_presets() : nullptr; } PresetCollection* TabIface::get_presets() { return m_tab!=nullptr ? m_tab->get_presets() : nullptr; }
std::vector<std::string> TabIface::get_dependent_tabs() { return m_tab->get_dependent_tabs(); } std::vector<std::string> TabIface::get_dependent_tabs() { return m_tab->get_dependent_tabs(); }

View file

@ -1,6 +1,8 @@
#include "wxExtensions.hpp" #include "wxExtensions.hpp"
const unsigned int wxCheckListBoxComboPopup::Height = 210; const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200;
const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200;
const unsigned int wxCheckListBoxComboPopup::DefaultItemHeight = 18;
bool wxCheckListBoxComboPopup::Create(wxWindow* parent) bool wxCheckListBoxComboPopup::Create(wxWindow* parent)
{ {
@ -25,16 +27,55 @@ wxString wxCheckListBoxComboPopup::GetStringValue() const
wxSize wxCheckListBoxComboPopup::GetAdjustedSize(int minWidth, int prefHeight, int maxHeight) wxSize wxCheckListBoxComboPopup::GetAdjustedSize(int minWidth, int prefHeight, int maxHeight)
{ {
// matches owner wxComboCtrl's width // matches owner wxComboCtrl's width
// and sets height dinamically in dependence of contained items count
wxComboCtrl* cmb = GetComboCtrl(); wxComboCtrl* cmb = GetComboCtrl();
if (cmb != nullptr) if (cmb != nullptr)
{ {
wxSize size = GetComboCtrl()->GetSize(); wxSize size = GetComboCtrl()->GetSize();
size.SetHeight(Height);
unsigned int count = GetCount();
if (count > 0)
size.SetHeight(count * DefaultItemHeight);
else
size.SetHeight(DefaultHeight);
return size; return size;
} }
else else
return wxSize(200, Height); return wxSize(DefaultWidth, DefaultHeight);
}
void wxCheckListBoxComboPopup::OnKeyEvent(wxKeyEvent& evt)
{
// filters out all the keys which are not working properly
switch (evt.GetKeyCode())
{
case WXK_LEFT:
case WXK_UP:
case WXK_RIGHT:
case WXK_DOWN:
case WXK_PAGEUP:
case WXK_PAGEDOWN:
case WXK_END:
case WXK_HOME:
case WXK_NUMPAD_LEFT:
case WXK_NUMPAD_UP:
case WXK_NUMPAD_RIGHT:
case WXK_NUMPAD_DOWN:
case WXK_NUMPAD_PAGEUP:
case WXK_NUMPAD_PAGEDOWN:
case WXK_NUMPAD_END:
case WXK_NUMPAD_HOME:
{
break;
}
default:
{
evt.Skip();
break;
}
}
} }
void wxCheckListBoxComboPopup::OnCheckListBox(wxCommandEvent& evt) void wxCheckListBoxComboPopup::OnCheckListBox(wxCommandEvent& evt)
@ -48,6 +89,8 @@ void wxCheckListBoxComboPopup::OnCheckListBox(wxCommandEvent& evt)
event.SetEventObject(cmb); event.SetEventObject(cmb);
cmb->ProcessWindowEvent(event); cmb->ProcessWindowEvent(event);
} }
evt.Skip();
} }
void wxCheckListBoxComboPopup::OnListBoxSelection(wxCommandEvent& evt) void wxCheckListBoxComboPopup::OnListBoxSelection(wxCommandEvent& evt)

View file

@ -6,7 +6,9 @@
class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
{ {
static const unsigned int Height; static const unsigned int DefaultWidth;
static const unsigned int DefaultHeight;
static const unsigned int DefaultItemHeight;
wxString m_text; wxString m_text;
@ -17,6 +19,8 @@ public:
virtual wxString GetStringValue() const; virtual wxString GetStringValue() const;
virtual wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight); virtual wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight);
virtual void OnKeyEvent(wxKeyEvent& evt);
void OnCheckListBox(wxCommandEvent& evt); void OnCheckListBox(wxCommandEvent& evt);
void OnListBoxSelection(wxCommandEvent& evt); void OnListBoxSelection(wxCommandEvent& evt);
}; };

View file

@ -0,0 +1,704 @@
#include "Bonjour.hpp"
#include <iostream> // XXX
#include <cstdint>
#include <algorithm>
#include <unordered_map>
#include <array>
#include <vector>
#include <string>
#include <random>
#include <thread>
#include <boost/optional.hpp>
#include <boost/system/error_code.hpp>
#include <boost/endian/conversion.hpp>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time_duration.hpp>
#include <boost/format.hpp>
using boost::optional;
using boost::system::error_code;
namespace endian = boost::endian;
namespace asio = boost::asio;
using boost::asio::ip::udp;
// TODO: Fuzzing test (done without TXT)
// FIXME: check char retype to unsigned
namespace Slic3r {
// Minimal implementation of a MDNS/DNS-SD client
// This implementation is extremely simple, only the bits that are useful
// for very basic MDNS discovery are present.
struct DnsName: public std::string
{
enum
{
MAX_RECURSION = 10, // Keep this low
};
static optional<DnsName> decode(const std::vector<char> &buffer, size_t &offset, unsigned depth = 0)
{
// Check offset sanity:
if (offset + 1 >= buffer.size()) {
return boost::none;
}
// Check for recursion depth to prevent parsing names that are nested too deeply
// or end up cyclic:
if (depth >= MAX_RECURSION) {
return boost::none;
}
DnsName res;
const size_t bsize = buffer.size();
while (true) {
const char* ptr = buffer.data() + offset;
unsigned len = static_cast<unsigned char>(*ptr);
if (len & 0xc0) {
// This is a recursive label
unsigned len_2 = static_cast<unsigned char>(ptr[1]);
size_t pointer = (len & 0x3f) << 8 | len_2;
const auto nested = decode(buffer, pointer, depth + 1);
if (!nested) {
return boost::none;
} else {
if (res.size() > 0) {
res.push_back('.');
}
res.append(*nested);
offset += 2;
return std::move(res);
}
} else if (len == 0) {
// This is a name terminator
offset++;
break;
} else {
// This is a regular label
len &= 0x3f;
if (len + offset + 1 >= bsize) {
return boost::none;
}
res.reserve(len);
if (res.size() > 0) {
res.push_back('.');
}
ptr++;
for (const auto end = ptr + len; ptr < end; ptr++) {
char c = *ptr;
if (c >= 0x20 && c <= 0x7f) {
res.push_back(c);
} else {
return boost::none;
}
}
offset += len + 1;
}
}
if (res.size() > 0) {
return std::move(res);
} else {
return boost::none;
}
}
};
struct DnsHeader
{
uint16_t id;
uint16_t flags;
uint16_t qdcount;
uint16_t ancount;
uint16_t nscount;
uint16_t arcount;
enum
{
SIZE = 12,
};
static DnsHeader decode(const std::vector<char> &buffer) {
DnsHeader res;
const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(buffer.data());
res.id = endian::big_to_native(data_16[0]);
res.flags = endian::big_to_native(data_16[1]);
res.qdcount = endian::big_to_native(data_16[2]);
res.ancount = endian::big_to_native(data_16[3]);
res.nscount = endian::big_to_native(data_16[4]);
res.arcount = endian::big_to_native(data_16[5]);
return res;
}
uint32_t rrcount() const {
return ancount + nscount + arcount;
}
};
struct DnsQuestion
{
enum
{
MIN_SIZE = 5,
};
DnsName name;
uint16_t type;
uint16_t qclass;
DnsQuestion() :
type(0),
qclass(0)
{}
static optional<DnsQuestion> decode(const std::vector<char> &buffer, size_t &offset)
{
auto qname = DnsName::decode(buffer, offset);
if (!qname) {
return boost::none;
}
DnsQuestion res;
res.name = std::move(*qname);
const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(buffer.data() + offset);
res.type = endian::big_to_native(data_16[0]);
res.qclass = endian::big_to_native(data_16[1]);
offset += 4;
return std::move(res);
}
};
struct DnsResource
{
DnsName name;
uint16_t type;
uint16_t rclass;
uint32_t ttl;
std::vector<char> data;
DnsResource() :
type(0),
rclass(0),
ttl(0)
{}
static optional<DnsResource> decode(const std::vector<char> &buffer, size_t &offset, size_t &dataoffset)
{
const size_t bsize = buffer.size();
if (offset + 1 >= bsize) {
return boost::none;
}
auto rname = DnsName::decode(buffer, offset);
if (!rname) {
return boost::none;
}
if (offset + 10 >= bsize) {
return boost::none;
}
DnsResource res;
res.name = std::move(*rname);
const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(buffer.data() + offset);
res.type = endian::big_to_native(data_16[0]);
res.rclass = endian::big_to_native(data_16[1]);
res.ttl = endian::big_to_native(*reinterpret_cast<const uint32_t*>(data_16 + 2));
uint16_t rdlength = endian::big_to_native(data_16[4]);
offset += 10;
if (offset + rdlength > bsize) {
return boost::none;
}
dataoffset = offset;
res.data = std::move(std::vector<char>(buffer.begin() + offset, buffer.begin() + offset + rdlength));
offset += rdlength;
return std::move(res);
}
};
struct DnsRR_A
{
enum { TAG = 0x1 };
asio::ip::address_v4 ip;
static void decode(optional<DnsRR_A> &result, const DnsResource &rr)
{
if (rr.data.size() == 4) {
DnsRR_A res;
const uint32_t ip = endian::big_to_native(*reinterpret_cast<const uint32_t*>(rr.data.data()));
res.ip = asio::ip::address_v4(ip);
result = std::move(res);
}
}
};
struct DnsRR_AAAA
{
enum { TAG = 0x1c };
asio::ip::address_v6 ip;
static void decode(optional<DnsRR_AAAA> &result, const DnsResource &rr)
{
if (rr.data.size() == 16) {
DnsRR_AAAA res;
std::array<unsigned char, 16> ip;
std::copy_n(rr.data.begin(), 16, ip.begin());
res.ip = asio::ip::address_v6(ip);
result = std::move(res);
}
}
};
struct DnsRR_SRV
{
enum
{
TAG = 0x21,
MIN_SIZE = 8,
};
uint16_t priority;
uint16_t weight;
uint16_t port;
DnsName hostname;
static optional<DnsRR_SRV> decode(const std::vector<char> &buffer, const DnsResource &rr, size_t dataoffset)
{
if (rr.data.size() < MIN_SIZE) {
return boost::none;
}
DnsRR_SRV res;
const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(rr.data.data());
res.priority = endian::big_to_native(data_16[0]);
res.weight = endian::big_to_native(data_16[1]);
res.port = endian::big_to_native(data_16[2]);
size_t offset = dataoffset + 6;
auto hostname = DnsName::decode(buffer, offset);
if (hostname) {
res.hostname = std::move(*hostname);
return std::move(res);
} else {
return boost::none;
}
}
};
struct DnsRR_TXT
{
enum
{
TAG = 0x10,
};
std::vector<std::string> values;
static optional<DnsRR_TXT> decode(const DnsResource &rr)
{
const size_t size = rr.data.size();
if (size < 2) {
return boost::none;
}
DnsRR_TXT res;
for (auto it = rr.data.begin(); it != rr.data.end(); ) {
unsigned val_size = static_cast<unsigned char>(*it);
if (val_size == 0 || it + val_size >= rr.data.end()) {
return boost::none;
}
++it;
std::string value(val_size, ' ');
std::copy(it, it + val_size, value.begin());
res.values.push_back(std::move(value));
it += val_size;
}
return std::move(res);
}
};
struct DnsSDPair
{
optional<DnsRR_SRV> srv;
optional<DnsRR_TXT> txt;
};
struct DnsSDMap : public std::map<std::string, DnsSDPair>
{
void insert_srv(std::string &&name, DnsRR_SRV &&srv)
{
auto hit = this->find(name);
if (hit != this->end()) {
hit->second.srv = std::move(srv);
} else {
DnsSDPair pair;
pair.srv = std::move(srv);
this->insert(std::make_pair(std::move(name), std::move(pair)));
}
}
void insert_txt(std::string &&name, DnsRR_TXT &&txt)
{
auto hit = this->find(name);
if (hit != this->end()) {
hit->second.txt = std::move(txt);
} else {
DnsSDPair pair;
pair.txt = std::move(txt);
this->insert(std::make_pair(std::move(name), std::move(pair)));
}
}
};
struct DnsMessage
{
enum
{
MAX_SIZE = 4096,
MAX_ANS = 30,
};
DnsHeader header;
optional<DnsQuestion> question;
optional<DnsRR_A> rr_a;
optional<DnsRR_AAAA> rr_aaaa;
std::vector<DnsRR_SRV> rr_srv;
DnsSDMap sdmap;
static optional<DnsMessage> decode(const std::vector<char> &buffer, optional<uint16_t> id_wanted = boost::none)
{
const auto size = buffer.size();
if (size < DnsHeader::SIZE + DnsQuestion::MIN_SIZE || size > MAX_SIZE) {
return boost::none;
}
DnsMessage res;
res.header = DnsHeader::decode(buffer);
if (id_wanted && *id_wanted != res.header.id) {
return boost::none;
}
if (res.header.qdcount > 1 || res.header.ancount > MAX_ANS) {
return boost::none;
}
size_t offset = DnsHeader::SIZE;
if (res.header.qdcount == 1) {
res.question = DnsQuestion::decode(buffer, offset);
}
for (unsigned i = 0; i < res.header.rrcount(); i++) {
size_t dataoffset = 0;
auto rr = DnsResource::decode(buffer, offset, dataoffset);
if (!rr) {
return boost::none;
} else {
res.parse_rr(buffer, std::move(*rr), dataoffset);
}
}
return std::move(res);
}
private:
void parse_rr(const std::vector<char> &buffer, DnsResource &&rr, size_t dataoffset)
{
switch (rr.type) {
case DnsRR_A::TAG: DnsRR_A::decode(this->rr_a, rr); break;
case DnsRR_AAAA::TAG: DnsRR_AAAA::decode(this->rr_aaaa, rr); break;
case DnsRR_SRV::TAG: {
auto srv = DnsRR_SRV::decode(buffer, rr, dataoffset);
if (srv) { this->sdmap.insert_srv(std::move(rr.name), std::move(*srv)); }
break;
}
case DnsRR_TXT::TAG: {
auto txt = DnsRR_TXT::decode(rr);
if (txt) { this->sdmap.insert_txt(std::move(rr.name), std::move(*txt)); }
break;
}
}
}
};
struct BonjourRequest
{
static const asio::ip::address_v4 MCAST_IP4;
static const uint16_t MCAST_PORT;
uint16_t id;
std::vector<char> data;
static optional<BonjourRequest> make(const std::string &service, const std::string &protocol);
private:
BonjourRequest(uint16_t id, std::vector<char> &&data) :
id(id),
data(std::move(data))
{}
};
const asio::ip::address_v4 BonjourRequest::MCAST_IP4{0xe00000fb};
const uint16_t BonjourRequest::MCAST_PORT = 5353;
optional<BonjourRequest> BonjourRequest::make(const std::string &service, const std::string &protocol)
{
if (service.size() > 15 || protocol.size() > 15) {
return boost::none;
}
std::random_device dev;
std::uniform_int_distribution<uint16_t> dist;
uint16_t id = dist(dev);
uint16_t id_big = endian::native_to_big(id);
const char *id_char = reinterpret_cast<char*>(&id_big);
std::vector<char> data;
data.reserve(service.size() + 18);
// Add the transaction ID
data.push_back(id_char[0]);
data.push_back(id_char[1]);
// Add metadata
static const unsigned char rq_meta[] = {
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data));
// Add PTR query name
data.push_back(service.size() + 1);
data.push_back('_');
data.insert(data.end(), service.begin(), service.end());
data.push_back(protocol.size() + 1);
data.push_back('_');
data.insert(data.end(), protocol.begin(), protocol.end());
// Add the rest of PTR record
static const unsigned char ptr_tail[] = {
0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, 0x00, 0xff,
};
std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data));
return BonjourRequest(id, std::move(data));
}
// API - private part
struct Bonjour::priv
{
const std::string service;
const std::string protocol;
const std::string service_dn;
unsigned timeout;
uint16_t rq_id;
std::vector<char> buffer;
std::thread io_thread;
Bonjour::ReplyFn replyfn;
Bonjour::CompleteFn completefn;
priv(std::string service, std::string protocol);
void udp_receive(udp::endpoint from, size_t bytes);
void lookup_perform();
};
Bonjour::priv::priv(std::string service, std::string protocol) :
service(std::move(service)),
protocol(std::move(protocol)),
service_dn((boost::format("_%1%._%2%.local") % this->service % this->protocol).str()),
timeout(10),
rq_id(0)
{
buffer.resize(DnsMessage::MAX_SIZE);
}
void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
{
if (bytes == 0 || !replyfn) {
return;
}
buffer.resize(bytes);
const auto dns_msg = DnsMessage::decode(buffer, rq_id);
if (dns_msg) {
asio::ip::address ip = from.address();
if (dns_msg->rr_a) { ip = dns_msg->rr_a->ip; }
else if (dns_msg->rr_aaaa) { ip = dns_msg->rr_aaaa->ip; }
for (const auto &sdpair : dns_msg->sdmap) {
if (! sdpair.second.srv) {
continue;
}
const auto &srv = *sdpair.second.srv;
BonjourReply reply(ip, sdpair.first, srv.hostname);
if (sdpair.second.txt) {
static const std::string tag_path = "path=";
static const std::string tag_version = "version=";
for (const auto &value : sdpair.second.txt->values) {
if (value.size() > tag_path.size() && value.compare(0, tag_path.size(), tag_path) == 0) {
reply.path = value.substr(tag_path.size());
} else if (value.size() > tag_version.size() && value.compare(0, tag_version.size(), tag_version) == 0) {
reply.version = value.substr(tag_version.size());
}
}
}
replyfn(std::move(reply));
}
}
}
void Bonjour::priv::lookup_perform()
{
const auto brq = BonjourRequest::make(service, protocol);
if (!brq) {
return;
}
auto self = this;
rq_id = brq->id;
try {
boost::asio::io_service io_service;
udp::socket socket(io_service);
socket.open(udp::v4());
socket.set_option(udp::socket::reuse_address(true));
udp::endpoint mcast(BonjourRequest::MCAST_IP4, BonjourRequest::MCAST_PORT);
socket.send_to(asio::buffer(brq->data), mcast);
bool timeout = false;
asio::deadline_timer timer(io_service);
timer.expires_from_now(boost::posix_time::seconds(10));
timer.async_wait([=, &timeout](const error_code &error) {
timeout = true;
if (self->completefn) {
self->completefn();
}
});
udp::endpoint recv_from;
const auto recv_handler = [&](const error_code &error, size_t bytes) {
if (!error) { self->udp_receive(recv_from, bytes); }
};
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
while (io_service.run_one()) {
if (timeout) {
socket.cancel();
} else {
buffer.resize(DnsMessage::MAX_SIZE);
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
}
}
} catch (std::exception& e) {
}
}
// API - public part
BonjourReply::BonjourReply(boost::asio::ip::address ip, std::string service_name, std::string hostname) :
ip(std::move(ip)),
service_name(std::move(service_name)),
hostname(std::move(hostname)),
path("/"),
version("Unknown")
{}
std::ostream& operator<<(std::ostream &os, const BonjourReply &reply)
{
os << "BonjourReply(" << reply.ip.to_string() << ", " << reply.service_name << ", "
<< reply.hostname << ", " << reply.path << ", " << reply.version << ")";
return os;
}
Bonjour::Bonjour(std::string service, std::string protocol) :
p(new priv(std::move(service), std::move(protocol)))
{}
Bonjour::Bonjour(Bonjour &&other) : p(std::move(other.p)) {}
Bonjour::~Bonjour()
{
if (p && p->io_thread.joinable()) {
p->io_thread.detach();
}
}
Bonjour& Bonjour::set_timeout(unsigned timeout)
{
if (p) { p->timeout = timeout; }
return *this;
}
Bonjour& Bonjour::on_reply(ReplyFn fn)
{
if (p) { p->replyfn = std::move(fn); }
return *this;
}
Bonjour& Bonjour::on_complete(CompleteFn fn)
{
if (p) { p->completefn = std::move(fn); }
return *this;
}
Bonjour::Ptr Bonjour::lookup()
{
auto self = std::make_shared<Bonjour>(std::move(*this));
if (self->p) {
auto io_thread = std::thread([self](){
self->p->lookup_perform();
});
self->p->io_thread = std::move(io_thread);
}
return self;
}
void Bonjour::pokus() // XXX
{
auto bonjour = Bonjour("octoprint")
.set_timeout(15)
.on_reply([](BonjourReply &&reply) {
std::cerr << "BonjourReply: " << reply << std::endl;
})
.on_complete([](){
std::cerr << "MDNS lookup complete" << std::endl;
})
.lookup();
}
}

View file

@ -0,0 +1,56 @@
#ifndef slic3r_Bonjour_hpp_
#define slic3r_Bonjour_hpp_
#include <memory>
#include <string>
#include <functional>
// #include <ostream>
#include <boost/asio/ip/address.hpp>
namespace Slic3r {
// TODO: reply data structure
struct BonjourReply
{
boost::asio::ip::address ip;
std::string service_name;
std::string hostname;
std::string path;
std::string version;
BonjourReply(boost::asio::ip::address ip, std::string service_name, std::string hostname);
};
std::ostream& operator<<(std::ostream &, const BonjourReply &);
/// Bonjour lookup performer
class Bonjour : public std::enable_shared_from_this<Bonjour> {
private:
struct priv;
public:
typedef std::shared_ptr<Bonjour> Ptr;
typedef std::function<void(BonjourReply &&reply)> ReplyFn;
typedef std::function<void()> CompleteFn;
Bonjour(std::string service, std::string protocol = "tcp");
Bonjour(Bonjour &&other);
~Bonjour();
Bonjour& set_timeout(unsigned timeout);
Bonjour& on_reply(ReplyFn fn);
Bonjour& on_complete(CompleteFn fn);
Ptr lookup();
static void pokus(); // XXX: remove
private:
std::unique_ptr<priv> p;
};
}
#endif

View file

@ -0,0 +1,261 @@
#include "Http.hpp"
#include <cstdlib>
#include <functional>
#include <thread>
#include <iostream>
#include <tuple>
#include <boost/format.hpp>
#include <curl/curl.h>
#include "../../libslic3r/libslic3r.h"
namespace Slic3r {
// Private
class CurlGlobalInit
{
static const CurlGlobalInit instance;
CurlGlobalInit() { ::curl_global_init(CURL_GLOBAL_DEFAULT); }
~CurlGlobalInit() { ::curl_global_cleanup(); }
};
struct Http::priv
{
enum {
DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024,
};
::CURL *curl;
::curl_httppost *form;
::curl_httppost *form_end;
::curl_slist *headerlist;
std::string buffer;
size_t limit;
std::thread io_thread;
Http::CompleteFn completefn;
Http::ErrorFn errorfn;
priv(const std::string &url);
~priv();
static size_t writecb(void *data, size_t size, size_t nmemb, void *userp);
std::string body_size_error();
void http_perform();
};
Http::priv::priv(const std::string &url) :
curl(::curl_easy_init()),
form(nullptr),
form_end(nullptr),
headerlist(nullptr)
{
if (curl == nullptr) {
throw std::runtime_error(std::string("Could not construct Curl object"));
}
::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally
::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_FORK_NAME "/" SLIC3R_VERSION);
}
Http::priv::~priv()
{
::curl_easy_cleanup(curl);
::curl_formfree(form);
::curl_slist_free_all(headerlist);
}
size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
{
auto self = static_cast<priv*>(userp);
const char *cdata = static_cast<char*>(data);
const size_t realsize = size * nmemb;
const size_t limit = self->limit > 0 ? self->limit : DEFAULT_SIZE_LIMIT;
if (self->buffer.size() + realsize > limit) {
// This makes curl_easy_perform return CURLE_WRITE_ERROR
return 0;
}
self->buffer.append(cdata, realsize);
return realsize;
}
std::string Http::priv::body_size_error()
{
return (boost::format("HTTP body data size exceeded limit (%1% bytes)") % limit).str();
}
void Http::priv::http_perform()
{
::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb);
::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(this));
#ifndef NDEBUG
::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
#endif
if (headerlist != nullptr) {
::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
}
if (form != nullptr) {
::curl_easy_setopt(curl, CURLOPT_HTTPPOST, form);
}
CURLcode res = ::curl_easy_perform(curl);
long http_status = 0;
::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status);
if (res != CURLE_OK) {
std::string error;
if (res == CURLE_WRITE_ERROR) {
error = std::move(body_size_error());
} else {
error = ::curl_easy_strerror(res);
};
if (errorfn) {
errorfn(std::move(buffer), std::move(error), http_status);
}
} else {
if (completefn) {
completefn(std::move(buffer), http_status);
}
}
}
Http::Http(const std::string &url) : p(new priv(url)) {}
// Public
Http::Http(Http &&other) : p(std::move(other.p)) {}
Http::~Http()
{
if (p && p->io_thread.joinable()) {
p->io_thread.detach();
}
}
Http& Http::size_limit(size_t sizeLimit)
{
if (p) { p->limit = sizeLimit; }
return *this;
}
Http& Http::header(std::string name, const std::string &value)
{
if (!p) { return * this; }
if (name.size() > 0) {
name.append(": ").append(value);
} else {
name.push_back(':');
}
p->headerlist = curl_slist_append(p->headerlist, name.c_str());
return *this;
}
Http& Http::remove_header(std::string name)
{
if (p) {
name.push_back(':');
p->headerlist = curl_slist_append(p->headerlist, name.c_str());
}
return *this;
}
Http& Http::ca_file(const std::string &name)
{
if (p) {
::curl_easy_setopt(p->curl, CURLOPT_CAINFO, name.c_str());
}
return *this;
}
Http& Http::form_add(const std::string &name, const std::string &contents)
{
if (p) {
::curl_formadd(&p->form, &p->form_end,
CURLFORM_COPYNAME, name.c_str(),
CURLFORM_COPYCONTENTS, contents.c_str(),
CURLFORM_END
);
}
return *this;
}
Http& Http::form_add_file(const std::string &name, const std::string &filename)
{
if (p) {
::curl_formadd(&p->form, &p->form_end,
CURLFORM_COPYNAME, name.c_str(),
CURLFORM_FILE, filename.c_str(),
CURLFORM_CONTENTTYPE, "application/octet-stream",
CURLFORM_END
);
}
return *this;
}
Http& Http::on_complete(CompleteFn fn)
{
if (p) { p->completefn = std::move(fn); }
return *this;
}
Http& Http::on_error(ErrorFn fn)
{
if (p) { p->errorfn = std::move(fn); }
return *this;
}
Http::Ptr Http::perform()
{
auto self = std::make_shared<Http>(std::move(*this));
if (self->p) {
auto io_thread = std::thread([self](){
self->p->http_perform();
});
self->p->io_thread = std::move(io_thread);
}
return self;
}
void Http::perform_sync()
{
if (p) { p->http_perform(); }
}
Http Http::get(std::string url)
{
return std::move(Http{std::move(url)});
}
Http Http::post(std::string url)
{
Http http{std::move(url)};
curl_easy_setopt(http.p->curl, CURLOPT_POST, 1L);
return http;
}
}

View file

@ -0,0 +1,53 @@
#ifndef slic3r_Http_hpp_
#define slic3r_Http_hpp_
#include <memory>
#include <string>
#include <functional>
namespace Slic3r {
/// Represetns a Http request
class Http : public std::enable_shared_from_this<Http> {
private:
struct priv;
public:
typedef std::shared_ptr<Http> Ptr;
typedef std::function<void(std::string /* body */, unsigned /* http_status */)> CompleteFn;
typedef std::function<void(std::string /* body */, std::string /* error */, unsigned /* http_status */)> ErrorFn;
Http(Http &&other);
static Http get(std::string url);
static Http post(std::string url);
~Http();
Http(const Http &) = delete;
Http& operator=(const Http &) = delete;
Http& operator=(Http &&) = delete;
Http& size_limit(size_t sizeLimit);
Http& header(std::string name, const std::string &value);
Http& remove_header(std::string name);
Http& ca_file(const std::string &filename);
Http& form_add(const std::string &name, const std::string &contents);
Http& form_add_file(const std::string &name, const std::string &filename);
Http& on_complete(CompleteFn fn);
Http& on_error(ErrorFn fn);
Ptr perform();
void perform_sync();
private:
Http(const std::string &url);
std::unique_ptr<priv> p;
};
}
#endif

View file

@ -0,0 +1,105 @@
#include "OctoPrint.hpp"
#include <iostream>
#include <boost/format.hpp>
#include <wx/frame.h>
#include <wx/event.h>
#include "libslic3r/PrintConfig.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "Http.hpp"
namespace Slic3r {
OctoPrint::OctoPrint(DynamicPrintConfig *config) :
host(config->opt_string("octoprint_host")),
apikey(config->opt_string("octoprint_apikey")),
cafile(config->opt_string("octoprint_cafile"))
{}
std::string OctoPrint::test() const
{
// Since the request is performed synchronously here,
// it is ok to refer to `res` from within the closure
std::string res;
auto http = Http::get(std::move(make_url("api/version")));
set_auth(http);
http.on_error([&](std::string, std::string error, unsigned status) {
res = format_error(error, status);
})
.perform_sync();
return res;
}
void OctoPrint::send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print) const
{
auto http = Http::post(std::move(make_url("api/files/local")));
set_auth(http);
http.form_add("print", print ? "true" : "false")
.form_add_file("file", filename)
.on_complete([=](std::string body, unsigned status) {
wxWindow *window = GUI::get_widget_by_id(windowId);
wxCommandEvent* evt = new wxCommandEvent(completeEvt);
evt->SetString("G-code file successfully uploaded to the OctoPrint server");
evt->SetInt(100);
wxQueueEvent(window, evt);
})
.on_error([=](std::string body, std::string error, unsigned status) {
wxWindow *window = GUI::get_widget_by_id(windowId);
wxCommandEvent* evt_complete = new wxCommandEvent(completeEvt);
evt_complete->SetInt(100);
wxQueueEvent(window, evt_complete);
wxCommandEvent* evt_error = new wxCommandEvent(errorEvt);
evt_error->SetString(wxString::Format("Error while uploading to the OctoPrint server: %s", format_error(error, status)));
wxQueueEvent(window, evt_error);
})
.perform();
}
void OctoPrint::set_auth(Http &http) const
{
http.header("X-Api-Key", apikey);
if (! cafile.empty()) {
http.ca_file(cafile);
}
}
std::string OctoPrint::make_url(const std::string &path) const
{
if (host.find("http://") == 0 || host.find("https://") == 0) {
if (host.back() == '/') {
return std::move((boost::format("%1%%2%") % host % path).str());
} else {
return std::move((boost::format("%1%/%2%") % host % path).str());
}
} else {
return std::move((boost::format("http://%1%/%2%") % host % path).str());
}
}
std::string OctoPrint::format_error(std::string error, unsigned status)
{
if (status != 0) {
std::string res{"HTTP "};
res.append(std::to_string(status));
if (status == 401) {
res.append(": Invalid API key");
}
return std::move(res);
} else {
return std::move(error);
}
}
}

View file

@ -0,0 +1,35 @@
#ifndef slic3r_OctoPrint_hpp_
#define slic3r_OctoPrint_hpp_
#include <string>
// #include "Http.hpp" // XXX: ?
namespace Slic3r {
class DynamicPrintConfig;
class Http;
class OctoPrint
{
public:
OctoPrint(DynamicPrintConfig *config);
std::string test() const;
// XXX: style
void send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print = false) const;
private:
std::string host;
std::string apikey;
std::string cafile;
void set_auth(Http &http) const;
std::string make_url(const std::string &path) const;
static std::string format_error(std::string error, unsigned status);
};
}
#endif

View file

@ -59,6 +59,15 @@ extern "C" {
#undef seek #undef seek
#undef send #undef send
#undef write #undef write
#undef open
#undef close
#undef seekdir
#undef setbuf
#undef fread
#undef fseek
#undef fputc
#undef fwrite
#undef fclose
#endif /* _MSC_VER */ #endif /* _MSC_VER */
} }
#endif #endif

View file

@ -35,11 +35,11 @@ void set_tab_panel(SV *ui)
void add_debug_menu(SV *ui, int event_language_change) void add_debug_menu(SV *ui, int event_language_change)
%code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_language_change); %}; %code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_language_change); %};
void create_preset_tabs(PresetBundle *preset_bundle, AppConfig *app_config, void create_preset_tabs(PresetBundle *preset_bundle, bool no_controller,
bool no_controller, bool is_disabled_button_browse, bool is_user_agent, bool is_disabled_button_browse, bool is_user_agent,
int event_value_change, int event_presets_changed, int event_value_change, int event_presets_changed,
int event_button_browse, int event_button_test) int event_button_browse, int event_button_test)
%code%{ Slic3r::GUI::create_preset_tabs(preset_bundle, app_config, no_controller, %code%{ Slic3r::GUI::create_preset_tabs(preset_bundle, no_controller,
is_disabled_button_browse, is_user_agent, is_disabled_button_browse, is_user_agent,
event_value_change, event_presets_changed, event_value_change, event_presets_changed,
event_button_browse, event_button_test); %}; event_button_browse, event_button_test); %};
@ -55,3 +55,9 @@ void create_combochecklist(SV *ui, std::string text, std::string items, bool ini
int combochecklist_get_flags(SV *ui) int combochecklist_get_flags(SV *ui)
%code%{ RETVAL=Slic3r::GUI::combochecklist_get_flags((wxComboCtrl*)wxPli_sv_2_object(aTHX_ ui, "Wx::ComboCtrl")); %}; %code%{ RETVAL=Slic3r::GUI::combochecklist_get_flags((wxComboCtrl*)wxPli_sv_2_object(aTHX_ ui, "Wx::ComboCtrl")); %};
void set_app_config(AppConfig *app_config)
%code%{ Slic3r::GUI::set_app_config(app_config); %};
void open_preferences_dialog(int preferences_event)
%code%{ Slic3r::GUI::open_preferences_dialog(preferences_event); %};

View file

@ -85,7 +85,11 @@
int count() int count()
%code{% RETVAL = THIS->volumes.size(); %}; %code{% RETVAL = THIS->volumes.size(); %};
std::vector<double> get_current_print_zs()
%code{% RETVAL = THIS->get_current_print_zs(); %};
void set_range(double low, double high); void set_range(double low, double high);
void render_VBOs() const; void render_VBOs() const;

View file

@ -0,0 +1,14 @@
%module{Slic3r::XS};
%{
#include <xsinit.h>
#include "slic3r/Utils/OctoPrint.hpp"
%}
%name{Slic3r::OctoPrint} class OctoPrint {
OctoPrint(DynamicPrintConfig *config);
~OctoPrint();
std::string test() const;
void send_gcode(int windowId, int completeEvt, int errorEvt, std::string filename, bool print = false) const;
};

View file

@ -236,6 +236,10 @@ Ref<PresetHints> O_OBJECT_SLIC3R_T
TabIface* O_OBJECT_SLIC3R TabIface* O_OBJECT_SLIC3R
Ref<TabIface> O_OBJECT_SLIC3R_T Ref<TabIface> O_OBJECT_SLIC3R_T
OctoPrint* O_OBJECT_SLIC3R
Ref<OctoPrint> O_OBJECT_SLIC3R_T
Clone<OctoPrint> O_OBJECT_SLIC3R_T
Axis T_UV Axis T_UV
ExtrusionLoopRole T_UV ExtrusionLoopRole T_UV
ExtrusionRole T_UV ExtrusionRole T_UV