diff --git a/CMakeLists.txt b/CMakeLists.txt
index 43d7dee70..b5f7cdec2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -42,6 +42,21 @@ message("PATH: $ENV{PATH}")
message("PERL5LIB: $ENV{PERL5LIB}")
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)
enable_testing ()
diff --git a/cmake/modules/FindCURL.cmake b/cmake/modules/FindCURL.cmake
new file mode 100644
index 000000000..b8724858c
--- /dev/null
+++ b/cmake/modules/FindCURL.cmake
@@ -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()
diff --git a/cmake/msvc/slic3r.wperl64d.props b/cmake/msvc/slic3r.wperl64d.props
new file mode 100644
index 000000000..68dac2085
--- /dev/null
+++ b/cmake/msvc/slic3r.wperl64d.props
@@ -0,0 +1,9 @@
+
+
+
+ C:\wperl64d\bin\perl.exe
+ slic3r.pl
+ WindowsLocalDebugger
+ ..\..
+
+
diff --git a/cmake/msvc/xs.wperl64d.props b/cmake/msvc/xs.wperl64d.props
new file mode 100644
index 000000000..101923581
--- /dev/null
+++ b/cmake/msvc/xs.wperl64d.props
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+ $(VC_ExecutablePath_x64);$(WindowsSDK_ExecutablePath);$(VS_ExecutablePath);$(MSBuild_ExecutablePath);$(FxCopDir);$(PATH);c:\wperl64d\bin\;
+
+
+
+
diff --git a/doc/How to build - UNIX.md b/doc/How to build - UNIX.md
new file mode 100644
index 000000000..77ce54419
--- /dev/null
+++ b/doc/How to build - UNIX.md
@@ -0,0 +1,2 @@
+# Building Slic3r PE on Linux/UNIX
+
diff --git a/doc/How to build - Windows.md b/doc/How to build - Windows.md
new file mode 100644
index 000000000..8209954bd
--- /dev/null
+++ b/doc/How to build - Windows.md
@@ -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
diff --git a/doc/Localization_guide.md b/doc/Localization_guide.md
new file mode 100644
index 000000000..33fcc1c08
--- /dev/null
+++ b/doc/Localization_guide.md
@@ -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.)
\ No newline at end of file
diff --git a/doc/Localization_manual.txt b/doc/Localization_manual.txt
deleted file mode 100644
index f31d2c7f6..000000000
--- a/doc/Localization_manual.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-From the begining you need to have GNUgettext and PoEdit.
-GNUgettext package contains a set of tools to extract strings from the source code and
-to create the Catalog to translation.
-PoEdit provide good interface for the translators.
-
-Those are possible to download here:
- GNUgettext - https://sourceforge.net/directory/os:windows/?q=gnu+gettext
- PoEdit - https://poedit.net/
-
-When GNUgettext and poEdit are downloaded and installationed, next step is
-to add path to gettext/bin directory to your PATH variable.
-You can use gettext from cmdline now.
-
-
-In Slic3rPE we have one macro (L) used to markup strings to localizations.
-
-So, to create Catalog to translation there are next steps:
- 1. create list of files with this macro (list.txt)
-
- 2. create template file(*.POT) with 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.1 if you start to create PO-file for your projest just open this POT-file in PoEdit.
- When you select translation language after first opening of POT-files,
- PO-file will be created immediatly.
-
- 3.2 if you already have PO-file created before, you have to merge old PO-file with
- strings from creaded POT-file. You can do that with 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.
-
- 3.3 if you already have PO-file created before and new PO-file created from new sourse files
- which is not related with first one, you have to concatenate old PO-file with
- strings from new PO-file. You can do that with command:
- msgcat -o new.po old.po
-
- 4. 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.)
\ No newline at end of file
diff --git a/doc/deps-build/unix-static/Makefile b/doc/deps-build/unix-static/Makefile
new file mode 100644
index 000000000..e7588b994
--- /dev/null
+++ b/doc/deps-build/unix-static/Makefile
@@ -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
diff --git a/doc/deps-build/windows/slic3r-makedeps.ps1 b/doc/deps-build/windows/slic3r-makedeps.ps1
new file mode 100644
index 000000000..8b39cae30
--- /dev/null
+++ b/doc/deps-build/windows/slic3r-makedeps.ps1
@@ -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
+}
diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm
index 572bdac32..442d0abc9 100644
--- a/lib/Slic3r/GUI/MainFrame.pm
+++ b/lib/Slic3r/GUI/MainFrame.pm
@@ -174,7 +174,6 @@ sub _init_tabpanel {
EVT_COMMAND($self, -1, $BUTTON_BROWSE_EVENT, sub {
my ($self, $event) = @_;
my $msg = $event->GetString;
- print "BUTTON_BROWSE_EVENT: ", $msg, "\n";
# look for devices
my $entries;
@@ -197,7 +196,6 @@ sub _init_tabpanel {
EVT_COMMAND($self, -1, $BUTTON_TEST_EVENT, sub {
my ($self, $event) = @_;
my $msg = $event->GetString;
- print "BUTTON_TEST_EVENT: ", $msg, "\n";
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
@@ -409,7 +407,7 @@ sub _init_menubar {
wxTheApp->about;
});
}
-
+
# menubar
# assign menubar to frame after appending items, otherwise special items
# will not be handled correctly
@@ -424,6 +422,7 @@ sub _init_menubar {
# (Select application language from the list of installed languages)
Slic3r::GUI::add_debug_menu($menubar, $self->{lang_ch_event});
$menubar->Append($helpMenu, L("&Help"));
+ # Add an optional debug menu. In production code, the add_debug_menu() call should do nothing.
$self->SetMenuBar($menubar);
}
}
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 5cedfbb08..d48e31462 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -52,7 +52,7 @@ sub new {
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
$self->{config} = Slic3r::Config::new_from_defaults_keys([qw(
bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height
- serial_port serial_speed octoprint_host octoprint_apikey
+ serial_port serial_speed octoprint_host octoprint_apikey octoprint_cafile
nozzle_diameter single_extruder_multi_material
wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe extruder_colour filament_colour
)]);
@@ -1458,8 +1458,13 @@ sub on_export_completed {
wxTheApp->notify($message);
$self->do_print if $do_print;
+
# 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->{send_gcode_file} = undef;
$self->{"print_info_cost"}->SetLabel(sprintf("%.2f" , $self->{print}->total_cost));
@@ -1488,45 +1493,6 @@ sub do_print {
my $filament_names = wxTheApp->{preset_bundle}->filament_presets;
$filament_stats = { map { $filament_names->[$_] => $filament_stats->{$_} } keys %$filament_stats };
$printer_panel->load_print_job($self->{print_file}, $filament_stats);
-
- $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(L("G-code file successfully uploaded to the OctoPrint server"));
- } else {
- my $message = L("Error while uploading to the OctoPrint server: ") . $res->status_line;
- Slic3r::GUI::show_error($self, $message);
- $self->statusbar->SetStatusText($message);
- }
}
sub export_stl {
diff --git a/resources/localization/list.txt b/resources/localization/list.txt
index 178ac0221..63919ec35 100644
--- a/resources/localization/list.txt
+++ b/resources/localization/list.txt
@@ -15,4 +15,4 @@ 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
\ No newline at end of file
+c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index caabeab6b..590b5d24f 100644
--- a/xs/CMakeLists.txt
+++ b/xs/CMakeLists.txt
@@ -199,6 +199,12 @@ add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/GUI/2DBed.hpp
${LIBDIR}/slic3r/GUI/wxExtensions.cpp
${LIBDIR}/slic3r/GUI/wxExtensions.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
@@ -338,6 +344,7 @@ set(XS_XSP_FILES
${XSP_DIR}/Surface.xsp
${XSP_DIR}/SurfaceCollection.xsp
${XSP_DIR}/TriangleMesh.xsp
+ ${XSP_DIR}/Utils_OctoPrint.xsp
${XSP_DIR}/XS.xsp
)
foreach (file ${XS_XSP_FILES})
@@ -475,6 +482,7 @@ if(SLIC3R_STATIC)
# Use boost libraries linked statically to the C++ runtime.
# set(Boost_USE_STATIC_RUNTIME ON)
endif()
+#set(Boost_DEBUG ON)
find_package(Boost REQUIRED COMPONENTS system filesystem thread log locale regex)
if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})
@@ -522,6 +530,27 @@ if (SLIC3R_PRUSACONTROL)
target_link_libraries(XS ${wxWidgets_LIBRARIES})
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
# Find eigen3 or use bundled version
@@ -596,6 +625,17 @@ elseif (NOT MSVC)
target_link_libraries(slic3r -lstdc++)
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
-install(TARGETS XS DESTINATION lib/slic3r-prusa3d/auto/Slic3r/XS)
-install(FILES lib/Slic3r/XS.pm DESTINATION lib/slic3r-prusa3d/Slic3r)
+install(TARGETS XS DESTINATION ${PERL_VENDORARCH}/auto/Slic3r/XS)
+install(FILES lib/Slic3r/XS.pm DESTINATION ${PERL_VENDORLIB}/Slic3r)
diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp
index 28c03ba2c..8ad479532 100644
--- a/xs/src/libslic3r/GCode.cpp
+++ b/xs/src/libslic3r/GCode.cpp
@@ -1402,18 +1402,13 @@ void GCode::apply_print_config(const PrintConfig &print_config)
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 };
for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++i) {
const StaticPrintConfig *cfg = configs[i];
for (const std::string &key : cfg->keys())
{
if (key != "compatible_printers")
- {
- sprintf(buff, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str());
- str += buff;
- }
+ str += "; " + key + " = " + cfg->serialize(key) + "\n";
}
}
}
diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
index 4077668f8..2b95ffa84 100644
--- a/xs/src/libslic3r/PrintConfig.cpp
+++ b/xs/src/libslic3r/PrintConfig.cpp
@@ -904,10 +904,17 @@ PrintConfigDef::PrintConfigDef()
def->cli = "octoprint-apikey=s";
def->default_value = new ConfigOptionString("");
+ def = this->add("octoprint_cafile", coString);
+ def->label = "HTTPS CA file";
+ def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
+ "If left blank, the default OS CA certificate repository is used.";
+ def->cli = "octoprint-cafile=s";
+ def->default_value = new ConfigOptionString("");
+
def = this->add("octoprint_host", coString);
- def->label = L("Host or IP");
+ def->label = L("Hostname, IP or URL");
def->tooltip = L("Slic3r can upload G-code files to OctoPrint. This field should contain "
- "the hostname or IP address of the OctoPrint instance.");
+ "the hostname, IP address or URL of the OctoPrint instance.");
def->cli = "octoprint-host=s";
def->default_value = new ConfigOptionString("");
diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp
index 7d2fb0145..c589d917a 100644
--- a/xs/src/libslic3r/PrintConfig.hpp
+++ b/xs/src/libslic3r/PrintConfig.hpp
@@ -684,6 +684,7 @@ class HostConfig : public StaticPrintConfig
public:
ConfigOptionString octoprint_host;
ConfigOptionString octoprint_apikey;
+ ConfigOptionString octoprint_cafile;
ConfigOptionString serial_port;
ConfigOptionInt serial_speed;
@@ -692,6 +693,7 @@ protected:
{
OPT_PTR(octoprint_host);
OPT_PTR(octoprint_apikey);
+ OPT_PTR(octoprint_cafile);
OPT_PTR(serial_port);
OPT_PTR(serial_speed);
}
diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp
index 1cdfb091d..ffbe507ef 100644
--- a/xs/src/libslic3r/TriangleMesh.cpp
+++ b/xs/src/libslic3r/TriangleMesh.cpp
@@ -1186,40 +1186,46 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic
loops correctly in some edge cases when original model had overlapping facets
*/
- std::vector area;
- std::vector sorted_area; // vector of indices
- 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]); });
+ /* The following lines are commented out because they can generate wrong polygons,
+ see for example issue #661 */
- // we don't perform a safety offset now because it might reverse cw loops
- Polygons p_slices;
- for (std::vector::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++ loop_idx) {
- /* we rely on the already computed area to determine the winding order
- 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);
- }
+ //std::vector area;
+ //std::vector sorted_area; // vector of indices
+ //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
+ //Polygons p_slices;
+ //for (std::vector::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++ loop_idx) {
+ // /* we rely on the already computed area to determine the winding order
+ // 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)
double safety_offset = scale_(0.0499);
//FIXME see https://github.com/prusa3d/Slic3r/issues/520
// double safety_offset = scale_(0.0001);
- ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset);
+
+ /* 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
size_t holes_count = 0;
@@ -1230,7 +1236,10 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic
#endif
// 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 &lines, ExPolygons* slices) const
diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp
index cb2ef7368..d7c9a590a 100644
--- a/xs/src/perlglue.cpp
+++ b/xs/src/perlglue.cpp
@@ -64,6 +64,7 @@ REGISTER_CLASS(PresetCollection, "GUI::PresetCollection");
REGISTER_CLASS(PresetBundle, "GUI::PresetBundle");
REGISTER_CLASS(PresetHints, "GUI::PresetHints");
REGISTER_CLASS(TabIface, "GUI::Tab");
+REGISTER_CLASS(OctoPrint, "OctoPrint");
SV* ConfigBase__as_hash(ConfigBase* THIS)
{
diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
index 1ccf80365..92d810bab 100644
--- a/xs/src/slic3r/GUI/GUI.cpp
+++ b/xs/src/slic3r/GUI/GUI.cpp
@@ -5,9 +5,9 @@
#include
#include
#include
-
#include
#include
+#include
#if __APPLE__
#import
@@ -428,7 +428,7 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
}
else{
ConfigOptionStrings* vec_new = new ConfigOptionStrings{ boost::any_cast(value) };
- config.option(opt_key)->set_at(vec_new, opt_index, opt_index);
+ config.option(opt_key)->set_at(vec_new, opt_index, 0);
}
}
break;
@@ -437,14 +437,14 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
break;
case coBools:{
ConfigOptionBools* vec_new = new ConfigOptionBools{ boost::any_cast(value) };
- config.option(opt_key)->set_at(vec_new, opt_index, opt_index);
+ config.option(opt_key)->set_at(vec_new, opt_index, 0);
break;}
case coInt:
config.set_key_value(opt_key, new ConfigOptionInt(boost::any_cast(value)));
break;
case coInts:{
ConfigOptionInts* vec_new = new ConfigOptionInts{ boost::any_cast(value) };
- config.option(opt_key)->set_at(vec_new, opt_index, opt_index);
+ config.option(opt_key)->set_at(vec_new, opt_index, 0);
}
break;
case coEnum:{
@@ -580,4 +580,18 @@ wxString from_u8(const 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;
+}
+
} }
diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp
index b7e67af92..889d31318 100644
--- a/xs/src/slic3r/GUI/GUI.hpp
+++ b/xs/src/slic3r/GUI/GUI.hpp
@@ -6,6 +6,7 @@
#include "Config.hpp"
class wxApp;
+class wxWindow;
class wxFrame;
class wxWindow;
class wxMenuBar;
@@ -123,6 +124,8 @@ wxString L_str(const std::string &str);
// Return wxString from std::string in UTF8
wxString from_u8(const std::string &str);
+wxWindow *get_widget_by_id(int id);
+
}
}
diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp
index c28c989fb..52717e1fc 100644
--- a/xs/src/slic3r/GUI/Preset.cpp
+++ b/xs/src/slic3r/GUI/Preset.cpp
@@ -224,7 +224,7 @@ const std::vector& Preset::printer_options()
if (s_opts.empty()) {
s_opts = {
"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",
"between_objects_gcode", "printer_notes"
};
diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp
index 1995d409a..36cfc0cb1 100644
--- a/xs/src/slic3r/GUI/Tab.cpp
+++ b/xs/src/slic3r/GUI/Tab.cpp
@@ -179,12 +179,25 @@ void Tab::update_tab_ui()
m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets);
}
+template
+boost::any get_new_value(const DynamicPrintConfig &config_new, const DynamicPrintConfig &config_old, std::string opt_key, int &index)
+{
+ for (int i = 0; i < config_new.option(opt_key)->values.size(); i++)
+ if (config_new.option(opt_key)->values[i] !=
+ config_old.option(opt_key)->values[i]){
+ index = i;
+ break;
+ }
+ return config_new.option(opt_key)->values[index];
+}
+
// Load a provied DynamicConfig into the tab, modifying the active preset.
// This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view.
void Tab::load_config(DynamicPrintConfig config)
{
bool modified = 0;
boost::any value;
+ int opt_index = 0;
for(auto opt_key : m_config->diff(config)) {
switch ( config.def()->get(opt_key)->type ){
case coFloatOrPercent:
@@ -200,28 +213,26 @@ void Tab::load_config(DynamicPrintConfig config)
value = config.opt_string(opt_key);
break;
case coPercents:
- value = config.option(opt_key)->values.at(0);
+ value = get_new_value(config, *m_config, opt_key, opt_index);
break;
case coFloats:
- value = config.opt_float(opt_key, 0);
+ value = get_new_value(config, *m_config, opt_key, opt_index);
break;
case coStrings:
- if (config.option(opt_key)->values.empty())
- value = "";
- else
- value = config.opt_string(opt_key, static_cast(0));
+ value = config.option(opt_key)->values.empty() ? "" :
+ get_new_value(config, *m_config, opt_key, opt_index);
break;
case coBool:
value = config.opt_bool(opt_key);
break;
case coBools:
- value = config.opt_bool(opt_key, 0);
+ value = get_new_value(config, *m_config, opt_key, opt_index);
break;
case coInt:
value = config.opt_int(opt_key);
break;
case coInts:
- value = config.opt_int(opt_key, 0);
+ value = get_new_value(config, *m_config, opt_key, opt_index);
break;
case coEnum:{
if (opt_key.compare("external_fill_pattern") == 0 ||
@@ -242,7 +253,7 @@ void Tab::load_config(DynamicPrintConfig config)
default:
break;
}
- change_opt_value(*m_config, opt_key, value);
+ change_opt_value(*m_config, opt_key, value, opt_index);
modified = 1;
// get_field(opt_key)->m_Label->SetBackgroundColour(*get_modified_label_clr());
}
diff --git a/xs/src/slic3r/Utils/Bonjour.cpp b/xs/src/slic3r/Utils/Bonjour.cpp
new file mode 100644
index 000000000..6107e2c60
--- /dev/null
+++ b/xs/src/slic3r/Utils/Bonjour.cpp
@@ -0,0 +1,704 @@
+#include "Bonjour.hpp"
+
+#include // XXX
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+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 decode(const std::vector &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(*ptr);
+ if (len & 0xc0) {
+ // This is a recursive label
+ unsigned len_2 = static_cast(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 &buffer) {
+ DnsHeader res;
+ const uint16_t *data_16 = reinterpret_cast(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 decode(const std::vector &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(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 data;
+
+ DnsResource() :
+ type(0),
+ rclass(0),
+ ttl(0)
+ {}
+
+ static optional decode(const std::vector &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(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(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(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 &result, const DnsResource &rr)
+ {
+ if (rr.data.size() == 4) {
+ DnsRR_A res;
+ const uint32_t ip = endian::big_to_native(*reinterpret_cast(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 &result, const DnsResource &rr)
+ {
+ if (rr.data.size() == 16) {
+ DnsRR_AAAA res;
+ std::array 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 decode(const std::vector &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(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 values;
+
+ static optional 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(*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 srv;
+ optional txt;
+};
+
+struct DnsSDMap : public std::map
+{
+ 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 question;
+
+ optional rr_a;
+ optional rr_aaaa;
+ std::vector rr_srv;
+
+ DnsSDMap sdmap;
+
+ static optional decode(const std::vector &buffer, optional 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 &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 data;
+
+ static optional make(const std::string &service, const std::string &protocol);
+
+private:
+ BonjourRequest(uint16_t id, std::vector &&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::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 dist;
+ uint16_t id = dist(dev);
+ uint16_t id_big = endian::native_to_big(id);
+ const char *id_char = reinterpret_cast(&id_big);
+
+ std::vector 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 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(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();
+}
+
+
+}
diff --git a/xs/src/slic3r/Utils/Bonjour.hpp b/xs/src/slic3r/Utils/Bonjour.hpp
new file mode 100644
index 000000000..285625c04
--- /dev/null
+++ b/xs/src/slic3r/Utils/Bonjour.hpp
@@ -0,0 +1,56 @@
+#ifndef slic3r_Bonjour_hpp_
+#define slic3r_Bonjour_hpp_
+
+#include
+#include
+#include
+// #include
+#include
+
+
+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 {
+private:
+ struct priv;
+public:
+ typedef std::shared_ptr Ptr;
+ typedef std::function ReplyFn;
+ typedef std::function 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 p;
+};
+
+
+}
+
+#endif
diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp
new file mode 100644
index 000000000..45a350a59
--- /dev/null
+++ b/xs/src/slic3r/Utils/Http.cpp
@@ -0,0 +1,261 @@
+#include "Http.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#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(userp);
+ const char *cdata = static_cast(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(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(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;
+}
+
+
+}
diff --git a/xs/src/slic3r/Utils/Http.hpp b/xs/src/slic3r/Utils/Http.hpp
new file mode 100644
index 000000000..c591e17c5
--- /dev/null
+++ b/xs/src/slic3r/Utils/Http.hpp
@@ -0,0 +1,53 @@
+#ifndef slic3r_Http_hpp_
+#define slic3r_Http_hpp_
+
+#include
+#include
+#include
+
+
+namespace Slic3r {
+
+
+/// Represetns a Http request
+class Http : public std::enable_shared_from_this {
+private:
+ struct priv;
+public:
+ typedef std::shared_ptr Ptr;
+ typedef std::function CompleteFn;
+ typedef std::function 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 p;
+};
+
+
+}
+
+#endif
diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp
new file mode 100644
index 000000000..58530833b
--- /dev/null
+++ b/xs/src/slic3r/Utils/OctoPrint.cpp
@@ -0,0 +1,105 @@
+#include "OctoPrint.hpp"
+
+#include
+#include
+
+#include
+#include
+
+#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);
+ }
+}
+
+
+}
diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp
new file mode 100644
index 000000000..eca3baa63
--- /dev/null
+++ b/xs/src/slic3r/Utils/OctoPrint.hpp
@@ -0,0 +1,35 @@
+#ifndef slic3r_OctoPrint_hpp_
+#define slic3r_OctoPrint_hpp_
+
+#include
+
+// #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
diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h
index 49981b74b..96c4b74d7 100644
--- a/xs/src/xsinit.h
+++ b/xs/src/xsinit.h
@@ -59,6 +59,15 @@ extern "C" {
#undef seek
#undef send
#undef write
+ #undef open
+ #undef close
+ #undef seekdir
+ #undef setbuf
+ #undef fread
+ #undef fseek
+ #undef fputc
+ #undef fwrite
+ #undef fclose
#endif /* _MSC_VER */
}
#endif
diff --git a/xs/xsp/Utils_OctoPrint.xsp b/xs/xsp/Utils_OctoPrint.xsp
new file mode 100644
index 000000000..062af4e0c
--- /dev/null
+++ b/xs/xsp/Utils_OctoPrint.xsp
@@ -0,0 +1,14 @@
+%module{Slic3r::XS};
+
+%{
+#include
+#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;
+};
diff --git a/xs/xsp/my.map b/xs/xsp/my.map
index e54601632..87a8d8d86 100644
--- a/xs/xsp/my.map
+++ b/xs/xsp/my.map
@@ -236,6 +236,10 @@ Ref O_OBJECT_SLIC3R_T
TabIface* O_OBJECT_SLIC3R
Ref O_OBJECT_SLIC3R_T
+OctoPrint* O_OBJECT_SLIC3R
+Ref O_OBJECT_SLIC3R_T
+Clone O_OBJECT_SLIC3R_T
+
Axis T_UV
ExtrusionLoopRole T_UV
ExtrusionRole T_UV