Merge branch 'master' into mesh_repair
This commit is contained in:
commit
63a98269bb
66 changed files with 6513 additions and 15694 deletions
3
Build.PL
3
Build.PL
|
@ -40,10 +40,7 @@ if ($gui) {
|
|||
Socket 2.016
|
||||
);
|
||||
%recommends = qw(
|
||||
Growl::GNTP 0.15
|
||||
Wx::GLCanvas 0
|
||||
LWP::UserAgent 0
|
||||
Net::Bonjour 0
|
||||
);
|
||||
if ($^O eq 'MSWin32') {
|
||||
$recommends{"Win32::TieRegistry"} = 0;
|
||||
|
|
|
@ -42,12 +42,31 @@ 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()
|
||||
|
||||
add_subdirectory(xs)
|
||||
|
||||
enable_testing ()
|
||||
get_filename_component(PERL_BIN_PATH "${PERL_EXECUTABLE}" DIRECTORY)
|
||||
add_test (NAME xs COMMAND "${PERL_EXECUTABLE}" ${PERL_BIN_PATH}/prove -I ${PROJECT_SOURCE_DIR}/local-lib/lib/perl5 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/xs)
|
||||
add_test (NAME integration COMMAND "${PERL_EXECUTABLE}" ${PERL_BIN_PATH}/prove WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
||||
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.)
|
||||
# Note: For some reason this needs to be set in the top-level CMakeLists.txt
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT XS)
|
||||
set(PERL_PROVE "${PERL_BIN_PATH}/prove.bat")
|
||||
else ()
|
||||
set(PERL_PROVE "${PERL_BIN_PATH}/prove")
|
||||
endif ()
|
||||
|
||||
enable_testing ()
|
||||
add_test (NAME xs COMMAND "${PERL_EXECUTABLE}" ${PERL_PROVE} -I ${PROJECT_SOURCE_DIR}/local-lib/lib/perl5 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/xs)
|
||||
add_test (NAME integration COMMAND "${PERL_EXECUTABLE}" ${PERL_PROVE} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
||||
|
||||
install(PROGRAMS slic3r.pl DESTINATION bin RENAME slic3r-prusa3d)
|
||||
|
||||
|
|
59
cmake/modules/FindCURL.cmake
Normal file
59
cmake/modules/FindCURL.cmake
Normal 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()
|
18
cmake/msvc/xs.wperl64d.props
Normal file
18
cmake/msvc/xs.wperl64d.props
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?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 />
|
||||
|
||||
<PropertyGroup>
|
||||
<LocalDebuggerCommand>C:\wperl64d\bin\perl.exe</LocalDebuggerCommand>
|
||||
<LocalDebuggerCommandArguments>slic3r.pl</LocalDebuggerCommandArguments>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
<LocalDebuggerWorkingDirectory>..\..</LocalDebuggerWorkingDirectory>
|
||||
</PropertyGroup>
|
||||
</Project>
|
2
doc/How to build - UNIX.md
Normal file
2
doc/How to build - UNIX.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Building Slic3r PE on Linux/UNIX
|
||||
|
92
doc/How to build - Windows.md
Normal file
92
doc/How to build - Windows.md
Normal file
|
@ -0,0 +1,92 @@
|
|||
# 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
|
||||
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 and debug Slic3r PE with Visual Studio (64 bits), replace the `cmake` command with:
|
||||
|
||||
cmake .. -G "Visual Studio 12 Win64" -DCMAKE_CONFIGURATION_TYPES=RelWithDebInfo
|
||||
|
||||
For the 32-bit variant, use:
|
||||
|
||||
cmake .. -G "Visual Studio 12" -DCMAKE_CONFIGURATION_TYPES=RelWithDebInfo
|
||||
|
||||
After `cmake` has finished, go to the build directory and open the `Slic3r.sln` solution file.
|
||||
This should open Visual Studio and load 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 (Hit `F5` to build and run with debugger).
|
||||
If you want to run or debug Slic3r from within Visual Studio, make sure the `XS` project is activated.
|
||||
It should be set as the Startup project by CMake by default, but you might want to check anyway.
|
||||
There are multiple projects in the Slic3r solution, but only the `XS` project is configured with the right
|
||||
commands to run and debug Slic3r.
|
||||
|
||||
The above cmake commands generate Visual Studio project files with the `RelWithDebInfo` configuration only.
|
||||
If you also want to use the `Release` configuration, you can generate Visual Studio projects with:
|
||||
|
||||
-DCMAKE_CONFIGURATION_TYPES=Release;RelWithDebInfo
|
||||
|
||||
(The `Debug` configuration is not supported as of now.)
|
||||
|
||||
### Building with ninja
|
||||
|
||||
To use [Ninja](https://ninja-build.org/), replace the `cmake` and `nmake` commands with:
|
||||
|
||||
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||
ninja
|
135
doc/deps-build/unix-static/Makefile
Normal file
135
doc/deps-build/unix-static/Makefile
Normal file
|
@ -0,0 +1,135 @@
|
|||
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
# Note: libcurl build system seems to be a bit wonky about finding openssl (cf. #2378).
|
||||
# It seems that currently the only working option is to set a prefix in the openssl build
|
||||
# and use the `--with-ssl=...` option in libcurl.
|
||||
# Additionally, pkg-config needs to be installed and openssl libs need to NOT be installed on the build system.
|
||||
|
||||
libopenssl: $(OPENSSL).tar.gz
|
||||
tar -zxvf $(OPENSSL).tar.gz
|
||||
cd $(OPENSSL) && ./config --prefix=$(DESTDIR)/usr/local no-shared no-ssl3-method no-dynamic-engine '-Wa,--noexecstack'
|
||||
$(MAKE) -C $(OPENSSL) depend
|
||||
$(MAKE) -C $(OPENSSL) -j$(NPROC)
|
||||
$(MAKE) -C $(OPENSSL) install_sw
|
||||
|
||||
$(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
|
||||
cd $(CURL) && ./configure \
|
||||
--enable-static \
|
||||
--disable-shared \
|
||||
--with-ssl=$(DESTDIR)/usr/local \
|
||||
--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
|
141
doc/deps-build/windows/slic3r-makedeps.ps1
Normal file
141
doc/deps-build/windows/slic3r-makedeps.ps1
Normal 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
|
||||
}
|
|
@ -34,7 +34,6 @@ use Slic3r::GUI::SystemInfo;
|
|||
use Wx::Locale gettext => 'L';
|
||||
|
||||
our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1";
|
||||
our $have_LWP = eval "use LWP::UserAgent; 1";
|
||||
|
||||
use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow :filedialog :font);
|
||||
use Wx::Event qw(EVT_IDLE EVT_COMMAND EVT_MENU);
|
||||
|
@ -117,6 +116,8 @@ sub OnInit {
|
|||
}
|
||||
eval { $self->{preset_bundle}->load_selections($self->{app_config}) };
|
||||
$run_wizard = 1 if $self->{preset_bundle}->has_defauls_only;
|
||||
|
||||
Slic3r::GUI::set_preset_bundle($self->{preset_bundle});
|
||||
|
||||
# application frame
|
||||
Wx::Image::FindHandlerType(wxBITMAP_TYPE_PNG) || Wx::Image::AddHandler(Wx::PNGHandler->new);
|
||||
|
|
|
@ -68,6 +68,7 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init
|
|||
_zoom
|
||||
|
||||
_legend_enabled
|
||||
_warning_enabled
|
||||
_apply_zoom_to_volumes_filter
|
||||
|
||||
) );
|
||||
|
@ -142,6 +143,7 @@ sub new {
|
|||
$self->_sphi(45);
|
||||
$self->_zoom(1);
|
||||
$self->_legend_enabled(0);
|
||||
$self->_warning_enabled(0);
|
||||
$self->use_plain_shader(0);
|
||||
$self->_apply_zoom_to_volumes_filter(0);
|
||||
|
||||
|
@ -217,7 +219,12 @@ sub new {
|
|||
|
||||
sub set_legend_enabled {
|
||||
my ($self, $value) = @_;
|
||||
$self->_legend_enabled($value);
|
||||
$self->_legend_enabled($value);
|
||||
}
|
||||
|
||||
sub set_warning_enabled {
|
||||
my ($self, $value) = @_;
|
||||
$self->_warning_enabled($value);
|
||||
}
|
||||
|
||||
sub Destroy {
|
||||
|
@ -400,6 +407,10 @@ sub mouse_event {
|
|||
$self->Refresh;
|
||||
$self->Update;
|
||||
} else {
|
||||
# The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate $pos->x,y,
|
||||
# an converts the screen space coordinate to unscaled object space.
|
||||
my $pos3d = ($volume_idx == -1) ? undef : $self->mouse_to_3d(@$pos);
|
||||
|
||||
# Select volume in this 3D canvas.
|
||||
# Don't deselect a volume if layer editing is enabled. We want the object to stay selected
|
||||
# during the scene manipulation.
|
||||
|
@ -427,9 +438,6 @@ sub mouse_event {
|
|||
|
||||
if ($volume_idx != -1) {
|
||||
if ($e->LeftDown && $self->enable_moving) {
|
||||
# The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate $pos->x,y,
|
||||
# an converts the screen space coordinate to unscaled object space.
|
||||
my $pos3d = $self->mouse_to_3d(@$pos);
|
||||
# Only accept the initial position, if it is inside the volume bounding box.
|
||||
my $volume_bbox = $self->volumes->[$volume_idx]->transformed_bounding_box;
|
||||
$volume_bbox->offset(1.);
|
||||
|
@ -948,6 +956,9 @@ sub mulquats {
|
|||
sub mouse_to_3d {
|
||||
my ($self, $x, $y, $z) = @_;
|
||||
|
||||
return unless $self->GetContext;
|
||||
$self->SetCurrent($self->GetContext);
|
||||
|
||||
my @viewport = glGetIntegerv_p(GL_VIEWPORT); # 4 items
|
||||
my @mview = glGetDoublev_p(GL_MODELVIEW_MATRIX); # 16 items
|
||||
my @proj = glGetDoublev_p(GL_PROJECTION_MATRIX); # 16 items
|
||||
|
@ -1296,11 +1307,16 @@ sub Render {
|
|||
}
|
||||
|
||||
glEnable(GL_LIGHTING);
|
||||
|
||||
|
||||
# draw objects
|
||||
if (! $self->use_plain_shader) {
|
||||
$self->draw_volumes;
|
||||
} elsif ($self->UseVBOs) {
|
||||
if ($self->enable_picking) {
|
||||
$self->mark_volumes_for_layer_height;
|
||||
$self->volumes->set_print_box($self->bed_bounding_box->x_min, $self->bed_bounding_box->y_min, 0.0, $self->bed_bounding_box->x_max, $self->bed_bounding_box->y_max, $self->{config}->get('max_print_height'));
|
||||
$self->volumes->update_outside_state($self->{config}, 0);
|
||||
}
|
||||
$self->{plain_shader}->enable if $self->{plain_shader};
|
||||
$self->volumes->render_VBOs;
|
||||
$self->{plain_shader}->disable;
|
||||
|
@ -1327,6 +1343,9 @@ sub Render {
|
|||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
# draw warning message
|
||||
$self->draw_warning;
|
||||
|
||||
# draw gcode preview legend
|
||||
$self->draw_legend;
|
||||
|
||||
|
@ -1345,36 +1364,10 @@ sub draw_volumes {
|
|||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
my $z_cursor_relative = $self->_variable_layer_thickness_bar_mouse_cursor_z_relative;
|
||||
foreach my $volume_idx (0..$#{$self->volumes}) {
|
||||
my $volume = $self->volumes->[$volume_idx];
|
||||
|
||||
my $shader_active = 0;
|
||||
my $object_id = int($volume->select_group_id / 1000000);
|
||||
if ($self->layer_editing_enabled && ! $fakecolor && $volume->selected && $self->{layer_height_edit_shader} &&
|
||||
$volume->has_layer_height_texture && $object_id < $self->{print}->object_count) {
|
||||
# Update the height texture if the ModelObject::layer_height_texture is invalid.
|
||||
$volume->generate_layer_height_texture($self->{print}->get_object($object_id), 0);
|
||||
$self->{layer_height_edit_shader}->enable;
|
||||
$self->{layer_height_edit_shader}->set_uniform('z_to_texture_row', $volume->layer_height_texture_z_to_row_id);
|
||||
$self->{layer_height_edit_shader}->set_uniform('z_texture_row_to_normalized', 1. / $volume->layer_height_texture_height);
|
||||
$self->{layer_height_edit_shader}->set_uniform('z_cursor', $volume->bounding_box->z_max * $z_cursor_relative);
|
||||
$self->{layer_height_edit_shader}->set_uniform('z_cursor_band_width', $self->{layer_height_edit_band_width});
|
||||
glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id});
|
||||
# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LEVEL, 0);
|
||||
# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
|
||||
glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $volume->layer_height_texture_width, $volume->layer_height_texture_height,
|
||||
0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2,
|
||||
0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
# glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
# glPixelStorei(GL_UNPACK_ROW_LENGTH, $self->{layer_preview_z_texture_width});
|
||||
glTexSubImage2D_c(GL_TEXTURE_2D, 0, 0, 0, $volume->layer_height_texture_width, $volume->layer_height_texture_height,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level0);
|
||||
glTexSubImage2D_c(GL_TEXTURE_2D, 1, 0, 0, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level1);
|
||||
$shader_active = 1;
|
||||
} elsif ($fakecolor) {
|
||||
if ($fakecolor) {
|
||||
# Object picking mode. Render the object with a color encoding the object index.
|
||||
my $r = ($volume_idx & 0x000000FF) >> 0;
|
||||
my $g = ($volume_idx & 0x0000FF00) >> 8;
|
||||
|
@ -1389,11 +1382,6 @@ sub draw_volumes {
|
|||
}
|
||||
|
||||
$volume->render;
|
||||
|
||||
if ($shader_active) {
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
$self->{layer_height_edit_shader}->disable;
|
||||
}
|
||||
}
|
||||
glDisableClientState(GL_NORMAL_ARRAY);
|
||||
glDisable(GL_BLEND);
|
||||
|
@ -1408,6 +1396,22 @@ sub draw_volumes {
|
|||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
}
|
||||
|
||||
sub mark_volumes_for_layer_height {
|
||||
my ($self) = @_;
|
||||
|
||||
foreach my $volume_idx (0..$#{$self->volumes}) {
|
||||
my $volume = $self->volumes->[$volume_idx];
|
||||
my $object_id = int($volume->select_group_id / 1000000);
|
||||
if ($self->layer_editing_enabled && $volume->selected && $self->{layer_height_edit_shader} &&
|
||||
$volume->has_layer_height_texture && $object_id < $self->{print}->object_count) {
|
||||
$volume->set_layer_height_texture_data($self->{layer_preview_z_texture_id}, $self->{layer_height_edit_shader}->shader_program_id,
|
||||
$self->{print}->get_object($object_id), $self->_variable_layer_thickness_bar_mouse_cursor_z_relative, $self->{layer_height_edit_band_width});
|
||||
} else {
|
||||
$volume->reset_layer_height_texture_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub _load_image_set_texture {
|
||||
my ($self, $file_name) = @_;
|
||||
# Load a PNG with an alpha channel.
|
||||
|
@ -1599,31 +1603,65 @@ sub draw_active_object_annotations {
|
|||
sub draw_legend {
|
||||
my ($self) = @_;
|
||||
|
||||
if ($self->_legend_enabled)
|
||||
{
|
||||
# If the legend texture has not been loaded into the GPU, do it now.
|
||||
my $tex_id = Slic3r::GUI::_3DScene::finalize_legend_texture;
|
||||
if ($tex_id > 0)
|
||||
{
|
||||
my $tex_w = Slic3r::GUI::_3DScene::get_legend_texture_width;
|
||||
my $tex_h = Slic3r::GUI::_3DScene::get_legend_texture_height;
|
||||
if (($tex_w > 0) && ($tex_h > 0))
|
||||
{
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
|
||||
my ($cw, $ch) = $self->GetSizeWH;
|
||||
|
||||
my $l = (-0.5 * $cw) / $self->_zoom;
|
||||
my $t = (0.5 * $ch) / $self->_zoom;
|
||||
my $r = $l + $tex_w / $self->_zoom;
|
||||
my $b = $t - $tex_h / $self->_zoom;
|
||||
$self->_render_texture($tex_id, $l, $r, $b, $t);
|
||||
if (!$self->_legend_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
glPopMatrix();
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
# If the legend texture has not been loaded into the GPU, do it now.
|
||||
my $tex_id = Slic3r::GUI::_3DScene::finalize_legend_texture;
|
||||
if ($tex_id > 0)
|
||||
{
|
||||
my $tex_w = Slic3r::GUI::_3DScene::get_legend_texture_width;
|
||||
my $tex_h = Slic3r::GUI::_3DScene::get_legend_texture_height;
|
||||
if (($tex_w > 0) && ($tex_h > 0))
|
||||
{
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
|
||||
my ($cw, $ch) = $self->GetSizeWH;
|
||||
|
||||
my $l = (-0.5 * $cw) / $self->_zoom;
|
||||
my $t = (0.5 * $ch) / $self->_zoom;
|
||||
my $r = $l + $tex_w / $self->_zoom;
|
||||
my $b = $t - $tex_h / $self->_zoom;
|
||||
$self->_render_texture($tex_id, $l, $r, $b, $t);
|
||||
|
||||
glPopMatrix();
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub draw_warning {
|
||||
my ($self) = @_;
|
||||
|
||||
if (!$self->_warning_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
# If the warning texture has not been loaded into the GPU, do it now.
|
||||
my $tex_id = Slic3r::GUI::_3DScene::finalize_warning_texture;
|
||||
if ($tex_id > 0)
|
||||
{
|
||||
my $tex_w = Slic3r::GUI::_3DScene::get_warning_texture_width;
|
||||
my $tex_h = Slic3r::GUI::_3DScene::get_warning_texture_height;
|
||||
if (($tex_w > 0) && ($tex_h > 0))
|
||||
{
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
|
||||
my ($cw, $ch) = $self->GetSizeWH;
|
||||
|
||||
my $l = (-0.5 * $tex_w) / $self->_zoom;
|
||||
my $t = (-0.5 * $ch + $tex_h) / $self->_zoom;
|
||||
my $r = $l + $tex_w / $self->_zoom;
|
||||
my $b = $t - $tex_h / $self->_zoom;
|
||||
$self->_render_texture($tex_id, $l, $r, $b, $t);
|
||||
|
||||
glPopMatrix();
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1692,57 +1730,77 @@ sub _vertex_shader_Gouraud {
|
|||
return <<'VERTEX';
|
||||
#version 110
|
||||
|
||||
#define INTENSITY_CORRECTION 0.7
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
#define LIGHT_TOP_DIR -0.6/1.31, 0.6/1.31, 1./1.31
|
||||
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.5 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 50.
|
||||
#define LIGHT_TOP_SPECULAR (0.25 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 200.0
|
||||
|
||||
#define LIGHT_FRONT_DIR 1./1.43, 0.2/1.43, 1./1.43
|
||||
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_FRONT_SHININESS 50.
|
||||
//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
|
||||
//#define LIGHT_FRONT_SHININESS 5.0
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
varying float intensity_specular;
|
||||
varying float intensity_tainted;
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
|
||||
struct PrintBoxDetection
|
||||
{
|
||||
vec3 min;
|
||||
vec3 max;
|
||||
// xyz contains the offset, if w == 1.0 detection needs to be performed
|
||||
vec4 volume_origin;
|
||||
};
|
||||
|
||||
uniform PrintBoxDetection print_box;
|
||||
|
||||
// x = tainted, y = specular;
|
||||
varying vec2 intensity;
|
||||
|
||||
varying vec3 delta_box_min;
|
||||
varying vec3 delta_box_max;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 eye, normal, lightDir, viewVector, halfVector;
|
||||
float NdotL, NdotHV;
|
||||
vec3 eye = -normalize((gl_ModelViewMatrix * gl_Vertex).xyz);
|
||||
|
||||
eye = vec3(0., 0., 1.);
|
||||
|
||||
// First transform the normal into eye space and normalize the result.
|
||||
normal = normalize(gl_NormalMatrix * gl_Normal);
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
|
||||
|
||||
// Now normalize the light's direction. Note that according to the OpenGL specification, the light is stored in eye space.
|
||||
// Also since we're talking about a directional light, the position field is actually direction.
|
||||
lightDir = vec3(LIGHT_TOP_DIR);
|
||||
halfVector = normalize(lightDir + eye);
|
||||
vec3 halfVector = normalize(LIGHT_TOP_DIR + eye);
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
NdotL = max(dot(normal, lightDir), 0.0);
|
||||
float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
intensity_tainted = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
intensity_specular = 0.;
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
intensity.y = 0.0;
|
||||
|
||||
if (NdotL > 0.0)
|
||||
intensity_specular = LIGHT_TOP_SPECULAR * pow(max(dot(normal, halfVector), 0.0), LIGHT_TOP_SHININESS);
|
||||
intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, halfVector), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source.
|
||||
lightDir = vec3(LIGHT_FRONT_DIR);
|
||||
// halfVector = normalize(lightDir + eye);
|
||||
NdotL = max(dot(normal, lightDir), 0.0);
|
||||
intensity_tainted += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
// compute the specular term if NdotL is larger than zero
|
||||
// if (NdotL > 0.0)
|
||||
// intensity_specular += LIGHT_FRONT_SPECULAR * pow(max(dot(normal, halfVector), 0.0), LIGHT_FRONT_SHININESS);
|
||||
// compute deltas for out of print volume detection (world coordinates)
|
||||
if (print_box.volume_origin.w == 1.0)
|
||||
{
|
||||
vec3 v = gl_Vertex.xyz + print_box.volume_origin.xyz;
|
||||
delta_box_min = v - print_box.min;
|
||||
delta_box_max = v - print_box.max;
|
||||
}
|
||||
else
|
||||
{
|
||||
delta_box_min = ZERO;
|
||||
delta_box_max = ZERO;
|
||||
}
|
||||
|
||||
gl_Position = ftransform();
|
||||
}
|
||||
|
@ -1754,16 +1812,25 @@ sub _fragment_shader_Gouraud {
|
|||
return <<'FRAGMENT';
|
||||
#version 110
|
||||
|
||||
varying float intensity_specular;
|
||||
varying float intensity_tainted;
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
|
||||
// x = tainted, y = specular;
|
||||
varying vec2 intensity;
|
||||
|
||||
varying vec3 delta_box_min;
|
||||
varying vec3 delta_box_max;
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor =
|
||||
vec4(intensity_specular, intensity_specular, intensity_specular, 0.) + uniform_color * intensity_tainted;
|
||||
gl_FragColor.a = uniform_color.a;
|
||||
gl_FragColor = vec4(intensity.y, intensity.y, intensity.y, 0.0) + uniform_color * intensity.x;
|
||||
|
||||
// if the fragment is outside the print volume darken it and set it as transparent
|
||||
if (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO)))
|
||||
gl_FragColor = vec4(mix(gl_FragColor.xyz, ZERO, 0.5), 0.5 * uniform_color.a);
|
||||
else
|
||||
gl_FragColor.a = uniform_color.a;
|
||||
}
|
||||
|
||||
FRAGMENT
|
||||
|
@ -1828,6 +1895,7 @@ void main() {
|
|||
// compute the specular term into spec
|
||||
// intensity_specular += LIGHT_FRONT_SPECULAR * pow(max(dot(h,normal), 0.0), LIGHT_FRONT_SHININESS);
|
||||
}
|
||||
|
||||
gl_FragColor = max(
|
||||
vec4(intensity_specular, intensity_specular, intensity_specular, 0.) + uniform_color * intensity_tainted,
|
||||
INTENSITY_AMBIENT * uniform_color);
|
||||
|
@ -1840,61 +1908,55 @@ sub _vertex_shader_variable_layer_height {
|
|||
return <<'VERTEX';
|
||||
#version 110
|
||||
|
||||
#define LIGHT_TOP_DIR 0., 1., 0.
|
||||
#define LIGHT_TOP_DIFFUSE 0.2
|
||||
#define LIGHT_TOP_SPECULAR 0.3
|
||||
#define LIGHT_TOP_SHININESS 50.
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
#define LIGHT_FRONT_DIR 0., 0., 1.
|
||||
#define LIGHT_FRONT_DIFFUSE 0.5
|
||||
#define LIGHT_FRONT_SPECULAR 0.3
|
||||
#define LIGHT_FRONT_SHININESS 50.
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.25 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 200.0
|
||||
|
||||
#define INTENSITY_AMBIENT 0.1
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
|
||||
//#define LIGHT_FRONT_SHININESS 5.0
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
uniform float z_to_texture_row;
|
||||
varying float intensity_specular;
|
||||
varying float intensity_tainted;
|
||||
|
||||
// x = tainted, y = specular;
|
||||
varying vec2 intensity;
|
||||
|
||||
varying float object_z;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 eye, normal, lightDir, viewVector, halfVector;
|
||||
float NdotL, NdotHV;
|
||||
vec3 eye = -normalize((gl_ModelViewMatrix * gl_Vertex).xyz);
|
||||
|
||||
// eye = gl_ModelViewMatrixInverse[3].xyz;
|
||||
eye = vec3(0., 0., 1.);
|
||||
|
||||
// First transform the normal into eye space and normalize the result.
|
||||
normal = normalize(gl_NormalMatrix * gl_Normal);
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
|
||||
|
||||
// Now normalize the light's direction. Note that according to the OpenGL specification, the light is stored in eye space.
|
||||
// Also since we're talking about a directional light, the position field is actually direction.
|
||||
lightDir = vec3(LIGHT_TOP_DIR);
|
||||
halfVector = normalize(lightDir + eye);
|
||||
vec3 halfVector = normalize(LIGHT_TOP_DIR + eye);
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
NdotL = max(dot(normal, lightDir), 0.0);
|
||||
float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
intensity_tainted = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
intensity_specular = 0.;
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
intensity.y = 0.0;
|
||||
|
||||
// if (NdotL > 0.0)
|
||||
// intensity_specular = LIGHT_TOP_SPECULAR * pow(max(dot(normal, halfVector), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source.
|
||||
lightDir = vec3(LIGHT_FRONT_DIR);
|
||||
halfVector = normalize(lightDir + eye);
|
||||
NdotL = max(dot(normal, lightDir), 0.0);
|
||||
intensity_tainted += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
// compute the specular term if NdotL is larger than zero
|
||||
if (NdotL > 0.0)
|
||||
intensity_specular += LIGHT_FRONT_SPECULAR * pow(max(dot(normal, halfVector), 0.0), LIGHT_FRONT_SHININESS);
|
||||
intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, halfVector), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular)
|
||||
NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
|
||||
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
// Scaled to widths of the Z texture.
|
||||
object_z = gl_Vertex.z / gl_Vertex.w;
|
||||
object_z = gl_Vertex.z;
|
||||
|
||||
gl_Position = ftransform();
|
||||
}
|
||||
|
@ -1913,13 +1975,14 @@ uniform sampler2D z_texture;
|
|||
// Scaling from the Z texture rows coordinate to the normalized texture row coordinate.
|
||||
uniform float z_to_texture_row;
|
||||
uniform float z_texture_row_to_normalized;
|
||||
|
||||
varying float intensity_specular;
|
||||
varying float intensity_tainted;
|
||||
varying float object_z;
|
||||
uniform float z_cursor;
|
||||
uniform float z_cursor_band_width;
|
||||
|
||||
// x = tainted, y = specular;
|
||||
varying vec2 intensity;
|
||||
|
||||
varying float object_z;
|
||||
|
||||
void main()
|
||||
{
|
||||
float object_z_row = z_to_texture_row * object_z;
|
||||
|
@ -1938,15 +2001,12 @@ void main()
|
|||
float lod = clamp(0.5 * log2(max(dx_vtc*dx_vtc, dy_vtc*dy_vtc)), 0., 1.);
|
||||
// Sample the Z texture. Texture coordinates are normalized to <0, 1>.
|
||||
vec4 color =
|
||||
(1. - lod) * texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.) +
|
||||
lod * texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.);
|
||||
mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.),
|
||||
texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod);
|
||||
|
||||
// Mix the final color.
|
||||
gl_FragColor =
|
||||
vec4(intensity_specular, intensity_specular, intensity_specular, 1.) +
|
||||
(1. - z_blend) * intensity_tainted * color +
|
||||
z_blend * vec4(1., 1., 0., 0.);
|
||||
// and reset the transparency.
|
||||
gl_FragColor.a = 1.;
|
||||
vec4(intensity.y, intensity.y, intensity.y, 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend);
|
||||
}
|
||||
|
||||
FRAGMENT
|
||||
|
|
|
@ -25,10 +25,6 @@ our $last_config;
|
|||
our $VALUE_CHANGE_EVENT = Wx::NewEventType;
|
||||
# 2) To inform about a preset selection change or a "modified" status change.
|
||||
our $PRESETS_CHANGED_EVENT = Wx::NewEventType;
|
||||
# 3) To inform about a click on Browse button
|
||||
our $BUTTON_BROWSE_EVENT = Wx::NewEventType;
|
||||
# 4) To inform about a click on Test button
|
||||
our $BUTTON_TEST_EVENT = Wx::NewEventType;
|
||||
|
||||
sub new {
|
||||
my ($class, %params) = @_;
|
||||
|
@ -169,61 +165,7 @@ sub _init_tabpanel {
|
|||
}
|
||||
}
|
||||
});
|
||||
# The following event is emited by the C++ Tab implementation ,
|
||||
# when the Browse button was clicked
|
||||
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;
|
||||
{
|
||||
my $res = Net::Bonjour->new('http');
|
||||
$res->discover;
|
||||
$entries = [ $res->entries ];
|
||||
}
|
||||
if (@{$entries}) {
|
||||
my $dlg = Slic3r::GUI::BonjourBrowser->new($self, $entries);
|
||||
my $tab = Slic3r::GUI::get_preset_tab("printer");
|
||||
$tab->load_key_value('octoprint_host', $dlg->GetValue . ":" . $dlg->GetPort)
|
||||
if $dlg->ShowModal == wxID_OK;
|
||||
} else {
|
||||
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 ,
|
||||
# when the Test button was clicked
|
||||
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);
|
||||
|
||||
my $config = Slic3r::GUI::get_preset_tab("printer")->get_config;
|
||||
my $res = $ua->get(
|
||||
"http://" . $config->octoprint_host . "/api/version",
|
||||
'X-Api-Key' => $config->octoprint_apikey,
|
||||
);
|
||||
if ($res->is_success) {
|
||||
Slic3r::GUI::show_info($self, L("Connection to OctoPrint works correctly."), _L("Success!"));
|
||||
} else {
|
||||
Slic3r::GUI::show_error($self,
|
||||
L("I wasn't able to connect to OctoPrint (") . $res->status_line .
|
||||
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
|
||||
$self->{is_disabled_button_browse} = (!eval "use Net::Bonjour; 1") ? 1 : 0 ;
|
||||
# A variable to inform C++ Tab implementation about user_agent
|
||||
$self->{is_user_agent} = (eval "use LWP::UserAgent; 1") ? 1 : 0 ;
|
||||
Slic3r::GUI::create_preset_tabs(wxTheApp->{preset_bundle}, $self->{no_controller},
|
||||
$self->{is_disabled_button_browse},
|
||||
$self->{is_user_agent},
|
||||
$VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT,
|
||||
$BUTTON_BROWSE_EVENT, $BUTTON_TEST_EVENT);
|
||||
Slic3r::GUI::create_preset_tabs($self->{no_controller}, $VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT);
|
||||
$self->{options_tabs} = {};
|
||||
for my $tab_name (qw(print filament printer)) {
|
||||
$self->{options_tabs}{$tab_name} = Slic3r::GUI::get_preset_tab("$tab_name");
|
||||
|
@ -409,7 +351,7 @@ sub _init_menubar {
|
|||
wxTheApp->about;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
# menubar
|
||||
# assign menubar to frame after appending items, otherwise special items
|
||||
# will not be handled correctly
|
||||
|
@ -424,6 +366,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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ use utf8;
|
|||
use File::Basename qw(basename dirname);
|
||||
use List::Util qw(sum first max);
|
||||
use Slic3r::Geometry qw(X Y Z scale unscale deg2rad rad2deg);
|
||||
use LWP::UserAgent;
|
||||
use threads::shared qw(shared_clone);
|
||||
use Wx qw(:button :colour :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc
|
||||
:panel :sizer :toolbar :window wxTheApp :notebook :combobox wxNullBitmap);
|
||||
|
@ -52,9 +51,10 @@ 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
|
||||
max_print_height
|
||||
)]);
|
||||
# C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm
|
||||
$self->{model} = Slic3r::Model->new;
|
||||
|
@ -113,6 +113,7 @@ sub new {
|
|||
$self->{canvas3D}->set_on_decrease_objects(sub { $self->decrease() });
|
||||
$self->{canvas3D}->set_on_remove_object(sub { $self->remove() });
|
||||
$self->{canvas3D}->set_on_instances_moved($on_instances_moved);
|
||||
$self->{canvas3D}->use_plain_shader(1);
|
||||
$self->{canvas3D}->set_on_wipe_tower_moved(sub {
|
||||
my ($new_pos_3f) = @_;
|
||||
my $cfg = Slic3r::Config->new;
|
||||
|
@ -389,9 +390,12 @@ sub new {
|
|||
});
|
||||
});
|
||||
$presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
|
||||
$presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 0);
|
||||
$presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 1);
|
||||
}
|
||||
}
|
||||
|
||||
my $frequently_changed_parameters_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
Slic3r::GUI::add_frequently_changed_parameters($self, $frequently_changed_parameters_sizer, $presets);
|
||||
|
||||
my $object_info_sizer;
|
||||
{
|
||||
|
@ -473,14 +477,15 @@ sub new {
|
|||
|
||||
my $right_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets;
|
||||
$right_sizer->Add($frequently_changed_parameters_sizer, 0, wxEXPAND | wxTOP, 10) if defined $frequently_changed_parameters_sizer;
|
||||
$right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5);
|
||||
$right_sizer->Add($self->{list}, 1, wxEXPAND, 5);
|
||||
$right_sizer->Add($object_info_sizer, 0, wxEXPAND, 0);
|
||||
$right_sizer->Add($print_info_sizer, 0, wxEXPAND, 0);
|
||||
# Callback for showing / hiding the print info box.
|
||||
$self->{"print_info_box_show"} = sub {
|
||||
if ($right_sizer->IsShown(4) != $_[0]) {
|
||||
$right_sizer->Show(4, $_[0]);
|
||||
if ($right_sizer->IsShown(5) != $_[0]) {
|
||||
$right_sizer->Show(5, $_[0]);
|
||||
$self->Layout
|
||||
}
|
||||
};
|
||||
|
@ -1458,8 +1463,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 +1498,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 {
|
||||
|
@ -1771,6 +1742,8 @@ sub on_config_change {
|
|||
$update_scheduled = 1;
|
||||
my $extruder_colors = $config->get('extruder_colour');
|
||||
$self->{preview3D}->set_number_extruders(scalar(@{$extruder_colors}));
|
||||
} elsif ($opt_key eq 'max_print_height') {
|
||||
$update_scheduled = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1968,6 +1941,7 @@ sub selection_changed {
|
|||
$self->{object_info_manifold_warning_icon}->SetToolTipString($message);
|
||||
} else {
|
||||
$self->{object_info_manifold}->SetLabel(L("Yes"));
|
||||
$self->{object_info_manifold_warning_icon}->Hide;
|
||||
}
|
||||
} else {
|
||||
$self->{object_info_facets}->SetLabel($object->facets);
|
||||
|
|
|
@ -8,6 +8,8 @@ use Wx qw(:misc :pen :brush :sizer :font :cursor :keycode wxTAB_TRAVERSAL);
|
|||
use Wx::Event qw(EVT_KEY_DOWN EVT_CHAR);
|
||||
use base qw(Slic3r::GUI::3DScene Class::Accessor);
|
||||
|
||||
use Wx::Locale gettext => 'L';
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(
|
||||
on_arrange on_rotate_object_left on_rotate_object_right on_scale_object_uniformly
|
||||
on_remove_object on_increase_objects on_decrease_objects));
|
||||
|
@ -208,6 +210,19 @@ sub reload_scene {
|
|||
$self->{model}->bounding_box->z_max, $self->UseVBOs);
|
||||
}
|
||||
}
|
||||
|
||||
# checks for geometry outside the print volume to render it accordingly
|
||||
if (scalar @{$self->volumes} > 0)
|
||||
{
|
||||
if (!$self->{model}->fits_print_volume($self->{config})) {
|
||||
$self->set_warning_enabled(1);
|
||||
Slic3r::GUI::_3DScene::generate_warning_texture(L("Detected object outside print volume"));
|
||||
} else {
|
||||
$self->set_warning_enabled(0);
|
||||
$self->volumes->update_outside_state($self->{config}, 1);
|
||||
Slic3r::GUI::_3DScene::reset_warning_texture();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub update_bed_size {
|
||||
|
|
|
@ -253,19 +253,19 @@ sub new {
|
|||
|
||||
# sets colors for gcode preview extrusion roles
|
||||
my @extrusion_roles_colors = (
|
||||
'Perimeter' => 'FFA500',
|
||||
'External perimeter' => 'FFFF66',
|
||||
'Perimeter' => 'FFFF66',
|
||||
'External perimeter' => 'FFA500',
|
||||
'Overhang perimeter' => '0000FF',
|
||||
'Internal infill' => 'FF0000',
|
||||
'Solid infill' => 'CD00CD',
|
||||
'Top solid infill' => 'FF3333',
|
||||
'Internal infill' => 'B1302A',
|
||||
'Solid infill' => 'D732D7',
|
||||
'Top solid infill' => 'FF1A1A',
|
||||
'Bridge infill' => '9999FF',
|
||||
'Gap fill' => 'FFFFFF',
|
||||
'Skirt' => '7F0000',
|
||||
'Skirt' => '845321',
|
||||
'Support material' => '00FF00',
|
||||
'Support material interface' => '008000',
|
||||
'Wipe tower' => 'B3E3AB',
|
||||
'Custom' => 'FFFF00',
|
||||
'Custom' => '28CC94',
|
||||
);
|
||||
$self->gcode_preview_data->set_extrusion_paths_colors(\@extrusion_roles_colors);
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ require Exporter;
|
|||
our @ISA = qw(Exporter);
|
||||
our @EXPORT_OK = qw(_eq);
|
||||
|
||||
use IO::Scalar;
|
||||
use List::Util qw(first);
|
||||
use Slic3r::Geometry qw(epsilon X Y Z);
|
||||
|
||||
|
|
BIN
resources/icons/action_undo.png
Normal file
BIN
resources/icons/action_undo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 616 B |
BIN
resources/icons/arrow_undo.png
Normal file
BIN
resources/icons/arrow_undo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 631 B |
File diff suppressed because it is too large
Load diff
|
@ -1,18 +1,20 @@
|
|||
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
|
||||
xs/src/slic3r/GUI/BedShapeDialog.cpp
|
||||
xs/src/slic3r/GUI/BedShapeDialog.hpp
|
||||
xs/src/slic3r/GUI/GUI.cpp
|
||||
xs/src/slic3r/GUI/Tab.cpp
|
||||
xs/src/slic3r/GUI/Tab.hpp
|
||||
xs/src/slic3r/GUI/Field.cpp
|
||||
xs/src/slic3r/GUI/OptionsGroup.cpp
|
||||
xs/src/slic3r/GUI/2DBed.cpp
|
||||
xs/src/slic3r/GUI/PresetHints.cpp
|
||||
xs/src/slic3r/GUI/Preferences.hpp
|
||||
xs/src/slic3r/GUI/Preferences.cpp
|
||||
xs/src/slic3r/GUI/BonjourDialog.cpp
|
||||
xs/src/slic3r/Utils/OctoPrint.cpp
|
||||
xs/src/libslic3r/PrintConfig.cpp
|
||||
xs/src/libslic3r/GCode/PreviewData.cpp
|
||||
lib/Slic3r/GUI.pm
|
||||
lib/Slic3r/GUI/MainFrame.pm
|
||||
lib/Slic3r/GUI/Plater.pm
|
||||
lib/Slic3r/GUI/Plater/2D.pm
|
||||
lib/Slic3r/GUI/Plater/3DPreview.pm
|
||||
|
|
BIN
resources/localization/uk/Slic3rPE_.mo
Normal file
BIN
resources/localization/uk/Slic3rPE_.mo
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
1018
resources/profiles/PrusaResearch.ini
Normal file
1018
resources/profiles/PrusaResearch.ini
Normal file
File diff suppressed because it is too large
Load diff
|
@ -12,7 +12,7 @@ BEGIN {
|
|||
use File::Basename qw(basename);
|
||||
use Getopt::Long qw(:config no_auto_abbrev);
|
||||
use List::Util qw(first);
|
||||
use POSIX qw(setlocale LC_NUMERIC);
|
||||
#use POSIX qw(setlocale LC_NUMERIC);
|
||||
use Slic3r;
|
||||
use Slic3r::Geometry qw(deg2rad);
|
||||
use Time::HiRes qw(gettimeofday tv_interval);
|
||||
|
@ -112,7 +112,7 @@ if ((!@ARGV || $opt{gui}) && !$opt{no_gui} && !$opt{save} && eval "require Slic3
|
|||
$Slic3r::GUI::autosave = $opt{autosave};
|
||||
}
|
||||
$gui = Slic3r::GUI->new;
|
||||
setlocale(LC_NUMERIC, 'C');
|
||||
#setlocale(LC_NUMERIC, 'C');
|
||||
$gui->{mainframe}->load_config_file($_) for @{$opt{load}};
|
||||
$gui->{mainframe}->load_config($cli_config);
|
||||
my @input_files = @ARGV;
|
||||
|
|
1
t/svg.t
1
t/svg.t
|
@ -10,6 +10,7 @@ BEGIN {
|
|||
|
||||
use Slic3r;
|
||||
use Slic3r::Test;
|
||||
use IO::Scalar;
|
||||
|
||||
{
|
||||
my $print = Slic3r::Test::init_print('20mm_cube');
|
||||
|
|
|
@ -11,7 +11,6 @@ BEGIN {
|
|||
}
|
||||
|
||||
use Getopt::Long qw(:config no_auto_abbrev);
|
||||
use IO::All;
|
||||
use List::Util qw(max);
|
||||
use Slic3r;
|
||||
use Slic3r::Geometry qw(X Y);
|
||||
|
|
|
@ -199,6 +199,14 @@ add_library(libslic3r_gui STATIC
|
|||
${LIBDIR}/slic3r/GUI/2DBed.hpp
|
||||
${LIBDIR}/slic3r/GUI/wxExtensions.cpp
|
||||
${LIBDIR}/slic3r/GUI/wxExtensions.hpp
|
||||
${LIBDIR}/slic3r/GUI/BonjourDialog.cpp
|
||||
${LIBDIR}/slic3r/GUI/BonjourDialog.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 +346,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})
|
||||
|
@ -523,6 +532,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
|
||||
|
@ -597,6 +627,21 @@ elseif (NOT MSVC)
|
|||
target_link_libraries(slic3r -lstdc++)
|
||||
endif ()
|
||||
|
||||
if (MSVC)
|
||||
# Here we associate some additional properties with the MSVC project to enable compilation and debugging out of the box.
|
||||
set_target_properties(XS PROPERTIES VS_USER_PROPS "${PROJECT_SOURCE_DIR}/cmake/msvc/xs.wperl64d.props")
|
||||
endif()
|
||||
|
||||
# l10n
|
||||
set(L10N_DIR "${PROJECT_SOURCE_DIR}/resources/localization")
|
||||
add_custom_target(pot
|
||||
COMMAND xgettext --keyword=L --from-code=UTF-8 --debug
|
||||
-f "${L10N_DIR}/list.txt"
|
||||
-o "${L10N_DIR}/Slic3rPE.pot"
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
COMMENT "Generate pot file from strings in the source tree"
|
||||
)
|
||||
|
||||
# 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)
|
||||
|
|
|
@ -94,6 +94,14 @@ public:
|
|||
void translate(const Pointf3 &pos) { this->translate(pos.x, pos.y, pos.z); }
|
||||
void offset(coordf_t delta);
|
||||
PointClass center() const;
|
||||
|
||||
bool contains(const PointClass &point) const {
|
||||
return BoundingBoxBase<PointClass>::contains(point) && point.z >= this->min.z && point.z <= this->max.z;
|
||||
}
|
||||
|
||||
bool contains(const BoundingBox3Base<PointClass>& other) const {
|
||||
return contains(other.min) && contains(other.max);
|
||||
}
|
||||
};
|
||||
|
||||
class BoundingBox : public BoundingBoxBase<Point>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -222,7 +222,7 @@ bool Model::add_default_instances()
|
|||
}
|
||||
|
||||
// this returns the bounding box of the *transformed* instances
|
||||
BoundingBoxf3 Model::bounding_box()
|
||||
BoundingBoxf3 Model::bounding_box() const
|
||||
{
|
||||
BoundingBoxf3 bb;
|
||||
for (ModelObject *o : this->objects)
|
||||
|
@ -230,6 +230,14 @@ BoundingBoxf3 Model::bounding_box()
|
|||
return bb;
|
||||
}
|
||||
|
||||
BoundingBoxf3 Model::transformed_bounding_box() const
|
||||
{
|
||||
BoundingBoxf3 bb;
|
||||
for (const ModelObject* obj : this->objects)
|
||||
bb.merge(obj->tight_bounding_box(false));
|
||||
return bb;
|
||||
}
|
||||
|
||||
void Model::center_instances_around_point(const Pointf &point)
|
||||
{
|
||||
// BoundingBoxf3 bb = this->bounding_box();
|
||||
|
@ -409,6 +417,51 @@ void Model::convert_multipart_object()
|
|||
this->objects.push_back(object);
|
||||
}
|
||||
|
||||
void Model::adjust_min_z()
|
||||
{
|
||||
if (objects.empty())
|
||||
return;
|
||||
|
||||
if (bounding_box().min.z < 0.0)
|
||||
{
|
||||
for (ModelObject* obj : objects)
|
||||
{
|
||||
if (obj != nullptr)
|
||||
{
|
||||
coordf_t obj_min_z = obj->bounding_box().min.z;
|
||||
if (obj_min_z < 0.0)
|
||||
obj->translate(0.0, 0.0, -obj_min_z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Model::fits_print_volume(const DynamicPrintConfig* config) const
|
||||
{
|
||||
if (config == nullptr)
|
||||
return false;
|
||||
|
||||
if (objects.empty())
|
||||
return true;
|
||||
|
||||
const ConfigOptionPoints* opt = dynamic_cast<const ConfigOptionPoints*>(config->option("bed_shape"));
|
||||
if (opt == nullptr)
|
||||
return false;
|
||||
|
||||
BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values));
|
||||
BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min.x), unscale(bed_box_2D.min.y), 0.0), Pointf3(unscale(bed_box_2D.max.x), unscale(bed_box_2D.max.y), config->opt_float("max_print_height")));
|
||||
return print_volume.contains(transformed_bounding_box());
|
||||
}
|
||||
|
||||
bool Model::fits_print_volume(const FullPrintConfig &config) const
|
||||
{
|
||||
if (objects.empty())
|
||||
return true;
|
||||
BoundingBox bed_box_2D = get_extents(Polygon::new_scale(config.bed_shape.values));
|
||||
BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min.x), unscale(bed_box_2D.min.y), 0.0), Pointf3(unscale(bed_box_2D.max.x), unscale(bed_box_2D.max.y), config.max_print_height));
|
||||
return print_volume.contains(transformed_bounding_box());
|
||||
}
|
||||
|
||||
ModelObject::ModelObject(Model *model, const ModelObject &other, bool copy_volumes) :
|
||||
name(other.name),
|
||||
input_file(other.input_file),
|
||||
|
@ -539,7 +592,7 @@ void ModelObject::clear_instances()
|
|||
|
||||
// Returns the bounding box of the transformed instances.
|
||||
// This bounding box is approximate and not snug.
|
||||
BoundingBoxf3 ModelObject::bounding_box()
|
||||
const BoundingBoxf3& ModelObject::bounding_box()
|
||||
{
|
||||
if (! m_bounding_box_valid) {
|
||||
BoundingBoxf3 raw_bbox;
|
||||
|
@ -555,6 +608,54 @@ BoundingBoxf3 ModelObject::bounding_box()
|
|||
return m_bounding_box;
|
||||
}
|
||||
|
||||
BoundingBoxf3 ModelObject::tight_bounding_box(bool include_modifiers) const
|
||||
{
|
||||
BoundingBoxf3 bb;
|
||||
|
||||
for (const ModelVolume* vol : this->volumes)
|
||||
{
|
||||
if (include_modifiers || !vol->modifier)
|
||||
{
|
||||
for (const ModelInstance* inst : this->instances)
|
||||
{
|
||||
double c = cos(inst->rotation);
|
||||
double s = sin(inst->rotation);
|
||||
|
||||
for (int f = 0; f < vol->mesh.stl.stats.number_of_facets; ++f)
|
||||
{
|
||||
const stl_facet& facet = vol->mesh.stl.facet_start[f];
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
// original point
|
||||
const stl_vertex& v = facet.vertex[i];
|
||||
Pointf3 p((double)v.x, (double)v.y, (double)v.z);
|
||||
|
||||
// scale
|
||||
p.x *= inst->scaling_factor;
|
||||
p.y *= inst->scaling_factor;
|
||||
p.z *= inst->scaling_factor;
|
||||
|
||||
// rotate Z
|
||||
double x = p.x;
|
||||
double y = p.y;
|
||||
p.x = c * x - s * y;
|
||||
p.y = s * x + c * y;
|
||||
|
||||
// translate
|
||||
p.x += inst->offset.x;
|
||||
p.y += inst->offset.y;
|
||||
|
||||
bb.merge(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bb;
|
||||
}
|
||||
|
||||
// A mesh containing all transformed instances of this object.
|
||||
TriangleMesh ModelObject::mesh() const
|
||||
{
|
||||
|
@ -671,7 +772,7 @@ void ModelObject::transform(const float* matrix3x4)
|
|||
v->mesh.transform(matrix3x4);
|
||||
}
|
||||
|
||||
origin_translation = Pointf3(0.0f, 0.0f, 0.0f);
|
||||
origin_translation = Pointf3(0.0, 0.0, 0.0);
|
||||
invalidate_bounding_box();
|
||||
}
|
||||
|
||||
|
|
|
@ -102,8 +102,12 @@ public:
|
|||
|
||||
// Returns the bounding box of the transformed instances.
|
||||
// This bounding box is approximate and not snug.
|
||||
BoundingBoxf3 bounding_box();
|
||||
// This bounding box is being cached.
|
||||
const BoundingBoxf3& bounding_box();
|
||||
void invalidate_bounding_box() { m_bounding_box_valid = false; }
|
||||
// Returns a snug bounding box of the transformed instances.
|
||||
// This bounding box is not being cached.
|
||||
BoundingBoxf3 tight_bounding_box(bool include_modifiers) const;
|
||||
|
||||
// A mesh containing all transformed instances of this object.
|
||||
TriangleMesh mesh() const;
|
||||
|
@ -260,7 +264,10 @@ public:
|
|||
void delete_material(t_model_material_id material_id);
|
||||
void clear_materials();
|
||||
bool add_default_instances();
|
||||
BoundingBoxf3 bounding_box();
|
||||
// Returns approximate axis aligned bounding box of this model
|
||||
BoundingBoxf3 bounding_box() const;
|
||||
// Returns tight axis aligned bounding box of this model
|
||||
BoundingBoxf3 transformed_bounding_box() const;
|
||||
void center_instances_around_point(const Pointf &point);
|
||||
void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
|
||||
TriangleMesh mesh() const;
|
||||
|
@ -273,6 +280,13 @@ public:
|
|||
bool looks_like_multipart_object() const;
|
||||
void convert_multipart_object();
|
||||
|
||||
// Ensures that the min z of the model is not negative
|
||||
void adjust_min_z();
|
||||
|
||||
// Returs true if this model is contained into the print volume defined inside the given config
|
||||
bool fits_print_volume(const DynamicPrintConfig* config) const;
|
||||
bool fits_print_volume(const FullPrintConfig &config) const;
|
||||
|
||||
void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); }
|
||||
};
|
||||
|
||||
|
|
|
@ -120,6 +120,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|
|||
"layer_gcode",
|
||||
"min_fan_speed",
|
||||
"max_fan_speed",
|
||||
"max_print_height",
|
||||
"min_print_speed",
|
||||
"max_print_speed",
|
||||
"max_volumetric_speed",
|
||||
|
@ -507,6 +508,15 @@ bool Print::has_skirt() const
|
|||
|
||||
std::string Print::validate() const
|
||||
{
|
||||
BoundingBox bed_box_2D = get_extents(Polygon::new_scale(config.bed_shape.values));
|
||||
BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min.x), unscale(bed_box_2D.min.y), 0.0), Pointf3(unscale(bed_box_2D.max.x), unscale(bed_box_2D.max.y), config.max_print_height));
|
||||
// Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced.
|
||||
print_volume.min.z = -1e10;
|
||||
for (PrintObject *po : this->objects) {
|
||||
if (! print_volume.contains(po->model_object()->tight_bounding_box(false)))
|
||||
return "Some objects are outside of the print volume.";
|
||||
}
|
||||
|
||||
if (this->config.complete_objects) {
|
||||
// Check horizontal clearance.
|
||||
{
|
||||
|
|
|
@ -803,6 +803,13 @@ PrintConfigDef::PrintConfigDef()
|
|||
def->min = 0;
|
||||
def->default_value = new ConfigOptionFloats { 0. };
|
||||
|
||||
def = this->add("max_print_height", coFloat);
|
||||
def->label = L("Max print height");
|
||||
def->tooltip = L("Set this to the maximum height that can be reached by your extruder while printing.");
|
||||
def->sidetext = L("mm");
|
||||
def->cli = "max-print-height=f";
|
||||
def->default_value = new ConfigOptionFloat(200.0);
|
||||
|
||||
def = this->add("max_print_speed", coFloat);
|
||||
def->label = L("Max print speed");
|
||||
def->tooltip = L("When setting other speed settings to 0 Slic3r will autocalculate the optimal speed "
|
||||
|
@ -904,10 +911,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("");
|
||||
|
||||
|
@ -1513,12 +1527,10 @@ PrintConfigDef::PrintConfigDef()
|
|||
def->enum_values.push_back("rectilinear");
|
||||
def->enum_values.push_back("rectilinear-grid");
|
||||
def->enum_values.push_back("honeycomb");
|
||||
def->enum_values.push_back("pillars");
|
||||
def->enum_labels.push_back("rectilinear");
|
||||
def->enum_labels.push_back("rectilinear grid");
|
||||
def->enum_labels.push_back("honeycomb");
|
||||
def->enum_labels.push_back("pillars");
|
||||
def->default_value = new ConfigOptionEnum<SupportMaterialPattern>(smpPillars);
|
||||
def->default_value = new ConfigOptionEnum<SupportMaterialPattern>(smpRectilinear);
|
||||
|
||||
def = this->add("support_material_spacing", coFloat);
|
||||
def->label = L("Pattern spacing");
|
||||
|
@ -1790,6 +1802,9 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
|
|||
values is a dirty hack and will need to be removed sometime in the future, but it
|
||||
will avoid lots of complaints for now. */
|
||||
value = "0";
|
||||
} else if (opt_key == "support_material_pattern" && value == "pillars") {
|
||||
// Slic3r PE does not support the pillars. They never worked well.
|
||||
value = "rectilinear";
|
||||
} else if (opt_key == "support_material_threshold" && value == "0") {
|
||||
// 0 used to be automatic threshold, but we introduced percent values so let's
|
||||
// transform it into the default value
|
||||
|
|
|
@ -33,7 +33,7 @@ enum InfillPattern {
|
|||
};
|
||||
|
||||
enum SupportMaterialPattern {
|
||||
smpRectilinear, smpRectilinearGrid, smpHoneycomb, smpPillars,
|
||||
smpRectilinear, smpRectilinearGrid, smpHoneycomb,
|
||||
};
|
||||
|
||||
enum SeamPosition {
|
||||
|
@ -87,7 +87,6 @@ template<> inline t_config_enum_values& ConfigOptionEnum<SupportMaterialPattern>
|
|||
keys_map["rectilinear"] = smpRectilinear;
|
||||
keys_map["rectilinear-grid"] = smpRectilinearGrid;
|
||||
keys_map["honeycomb"] = smpHoneycomb;
|
||||
keys_map["pillars"] = smpPillars;
|
||||
}
|
||||
return keys_map;
|
||||
}
|
||||
|
@ -583,6 +582,7 @@ public:
|
|||
ConfigOptionFloats max_layer_height;
|
||||
ConfigOptionInts min_fan_speed;
|
||||
ConfigOptionFloats min_layer_height;
|
||||
ConfigOptionFloat max_print_height;
|
||||
ConfigOptionFloats min_print_speed;
|
||||
ConfigOptionFloat min_skirt_length;
|
||||
ConfigOptionString notes;
|
||||
|
@ -647,6 +647,7 @@ protected:
|
|||
OPT_PTR(max_layer_height);
|
||||
OPT_PTR(min_fan_speed);
|
||||
OPT_PTR(min_layer_height);
|
||||
OPT_PTR(max_print_height);
|
||||
OPT_PTR(min_print_speed);
|
||||
OPT_PTR(min_skirt_length);
|
||||
OPT_PTR(notes);
|
||||
|
@ -684,6 +685,7 @@ class HostConfig : public StaticPrintConfig
|
|||
public:
|
||||
ConfigOptionString octoprint_host;
|
||||
ConfigOptionString octoprint_apikey;
|
||||
ConfigOptionString octoprint_cafile;
|
||||
ConfigOptionString serial_port;
|
||||
ConfigOptionInt serial_speed;
|
||||
|
||||
|
@ -692,6 +694,7 @@ protected:
|
|||
{
|
||||
OPT_PTR(octoprint_host);
|
||||
OPT_PTR(octoprint_apikey);
|
||||
OPT_PTR(octoprint_cafile);
|
||||
OPT_PTR(serial_port);
|
||||
OPT_PTR(serial_speed);
|
||||
}
|
||||
|
|
|
@ -2491,7 +2491,6 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
infill_pattern = ipRectilinear;
|
||||
break;
|
||||
case smpHoneycomb:
|
||||
case smpPillars:
|
||||
infill_pattern = ipHoneycomb;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -46,6 +46,25 @@ void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh)
|
|||
}
|
||||
}
|
||||
|
||||
void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh)
|
||||
{
|
||||
assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0);
|
||||
assert(quad_indices.empty() && triangle_indices_size == 0);
|
||||
assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size());
|
||||
|
||||
this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count());
|
||||
|
||||
unsigned int vertices_count = 0;
|
||||
for (int i = 0; i < mesh.stl.stats.number_of_facets; ++i) {
|
||||
const stl_facet &facet = mesh.stl.facet_start[i];
|
||||
for (int j = 0; j < 3; ++j)
|
||||
this->push_geometry(facet.vertex[j].x, facet.vertex[j].y, facet.vertex[j].z, facet.normal.x, facet.normal.y, facet.normal.z);
|
||||
|
||||
this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2);
|
||||
vertices_count += 3;
|
||||
}
|
||||
}
|
||||
|
||||
void GLIndexedVertexArray::finalize_geometry(bool use_VBOs)
|
||||
{
|
||||
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
|
||||
|
@ -173,6 +192,45 @@ void GLIndexedVertexArray::render(
|
|||
glDisableClientState(GL_NORMAL_ARRAY);
|
||||
}
|
||||
|
||||
const float GLVolume::SELECTED_COLOR[4] = { 0.0f, 1.0f, 0.0f, 1.0f };
|
||||
const float GLVolume::HOVER_COLOR[4] = { 0.4f, 0.9f, 0.1f, 1.0f };
|
||||
const float GLVolume::OUTSIDE_COLOR[4] = { 0.75f, 0.0f, 0.75f, 1.0f };
|
||||
const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 1.0f, 0.0f, 1.0f, 1.0f };
|
||||
|
||||
void GLVolume::set_render_color(float r, float g, float b, float a)
|
||||
{
|
||||
render_color[0] = r;
|
||||
render_color[1] = g;
|
||||
render_color[2] = b;
|
||||
render_color[3] = a;
|
||||
}
|
||||
|
||||
void GLVolume::set_render_color(const float* rgba, unsigned int size)
|
||||
{
|
||||
size = std::min((unsigned int)4, size);
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
render_color[i] = rgba[i];
|
||||
}
|
||||
}
|
||||
|
||||
void GLVolume::set_render_color()
|
||||
{
|
||||
if (selected)
|
||||
{
|
||||
if (is_outside)
|
||||
set_render_color(SELECTED_OUTSIDE_COLOR, 4);
|
||||
else
|
||||
set_render_color(SELECTED_COLOR, 4);
|
||||
}
|
||||
else if (hover)
|
||||
set_render_color(HOVER_COLOR, 4);
|
||||
else if (is_outside)
|
||||
set_render_color(OUTSIDE_COLOR, 4);
|
||||
else
|
||||
set_render_color(color, 4);
|
||||
}
|
||||
|
||||
void GLVolume::set_range(double min_z, double max_z)
|
||||
{
|
||||
this->qverts_range.first = 0;
|
||||
|
@ -223,6 +281,51 @@ void GLVolume::render() const
|
|||
glPopMatrix();
|
||||
}
|
||||
|
||||
void GLVolume::render_using_layer_height() const
|
||||
{
|
||||
if (!is_active)
|
||||
return;
|
||||
|
||||
GLint current_program_id;
|
||||
glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id);
|
||||
|
||||
if ((layer_height_texture_data.shader_id > 0) && (layer_height_texture_data.shader_id != current_program_id))
|
||||
glUseProgram(layer_height_texture_data.shader_id);
|
||||
|
||||
GLint z_to_texture_row_id = (layer_height_texture_data.shader_id > 0) ? glGetUniformLocation(layer_height_texture_data.shader_id, "z_to_texture_row") : -1;
|
||||
GLint z_texture_row_to_normalized_id = (layer_height_texture_data.shader_id > 0) ? glGetUniformLocation(layer_height_texture_data.shader_id, "z_texture_row_to_normalized") : -1;
|
||||
GLint z_cursor_id = (layer_height_texture_data.shader_id > 0) ? glGetUniformLocation(layer_height_texture_data.shader_id, "z_cursor") : -1;
|
||||
GLint z_cursor_band_width_id = (layer_height_texture_data.shader_id > 0) ? glGetUniformLocation(layer_height_texture_data.shader_id, "z_cursor_band_width") : -1;
|
||||
|
||||
if (z_to_texture_row_id >= 0)
|
||||
glUniform1f(z_to_texture_row_id, (GLfloat)layer_height_texture_z_to_row_id());
|
||||
|
||||
if (z_texture_row_to_normalized_id >= 0)
|
||||
glUniform1f(z_texture_row_to_normalized_id, (GLfloat)(1.0f / layer_height_texture_height()));
|
||||
|
||||
if (z_cursor_id >= 0)
|
||||
glUniform1f(z_cursor_id, (GLfloat)(bounding_box.max.z * layer_height_texture_data.z_cursor_relative));
|
||||
|
||||
if (z_cursor_band_width_id >= 0)
|
||||
glUniform1f(z_cursor_band_width_id, (GLfloat)layer_height_texture_data.edit_band_width);
|
||||
|
||||
unsigned int w = layer_height_texture_width();
|
||||
unsigned int h = layer_height_texture_height();
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, layer_height_texture_data.texture_id);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, w / 2, h / 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, layer_height_texture_data_ptr_level0());
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, w / 2, h / 2, GL_RGBA, GL_UNSIGNED_BYTE, layer_height_texture_data_ptr_level1());
|
||||
|
||||
render();
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
if ((current_program_id > 0) && (layer_height_texture_data.shader_id != current_program_id))
|
||||
glUseProgram(current_program_id);
|
||||
}
|
||||
|
||||
void GLVolume::generate_layer_height_texture(PrintObject *print_object, bool force)
|
||||
{
|
||||
GLTexture *tex = this->layer_height_texture.get();
|
||||
|
@ -288,7 +391,11 @@ std::vector<int> GLVolumeCollection::load_object(
|
|||
color[3] = model_volume->modifier ? 0.5f : 1.f;
|
||||
this->volumes.emplace_back(new GLVolume(color));
|
||||
GLVolume &v = *this->volumes.back();
|
||||
v.indexed_vertex_array.load_mesh_flat_shading(mesh);
|
||||
if (use_VBOs)
|
||||
v.indexed_vertex_array.load_mesh_full_shading(mesh);
|
||||
else
|
||||
v.indexed_vertex_array.load_mesh_flat_shading(mesh);
|
||||
|
||||
// finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
|
||||
v.bounding_box = v.indexed_vertex_array.bounding_box();
|
||||
v.indexed_vertex_array.finalize_geometry(use_VBOs);
|
||||
|
@ -319,7 +426,11 @@ int GLVolumeCollection::load_wipe_tower_preview(
|
|||
this->volumes.emplace_back(new GLVolume(color));
|
||||
GLVolume &v = *this->volumes.back();
|
||||
auto mesh = make_cube(width, depth, height);
|
||||
v.indexed_vertex_array.load_mesh_flat_shading(mesh);
|
||||
if (use_VBOs)
|
||||
v.indexed_vertex_array.load_mesh_full_shading(mesh);
|
||||
else
|
||||
v.indexed_vertex_array.load_mesh_flat_shading(mesh);
|
||||
|
||||
v.origin = Pointf3(pos_x, pos_y, 0.);
|
||||
// finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
|
||||
v.bounding_box = v.indexed_vertex_array.bounding_box();
|
||||
|
@ -332,15 +443,19 @@ int GLVolumeCollection::load_wipe_tower_preview(
|
|||
|
||||
void GLVolumeCollection::render_VBOs() const
|
||||
{
|
||||
// glEnable(GL_BLEND);
|
||||
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glCullFace(GL_BACK);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
::glEnable(GL_BLEND);
|
||||
::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
::glCullFace(GL_BACK);
|
||||
::glEnableClientState(GL_VERTEX_ARRAY);
|
||||
::glEnableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
GLint current_program_id;
|
||||
glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id);
|
||||
::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id);
|
||||
GLint color_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "uniform_color") : -1;
|
||||
GLint print_box_min_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "print_box.min") : -1;
|
||||
GLint print_box_max_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "print_box.max") : -1;
|
||||
GLint print_box_origin_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "print_box.volume_origin") : -1;
|
||||
|
||||
for (GLVolume *volume : this->volumes) {
|
||||
if (!volume->is_active)
|
||||
|
@ -348,61 +463,110 @@ void GLVolumeCollection::render_VBOs() const
|
|||
|
||||
if (!volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id)
|
||||
continue;
|
||||
|
||||
if (volume->layer_height_texture_data.can_use())
|
||||
{
|
||||
::glDisableClientState(GL_VERTEX_ARRAY);
|
||||
::glDisableClientState(GL_NORMAL_ARRAY);
|
||||
volume->generate_layer_height_texture(volume->layer_height_texture_data.print_object, false);
|
||||
volume->render_using_layer_height();
|
||||
::glEnableClientState(GL_VERTEX_ARRAY);
|
||||
::glEnableClientState(GL_NORMAL_ARRAY);
|
||||
continue;
|
||||
}
|
||||
|
||||
volume->set_render_color();
|
||||
|
||||
GLsizei n_triangles = GLsizei(std::min(volume->indexed_vertex_array.triangle_indices_size, volume->tverts_range.second - volume->tverts_range.first));
|
||||
GLsizei n_quads = GLsizei(std::min(volume->indexed_vertex_array.quad_indices_size, volume->qverts_range.second - volume->qverts_range.first));
|
||||
if (n_triangles + n_quads == 0)
|
||||
{
|
||||
if (_render_interleaved_only_volumes.enabled)
|
||||
::glDisableClientState(GL_VERTEX_ARRAY);
|
||||
::glDisableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
if (color_id >= 0)
|
||||
{
|
||||
::glDisableClientState(GL_VERTEX_ARRAY);
|
||||
::glDisableClientState(GL_NORMAL_ARRAY);
|
||||
::glEnable(GL_BLEND);
|
||||
::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
if (color_id >= 0)
|
||||
{
|
||||
float color[4];
|
||||
::memcpy((void*)color, (const void*)volume->color, 3 * sizeof(float));
|
||||
color[3] = _render_interleaved_only_volumes.alpha;
|
||||
::glUniform4fv(color_id, 1, (const GLfloat*)color);
|
||||
}
|
||||
else
|
||||
::glColor4f(volume->color[0], volume->color[1], volume->color[2], _render_interleaved_only_volumes.alpha);
|
||||
|
||||
volume->render();
|
||||
|
||||
::glDisable(GL_BLEND);
|
||||
::glEnableClientState(GL_VERTEX_ARRAY);
|
||||
::glEnableClientState(GL_NORMAL_ARRAY);
|
||||
float color[4];
|
||||
::memcpy((void*)color, (const void*)volume->render_color, 4 * sizeof(float));
|
||||
::glUniform4fv(color_id, 1, (const GLfloat*)color);
|
||||
}
|
||||
else
|
||||
::glColor4f(volume->render_color[0], volume->render_color[1], volume->render_color[2], volume->render_color[3]);
|
||||
|
||||
if (print_box_min_id != -1)
|
||||
::glUniform3fv(print_box_min_id, 1, (const GLfloat*)print_box_min);
|
||||
|
||||
if (print_box_max_id != -1)
|
||||
::glUniform3fv(print_box_max_id, 1, (const GLfloat*)print_box_max);
|
||||
|
||||
if (print_box_origin_id != -1)
|
||||
{
|
||||
float origin[4] = { (float)volume->origin.x, (float)volume->origin.y, (float)volume->origin.z, volume->outside_printer_detection_enabled ? 1.0f : 0.0f };
|
||||
::glUniform4fv(print_box_origin_id, 1, (const GLfloat*)origin);
|
||||
}
|
||||
|
||||
volume->render();
|
||||
|
||||
::glEnableClientState(GL_VERTEX_ARRAY);
|
||||
::glEnableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (color_id >= 0)
|
||||
glUniform4fv(color_id, 1, (const GLfloat*)volume->color);
|
||||
::glUniform4fv(color_id, 1, (const GLfloat*)volume->render_color);
|
||||
else
|
||||
glColor4f(volume->color[0], volume->color[1], volume->color[2], volume->color[3]);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id);
|
||||
glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)));
|
||||
glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr);
|
||||
::glColor4f(volume->render_color[0], volume->render_color[1], volume->render_color[2], volume->render_color[3]);
|
||||
|
||||
if (print_box_min_id != -1)
|
||||
::glUniform3fv(print_box_min_id, 1, (const GLfloat*)print_box_min);
|
||||
|
||||
if (print_box_max_id != -1)
|
||||
::glUniform3fv(print_box_max_id, 1, (const GLfloat*)print_box_max);
|
||||
|
||||
if (print_box_origin_id != -1)
|
||||
{
|
||||
float origin[4] = { (float)volume->origin.x, (float)volume->origin.y, (float)volume->origin.z, volume->outside_printer_detection_enabled ? 1.0f : 0.0f };
|
||||
::glUniform4fv(print_box_origin_id, 1, (const GLfloat*)origin);
|
||||
}
|
||||
|
||||
::glBindBuffer(GL_ARRAY_BUFFER, volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id);
|
||||
::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)));
|
||||
::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr);
|
||||
|
||||
bool has_offset = (volume->origin.x != 0) || (volume->origin.y != 0) || (volume->origin.z != 0);
|
||||
if (has_offset) {
|
||||
::glPushMatrix();
|
||||
::glTranslated(volume->origin.x, volume->origin.y, volume->origin.z);
|
||||
}
|
||||
|
||||
if (n_triangles > 0) {
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.triangle_indices_VBO_id);
|
||||
glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, (const void*)(volume->tverts_range.first * 4));
|
||||
::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.triangle_indices_VBO_id);
|
||||
::glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, (const void*)(volume->tverts_range.first * 4));
|
||||
}
|
||||
if (n_quads > 0) {
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.quad_indices_VBO_id);
|
||||
glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, (const void*)(volume->qverts_range.first * 4));
|
||||
::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.quad_indices_VBO_id);
|
||||
::glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, (const void*)(volume->qverts_range.first * 4));
|
||||
}
|
||||
|
||||
if (has_offset)
|
||||
::glPopMatrix();
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_NORMAL_ARRAY);
|
||||
// glDisable(GL_BLEND);
|
||||
::glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
||||
::glDisableClientState(GL_VERTEX_ARRAY);
|
||||
::glDisableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
::glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
void GLVolumeCollection::render_legacy() const
|
||||
{
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
glCullFace(GL_BACK);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
|
@ -412,27 +576,25 @@ void GLVolumeCollection::render_legacy() const
|
|||
if (!volume->is_active)
|
||||
continue;
|
||||
|
||||
volume->set_render_color();
|
||||
|
||||
GLsizei n_triangles = GLsizei(std::min(volume->indexed_vertex_array.triangle_indices_size, volume->tverts_range.second - volume->tverts_range.first));
|
||||
GLsizei n_quads = GLsizei(std::min(volume->indexed_vertex_array.quad_indices_size, volume->qverts_range.second - volume->qverts_range.first));
|
||||
if (n_triangles + n_quads == 0)
|
||||
{
|
||||
if (_render_interleaved_only_volumes.enabled)
|
||||
{
|
||||
::glDisableClientState(GL_VERTEX_ARRAY);
|
||||
::glDisableClientState(GL_NORMAL_ARRAY);
|
||||
::glEnable(GL_BLEND);
|
||||
::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
::glDisableClientState(GL_VERTEX_ARRAY);
|
||||
::glDisableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
::glColor4f(volume->color[0], volume->color[1], volume->color[2], _render_interleaved_only_volumes.alpha);
|
||||
volume->render();
|
||||
::glColor4f(volume->render_color[0], volume->render_color[1], volume->render_color[2], volume->render_color[3]);
|
||||
volume->render();
|
||||
|
||||
::glEnableClientState(GL_VERTEX_ARRAY);
|
||||
::glEnableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
::glDisable(GL_BLEND);
|
||||
::glEnableClientState(GL_VERTEX_ARRAY);
|
||||
::glEnableClientState(GL_NORMAL_ARRAY);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
glColor4f(volume->color[0], volume->color[1], volume->color[2], volume->color[3]);
|
||||
|
||||
glColor4f(volume->render_color[0], volume->render_color[1], volume->render_color[2], volume->render_color[3]);
|
||||
glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), volume->indexed_vertex_array.vertices_and_normals_interleaved.data() + 3);
|
||||
glNormalPointer(GL_FLOAT, 6 * sizeof(float), volume->indexed_vertex_array.vertices_and_normals_interleaved.data());
|
||||
bool has_offset = volume->origin.x != 0 || volume->origin.y != 0 || volume->origin.z != 0;
|
||||
|
@ -445,11 +607,37 @@ void GLVolumeCollection::render_legacy() const
|
|||
if (n_quads > 0)
|
||||
glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, volume->indexed_vertex_array.quad_indices.data() + volume->qverts_range.first);
|
||||
if (has_offset)
|
||||
glPushMatrix();
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
void GLVolumeCollection::update_outside_state(const DynamicPrintConfig* config, bool all_inside)
|
||||
{
|
||||
if (config == nullptr)
|
||||
return;
|
||||
|
||||
const ConfigOptionPoints* opt = dynamic_cast<const ConfigOptionPoints*>(config->option("bed_shape"));
|
||||
if (opt == nullptr)
|
||||
return;
|
||||
|
||||
BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values));
|
||||
BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min.x), unscale(bed_box_2D.min.y), 0.0), Pointf3(unscale(bed_box_2D.max.x), unscale(bed_box_2D.max.y), config->opt_float("max_print_height")));
|
||||
|
||||
for (GLVolume* volume : this->volumes)
|
||||
{
|
||||
if (all_inside)
|
||||
{
|
||||
volume->is_outside = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
volume->is_outside = !print_volume.contains(volume->transformed_bounding_box());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> GLVolumeCollection::get_current_print_zs() const
|
||||
|
@ -1133,6 +1321,100 @@ static void point3_to_verts(const Point3& point, double width, double height, GL
|
|||
|
||||
_3DScene::GCodePreviewVolumeIndex _3DScene::s_gcode_preview_volume_index;
|
||||
_3DScene::LegendTexture _3DScene::s_legend_texture;
|
||||
_3DScene::WarningTexture _3DScene::s_warning_texture;
|
||||
|
||||
unsigned int _3DScene::TextureBase::finalize()
|
||||
{
|
||||
if (!m_data.empty()) {
|
||||
// sends buffer to gpu
|
||||
::glGenTextures(1, &m_tex_id);
|
||||
::glBindTexture(GL_TEXTURE_2D, m_tex_id);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_tex_width, (GLsizei)m_tex_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid*)m_data.data());
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
|
||||
::glBindTexture(GL_TEXTURE_2D, 0);
|
||||
m_data.clear();
|
||||
}
|
||||
return (m_tex_width > 0 && m_tex_height > 0) ? m_tex_id : 0;
|
||||
}
|
||||
|
||||
void _3DScene::TextureBase::_destroy_texture()
|
||||
{
|
||||
if (m_tex_id > 0)
|
||||
{
|
||||
::glDeleteTextures(1, &m_tex_id);
|
||||
m_tex_id = 0;
|
||||
m_tex_height = 0;
|
||||
m_tex_width = 0;
|
||||
}
|
||||
m_data.clear();
|
||||
}
|
||||
|
||||
|
||||
const unsigned char _3DScene::WarningTexture::Background_Color[3] = { 9, 91, 134 };
|
||||
const unsigned char _3DScene::WarningTexture::Opacity = 255;
|
||||
|
||||
// Generate a texture data, but don't load it into the GPU yet, as the GPU context may not yet be valid.
|
||||
bool _3DScene::WarningTexture::generate(const std::string& msg)
|
||||
{
|
||||
// Mark the texture as released, but don't release the texture from the GPU yet.
|
||||
m_tex_width = m_tex_height = 0;
|
||||
m_data.clear();
|
||||
|
||||
if (msg.empty())
|
||||
return false;
|
||||
|
||||
wxMemoryDC memDC;
|
||||
// select default font
|
||||
memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
|
||||
|
||||
// calculates texture size
|
||||
wxCoord w, h;
|
||||
memDC.GetTextExtent(msg, &w, &h);
|
||||
m_tex_width = (unsigned int)w;
|
||||
m_tex_height = (unsigned int)h;
|
||||
|
||||
// generates bitmap
|
||||
wxBitmap bitmap(m_tex_width, m_tex_height);
|
||||
|
||||
#if defined(__APPLE__) || defined(_MSC_VER)
|
||||
bitmap.UseAlpha();
|
||||
#endif
|
||||
|
||||
memDC.SelectObject(bitmap);
|
||||
memDC.SetBackground(wxBrush(wxColour(Background_Color[0], Background_Color[1], Background_Color[2])));
|
||||
memDC.Clear();
|
||||
|
||||
memDC.SetTextForeground(*wxWHITE);
|
||||
|
||||
// draw message
|
||||
memDC.DrawText(msg, 0, 0);
|
||||
|
||||
memDC.SelectObject(wxNullBitmap);
|
||||
|
||||
// Convert the bitmap into a linear data ready to be loaded into the GPU.
|
||||
{
|
||||
wxImage image = bitmap.ConvertToImage();
|
||||
image.SetMaskColour(Background_Color[0], Background_Color[1], Background_Color[2]);
|
||||
|
||||
// prepare buffer
|
||||
m_data.assign(4 * m_tex_width * m_tex_height, 0);
|
||||
for (unsigned int h = 0; h < m_tex_height; ++h)
|
||||
{
|
||||
unsigned int hh = h * m_tex_width;
|
||||
unsigned char* px_ptr = m_data.data() + 4 * hh;
|
||||
for (unsigned int w = 0; w < m_tex_width; ++w)
|
||||
{
|
||||
*px_ptr++ = image.GetRed(w, h);
|
||||
*px_ptr++ = image.GetGreen(w, h);
|
||||
*px_ptr++ = image.GetBlue(w, h);
|
||||
*px_ptr++ = image.IsTransparent(w, h) ? 0 : Opacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const unsigned char _3DScene::LegendTexture::Squares_Border_Color[3] = { 64, 64, 64 };
|
||||
const unsigned char _3DScene::LegendTexture::Background_Color[3] = { 9, 91, 134 };
|
||||
|
@ -1276,34 +1558,6 @@ bool _3DScene::LegendTexture::generate(const GCodePreviewData& preview_data, con
|
|||
return true;
|
||||
}
|
||||
|
||||
unsigned int _3DScene::LegendTexture::finalize()
|
||||
{
|
||||
if (! m_data.empty()) {
|
||||
// sends buffer to gpu
|
||||
::glGenTextures(1, &m_tex_id);
|
||||
::glBindTexture(GL_TEXTURE_2D, m_tex_id);
|
||||
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_tex_width, (GLsizei)m_tex_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid*)m_data.data());
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
|
||||
::glBindTexture(GL_TEXTURE_2D, 0);
|
||||
m_data.clear();
|
||||
}
|
||||
return (m_tex_width > 0 && m_tex_height > 0) ? m_tex_id : 0;
|
||||
}
|
||||
|
||||
void _3DScene::LegendTexture::_destroy_texture()
|
||||
{
|
||||
if (m_tex_id > 0)
|
||||
{
|
||||
::glDeleteTextures(1, &m_tex_id);
|
||||
m_tex_id = 0;
|
||||
m_tex_height = 0;
|
||||
m_tex_width = 0;
|
||||
}
|
||||
m_data.clear();
|
||||
}
|
||||
|
||||
void _3DScene::_glew_init()
|
||||
{
|
||||
glewInit();
|
||||
|
@ -1353,27 +1607,17 @@ void _3DScene::load_gcode_preview(const Print* print, const GCodePreviewData* pr
|
|||
_load_gcode_unretractions(*preview_data, *volumes, use_VBOs);
|
||||
|
||||
if (volumes->empty())
|
||||
{
|
||||
reset_legend_texture();
|
||||
volumes->set_render_interleaved_only_volumes(GLVolumeCollection::RenderInterleavedOnlyVolumes(false, 0.0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
_generate_legend_texture(*preview_data, tool_colors);
|
||||
|
||||
_load_shells(*print, *volumes, use_VBOs);
|
||||
volumes->set_render_interleaved_only_volumes(GLVolumeCollection::RenderInterleavedOnlyVolumes(true, 0.25f));
|
||||
}
|
||||
}
|
||||
|
||||
_update_gcode_volumes_visibility(*preview_data, *volumes);
|
||||
}
|
||||
|
||||
unsigned int _3DScene::get_legend_texture_id()
|
||||
{
|
||||
return s_legend_texture.get_texture_id();
|
||||
}
|
||||
|
||||
unsigned int _3DScene::get_legend_texture_width()
|
||||
{
|
||||
return s_legend_texture.get_texture_width();
|
||||
|
@ -1389,6 +1633,36 @@ void _3DScene::reset_legend_texture()
|
|||
s_legend_texture.reset_texture();
|
||||
}
|
||||
|
||||
unsigned int _3DScene::finalize_legend_texture()
|
||||
{
|
||||
return s_legend_texture.finalize();
|
||||
}
|
||||
|
||||
unsigned int _3DScene::get_warning_texture_width()
|
||||
{
|
||||
return s_warning_texture.get_texture_width();
|
||||
}
|
||||
|
||||
unsigned int _3DScene::get_warning_texture_height()
|
||||
{
|
||||
return s_warning_texture.get_texture_height();
|
||||
}
|
||||
|
||||
void _3DScene::generate_warning_texture(const std::string& msg)
|
||||
{
|
||||
s_warning_texture.generate(msg);
|
||||
}
|
||||
|
||||
void _3DScene::reset_warning_texture()
|
||||
{
|
||||
s_warning_texture.reset_texture();
|
||||
}
|
||||
|
||||
unsigned int _3DScene::finalize_warning_texture()
|
||||
{
|
||||
return s_warning_texture.finalize();
|
||||
}
|
||||
|
||||
// Create 3D thick extrusion lines for a skirt and brim.
|
||||
// Adds a new Slic3r::GUI::3DScene::Volume to volumes.
|
||||
void _3DScene::_load_print_toolpaths(
|
||||
|
@ -1501,6 +1775,7 @@ void _3DScene::_load_print_object_toolpaths(
|
|||
auto new_volume = [volumes, &new_volume_mutex](const float *color) -> GLVolume* {
|
||||
auto *volume = new GLVolume(color);
|
||||
new_volume_mutex.lock();
|
||||
volume->outside_printer_detection_enabled = false;
|
||||
volumes->volumes.emplace_back(volume);
|
||||
new_volume_mutex.unlock();
|
||||
return volume;
|
||||
|
@ -1651,6 +1926,7 @@ void _3DScene::_load_wipe_tower_toolpaths(
|
|||
auto new_volume = [volumes, &new_volume_mutex](const float *color) -> GLVolume* {
|
||||
auto *volume = new GLVolume(color);
|
||||
new_volume_mutex.lock();
|
||||
volume->outside_printer_detection_enabled = false;
|
||||
volumes->volumes.emplace_back(volume);
|
||||
new_volume_mutex.unlock();
|
||||
return volume;
|
||||
|
@ -2221,6 +2497,7 @@ void _3DScene::_update_gcode_volumes_visibility(const GCodePreviewData& preview_
|
|||
for (std::vector<GLVolume*>::iterator it = begin; it != end; ++it)
|
||||
{
|
||||
GLVolume* volume = *it;
|
||||
volume->outside_printer_detection_enabled = false;
|
||||
|
||||
switch (s_gcode_preview_volume_index.first_volumes[i].type)
|
||||
{
|
||||
|
@ -2253,6 +2530,7 @@ void _3DScene::_update_gcode_volumes_visibility(const GCodePreviewData& preview_
|
|||
case GCodePreviewVolumeIndex::Shell:
|
||||
{
|
||||
volume->is_active = preview_data.shell.is_visible;
|
||||
volume->color[3] = 0.25f;
|
||||
volume->zoom_to_volumes = false;
|
||||
break;
|
||||
}
|
||||
|
@ -2272,11 +2550,6 @@ void _3DScene::_generate_legend_texture(const GCodePreviewData& preview_data, co
|
|||
s_legend_texture.generate(preview_data, tool_colors);
|
||||
}
|
||||
|
||||
unsigned int _3DScene::finalize_legend_texture()
|
||||
{
|
||||
return s_legend_texture.finalize();
|
||||
}
|
||||
|
||||
void _3DScene::_load_shells(const Print& print, GLVolumeCollection& volumes, bool use_VBOs)
|
||||
{
|
||||
size_t initial_volumes_count = volumes.volumes.size();
|
||||
|
|
|
@ -16,6 +16,7 @@ class PrintObject;
|
|||
class Model;
|
||||
class ModelObject;
|
||||
class GCodePreviewData;
|
||||
class DynamicPrintConfig;
|
||||
|
||||
// A container for interleaved arrays of 3D vertices and normals,
|
||||
// possibly indexed by triangles and / or quads.
|
||||
|
@ -85,6 +86,7 @@ public:
|
|||
unsigned int quad_indices_VBO_id;
|
||||
|
||||
void load_mesh_flat_shading(const TriangleMesh &mesh);
|
||||
void load_mesh_full_shading(const TriangleMesh &mesh);
|
||||
|
||||
inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; }
|
||||
|
||||
|
@ -208,7 +210,38 @@ public:
|
|||
};
|
||||
|
||||
class GLVolume {
|
||||
struct LayerHeightTextureData
|
||||
{
|
||||
// ID of the layer height texture
|
||||
unsigned int texture_id;
|
||||
// ID of the shader used to render with the layer height texture
|
||||
unsigned int shader_id;
|
||||
// The print object to update when generating the layer height texture
|
||||
PrintObject* print_object;
|
||||
|
||||
float z_cursor_relative;
|
||||
float edit_band_width;
|
||||
|
||||
LayerHeightTextureData() { reset(); }
|
||||
|
||||
void reset()
|
||||
{
|
||||
texture_id = 0;
|
||||
shader_id = 0;
|
||||
print_object = nullptr;
|
||||
z_cursor_relative = 0.0f;
|
||||
edit_band_width = 0.0f;
|
||||
}
|
||||
|
||||
bool can_use() { return (texture_id > 0) && (shader_id > 0) && (print_object != nullptr); }
|
||||
};
|
||||
|
||||
public:
|
||||
static const float SELECTED_COLOR[4];
|
||||
static const float HOVER_COLOR[4];
|
||||
static const float OUTSIDE_COLOR[4];
|
||||
static const float SELECTED_OUTSIDE_COLOR[4];
|
||||
|
||||
GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f) :
|
||||
composite_id(-1),
|
||||
select_group_id(-1),
|
||||
|
@ -216,6 +249,8 @@ public:
|
|||
selected(false),
|
||||
is_active(true),
|
||||
zoom_to_volumes(true),
|
||||
outside_printer_detection_enabled(true),
|
||||
is_outside(false),
|
||||
hover(false),
|
||||
tverts_range(0, size_t(-1)),
|
||||
qverts_range(0, size_t(-1))
|
||||
|
@ -224,6 +259,7 @@ public:
|
|||
color[1] = g;
|
||||
color[2] = b;
|
||||
color[3] = a;
|
||||
set_render_color(r, g, b, a);
|
||||
}
|
||||
GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {}
|
||||
|
||||
|
@ -243,7 +279,8 @@ public:
|
|||
Pointf3 origin;
|
||||
// Color of the triangles / quads held by this volume.
|
||||
float color[4];
|
||||
|
||||
// Color used to render this volume.
|
||||
float render_color[4];
|
||||
// An ID containing the object ID, volume ID and instance ID.
|
||||
int composite_id;
|
||||
// An ID for group selection. It may be the same for all meshes of all object instances, or for just a single object instance.
|
||||
|
@ -256,6 +293,10 @@ public:
|
|||
bool is_active;
|
||||
// Whether or not to use this volume when applying zoom_to_volumes()
|
||||
bool zoom_to_volumes;
|
||||
// Wheter or not this volume is enabled for outside print volume detection.
|
||||
bool outside_printer_detection_enabled;
|
||||
// Wheter or not this volume is outside print volume.
|
||||
bool is_outside;
|
||||
// Boolean: Is mouse over this object?
|
||||
bool hover;
|
||||
|
||||
|
@ -271,6 +312,10 @@ public:
|
|||
// Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer.
|
||||
std::vector<size_t> offsets;
|
||||
|
||||
void set_render_color(float r, float g, float b, float a);
|
||||
void set_render_color(const float* rgba, unsigned int size);
|
||||
// Sets render color in dependence of current state
|
||||
void set_render_color();
|
||||
|
||||
int object_idx() const { return this->composite_id / 1000000; }
|
||||
int volume_idx() const { return (this->composite_id / 1000) % 1000; }
|
||||
|
@ -282,11 +327,14 @@ public:
|
|||
|
||||
void set_range(coordf_t low, coordf_t high);
|
||||
void render() const;
|
||||
void render_using_layer_height() const;
|
||||
void finalize_geometry(bool use_VBOs) { this->indexed_vertex_array.finalize_geometry(use_VBOs); }
|
||||
void release_geometry() { this->indexed_vertex_array.release_geometry(); }
|
||||
|
||||
/************************************************ Layer height texture ****************************************************/
|
||||
std::shared_ptr<GLTexture> layer_height_texture;
|
||||
// Data to render this volume using the layer height texture
|
||||
LayerHeightTextureData layer_height_texture_data;
|
||||
|
||||
bool has_layer_height_texture() const
|
||||
{ return this->layer_height_texture.get() != nullptr; }
|
||||
|
@ -296,11 +344,11 @@ public:
|
|||
{ return (this->layer_height_texture.get() == nullptr) ? 0 : this->layer_height_texture->height; }
|
||||
size_t layer_height_texture_cells() const
|
||||
{ return (this->layer_height_texture.get() == nullptr) ? 0 : this->layer_height_texture->cells; }
|
||||
void* layer_height_texture_data_ptr_level0() {
|
||||
void* layer_height_texture_data_ptr_level0() const {
|
||||
return (layer_height_texture.get() == nullptr) ? 0 :
|
||||
(void*)layer_height_texture->data.data();
|
||||
}
|
||||
void* layer_height_texture_data_ptr_level1() {
|
||||
void* layer_height_texture_data_ptr_level1() const {
|
||||
return (layer_height_texture.get() == nullptr) ? 0 :
|
||||
(void*)(layer_height_texture->data.data() + layer_height_texture->width * layer_height_texture->height * 4);
|
||||
}
|
||||
|
@ -309,31 +357,24 @@ public:
|
|||
double(this->layer_height_texture->cells - 1) / (double(this->layer_height_texture->width) * bounding_box.max.z);
|
||||
}
|
||||
void generate_layer_height_texture(PrintObject *print_object, bool force);
|
||||
|
||||
void set_layer_height_texture_data(unsigned int texture_id, unsigned int shader_id, PrintObject* print_object, float z_cursor_relative, float edit_band_width)
|
||||
{
|
||||
layer_height_texture_data.texture_id = texture_id;
|
||||
layer_height_texture_data.shader_id = shader_id;
|
||||
layer_height_texture_data.print_object = print_object;
|
||||
layer_height_texture_data.z_cursor_relative = z_cursor_relative;
|
||||
layer_height_texture_data.edit_band_width = edit_band_width;
|
||||
}
|
||||
|
||||
void reset_layer_height_texture_data() { layer_height_texture_data.reset(); }
|
||||
};
|
||||
|
||||
class GLVolumeCollection
|
||||
{
|
||||
public:
|
||||
struct RenderInterleavedOnlyVolumes
|
||||
{
|
||||
bool enabled;
|
||||
float alpha; // [0..1]
|
||||
|
||||
RenderInterleavedOnlyVolumes()
|
||||
: enabled(false)
|
||||
, alpha(0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
RenderInterleavedOnlyVolumes(bool enabled, float alpha)
|
||||
: enabled(enabled)
|
||||
, alpha(alpha)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
RenderInterleavedOnlyVolumes _render_interleaved_only_volumes;
|
||||
// min and max vertex of the print box volume
|
||||
float print_box_min[3];
|
||||
float print_box_max[3];
|
||||
|
||||
public:
|
||||
std::vector<GLVolume*> volumes;
|
||||
|
@ -370,7 +411,12 @@ public:
|
|||
bool empty() const { return volumes.empty(); }
|
||||
void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); }
|
||||
|
||||
void set_render_interleaved_only_volumes(const RenderInterleavedOnlyVolumes& render_interleaved_only_volumes) { _render_interleaved_only_volumes = render_interleaved_only_volumes; }
|
||||
void set_print_box(float min_x, float min_y, float min_z, float max_x, float max_y, float max_z) {
|
||||
print_box_min[0] = min_x; print_box_min[1] = min_y; print_box_min[2] = min_z;
|
||||
print_box_max[0] = max_x; print_box_max[1] = max_y; print_box_max[2] = max_z;
|
||||
}
|
||||
|
||||
void update_outside_state(const DynamicPrintConfig* config, bool all_inside);
|
||||
|
||||
// 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;
|
||||
|
@ -411,27 +457,20 @@ class _3DScene
|
|||
|
||||
static GCodePreviewVolumeIndex s_gcode_preview_volume_index;
|
||||
|
||||
class LegendTexture
|
||||
class TextureBase
|
||||
{
|
||||
static const unsigned int Px_Title_Offset = 5;
|
||||
static const unsigned int Px_Text_Offset = 5;
|
||||
static const unsigned int Px_Square = 20;
|
||||
static const unsigned int Px_Square_Contour = 1;
|
||||
static const unsigned int Px_Border = Px_Square / 2;
|
||||
static const unsigned char Squares_Border_Color[3];
|
||||
static const unsigned char Background_Color[3];
|
||||
static const unsigned char Opacity;
|
||||
|
||||
protected:
|
||||
unsigned int m_tex_id;
|
||||
unsigned int m_tex_width;
|
||||
unsigned int m_tex_height;
|
||||
|
||||
// generate() fills in m_data with the pixels, while finalize() moves the data to the GPU before rendering.
|
||||
std::vector<unsigned char> m_data;
|
||||
|
||||
public:
|
||||
LegendTexture() : m_tex_id(0), m_tex_width(0), m_tex_height(0) {}
|
||||
~LegendTexture() { _destroy_texture(); }
|
||||
|
||||
// Generate a texture data, but don't load it into the GPU yet, as the glcontext may not be valid yet.
|
||||
bool generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
|
||||
TextureBase() : m_tex_id(0), m_tex_width(0), m_tex_height(0) {}
|
||||
virtual ~TextureBase() { _destroy_texture(); }
|
||||
|
||||
// If not loaded, load the texture data into the GPU. Return a texture ID or 0 if the texture has zero size.
|
||||
unsigned int finalize();
|
||||
|
||||
|
@ -442,26 +481,61 @@ class _3DScene
|
|||
void reset_texture() { _destroy_texture(); }
|
||||
|
||||
private:
|
||||
bool _create_texture(const GCodePreviewData& preview_data, const wxBitmap& bitmap);
|
||||
void _destroy_texture();
|
||||
// generate() fills in m_data with the pixels, while finalize() moves the data to the GPU before rendering.
|
||||
std::vector<unsigned char> m_data;
|
||||
};
|
||||
|
||||
class WarningTexture : public TextureBase
|
||||
{
|
||||
static const unsigned char Background_Color[3];
|
||||
static const unsigned char Opacity;
|
||||
|
||||
public:
|
||||
WarningTexture() : TextureBase() {}
|
||||
|
||||
// Generate a texture data, but don't load it into the GPU yet, as the glcontext may not be valid yet.
|
||||
bool generate(const std::string& msg);
|
||||
};
|
||||
|
||||
class LegendTexture : public TextureBase
|
||||
{
|
||||
static const unsigned int Px_Title_Offset = 5;
|
||||
static const unsigned int Px_Text_Offset = 5;
|
||||
static const unsigned int Px_Square = 20;
|
||||
static const unsigned int Px_Square_Contour = 1;
|
||||
static const unsigned int Px_Border = Px_Square / 2;
|
||||
static const unsigned char Squares_Border_Color[3];
|
||||
static const unsigned char Background_Color[3];
|
||||
static const unsigned char Opacity;
|
||||
|
||||
public:
|
||||
LegendTexture() : TextureBase() {}
|
||||
|
||||
// Generate a texture data, but don't load it into the GPU yet, as the glcontext may not be valid yet.
|
||||
bool generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
|
||||
};
|
||||
|
||||
static LegendTexture s_legend_texture;
|
||||
static WarningTexture s_warning_texture;
|
||||
|
||||
public:
|
||||
static void _glew_init();
|
||||
|
||||
static void load_gcode_preview(const Print* print, const GCodePreviewData* preview_data, GLVolumeCollection* volumes, const std::vector<std::string>& str_tool_colors, bool use_VBOs);
|
||||
|
||||
static unsigned int get_legend_texture_id();
|
||||
static unsigned int get_legend_texture_width();
|
||||
static unsigned int get_legend_texture_height();
|
||||
|
||||
static void reset_legend_texture();
|
||||
static unsigned int finalize_legend_texture();
|
||||
|
||||
static unsigned int get_warning_texture_width();
|
||||
static unsigned int get_warning_texture_height();
|
||||
|
||||
// generates a warning texture containing the given message
|
||||
static void generate_warning_texture(const std::string& msg);
|
||||
static void reset_warning_texture();
|
||||
static unsigned int finalize_warning_texture();
|
||||
|
||||
static void _load_print_toolpaths(
|
||||
const Print *print,
|
||||
GLVolumeCollection *volumes,
|
||||
|
|
200
xs/src/slic3r/GUI/BonjourDialog.cpp
Normal file
200
xs/src/slic3r/GUI/BonjourDialog.cpp
Normal file
|
@ -0,0 +1,200 @@
|
|||
#include "slic3r/Utils/Bonjour.hpp" // On Windows, boost needs to be included before wxWidgets headers
|
||||
|
||||
#include "BonjourDialog.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <mutex>
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/listctrl.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/timer.h>
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/Utils/Bonjour.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
struct BonjourReplyEvent : public wxEvent
|
||||
{
|
||||
BonjourReply reply;
|
||||
|
||||
BonjourReplyEvent(wxEventType eventType, int winid, BonjourReply &&reply) :
|
||||
wxEvent(winid, eventType),
|
||||
reply(std::move(reply))
|
||||
{}
|
||||
|
||||
virtual wxEvent *Clone() const
|
||||
{
|
||||
return new BonjourReplyEvent(*this);
|
||||
}
|
||||
};
|
||||
|
||||
wxDEFINE_EVENT(EVT_BONJOUR_REPLY, BonjourReplyEvent);
|
||||
|
||||
wxDECLARE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
|
||||
|
||||
class ReplySet: public std::set<BonjourReply> {};
|
||||
|
||||
struct LifetimeGuard
|
||||
{
|
||||
std::mutex mutex;
|
||||
BonjourDialog *dialog;
|
||||
|
||||
LifetimeGuard(BonjourDialog *dialog) : dialog(dialog) {}
|
||||
};
|
||||
|
||||
|
||||
BonjourDialog::BonjourDialog(wxWindow *parent) :
|
||||
wxDialog(parent, wxID_ANY, _(L("Network lookup"))),
|
||||
list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxSize(800, 300))),
|
||||
replies(new ReplySet),
|
||||
label(new wxStaticText(this, wxID_ANY, "")),
|
||||
timer(new wxTimer()),
|
||||
timer_state(0)
|
||||
{
|
||||
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
|
||||
|
||||
list->SetSingleStyle(wxLC_SINGLE_SEL);
|
||||
list->SetSingleStyle(wxLC_SORT_DESCENDING);
|
||||
list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 50);
|
||||
list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 100);
|
||||
list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 200);
|
||||
list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 50);
|
||||
|
||||
vsizer->Add(list, 1, wxEXPAND | wxALL, 10);
|
||||
|
||||
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, 10);
|
||||
button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, 10);
|
||||
// ^ Note: The Ok/Cancel labels are translated by wxWidgets
|
||||
|
||||
vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
|
||||
SetSizerAndFit(vsizer);
|
||||
|
||||
Bind(EVT_BONJOUR_REPLY, &BonjourDialog::on_reply, this);
|
||||
|
||||
Bind(EVT_BONJOUR_COMPLETE, [this](wxCommandEvent &) {
|
||||
this->timer_state = 0;
|
||||
});
|
||||
|
||||
Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this);
|
||||
}
|
||||
|
||||
BonjourDialog::~BonjourDialog()
|
||||
{
|
||||
// Needed bacuse of forward defs
|
||||
}
|
||||
|
||||
bool BonjourDialog::show_and_lookup()
|
||||
{
|
||||
Show(); // Because we need GetId() to work before ShowModal()
|
||||
|
||||
timer->Stop();
|
||||
timer->SetOwner(this);
|
||||
timer_state = 1;
|
||||
timer->Start(1000);
|
||||
wxTimerEvent evt_dummy;
|
||||
on_timer(evt_dummy);
|
||||
|
||||
// The background thread needs to queue messages for this dialog
|
||||
// and for that it needs a valid pointer to it (mandated by the wxWidgets API).
|
||||
// Here we put the pointer under a shared_ptr and protect it by a mutex,
|
||||
// so that both threads can access it safely.
|
||||
auto dguard = std::make_shared<LifetimeGuard>(this);
|
||||
|
||||
bonjour = std::move(Bonjour("octoprint")
|
||||
.set_retries(3)
|
||||
.set_timeout(4)
|
||||
.on_reply([dguard](BonjourReply &&reply) {
|
||||
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||
auto dialog = dguard->dialog;
|
||||
if (dialog != nullptr) {
|
||||
auto evt = new BonjourReplyEvent(EVT_BONJOUR_REPLY, dialog->GetId(), std::move(reply));
|
||||
wxQueueEvent(dialog, evt);
|
||||
}
|
||||
})
|
||||
.on_complete([dguard]() {
|
||||
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||
auto dialog = dguard->dialog;
|
||||
if (dialog != nullptr) {
|
||||
auto evt = new wxCommandEvent(EVT_BONJOUR_COMPLETE, dialog->GetId());
|
||||
wxQueueEvent(dialog, evt);
|
||||
}
|
||||
})
|
||||
.lookup()
|
||||
);
|
||||
|
||||
bool res = ShowModal() == wxID_OK && list->GetFirstSelected() >= 0;
|
||||
{
|
||||
// Tell the background thread the dialog is going away...
|
||||
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||
dguard->dialog = nullptr;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
wxString BonjourDialog::get_selected() const
|
||||
{
|
||||
auto sel = list->GetFirstSelected();
|
||||
return sel >= 0 ? list->GetItemText(sel) : wxString();
|
||||
}
|
||||
|
||||
|
||||
// Private
|
||||
|
||||
void BonjourDialog::on_reply(BonjourReplyEvent &e)
|
||||
{
|
||||
if (replies->find(e.reply) != replies->end()) {
|
||||
// We already have this reply
|
||||
return;
|
||||
}
|
||||
|
||||
replies->insert(std::move(e.reply));
|
||||
|
||||
auto selected = get_selected();
|
||||
list->DeleteAllItems();
|
||||
|
||||
// The whole list is recreated so that we benefit from it already being sorted in the set.
|
||||
// (And also because wxListView's sorting API is bananas.)
|
||||
for (const auto &reply : *replies) {
|
||||
auto item = list->InsertItem(0, reply.full_address);
|
||||
list->SetItem(item, 1, reply.hostname);
|
||||
list->SetItem(item, 2, reply.service_name);
|
||||
list->SetItem(item, 3, reply.version);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
this->list->SetColumnWidth(i, wxLIST_AUTOSIZE);
|
||||
if (this->list->GetColumnWidth(i) < 100) { this->list->SetColumnWidth(i, 100); }
|
||||
}
|
||||
|
||||
if (!selected.IsEmpty()) {
|
||||
// Attempt to preserve selection
|
||||
auto hit = list->FindItem(-1, selected);
|
||||
if (hit >= 0) { list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); }
|
||||
}
|
||||
}
|
||||
|
||||
void BonjourDialog::on_timer(wxTimerEvent &)
|
||||
{
|
||||
const auto search_str = _(L("Searching for devices"));
|
||||
|
||||
if (timer_state > 0) {
|
||||
const std::string dots(timer_state, '.');
|
||||
label->SetLabel(wxString::Format("%s %s", search_str, dots));
|
||||
timer_state = (timer_state) % 3 + 1;
|
||||
} else {
|
||||
label->SetLabel(wxString::Format("%s: %s", search_str, _(L("Finished."))));
|
||||
timer->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
49
xs/src/slic3r/GUI/BonjourDialog.hpp
Normal file
49
xs/src/slic3r/GUI/BonjourDialog.hpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
#ifndef slic3r_BonjourDialog_hpp_
|
||||
#define slic3r_BonjourDialog_hpp_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <wx/dialog.h>
|
||||
|
||||
class wxListView;
|
||||
class wxStaticText;
|
||||
class wxTimer;
|
||||
class wxTimerEvent;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Bonjour;
|
||||
class BonjourReplyEvent;
|
||||
class ReplySet;
|
||||
|
||||
|
||||
class BonjourDialog: public wxDialog
|
||||
{
|
||||
public:
|
||||
BonjourDialog(wxWindow *parent);
|
||||
BonjourDialog(BonjourDialog &&) = delete;
|
||||
BonjourDialog(const BonjourDialog &) = delete;
|
||||
BonjourDialog &operator=(BonjourDialog &&) = delete;
|
||||
BonjourDialog &operator=(const BonjourDialog &) = delete;
|
||||
~BonjourDialog();
|
||||
|
||||
bool show_and_lookup();
|
||||
wxString get_selected() const;
|
||||
private:
|
||||
wxListView *list;
|
||||
std::unique_ptr<ReplySet> replies;
|
||||
wxStaticText *label;
|
||||
std::shared_ptr<Bonjour> bonjour;
|
||||
std::unique_ptr<wxTimer> timer;
|
||||
unsigned timer_state;
|
||||
|
||||
void on_reply(BonjourReplyEvent &);
|
||||
void on_timer(wxTimerEvent &);
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -18,6 +18,17 @@ namespace Slic3r { namespace GUI {
|
|||
wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None);
|
||||
}
|
||||
|
||||
void Field::PostInitialize(){
|
||||
m_Undo_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER);
|
||||
// use bouth of temporary_icons till don't have "undo_icon"
|
||||
auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
if (wxMSW) m_Undo_btn->SetBackgroundColour(color);
|
||||
m_Undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG));
|
||||
m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); }));
|
||||
|
||||
BUILD();
|
||||
}
|
||||
|
||||
void Field::on_kill_focus(wxEvent& event) {
|
||||
// Without this, there will be nasty focus bugs on Windows.
|
||||
// Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all
|
||||
|
@ -34,6 +45,12 @@ namespace Slic3r { namespace GUI {
|
|||
m_on_change(m_opt_id, get_value());
|
||||
}
|
||||
|
||||
void Field::on_back_to_initial_value()
|
||||
{
|
||||
if (m_back_to_initial_value != nullptr && m_is_modified_value)
|
||||
m_back_to_initial_value(m_opt_id);
|
||||
}
|
||||
|
||||
wxString Field::get_tooltip_text(const wxString& default_string)
|
||||
{
|
||||
wxString tooltip_text("");
|
||||
|
@ -187,6 +204,17 @@ void CheckBox::BUILD() {
|
|||
window = dynamic_cast<wxWindow*>(temp);
|
||||
}
|
||||
|
||||
boost::any CheckBox::get_value()
|
||||
{
|
||||
boost::any ret_val;
|
||||
bool value = dynamic_cast<wxCheckBox*>(window)->GetValue();
|
||||
if (m_opt.type == coBool)
|
||||
ret_val = static_cast<bool>(value);
|
||||
else
|
||||
ret_val = static_cast<unsigned char>(value);
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
int undef_spin_val = -9999; //! Probably, It's not necessary
|
||||
|
||||
void SpinCtrl::BUILD() {
|
||||
|
@ -217,8 +245,13 @@ void SpinCtrl::BUILD() {
|
|||
break;
|
||||
}
|
||||
|
||||
const int min_val = m_opt_id == "standby_temperature_delta" ?
|
||||
-500 : m_opt.min > 0 ?
|
||||
m_opt.min : 0;
|
||||
const int max_val = m_opt.max < 2147483647 ? m_opt.max : 2147483647;
|
||||
|
||||
auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size,
|
||||
0, m_opt.min >0 ? m_opt.min : 0, m_opt.max < 2147483647 ? m_opt.max : 2147483647, default_value);
|
||||
0, min_val, max_val, default_value);
|
||||
|
||||
temp->Bind(wxEVT_SPINCTRL, ([this](wxCommandEvent e) { tmp_value = undef_spin_val; on_change_field(); }), temp->GetId());
|
||||
temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { tmp_value = undef_spin_val; on_kill_focus(e); }), temp->GetId());
|
||||
|
@ -228,7 +261,7 @@ void SpinCtrl::BUILD() {
|
|||
// # when it was changed from the text control, so the on_change callback
|
||||
// # gets the old one, and on_kill_focus resets the control to the old value.
|
||||
// # As a workaround, we get the new value from $event->GetString and store
|
||||
// # here temporarily so that we can return it from $self->get_value
|
||||
// # here temporarily so that we can return it from $self->get_value
|
||||
std::string value = e.GetString().utf8_str().data();
|
||||
if (is_matched(value, "^\\d+$"))
|
||||
tmp_value = std::stoi(value);
|
||||
|
@ -261,8 +294,10 @@ void Choice::BUILD() {
|
|||
if (m_opt.enum_labels.empty() && m_opt.enum_values.empty()){
|
||||
}
|
||||
else{
|
||||
for (auto el : m_opt.enum_labels.empty() ? m_opt.enum_values : m_opt.enum_labels)
|
||||
temp->Append(wxString(el));
|
||||
for (auto el : m_opt.enum_labels.empty() ? m_opt.enum_values : m_opt.enum_labels){
|
||||
const wxString& str = m_opt_id == "support" ? L_str(el) : el;
|
||||
temp->Append(str);
|
||||
}
|
||||
set_selection();
|
||||
}
|
||||
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId());
|
||||
|
@ -330,9 +365,9 @@ void Choice::set_selection()
|
|||
}
|
||||
}
|
||||
|
||||
void Choice::set_value(const std::string value) //! Redundant?
|
||||
void Choice::set_value(const std::string value, bool change_event) //! Redundant?
|
||||
{
|
||||
m_disable_change_event = true;
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
size_t idx=0;
|
||||
for (auto el : m_opt.enum_values)
|
||||
|
@ -349,9 +384,9 @@ void Choice::set_value(const std::string value) //! Redundant?
|
|||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
void Choice::set_value(boost::any value)
|
||||
void Choice::set_value(boost::any value, bool change_event)
|
||||
{
|
||||
m_disable_change_event = true;
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
switch (m_opt.type){
|
||||
case coInt:
|
||||
|
@ -394,7 +429,7 @@ void Choice::set_values(const std::vector<std::string> values)
|
|||
return;
|
||||
m_disable_change_event = true;
|
||||
|
||||
// # it looks that Clear() also clears the text field in recent wxWidgets versions,
|
||||
// # it looks that Clear() also clears the text field in recent wxWidgets versions,
|
||||
// # but we want to preserve it
|
||||
auto ww = dynamic_cast<wxComboBox*>(window);
|
||||
auto value = ww->GetValue();
|
||||
|
@ -411,6 +446,9 @@ boost::any Choice::get_value()
|
|||
boost::any ret_val;
|
||||
wxString ret_str = static_cast<wxComboBox*>(window)->GetValue();
|
||||
|
||||
if (m_opt_id == "support")
|
||||
return ret_str;
|
||||
|
||||
if (m_opt.type != coEnum)
|
||||
ret_val = get_value_by_opt_type(ret_str);
|
||||
else
|
||||
|
@ -503,9 +541,9 @@ void PointCtrl::BUILD()
|
|||
y_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
|
||||
}
|
||||
|
||||
void PointCtrl::set_value(const Pointf value)
|
||||
void PointCtrl::set_value(const Pointf value, bool change_event)
|
||||
{
|
||||
m_disable_change_event = true;
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
double val = value.x;
|
||||
x_textctrl->SetValue(val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None));
|
||||
|
@ -515,7 +553,7 @@ void PointCtrl::set_value(const Pointf value)
|
|||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
void PointCtrl::set_value(boost::any value)
|
||||
void PointCtrl::set_value(boost::any value, bool change_event)
|
||||
{
|
||||
Pointf pt;
|
||||
Pointf *ptf = boost::any_cast<Pointf>(&value);
|
||||
|
@ -541,7 +579,7 @@ void PointCtrl::set_value(boost::any value)
|
|||
// return;
|
||||
// }
|
||||
// }
|
||||
set_value(pt);
|
||||
set_value(pt, change_event);
|
||||
}
|
||||
|
||||
boost::any PointCtrl::get_value()
|
||||
|
|
|
@ -18,6 +18,13 @@
|
|||
|
||||
//#include "slic3r_gui.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#ifdef __WXMSW__
|
||||
#define wxMSW true
|
||||
#else
|
||||
#define wxMSW false
|
||||
#endif
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
|
@ -25,13 +32,14 @@ class Field;
|
|||
using t_field = std::unique_ptr<Field>;
|
||||
using t_kill_focus = std::function<void()>;
|
||||
using t_change = std::function<void(t_config_option_key, boost::any)>;
|
||||
using t_back_to_init = std::function<void(std::string)>;
|
||||
|
||||
wxString double_to_string(double const value);
|
||||
|
||||
class Field {
|
||||
protected:
|
||||
// factory function to defer and enforce creation of derived type.
|
||||
virtual void PostInitialize() { BUILD(); }
|
||||
virtual void PostInitialize();
|
||||
|
||||
/// Finish constructing the Field's wxWidget-related properties, including setting its own sizer, etc.
|
||||
virtual void BUILD() = 0;
|
||||
|
@ -42,6 +50,8 @@ protected:
|
|||
void on_kill_focus(wxEvent& event);
|
||||
/// Call the attached on_change method.
|
||||
void on_change_field();
|
||||
/// Call the attached m_back_to_initial_value method.
|
||||
void on_back_to_initial_value();
|
||||
|
||||
public:
|
||||
/// parent wx item, opportunity to refactor (probably not necessary - data duplication)
|
||||
|
@ -53,8 +63,13 @@ public:
|
|||
/// Function object to store callback passed in from owning object.
|
||||
t_change m_on_change {nullptr};
|
||||
|
||||
/// Function object to store callback passed in from owning object.
|
||||
t_back_to_init m_back_to_initial_value{ nullptr };
|
||||
|
||||
// This is used to avoid recursive invocation of the field change/update by wxWidgets.
|
||||
bool m_disable_change_event {false};
|
||||
// This is used to avoid recursive invocation of the field change/update by wxWidgets.
|
||||
bool m_is_modified_value {false};
|
||||
|
||||
/// Copy of ConfigOption for deduction purposes
|
||||
const ConfigOptionDef m_opt {ConfigOptionDef()};
|
||||
|
@ -63,7 +78,7 @@ public:
|
|||
/// Sets a value for this control.
|
||||
/// subclasses should overload with a specific version
|
||||
/// Postcondition: Method does not fire the on_change event.
|
||||
virtual void set_value(boost::any value) = 0;
|
||||
virtual void set_value(boost::any value, bool change_event) = 0;
|
||||
|
||||
/// Gets a boost::any representing this control.
|
||||
/// subclasses should overload with a specific version
|
||||
|
@ -72,6 +87,9 @@ public:
|
|||
virtual void enable() = 0;
|
||||
virtual void disable() = 0;
|
||||
|
||||
wxStaticText* m_Label = nullptr;
|
||||
wxButton* m_Undo_btn = nullptr;
|
||||
|
||||
/// Fires the enable or disable function, based on the input.
|
||||
inline void toggle(bool en) { en ? enable() : disable(); }
|
||||
|
||||
|
@ -85,7 +103,7 @@ public:
|
|||
virtual wxWindow* getWindow() { return nullptr; }
|
||||
|
||||
bool is_matched(std::string string, std::string pattern);
|
||||
boost::any get_value_by_opt_type(wxString str);
|
||||
boost::any get_value_by_opt_type(wxString str);
|
||||
|
||||
/// Factory method for generating new derived classes.
|
||||
template<class T>
|
||||
|
@ -116,13 +134,13 @@ public:
|
|||
void BUILD();
|
||||
wxWindow* window {nullptr};
|
||||
|
||||
virtual void set_value(std::string value) {
|
||||
m_disable_change_event = true;
|
||||
virtual void set_value(std::string value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxTextCtrl*>(window)->SetValue(wxString(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
virtual void set_value(boost::any value) {
|
||||
m_disable_change_event = true;
|
||||
virtual void set_value(boost::any value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxTextCtrl*>(window)->SetValue(boost::any_cast<wxString>(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
@ -143,19 +161,17 @@ public:
|
|||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const bool value) {
|
||||
m_disable_change_event = true;
|
||||
void set_value(const bool value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxCheckBox*>(window)->SetValue(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(boost::any value) {
|
||||
m_disable_change_event = true;
|
||||
void set_value(boost::any value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxCheckBox*>(window)->SetValue(boost::any_cast<bool>(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
boost::any get_value() override {
|
||||
return boost::any(dynamic_cast<wxCheckBox*>(window)->GetValue());
|
||||
}
|
||||
boost::any get_value() override;
|
||||
|
||||
void enable() override { dynamic_cast<wxCheckBox*>(window)->Enable(); }
|
||||
void disable() override { dynamic_cast<wxCheckBox*>(window)->Disable(); }
|
||||
|
@ -173,13 +189,13 @@ public:
|
|||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const std::string value) {
|
||||
m_disable_change_event = true;
|
||||
void set_value(const std::string value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxSpinCtrl*>(window)->SetValue(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(boost::any value) {
|
||||
m_disable_change_event = true;
|
||||
void set_value(boost::any value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxSpinCtrl*>(window)->SetValue(boost::any_cast<int>(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
@ -202,8 +218,8 @@ public:
|
|||
void BUILD() override;
|
||||
|
||||
void set_selection();
|
||||
void set_value(const std::string value);
|
||||
void set_value(boost::any value);
|
||||
void set_value(const std::string value, bool change_event = false);
|
||||
void set_value(boost::any value, bool change_event = false);
|
||||
void set_values(const std::vector<std::string> values);
|
||||
boost::any get_value() override;
|
||||
|
||||
|
@ -221,13 +237,13 @@ public:
|
|||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const std::string value) {
|
||||
m_disable_change_event = true;
|
||||
void set_value(const std::string value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxColourPickerCtrl*>(window)->SetColour(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(boost::any value) {
|
||||
m_disable_change_event = true;
|
||||
void set_value(boost::any value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxColourPickerCtrl*>(window)->SetColour(boost::any_cast<wxString>(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
@ -251,8 +267,8 @@ public:
|
|||
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const Pointf value);
|
||||
void set_value(boost::any value);
|
||||
void set_value(const Pointf value, bool change_event = false);
|
||||
void set_value(boost::any value, bool change_event = false);
|
||||
boost::any get_value() override;
|
||||
|
||||
void enable() override {
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#if __APPLE__
|
||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||
|
@ -45,6 +45,7 @@
|
|||
#include "AppConfig.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "Preferences.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
|
@ -172,11 +173,15 @@ wxApp *g_wxApp = nullptr;
|
|||
wxFrame *g_wxMainFrame = nullptr;
|
||||
wxNotebook *g_wxTabPanel = nullptr;
|
||||
AppConfig *g_AppConfig = nullptr;
|
||||
PresetBundle *g_PresetBundle= nullptr;
|
||||
|
||||
std::vector<Tab *> g_tabs_list;
|
||||
|
||||
wxLocale* g_wxLocale;
|
||||
|
||||
std::shared_ptr<ConfigOptionsGroup> m_optgroup;
|
||||
double m_brim_width = 0.0;
|
||||
|
||||
void set_wxapp(wxApp *app)
|
||||
{
|
||||
g_wxApp = app;
|
||||
|
@ -197,6 +202,11 @@ void set_app_config(AppConfig *app_config)
|
|||
g_AppConfig = app_config;
|
||||
}
|
||||
|
||||
void set_preset_bundle(PresetBundle *preset_bundle)
|
||||
{
|
||||
g_PresetBundle = preset_bundle;
|
||||
}
|
||||
|
||||
std::vector<Tab *>& get_tabs_list()
|
||||
{
|
||||
return g_tabs_list;
|
||||
|
@ -240,6 +250,7 @@ bool select_language(wxArrayString & names,
|
|||
g_wxLocale->Init(identifiers[index]);
|
||||
g_wxLocale->AddCatalogLookupPathPrefix(wxPathOnly(localization_dir()));
|
||||
g_wxLocale->AddCatalog(g_wxApp->GetAppName());
|
||||
wxSetlocale(LC_NUMERIC, "C");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -268,6 +279,7 @@ bool load_language()
|
|||
g_wxLocale->Init(identifiers[i]);
|
||||
g_wxLocale->AddCatalogLookupPathPrefix(wxPathOnly(localization_dir()));
|
||||
g_wxLocale->AddCatalog(g_wxApp->GetAppName());
|
||||
wxSetlocale(LC_NUMERIC, "C");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -346,26 +358,17 @@ void open_preferences_dialog(int event_preferences)
|
|||
dlg->ShowModal();
|
||||
}
|
||||
|
||||
void create_preset_tabs(PresetBundle *preset_bundle,
|
||||
bool no_controller, bool is_disabled_button_browse, bool is_user_agent,
|
||||
int event_value_change, int event_presets_changed,
|
||||
int event_button_browse, int event_button_test)
|
||||
void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed)
|
||||
{
|
||||
add_created_tab(new TabPrint (g_wxTabPanel, no_controller), preset_bundle);
|
||||
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),
|
||||
preset_bundle);
|
||||
add_created_tab(new TabPrint (g_wxTabPanel, no_controller));
|
||||
add_created_tab(new TabFilament (g_wxTabPanel, no_controller));
|
||||
add_created_tab(new TabPrinter (g_wxTabPanel, no_controller));
|
||||
for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) {
|
||||
Tab *tab = dynamic_cast<Tab*>(g_wxTabPanel->GetPage(i));
|
||||
if (! tab)
|
||||
continue;
|
||||
tab->set_event_value_change(wxEventType(event_value_change));
|
||||
tab->set_event_presets_changed(wxEventType(event_presets_changed));
|
||||
if (tab->name() == "printer"){
|
||||
TabPrinter* tab_printer = static_cast<TabPrinter*>(tab);
|
||||
tab_printer->set_event_button_browse(wxEventType(event_button_browse));
|
||||
tab_printer->set_event_button_test(wxEventType(event_button_test));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -419,9 +422,13 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
|
|||
config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast<std::string>(value)));
|
||||
break;
|
||||
case coStrings:{
|
||||
if (opt_key.compare("compatible_printers") == 0){
|
||||
if (opt_key.compare("compatible_printers") == 0 ||
|
||||
config.def()->get(opt_key)->gui_flags.compare("serialized") == 0){
|
||||
config.option<ConfigOptionStrings>(opt_key)->values.resize(0);
|
||||
for (auto el : boost::any_cast<std::vector<std::string>>(value))
|
||||
std::vector<std::string> values = boost::any_cast<std::vector<std::string>>(value);
|
||||
if (values.size() == 1 && values[0] == "")
|
||||
break;
|
||||
for (auto el : values)
|
||||
config.option<ConfigOptionStrings>(opt_key)->values.push_back(el);
|
||||
}
|
||||
else{
|
||||
|
@ -434,7 +441,7 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
|
|||
config.set_key_value(opt_key, new ConfigOptionBool(boost::any_cast<bool>(value)));
|
||||
break;
|
||||
case coBools:{
|
||||
ConfigOptionBools* vec_new = new ConfigOptionBools{ boost::any_cast<bool>(value) };
|
||||
ConfigOptionBools* vec_new = new ConfigOptionBools{ (bool)boost::any_cast<unsigned char>(value) };
|
||||
config.option<ConfigOptionBools>(opt_key)->set_at(vec_new, opt_index, 0);
|
||||
break;}
|
||||
case coInt:
|
||||
|
@ -458,9 +465,8 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
|
|||
}
|
||||
break;
|
||||
case coPoints:{
|
||||
ConfigOptionPoints points;
|
||||
points.values = boost::any_cast<std::vector<Pointf>>(value);
|
||||
config.set_key_value(opt_key, new ConfigOptionPoints(points));
|
||||
ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast<Pointf>(value) };
|
||||
config.option<ConfigOptionPoints>(opt_key)->set_at(vec_new, opt_index, 0);
|
||||
}
|
||||
break;
|
||||
case coNone:
|
||||
|
@ -475,9 +481,9 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
|
|||
}
|
||||
}
|
||||
|
||||
void add_created_tab(Tab* panel, PresetBundle *preset_bundle)
|
||||
void add_created_tab(Tab* panel)
|
||||
{
|
||||
panel->create_preset_tab(preset_bundle);
|
||||
panel->create_preset_tab(g_PresetBundle);
|
||||
|
||||
// Load the currently selected preset into the GUI, update the preset selection box.
|
||||
panel->load_current_preset();
|
||||
|
@ -505,6 +511,11 @@ wxApp* get_app(){
|
|||
return g_wxApp;
|
||||
}
|
||||
|
||||
wxColour* get_modified_label_clr()
|
||||
{
|
||||
return new wxColour(253, 88, 0);
|
||||
}
|
||||
|
||||
void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value)
|
||||
{
|
||||
if (comboCtrl == nullptr)
|
||||
|
@ -562,15 +573,117 @@ AppConfig* get_app_config()
|
|||
return g_AppConfig;
|
||||
}
|
||||
|
||||
wxString L_str(std::string str)
|
||||
wxString L_str(const 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)
|
||||
wxString from_u8(const std::string &str)
|
||||
{
|
||||
return wxString::FromUTF8(str.c_str());
|
||||
}
|
||||
|
||||
|
||||
void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer)
|
||||
{
|
||||
DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config;
|
||||
m_optgroup = std::make_shared<ConfigOptionsGroup>(parent, "", config);
|
||||
const wxArrayInt& ar = preset_sizer->GetColWidths();
|
||||
m_optgroup->label_width = ar.IsEmpty() ? 100 : ar.front();
|
||||
m_optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){
|
||||
TabPrint* tab_print = nullptr;
|
||||
for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) {
|
||||
Tab *tab = dynamic_cast<Tab*>(g_wxTabPanel->GetPage(i));
|
||||
if (!tab)
|
||||
continue;
|
||||
if (tab->name() == "print"){
|
||||
tab_print = static_cast<TabPrint*>(tab);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tab_print == nullptr)
|
||||
return;
|
||||
|
||||
if (opt_key == "fill_density"){
|
||||
value = m_optgroup->get_config_value(*config, opt_key);
|
||||
tab_print->set_value(opt_key, value);
|
||||
tab_print->update();
|
||||
}
|
||||
else{
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
if (opt_key == "brim"){
|
||||
double new_val;
|
||||
double brim_width = config->opt_float("brim_width");
|
||||
if (boost::any_cast<bool>(value) == true)
|
||||
{
|
||||
new_val = m_brim_width == 0.0 ? 10 :
|
||||
m_brim_width < 0.0 ? m_brim_width * (-1) :
|
||||
m_brim_width;
|
||||
}
|
||||
else{
|
||||
m_brim_width = brim_width * (-1);
|
||||
new_val = 0;
|
||||
}
|
||||
new_conf.set_key_value("brim_width", new ConfigOptionFloat(new_val));
|
||||
}
|
||||
else{ //(opt_key == "support")
|
||||
const wxString& selection = boost::any_cast<wxString>(value);
|
||||
|
||||
auto support_material = selection == _("None") ? false : true;
|
||||
new_conf.set_key_value("support_material", new ConfigOptionBool(support_material));
|
||||
|
||||
if (selection == _("Everywhere"))
|
||||
new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false));
|
||||
else if (selection == _("Support on build plate only"))
|
||||
new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(true));
|
||||
}
|
||||
tab_print->load_config(new_conf);
|
||||
}
|
||||
|
||||
tab_print->update_dirty();
|
||||
};
|
||||
|
||||
const int width = 250;
|
||||
Option option = m_optgroup->get_option("fill_density");
|
||||
option.opt.sidetext = "";
|
||||
option.opt.width = width;
|
||||
m_optgroup->append_single_option_line(option);
|
||||
|
||||
ConfigOptionDef def;
|
||||
|
||||
def.label = L("Support");
|
||||
def.type = coStrings;
|
||||
def.gui_type = "select_open";
|
||||
def.tooltip = L("Select what kind of support do you need");
|
||||
def.enum_labels.push_back(L("None"));
|
||||
def.enum_labels.push_back(L("Support on build plate only"));
|
||||
def.enum_labels.push_back(L("Everywhere"));
|
||||
std::string selection = !config->opt_bool("support_material") ?
|
||||
"None" :
|
||||
config->opt_bool("support_material_buildplate_only") ?
|
||||
"Support on build plate only" :
|
||||
"Everywhere";
|
||||
def.default_value = new ConfigOptionStrings { selection };
|
||||
option = Option(def, "support");
|
||||
option.opt.width = width;
|
||||
m_optgroup->append_single_option_line(option);
|
||||
|
||||
m_brim_width = config->opt_float("brim_width");
|
||||
def.label = L("Brim");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("This flag enables the brim that will be printed around each object on the first layer.");
|
||||
def.gui_type = "";
|
||||
def.default_value = new ConfigOptionBool{ m_brim_width > 0.0 ? true : false };
|
||||
option = Option(def, "brim");
|
||||
m_optgroup->append_single_option_line(option);
|
||||
|
||||
sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxBottom, 1);
|
||||
}
|
||||
|
||||
ConfigOptionsGroup* get_optgroup()
|
||||
{
|
||||
return m_optgroup.get();
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "Config.hpp"
|
||||
|
||||
class wxApp;
|
||||
class wxWindow;
|
||||
class wxFrame;
|
||||
class wxWindow;
|
||||
class wxMenuBar;
|
||||
|
@ -14,6 +15,9 @@ class wxComboCtrl;
|
|||
class wxString;
|
||||
class wxArrayString;
|
||||
class wxArrayLong;
|
||||
class wxColour;
|
||||
class wxBoxSizer;
|
||||
class wxFlexGridSizer;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -35,11 +39,12 @@ class TabIface;
|
|||
#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
|
||||
#define MIN_BUF_LENGTH_FOR_L 512
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class Tab;
|
||||
class ConfigOptionsGroup;
|
||||
// Map from an file_type name to full file wildcard name.
|
||||
typedef std::map<std::string, std::string> t_file_wild_card;
|
||||
inline t_file_wild_card& get_file_wild_card() {
|
||||
|
@ -69,9 +74,11 @@ void set_wxapp(wxApp *app);
|
|||
void set_main_frame(wxFrame *main_frame);
|
||||
void set_tab_panel(wxNotebook *tab_panel);
|
||||
void set_app_config(AppConfig *app_config);
|
||||
void set_preset_bundle(PresetBundle *preset_bundle);
|
||||
|
||||
AppConfig* get_app_config();
|
||||
wxApp* get_app();
|
||||
wxColour* get_modified_label_clr();
|
||||
|
||||
void add_debug_menu(wxMenuBar *menu, int event_language_change);
|
||||
|
||||
|
@ -79,14 +86,11 @@ void add_debug_menu(wxMenuBar *menu, int event_language_change);
|
|||
void open_preferences_dialog(int event_preferences);
|
||||
|
||||
// Create a new preset tab (print, filament and printer),
|
||||
void create_preset_tabs(PresetBundle *preset_bundle,
|
||||
bool no_controller, bool is_disabled_button_browse, bool is_user_agent,
|
||||
int event_value_change, int event_presets_changed,
|
||||
int event_button_browse, int event_button_test);
|
||||
void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed);
|
||||
TabIface* get_preset_tab_iface(char *name);
|
||||
|
||||
// add it at the end of the tab panel.
|
||||
void add_created_tab(Tab* panel, PresetBundle *preset_bundle);
|
||||
void add_created_tab(Tab* panel);
|
||||
// Change option value in config
|
||||
void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, boost::any value, int opt_index = 0);
|
||||
|
||||
|
@ -117,9 +121,14 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string
|
|||
int combochecklist_get_flags(wxComboCtrl* comboCtrl);
|
||||
|
||||
// Return translated std::string as a wxString
|
||||
wxString L_str(std::string str);
|
||||
wxString L_str(const std::string &str);
|
||||
// Return wxString from std::string in UTF8
|
||||
wxString from_u8(std::string str);
|
||||
wxString from_u8(const std::string &str);
|
||||
|
||||
|
||||
void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer);
|
||||
|
||||
ConfigOptionsGroup* get_optgroup();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,18 +3,21 @@
|
|||
|
||||
#include <utility>
|
||||
#include <wx/numformatter.h>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
const t_field& OptionsGroup::build_field(const Option& opt) {
|
||||
return build_field(opt.opt_id, opt.opt);
|
||||
const t_field& OptionsGroup::build_field(const Option& opt, wxStaticText* label/* = nullptr*/) {
|
||||
return build_field(opt.opt_id, opt.opt, label);
|
||||
}
|
||||
const t_field& OptionsGroup::build_field(const t_config_option_key& id) {
|
||||
const t_field& OptionsGroup::build_field(const t_config_option_key& id, wxStaticText* label/* = nullptr*/) {
|
||||
const ConfigOptionDef& opt = m_options.at(id).opt;
|
||||
return build_field(id, opt);
|
||||
return build_field(id, opt, label);
|
||||
}
|
||||
|
||||
const t_field& OptionsGroup::build_field(const t_config_option_key& id, const ConfigOptionDef& opt) {
|
||||
const t_field& OptionsGroup::build_field(const t_config_option_key& id, const ConfigOptionDef& opt, wxStaticText* label/* = nullptr*/) {
|
||||
// Check the gui_type field first, fall through
|
||||
// is the normal type.
|
||||
if (opt.gui_type.compare("select") == 0) {
|
||||
|
@ -72,7 +75,16 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
|
|||
this->on_kill_focus();
|
||||
};
|
||||
field->m_parent = parent();
|
||||
// assign function objects for callbacks, etc.
|
||||
|
||||
//! Label to change background color, when option is modified
|
||||
field->m_Label = label;
|
||||
field->m_back_to_initial_value = [this](std::string opt_id){
|
||||
if (!this->m_disabled)
|
||||
this->back_to_initial_value(opt_id);
|
||||
};
|
||||
if (!m_is_tab_opt) field->m_Undo_btn->Hide();
|
||||
|
||||
// assign function objects for callbacks, etc.
|
||||
return field;
|
||||
}
|
||||
|
||||
|
@ -100,6 +112,7 @@ void OptionsGroup::append_line(const Line& line) {
|
|||
const auto& option = option_set.front();
|
||||
const auto& field = build_field(option);
|
||||
|
||||
sizer->Add(field->m_Undo_btn);
|
||||
if (is_window_field(field))
|
||||
sizer->Add(field->getWindow(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5);
|
||||
if (is_sizer_field(field))
|
||||
|
@ -110,8 +123,9 @@ void OptionsGroup::append_line(const Line& line) {
|
|||
auto grid_sizer = m_grid_sizer;
|
||||
|
||||
// Build a label if we have it
|
||||
wxStaticText* label=nullptr;
|
||||
if (label_width != 0) {
|
||||
auto label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ":"),
|
||||
label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ":"),
|
||||
wxDefaultPosition, wxSize(label_width, -1));
|
||||
label->SetFont(label_font);
|
||||
label->Wrap(label_width); // avoid a Linux/GTK bug
|
||||
|
@ -128,25 +142,24 @@ void OptionsGroup::append_line(const Line& line) {
|
|||
}
|
||||
|
||||
// if we have a single option with no sidetext just add it directly to the grid sizer
|
||||
if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 &&
|
||||
option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) {
|
||||
const auto& option = option_set.front();
|
||||
const auto& field = build_field(option);
|
||||
//! std::cerr << "single option, no sidetext.\n";
|
||||
//! std::cerr << "field parent is not null?: " << (field->parent != nullptr) << "\n";
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
grid_sizer->Add(sizer, 0, wxEXPAND | wxALL, 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) {
|
||||
const auto& option = option_set.front();
|
||||
const auto& field = build_field(option, label);
|
||||
|
||||
if (is_window_field(field))
|
||||
grid_sizer->Add(field->getWindow(), 0, (option.opt.full_width ? wxEXPAND : 0) |
|
||||
sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL);
|
||||
if (is_window_field(field))
|
||||
sizer->Add(field->getWindow(), 0, (option.opt.full_width ? wxEXPAND : 0) |
|
||||
wxBOTTOM | wxTOP | wxALIGN_CENTER_VERTICAL, wxOSX ? 0 : 2);
|
||||
if (is_sizer_field(field))
|
||||
grid_sizer->Add(field->getSizer(), 0, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
|
||||
return;
|
||||
}
|
||||
if (is_sizer_field(field))
|
||||
sizer->Add(field->getSizer(), 0, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're here, we have more than one option or a single option with sidetext
|
||||
// so we need a horizontal sizer to arrange these things
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
grid_sizer->Add(sizer, 0, wxEXPAND | wxALL, 0);
|
||||
for (auto opt : option_set) {
|
||||
ConfigOptionDef option = opt.opt;
|
||||
// add label if any
|
||||
|
@ -156,14 +169,15 @@ void OptionsGroup::append_line(const Line& line) {
|
|||
// 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);
|
||||
sizer->Add(field_label, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
label = new wxStaticText(parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize);
|
||||
label->SetFont(label_font);
|
||||
sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
}
|
||||
|
||||
// add field
|
||||
const Option& opt_ref = opt;
|
||||
auto& field = build_field(opt_ref);
|
||||
auto& field = build_field(opt_ref, label);
|
||||
sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
is_sizer_field(field) ?
|
||||
sizer->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) :
|
||||
sizer->Add(field->getWindow(), 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
|
@ -244,6 +258,12 @@ void ConfigOptionsGroup::on_change_OG(t_config_option_key opt_id, boost::any val
|
|||
// # Currently used for the post_process config value only.
|
||||
// my @values = split / ; / , $field_value;
|
||||
// $self->config->set($opt_key, \@values);
|
||||
std::string str = boost::any_cast<std::string>(value);
|
||||
if (str.back() == ';')
|
||||
str.pop_back();
|
||||
std::vector<std::string> values;
|
||||
boost::split(values, str, boost::is_any_of(";"));
|
||||
change_opt_value(*m_config, opt_key, values);
|
||||
}
|
||||
else {
|
||||
if (opt_index == -1) {
|
||||
|
@ -263,6 +283,30 @@ void ConfigOptionsGroup::on_change_OG(t_config_option_key opt_id, boost::any val
|
|||
OptionsGroup::on_change_OG(opt_id, value); //!? Why doing this
|
||||
}
|
||||
|
||||
void ConfigOptionsGroup::back_to_initial_value(const std::string opt_key)
|
||||
{
|
||||
if (m_get_initial_config == nullptr)
|
||||
return;
|
||||
DynamicPrintConfig config = m_get_initial_config();
|
||||
boost::any value;
|
||||
if (opt_key == "extruders_count"){
|
||||
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter"));
|
||||
value = int(nozzle_diameter->values.size());
|
||||
}
|
||||
else if (m_opt_map.find(opt_key) != m_opt_map.end())
|
||||
{
|
||||
auto opt_id = m_opt_map.find(opt_key)->first;
|
||||
std::string opt_short_key = m_opt_map.at(opt_id).first;
|
||||
int opt_index = m_opt_map.at(opt_id).second;
|
||||
value = get_config_value(config, opt_short_key, opt_index);
|
||||
}
|
||||
else
|
||||
value = get_config_value(config, opt_key);
|
||||
|
||||
set_value(opt_key, value);
|
||||
on_change_OG(opt_key, get_value(opt_key));
|
||||
}
|
||||
|
||||
void ConfigOptionsGroup::reload_config(){
|
||||
for (std::map< std::string, std::pair<std::string, int> >::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) {
|
||||
auto opt_id = it->first;
|
||||
|
@ -323,7 +367,7 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std:
|
|||
double val = opt->type == coFloats ?
|
||||
config.opt_float(opt_key, idx) :
|
||||
opt->type == coFloat ? config.opt_float(opt_key) :
|
||||
config.option<ConfigOptionPercents>(opt_key)->values.at(idx);
|
||||
config.option<ConfigOptionPercents>(opt_key)->get_at(idx);
|
||||
ret = double_to_string(val);
|
||||
}
|
||||
break;
|
||||
|
@ -333,6 +377,12 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std:
|
|||
case coStrings:
|
||||
if (config.option<ConfigOptionStrings>(opt_key)->values.empty())
|
||||
ret = text_value;
|
||||
else if (opt->gui_flags.compare("serialized") == 0){
|
||||
std::vector<std::string> values = config.option<ConfigOptionStrings>(opt_key)->values;
|
||||
for (auto el : values)
|
||||
text_value += el + ";";
|
||||
ret = text_value;
|
||||
}
|
||||
else
|
||||
ret = static_cast<wxString>(config.opt_string(opt_key, static_cast<unsigned int>(idx)));
|
||||
break;
|
||||
|
@ -363,10 +413,8 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std:
|
|||
ret = static_cast<int>(config.option<ConfigOptionEnum<SeamPosition>>(opt_key)->value);
|
||||
}
|
||||
break;
|
||||
case coPoints:{
|
||||
const auto &value = *config.option<ConfigOptionPoints>(opt_key);
|
||||
ret = value.values.at(idx);
|
||||
}
|
||||
case coPoints:
|
||||
ret = config.option<ConfigOptionPoints>(opt_key)->get_at(idx);
|
||||
break;
|
||||
case coNone:
|
||||
default:
|
||||
|
@ -376,6 +424,9 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std:
|
|||
}
|
||||
|
||||
Field* ConfigOptionsGroup::get_fieldc(t_config_option_key opt_key, int opt_index){
|
||||
Field* field = get_field(opt_key);
|
||||
if (field != nullptr)
|
||||
return field;
|
||||
std::string opt_id = "";
|
||||
for (std::map< std::string, std::pair<std::string, int> >::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) {
|
||||
if (opt_key == m_opt_map.at(it->first).first && opt_index == m_opt_map.at(it->first).second){
|
||||
|
|
|
@ -27,6 +27,9 @@ namespace Slic3r { namespace GUI {
|
|||
using widget_t = std::function<wxSizer*(wxWindow*)>;//!std::function<wxWindow*(wxWindow*)>;
|
||||
using column_t = std::function<wxSizer*(const Line&)>;
|
||||
|
||||
//auto default_label_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); //GetSystemColour
|
||||
//auto modified_label_clr = *new wxColour(254, 189, 101);
|
||||
|
||||
/// Wraps a ConfigOptionDef and adds function object for creating a side_widget.
|
||||
struct Option {
|
||||
ConfigOptionDef opt { ConfigOptionDef() };
|
||||
|
@ -75,6 +78,7 @@ public:
|
|||
wxSizer* sizer {nullptr};
|
||||
column_t extra_column {nullptr};
|
||||
t_change m_on_change {nullptr};
|
||||
std::function<DynamicPrintConfig()> m_get_initial_config{ nullptr };
|
||||
|
||||
wxFont sidetext_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) };
|
||||
wxFont label_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) };
|
||||
|
@ -93,9 +97,9 @@ public:
|
|||
if (m_fields.find(id) == m_fields.end()) return nullptr;
|
||||
return m_fields.at(id).get();
|
||||
}
|
||||
bool set_value(t_config_option_key id, boost::any value) {
|
||||
bool set_value(t_config_option_key id, boost::any value, bool change_event = false) {
|
||||
if (m_fields.find(id) == m_fields.end()) return false;
|
||||
m_fields.at(id)->set_value(value);
|
||||
m_fields.at(id)->set_value(value, change_event);
|
||||
return true;
|
||||
}
|
||||
boost::any get_value(t_config_option_key id) {
|
||||
|
@ -109,8 +113,8 @@ public:
|
|||
inline void enable() { for (auto& field : m_fields) field.second->enable(); }
|
||||
inline void disable() { for (auto& field : m_fields) field.second->disable(); }
|
||||
|
||||
OptionsGroup(wxWindow* _parent, wxString title) :
|
||||
m_parent(_parent), title(title) {
|
||||
OptionsGroup(wxWindow* _parent, wxString title, bool is_tab_opt=false) :
|
||||
m_parent(_parent), title(title), m_is_tab_opt(is_tab_opt), staticbox(title!="") {
|
||||
sizer = (staticbox ? new wxStaticBoxSizer(new wxStaticBox(_parent, wxID_ANY, title), wxVERTICAL) : new wxBoxSizer(wxVERTICAL));
|
||||
auto num_columns = 1U;
|
||||
if (label_width != 0) num_columns++;
|
||||
|
@ -132,22 +136,25 @@ protected:
|
|||
t_optionfield_map m_fields;
|
||||
bool m_disabled {false};
|
||||
wxGridSizer* m_grid_sizer {nullptr};
|
||||
// "true" if option is created in preset tabs
|
||||
bool m_is_tab_opt{ false };
|
||||
|
||||
/// Generate a wxSizer or wxWindow from a configuration option
|
||||
/// Precondition: opt resolves to a known ConfigOption
|
||||
/// Postcondition: fields contains a wx gui object.
|
||||
const t_field& build_field(const t_config_option_key& id, const ConfigOptionDef& opt);
|
||||
const t_field& build_field(const t_config_option_key& id);
|
||||
const t_field& build_field(const Option& opt);
|
||||
const t_field& build_field(const t_config_option_key& id, const ConfigOptionDef& opt, wxStaticText* label = nullptr);
|
||||
const t_field& build_field(const t_config_option_key& id, wxStaticText* label = nullptr);
|
||||
const t_field& build_field(const Option& opt, wxStaticText* label = nullptr);
|
||||
|
||||
virtual void on_kill_focus (){};
|
||||
virtual void on_change_OG(t_config_option_key opt_id, boost::any value);
|
||||
virtual void back_to_initial_value(const std::string opt_key){};
|
||||
};
|
||||
|
||||
class ConfigOptionsGroup: public OptionsGroup {
|
||||
public:
|
||||
ConfigOptionsGroup(wxWindow* parent, wxString title, DynamicPrintConfig* _config = nullptr) :
|
||||
OptionsGroup(parent, title), m_config(_config) {}
|
||||
ConfigOptionsGroup(wxWindow* parent, wxString title, DynamicPrintConfig* _config = nullptr, bool is_tab_opt = false) :
|
||||
OptionsGroup(parent, title, is_tab_opt), m_config(_config) {}
|
||||
|
||||
/// reference to libslic3r config, non-owning pointer (?).
|
||||
DynamicPrintConfig* m_config {nullptr};
|
||||
|
@ -169,10 +176,8 @@ public:
|
|||
}
|
||||
|
||||
void on_change_OG(t_config_option_key opt_id, boost::any value) override;
|
||||
void on_kill_focus() override
|
||||
{
|
||||
reload_config();
|
||||
}
|
||||
void back_to_initial_value(const std::string opt_key) override;
|
||||
void on_kill_focus() override{ reload_config();}
|
||||
void reload_config();
|
||||
boost::any config_value(std::string opt_key, int opt_index, bool deserialize);
|
||||
// return option value from config
|
||||
|
|
|
@ -224,9 +224,9 @@ const std::vector<std::string>& 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"
|
||||
"between_objects_gcode", "printer_notes", "max_print_height"
|
||||
};
|
||||
s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end());
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/locale.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <wx/dcmemory.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);
|
||||
}
|
||||
|
||||
// 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 §ion : 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()), §ion.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
|
||||
// of the local configuration directory.
|
||||
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;
|
||||
boost::nowide::ifstream ifs(path);
|
||||
pt::read_ini(ifs, tree);
|
||||
// Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed.
|
||||
flatten_configbundle_hierarchy(tree);
|
||||
|
||||
// 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;
|
||||
|
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
#define MIN_BUF_LENGTH 4096
|
||||
std::string PresetHints::cooling_description(const Preset &preset)
|
||||
{
|
||||
std::string out;
|
||||
char buf[4096];
|
||||
char buf[MIN_BUF_LENGTH/*4096*/];
|
||||
if (preset.config.opt_bool("cooling", 0)) {
|
||||
int slowdown_below_layer_time = preset.config.opt_int("slowdown_below_layer_time", 0);
|
||||
int min_fan_speed = preset.config.opt_int("min_fan_speed", 0);
|
||||
|
@ -220,7 +221,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
|
|||
+ _CHB(L(" with a volumetric rate "));
|
||||
if (limited_by_max_volumetric_speed)
|
||||
max_flow = max_volumetric_speed;
|
||||
char buf[2048];
|
||||
char buf[MIN_BUF_LENGTH/*2048*/];
|
||||
sprintf(buf, _CHB(L("%3.2f mm³/s")), max_flow);
|
||||
out += buf;
|
||||
sprintf(buf, _CHB(L(" at filament speed %3.2f mm/s.")), max_flow / filament_crossection);
|
||||
|
@ -258,7 +259,7 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre
|
|||
|
||||
if (num_perimeters > 0) {
|
||||
int num_lines = std::min(num_perimeters * 2, 10);
|
||||
char buf[256];
|
||||
char buf[MIN_BUF_LENGTH/*256*/];
|
||||
sprintf(buf, _CHB(L("Recommended object thin wall thickness for layer height %.2f and ")), layer_height);
|
||||
out += buf;
|
||||
// Start with the width of two closely spaced
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
#include "PresetBundle.hpp"
|
||||
#include "PresetHints.hpp"
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
#include "slic3r/Utils/Http.hpp"
|
||||
#include "slic3r/Utils/OctoPrint.hpp"
|
||||
#include "BonjourDialog.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/button.h>
|
||||
|
@ -14,6 +17,7 @@
|
|||
#include <wx/treectrl.h>
|
||||
#include <wx/imaglist.h>
|
||||
#include <wx/settings.h>
|
||||
#include <wx/filedlg.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
|
@ -33,19 +37,23 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle)
|
|||
|
||||
// preset chooser
|
||||
m_presets_choice = new wxBitmapComboBox(panel, wxID_ANY, "", wxDefaultPosition, wxSize(270, -1), 0, 0,wxCB_READONLY);
|
||||
const wxBitmap* bmp = new wxBitmap(from_u8(Slic3r::var("flag-green-icon.png")), wxBITMAP_TYPE_PNG);
|
||||
|
||||
auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
|
||||
//buttons
|
||||
wxBitmap bmpMenu;
|
||||
bmpMenu = wxBitmap(from_u8(Slic3r::var("disk.png")), wxBITMAP_TYPE_PNG);
|
||||
m_btn_save_preset = new wxBitmapButton(panel, wxID_ANY, bmpMenu, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
|
||||
if (wxMSW) m_btn_save_preset->SetBackgroundColour(color);
|
||||
bmpMenu = wxBitmap(from_u8(Slic3r::var("delete.png")), wxBITMAP_TYPE_PNG);
|
||||
m_btn_delete_preset = new wxBitmapButton(panel, wxID_ANY, bmpMenu, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
|
||||
if (wxMSW) m_btn_delete_preset->SetBackgroundColour(color);
|
||||
|
||||
m_show_incompatible_presets = false;
|
||||
m_bmp_show_incompatible_presets = new wxBitmap(from_u8(Slic3r::var("flag-red-icon.png")), wxBITMAP_TYPE_PNG);
|
||||
m_bmp_hide_incompatible_presets = new wxBitmap(from_u8(Slic3r::var("flag-green-icon.png")), wxBITMAP_TYPE_PNG);
|
||||
m_btn_hide_incompatible_presets = new wxBitmapButton(panel, wxID_ANY, *m_bmp_hide_incompatible_presets, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
|
||||
if (wxMSW) m_btn_hide_incompatible_presets->SetBackgroundColour(color);
|
||||
|
||||
m_btn_save_preset->SetToolTip(_(L("Save current ")) + m_title);
|
||||
m_btn_delete_preset->SetToolTip(_(L("Delete this preset")));
|
||||
|
@ -93,6 +101,7 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle)
|
|||
if (selected_item >= 0){
|
||||
std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data();
|
||||
select_preset(selected_string);
|
||||
update_changed_ui();
|
||||
}
|
||||
}));
|
||||
|
||||
|
@ -134,11 +143,93 @@ PageShp Tab::add_options_page(wxString title, std::string icon, bool is_extruder
|
|||
return page;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void add_correct_opts_to_dirty_options(const std::string &opt_key, std::vector<std::string> *vec, TabPrinter *tab)
|
||||
{
|
||||
auto opt_init = static_cast<T*>(tab->m_presets->get_selected_preset().config.option(opt_key));
|
||||
auto opt_cur = static_cast<T*>(tab->m_config->option(opt_key));
|
||||
int opt_init_max_id = opt_init->values.size()-1;
|
||||
for (int i = 0; i < opt_cur->values.size(); i++)
|
||||
{
|
||||
int init_id = i <= opt_init_max_id ? i : 0;
|
||||
if (opt_cur->values[i] != opt_init->values[init_id])
|
||||
vec->emplace_back(opt_key + "#" + std::to_string(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Update UI according to changes
|
||||
void Tab::update_changed_ui()
|
||||
{
|
||||
auto dirty_options = m_presets->current_dirty_options();
|
||||
|
||||
if (name() == "printer"){
|
||||
// Update dirty_options in case changes of Extruder's options
|
||||
TabPrinter* tab = static_cast<TabPrinter*>(this);
|
||||
std::vector<std::string> new_dirty;
|
||||
for (auto opt_key : dirty_options)
|
||||
{
|
||||
switch (m_config->option(opt_key)->type())
|
||||
{
|
||||
case coInts: add_correct_opts_to_dirty_options<ConfigOptionInts >(opt_key, &new_dirty, tab); break;
|
||||
case coBools: add_correct_opts_to_dirty_options<ConfigOptionBools >(opt_key, &new_dirty, tab); break;
|
||||
case coFloats: add_correct_opts_to_dirty_options<ConfigOptionFloats >(opt_key, &new_dirty, tab); break;
|
||||
case coStrings: add_correct_opts_to_dirty_options<ConfigOptionStrings >(opt_key, &new_dirty, tab); break;
|
||||
case coPercents:add_correct_opts_to_dirty_options<ConfigOptionPercents >(opt_key, &new_dirty, tab); break;
|
||||
case coPoints: add_correct_opts_to_dirty_options<ConfigOptionPoints >(opt_key, &new_dirty, tab); break;
|
||||
default: new_dirty.emplace_back(opt_key); break;
|
||||
}
|
||||
}
|
||||
|
||||
dirty_options.resize(0);
|
||||
dirty_options = new_dirty;
|
||||
if (tab->m_initial_extruders_count != tab->m_extruders_count){
|
||||
dirty_options.emplace_back("extruders_count");
|
||||
}
|
||||
}
|
||||
|
||||
// Add new dirty options to m_dirty_options
|
||||
for (auto opt_key : dirty_options){
|
||||
Field* field = get_field(opt_key);
|
||||
if (field != nullptr && find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()){
|
||||
if (field->m_Label != nullptr){
|
||||
field->m_Label->SetForegroundColour(*get_modified_label_clr());
|
||||
field->m_Label->Refresh(true);
|
||||
}
|
||||
field->m_Undo_btn->SetBitmap(wxBitmap(from_u8(wxMSW ? var("action_undo.png") : var("arrow_undo.png")), wxBITMAP_TYPE_PNG));
|
||||
field->m_is_modified_value = true;
|
||||
|
||||
m_dirty_options.push_back(opt_key);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete clear options from m_dirty_options
|
||||
for (auto i = 0; i < m_dirty_options.size(); ++i)
|
||||
{
|
||||
const std::string &opt_key = m_dirty_options[i];
|
||||
Field* field = get_field(opt_key);
|
||||
if (field != nullptr && find(dirty_options.begin(), dirty_options.end(), opt_key) == dirty_options.end())
|
||||
{
|
||||
field->m_Undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG));
|
||||
if (field->m_Label != nullptr){
|
||||
field->m_Label->SetForegroundColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
field->m_Label->Refresh(true);
|
||||
}
|
||||
field->m_is_modified_value = false;
|
||||
std::vector<std::string>::iterator itr = find(m_dirty_options.begin(), m_dirty_options.end(), opt_key);
|
||||
if (itr != m_dirty_options.end()){
|
||||
m_dirty_options.erase(itr);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the combo box label of the selected preset based on its "dirty" state,
|
||||
// comparing the selected preset config with $self->{config}.
|
||||
void Tab::update_dirty(){
|
||||
m_presets->update_dirty_ui(m_presets_choice);
|
||||
on_presets_changed();
|
||||
on_presets_changed();
|
||||
update_changed_ui();
|
||||
}
|
||||
|
||||
void Tab::update_tab_ui()
|
||||
|
@ -146,81 +237,13 @@ void Tab::update_tab_ui()
|
|||
m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
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<T>(opt_key)->values.size(); i++)
|
||||
if (config_new.option<T>(opt_key)->values[i] !=
|
||||
config_old.option<T>(opt_key)->values[i]){
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
return config_new.option<T>(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:
|
||||
value = config.option<ConfigOptionFloatOrPercent>(opt_key)->value;
|
||||
break;
|
||||
case coPercent:
|
||||
value = config.option<ConfigOptionPercent>(opt_key)->value;
|
||||
break;
|
||||
case coFloat:
|
||||
value = config.opt_float(opt_key);
|
||||
break;
|
||||
case coString:
|
||||
value = config.opt_string(opt_key);
|
||||
break;
|
||||
case coPercents:
|
||||
value = get_new_value<ConfigOptionPercents>(config, *m_config, opt_key, opt_index);
|
||||
break;
|
||||
case coFloats:
|
||||
value = get_new_value<ConfigOptionFloats>(config, *m_config, opt_key, opt_index);
|
||||
break;
|
||||
case coStrings:
|
||||
value = config.option<ConfigOptionStrings>(opt_key)->values.empty() ? "" :
|
||||
get_new_value<ConfigOptionStrings>(config, *m_config, opt_key, opt_index);
|
||||
break;
|
||||
case coBool:
|
||||
value = config.opt_bool(opt_key);
|
||||
break;
|
||||
case coBools:
|
||||
value = get_new_value<ConfigOptionBools>(config, *m_config, opt_key, opt_index);
|
||||
break;
|
||||
case coInt:
|
||||
value = config.opt_int(opt_key);
|
||||
break;
|
||||
case coInts:
|
||||
value = get_new_value<ConfigOptionInts>(config, *m_config, opt_key, opt_index);
|
||||
break;
|
||||
case coEnum:{
|
||||
if (opt_key.compare("external_fill_pattern") == 0 ||
|
||||
opt_key.compare("fill_pattern") == 0)
|
||||
value = config.option<ConfigOptionEnum<InfillPattern>>(opt_key)->value;
|
||||
else if (opt_key.compare("gcode_flavor") == 0)
|
||||
value = config.option<ConfigOptionEnum<GCodeFlavor>>(opt_key)->value;
|
||||
else if (opt_key.compare("support_material_pattern") == 0)
|
||||
value = config.option<ConfigOptionEnum<SupportMaterialPattern>>(opt_key)->value;
|
||||
else if (opt_key.compare("seam_position") == 0)
|
||||
value = config.option<ConfigOptionEnum<SeamPosition>>(opt_key)->value;
|
||||
}
|
||||
break;
|
||||
case coPoints:
|
||||
break;
|
||||
case coNone:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
change_opt_value(*m_config, opt_key, value, opt_index);
|
||||
m_config->set_key_value(opt_key, config.option(opt_key)->clone());
|
||||
modified = 1;
|
||||
}
|
||||
if (modified) {
|
||||
|
@ -291,6 +314,27 @@ void Tab::on_value_change(std::string opt_key, boost::any value)
|
|||
}
|
||||
g_wxMainFrame->ProcessWindowEvent(event);
|
||||
}
|
||||
if (opt_key == "fill_density")
|
||||
{
|
||||
value = get_optgroup()->get_config_value(*m_config, opt_key);
|
||||
get_optgroup()->set_value(opt_key, value);
|
||||
}
|
||||
if (opt_key == "support_material" || opt_key == "support_material_buildplate_only")
|
||||
{
|
||||
wxString new_selection = !m_config->opt_bool("support_material") ?
|
||||
_("None") :
|
||||
m_config->opt_bool("support_material_buildplate_only") ?
|
||||
_("Support on build plate only") :
|
||||
_("Everywhere");
|
||||
get_optgroup()->set_value("support", new_selection);
|
||||
}
|
||||
if (opt_key == "brim_width")
|
||||
{
|
||||
bool val = m_config->opt_float("brim_width") > 0.0 ? true : false;
|
||||
get_optgroup()->set_value("brim", val);
|
||||
}
|
||||
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
|
@ -308,6 +352,22 @@ void Tab::on_presets_changed()
|
|||
}
|
||||
}
|
||||
|
||||
void Tab::update_frequently_changed_parameters()
|
||||
{
|
||||
boost::any value = get_optgroup()->get_config_value(*m_config, "fill_density");
|
||||
get_optgroup()->set_value("fill_density", value);
|
||||
|
||||
wxString new_selection = !m_config->opt_bool("support_material") ?
|
||||
_("None") :
|
||||
m_config->opt_bool("support_material_buildplate_only") ?
|
||||
_("Support on build plate only") :
|
||||
_("Everywhere");
|
||||
get_optgroup()->set_value("support", new_selection);
|
||||
|
||||
bool val = m_config->opt_float("brim_width") > 0.0 ? true : false;
|
||||
get_optgroup()->set_value("brim", val);
|
||||
}
|
||||
|
||||
void Tab::reload_compatible_printers_widget()
|
||||
{
|
||||
bool has_any = !m_config->option<ConfigOptionStrings>("compatible_printers")->values.empty();
|
||||
|
@ -576,7 +636,8 @@ void TabPrint::update()
|
|||
DynamicPrintConfig new_conf = *m_config;
|
||||
if (dialog->ShowModal() == wxID_YES) {
|
||||
const auto &val = *m_config->option<ConfigOptionFloatOrPercent>("first_layer_height");
|
||||
new_conf.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.2, val.percent));
|
||||
auto percent = val.percent;
|
||||
new_conf.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.2, percent));
|
||||
|
||||
if (m_config->opt_float("layer_height") < 0.15) new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.15));
|
||||
if (m_config->opt_float("layer_height") > 0.35) new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.35));
|
||||
|
@ -674,13 +735,16 @@ void TabPrint::update()
|
|||
"\nShall I switch to rectilinear fill pattern?"));
|
||||
auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Infill")), wxICON_WARNING | wxYES | wxNO);
|
||||
DynamicPrintConfig new_conf = *m_config;
|
||||
double fill_density;
|
||||
if (dialog->ShowModal() == wxID_YES) {
|
||||
new_conf.set_key_value("fill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinear));
|
||||
new_conf.set_key_value("fill_density", new ConfigOptionPercent(100));
|
||||
fill_density = 100;
|
||||
}
|
||||
else
|
||||
new_conf.set_key_value("fill_density", new ConfigOptionPercent(40));
|
||||
fill_density = m_presets->get_selected_preset().config.option<ConfigOptionPercent>("fill_density")->value;
|
||||
new_conf.set_key_value("fill_density", new ConfigOptionPercent(fill_density));
|
||||
load_config(new_conf);
|
||||
on_value_change("fill_density", fill_density);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -943,14 +1007,14 @@ void TabPrinter::build()
|
|||
auto default_config = m_preset_bundle->full_config();
|
||||
|
||||
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(m_config->option("nozzle_diameter"));
|
||||
m_extruders_count = nozzle_diameter->values.size();
|
||||
m_initial_extruders_count = m_extruders_count = nozzle_diameter->values.size();
|
||||
|
||||
auto page = add_options_page(_(L("General")), "printer_empty.png");
|
||||
auto optgroup = page->new_optgroup(_(L("Size and coordinates")));
|
||||
|
||||
Line line{ _(L("Bed shape")), "" };
|
||||
line.widget = [this](wxWindow* parent){
|
||||
auto btn = new wxButton(parent, wxID_ANY, _(L(" Set "))+"\u2026", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
|
||||
auto btn = new wxButton(parent, wxID_ANY, _(L(" Set "))+"\u2026", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
|
||||
// btn->SetFont(Slic3r::GUI::small_font);
|
||||
btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG));
|
||||
|
||||
|
@ -968,7 +1032,8 @@ void TabPrinter::build()
|
|||
return sizer;
|
||||
};
|
||||
optgroup->append_line(line);
|
||||
optgroup->append_single_option_line("z_offset");
|
||||
optgroup->append_single_option_line("max_print_height");
|
||||
optgroup->append_single_option_line("z_offset");
|
||||
|
||||
optgroup = page->new_optgroup(_(L("Capabilities")));
|
||||
ConfigOptionDef def;
|
||||
|
@ -1041,39 +1106,18 @@ void TabPrinter::build()
|
|||
}
|
||||
|
||||
optgroup = page->new_optgroup(_(L("OctoPrint upload")));
|
||||
// # append two buttons to the Host line
|
||||
auto octoprint_host_browse = [this] (wxWindow* parent) {
|
||||
|
||||
auto octoprint_host_browse = [this, optgroup] (wxWindow* parent) {
|
||||
auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+"\u2026", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
// btn->SetFont($Slic3r::GUI::small_font);
|
||||
btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG));
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(btn);
|
||||
|
||||
if (m_is_disabled_button_browse)
|
||||
btn->Disable();
|
||||
|
||||
btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e){
|
||||
if (m_event_button_browse > 0){
|
||||
wxCommandEvent event(m_event_button_browse);
|
||||
event.SetString("Button BROWSE was clicked!");
|
||||
g_wxMainFrame->ProcessWindowEvent(event);
|
||||
btn->Bind(wxEVT_BUTTON, [this, parent, optgroup](wxCommandEvent e) {
|
||||
BonjourDialog dialog(parent);
|
||||
if (dialog.show_and_lookup()) {
|
||||
optgroup->set_value("octoprint_host", std::move(dialog.get_selected()), true);
|
||||
}
|
||||
// // # look for devices
|
||||
// auto entries;
|
||||
// {
|
||||
// my $res = Net::Bonjour->new('http');
|
||||
// $res->discover;
|
||||
// $entries = [$res->entries];
|
||||
// }
|
||||
// if (@{$entries}) {
|
||||
// my $dlg = Slic3r::GUI::BonjourBrowser->new($self, $entries);
|
||||
// $self->_load_key_value('octoprint_host', $dlg->GetValue . ":".$dlg->GetPort)
|
||||
// if $dlg->ShowModal == wxID_OK;
|
||||
// }
|
||||
// else {
|
||||
// auto msg_window = new wxMessageDialog(parent, "No Bonjour device found", "Device Browser", wxOK | wxICON_INFORMATION);
|
||||
// msg_window->ShowModal();
|
||||
// }
|
||||
});
|
||||
|
||||
return sizer;
|
||||
|
@ -1082,33 +1126,23 @@ void TabPrinter::build()
|
|||
auto octoprint_host_test = [this](wxWindow* parent) {
|
||||
auto btn = m_octoprint_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")),
|
||||
wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
|
||||
// btn->SetFont($Slic3r::GUI::small_font);
|
||||
btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG));
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(btn);
|
||||
|
||||
btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e) {
|
||||
if (m_event_button_test > 0){
|
||||
wxCommandEvent event(m_event_button_test);
|
||||
event.SetString("Button TEST was clicked!");
|
||||
g_wxMainFrame->ProcessWindowEvent(event);
|
||||
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {
|
||||
OctoPrint octoprint(m_config);
|
||||
wxString msg;
|
||||
if (octoprint.test(msg)) {
|
||||
show_info(this, _(L("Connection to OctoPrint works correctly.")), _(L("Success!")));
|
||||
} else {
|
||||
const auto text = wxString::Format("%s: %s\n\n%s",
|
||||
_(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required."))
|
||||
);
|
||||
show_error(this, text);
|
||||
}
|
||||
// my $ua = LWP::UserAgent->new;
|
||||
// $ua->timeout(10);
|
||||
//
|
||||
// my $res = $ua->get(
|
||||
// "http://".$self->{config}->octoprint_host . "/api/version",
|
||||
// 'X-Api-Key' = > $self->{config}->octoprint_apikey,
|
||||
// );
|
||||
// if ($res->is_success) {
|
||||
// show_info(parent, "Connection to OctoPrint works correctly.", "Success!");
|
||||
// }
|
||||
// else {
|
||||
// show_error(parent,
|
||||
// "I wasn't able to connect to OctoPrint (".$res->status_line . "). "
|
||||
// . "Check hostname and OctoPrint version (at least 1.1.0 is required).");
|
||||
// }
|
||||
});
|
||||
});
|
||||
|
||||
return sizer;
|
||||
};
|
||||
|
||||
|
@ -1118,6 +1152,45 @@ void TabPrinter::build()
|
|||
optgroup->append_line(host_line);
|
||||
optgroup->append_single_option_line("octoprint_apikey");
|
||||
|
||||
if (Http::ca_file_supported()) {
|
||||
|
||||
Line cafile_line = optgroup->create_single_option_line("octoprint_cafile");
|
||||
|
||||
auto octoprint_cafile_browse = [this, optgroup] (wxWindow* parent) {
|
||||
auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+"\u2026", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG));
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(btn);
|
||||
|
||||
btn->Bind(wxEVT_BUTTON, [this, optgroup] (wxCommandEvent e){
|
||||
static const auto filemasks = _(L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*"));
|
||||
wxFileDialog openFileDialog(this, _(L("Open CA certificate file")), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (openFileDialog.ShowModal() != wxID_CANCEL) {
|
||||
optgroup->set_value("octoprint_cafile", std::move(openFileDialog.GetPath()), true);
|
||||
}
|
||||
});
|
||||
|
||||
return sizer;
|
||||
};
|
||||
|
||||
cafile_line.append_widget(octoprint_cafile_browse);
|
||||
optgroup->append_line(cafile_line);
|
||||
|
||||
auto octoprint_cafile_hint = [this, optgroup] (wxWindow* parent) {
|
||||
auto txt = new wxStaticText(parent, wxID_ANY,
|
||||
_(L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate.")));
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(txt);
|
||||
return sizer;
|
||||
};
|
||||
|
||||
Line cafile_hint { "", "" };
|
||||
cafile_hint.full_width = 1;
|
||||
cafile_hint.widget = std::move(octoprint_cafile_hint);
|
||||
optgroup->append_line(cafile_hint);
|
||||
|
||||
}
|
||||
|
||||
optgroup = page->new_optgroup(_(L("Firmware")));
|
||||
optgroup->append_single_option_line("gcode_flavor");
|
||||
|
||||
|
@ -1188,6 +1261,7 @@ void TabPrinter::extruders_count_changed(size_t extruders_count){
|
|||
m_preset_bundle->printers.get_edited_preset().set_num_extruders(extruders_count);
|
||||
m_preset_bundle->update_multi_material_filament_presets();
|
||||
build_extruder_pages();
|
||||
reload_config();
|
||||
on_value_change("extruders_count", extruders_count);
|
||||
}
|
||||
|
||||
|
@ -1275,13 +1349,8 @@ void TabPrinter::update(){
|
|||
m_serial_test_btn->Disable();
|
||||
}
|
||||
|
||||
en = !m_config->opt_string("octoprint_host").empty();
|
||||
if ( en && m_is_user_agent)
|
||||
m_octoprint_host_test_btn->Enable();
|
||||
else
|
||||
m_octoprint_host_test_btn->Disable();
|
||||
get_field("octoprint_apikey")->toggle(en);
|
||||
|
||||
m_octoprint_host_test_btn->Enable(!m_config->opt_string("octoprint_host").empty());
|
||||
|
||||
bool have_multiple_extruders = m_extruders_count > 1;
|
||||
get_field("toolchange_gcode")->toggle(have_multiple_extruders);
|
||||
get_field("single_extruder_multi_material")->toggle(have_multiple_extruders);
|
||||
|
@ -1326,8 +1395,9 @@ void TabPrinter::update(){
|
|||
|
||||
DynamicPrintConfig new_conf = *m_config;
|
||||
if (dialog->ShowModal() == wxID_YES) {
|
||||
auto wipe = static_cast<ConfigOptionBools*>(m_config->option("wipe"));
|
||||
wipe->values[i] = 0;
|
||||
auto wipe = static_cast<ConfigOptionBools*>(m_config->option("wipe")->clone());
|
||||
for (int w = 0; w < wipe->values.size(); w++)
|
||||
wipe->values[w] = false;
|
||||
new_conf.set_key_value("wipe", wipe);
|
||||
}
|
||||
else {
|
||||
|
@ -1350,15 +1420,13 @@ void TabPrinter::update(){
|
|||
void Tab::load_current_preset()
|
||||
{
|
||||
auto preset = m_presets->get_edited_preset();
|
||||
// try{
|
||||
// local $SIG{ __WARN__ } = Slic3r::GUI::warning_catcher($self);
|
||||
preset.is_default ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true);
|
||||
update();
|
||||
// For the printer profile, generate the extruder pages.
|
||||
on_preset_loaded();
|
||||
// Reload preset pages with the new configuration values.
|
||||
reload_config();
|
||||
// };
|
||||
preset.is_default ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true);
|
||||
update();
|
||||
// For the printer profile, generate the extruder pages.
|
||||
on_preset_loaded();
|
||||
// Reload preset pages with the new configuration values.
|
||||
reload_config();
|
||||
|
||||
// use CallAfter because some field triggers schedule on_change calls using CallAfter,
|
||||
// and we don't want them to be called after this update_dirty() as they would mark the
|
||||
// preset dirty again
|
||||
|
@ -1369,6 +1437,12 @@ void Tab::load_current_preset()
|
|||
return;
|
||||
update_tab_ui();
|
||||
on_presets_changed();
|
||||
|
||||
if (name() == "print")
|
||||
update_frequently_changed_parameters();
|
||||
if (m_name == "printer")
|
||||
static_cast<TabPrinter*>(this)->m_initial_extruders_count = static_cast<TabPrinter*>(this)->m_extruders_count;
|
||||
update_changed_ui();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1737,7 +1811,7 @@ bool Page::set_value(t_config_option_key opt_key, boost::any value){
|
|||
ConfigOptionsGroupShp Page::new_optgroup(wxString title, int noncommon_label_width /*= -1*/)
|
||||
{
|
||||
//! config_ have to be "right"
|
||||
ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(this, title, m_config);
|
||||
ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(this, title, m_config, true);
|
||||
if (noncommon_label_width >= 0)
|
||||
optgroup->label_width = noncommon_label_width;
|
||||
|
||||
|
@ -1751,6 +1825,11 @@ ConfigOptionsGroupShp Page::new_optgroup(wxString title, int noncommon_label_wid
|
|||
//! });
|
||||
};
|
||||
|
||||
optgroup->m_get_initial_config = [this](){
|
||||
DynamicPrintConfig config = static_cast<Tab*>(GetParent())->m_presets->get_selected_preset().config;
|
||||
return config;
|
||||
};
|
||||
|
||||
vsizer()->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10);
|
||||
m_optgroups.push_back(optgroup);
|
||||
|
||||
|
|
|
@ -99,6 +99,7 @@ protected:
|
|||
bool m_no_controller;
|
||||
|
||||
std::vector<std::string> m_reload_dependent_tabs = {};
|
||||
std::vector<std::string> m_dirty_options = {};
|
||||
|
||||
// The two following two event IDs are generated at Plater.pm by calling Wx::NewEventType.
|
||||
wxEventType m_event_value_change = 0;
|
||||
|
@ -145,6 +146,7 @@ public:
|
|||
void toggle_show_hide_incompatible();
|
||||
void update_show_hide_incompatible_button();
|
||||
void update_ui_from_settings();
|
||||
void update_changed_ui();
|
||||
|
||||
PageShp add_options_page(wxString title, std::string icon, bool is_extruder_pages = false);
|
||||
|
||||
|
@ -171,6 +173,7 @@ public:
|
|||
|
||||
protected:
|
||||
void on_presets_changed();
|
||||
void update_frequently_changed_parameters();
|
||||
};
|
||||
|
||||
//Slic3r::GUI::Tab::Print;
|
||||
|
@ -211,23 +214,16 @@ public:
|
|||
//Slic3r::GUI::Tab::Printer;
|
||||
class TabPrinter : public Tab
|
||||
{
|
||||
bool m_is_disabled_button_browse;
|
||||
bool m_is_user_agent;
|
||||
// similar event by clicking Buttons "Browse" & "Test"
|
||||
wxEventType m_event_button_browse = 0;
|
||||
wxEventType m_event_button_test = 0;
|
||||
public:
|
||||
wxButton* m_serial_test_btn;
|
||||
wxButton* m_octoprint_host_test_btn;
|
||||
|
||||
size_t m_extruders_count;
|
||||
size_t m_initial_extruders_count;
|
||||
std::vector<PageShp> m_extruder_pages;
|
||||
|
||||
TabPrinter() {}
|
||||
TabPrinter(wxNotebook* parent, bool no_controller, bool is_disabled_btn_browse, bool is_user_agent) :
|
||||
Tab(parent, _(L("Printer Settings")), "printer", no_controller),
|
||||
m_is_disabled_button_browse(is_disabled_btn_browse),
|
||||
m_is_user_agent(is_user_agent) {}
|
||||
TabPrinter(wxNotebook* parent, bool no_controller) : Tab(parent, _(L("Printer Settings")), "printer", no_controller) {}
|
||||
~TabPrinter(){}
|
||||
|
||||
void build() override;
|
||||
|
@ -236,10 +232,6 @@ public:
|
|||
void extruders_count_changed(size_t extruders_count);
|
||||
void build_extruder_pages();
|
||||
void on_preset_loaded() override;
|
||||
|
||||
// Set the events to the callbacks posted to the main frame window (currently implemented in Perl).
|
||||
void set_event_button_browse(wxEventType evt) { m_event_button_browse = evt; }
|
||||
void set_event_button_test(wxEventType evt) { m_event_button_test = evt; }
|
||||
};
|
||||
|
||||
class SavePresetWindow :public wxDialog
|
||||
|
|
781
xs/src/slic3r/Utils/Bonjour.cpp
Normal file
781
xs/src/slic3r/Utils/Bonjour.cpp
Normal file
|
@ -0,0 +1,781 @@
|
|||
#include "Bonjour.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#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;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
// Minimal implementation of a MDNS/DNS-SD client
|
||||
// This implementation is extremely simple, only the bits that are useful
|
||||
// for basic MDNS discovery of OctoPi devices are present.
|
||||
// However, the bits that are present are implemented with security in mind.
|
||||
// Only fully correct DNS replies are allowed through.
|
||||
// While decoding the decoder will bail the moment it encounters anything fishy.
|
||||
// At least that's the idea. To help prove this is actually the case,
|
||||
// the implementations has been tested with AFL.
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream &os, const DnsMessage &msg)
|
||||
{
|
||||
os << "DnsMessage(ID: " << msg.header.id << ", "
|
||||
<< "Q: " << (msg.question ? msg.question->name.c_str() : "none") << ", "
|
||||
<< "A: " << (msg.rr_a ? msg.rr_a->ip.to_string() : "none") << ", "
|
||||
<< "AAAA: " << (msg.rr_aaaa ? msg.rr_aaaa->ip.to_string() : "none") << ", "
|
||||
<< "services: [";
|
||||
|
||||
enum { SRV_PRINT_MAX = 3 };
|
||||
unsigned i = 0;
|
||||
for (const auto &sdpair : msg.sdmap) {
|
||||
os << sdpair.first << ", ";
|
||||
|
||||
if (++i >= SRV_PRINT_MAX) {
|
||||
os << "...";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
os << "])";
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
unsigned retries;
|
||||
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);
|
||||
|
||||
std::string strip_service_dn(const std::string &service_name) const;
|
||||
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),
|
||||
retries(1),
|
||||
rq_id(0)
|
||||
{
|
||||
buffer.resize(DnsMessage::MAX_SIZE);
|
||||
}
|
||||
|
||||
std::string Bonjour::priv::strip_service_dn(const std::string &service_name) const
|
||||
{
|
||||
if (service_name.size() <= service_dn.size()) {
|
||||
return service_name;
|
||||
}
|
||||
|
||||
auto needle = service_name.rfind(service_dn);
|
||||
if (needle == service_name.size() - service_dn.size()) {
|
||||
return service_name.substr(0, needle - 1);
|
||||
} else {
|
||||
return service_name;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
auto service_name = strip_service_dn(sdpair.first);
|
||||
|
||||
std::string path;
|
||||
std::string version;
|
||||
|
||||
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) {
|
||||
path = std::move(value.substr(tag_path.size()));
|
||||
} else if (value.size() > tag_version.size() && value.compare(0, tag_version.size(), tag_version) == 0) {
|
||||
version = std::move(value.substr(tag_version.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BonjourReply reply(ip, srv.port, std::move(service_name), srv.hostname, std::move(path), std::move(version));
|
||||
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 expired = false;
|
||||
bool retry = false;
|
||||
asio::deadline_timer timer(io_service);
|
||||
retries--;
|
||||
std::function<void(const error_code &)> timer_handler = [&](const error_code &error) {
|
||||
if (retries == 0 || error) {
|
||||
expired = true;
|
||||
if (self->completefn) {
|
||||
self->completefn();
|
||||
}
|
||||
} else {
|
||||
retry = true;
|
||||
retries--;
|
||||
timer.expires_from_now(boost::posix_time::seconds(timeout));
|
||||
timer.async_wait(timer_handler);
|
||||
}
|
||||
};
|
||||
|
||||
timer.expires_from_now(boost::posix_time::seconds(timeout));
|
||||
timer.async_wait(timer_handler);
|
||||
|
||||
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 (expired) {
|
||||
socket.cancel();
|
||||
} else if (retry) {
|
||||
retry = false;
|
||||
socket.send_to(asio::buffer(brq->data), mcast);
|
||||
} 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, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version) :
|
||||
ip(std::move(ip)),
|
||||
port(port),
|
||||
service_name(std::move(service_name)),
|
||||
hostname(std::move(hostname)),
|
||||
path(path.empty() ? std::move(std::string("/")) : std::move(path)),
|
||||
version(version.empty() ? std::move(std::string("Unknown")) : std::move(version))
|
||||
{
|
||||
std::string proto;
|
||||
std::string port_suffix;
|
||||
if (port == 443) { proto = "https://"; }
|
||||
if (port != 443 && port != 80) { port_suffix = std::to_string(port).insert(0, 1, ':'); }
|
||||
if (this->path[0] != '/') { this->path.insert(0, 1, '/'); }
|
||||
full_address = proto + ip.to_string() + port_suffix;
|
||||
if (this->path != "/") { full_address += path; }
|
||||
}
|
||||
|
||||
bool BonjourReply::operator==(const BonjourReply &other) const
|
||||
{
|
||||
return this->full_address == other.full_address
|
||||
&& this->service_name == other.service_name;
|
||||
}
|
||||
|
||||
bool BonjourReply::operator<(const BonjourReply &other) const
|
||||
{
|
||||
if (this->ip != other.ip) {
|
||||
// So that the common case doesn't involve string comparison
|
||||
return this->ip < other.ip;
|
||||
} else {
|
||||
auto cmp = this->full_address.compare(other.full_address);
|
||||
return cmp != 0 ? cmp < 0 : this->service_name < other.service_name;
|
||||
}
|
||||
}
|
||||
|
||||
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::set_retries(unsigned retries)
|
||||
{
|
||||
if (p && retries > 0) { p->retries = retries; }
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
64
xs/src/slic3r/Utils/Bonjour.hpp
Normal file
64
xs/src/slic3r/Utils/Bonjour.hpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
#ifndef slic3r_Bonjour_hpp_
|
||||
#define slic3r_Bonjour_hpp_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
struct BonjourReply
|
||||
{
|
||||
boost::asio::ip::address ip;
|
||||
uint16_t port;
|
||||
std::string service_name;
|
||||
std::string hostname;
|
||||
std::string full_address;
|
||||
std::string path;
|
||||
std::string version;
|
||||
|
||||
BonjourReply() = delete;
|
||||
BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version);
|
||||
|
||||
bool operator==(const BonjourReply &other) const;
|
||||
bool operator<(const BonjourReply &other) const;
|
||||
};
|
||||
|
||||
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 &&)> ReplyFn;
|
||||
typedef std::function<void()> CompleteFn;
|
||||
|
||||
Bonjour(std::string service, std::string protocol = "tcp");
|
||||
Bonjour(Bonjour &&other);
|
||||
~Bonjour();
|
||||
|
||||
Bonjour& set_timeout(unsigned timeout);
|
||||
Bonjour& set_retries(unsigned retries);
|
||||
// ^ Note: By default there is 1 retry (meaning 1 broadcast is sent).
|
||||
// Timeout is per one retry, ie. total time spent listening = retries * timeout.
|
||||
// If retries > 1, then care needs to be taken as more than one reply from the same service may be received.
|
||||
|
||||
Bonjour& on_reply(ReplyFn fn);
|
||||
Bonjour& on_complete(CompleteFn fn);
|
||||
|
||||
Ptr lookup();
|
||||
private:
|
||||
std::unique_ptr<priv> p;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
301
xs/src/slic3r/Utils/Http.cpp
Normal file
301
xs/src/slic3r/Utils/Http.cpp
Normal file
|
@ -0,0 +1,301 @@
|
|||
#include "Http.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#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 bool ca_file_supported(::CURL *curl);
|
||||
static size_t writecb(void *data, size_t size, size_t nmemb, void *userp);
|
||||
std::string curl_error(CURLcode curlcode);
|
||||
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);
|
||||
}
|
||||
|
||||
bool Http::priv::ca_file_supported(::CURL *curl)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
bool res = false;
|
||||
#else
|
||||
bool res = true;
|
||||
#endif
|
||||
|
||||
if (curl == nullptr) { return res; }
|
||||
|
||||
#if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 48
|
||||
::curl_tlssessioninfo *tls;
|
||||
if (::curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &tls) == CURLE_OK) {
|
||||
if (tls->backend == CURLSSLBACKEND_SCHANNEL || tls->backend == CURLSSLBACKEND_DARWINSSL) {
|
||||
// With Windows and OS X native SSL support, cert files cannot be set
|
||||
res = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
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::curl_error(CURLcode curlcode)
|
||||
{
|
||||
return (boost::format("%1% (%2%)")
|
||||
% ::curl_easy_strerror(curlcode)
|
||||
% curlcode
|
||||
).str();
|
||||
}
|
||||
|
||||
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 = std::move(curl_error(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 && priv::ca_file_supported(p->curl)) {
|
||||
::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;
|
||||
}
|
||||
|
||||
bool Http::ca_file_supported()
|
||||
{
|
||||
::CURL *curl = ::curl_easy_init();
|
||||
bool res = priv::ca_file_supported(curl);
|
||||
if (curl != nullptr) { ::curl_easy_cleanup(curl); }
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
}
|
54
xs/src/slic3r/Utils/Http.hpp
Normal file
54
xs/src/slic3r/Utils/Http.hpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#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();
|
||||
|
||||
static bool ca_file_supported();
|
||||
private:
|
||||
Http(const std::string &url);
|
||||
|
||||
std::unique_ptr<priv> p;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
109
xs/src/slic3r/Utils/OctoPrint.cpp
Normal file
109
xs/src/slic3r/Utils/OctoPrint.cpp
Normal file
|
@ -0,0 +1,109 @@
|
|||
#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"))
|
||||
{}
|
||||
|
||||
bool OctoPrint::test(wxString &msg) const
|
||||
{
|
||||
// Since the request is performed synchronously here,
|
||||
// it is ok to refer to `msg` from within the closure
|
||||
|
||||
bool res = true;
|
||||
|
||||
auto url = std::move(make_url("api/version"));
|
||||
auto http = Http::get(std::move(url));
|
||||
set_auth(http);
|
||||
http.on_error([&](std::string, std::string error, unsigned status) {
|
||||
res = false;
|
||||
msg = 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 = wxWindow::FindWindowById(windowId);
|
||||
if (window == nullptr) { return; }
|
||||
|
||||
wxCommandEvent* evt = new wxCommandEvent(completeEvt);
|
||||
evt->SetString(_(L("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 = wxWindow::FindWindowById(windowId);
|
||||
if (window == nullptr) { return; }
|
||||
|
||||
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("%s: %s",
|
||||
_(L("Error while uploading to the OctoPrint server")),
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
wxString OctoPrint::format_error(std::string error, unsigned status)
|
||||
{
|
||||
const wxString wxerror = error;
|
||||
|
||||
if (status != 0) {
|
||||
return wxString::Format("HTTP %u: %s", status,
|
||||
(status == 401 ? _(L("Invalid API key")) : wxerror));
|
||||
} else {
|
||||
return std::move(wxerror);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
34
xs/src/slic3r/Utils/OctoPrint.hpp
Normal file
34
xs/src/slic3r/Utils/OctoPrint.hpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
#ifndef slic3r_OctoPrint_hpp_
|
||||
#define slic3r_OctoPrint_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
|
||||
class OctoPrint
|
||||
{
|
||||
public:
|
||||
OctoPrint(DynamicPrintConfig *config);
|
||||
|
||||
bool test(wxString &curl_msg) const;
|
||||
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 wxString format_error(std::string error, unsigned status);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -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
|
||||
|
|
|
@ -99,8 +99,8 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo
|
|||
$config->set_deserialize('fill_pattern', 'line');
|
||||
is $config->get('fill_pattern'), 'line', 'deserialize enum (fill_pattern)';
|
||||
|
||||
$config->set_deserialize('support_material_pattern', 'pillars');
|
||||
is $config->get('support_material_pattern'), 'pillars', 'deserialize enum (support_material_pattern)';
|
||||
$config->set_deserialize('support_material_pattern', 'rectilinear');
|
||||
is $config->get('support_material_pattern'), 'rectilinear', 'deserialize enum (support_material_pattern)';
|
||||
|
||||
$config->set('extruder_offset', [[10,20],[30,45]]);
|
||||
is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[10,20],[30,45]], 'set/get points';
|
||||
|
|
|
@ -35,14 +35,8 @@ void set_tab_panel(SV *ui)
|
|||
void add_debug_menu(SV *ui, int event_language_change)
|
||||
%code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_language_change); %};
|
||||
|
||||
void create_preset_tabs(PresetBundle *preset_bundle, bool no_controller,
|
||||
bool is_disabled_button_browse, bool is_user_agent,
|
||||
int event_value_change, int event_presets_changed,
|
||||
int event_button_browse, int event_button_test)
|
||||
%code%{ Slic3r::GUI::create_preset_tabs(preset_bundle, no_controller,
|
||||
is_disabled_button_browse, is_user_agent,
|
||||
event_value_change, event_presets_changed,
|
||||
event_button_browse, event_button_test); %};
|
||||
void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed)
|
||||
%code%{ Slic3r::GUI::create_preset_tabs(no_controller, event_value_change, event_presets_changed); %};
|
||||
|
||||
Ref<TabIface> get_preset_tab(char *name)
|
||||
%code%{ RETVAL=Slic3r::GUI::get_preset_tab_iface(name); %};
|
||||
|
@ -61,3 +55,11 @@ void set_app_config(AppConfig *app_config)
|
|||
|
||||
void open_preferences_dialog(int preferences_event)
|
||||
%code%{ Slic3r::GUI::open_preferences_dialog(preferences_event); %};
|
||||
|
||||
void set_preset_bundle(PresetBundle *preset_bundle)
|
||||
%code%{ Slic3r::GUI::set_preset_bundle(preset_bundle); %};
|
||||
|
||||
void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_sizer)
|
||||
%code%{ Slic3r::GUI::add_frequently_changed_parameters((wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"),
|
||||
(wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"),
|
||||
(wxFlexGridSizer*)wxPli_sv_2_object(aTHX_ ui_p_sizer, "Wx::FlexGridSizer")); %};
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
void enable() const;
|
||||
void disable() const;
|
||||
|
||||
int shader_program_id() const
|
||||
%code%{ RETVAL = THIS->shader_program_id; %};
|
||||
|
||||
std::string last_error() const
|
||||
%code%{ RETVAL = THIS->last_error; %};
|
||||
};
|
||||
|
@ -45,6 +48,9 @@
|
|||
int zoom_to_volumes()
|
||||
%code%{ RETVAL = THIS->zoom_to_volumes; %};
|
||||
|
||||
void set_layer_height_texture_data(unsigned int texture_id, unsigned int shader_id, PrintObject* print_object, float z_cursor_relative, float edit_band_width);
|
||||
void reset_layer_height_texture_data();
|
||||
|
||||
int object_idx() const;
|
||||
int volume_idx() const;
|
||||
int instance_idx() const;
|
||||
|
@ -97,6 +103,9 @@
|
|||
void finalize_geometry(bool use_VBOs);
|
||||
void release_geometry();
|
||||
|
||||
void set_print_box(float min_x, float min_y, float min_z, float max_x, float max_y, float max_z);
|
||||
void update_outside_state(DynamicPrintConfig* config, bool all_inside);
|
||||
|
||||
bool move_volume_up(int idx)
|
||||
%code%{
|
||||
if (idx > 0 && idx < int(THIS->volumes.size())) {
|
||||
|
@ -154,13 +163,6 @@ finalize_legend_texture()
|
|||
OUTPUT:
|
||||
RETVAL
|
||||
|
||||
unsigned int
|
||||
get_legend_texture_id()
|
||||
CODE:
|
||||
RETVAL = _3DScene::get_legend_texture_id();
|
||||
OUTPUT:
|
||||
RETVAL
|
||||
|
||||
unsigned int
|
||||
get_legend_texture_width()
|
||||
CODE:
|
||||
|
@ -179,7 +181,38 @@ void
|
|||
reset_legend_texture()
|
||||
CODE:
|
||||
_3DScene::reset_legend_texture();
|
||||
|
||||
|
||||
void
|
||||
generate_warning_texture(std::string msg)
|
||||
CODE:
|
||||
_3DScene::generate_warning_texture(msg);
|
||||
|
||||
unsigned int
|
||||
finalize_warning_texture()
|
||||
CODE:
|
||||
RETVAL = _3DScene::finalize_warning_texture();
|
||||
OUTPUT:
|
||||
RETVAL
|
||||
|
||||
unsigned int
|
||||
get_warning_texture_width()
|
||||
CODE:
|
||||
RETVAL = _3DScene::get_warning_texture_width();
|
||||
OUTPUT:
|
||||
RETVAL
|
||||
|
||||
unsigned int
|
||||
get_warning_texture_height()
|
||||
CODE:
|
||||
RETVAL = _3DScene::get_warning_texture_height();
|
||||
OUTPUT:
|
||||
RETVAL
|
||||
|
||||
void
|
||||
reset_warning_texture()
|
||||
CODE:
|
||||
_3DScene::reset_warning_texture();
|
||||
|
||||
void
|
||||
_load_print_toolpaths(print, volumes, tool_colors, use_VBOs)
|
||||
Print *print;
|
||||
|
|
|
@ -99,6 +99,9 @@
|
|||
|
||||
void print_info() const;
|
||||
|
||||
bool fits_print_volume(DynamicPrintConfig* config) const
|
||||
%code%{ RETVAL = THIS->fits_print_volume(config); %};
|
||||
|
||||
bool store_stl(char *path, bool binary)
|
||||
%code%{ TriangleMesh mesh = THIS->mesh(); RETVAL = Slic3r::store_stl(path, &mesh, binary); %};
|
||||
bool store_amf(char *path, Print* print)
|
||||
|
@ -361,9 +364,9 @@ ModelMaterial::attributes()
|
|||
%code%{ RETVAL = &THIS->offset; %};
|
||||
|
||||
void set_rotation(double val)
|
||||
%code%{ THIS->rotation = val; %};
|
||||
%code%{ THIS->rotation = val; THIS->get_object()->invalidate_bounding_box(); %};
|
||||
void set_scaling_factor(double val)
|
||||
%code%{ THIS->scaling_factor = val; %};
|
||||
%code%{ THIS->scaling_factor = val; THIS->get_object()->invalidate_bounding_box(); %};
|
||||
void set_offset(Pointf *offset)
|
||||
%code%{ THIS->offset = *offset; %};
|
||||
|
||||
|
|
13
xs/xsp/Utils_OctoPrint.xsp
Normal file
13
xs/xsp/Utils_OctoPrint.xsp
Normal file
|
@ -0,0 +1,13 @@
|
|||
%module{Slic3r::XS};
|
||||
|
||||
%{
|
||||
#include <xsinit.h>
|
||||
#include "slic3r/Utils/OctoPrint.hpp"
|
||||
%}
|
||||
|
||||
%name{Slic3r::OctoPrint} class OctoPrint {
|
||||
OctoPrint(DynamicPrintConfig *config);
|
||||
~OctoPrint();
|
||||
|
||||
void send_gcode(int windowId, int completeEvt, int errorEvt, std::string filename, bool print = false) const;
|
||||
};
|
|
@ -236,6 +236,10 @@ Ref<PresetHints> O_OBJECT_SLIC3R_T
|
|||
TabIface* O_OBJECT_SLIC3R
|
||||
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
|
||||
ExtrusionLoopRole T_UV
|
||||
ExtrusionRole T_UV
|
||||
|
|
Loading…
Add table
Reference in a new issue